从零搭建音乐识别系统(二)音频特征提取

本文介绍了从零构建音乐识别系统的第二部分——音频特征提取。利用AudioSet数据集和梅尔频谱进行二分类模型的训练,并详细阐述了音频的梅尔频谱转换过程。此外,讨论了针对歌曲数据集的embedding特征提取模型的训练,通过度量学习方法确保同类歌曲片段的embedding相似性高于不同歌曲。文章提供了代码示例,展示了如何将10秒音频转换为[64,1001]的矩阵,并对AudioSet数据集和歌曲数据集采取不同的采样策略来满足训练需求。

从零搭建音乐识别系统(一)整体功能介绍_程大海的博客-优快云博客

从零搭建音乐识别系统(二)音频特征提取_程大海的博客-优快云博客_音乐特征提取

从零搭建音乐识别系统(三)音乐分类模型_程大海的博客-优快云博客

从零搭建音乐识别系统(四)embedding特征提取模型_程大海的博客-优快云博客

从零搭建音乐识别系统(五)embedding特征提取模型验证_程大海的博客-优快云博客

 代码地址:GitHub - xxcheng0708/AudioEmbeddingExtraction: Extract audio embedding feature by metric learning


        在前面的总体功能概述中提到了,这个系统会训练两个模型,一个是音乐分类模型,用来判断是否是音乐片段,一个是embedding特征提取模型,用来提取音频的embedding特征向量。

分类模型数据集(AudioSet数据集):

        在这里,对于分类模型的训练,我们采用开源的AudioSet数据集。AudioSet数据集是一个用于声音分类的数据集,其中包含了各种类别的声音,如音乐,唱歌,哼唱,演讲,钢琴等,每个音频片段的长度都是10秒,具体可以去官网看一下介绍。而我们要训练的是一个二分类模型,只要能够区分音频片段是不是音乐片段就行,所以,我们把AudioSet数据集一分为二,把和音乐相关的所有类别作为音乐类,其余类别作为其他类。用这两个类别来训练分类模型。

embedding模型数据集(歌曲数据集):

        对于训练embedding模型的数据集,就使用常见的歌曲MP3文件就行,没啥好说的。

音频梅尔频谱特征提取:

        对于AudioSet数据集和MP3歌曲数据集,我们统一都使用梅尔频谱把音频文件向量化,将每10秒的音频文件转换为[64, 1001]大小的矩阵。具体实现代码如下:

# coding:utf-8
import librosa
import os
import numpy as np
import resampy


DEFAULT_FS = 32000


def read_audio_file(filename, sampleRate=DEFAULT_FS):
    """
    读取音频文件,如果采样率不为DEFAULT_FS,则重采样
    :param filename:
    :return:
    """
    try:
        y, sr = librosa.load(filename, sr=None)
        # 如果音频原始的采样率与预设的不一致,则对音频进行重采样
        if sr != sampleRate:
            y_44k = resampy.resample(y, sr, sampleRate)
            sr_44k = sampleRate
        else:
            y_44k = y
            sr_44k = sampleRate
        return y_44k, sr_44k
    except EOFError as e:
        print(filename)
    return None, None


def split_audio(x, split_duration=5, step_duration=3):
    """
    按照时域长度对音频片段进行滑窗拆分
    :param x:
    :param split_duration: 滑窗大小
    :param step_duration: 滑窗步长
    :return:
    """
    try:
        std_duration = split_duration * DEFAULT_FS
        std_step = step_duration * DEFAULT_FS
        d_len = x.shape[1]

        if x.shape[1] < std_duration:
            out = np.repeat(x, std_duration // x.shape[1] + 1, axis=1)
            print("out.shape: {}".format(out.shape))
            return [out[:, :std_duration]]
        else:
            result = []
            for s_len in range(0, d_len, std_step):
                print("s_len: {}".format(s_len))
                if d_len - s_len < std_duration:
                    new_data_array = x[:, -std_duration:]
                else:
                    new_data_array = x[:, s_len:s_len + std_duration]
                result.append(new_data_array)
            return result
    except Exception as e:
        # raise Exception(e)
        print(e)
    return None


def music_mel_frequency(x, fs):
    """
    计算梅尔频谱
    :param x:
    :param fs:
    :return:
    """
    mel_spectrogram_librosa = librosa.feature.melspectrogram(y=x, sr=fs, n_fft=1024, hop_length=320, n_mels=64,
                                                             fmin=50, fmax=14000, power=1)
    print('shape: {}'.format(mel_spectrogram_librosa.shape), 'max : ', np.max(mel_spectrogram_librosa), 'min: ', np.min(mel_spectrogram_librosa))
    mel_spectrogram_librosa_db = librosa.power_to_db(mel_spectrogram_librosa, ref=1, top_db=120)
    print('shape: {}'.format(mel_spectrogram_librosa_db.shape), 'db max : ', np.max(mel_spectrogram_librosa_db), 'db min: ', np.min(mel_spectrogram_librosa_db))
    spectrogram = mel_spectrogram_librosa_db
    return spectrogram


def audio_dataset_mel_spec(audio_path, out_path):
    """
    计算音频文件的梅尔频谱特征
    :param audio_path:
    :param out_path:
    :return:
    """
    audio_list = os.listdir(audio_path)
    for audio_name in audio_list:
        print("audio_name-1: {}".format(audio_name))
        audio_base_name = os.path.splitext(os.path.basename(audio_name))[0]
        dest_path = os.path.join(out_path, audio_base_name)
        if os.path.exists(dest_path) is False:
            os.makedirs(dest_path)

        if audio_name.endswith("mp3"):
            audio_file_path = os.path.join(audio_path, audio_name)
            audio, fs = read_audio_file(audio_file_path, sampleRate=DEFAULT_FS)
            if audio is None:
                continue

            audio = audio[np.newaxis, :]
            result = split_audio(audio, split_duration=10, step_duration=10)
            print("len result: {}".format(len(result)))

            for i, res in enumerate(result):
                x = np.squeeze(res)
                mel_spectrogram_librosa_db = music_mel_frequency(x, fs)
                print("mel_spectrogram_librosa_db: {}".format(mel_spectrogram_librosa_db.shape))

                np.save(os.path.join(dest_path, "{}_{}.npy".format(audio_base_name, i)), mel_spectrogram_librosa_db)


if __name__ == '__main__':
    # (64, 1001)
    audio_path = "./music"
    out_path = "./feature"
    audio_dataset_mel_spec(audio_path, out_path)

        基于上述音频梅尔频谱提取代码,设置不同的提取参数,分别提取AudioSet数据集和歌曲数据集的梅尔频谱数据。将每10秒音频片段提取[64, 1001]大小的矩阵。

AudioSet数据集梅尔频谱提取

        由于AudioSet数据集中每段音频的长度只有10秒,所以直接将每段音频提取得到一个[64, 1001]大小的数据矩阵。

歌曲数据集梅尔频谱提取

        歌曲数据集是用来训练embedding特征提取模型的,我们在后续会采用度量学习的方法来训练embedding特征提取模型。度量学习也是监督学习方法,训练数据也有各自的类别,度量学习的目标是使属于同一类别ID的样本的embedding之间的相似性高于不同类别样本embedding的相似性,从而通过相似性来完成识别的目标。对应到音乐识别就是,通过度量学习训练出来的音乐embedding模型,使得相同或者相似的歌曲片段之间的embedding相似度高于不同歌曲之间的相似度。关于度量学习的相关方法介绍,可以看一下我之前的文章。

        由于音频数据是时序数据,如何进行数据采样才能满足度量学习的训练数据要求,即同一类别的训练数据之间具有更高的相似性?参考google 2017年的论文《Now Playing: Continuous low-power music recognition》,我们对于一首歌曲,从歌曲中间的任意位置开始,我们以250ms为间隔,在5秒内连续采样20个持续时间为10秒的音乐片段,作为从一首歌曲中采集的训练数据,对应到上面的特征提取方法中就是把split_audio函数的参数设置为split_duration=10, step_duration=0.25。这样采集到的20个音乐片段相互之间具有50%~97.5%不等的重合度,我们认为这样的音乐片段之间是高度相似的。

        这样,对于每首歌曲文件,我们就可以提取得到20个[64, 1001]大小的矩阵作为训练数据。

Audio-Fingerprinting 是一个音乐指纹识别系统,使用的语言为 JAVA,同时需要用到 MySQL 数据库(虽然不是必须的,但这个系统采用他保存指纹和音乐信息)。他包含了指纹生成,数据库存储,和简易的服务器和客户端。他通过生成和记录音乐指纹,能够识别来自麦克风、文件等各个来源的音乐,并且有很高的抗噪性,同时他对文件属性和音乐质量不敏感。你可以使用服务器给手机或者其他程序提供音乐识别服务。你可以根据需求调节里面的参数,当前参数是为了在较短时间识别来自极大噪声和失真的音源,1500个左右的文件将产生接近24000000个指纹数 据。如果你只用于识别文件并且没有严重的噪声与失真,你可以修改参数,1个文件只需要少量指纹就可以识别,对于噪声较低的音源10s 200个指纹已经满足大多需求。简易使用方法需要安装MySQL,并执行Fingerprint. sql, 同时你可能需要修改max_allowed_packet参数,因为添加歌曲需要发送较大的包,我采用的参数是32M。修改MysqlDB中的数据库信息为你的数据库信息,如:private final String url = "jdbc:mysql://127.0.0.1:3306/musiclibary?user=yecheng"; private final String user = "yecheng"; private final String password = "yecheng";添加文件的方法:Ps:你可以重写添加的方法或者制作脚本或者直接使用其他软件实现转码功能,目前他能够从%title%}}%album%}}%artist%的文件名中获得信息。将文件转码为WAV,采样率为8000。调用Insert,参数为文件名或者文件夹。搜索音乐你可以调用Search 文件名搜索。在数据库较大的情况推荐采用运行Server,使用Client 文件名搜索。主要参数介绍Fingerprint:NPeaks:一个周期中每个子带的峰值点的个数 fftSize:FFT的窗口大小 overlap:FFT的窗口重叠大小 C:一个周期包含多少个窗口 peakRange:取峰值点时与多大范围的邻居比较 range_time:取点对的时候的时间范围,单位为秒 range_freq:取点对的时候的频率范围,单位为频率 Band:分成的子带,值对应FFT产生的数组索引 minFreq:最小频率 maxFreq:最大频率 minPower:最小能量修改的建议:提高识别率:减小minPower, 增加Band、NPeaks、range_time降低数据量:增大minPower,减小Band、NPeaks、rang_time其中建议先修改Band和minPower。Server:port:服务器的端口Client:ip:服务器的ip port:服务器的端口性能与效果数据量:音乐库为1500首歌,指纹数量为24000000个左右,服务器稳定后占用内存约340M。速度:处理器i7-3632QM,添加1500首歌用时约1919秒,一首歌约用时1.3秒。使用服务器查找10s的歌曲用时约0.2秒(不考虑客户端读取文件的时间)。准确度:对噪声较低的音频有很高的识别率,对噪声较高的也有接近商用的准确率,但是相对来说如果对于未出现在曲库的歌曲,也有一定的误报率。抗噪性:能够抵抗较强的失真和噪声,可以参考我给的测试音频。工作原理参考文档:ShazamMel scale本算法实现类似Shazam,首先我计算出音频频谱图,将频谱根据频率分成若干子带,对每个子带查找若干个峰值点,本算法子带划分基于Mel频率。将获得的峰值点根据频率、时间范围组成点对。本算法的取点对频率范围为在子带内,其目的在于减少点对的数目并且提高分布式能力。取点对的时间范围为1s-4s。你可以根据需要修改这些参数。 标签:Audio
评论 12
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值