微信号:DianrongMafia

介绍:点融黑帮——一个充满激情和梦想的技术团队,吸引了来自金融及信息科技领域的顶尖人才.我们正在用技术创新改变传统金融.

python音频库dejavu原理浅析

2017-10-08 19:00 赵宁博




dejavu库是利用python开发的音频指纹提取与识别库,用来实现听歌识曲的功能。

由于近一段时间正在学习音频分析方面的知识。


古人有云:要想快速掌握知识,就要学会站在巨人的肩膀上。


因此特意研究了一下dejavu的源码。

这篇文章主要是记录学习的过程以及库的核心方法,权当做读书笔记。

关于库的使用方法,本篇不做进一步说明,作者已进行了详细介绍,需要说明的是库的开发者目前已不再维护该库。


1

前言


在音频分析中,最简单的是时域分析,音频信号的时域分析是指对声音信号幅值随时间变化曲线进行分析。利用pydub读取“我.mp3”的文件(音频的内容只有一个字为“我”),将其波形画出如下图,横轴为时间,纵轴为音频的幅值。从图中可以看出,在时域内只能分析声音信号的强弱,从波形中很难看出频率的变化,无法对不同的音频信号做出有效的区分。




音频信号分析的另外一种方法是频域分析,具体来说就是借助傅里叶变化将原始信号从时域空间转换至频域空间,揭示出构成音频信号的不同频率成分。下图为“我.mp3”文件的频谱图,从中可以看出音频信号的频率分布,这种分析方法可以有效的区分不同的音频文件。但是频谱分析无法反映音频信号的时间信息,只能提供全局的频率信息,不能提供某一时刻的频率信息,只能用于稳态信号的分析,不能用来进行时变信号的分析,单纯的利用频谱分析无法达到听歌识曲的目的。


下面介绍声音的时频分析,获取时频信息最常用的方法是短时傅里叶变换,也是dejavu库所采用的方法。短时傅里叶变换的原理如下,在计算音频文件的频率信息时,不同于频域分析计算整段音频文件的频率信息,短时傅里叶变换方法会对音频文件进行加窗操作,选择一个较短的窗函数对音频信号进行截断,利用快速傅里叶变换计算该窗口内的信号的频率信息,然后移动窗函数,以得到音频文件不同时刻内的频率信息。这样就得到了声音信号不同时刻的频率分布。


dejavu库会读取音频文件,利用时频分析的方法得到不同时刻的频率分布,然后按照一定的算法将音频的指纹信息从音频文件的时频信息中提取出来。通过指纹信息来识别和区分声音文件,每个音频文件都有其单独的指纹库,比对指纹库可以根据声音片段以识别出整个音频文件,以达到听歌识曲的目的。


2

音频内容的读取


通过fingerprint_file()方法进行音频的读入,该方法返回音频的各个通道的raw_data、frame_rate以及文件的unique_hash(该方法通过获取文件MD5来对文件进行标记,主要是为了后续的文件去重)。在read()方法内引入了两个外部库来进行音频文件的读取,一个是pydub(基于ffmpeg),一个是wavio,这两个库都是用来进行对音频的读取,作者在源码内注释


Reads any file supportedby pydub (ffmpeg) and returns the data contained within.If filereading fails due to input being a 24-bit wav file, wavio is used asa backup.

pydub在读取24位wav文件时可能会出错,因此使用wavio来进行wav文件处理。另外在读取文件时还提供另外一种方法fingerprint_directory()以支持文件的批量读取。


3

音频指纹提取


首先介绍一下本库所利用的指纹提取的方法,在读取完音频文件之后,会利用短时傅里叶转化对音频文件的各个通道的raw_data进行转换,以获取时频信息,通过指定的算子进行过滤,获取时频信息内突出的点,时频图如下(获取颜色相较周围重的几个点)(该图片取自github)。通过比较获取同一时间上突出点频率最大的峰值点,然后求出每个峰值点及其后面相邻的15个峰值点的时间差,并将相应的峰值点和时间差哈希化,这样就完成音频文件的指纹提取。



指纹提取的方法在fingerprint内的fingerprint方法内,利用matplotlib内的specgram方法获取频率的信息,之前对于音频分析方面并不太熟悉,通过对这个库的学习算是初识音频分析。下面做一个简要的说明,在第一步内获取了音频的raw_data,这是音频的时间与音频幅度(也就是声音大小)的数值列表,区分声音不同的指标主要是声音的频率,因此为了进行音频指纹的提取,需要获取音频的时间与频率的对应值。使用specgram方法获得的频率是由一个二维列表组成的列表,列表的长度代表着音频的时间长度单位为ms,二维列表内单个列表是各个时间点的频率构成,每个时间点的声音是由多个不同频率的波组合而成。


获取时频信息之后,在fingerprint内的get_2D_peak方法就是对其进行过滤并提取特征值,使用了scipy内的max_filter方法进行频率峰值的筛选,在进行过滤时选择的原始算子如下,中间一行和中间一列全部为true,然后逐次上下递减,呈对称金子塔状。

[0,1, 0]

[1,1, 1]

[0,1, 0]

该库采用的算子长度为41*41,代码如下:

struct= generate_binary_structure(2, 1)
neighborhood= iterate_structure(struct, 20)

获取到所有的突出点之后,通过比较获取同一时间上突出点频率最大的峰值点,下图为“我.mp3”所提取出来的突出点以及峰值点,其中红点为按照上述算子所过滤出来的突出点,蓝点为各个时间点内所提取出来的峰值点,图示如下。

在dejavu库的源码当中,图标显示的方法有一些错误,目前已经pullrequest了,但是项目已经没人维护了,修复见这里(https://github.com/worldveil/dejavu/pull/107)



在提取出所有的峰值点后(图中蓝点),计算出和后续相邻的的十五个峰值点的时间差(峰值点的数目可以可以根据自己的需求进行更改),然后利用generate_hashes对相应的峰值点和时间差进行hash化处理。generate_hashes函数如下:

for i in range(len(peaks)): 
        for j in range(1, fan_value):   
            if (i + j) < len(peaks):

                freq1 = peaks[i][IDX_FREQ_I]
                freq2 = peaks[i + j][IDX_FREQ_I]
                t1 = peaks[i][IDX_TIME_J]
                t2 = peaks[i + j][IDX_TIME_J]
                t_delta = t2 - t1
                if t_delta >= MIN_HASH_TIME_DELTA and t_delta <= MAX_HASH_TIME_DELTA:
                    h = hashlib.sha1(
                        "%s|%s|%s" % (str(freq1), str(freq2), str(t_delta)))
                    yield (h.hexdigest()[0:FINGERPRINT_REDUCTION], t1)

至此,整个音频文件的指纹信息已经提取出来了,在整个提取过程中,最重要的是特征点的提取。在不同的指纹提取方法中,特征点的提取方法也不相同,文章末尾所推荐的parper中有介绍另外的方法,有兴趣的同学可以看看。


4

存储与比对


指纹提取完毕后,会将提取到的指纹存储至数据库内,默认存储为Mysql,database类为数据库的抽象类,里面定义了必须重写的一些方法,database_sql为具体的实现。这方面不做过多描述。

通过麦克风或者硬盘读取完一段音频文件后,会提取出这段音频文件的所有指纹,然后与数据库内的指纹库进行比对,将匹配成功的音频信息返回。


5

总结


音频识别最主要的是对于音频特征值的提取分析,难点也在这里。由于本人是音频分析的新手,通过阅读这些源码似的我对音频方面的分析方法和流程的学习更加深入,因此写下这篇文章以示记录。下面是我在进行学习过程中读到的几篇parper,很不错,同样推荐给大家。


Shazam

(http://www.ee.columbia.edu/~dpwe/papers/Wang03-shazam.pdf)

基于哈希的音频指纹提取算法的研究

(http://gb.oversea.cnki.net/KCMS/detail/detail.aspx?filename=1016092927.nh&dbcode=CMFD&dbname=CMFD2017)





点击回顾往期精彩内容

那些工程师们玩的聚会桌游

从Java看字符编码

浅析Spring AOP

香港金融科技周即将开启,点融成为钻石赞助商

Vue世界中的Redux——Vuex

点融荣登“中国网贷先锋榜”

Affordance杂谈

浅谈金融类APP测试

今天我们来谈谈信息收集这件小事

用Django编写后端任务流程

Webpack优化 | 快一点,再快一点

活动回顾 | CTDC2017首席技术官领袖峰会成功举办

今天我们来聊一聊运动这件事。

CSP与并发编程

使用 Mesos 管理 Docker 实践

来共享密钥吧

浅谈交易手续费设计#1

多Header or Footer的RecyclerViewAdapter

浅谈 golang channel



想了解更多请关注我们



 
点融黑帮 更多文章 视错觉与UI元素间的可能 他曾经是个王者... 今天我们来聊聊公司估值 中秋特辑|月是故乡月,人是异乡人 那些工程师们玩的聚会桌游
猜您喜欢 Gartner:混合云还需2到5年才能成为主流 PyTips 0x19 - Python 类与元类的深度挖掘 II 通用技术丨程序员找女朋友的技术攻略之实战! Dev Daily: 使用Azure Marketplace丰富你的应用 细说云管理平台系列(二):三大建设误区