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、區別音樂特徵匹配與語音識別。

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