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

您所在的位置:网站首页 最难听歌曲本亮 用Python实现一个简易的“听歌识曲”demo(一)

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

2023-08-27 15:05| 来源: 网络整理| 查看: 265

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

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

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

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

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

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

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

# -*- 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)

  其中,需要注意的一点,对于时间序列的匹配我们选取的算法是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))

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

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

  短短不到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

演示视频如下:

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/



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3