(kaldi、matlab、自己編寫的)三種不同方式得到的mfcc數據之性能比較


在之前的博客中分別實現了使用matlab編程獲取wav語音數據的mfcc特徵值、調用他人的方法獲取mfcc方法、調用kaldi封裝的mfcc函數進行特徵值提取,那麼究竟這三種方法哪種性能更優呢?也或者我們是否能在這個實驗的比較中得到什麼結果呢?
實驗準備材料,本次實驗將分別採用這三種方法,對同一語音數據進行測試,並將其進行特徵值最後進行繪圖表示,查看其具體差異。該語音信號是fs爲16k,時長5s,內容爲"中國加油",接下來就讓我們看下去吧~~

1.繪製kaldi方式提取出的mfcc特徵值

在開始本方法之前,我們需要先將之前通過kaldi方式產生的特徵值讀入到matlab。

1.1 將本地的mfcc數據讀入matlab

因爲之前產生得到是就是一個數據矩陣,所以matlab很輕易的就可以將文件讀入,具體可以查看以下代碼及結果:

%清除歷史數據
clc;
clear all
%讀入txt2.ark中的mfcc特徵值
Y = load('txt2.ark');   
y=Y(:,1);
%獲取mfcc特徵矩陣的第一列
N=length(Y)
x=1:1:N;
set(gcf,'position',[180,160,960,600]);%設置畫圖的大小
plot(x,y);
grid on;

很快我們即可得到其結果顯示如下所示:
在這裏插入圖片描述

2.繪製matlab調用函數產生的mfcc特徵值

在這裏還有一個問題就是爲了確保該方式和kaldi方式產生的相同,換句話說就是要得到和kaldi方法相同的語音幀數,此刻我們還缺少兩個參數,分別爲每一幀的幀長和幀移。

2.1 求取幀長和幀移

通過matlab讀取該語音文件我們得知該語音文件共含有79872個採樣點,又因爲kaildi得出的數據爲497幀,於是爲了計算出幀長和幀移,我們不妨設置如下等式:
(xy)496+x=49872 (x-y)\cdot496+x=49872
式中字母xxyy分別代表如下:

  1. xx代表幀長
  2. yy代表幀移

或許此刻你或許會說一個方程兩個未知數,怎麼可以得到結果呢?在這裏我還要說明一點,因爲在做語音分幀的時候幀的長度都是2的N次方,憑藉着直覺,我將 x=512x=512 帶入方程,解得 y=160y=160,並將這一組結果帶入該函數,恰好得出我們需要的497幀語音數據。有了這個數據,我們便可以愉快的玩耍了。哈哈哈~~

2.2 繪製結果&顯示

這裏我直接調用的別人封裝好的函數,並編輯了一下代碼,如下:

[x1,fs]=audioread('test3.wav');%讀取wav文件
%調用別人封裝好的函數獲取mfcc數據
ccc=Nmfcc(x1,fs,24,512,160);%mfcc特徵值提取
%注意一下,這裏的函數中需要傳參,這裏的512和160就是我們剛剛計算得出的數值。
%提取每一幀數據的第一列
xx=ccc(:,1)';
set(gcf,'position',[180,160,960,600]);%設置畫圖的大小
plot(xx);
grid on;

執行完之後我們即可看到畫出的波形圖:
在這裏插入圖片描述
由於前邊調用了別人寫好的函數,但是我也對其封裝函數進行了適當修改,這裏我就將修改後的函數展示如下:

%MFCC計算函數
function ccc=Nmfcc(x,fs,p,frameSize,inc)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                 function ccc=Nmfcc(x);
% x是輸入語音序列,Mel濾波器的個數爲p,採樣頻率爲fs,frameSize爲幀長和FFT點數,inc爲幀移;ccc爲MFCC參數。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% 按幀長爲frameSize,Mel濾波器的個數爲p,採樣頻率爲fs
% 提取Mel濾波器參數,用漢明窗函數
bank=melbankm(p,frameSize,fs,0,0.5,'m');
% 歸一化Mel濾波器組係數
bank=full(bank);
bank=bank/max(bank(:));

% DCT係數,12*p
for k=1:13
  n=0:p-1;
  dctcoef(k,:)=cos((2*n+1)*k*pi/(2*p));
end

% 歸一化倒譜提升窗口
w = 1 + 6 * sin(pi * [1:13] ./ 12);
w = w/max(w);

% 預加重濾波器
xx=double(x);
xx=filter([1 -0.9375],1,xx);

% 語音信號分幀
xx=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));
  c2 = c1.*w';
  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;

%求取二階差分系數
dttm = zeros(size(dtm));
for i=3:size(m,1)-2
  dttm(i,:) = -2*dtm(i-2,:) - dtm(i-1,:) + dtm(i+1,:) + 2*dtm(i+2,:);
end
dttm = dttm / 3;

%合併MFCC參數、一階差分MFCC參數、二階差分MFCC參數
ccc = [m dtm];

%去除首尾兩幀,因爲這兩幀的一階差分參數爲0
%ccc = ccc(3:size(m,1)-2,:);

3.繪製自己編寫的matlab函數提取出來的mfcc特徵值

這裏我們需要調用自己之前寫過的一個獲取mfcc特徵值的函數,來與之前的兩種方式得出的數據進行對比,在這裏我還需要說一下,因爲函數是自己寫的,基本沒調用什麼函數,所以代碼量而言,就比其它兩種方式的多很多。

%得到返回結果
myMfcc=Mymfcc();
%獲取矩陣的第一列
xxx=myMfcc(:,1)';
%開始繪製圖像
set(gcf,'position',[180,160,960,600]);%設置畫圖的大小
plot(xxx);
grid on;

這裏我對自己的函數進行了一下封裝,大家可以來看一下效果:
在這裏插入圖片描述
當然其中大部分的工作都在下邊的函數裏邊,其內容如下:

function [mfcc_final]=Mymfcc()

frameSize=512;
inc=160;
[x,fs]=audioread('test3.wav');%讀取wav文件
N=length(x);
%預加重y=x(i)-0.97*x(i-1)
for i=2:N
    y(i)=x(i)-0.97*x(i-1);
end
y=y';%對y取轉置
S=enframe(x,frameSize,inc);%分幀,對x進行分幀,
[a b]=size(S);

%嘗試一下漢明窗a=0.46,得到漢明窗W=(1-a)-a*cos(2*pi*n/N)
n=1:b;
W=0.54-0.46*cos((2*pi.*n)/b);
%創建漢明窗矩陣C
C=zeros(a,b);
ham=hamming(b);
for i=1:a
    C(i,:)=ham;
end
%將漢明窗C和S相乘得SC
SC=S.*C;

%對SC的每一幀都進行N=4096的快速傅里葉變換,得到一個301*4096的矩陣
F=0;N=4096;
for i=1:a
    %對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

%開始計算譜熵
%求取P_i(k)
P=zeros(a,4096);
for i=1:a
    sum1=0;
    for t=1:N
        sum1=sum1+E(i,t);
    end
    for j=1:N
        P(i,j)=(E(i,j))/(sum1);
    end
end
%算出每一幀的譜熵H(i)
for i=1:a
    sum1=0;
    for j=1:N
        sum1=sum1+P(i,j)*log(P(i,j));
    end
    HH(i)=-sum1;
end
%計算譜熵結束


%將頻率轉換爲梅爾頻率
%梅爾頻率轉化函數圖像
N1=length(x)
for i=1:N1
    mel(i)=2595*log10(1+i/700);
end

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頻率轉換爲頻率
W2=N/2+1;%fs/2內對應的FFT點數,2049個頻率分量

k=((N+1)*fm)/fs%計算28個不同的k值
hm=zeros(26,N);%創建hm矩陣
df=fs/N;
freq=(0:N-1)*df;%採樣頻率值

%繪製梅爾濾波器
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
%繪圖,且每條顏色顯示不一樣
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.1]);%設置顯示範圍
 
 %得到能量特徵參數的和
 H=E*hm';
 %對H作自然對數運算
 %因爲人耳聽到的聲音與信號本身的大小是冪次方關係,所以要求個對數
 for i=1:a
     for j=1:26
         H(i,j)=log(H(i,j));
     end
 end
 %作離散餘弦變換   具體參考信號檢測與估計的論文
 for i=1:a
     for j=1:26
         %先求取每一幀的能量總和
         sum1=0;
         %作離散餘弦變換
         for p=1:26
             sum1=sum1+H(i,p)*cos((pi*j)*(2*p-1)/(2*26));
         end
         mfcc(i,j)=((2/26)^0.5)*sum1;  
         %完成離散餘弦變換
     end    
 end
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %%%%%%%   以下爲求取mfcc的三個參數過程  %%%%%%%%%%
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
  %作升倒譜運算
 %因爲大部分的信號數據一般集中在變換後的低頻區,所以對每一幀只取前13個數據就好了
 J=mfcc(:,(1:13));
 %默認升到普係數爲22
 for i=1:13
     K(i)=1+(22/2)*sin(pi*i/22);
 end
 %得到二維數組feat,這是mfcc的第一組數據,默認爲三組
 for i=1:a
     for j=1:13
         L(i,j)=J(i,j)*K(j);
     end
 end
 feat=L;
 %接下來求取第二組(一階差分系數)301x13 ,這也是mfcc參數的第二組參數
 dtfeat=0;
 dtfeat=zeros(size(L));%默認初始化
 for i=3:a-2
     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:a-2
     dttfeat(i,:)=-2*dtfeat(i-2,:)-dtfeat(i-1,:)+dtfeat(i+1,:)+2*dtfeat(i+2,:); 
 end
 dttfeat=dttfeat/10;
 %這裏的10是根據數據確定的,默認爲2
 
 %將得到的mfcc的三個參數feat、dtfeat、dttfeat拼接到一起
 %得到最後的mfcc係數301x39
 mfcc_final=0;
 mfcc_final=[feat,dtfeat,dttfeat];%拼接完成

4.三種結果比較&結論

在這之前我們已經將三種方式的得到的mfcc特徵值分別進行了繪畫,但是我們很難直接看出差別,於是在這一節,我們嘗試將這三個顯示在一張圖中,從而比較其差別。

set(gcf,'position',[180,160,960,600]);%設置畫圖的大小
subplot(3,1,1);
plot(y,'r','linewidth',1.5)
grid on;title('kaldi生成的mfcc特徵值','FontSize',15)
subplot(3,1,2);
plot(xx,'m','linewidth',1.5)
grid on;title('調用matlab函數生成的mfcc特徵值','FontSize',15)
subplot(3,1,3);
plot(xxx,'linewidth',1.5)
grid on;title('自己編寫的函數生成的mfcc特徵值','FontSize',15)

說實話當我看到這個結果的時候還是有些驚詫的:
在這裏插入圖片描述
或許現在還看不出區別在哪裏,但是我現在告訴你,這個測試的語音數據內容說的是"武漢加油",四個字。這時候你可以再看一下其結果差異。我們可以看到後兩種方法都依稀可以將這四個字判別開來,而kaldi產生的函數顯然效果差些。再對其內容進行進一步分析:

%開始比較三種結果的差異
set(gcf,'position',[180,160,960,600]);%設置畫圖的大小
subplot(3,1,1);
plot(y,'r','linewidth',1.5)
grid on;title('kaldi生成的mfcc特徵值','FontSize',15)
axis([0 500 20 90])
hold on
%畫分割線
plot([126,126],[20,90],'g','linewidth',1.5);
plot([153,153],[20,90],'g','linewidth',1.5);
plot([183,183],[20,90],'g','linewidth',1.5);
plot([208,208],[20,90],'g','linewidth',1.5);
plot([245,245],[20,90],'g','linewidth',1.5);
%寫字

subplot(3,1,2);
plot(xx,'m','linewidth',1.5)
grid on;title('調用matlab函數生成的mfcc特徵值','FontSize',15)
hold on
%畫分割線
plot([126,126],[-20,20],'g','linewidth',1.5);
plot([153,153],[-20,20],'g','linewidth',1.5);
plot([183,183],[-20,20],'g','linewidth',1.5);
plot([208,208],[-20,20],'g','linewidth',1.5);
plot([245,245],[-20,20],'g','linewidth',1.5);
%寫字
hold off

subplot(3,1,3);
plot(xxx,'linewidth',1.5)
grid on;title('自己編寫的函數生成的mfcc特徵值','FontSize',15)
axis([0 500 -10 60])
hold on
%畫分割線
plot([126,126],[-10,60],'g','linewidth',1.5);
plot([153,153],[-10,60],'g','linewidth',1.5);
plot([183,183],[-10,60],'g','linewidth',1.5);
plot([208,208],[-10,60],'g','linewidth',1.5);
plot([245,245],[-10,60],'g','linewidth',1.5);
%寫字
text(128,20,'中','color','r','fontsize',20);
text(155,20,'國','color','r','fontsize',20);
text(185,20,'加','color','r','fontsize',20);
text(213,20,'油','color','r','fontsize',20);
hold off

其內容如下 所示:
在這裏插入圖片描述
相信在上邊的結果中我們可以清晰的看到,經過分析我也大致得到了以下結論:

  1. kaldi產生的mfcc特徵值在‘中‘,’國’這兩個字處的波形與其它兩個matlab函數生成的波形相差較大;
  2. 在kaldi產生的波形裏邊,在語音‘中’字之前出現了一個波峯,而在其它兩個波形中均未出現;
  3. 調用matlab函數產生的那個波形,也就是第二個波形,在語音剛開始的那一段,峯值較高,與其它兩個波形不一致;
  4. 還有一個問題需要指出的是如果單要比較這三個mfcc數據得到的速度的話,kaldi速度>調用matlab函數的速度>自己書寫的函數的速度;
  5. 如果單從效果來說的話,自己書寫的函數的效果>調用matlab函數的效果>kaldi的效果。

如果單從上邊的的一條數據就得出一些結論,顯然還是沒有那麼有說服力,接下來我又對其它兩條語音數據進行了測試,這是語音文件“武漢加油”的波形顯示。並且我還在該圖中顯示了語音信號的原始波形:
在這裏插入圖片描述
在上邊的圖示中我們可以發現我們之前說的那個異常波動點其實不是異常波動點,而是我們的原始語音信號中的噪聲影響。再仔細分析就可發現,

  1. 三個波形中,是kaldi所生成的波形與原始波形一致性最高,但是語音開始的那段噪聲並未明顯顯示;
  2. 調用別人matlab函數的波形圖雖然將語音開始的那段噪聲波動顯示,但在”“字的波形與kaldi的相差較大;
  3. 而自己編寫的函數則集成了kaldi的缺點和別人matlab函數的缺點,着實有點悲哀。

無獨有偶,測試另一個語音文件,該語音文件所說的是“你好,其問題就和我們剛剛總結的現象一致:如下所示
在這裏插入圖片描述
另外在這三個語音文件產生的幾個波形圖中,我們可以看到kaldi版本的波形與原始波形一致性較高,其次是調用matlab版本的調用其它函數所產生的數據,自己編寫的函數則是與兩者無論速度還是性能都與其它兩種方法有差別,但是我並沒有就此認輸,我會查清楚kaldi版本所產生的數據準確度高的原因,並將在日後的研究中將差距原因再做總結。現在我再將此次實驗得出的結果進行如下總結:

  1. kaldi版本的特徵值質量>調用別人函數的matlab版本的特徵值質量>自己編寫的;
  2. 兩種matlab形式的在含有語音段的語音趨勢與kaldi的和原始語音趨勢有些許差距。

或許我在此做出的誤差分析並不是真正的原因,如果各位讀者有知道箇中原因的,非常感謝您的指導,也歡迎大家一起討論誤差原因。

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