史上最詳細的MFCC算法實現(附測試數據)
1.matlab安裝voicebox語音包
這裏該包的安裝我直接附上我們師姐寫過的一篇文章,裏邊的介紹很詳細:
戳這裏!!!跳轉到文章鏈接地址
2.MFCC原理講解
整個MFCC過程大致可以分爲以下幾步:
1.音頻文件讀取(最好是.wav文件)
2.預先加重
3.分幀
4.加窗
5.傅里葉變換(當是2的N次方時,可以使用FFT快速傅里葉變換)
6.梅爾濾波器組
7.離散餘弦變換DCT
3.MFCC算法設計實現(matlab)
3.1 .wav格式語音文件提取【x(200000*1)】
在matlab中,使用函數audioread函數來讀取本地wav文件,這裏要注意的是,採樣頻率一般爲8000Hz和16000Hz,採樣頻率需要大於真實信號最大頻率的2倍,纔不會導致頻譜混疊。
clc;
clear;
[x,fs]=audioread('diguashao.wav');%讀取wav文件
這裏我們用於測試的數據的採樣頻率 44100,這個是由 audioread函數默認決定的。
3.2 預加重【x(200000*1)】
爲了避免在後邊的FFT操作中出現數值問題,我們需要加強一下高頻信息,因爲一般高頻能量比低頻小。其預加重函數如下所示:
其中一般取值爲
%預加重y=x(i)-0.97*x(i-1)
for i=2:200000
x(i)=x(i)-0.97*x(i-1);
end
y=y';%對y取轉置
然後我們再來對比一下原始文件和預加重後的數據差異
我們可以看到整個的數據其幅度範圍是有所減小的,但是可以看得出來,高頻部分的縮小倍數是小於低頻部分的縮小倍數的。
3.3 分幀{S(301*1103)}
我們要對語音數據做傅里葉變換,將信息從時域轉化爲頻域。但是如果對整段語音做FFT,就會損失時序信息。因此,我們假設在很短的一段時間t內的頻率信息不變,對長度爲t的幀做傅里葉變換,就能得到對語音數據的頻域和時域信息的適當表達。例如我們這裏的採樣點數爲200000個點,如果真的這樣做的話,就很麻煩了,於是我們在語音分析中引入分幀的概念,將原始語音信號分成大小固定的N段語音信號,這裏每一段語音信號都被稱爲一幀。
但是,如果我們這樣分幀的話,幀與幀之間的連貫性就會變差,於是我們每一幀的前N個採樣點數據與前一幀的後N個採樣點數據一樣。其原理圖大致如下所示:
對於整個採樣點數據可以分爲多少幀以及幀與幀之間交叉的採樣點個數N,不是隨便分的,一般來說幀長設置爲,幀移設置爲,對於我這次的仿真,其幀數和幀長數值如下:
在這裏我們要調用matlab的enframe函數來進行分幀操作,要知道這個函數是包含在voicebox工具箱裏邊,首先確保其已經安裝成功。
S=enframe(x,1103,662);%分幀,對x進行分幀,
%每幀長度爲1103個採樣點,每幀之間非重疊部分爲662個採樣點
%1103=44100*0.025, 441=44100*0.01 662=1103-441
%根據計算,我們可以將108721個數據根據公式662*301+1103=200365
%可以將其分爲301幀
3.4 加窗{C(301*1103)}
將信號分幀後,我們將每一幀代入窗函數,窗外的值設定爲0,其目的是消除各個幀兩端可能會造成的信號不連續性(即譜泄露 spectral leakage)。常用的窗函數有方窗、漢明窗和漢寧窗等,根據窗函數的頻域特性,常採用漢明窗(hamming window)。接下來我來講解一下怎麼加窗:我們需要做的就是爲每一幀數據,也就是301幀數據都加入大小爲1103的漢明窗。其漢明窗的表達公式如下所示:
對於a的取值不同,將會產生不同的漢明窗,一般情況下,
%嘗試一下漢明窗a=0.46,得到漢明窗W=(1-a)-a*cos(2*pi*n/N)
n=1:1103;
W=0.54-0.46*cos((2*pi.*n)/1103)
plot(W);title('漢明窗');grid on;
xlabel('取樣點');ylabel('幅值')
%創建漢明窗矩陣C
C=zeros(301,1103);
for i=1:301
C(i,:)=W;
end
由上邊的公式我們可以得到漢明窗矩陣C,其大小爲{301**1103},由於漢明窗矩陣和分幀後的矩陣S具有相同大小,所以在matlab中使這兩個矩陣的對應位置相乘,即可得到加窗後的矩陣SC,其大小爲{301*1103}。接下來我將隨便選取一幀數據來展示一下漢明窗、原始數據、加窗後的數據。其matlab代碼如下所示:
SC=S.*C;
subplot(3,1,1);plot(C(7,:),'r');
title('漢明窗圖像');grid on;%畫出第7幀的漢明窗圖像
subplot(3,1,2);plot(S(7,:),'g');
title('原始信號圖像');grid on;%畫出第7幀的原始信號圖像
subplot(3,1,3);plot(SC(7,:),'m');
title('加了漢明窗的信號圖像');grid on;%畫出第7幀加了漢明窗的信號圖像
在上邊的圖示中我們就可以看到,在每一幀的低頻部分和高頻部分都被漢明窗相乘後起了較大抑制作用,使其結果接近於0。
3.5 傅里葉變換
對於加窗後的矩陣SC,它是一個301*1103的矩陣,也就是說,它有301幀數據,且每一幀數據都有1103個採樣點,那麼我們接下來就要對這301幀的每一幀都要進行N=4096的FFT快速傅里葉變換,得到一個大小爲301**4096大小的矩陣D,其幀數還是301幀,對每一幀的4096個數據點分別取模再取平方,然後除以4096;便得到能量譜密度E,其大小爲301x4096,然後再對每一幀得到的能量進行相加,即得到一個301x1的矩陣F,其中的每個元素代表每一幀能量的總和。
%對SC的每一幀都進行N=4096的快速傅里葉變換,得到一個301*4096的矩陣
F=0;N=4096;
for i=1:301
%對SC作N=4096的FFT變換
D(i,:)=fft(SC(i,:),N);
%以下循環實現求取能量譜密度E
for j=1:N
t=abs(D(i,j));
E(i,j)=(t^2)/N;
end
%獲取每一幀的能量總和F(i)
F(i)=sum(D(i,:));
end
3.6 梅爾濾波器
首先我要講一下什麼是梅爾值,這是一個新的量度,相比於正常的頻率機制,梅爾值更加接近於人耳的聽覺機制,其在低頻範圍內增長速度很快,但在高頻範圍內,梅爾值的增長速度很慢。每一個頻率值都對應着一個梅爾值,其對應關係如下
在matlab上畫出其對應圖像如下所示:
對於該函數圖像的顯示的matlab代碼如下所示:
%梅爾頻率轉化函數圖像
for i=1:200000
mel(i)=2595*log10(1+i/700);
end
set(gcf,'position',[180,160,960,550]);%設置畫圖的大小
plot(mel,'linewidth',1.5);grid on;
title('梅爾頻率轉換');xlabel('頻率');ylabel('梅爾頻率');
但是如果我們要將梅爾頻率m轉換爲頻率f呢,我們對上式整理即可得到:
好了介紹到這裏,對於如何得到梅爾濾波器組我們還是無從下手,於是,我在這裏描述了一下獲得梅爾濾波器的幾個簡單步驟。然後接下來的操作我們也就將會按照這個步驟來實現。
其中過程1、2、3、4的實現代碼如下所示:
fl=0;fh=fs/2;%定義頻率範圍,低頻和高頻
bl=2595*log10(1+fl/700);%得到梅爾刻度的最小值
bh=2595*log10(1+fh/700);%得到梅爾刻度的最大值
%梅爾座標範圍
p=26;%濾波器個數
B=bh-bl;%梅爾刻度長度
mm=linspace(0,B,p+2);%規劃28個不同的梅爾刻度
fm=700*(10.^(mm/2595)-1);%將Mel頻率轉換爲頻率
上邊幾步都比較好理解,但是對於接下來譜線索引號k的定義,或許就需要一些理解了,其定義公式如下所示:
其中爲FFT點數,爲抽樣頻率,爲之前那28個梅爾刻度轉化爲頻率後的值,最後我們得到的值爲一個1*28的矩陣。且k值範圍爲。這個式子是把頻率對應到頻譜中2048個頻率分量的某個。
以下則是k值得求解過程:
k=((N+1)*fm)/fs%計算28個不同的k值
hm=zeros(26,N);%創建hm矩陣
df=fs/N;
freq=(0:N-1)*df;%採樣頻率值
好了,現在我們只剩下最後一步了,創建Hm矩陣,這個矩陣得定義如下所示:
上式中的m代表第幾個濾波器,k爲橫座標0-2048。26個濾波器就是算2049個頻率分量分別屬於26個頻率帶的概率.根據上式計算26個濾波器的二維數組,也就是26*4096二維數組Hm.
以下貼出步驟6 Hm矩陣的求解
for i=2:27
%取整,這裏取得是28個k中的第2-27個,捨棄0和28
n0=floor(k(i-1));
n1=floor(k(i));
n2=floor(k(i+1));
%要知道k(i)分別代表的是每個梅爾值在新的範圍內的映射,其取值範圍爲:0-N/2
%以下實現公式--,求取三角濾波器的頻率響應。
for j=1:N
if n0<=j & j<=n1
hm(i-1,j)=2*(j-n0)/((n2-n0)*(n1-n0));
elseif n1<=j & j<=n2
hm(i-1,j)=2*(n2-j)/((n2-n0)*(n1-n0));
end
end
%此處求取H1(k)結束。
end
接下來將要進行最後一步,輸出Hm矩陣,並且將梅爾濾波器組畫出來。
%繪圖,且每條顏色顯示不一樣
c=colormap(lines(26));%定義26條不同顏色的線條
set(gcf,'position',[180,160,1020,550]);%設置畫圖的大小
for i=1:26
plot(freq,hm(i,:),'--','color',c(i,:),'linewidth',2.5);%開始循環繪製每個梅爾濾波器
hold on
end
grid on;%顯示方格
axis([0 1500 0 0.2]);%設置顯示範圍
畫出來之後,我們就會發現該梅爾濾波器,在頻率很小的時候,濾波器寬度很窄,隨着其頻率的增大,濾波器的寬度也會逐漸增大,但其幅值也會逐漸減小。
3.7 離散餘弦變換
在進行離散餘弦變換之前,我們還需要做的就是把第3.5節得到的二維矩陣能量譜E(3014096),乘以第3.6節得到的二維數組梅爾濾波器Hm(264096)的轉置,矩陣的轉置可得到301*26的矩陣,然後滿足矩陣乘法定律,得到參數H,其定義如下:
此處的H其實是301x26的二維矩陣。
由於濾波器組得到的係數是相關性很高的,因此我們用離散餘弦變換(Discrete Cosine Transform)來去相關並且降維。一般來說,在自動語音識別(Automatic Speech Recognition)領域,因爲大部分信號數據一般集中在變換後的低頻區,所以對每一幀只取前13個數據就好了。
好了接下來我們就要進行離散餘弦變換了,但是在開始之前我感覺還是先講解一下其具體的步驟流程吧。
根據mfcc的定義,我們需要對能量的對數作離散餘弦變換,即可得到MFCC參數:
其中H爲我們上邊得到的矩陣H,M代表梅爾濾波器個數,i代表第幾幀數據(取值爲1-301),n代表第i幀的第n列(n取值範圍爲1-26)。那麼根據上述公式我們可以寫出求取mfcc的代碼如下:
%對H作自然對數運算
%因爲人耳聽到的聲音與信號本身的大小是冪次方關係,所以要求個對數
for i=1:301
for j=1:26
H(i,j)=log(H(i,j));%取對數運算
end
end
%作離散餘弦變換
for i=1:301
for j=1:26
%先求取每一幀的能量總和
sum=0;
%作離散餘弦變換
for p=1:26
sum=sum+H(i,p)*cos((pi*j)*(2*p-1)/(2*26));
end
mfcc(i,j)=((2/26)^0.5)*sum;
%完成離散餘弦變換
end
end
接下來我們就要根據公式進行升倒譜的計算了,前邊我們已經講到了,因爲大部分的信號數據一般集中在變換後的低頻區,所以對每一幀只取前13個數據就好了。其公式表達如下:
其中L爲升倒譜系數,一般取值爲22,我們將其帶入即可求得矩陣K,這是一個1x13大小的矩陣,這一部分的升倒譜的其實現代碼如下:
J=mfcc(:,(1:13));
%默認升到普係數爲22
for i=1:13
K(i)=1+(22/2)*sin(pi*i/22);
end
接下來我們就要求取MFCC的三個參數了,首先我們先獲取mfcc的第一組數據,根據公式:
根據公式我們可以實現如下代碼:
%得到二維數組feat,這是mfcc的第一組數據,默認爲三組
for i=1:301
for j=1:13
L(i,j)=J(i,j)*K(j);
end
end
feat=L;%將其值賦值到feat矩陣
接下來就是求取MFCC的第二組,第三組參數了。第二組參數其實就是在已有的基礎參數下作一階微分操作,第三組參數在第二組參數下作一階微分操作。那麼表達公式事什麼樣的呢,別急,等我慢慢道來:
按照上邊的公式,我們可以使用代碼實現一階求導和二階求導的計算
%接下來求取第二組(一階差分系數)301x13 ,這也是mfcc參數的第二組參數
dtfeat=0;
dtfeat=zeros(size(L));%默認初始化
for i=3:299
dtfeat(i,:)=-2*feat(i-2,:)-feat(i-1,:)+feat(i+1,:)+2*feat(i+2,:);
end
dtfeat=dtfeat/10;
%求取二階差分系數,mfcc參數的第三組參數
%二階差分系數就是對前面產生的一階差分系數dtfeat再次進行操作。
dttfeat=0;
dttfeat=zeros(size(dtfeat));%默認初始化
for i=3:299
dttfeat(i,:)=-2*dtfeat(i-2,:)-dtfeat(i-1,:)+dtfeat(i+1,:)+2*dtfeat(i+2,:);
end
dttfeat=dttfeat/10;
%這裏的10是根據數據確定的
好了到這裏我們就完成了,MFCC三組參數的求解,我們現在就只需要將這三組數據拼接到一起形成一個301x39的矩陣即可。其實現代碼如下:
%將得到的mfcc的三個參數feat、dtfeat、dttfeat拼接到一起
%得到最後的mfcc係數301x39
mfcc_final=0;
mfcc_final=[feat,dtfeat,dttfeat];%拼接完成
前面導圖中,我們也有講到過,由於一階求導和二階求導的前兩幀和後兩幀數據都爲0,於是我們就要對在mfcc_final中去除這四幀數據。而後我們再選取每一幀的mfcc係數的第一個數得到,這是一個297x1的數據,對來進行繪圖,並與原始信號進行比對。
mfcc24=mfcc_final((3:299),:);
%以下畫圖對比一下原始信號和提取出來的mfcc0
set(gcf,'position',[180,160,960,550]);%設置畫圖的大小
subplot(211)
plot(x,'m');grid on;
title('原始信號');
axis([0 200000 -1 1]);%對數據,進行繪圖
mfcc0=mfcc24(:,1)%選取mfcc係數的第一個數,組成新的特徵參數mfcc0
subplot(212)
mfcc00=(mfcc0-80)/2
plot(mfcc00,'r','linewidth',2);
title('MFCC_0');
axis([0 300 -30 5]);grid on
比對結果如下所示:
好了,到了這裏我們就可以看到了,原始信號之前是20000個採樣點的數據,而現在的參數圖形大致與原始信號一致,並且其點數只有297個點,這也就說明通過此方法,我們可以提取出語音信號的特點以及走向趨勢,也就是說在某個程度上我們可以用這297個點來代替點的數據。
4.總結
本次訓練是在參考了很多資料的前提下完成的,爲了防止自己忘記,所以特此寫了本篇文章。
5.參考文獻,資料
1.基於譜熵梅爾積的語音端點檢測方法
2.語音識別MFCC
3.語音特徵參數MFCC提取過程詳解
4.Mel濾波器組的設計與實現(基於MATLAB和Python)