我们现在完整走一遍mfcc,假定,我们的‘开’音记录在bt4096[4096]数组之中,要搞定这个,前面博客有例子程序,我是一路走下来的,我们取样率8k/s,但我们只取4096字节,一个字节范围是0-255,记录波形以128为中心,上下波动,比如,一般噪音都在128取值附近,所以当我们要想知道一个波形的振幅A,用A=bt4096[i]-128来表达。
1,预加重,公式用程序语言表达,bt4096[i]=bt4096[i+1]-0.95*bt4096[i];以前经常处理图像用这样的公式,bt4096[i]=bt4096[i+1]-bt4096[i],他表示,两个像素之间颜色变化有多大,越小,说明比较平坦,越大,表示,这可能是边界上的点,想必是可以借鉴的吧(整幅图像如此处理,就是差分图像,他的意思是说,我只想看到整图的变化部分)!
2,首先应该是分帧,预加重抢了先,那就这样吧!256字节一帧,128为帧移动,那4096字节能有多少帧呢?4096/128=32,那么当取31帧时,已经只剩128字节了,不够取,所以我们干脆取30帧即可。
for (int i = 0; i < 30; i++)
{
double[] frame = new double[256];
for (int j = 0; j < 256; j++)
{
frame[j] = bt4096[i *128+ j ] ;
}
。。。。。。
}
3,汉明窗,别人讲的够多了,我们直接上代码:
for (int i = 0; i < 30; i++)
{ 。。。。。。
// windowing,加汉明窗
for (int j = 0; j < 256; j++)
{
frame[j] *= 0.54 - 0.46 * Math.Cos(2 * Math.PI * j / (256- 1));
}
/////////////////////
}
4,快速傅里叶变换fft,前面博客有,我就过了,用别人的,结果和我一样,已经验证。
需要注意的是,以下都砸在这个循环里for (int i = 0; i < 30; i++){。。。。。。}
5,分26组和计算s(m)=ln∑Hm(k)*|Xa(k)|^2,前面都有了,代码连起来的样子如下(这以下都是一帧frame【256】的处理,懂了,再放入循环里去):
double[] filterbank = new double[26];
double[] Xr = new double[256];
double[] Xi = new double[256];
for (int i = 0; i < 256; i++)
{
Xr[i] = frame[i];//实部
Xi[i] = 0;//虚部
}
//FFT//别人的fft已经验证过了,256=2^8(fft条件)
FFT(1, 256, Xr, Xi);//计算完,实部和虚部被重新写入值
//以下melfre用自己的202002062131
//假定300hz到8k,分26组
double min_Mel_frequency = Mel_Scale(1, 300); //mel=2595*Math.Log10(1+f/700)
double max_Mel_frequency = Mel_Scale(1, 8000);
double 梅尔间隔 = (max_Mel_frequency - min_Mel_frequency) / (26 + 1);
double[] 等距梅尔频率组 = new double[28];
double[] 等同mel组映射频率组fanli = new double[28];
int[] fft128样本点256映射temp = new int[28];
等距梅尔频率组[0] = min_Mel_frequency;
等同mel组映射频率组fanli[0] = 300;
for (int i = 1; i < 28; i++)
{
等距梅尔频率组[i] = min_Mel_frequency + 梅尔间隔 * i;
等同mel组映射频率组fanli[i] = Mel_Scale(-1, 等距梅尔频率组[i]);
fft128样本点256映射temp[i] = (int)((128 + 1) * 等同mel组映射频率组fanli[i] / 8000);
}
fft128样本点256映射temp[0] = (int)((128 + 1) * 等同mel组映射频率组fanli[0] / 8000);//记住,要取整数202002061940
//下一步,可以求Hm(k)了。m是26组filterbanks,k在4到128之间。因为是从300开始的。
//画一个256(128*2)的图出来?ok
for (int k = 4; k < 128; k++)
{
// //取平方值
double power = (Xr[k] * Xr[k] + Xi[k] * Xi[k]) / 256;
for (int m = 0; m < 26; m++)
{
double[] frequency_boundary = new double[3];
frequency_boundary[0] = fft128样本点256映射temp[m + 0];
frequency_boundary[1] = fft128样本点256映射temp[m + 1];
frequency_boundary[2] = fft128样本点256映射temp[m + 2];
if (frequency_boundary[0] <= k && k <= frequency_boundary[1])
{
double hmk = (k - frequency_boundary[0]) / (frequency_boundary[1] - frequency_boundary[0]);
filterbank[m] += power * hmk;
}
else if (frequency_boundary[1] <= k && k <= frequency_boundary[2])
{
double hmk = (frequency_boundary[2] - k) / (frequency_boundary[2] - frequency_boundary[1]);
filterbank[m] += power * hmk;
}
}
}
6,取对数(自然对数):
for (int i = 0; i < 26; i++)
{
filterbank[i] = Math.Log(filterbank[i]);
}
7,进行离散余弦变换,他们说,在江湖混,有一天是要还回来的(离散余弦变换)。或许还回来的是惊喜,是衣锦还乡。
//DCT//dct不用验证了,因为他的fft是ok的。
DCT(1, 26, filterbank);//离散余弦变换实质是fft的实部计算,非常省心。
//获取MFCC特征向量
for (int i = 0; i < 13; i++)
{
feature_vector[i] = filterbank[i];//特征在这里边,乡音未改。
}
好了打完,收工,代码走完,这是不带画图的(画图,耗时,有时还容易出错),画图是给自己学习和别人学习看的,至于去试一试esp8266WiFi开关灯,前面博客代码已经有了,如果你通过对一个实时语音4096字节mfcc计算,如果是开音特征,就让你的台灯亮起来吧!
相信你的台灯是可以亮起来的!千呼万唤始出来。