语谱图的生成

最近一直在做语音方面的工作,由于任务需要所以提取MFCC特征,并保存为语谱图,在以下的讲述中我并没有用到什么源码或者相关理论,但是它给了一个很好的诠释,让我在写自己的代码时有章可循,谢谢原著作者。


需要注意的是,下文中的data.txt的来源:

1.使用Matlab的函数audiorecorder进行录音
2.使用Matlab的函数getaudiodata获取音频数组
3.将获取的音频数组存入Data.txt文件。


原文参考链接:http://blog.csdn.net/xiao13yu14/article/details/46991581



MFCC参数提取步骤

——>预加重

——>分帧

——>对每一帧加窗

——>对每一帧补零

——>各帧信号的FFT变换及其功率谱

——>梅尔滤波(通过40个滤波器)

——>取对数

——>DCT变换

——>归一化

 

1.预加重

如果数据在低频的强度大于高频,就会不利于处理,因此需要通过一个传递函数为s[n]-a*s[n]的高通滤波器。滤去数据中的低频成分,使高频特性更加突现。

 

 

2.分帧

分帧就是将N个采样点集合成一个观测单位。我们设定每帧涵盖的时间是25ms,因为采样率是16000,所以得到每帧的样本点个数是400。

另外,为了避免相邻两帧的变化过大,因此会让两相邻帧之间有一段重叠区域。我们设定的重叠区域是15ms,所以就是每隔10ms取一帧。

 

 

3.对每一帧加窗

分帧后马上进行FFT,由于转换时会将帧内信号当作周期信号处理,所以在帧的两个端点处会发生突变,转换出来的频谱与原信号频谱差别很大。所以要对每一帧加窗,使帧内信号作FFT时的两个端点处不会发生突变。

我们采用的窗是汉明窗:(M为帧长,即400)

 

 

4.对每一帧补零

我们要对每一帧信号进行FFT,而FFT要求输入数据长度一定是2^K,现在一帧为400个采样点,所以补零至最接近的512位。

 

5.各帧信号的FFT变换及其功率谱

对分帧加窗后的各帧信号进行512点的FFT变换得到各帧的频谱。并对语音信号的频谱取模平方得到语音信号的功率谱。

 

6.梅尔滤波(通过40个滤波器)

40个三角滤波器在MEL谱上均匀分布,每两个滤波器间有50%的重叠部分。

所以要先把实际频率转换成梅尔频率,实际频率最小为0Hz,最大为16000 / 2 = 8000Hz 

 

转换成梅尔频率后,我们要实现的是40个滤波器,所以计算这40个滤波器的梅尔频率分布,然后把梅尔频率转换成实际频率

 

然后根据实际频率的数组,计算出f数组,公式如下(h数组就是40个滤波器的实际频率分布数组):

 

最后根据以下公式,计算滤波器的输出(m为滤波器的个数)

 

以下为10个滤波器的图:

 

 

7.取对数

三角窗滤波器组的输出求取对数,可以得到近似于同态变换的结果。

 

8.DCT变换

对对数能量梅尔谱进行DCT变换,取前13维输出,得到梅尔倒谱。

公式为:

 

 

9.归一化

对所有的梅尔倒谱归一化。

先求出所有倒谱向量的均值向量,公式为

 

再用每一个倒谱向量减去均值向量,即

 


C++代码实现

  1. #include<iostream>  
  2. #include<fstream>  
  3. #include<complex>  
  4. using namespace std;  
  5.   
  6. int filterNum = 40;  
  7. int sampleRate = 16000;  
  8.   
  9. #define Win_Time 0.025//把25ms里的所有点作为一个点分析   
  10. #define Hop_Time 0.01//每隔10ms分一次帧   
  11. #define Pi 3.1415927  
  12.   
  13. //1.预加重  
  14. double* pre_emphasizing(double *sample, int len, double factor)  
  15. {  
  16.     double *Sample = new double[len];  
  17.     Sample[0] = sample[0];  
  18.     for(int i = 1; i < len; i++)  
  19.     {  
  20.         //预加重过程  
  21.         Sample[i] = sample[i] - factor * sample[i - 1];   
  22.     }  
  23.     return Sample;  
  24. }   
  25.   
  26. void Hamming( double *hamWin, int hamWinSize )  
  27. {  
  28.     for (int i = 0; i < hamWinSize; i++)  
  29.     {  
  30.         hamWin[i] = (double)(0.54 - 0.46 * cos(2 * Pi * (double)i / ((double)hamWinSize - 1) ));  
  31.     }  
  32. }  
  33.   
  34. //计算每一帧的功率谱   
  35. void mfccFFT(double *frameSample, double *FFTSample, int frameSize, int pos)  
  36. {  
  37.     //对分帧加窗后的各帧信号进行FFT变换得到各帧的频谱  
  38.     //并对语音信号的频谱取模平方得到语音信号的功率谱  
  39.     double dataR[frameSize];  
  40.     double dataI[frameSize];  
  41.     for(int i = 0; i < frameSize; i++)  
  42.     {  
  43.         dataR[i] = frameSample[i + pos];  
  44.         dataI[i] = 0.0f;  
  45.     }  
  46.       
  47.     int x0, x1, x2, x3, x4, x5, x6, xx, x7, x8;  
  48.     int i, j, k, b, p, L;  
  49.     float TR, TI, temp;  
  50.     /********** following code invert sequence ************/  
  51.     for(i = 0; i < frameSize; i++)  
  52.     {  
  53.         x0 = x1 = x2 = x3 = x4 = x5 = x6 = x7 = x8 = 0;  
  54.         x0 = i & 0x01; x1 = (i / 2) & 0x01; x2 = (i / 4) & 0x01; x3 = (i / 8) & 0x01; x4 = (i / 16) & 0x01;   
  55.         x5 = (i / 32) & 0x01; x6 = (i / 64) & 0x01; x7 = (i / 128) & 0x01; x8 = (i / 256) & 0x01;  
  56.         xx = x0 * 256 + x1 * 128 + x2 * 64 + x3 * 32 + x4 * 16 + x5 * 8 + x6 * 4 + x7 * 2 + x8;  
  57.         dataI[xx] = dataR[i];  
  58.     }  
  59.     for(i = 0; i < frameSize; i++)  
  60.     {  
  61.         dataR[i] = dataI[i]; dataI[i] = 0;   
  62.     }  
  63.   
  64.     /************** following code FFT *******************/  
  65.     for(L = 1; L <= 9; L++)  
  66.     { /* for(1) */  
  67.         b = 1; i = L - 1;  
  68.         while(i > 0)   
  69.         {  
  70.             b = b * 2; i--;  
  71.         } /* b= 2^(L-1) */  
  72.         for(j = 0; j <= b-1; j++) /* for (2) */  
  73.         {  
  74.             p = 1; i = 9 - L;  
  75.             while(i > 0) /* p=pow(2,7-L)*j; */  
  76.             {  
  77.                 p = p * 2; i--;  
  78.             }  
  79.             p = p * j;  
  80.             for(k = j; k < 512; k = k + 2*b) /* for (3) */  
  81.             {  
  82.                 TR = dataR[k]; TI = dataI[k]; temp = dataR[k + b];  
  83.                 dataR[k] = dataR[k] + dataR[k + b] * cos(2 * Pi * p / frameSize) + dataI[k + b] * sin(2 * Pi * p / frameSize);  
  84.                 dataI[k] = dataI[k] - dataR[k + b] * sin(2 * Pi * p / frameSize) + dataI[k + b] * cos(2 * Pi * p / frameSize);  
  85.                 dataR[k + b] = TR - dataR[k + b] * cos(2 * Pi * p / frameSize) - dataI[k + b] * sin(2 * Pi * p / frameSize);  
  86.                 dataI[k + b] = TI + temp * sin(2 * Pi * p / frameSize) - dataI[k + b] * cos(2 * Pi * p / frameSize);  
  87.             } /* END for (3) */  
  88.         } /* END for (2) */  
  89.     } /* END for (1) */  
  90.     for(i = 0; i < frameSize / 2; i++)  
  91.     {   
  92.         FFTSample[i + pos] = (dataR[i] * dataR[i] + dataI[i] * dataI[i]);   
  93.     }     
  94. }  
  95.   
  96. //参数说明:frameSample为处理之后的数组,Sample为对样本进行预加重之后的数组  
  97. //          len为Sample的长度,frameSize为每帧的样本点个数,frameSampleLen为处理之后的长度   
  98. double* mfccFrame(double *frameSample, double *Sample, int *len, int frameSize, int &frameSampleLen)  
  99. {  
  100.     double *hamWin;  
  101.     int hamWinSize = sampleRate * Win_Time;  
  102.     hamWin = new double[hamWinSize];  
  103.     Hamming(hamWin, hamWinSize);//计算hamming窗  
  104.       
  105.     int hopStep = Hop_Time * sampleRate;  
  106.     int frameNum = ceil(double(*len) / hopStep);//计算一共会有多少帧  
  107.     frameSampleLen = frameNum * frameSize;//经过处理之后的长度   
  108.     frameSample = new double[frameSampleLen];  
  109.     for(int i = 0; i < frameSampleLen; i++)  
  110.         frameSample[i] = 0;  
  111.       
  112.     double *FFTSample = new double[frameSampleLen];  
  113.     for(int i = 0; i < frameSampleLen; i++)  
  114.         FFTSample[i] = 0;  
  115.       
  116.     for(int i = 0; i * hopStep < *len; i++)//分帧   
  117.     {  
  118.         for(int j = 0; j < frameSize; j++)  
  119.         {  
  120.             if(j < hamWinSize && i * hopStep + j < *len)  
  121.                 frameSample[i * frameSize + j] = Sample[i * hopStep + j] * hamWin[j];  
  122.             else  
  123.                 frameSample[i * frameSize + j] = 0;//补0   
  124.         }  
  125.         mfccFFT(frameSample, FFTSample, frameSize, i * frameSize);  
  126.     }  
  127.       
  128.     ofstream fileFrame("C:\\Users\\john\\Desktop\\txt\\Frame.txt");  
  129.     for(int i = 0; i < frameSize; i++)  
  130.         fileFrame << frameSample[100 * frameSize + i] << endl;  
  131.       
  132.     delete []hamWin;  
  133.     return FFTSample;   
  134. }  
  135.   
  136. void DCT(double mel[400][40], double c[400][40], int frameNum)    
  137. {    
  138.     for(int k = 0; k < frameNum; k++)  
  139.     {  
  140.         for(int i = 0; i < 13; i++)    
  141.         {    
  142.             for(int j = 0; j < filterNum; j++)    
  143.             {      
  144.                 c[k][i] += mel[k][j] * cos(Pi * i / (2 * filterNum) *  (2 * j + 1));    
  145.                 //if(k == 0 && i ==0)  
  146.                     //cout << c[0][0] << endl;  
  147.             }       
  148.         }  
  149.     }  
  150.     //cout << "c[0][0] = " << c[0][0] << endl;  
  151. }    
  152.   
  153. void computeMel(double mel[400][40], int sampleRate, double *FFTSample, int frameNum, int frameSize)  
  154. {  
  155.     double freMax = sampleRate / 2;//实际最大频率   
  156.     double freMin = 0;//实际最小频率   
  157.     double melFremax = 1125 * log(1 + freMax / 700);//将实际频率转换成梅尔频率   
  158.     double melFremin = 1125 * log(1 + freMin / 700);  
  159.     double melFilters[filterNum][3];  
  160.     double k = (melFremax - melFremin) / (filterNum + 1);  
  161.       
  162.     double *m = new double[filterNum + 2];  
  163.     double *h = new double[filterNum + 2];  
  164.     double *f = new double[filterNum + 2];  
  165.       
  166.     for(int i = 0; i < filterNum + 2; i++)  
  167.     {  
  168.         m[i] = melFremin + k * i;  
  169.         h[i] = 700 * (exp(m[i] / 1125) - 1);//将梅尔频率转换成实际频率   
  170.         f[i] = floor((frameSize + 1) * h[i] / sampleRate);  
  171.     }         
  172.   
  173.     delete[] m;    
  174.     delete[] h;    
  175.     //delete[] f;  
  176.       
  177.     for(int k = 0; k < frameNum; k++)  
  178.     {  
  179.         for(int j = 0; j < filterNum; j++)  
  180.             mel[k][j] = 0;  
  181.     }  
  182.       
  183.     //计算出每个三角滤波器的输出: 对每一帧进行处理     
  184.     for(int i = 0; i < frameNum; i++)  
  185.     {  
  186.         for(int j = 1; j <= filterNum; j++)  
  187.         {  
  188.             double temp = 0;  
  189.             for(int z = 0; z < frameSize; z++)  
  190.             {  
  191.                 if(z < f[j - 1])  
  192.                     temp = 0;  
  193.                 else if(z >= f[j - 1] && z <= f[j])  
  194.                     temp = (z - f[j - 1]) / (f[j] - f[j - 1]);  
  195.                 else if(z >= f[j] && z <= f[j + 1])  
  196.                     temp = (f[j + 1] - z) / (f[j + 1] - f[j]);  
  197.                 else if(z > f[j + 1])  
  198.                     temp = 0;  
  199.                 mel[i][j - 1] += FFTSample[i * frameSize + z] * temp;   
  200.             }  
  201.         }  
  202.     }  
  203.       
  204.     ofstream fileMel("C:\\Users\\john\\Desktop\\txt\\Mel.txt");  
  205.     for(int i = 1; i <= filterNum; i++)  
  206.         fileMel << mel[100][i] << endl;  
  207.           
  208.     //取对数   
  209.     for(int i = 0; i < frameNum; i++)  
  210.     {  
  211.         for(int j = 0; j < filterNum; j++)  
  212.         {  
  213.             if(mel[i][j] <= 0.00000000001 || mel[i][j] >= 0.00000000001)  
  214.                 mel[i][j] = log(mel[i][j]);  
  215.         }  
  216.     }   
  217. }  
  218.   
  219. void writeToFile(int frameNum, int frameSize, int hopStep, double DCT[400][40], double *sample, double *Sample, double *frameSample, double *FFTSample, double mel[400][40])  
  220. {  
  221.     ofstream fileDCT("C:\\Users\\john\\Desktop\\txt\\DCT.txt");  
  222.     ofstream filesample("C:\\Users\\john\\Desktop\\txt\\sample.txt");  
  223.     ofstream filePreemphasized("C:\\Users\\john\\Desktop\\txt\\Preemphasized.txt");  
  224.     ofstream fileFft("C:\\Users\\john\\Desktop\\txt\\Fft.txt");  
  225.     ofstream fileLogMel("C:\\Users\\john\\Desktop\\txt\\LogMel.txt");  
  226.     for(int i = 0; i < frameSize; i++)  
  227.     {  
  228.         filesample << sample[100 * hopStep + i] << endl;  
  229.         filePreemphasized << Sample[100 * hopStep + i] << endl;  
  230.         fileFft << FFTSample[100 * frameSize + i] << endl;  
  231.     }  
  232.   
  233.     for(int i = 0; i < filterNum; i++)  
  234.         fileLogMel << mel[100][i] << endl;  
  235.       
  236.     //归一化  
  237. /*  for(int i = 0; i < 13; i++)  
  238.     { 
  239.         double sum = 0.0f; 
  240.         double Mrecording = 0.0f; 
  241.         for(int j = 0; j < frameNum; j++) 
  242.         { 
  243.             sum = sum + DCT[j][i]; 
  244.         } 
  245.         Mrecording = sum / frameNum; 
  246.         cout << Mrecording << endl; 
  247.         for(int j = 0; j < frameNum; j++) 
  248.         { 
  249.             DCT[j][i] = abs(DCT[j][i] - Mrecording); 
  250.         } 
  251.     } */  
  252.       
  253.     for (int i = 0; i < 13; i++)//write DCT  
  254.     {  
  255.         for (int j = 0; j < frameNum; j++)  
  256.             fileDCT << DCT[j][i] << " ";  
  257.         fileDCT << endl;  
  258.     }  
  259. }  
  260.   
  261. //MFCC  
  262. void MFCC(double *sample, int len)  
  263. {  
  264.     double factor = 0.95;//预加重参数  
  265.     double *Sample;  
  266.     //1.预加重   
  267.     Sample = pre_emphasizing(sample, len, factor);  
  268.       
  269.     //计算出每帧有多少个点,然后算出最接近点的个数的2^k,使得每帧的点的个数为2^k,以便进行补0   
  270.     int frameSize = (int)pow(2, ceil( log(Win_Time * sampleRate) / log(2.0) ));  
  271.     double *frameSample = NULL, *FFTSample = NULL;  
  272.     int frameSampleLen;  
  273.       
  274.     //分帧、加窗、功率谱   
  275.     FFTSample = mfccFrame(frameSample, Sample, &len, frameSize, frameSampleLen);  
  276.       
  277.     int hopStep = Hop_Time * sampleRate;//隔hopStep个样本点分一次帧   
  278.     int frameNum = ceil(double(len) / hopStep);//计算所有样本点一共有多少帧   
  279.   
  280.     double mel[400][40];  
  281.     computeMel(mel, sampleRate, FFTSample, frameNum, frameSize);  
  282.       
  283.     double c[400][40];  
  284.     for(int i = 0; i < 400; i++)  
  285.     {  
  286.         for(int j = 0; j < 40; j++)  
  287.             c[i][j] = 0;  
  288.     }  
  289.     DCT(mel, c, frameNum);  
  290.       
  291.     writeToFile(frameNum, frameSize, hopStep, c, sample, Sample, frameSample, FFTSample, mel);  
  292. }  
  293.   
  294. int main()  
  295. {  
  296.     ifstream filedata("C:\\Users\\john\\Desktop\\txt\\Data.txt");  
  297.     int len = 0;  
  298.     //读取wav文件的数值   
  299.     double *sample = new double[160000];  
  300.     while(!filedata.eof())  
  301.     {  
  302.         filedata >> sample[len];  
  303.         sample[len] = sample[len] * 1000;  
  304.         len++;  
  305.     }  
  306.     cout << len << endl;  
  307.     MFCC(sample, len);  
  308.     delete [] sample;  
  309.       
  310.     return 0;  
  311. }   


Matlab画出结果图

1.feature矩阵的色图

  1. clc;  
  2. clear all;  
  3. original = importdata('C:\\Users\\john\\Desktop\\txt\\Data.txt');  
  4. figure;  
  5. plot(original * 1000);  
  6. DCT = importdata('C:\\Users\\john\\Desktop\\txt\\DCT.txt');  
  7. figure;  
  8. imagesc(DCT);  
  9. colorbar;  
  10. xlabel('cepstrum after DCT');  


2.单帧数据变化图

  1. sample = importdata('C:\\Users\\john\\Desktop\\txt\\sample.txt');  
  2. subplot(2, 3, 1),  
  3. plot(sample);  
  4. axis([0, 400, -60, 60]);  
  5. xlabel('400 sample segment from 16kHz signal');  
  6.   
  7. yujiazhong = importdata('C:\\Users\\john\\Desktop\\txt\\Preemphasized.txt');  
  8. subplot(2, 3, 2),  
  9. plot(yujiazhong);  
  10. axis([0, 400, -20, 20]);  
  11. xlabel('preemphasized');  
  12.   
  13. frame = importdata('C:\\Users\\john\\Desktop\\txt\\Frame.txt');  
  14. subplot(2, 3, 3),  
  15. plot(frame);  
  16. axis([0, 400, -25, 25]);  
  17. xlabel('windowed')  
  18.   
  19. fft = importdata('C:\\Users\\john\\Desktop\\txt\\Fft.txt');  
  20. subplot(2, 3, 4),  
  21. max(fft)  
  22. plot(fft);  
  23. axis([0, 256, 0, 1000000]);  
  24. xlabel('Power spectrum');  
  25.   
  26. mel = importdata('C:\\Users\\john\\Desktop\\txt\\Mel.txt');  
  27. subplot(2, 3, 5),  
  28. plot(mel);  
  29. xlabel('40 point Mel spectrum');  
  30.   
  31. duishu = importdata('C:\\Users\\john\\Desktop\\txt\\LogMel.txt');  
  32. subplot(2, 3, 6),  
  33. plot(duishu);  
  34. axis([0, 40, 0, 15]);  
  35. xlabel('Log Mel spectrum');  


参考资料:

1.http://practicalcryptography.com/miscellaneous/machine-learning/guide-mel-frequency-cepstral-coefficients-mfccs/

2.http://www.speech.cs.cmu.edu/15-492/slides/03_mfcc.pdf

3.http://blog.csdn.net/zouxy09/article/details/9156785

4.http://my.oschina.net/jamesju/blog/193343


最后,引用龙应台的一句话作为总结:

”有些事,只能一个人做;有些关,只能一个人过;有些路啊,只能一个人走。“

  • 2
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值