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 相关文章推荐
Python中在for循环中嵌套使用if和else语句的技巧
Jun 20 Python
Python实现使用卷积提取图片轮廓功能示例
May 12 Python
python3基于TCP实现CS架构文件传输
Jul 28 Python
Selenium(Python web测试工具)基本用法详解
Aug 10 Python
Python中函数的基本定义与调用及内置函数详解
May 13 Python
Python3+Pycharm+PyQt5环境搭建步骤图文详解
May 29 Python
浅谈Django+Gunicorn+Nginx部署之路
Sep 11 Python
Flask框架 CSRF 保护实现方法详解
Oct 30 Python
使用Python给头像加上圣诞帽或圣诞老人小图标附源码
Dec 25 Python
解决Opencv+Python cv2.imshow闪退问题
Apr 24 Python
python实现过滤敏感词
May 08 Python
使用qt quick-ListView仿微信好友列表和聊天列表的示例代码
Jun 13 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中使用Imagick实现各种图片效果实例
2015/01/21 PHP
php基本函数汇总
2015/07/09 PHP
使用PHP如何实现高效安全的ftp服务器(二)
2015/12/30 PHP
PHP基本语法实例总结
2016/09/09 PHP
基于PHP实现短信验证码发送次数限制
2020/07/11 PHP
通过JAVASCRIPT读取ASP设定的COOKIE
2006/11/24 Javascript
详解强大的jQuery选择器之基本选择器、层次选择器
2012/02/07 Javascript
javascript五图轮播切换实用版
2012/08/17 Javascript
JavaScript在for循环中绑定事件解决事件参数不同的情况
2014/01/20 Javascript
js对象转json数组的简单实现案例
2014/02/28 Javascript
JavaScript变量声明详解
2014/11/27 Javascript
js实现禁止中文输入的方法
2015/01/14 Javascript
JavaScript数据类型判定的总结笔记
2015/07/31 Javascript
浅析Node.js实现HTTP文件下载
2016/08/05 Javascript
js实现把图片的绝对路径转为base64字符串、blob对象再上传
2016/12/29 Javascript
vue计算属性和监听器实例解析
2018/05/10 Javascript
React Native基础入门之调试React Native应用的一小步
2018/07/02 Javascript
详解Vue Elementui中的Tag与页面其它元素相互交互的两三事
2018/09/25 Javascript
浅谈微信页面入口文件被缓存解决方案
2018/09/29 Javascript
vue实现的组件兄弟间通信功能示例
2018/12/04 Javascript
jquery分页优化操作实例分析
2019/08/23 jQuery
js抽奖转盘实现方法分析
2020/05/16 Javascript
python比较2个xml内容的方法
2015/05/11 Python
基于python socketserver框架全面解析
2017/09/21 Python
Python实现识别手写数字 简易图片存储管理系统
2018/01/29 Python
利用Python实现Excel的文件间的数据匹配功能
2020/06/16 Python
利用纯CSS3实现tab选项卡切换示例代码
2016/09/21 HTML / CSS
HTML5实现WebSocket协议原理浅析
2014/07/07 HTML / CSS
淘宝客服自我总结鉴定
2014/01/25 职场文书
争论的故事教学反思
2014/02/06 职场文书
通信工程专业求职信
2014/06/04 职场文书
机关党员公开承诺书
2014/08/30 职场文书
离婚协议书怎么写(范本参考)
2014/09/30 职场文书
校园广播稿精选
2014/10/01 职场文书
初三英语教学计划
2015/01/23 职场文书
OpenCV实现常见的四种图像几何变换
2022/04/01 Python