Python通过90行代码搭建一个音乐搜索工具


Posted in Python onJuly 29, 2015

下面小编把具体实现代码给大家分享如下:

之前一段时间读到了这篇博客,其中描述了作者如何用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。

以上就是Python通过90行代码搭建音乐搜索工具,希望大家喜欢。

Python 相关文章推荐
web.py在模板中输出美元符号的方法
Aug 26 Python
介绍Python中内置的itertools模块
Apr 29 Python
python中的代码编码格式转换问题
Jun 10 Python
Python内置的HTTP协议服务器SimpleHTTPServer使用指南
Mar 30 Python
Jupyter notebook远程访问服务器的方法
May 24 Python
Python中偏函数用法示例
Jun 07 Python
对python PLT中的image和skimage处理图片方法详解
Jan 10 Python
python2和python3在处理字符串上的区别详解
May 29 Python
Python imageio读取视频并进行编解码详解
Dec 10 Python
Python使用Tkinter实现转盘抽奖器的步骤详解
Jan 06 Python
pycharm配置python 设置pip安装源为豆瓣源
Feb 05 Python
OpenCV-Python直方图均衡化实现图像去雾
Jun 07 Python
Python的迭代器和生成器
Jul 29 #Python
在Python程序中操作MySQL的基本方法
Jul 29 #Python
Python操作Word批量生成文章的方法
Jul 28 #Python
Python实现批量转换文件编码的方法
Jul 28 #Python
Python中subprocess的简单使用示例
Jul 28 #Python
Python中文竖排显示的方法
Jul 28 #Python
Python中的getopt函数使用详解
Jul 28 #Python
You might like
php 上传功能实例代码
2010/04/13 PHP
Yii操作数据库的3种方法
2014/03/11 PHP
php天翼开放平台短信发送接口实现方法
2014/12/22 PHP
PHP实现Huffman编码/解码的示例代码
2018/04/20 PHP
PHP+fiddler抓包采集微信文章阅读数点赞数的思路详解
2019/12/20 PHP
记Laravel调用Gin接口调用formData上传文件的实现方法
2019/12/12 PHP
经常用的图片在容器中的水平垂直居中实例
2007/06/10 Javascript
jQuery 事件队列调整方法
2009/09/18 Javascript
关于jquery input textare 事件绑定及用法学习
2013/04/03 Javascript
鼠标移入移出事件改变图片的分辨率的两种方法
2013/12/17 Javascript
JavaScript中实现异步编程模式的4种方法
2014/09/24 Javascript
jquery插件validation实现验证身份证号等
2015/06/04 Javascript
javascript中window.open在原来的窗口中打开新的窗口(不同名)
2015/11/15 Javascript
jQuery获取某天的农历日期并判断是否除夕或新年的方法
2016/03/01 Javascript
深入理解js promise chain
2016/05/05 Javascript
微信开发之调起摄像头、本地展示图片、上传下载图片实例
2016/12/08 Javascript
jquery validation验证表单插件
2017/01/07 Javascript
Vue数据驱动模拟实现3
2017/01/11 Javascript
React 无状态组件(Stateless Component) 与高阶组件
2018/08/14 Javascript
vue使用高德地图点击下钻上浮效果的实现思路
2019/10/12 Javascript
[01:06:54]DOTA2-DPC中国联赛 正赛 SAG vs DLG BO3 第二场 2月28日
2021/03/11 DOTA
Python 解析xml文件的示例
2020/09/29 Python
python 输入字符串生成所有有效的IP地址(LeetCode 93号题)
2020/10/15 Python
浅谈Selenium+Webdriver 常用的元素定位方式
2021/01/13 Python
浅谈CSS3中display属性的Flex布局的方法
2017/08/14 HTML / CSS
Molton Brown美国官网:奢华美容、香水、沐浴和身体护理
2020/09/02 全球购物
密封类可以有虚函数吗
2014/08/11 面试题
工商学院毕业生个人自我评价
2013/09/19 职场文书
国际商务系学生个人的自我评价
2013/11/26 职场文书
成龙霸王洗发水广告词
2014/03/14 职场文书
开展批评与自我批评发言材料
2014/05/15 职场文书
活动宣传策划方案
2014/05/23 职场文书
金融专业毕业生自荐信
2014/06/26 职场文书
小学校园广播稿集锦
2014/10/04 职场文书
房屋转让协议书(标准范本)
2016/03/21 职场文书
MySQL kill不掉线程的原因
2021/05/07 MySQL