Dejavu 是音频指纹和识别的算法的一个 Python实现。Dejavu可以通过听一次,就记住音频然后指纹识别它。通过播放歌曲和录音麦克风输入,dejavu会尝试匹配保存在数据库中的指纹音频,识别歌曲。
ffmpeg用来把.mp3转换为.wav格式
$ sudo apt install ffmpeg
pydub是ffmpeg的Python封装
$ sudo pip install pydub
portaudio是一个免费、跨平台、开源的音频I/O库。PortAudio操作的I/O不是文件,而是音频设备。pyaudio需要用到这个库
$ sudo apt install portaudio19-dev
pyaudio用来从麦克风捕获音频
$ sudo pip install pyaudio
numpy负责对音频信号执行快速傅氏变换
$ sudo pip install numpy
scipy用在执行寻峰算法(peak finding)
$ sudo pip install scipy
matplotlib用来绘图
$ sudo apt install python-matplotlib
MySQLdb操作MySQL数据库
$ sudo apt install python-mysqldb mysql-server
在安装MySQL-server时需要设置MySQL root用户密码。
安装PyDejavu:
$ sudo pip install PyDejavu
PyDejavu这个包好像没有python3版本的,安装之后里面的内容,需要修改,import fingerprint会找不到,改成这个import dejavu.fingerprint as fingerprint
配置数据库
创建一个数据库用来存储音频指纹:
$ mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 5
Server version: 5.7.12-0ubuntu1.1 (Ubuntu)
Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> CREATE DATABASE IF NOT EXISTS dejavu;
Query OK, 1 row affected (0.00 sec)
mysql> \q
从一堆mp3文件中生成对应的指纹,然后保存到数据库:
genFinger.py:
from dejavu import Dejavu
config = {
"database": {
"host": "127.0.0.1",
"user": "root",
"passwd": "test1234",
"db": "dejavu",
},
"database_type" : "mysql",
}
djv = Dejavu(config)
djv.fingerprint_directory("/home/snail/Music", [".mp3"])
print(djv.db.get_num_fingerprints())
识别歌曲:pgetSongName.py
from dejavu import Dejavu
from dejavu.recognize import FileRecognizer, MicrophoneRecognizer
config = {
"database": {
"host": "127.0.0.1",
"user": "root",
"passwd": "test1234",
"db": "dejavu",
},
"database_type" : "mysql"
}
djv = Dejavu(config)
# 识别文件
song = djv.recognize(FileRecognizer, "/home/snail/Music/Crystals.mp3")
print(song)
# 识别从mic输入的音频
secs = 5
song = djv.recognize(MicrophoneRecognizer, seconds=secs)
if song is None:
print "No Match"
else:
print(song)
关于库的使用方法,本篇不做进一步说明,作者已进行了详细介绍,需要说明的是库的开发者目前已不再维护该库。
在音频分析中,最简单的是时域分析,音频信号的时域分析是指对声音信号幅值随时间变化曲线进行分析。利用pydub读取“我.mp3”的文件(音频的内容只有一个字为“我”),将其波形画出如下图,横轴为时间,纵轴为音频的幅值。从图中可以看出,在时域内只能分析声音信号的强弱,从波形中很难看出频率的变化,无法对不同的音频信号做出有效的区分。
音频信号分析的另外一种方法是频域分析,具体来说就是借助傅里叶变化将原始信号从时域空间转换至频域空间,揭示出构成音频信号的不同频率成分。下图为“我.mp3”文件的频谱图,从中可以看出音频信号的频率分布,这种分析方法可以有效的区分不同的音频文件。但是频谱分析无法反映音频信号的时间信息,只能提供全局的频率信息,不能提供某一时刻的频率信息,只能用于稳态信号的分析,不能用来进行时变信号的分析,单纯的利用频谱分析无法达到听歌识曲的目的。
下面介绍声音的时频分析,获取时频信息最常用的方法是短时傅里叶变换,也是dejavu库所采用的方法。短时傅里叶变换的原理如下,**在计算音频文件的频率信息时,不同于频域分析计算整段音频文件的频率信息,短时傅里叶变换方法会对音频文件进行加窗操作,选择一个较短的窗函数对音频信号进行截断,利用快速傅里叶变换计算该窗口内的信号的频率信息,然后移动窗函数,以得到音频文件不同时刻内的频率信息。**这样就得到了声音信号不同时刻的频率分布。
dejavu库会读取音频文件,利用时频分析的方法得到不同时刻的频率分布,然后按照一定的算法将音频的指纹信息从音频文件的时频信息中提取出来。通过指纹信息来识别和区分声音文件,每个音频文件都有其单独的指纹库,比对指纹库可以根据声音片段以识别出整个音频文件,以达到听歌识曲的目的。
通过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()以支持文件的批量读取。
首先介绍一下本库所利用的指纹提取的方法,在读取完音频文件之后,会利用短时傅里叶转化对音频文件的各个通道的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化处理。
至此,整个音频文件的指纹信息已经提取出来了,在整个提取过程中,最重要的是特征点的提取。在不同的指纹提取方法中,特征点的提取方法也不相同,文章末尾所推荐的parper中有介绍另外的方法,有兴趣的同学可以看看。
指纹提取完毕后,会将提取到的指纹存储至数据库内,默认存储为Mysql,database类为数据库的抽象类,里面定义了必须重写的一些方法,database_sql为具体的实现。这方面不做过多描述。
通过麦克风或者硬盘读取完一段音频文件后,会提取出这段音频文件的所有指纹,然后与数据库内的指纹库进行比对,将匹配成功的音频信息返回。
音频识别最主要的是对于音频特征值的提取分析,难点也在这里。
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