Matlab MFCC音乐特征匹配与DTW算法识别(含GUI设计)

前言

最近学校的大作业,以为还要编写实现MFCC系数的提取程序(包括分帧、加窗、帧移、DCT等操作),结果上csdn一查有现成的函数直接出结果。本文不会详细讲原理,也不讨论识别正确率(因为自己也没测试对比过),就只是贴点程序讨论一下,顺便作为自己的研究笔记,如有错误,欢迎指正。

音乐特征库的建立

本文从整个音乐特征匹配与识别的流程来讲述。

批量导入音乐文件

music_bank=dir(fullfile('D:\CloudMusic','*.mp3'));
music_names={music_bank.name}';
music_names=strrep(music_names,'.mp3','');  %名字列表中去掉后缀名.mp3
for i=1:length(music_bank)
    filename=strcat('D:\CloudMusic\',music_names{i},'.mp3');
    [x,fs]=audioread(filename);
    x=x((fs*5+1:fs*15),:); %特征提取库不需要太长时间段可自选,最好不要从0秒开始,有些音乐前奏还没开始
    %(未结束)

音乐库均保存在某一文件夹下,“*.mp3”后缀,由于作为特征库,其导入的数据作为元胞类型,反正之后无需修改。获取的music_names去掉歌名的后缀.mp3为了后续在显示听歌识曲结果时查找原歌名的时候需要(这里应该有别的方式)。
之后进入循环,循环audioread读入[x,fs],进行MFCC系数提取。

mfcc_m、mfcc、v_melcepst分析比较

看了csdn上很多关于mfcc的matlab实现,大多都是引用自宋知用老师编著的《MATLAB在语音信号分析与合成中的应用》中的程序,这里给出一篇博客的链接戳这里(只是我看到的引用过此程序的博客之一),注释还算详细。
首先从来源于此文的mfcc_m函数讲起,我修改的代码先贴出来(注意注释):

function ccc=mfcc_m(x,fs,p,L,framesize,inc)
% function ccc=mfcc_m(x);
%对输入的语音序列x进行MFCC参数的提取,
%返回MFCC参数和一阶、二阶(可选)差分MFCC参数,Mel滤波器的个数为p,
%MFCC参数个数L,采样频率为fs
%原文参数个数L确定,我觉得可以改成自定
%按帧长为n点分为一帧,相邻两帧之间的帧移为inc
bank=v_melbankm(p,framesize,fs,0,0.5,'t'); 
%归一化Mel滤波器组系数  
bank=full(bank);  %将稀疏矩阵转换为满存储 
bank=bank/max(bank(:)); 
%计算DCT系数 L*p
for k=1:L  %MFCC参数的个数,通常取12个,一般在12-16个之间
    n=0:p-1; 
    dctcoef(k,:)=cos((2*n+1)*k*pi/(2*p));  
end  
%归一化倒谱提升窗口
%ceplifter = @( N, L )( 1+0.5*L*sin(pi*[0:N-1]/L) );
w=1+0.5*L*sin(pi*(1:L)/L);  %c'=(1+0.5*L*sin(pi*n/L))cn 倒谱提升
w=w/max(w);
%预加重滤波器  
xx=double(x);  
xx=filter([1,-0.93],1,xx); % 通过一个HF,H(z)=1-uz^(-1),u可看效果自取
%语音信号分帧
xx=v_enframe(xx,framesize,inc); 
n2=fix(framesize/2)+1;
%计算每帧的MFCC参数  
for i=1:size(xx,1)  
    y=xx(i,:);  
    s=y'.*hamming(framesize);  
    t=abs(fft(s));
    t=t.^2;  
    c1=dctcoef*log(bank*t(1:n2));  %由于fft对称性,当N为偶数时取N/2+1个点
    c2=c1.*w';
    c2=c2.*sqrt(2.0/p); % 一维DCT公式(原文中没有这个系数,但是查询了DCT公式却有这个系数)
    m(i,:)=c2';  
end  
%求一阶差分系数  
dtm=zeros(size(m));  
for i=3:size(m,1)-2  
    dtm(i,:)=-2*m(i-2,:)-m(i-1,:)+m(i+1,:)+2*m(i+2,:);  
end  
dtm=dtm/3;  
%求取二阶差分系数  
dtmm=zeros(size(dtm));  
for i=3:size(dtm,1)-2  
    dtmm(i,:)=-2*dtm(i-2,:)-dtm(i-1,:)+dtm(i+1,:)+2*dtm(i+2,:);  
end  
dtmm=dtmm/3;  
%合并mfcc参数和一阶、二阶差分mfcc参数  
ccc=[m dtm dtmm];  
%去除首尾两帧,因为这两帧的一阶差分参数为0  
ccc=ccc(3:size(m,1)-2,:); 

其中,有v_melbankm函数,可以翻看我之前的一篇博客有详细解释这个函数的用法及参数含义戳这里

归一化倒谱提升窗口我看宋老师也没讲明白,全网我也没看到提及这个的博客,我目前也没找到相关资料。总的来说,mfcc_m还是借助v_melbankm函数提取MFCC系数的,在预处理和选取MFCC参数种类上做了处理。

mfcc 是Matlab自带的函数,源代码用open mfcc命令看,以下简略讲讲自己对该函数的理解。

[coeffs,delta,deltaDelta,loc] = mfcc(x,fs,
'WindowLength',WINDOWLENGTH, %(帧长 default=round(0.030*fs))
'OverlapLength',OVERLAPLENGTH, %(帧移 default=round(fs*0.02))
'NumCoeffs',NUMCOEFFS, %(参数个数 default=13)
'FFTLength',FFTLENGTH, %(FFT长度,被帧长限制,一般不需要提及)
'DeltaWindowLength',DELTAWINDOWLENGTH, %(一阶、二阶的窗长,默认为2)
'LogEnergy',LOGENERGY); %(关于log能量使用与否等)
%LOGENERGY='Append'(default) or 'Replace' or 'Ignore'
%loc是用来计算系数的最新样本

其中,coeffs为一个L*M*N矩阵:
L为音频信号被分割成的帧数,这是由窗口长度和重叠长度属性决定的。
M为每帧返回的系数,这是由NUMCOEFFS属性决定的。
N为通道数(可以分析双声道)。

LogEnergy下的选项解释:

‘Append’——LogEnergy(第1列)+Num列参数
注意:0号系数(?)(zeroth coefficient)在第2列
‘Replace’——Num列(第一列log-energy替换0)
‘Ignore’——忽略并且不返回log-energy

之后,ccc=[coeffs delta dletaDelta]可生成如mfcc_m返回的参数数据形式,注意提取单声道即可。

v_melcepst 源代码中调用了v_melbankm函数,输入参数内容和mfcc_m差不多,关于此函数详细介绍戳这里

笔者做实验对比了一下:

v_melcepst(x,fs,'E0dD',13,40)=mfcc(x,fs,'WindowLength',512,'OverlapLength',256,'NumCoeffs',14,'LogEnergy','Append')

(可以看出v_melcepst 默认帧长512,帧移256)
二者得出的MFCC系数维度相同,第一列为Log-Energy,第二列为zeroth coeffcients,但是数值不一样。

因此,在循环中使用上述方式即可提取MFCC参数:

%方案自选
%ccc{i}=v_melcepst(x,fs,'E0dD',13,40);
%[coeffs,delta,deltaDelta,~]=mfcc(x,fs,'WindowLength',512,'OverlapLength',256,'NumCoeffs',14,'LogEnergy','Append');
%ccc{i}=[coeffs(:,:,1) delta(:,:,1) deltaDelta(:,:,1)];
end %循环结束
save('music_base.mat','ccc','music_names');

最后把元胞数据保存成.mat,供识别部分使用此特征库。

音乐识别

测试音频剪切*

load('music_base.mat');
for i=1:length(music_names)
    filename=strcat('D:\CloudMusic\',music_names{i},'.mp3');
    [x,fs]=audioread(filename);
    x=x((fs*7+1:fs*(7+10)),:);%可更改tstart、tend
    out_names{i}=strcat('C:\Users\Dell\Desktop\test_music\','test',music_names{i},'.wav');
    audiowrite(out_names{i},x,fs);
end

批量剪切容易,可以根据需要剪切不同时间段的10秒片段。

录音输入音频信号

由于听歌识曲大多都是录音输入的,上述方式只是为了便于调试。

fs=44100;
recObj=audiorecorder(fs,16,1);
recObj.StartFcn='disp(''Start recording...'')';
recObj.StopFcn = 'disp(''End of recording.'')';
recordblocking(recObj,10);
x=getaudiodata(recObj);

使用audiorecorder(fs,nbits,channels)生成一个录音机,参数对应采样频率、采样位数、通道数。
之后两行设置开始录音、结束录音的提示语。
recordblocking(recObj,duration)控制录音时长,可更改,最后得到[x,fs].

特征匹配

为了做批量测试,同样根据length(music_names)循环:

for i=1:length(music_names)
    test_names{i}=strcat('C:\Users\Dell\Desktop\test_music\','test',music_names{i},'.wav');
    test_names=test_names';
    [x,fs]=audioread(test_names{i});
    %测试样本的mfcc提取方法可以做测试,比较不同方案的正确率
    %ctest=v_melcepst(x,fs,'E0dD',14,40);
    %[coeffs,delta,deltaDelta,~]=mfcc(x,fs,'WindowLength',512,'OverlapLength',256,'NumCoeffs',14,'LogEnergy','Append');
    %ctest=[coeffs(:,:,1) delta(:,:,1) deltaDelta(:,:,1)];
    
    % dtw, matlab自带这个函数
    for j=1:length(ccc)
        %dist(j,1)=dtw(ctest',ccc{j}');
        dist(j,1)=dtw_m(ctest,ccc{j});
    end
    
    % 查找结果
    [~,index]=min(dist);
    
    % 显示查找结果
    fprintf('test %d:The music is %s\n',i,music_names{index});
end

注意:dtw_m与Matlab自带dtw函数有点不同,dtw需要的矩阵形如参数个数×帧数;而dtw_m需要帧数×参数个数,由于上述程序得出的矩阵形如帧数×参数个数,因此使用dtw需转置。

以上均是未做成GUI时的散装程序。

GUI设计

界面fig就不讲了,相信大家会设计得比我好,我这只贴点回调函数,如果基本的GUI操作还不会就先去学一下,笔者都是翻命令文档看的。这里一笔带过。

特征库音乐输入

在“导入音乐”按钮回调函数下:

global MusicData; 
global FsData;
global MusicNames;
global N;
[MusicFile,MusicPath,MusicIndex]=uigetfile({'*.mp3;*.wav;*.flac;*.ogg;*.*','All Files(*.*)'},'Select musics',...
    'MultiSelect','on',...
    'D:\CloudMusic'); %设置默认路径
if isequal(MusicFile,0)||isequal(MusicPath,0)||isequal(MusicIndex,0)
    opts=struct('WindowStyle','modal',... 
              'Interpreter','tex');
    errordlg('\fontsize{10} 导入音乐失败,请重新导入','Error',opts);
elseif iscell(MusicFile)
    f=waitbar(0,'正在导入...');
    pause(.5)
    N=length(MusicFile);
    MusicFullPath=cell(N,1);
    for i=1:N
        MusicFullPath{i}=fullfile(MusicPath,MusicFile{i}); %音乐路径
        MusicNames{i}=strrep(MusicFile{i},'.mp3','');
        [x,fs]=audioread(MusicFullPath{i});
        x=x((fs*30+1:fs*40),1);%可修改
        MusicData{i}=x;
        FsData{i}=fs;
        waitbar(i/N,f,sprintf('正在导入%d/%d...',i,N),'modal');
    end
    waitbar(1,f,'导入成功!');
    pause(.5)
    close(f) %关闭进度条
end

uigetfile获取文件,errordlg生成错误弹窗,f=waitbar生成进度条,由于Matlab是单线程的,因此只能写入循环,让它有动态进度条效果。

特征库建立

分为两部分,一部分用户选择提取MFCC系数方式与参数设置,另一部分处理、生成参数库。

1、建立一个参数输入面板,里面可以选择方式、输入滤波器个数p、参数数量L、帧长framesize、帧移inc,之后将这些参数输入至全局变量,以“滤波器个数”编辑框回调函数为例:

global p;
value=get(handles.edit2,'string');
if isempty(str2num(value))
    opts=struct('WindowStyle','modal',... 
              'Interpreter','tex');
    errordlg('\fontsize{10} 输入参数错误','Error',opts);
else
    p=str2num(value);
end

2、在“生成特征库”按钮回调函数下:

global method;
global p;
global L;
global framesize;
global inc;
global N;
global MusicData;
global FsData;
global ccc;
ccc=cell(N,1);
f=waitbar(0,'正在生成...');
pause(.5)
if method==1
    for i=1:N
        [coeffs,delta,deltaDelta,~]=mfcc(MusicData{i},FsData{i},...
        'WindowLength',framesize,'OverlapLength',inc,...
        'NumCoeffs',L,'LogEnergy','Append');
        ccc{i}=[coeffs delta deltaDelta];
        waitbar(i/N,f,sprintf('正在生成%d/%d...',i,N),'modal');
    end
elseif method==2
    for i=1:N
        ccc{i}=mfcc_m(MusicData{i},FsData{i},p,L,framesize,inc);
        waitbar(i/N,f,sprintf('正在生成%d/%d...',i,N),'modal');
    end
else
    for i=1:N
        ccc{i}=v_melcepst(MusicData{i},FsData{i},'E0dD',L,p,framesize,inc);
        waitbar(i/N,f,sprintf('正在生成%d/%d...',i,N),'modal');
    end
end
waitbar(1,f,'生成完毕!'); 
pause(.5)
close(f)

录音识别

在“录音识别”按钮回调函数下:

global method;
global p;
global L;
global framesize;
global inc;
global ccc;
global MusicNames;
global Click; 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
请在GUI的OpeningFcn函数下先设置global Click;Click=0;
这样启动GUI时Click=0;
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
fs=44100;
recObj=audiorecorder(fs,16,1);
f=waitbar(0,'正在录音...');
recordblocking(recObj,10);
if isrecording(recObj)==0
    waitbar(1,f,'录音完成!');
    Click=Click+1;%计算这是第几次识别
    pause(.5)
    close(f)
else
    opts=struct('WindowStyle','modal',... 
              'Interpreter','tex');
    errordlg('\fontsize{10} 录音失败,请重试','Error',opts);
end 
x=getaudiodata(recObj);
G=1.2;%对录音信号的增益,依实际情况而定
x=x*G;
x_2=x-mean(x); %消除直流,效果不太明显
if method==1
    [coeffs,delta,deltaDelta,~]=mfcc(x_2,fs,...
    'WindowLength',framesize,'OverlapLength',inc,...
    'NumCoeffs',L,'LogEnergy','Append');
    ctest=[coeffs delta deltaDelta];
elseif method==2
    ctest=mfcc_m(x_2,fs,p,L,framesize,inc);
else
    ctest=v_melcepst(x_2,fs,'E0dD',L,p,framesize,inc);
end
for j=1:length(ccc)
    %dist(j,1)=dtw_m(ctest,ccc{j});
    dist(j,1)=dtw(ctest',ccc{j}');
end
[~,index]=min(dist);
set(handles.text10,'string',...
    sprintf('测试结果%d:%s',Click,MusicNames{index}));
    
%handels.text10为显示结果的文本框的句柄

此外,还可以做一个“退出程序”的按钮,回调函数如下:

clear all;
close

清空变量,释放内存。

总结

至此,大致就是制作基于MFCC的音乐特征匹配的GUI设计全过程了。
注意:如果制作语音识别(孤立词或者一句话)系统,仅有MFCC和DTW不够精确,还需要语端检测(VAD)、减噪(如果有必要,诸如谱减法等)等操作。笔者也将这些过程用于音乐识别,发现效果不佳,由于噪声大多是高频分段的,因此很大部分的音乐(欢快的、快节奏的)被滤去,人声基本保留;语端检测也会将音乐的低潮段删去。因此,语音识别需要在此基础上做出改进。

*本文于4.16日修改,重大更新如下:
1、优化、修改特征匹配算法;
2、新增录音输入信号功能;
3、新增GUI设计程序;
4、区别音乐特征匹配与语音识别。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章