之前一段时间读到了这篇博客,其中描述了作者如何用java实现国外著名音乐搜索工具shazam的基本功能。其中所提到的文章又将我引向了关于shazam的一篇论文另外一篇博客。读完之后发现其中的原理并不十分复杂,但是方法对噪音的健壮性却非常好,出于好奇决定自己用python自己实现了一个简单的音乐搜索工具—— Song Finder, 它的核心功能被封装在SFEngine 中,第三方依赖方面只使用到了 scipy

工具demo

这个demo在ipython下展示工具的使用,本项目名称为Song Finder,我把索引、搜索的功能全部封装在Song Finder中的SFEngine中。首先是简单的准备工作:

In [1]: from SFEngine import *

In [2]: engine = SFEngine()

在这之后我们对现有歌曲进行索引,我在original目录下准备了几十首歌曲(.wav文件)作为曲库:

In [3]: engine.index('original') # 索引该目录下的所有歌曲

在完成索引之后我们向Song Finder提交一段有背景噪音的歌曲录音进行搜索。对于这段《枫》在1分15秒左右的录音:

工具的返回结果是:

In [4]: engine.search('record/record0.wav')
original/周杰伦-枫 73
original/周杰伦-枫 31
original/周杰伦-枫 10
original/周杰伦-枫 28
original/我要快樂 - 張惠妹 28

其中展示的分别是歌曲名称及片段在歌曲中出现的位置(以秒计),可以看到工具正确找回了歌曲的曲名,也找到了其在歌曲中的正确位置。

而对于这段《童话》在1分05秒左右的背景噪音更加嘈杂的录音:

工具的返回结果是:

In [5]: engine.search('record/record8.wav')
original/光良 - 童话 67
original/光良 - 童话 39
original/光良 - 童话 33
original/光良 - 童话 135
original/光良 - 童话 69

可以看到尽管噪音非常嘈杂,但是工具仍然能成功识别所对应的歌曲并对应到歌曲的正确位置,说明工具在噪音较大的环境下有良好的健壮性!

项目主页: Github

Song Finder原理

给定曲库对一个录音片段进行检索是一个不折不扣的搜索问题,但是对音频的搜索并不像对文档、数据的搜索那么直接。为了完成对音乐的搜索,工具需要完成下列3个任务:

  • 对曲库中的所有歌曲抽取特征
  • 以相同的方式对录音片段提取特征
  • 根据录音片段的特征对曲库进行搜索,返回最相似的歌曲及其在歌曲中的位置

特征提取?离散傅立叶变换!

为了对音乐(音频)提取特征,一个很直接的想法是得到音乐的音高的信息,而音高在物理上对应的则又是波的频率信息。为了获取这类信息,一个非常直接的额做法是使用离散傅叶变化对声音进行分析,即使用一个滑动窗口对声音进行采样,对窗口内的数据进行离散傅立叶变化,将时间域上的信息变换为频率域上的信息,使用scipy的接口可以很轻松的完成。在这之后我们将频率分段,提取每频率中振幅最大的频率:

def extract_feature(self, scaled, start, interval):
end = start + interval
dst = fft(scaled[start: end])
length = len(dst)/2
normalized = abs(dst[:(length-1)])
feature = [ normalized[:50].argmax(), \
50 + normalized[50:100].argmax(), \
100 + normalized[100:200].argmax(), \
200 + normalized[200:300].argmax(), \
300 + normalized[300:400].argmax(), \
400 + normalized[400:].argmax()]
return feature

这样,对于一个滑动窗口,我提取到了6个频率作为其特征。对于整段音频,我们重复调用这个函数进行特征抽取:

def sample(self, filename, start_second, duration = 5, callback = None):

    start = start_second * 44100
if duration == 0:
end = 1e15
else:
end = start + 44100 * duration
interval = 8192
scaled = self.read_and_scale(filename)
length = scaled.size
while start < min(length, end):
feature = self.extract_feature(scaled, start, interval)
if callback != None:
callback(filename, start, feature)
start += interval

其中44100为音频文件自身的采样频率,8192是我设定的取样窗口(对,这样hardcode是很不对的),callback是一个传入的函数,需要这个参数是因为在不同场景下对于所得到的特征会有不同的后续操作。

匹配曲库

在得到歌曲、录音的大量特征后,如何进行高效搜索是一个问题。一个有效的做法是建立一个特殊的哈希表,其中的key是频率,其对应的value是一系列(曲名,时间)的tuple,其记录的是某一歌曲在某一时间出现了某一特征频率,但是以频率为key而非以曲名或时间为key。

表格。。

这样做的好处是,当在录音中提取到某一个特征频率时,我们可以从这个哈希表中找出与该特征频率相关的歌曲及时间!

当然有了这个哈希表还不够用,我们不可能把所有与特征频率相关的歌曲都抽出来,看看谁命中的次数多,因为这样会完全无视歌曲的时序信息,并引入一些错误的匹配。

我们的做法是,对于录音中在t时间点的一个特征频率f,从曲库找出所有与f相关的(曲名,时间)tuple,例如我们得到了

[(s1, t1), (s2, t2), (s3, t3)]

我们使用时间进行对齐,得到这个列表

[(s1, t1-t), (s2, t2-t), (s3, t3-t)]

记为

[(s1, t1`), (s2, t2`), (s3, t3`)]

我们对所有时间点的所有特征频率均做上述操作,得到了一个大列表:

[(s1, t1`), (s2, t2`), (s3, t3`), ..., (sn, tn`)]

对这个列表进行计数,可以看到哪首歌曲的哪个时间点命中的次数最多,并将命中次数最多的(曲名,时间)对返回给用户。

不足

这个小工具是一个几个小时写成的hack,有许都地方需要改进,例如:

  • 目前只支持了wav格式的曲库及录音
  • 所有数据都放在内存中,曲库体积增大时需要引入更好的后端存储
  • 索引应该并行化,匹配也应该并行化,匹配的模型其实是典型的map-reduce。

项目主页

Github

90 行 Python 搭一个音乐搜索工具的更多相关文章

  1. 听歌识曲--用python实现一个音乐检索器

    听歌识曲,顾名思义,用设备"听"歌曲,然后它要告诉你这是首什么歌.而且十之八九它还得把这首歌给你播放出来.这样的功能在QQ音乐等应用上早就出现了.我们今天来自己动手做一个自己的听歌 ...

  2. 用python搭一个超简易的文件服务器

    这个文件服务器纯粹是在学习python cgi编程时,顺便玩玩而已,因为搭文件服务器的话完全可以linux,简单方便,这里就是随便玩玩,功能也就是只能下载文件 1.登录页面,做个简单验证 新建一个ht ...

  3. Python打造一个目录扫描工具

    目标:用Python3写一款小型的web目录扫描工具 功能:1.扫描指定站点 2.指定网站脚本类型来扫描 3.可控线程 4.可保存扫描结果 首先定义一个命令参数的函数 def parse_option ...

  4. Python编程-一个小爬虫工具的实现过程

    需求描述: 1,打开网站: 2,获取网站的文件内容: 3,返回保存到文件中: 这里的就用到了多线程的方法 import requests,threading,time def write_html(u ...

  5. 用Python手把手教你搭一个Transformer!

    来源商业新知网,原标题:百闻不如一码!手把手教你用Python搭一个Transformer 与基于RNN的方法相比,Transformer 不需要循环,主要是由Attention 机制组成,因而可以充 ...

  6. Everything:速度最快的文件名搜索工具(Linux版本) 转

    Everything是windows的一个快速搜索工具. 基本上转移到Linux上来后,没有怎么用过. 一直在用Gnome-Do,感觉还可以. 这个程序只是用来练习wxPython用的,目前还只是一个 ...

  7. Linux 命令行下搜索工具大盘点,效率提高不止一倍!

    在 Linux 命令行下进行文本关键字的搜索,大家肯定第一时间会想到 grep 命令.grep 命令确实十分强大,但如果需要用到它更加灵活的功能时,可能命令就会显得十分复杂. 于是,为了简化 grep ...

  8. 用Python做一个简单的翻译工具

    编程本身是跟年龄无关的一件事,不论你现在是十四五岁,还是四五十岁,如果你热爱它,并且愿意持续投入其中,必定会有所收获. 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过 ...

  9. 一个 11 行 Python 代码实现的神经网络

    一个 11 行 Python 代码实现的神经网络 2015/12/02 · 实践项目 · 15 评论· 神经网络 分享到:18 本文由 伯乐在线 - 耶鲁怕冷 翻译,Namco 校稿.未经许可,禁止转 ...

随机推荐

  1. java开发过程中,报错Dangling meta character '*' near index 0,解决办法

    1.split方法转化字符串为数组: String[] strPicArr = map.get("hw_pic").toString().split("*"); ...

  2. Notes on Operating System

  3. linux 环境统配

    #java JAVA_HOME=/opt/jdk CLASSPATH=$JAVA_HOME/lib/ PATH=$PATH:$JAVA_HOME/bin export PATH JAVA_HOME C ...

  4. python_redis简介与安装和使用

    一.简介 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted ...

  5. react-eslintrc

    { "extends": ["eslint:recommended"], "plugins": [ "react" ], ...

  6. js操作bom和dom

    Bom 概念 BOM : Browser Object Model 浏览器对象模型,描述与浏览器进行交互的方法和接 口, ECMAscript是javascript的核心,但如果要在web中使用jav ...

  7. Senparc.Weixin SDK v5.0 升级公告

    经过五年半的持续维护,Senparc.Weixin SDK 逐步丰满和完善,在升级的过程中,我们为基础库(Senparc.Weixin.dll)加入了许多通用的功能,例如加密/解密算法.通用缓存方法等 ...

  8. python写的翻译代码

    # -*- coding: utf-8 -*- from Tkinter import *import difflibimport urllib2import urllib # python2.7才需 ...

  9. 开源网盘云存储 Seafile

    摘要: Seafile 是一款安全.高性能的开源网盘(云存储)软件.Seafile 提供了主流网盘(云盘)产品所具有的功能,包括文件同步.文件共享等.在此基础上,Seafile 还提供了高级的安全保护 ...

  10. Docker学习笔记-Redis 安装

    拉取官方的镜像 docker pull redis:3.2 查看 docker images redis 运行容器 docker run -p 6379:6379 -v $PWD/data:/data ...