当前位置:   article > 正文

用Python实现一个简易的“听歌识曲”demo(一)_python 听歌识曲

python 听歌识曲

0. 背景

  最近两年,“听歌识曲”这个应用在国内众多的音乐类APP火热上线,比如网易云音乐,QQ音乐。用户可以通过这个功能识别当前环境里正在播放的歌曲名字,听起来很酷。其实“听歌识曲”这个想法最早是由一家叫Shazam的国外公司提出的。
  - 2008年,Shazam率先在ios和android上发布了APP,并且整合了iTunes/Amazon’s MP3 store歌曲购买服务;
  - 2013年,Shazam成为年度十大最受欢迎的手机应用;
  - 2017年12月,苹果公司宣布以4亿美元收购Shazam,将“听歌识曲”整合在iTunes里,加大自己在音乐服务领域的竞争力,以对抗Apple Music最大的竞争对手Spotify。
  历史就先讲到这里,回到正题。今天我们要做的是做一个简易的“听歌识曲”,在这篇博客中,我不会讲过多算法的细节,只是完全从代码的角度来讲述实现过程。

1. “听歌识曲”原理

  那么我们怎样才能实现听歌识曲呢?以下两个要素是必要的:

  1. 对歌曲进行特征提取。一般来说,鲁棒性高并且容易分别的特征存在于音频文件的频谱。从音乐的角度来讲,一首歌曲的旋律,节奏,韵律都属于这类特征。
  2. 搜索库的构建。对歌曲的识别应该是在一个音乐歌曲库里进行搜索,选择和待识别歌曲最相似的作为匹配歌曲输出。

  在这个demo实现中,我们选取最简单的一个特征来进行识别——节奏,可以很确定的是,每首歌的节奏都会有所不同,不大可能出现100%一致的两首歌曲;同样,可能会存在一些节奏很类似的歌曲,也许节奏点的重合度达到80%以上。
  综上,所以我认为“节奏”只能作为一个初步的特征识别的过滤,原因如下:节奏差别很大的两首歌肯定不同;在噪声的影响下,节奏差别很小的两首歌很难确定是否相同。对于本文中提及的实现“听歌识曲”的简易demo,用节奏(beat)作为歌曲的特征是完全可行的,但是要做很复杂很精确的“听歌识曲”应用,应该加入其它的特征(比如音频指纹)做更加细致的特征区分。

2. 代码实现

  我们用python来实现整个demo,需要安装的依赖库有以下:

  - librosa,音乐信号分析的python库
  - dtw,衡量时间序列的相似度
  - numpy,数值计算库

  首先用librosa库来提取歌曲的节奏点,并创建搜索库:

import librosa
import os
import numpy as np

audioList = os.listdir('music_base')
raw_audioList = {}
beat_database = {}
for tmp in audioList:
    audioName = os.path.join('music_base', tmp)
    if audioName.endswith('.wav'):
        y, sr = librosa.load(audioName)
        tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr)
        beat_frames = librosa.feature.delta(beat_frames)
        beat_database[audioName] = beat_frames
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

  其中最关键的两行代码是:

tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr)
beat_frames = librosa.feature.delta(beat_frames)
  • 1
  • 2

  第一行代码是调用librosa的beat_track对歌曲时间序列进行节奏点的跟踪,返回的beat_frames即为节奏点的时间坐标,需要特别注意的是第二行代码,我们对提取出的节奏时间序列进行差分,即最终保存的特征是是连续前后两个节奏点时间坐标的差值δ。为什么要这么做?原因在于,在对环境歌曲进行识别时,我们并不知道这首歌的起始点在哪里,也许用户打开这个功能时,歌曲已经播放一半时间了,那么去匹配绝对的节奏点的时间坐标是没有意义的。但是,节奏的间隔却是不变的。

  然后将每首歌的特征和歌曲名字存放到一个字典中,以供测试识别时可以快速查找:

np.save('beatDatabase.npy', beat_database)
  • 1

  最后,我们打开一首歌,通过电脑的麦克风对环境歌曲进行录制,然后同样地提取它的节奏间隔特征,并且音乐库的所有歌曲分别进行序列匹配,输出与它最相似的歌曲:

# -*- coding: utf-8 -*-

from dtw import dtw
from numpy.linalg import norm
from numpy import array
import numpy as np
import librosa
import pyaudio
import wave

all_data = np.load('beatDatabase.npy')
beat_database = all_data.item()


sr = 44100
chunk = sr
p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paInt16,
                channels=1,
                rate=sr,
                input=True,
                frames_per_buffer=chunk)
frames = []
for i in range(0, int(sr / chunk * 30)):
    data = stream.read(chunk)
    frames.append(data)
stream.stop_stream()
stream.close()
p.terminate()
#
wf = wave.open('test.wav', 'wb')
wf.setnchannels(1)
wf.setsampwidth(p.get_sample_size(pyaudio.paInt16))
wf.setframerate(sr)
wf.writeframes(b''.join(frames))
wf.close()


# testAudio = "test_music/record_jayzhou.wav"
testAudio = "test.wav"
y, sr = librosa.load(testAudio)
tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr)
beat_frames = librosa.feature.delta(beat_frames)

x = array(beat_frames).reshape(-1, 1)

compare_result = {}
for songID in beat_database.keys():
    y = beat_database[songID]
    y = array(y).reshape(-1, 1)
    dist, cost, acc, path = dtw(x, y, dist=lambda x, y: norm(x - y, ord=1))
    print('Minimum distance found for ', songID.split("\\")[1], ": ", dist)
    compare_result[songID] = dist

matched_song = min(compare_result, key=compare_result.get)

print(matched_song)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

  其中,需要注意的一点,对于时间序列的匹配我们选取的算法是dtw(动态时间规整),对应的代码段如下。其中y是音乐库里歌曲songID对应的特征,x是当前麦克风捕捉到的音乐段的特征,调用函数dtw对两者按照最小均方误差的标准进行匹配,返回的dist用来表征两个时间序列的距离,距离越小则相似度越高。

y = beat_database[songID]
y = array(y).reshape(-1, 1)
dist, cost, acc, path = dtw(x, y, dist=lambda x, y: norm(x - y, ord=1))
  • 1
  • 2
  • 3

  具体的细节可以阅读Python库dtw的示例代码:

https://github.com/pierre-rouanet/dtw/blob/master/examples/simple%20example.ipynb
  • 1

  短短不到100行代码,我们就完成了一个很酷的“听歌识曲”demo。我们用周杰伦的范特西专辑来进行测试,效果如下:

D:\Developer\python\anaconda3\python.exe D:/learning/music_retrieve/librosa_main.py
Minimum distance found for  周杰伦 - 对不起.mp3 :  0.035980221058757346
Minimum distance found for  周杰伦 - 爸 我回來了.mp3 :  0.2417422867513621
Minimum distance found for  周杰伦 - 双截棍.mp3 :  5.815719207579681
Minimum distance found for  周杰伦 - 爱在西元前.mp3 :  1.5796865581675672
Minimum distance found for  周杰伦 - 忍者.mp3 :  4.666914682539685
Minimum distance found for  周杰伦 - 开不了口.mp3 :  0.059177365668093604
Minimum distance found for  周杰伦 - 上海 一九四三.mp3 :  2.13738962472406
Minimum distance found for  周杰伦 - 简单爱.mp3 :  6.958281998631065
Minimum distance found for  周杰伦 - 威廉古堡.mp3 :  14.53719958202717
Minimum distance found for  周杰伦 - 安静.mp3 :  14.806564551422317
Matched song is: music_base\周杰伦 - 对不起.mp3

Process finished with exit code 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

演示视频如下:

width="560" height="315" src="https://v.youku.com/v_show/id_XMzgxNzIwODQ0OA==" allowfullscreen="">

3. 项目地址

https://github.com/wblgers/music_retrieve
将你的音乐库放入文件夹music_base,后缀名支持.wav;将你的待识别的歌曲片段放入文件夹music_test;运行代码librosa_music.py进行搜索库的创建,运行代码librosa_main.py进行识别,也支持直接打开麦克风录音完成识别。具体的细节可以看代码实现。

喜欢的话可以点个star!

4. 参考文献

https://en.wikipedia.org/wiki/Shazam_(application)
http://librosa.github.io/librosa/generated/librosa.beat.beat_track.html#librosa.beat.beat_track
https://labrosa.ee.columbia.edu/projects/beattrack/

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/112124?site
推荐阅读
相关标签
  

闽ICP备14008679号