如何提取歌曲副歌(高潮) | 您所在的位置:网站首页 › 如何快速截取音乐歌曲 › 如何提取歌曲副歌(高潮) |
摘要
副歌就是我们日常说的高潮。副歌分析属于MIR领域的一个小分支,MIR(Music information retrieval)是从音乐中检索信息的跨学科科学,该领域需要数学、乐理、信号处理、机器学习、概率、算法等学科的背景知识。 业界类似方案 1:根据能量值、音高区间差异定位 2:根据动态歌词定位 3:谱聚类&能量值第一种方式太简单暴力,很容易命中一些间奏,且起始点不准。第二种依赖精准的歌词,对纯音乐无歌词无能为力。本文使用的是第三种方式,是作者结合业界谱聚类算法以及自己对歌曲的理解整合的一个方案。下面一点点剖析,本文实验歌曲是著名实力加偶像派歌手不罕见的《你好再见》,不妨打开虾米听着歌曲看文章,更容易理解。 提取大步骤 1:解析频谱 2:谱聚类分段分类 3:分析副歌段落 基础知识在开始进入正题之前,很有必要花1分钟了解下音频基础知识,老司机略过 其实平时大家理解的无损仅仅是相对的,很多高逼格耳机音箱,如果音频源不好,也只能装逼用; 共振峰[必须了解]:共振峰是指在声音的频谱中能量相对集中的一些区域,共振峰不但是音色的决定因素,而且反映了声道(共振腔)的物理特征。例如刘德华和赵本山唱歌声音不一样,很大程度由共振峰决定。共振峰值携带了声音的辨识属性,如同人的身份证 频谱包络[必须了解] :频谱包络是将不同频率的振幅最高点连结起来形成的曲线,就叫频谱包络线,是语音合成技术的重要组成部分 1:解析频谱 y, sr = librosa.load('audio/你好再见.mp3')平时我们听到的所有声音都可以理解为一种波,例如一个音叉发出频率为440HZ 的声波,他的波形可能是这样的: 然鹅,一首歌曲里有太多各种各种的频率的波交叠在一起,就像如下这个gif 图 傅里叶变换FFT是将按时间或空间采样的信号与按频率采样的相同信号进行关联的数学公式。在信号处理中,傅里叶变换可以揭示信号的重要特征(即其频率分量) 它是个伟大的公式,不仅仅用在音乐领域,还试图预测即将到来的地震,识别距离遥远的星系的组成部分,寻找热量大爆炸残余物中的新物理成分,从x射线衍射模式揭示蛋白质的结构,为NASA分析数字信号,研究乐器的声学原理,改进水循环的模型,寻找脉冲星(自转的中子星),用核磁共振研究分子结构。傅里叶变换已经被用于通过破译油画中的化学物质,来识别假冒的绘画。下图黑色波形就是被混合后的,其他则是分解之后的。 而FFT 只能分离瞬间一个点的信号,只变换出了频域,但声音毕竟是连续的有时域概念的,且歌曲并不是平稳信号,如果说通过对信号加窗处理,信号在一窗内可看成平稳信号(可以简单理解频率不变),然后再用傅里叶变换。这样就能得到既有频域又有时域的信号,所以又有了 STFT -短时傅里叶变换经过STFT 变换后的信号可以看下这个图: 到波形层面如下 好,我们是不是可以立即去对歌曲做短时傅里叶变换拿到数据进行分析了?因为在频域一个点上的频率可以从0到20000,这个维度去计算一来代价很大,二来也不太符合人耳听到后的感受。我们听歌听到的音符也就是音阶,这些都可以对应到钢琴上的某个键。所以我们不妨把STFT变换后的数据在对频域进行log 压缩(为什么是log ,因为频率和音阶的关系也是这样的),把频域数据归属到若干个音阶上。这样一首歌曲经过这样变化后就可以得到如下这种图 刚才的变换学术上就叫常数Q转换(Constant Q transform),与短时距傅立叶转换一样为重要时频分析工具,其中特别适用于音乐信号的分析,这个转换产生的频谱最大的特色是在频率轴为对数标度而不是线性标度,且窗口长度会随着频率而改变。 经过CQT后的数据里表示能量值的维度,还需要转换为DB,因为这样可以使分布跨度很大的能量值相对集中到一个小区间内,不至于使太低的能量被忽略掉。 C = librosa.amplitude_to_db(np.abs(librosa.cqt(y=y, sr=sr,bins_per_octave=BINS_PER_OCTAVE,n_bins=N_OCTAVES * BINS_PER_OCTAVE)),ref=np.max) MFCC(Mel频率倒谱系数) mfcc是从频谱提取的一种特征,这个特征在自动语音和说话人识别中广泛的使用一首歌曲我们可以得到它的频谱包络(连接所有共振峰值点的平滑曲线,共振峰值携带了声音的辨识属性,如同人的身份证),但是对于人类来说,人类听觉的感知只会聚焦在某些特定的区域而不是整个频谱包络,所以为了最大程度让数据符合人耳听到的频率变化,MFCC 就出现了。 MFCC 的具体提取原理网上有太多介绍,简单说它是先做信号预处理(预加重、分帧、加窗)、FFT、对数、DCT。我们提取一首歌的mfcc mfcc = librosa.feature.mfcc(y=y, sr=sr) Msync = librosa.util.sync(mfcc, beats) 降维之节拍分隔取均值刚才利用CQT 算是对频域维度很好的进行了降维,但是时域维度上看,假如一首歌有4分钟,采样率为44100HZ/S,那么意味着时域上有46044100个采集点,可以想象下这个矩阵的维度,这样计算代价是非常大的,回到我们副歌提取的目标上看,完全没必要使用这么多的数据,怎么可以更低维的数据呢?一种办法是用较低的采样率重新采样一次,但即便这样,数据维度仍旧很大,如果强力降低采样率,会直接影响分析结果,因为降低到一定程度很可能连人也听不出来了,何况机器。另一种比较取巧的方法是获取一定时间段的平均值,无外乎就平均算法和中位数算法,考虑到歌曲的动态幅度,采用中位数比较稳妥。那是简单段怎么切分比较好呢?节拍,对,没有比它更适合的了。接下来就是计算歌曲的节拍分布(这个技术是另外一个话题,本文不展开),然后每个拍之间求频谱中位数(频率维度)左图是没有节拍分隔降维的,右图是降维后的,可以发现从图纹理看,差别不大,也就是信息还原度比较高。当然,数据量已经减少了很多很多了 这一步骤很关键,也较多点,我们必须先了解下什么是谱聚类。 谱聚类(spectral clustering)是描述图的一种矩阵,在降维,分类,聚类等领域有广泛的运用。谱聚类是一种基于图论的聚类方法。将样本看成顶点,样本的相似度看作带权边。这样,把样本集划分成K个簇的过程就等同于一个图的分割问题。 如上图所示,每个顶点是一个样本。边的权值就是样本之间的相似度。然后我们需要分割,分割之后要使得连接不同组之间的边的权重尽可能低(组间相似度小),组内部的边的权重尽可能高(组内相似度高)。 是不是看着挺简单的?其实做起来并不简单,它要比传统的Kmean 聚类的复杂些,基本步骤如下: 或许你已经看出来,前面的拉普拉斯矩阵的构造是为了将数据之间的关系反映到矩阵中,那么计算特征值以及特征向量从而达到将维度从N维降到k维,然后维度降下来了,我们就可以轻松的使用kmeans聚类,聚类完成后再将数据投影到原始数据上,这样原始数据就成功聚类啦 谱聚类算法的主要优点有:1)谱聚类只需要数据之间的相似度矩阵,因此对于处理稀疏数据的聚类很有效。这点传统聚类算法比如K-Means很难做到 2)由于使用了降维,因此在处理高维数据聚类时的复杂度比传统聚类算法好。 谱聚类算法的主要缺点有:1)如果最终聚类的维度非常高,则由于降维的幅度不够,谱聚类的运行速度和最后的聚类效果均不好。 2) 聚类效果依赖于相似矩阵,不同的相似矩阵得到的最终聚类效果可能很不同。 类似于谱聚类的算法除了kmean ,还有 kmedoids DBSCAN 等(注意KNN 不是聚类算法,它是分类算法,和PCA 支持向量机等都属于机器学习分类领域) 到这里谱聚类原理就介绍完了,你可以疑惑谱聚类不就是求一个把一个大图分隔出多个子图的RatioCut的解吗?是的,RatioCut 经过推导后,神奇的把一个NP难度的问题转换成拉普拉斯矩阵特征值(向量)的问题,数学就是这么美。 2.1:频谱自相关邻接矩阵通过上面的介绍,我们已经了解了什么是谱聚类,那么接下来我们要构造关键的拉普拉斯矩阵 L = D - W中的W; 我们的目标是找到副歌段落,那么首先要把歌曲分成不同的段落,而歌曲往往是段落间有重复规律的。利用刚才得到的CQT数据Csync,我们来做下自相关,自相关可以理解为自己和自己逐帧对比,得出帧之间的相关度(底层使用的knn算法),生成邻接矩阵 R R = librosa.segment.recurrence_matrix(Csync, width=3, mode='affinity',sym=True)为了得到更连续平滑的重复片段,可以采用加窗的方法,求窗内的中位数,这样可以使断掉的片段连续光滑起来,还可以减低噪点的影响。这个窗的长度参数很关键,不能太大或太小,可以多次尝试取个合适的值。 df = librosa.segment.timelag_filter(scipy.ndimage.median_filter) Rf = df(R, size=(1, 7)) 2.3 相邻关系邻接矩阵歌曲段落除了在音调上有规律外,还有一个就是音色,通俗的说就是不同乐器或人的声音特征,往往一段内是同一种特征, 而提到这个就用到了上文提到的MFCC。这个mfcc数据不适合再去做自相关,它表现出的不同音色的区别,更适用于分段,那么我们可以计算前一帧和当前帧的相似度(这里使用高斯核函数),然后做矩阵变换构造出邻接矩阵R_path。 mfcc = librosa.feature.mfcc(y=y, sr=sr) Msync = librosa.util.sync(mfcc, beats) path_distance = np.sum(np.diff(Msync, axis=1)**2, axis=0) sigma = np.median(path_distance) path_sim = np.exp(-path_distance / sigma) R_path = np.diag(path_sim, k=1) + np.diag(path_sim, k=-1)自相关邻接矩阵R和临近帧距离邻接矩阵R_path都已经有了,那么毕竟求拉普拉斯矩阵时只需要一个邻接矩阵,所以我们要把两者合一,做归一化后进行融合。这个里融合其实也很关键,两者权重非常影响结果,这一步我还有些疑问。 deg_path = np.sum(R_path, axis=1) deg_rec = np.sum(Rf, axis=1) mu = deg_path.dot(deg_path + deg_rec) / np.sum((deg_path + deg_rec)**2) A = mu * Rf + (1 - mu) * R_path谱聚类后画出分段的情况如下图,这个就是歌曲《你好再见》的频谱图被聚类分段后的样子 经过以上步骤已经把歌曲分为了几个片段,并且片段之间进行了分类。那么到底哪一个分类或者哪一个片段属于副歌呢?这个还没有很管用成熟的算法可以使用,但是可以根据副歌一些特点来进行甄别。最明显的副歌片段在整首歌里的能量值是偏高的,顺着这个思路我们可以往下走。 3.1 剔除过短时间片段,例如可以认为10秒以内的都是无效片段,极少有10s 以内的副歌的 3.2 计算每拍DB均值,然后计算每段的能量(每段有n拍) meanCsync = np.mean(Csync,0) #根据节拍算均值 for bf ,bt , label in zip(zip(bound_beats, bound_beats[1:]), zip(bound_times, bound_times[1:]),bound_segs): ...... energyListSorted = sorted(timeEnergyMap.values()) 3.3 找到最大能量对应的label 值(也就是谱聚类出来的那个分类的label) targetLabel = energyLabelMap.get(energyListSorted[-1]) 3.4 找到这个label 里按时间维度最后的那个片段,副歌大多在后面,这样命中的概率更高,副歌质量更高 fugeSeg = labelTimelistMap.get(targetLabel)[-1] // the fuge time is 165.326077098-214.157641723到这里一首歌曲的副歌片段就定位出来了,我通过对大量歌曲的人工核对,准确率在80%以上,bad case 中有很多是很怪的歌曲,人工也听不出副歌在哪里;理论上正常歌曲的准确率在90%以上。后面为了提高准确率,还可以加上帧人声识别和歌词定位,通过这两种方式可以更大概率的定位到副歌片段。当然,我个人认为终极方案还是要结合机器学习和音频算法相结合的方式。非常感谢这期间@屠零 博士给予音频信号领域的指导答疑~ 参考论文 http://bmcfee.github.io/papers/ismir2014_spectral.pdf常用工具包librosa sklearn msaf Essntia等 |
CopyRight 2018-2019 实验室设备网 版权所有 |