利用DTW算法对声音信号的MFCC特征矢量矩阵进行模式匹配
该部分主要阐述了完整的语音信号处理的算法内容,其中包括语音信号预处理(信号分帧),端点检测,预加重,加窗,计算MFCC矩阵以及进行声音的识别匹配的DTW算法。接下来将按处理的流程进行阐释:
一、预处理
预处理的内容包括:
1、对于声音信号,人为的设定每一帧的帧时长fram_time(一般为20ms~40ms,本程序中设定为20ms)以及帧移系数fram_mov_rito(即相邻帧之间重叠的时长,本程序中设定帧移系数为0.5即10ms)。既定的声音信号的采样率fs(由录音设备决定,算法中初始定义为8000HZ)将决定每一帧应该包含多少个采样点。根据帧长与帧移可知,信号总点数L有L=K*1-fram_mov_rito*fram_time*fs=80*K
即总点数应该是80个采样点的整数倍。
然而由于读取的声音信号长度不为整,因此需要对长度进行修剪修剪为最接近80的整数倍的值。考虑到声音可能在结束的时候未停止,即包含的尾音,因此从声音信号的起始点进行剪裁。代码处理如下:
fscanf_s(fp, "%d", &voice_end);
useless= fgetc(fp);
for (con = 0;con<voice_end;con++)
{
fscanf_s(fp, "%lf", (y+ con));
useless = fgetc(fp);
}
//声音信号结束点为voice_end
for (voice_length = voice_end;voice_length % 160 != 0;voice_length--);
voice_start = voice_end - voice_length;
//声音信号长度为voice_length起始点为voice_start
y1 = y + voice_start - 1; //将声音起始指针指向起始点
2、录制的声音信号都不可避免的会有噪音干扰。为了能够不让噪声影响声音信号的端点检测以及特征向量的提取,必须在处理信号之前进行降噪处理。由于在无录音人人身时的背景噪音基本属于高斯白噪声,因此可以对噪声段进行时域均值滤波即可达到不不错的降噪效果。在这,本算法的改动在于,这里的“降噪”并不是真正意义上的将噪声降为0,而是通过对噪声的幅值计算均值(并不是信号的算术均值),并且将信号区分正负后加上(或减去)幅值的均值,可以通过Matlab的plot输出发现,噪声都被强行拉至一个相对平稳的水平,这对于之后的端点检测有很大帮助。另外,本算法设定取10帧即200ms的语音时长为噪声段,在提取噪声的时候从第2480个采样点开始计算,刻意规避了在录音设备刚启动时带来的噪声。代码处理如下
效果图:
可以清晰的看见在非语音段出现了一个明显的水平线(上为原信号下为处理信号)
代码部分
for (con = 2480;con < 4080;con++) //默认噪声帧数为10帧 { sum = sum + fabs(*(y + con)); } sum = sum / 1600.0;
for (con = 0;con < voice_length;con++) { if (*(y + con) > 0) *(p_voice + con) = *(y + con) - sum; else *(p_voice + con) = *(y + con) + sum; *(n_voice + con) = *(p_voice + con); } |
二、端点检测
端点检测为本算法包括所有语音信号处理过程中极为重要的一部分,端点检测的准确程度直接影响声音识别效果的好坏,因为准确的端点检测才能避免在对语音帧进行傅里叶变换的过程中不会受到太多无效信号段的影响。端点检测算法目前普遍使用的是短时过零率和短时幅度值的双阈值法。
1、双阈值判断
短时过零率即计算一帧语音中过零采样点的比例,当该比例大于一定值时认为该帧为有声帧。在本算法中,“过零”并非真正意义的越过0值极为过零。由于有噪声的存在不简单的认为过零值的信号为有声信号。在这里我们选择的是10帧噪声帧每一帧最大值的均值。之所以这样选择是因为在经过预处理后噪声基本在同一水平,在该水平下的信号一律认为是噪声或者清音(由于清音一定配合浊音,此处的判别会在后面提及),只有超过该水平的声音才认为是有效声音。在本算法中,为了进一步提升抗噪性能,在做了均值处理后,大于均值2倍的信号认为是有效信号值。
短时幅度值即计算一帧语音中幅度值的均值,当均值大于一定阈值时认为该帧为有声帧。在本算法中,对于该阈值的选择。经过测试,选在在噪声幅值最大值均值的50%。即一帧语音中有50%的信号的赋值都超过了门限值,则可认为这一帧是有效语音帧。
代码处理:
con = noise_start; //噪声段的起始点采用了与预处理相同的2480
temp = noise_start;
n_thl_ratio=2; s_thl_ratio=0.5;
for (con2 = 1;con2 <= noise_num;con2++)
//默认噪声帧数为10帧,来计算幅度阈值
{
for (con = temp;con < temp + noise_len-1;con++)
{
if (fabs(*(n_voice + con)) > noise_max)
noise_max = fabs(*(n_voice + con));
}
n_max_mean = n_max_mean + noise_max;
noise_max = 0.0;
temp = con;
}
n_max_mean = n_max_mean / (double)noise_num;
noise_thl = n_max_mean*n_thl_ratio;
s_thl = ((double)frame_len)*noise_thl*s_thl_ratio;
//printf("端点检测短时幅度值检测完成\n");
temp = (voice_length - frame_len) / (frame_len - frame_mov) + 1;
S = (double *)malloc(temp * sizeof(double));
Z = (double *)malloc(temp * sizeof(double));
//用于保存短时过零率和短时幅度值
for (temp = temp - 1;temp >= 0;temp--)
{
*(S + temp) = 0;
*(Z + temp) = 0;
}
frame = (double *)malloc((frame_len ) * sizeof(double));
frame_con = -1;
for (con2 = 0;con2 <= (voice_length - frame_len);con2 = con2 + (frame_len - frame_mov))
{
frame_con = frame_con + 1;
for (temp = con2, con = 0;temp <= con2 + frame_len - 1;temp++, con++)
*(frame + con) = *(n_voice + temp);
for (con = 0;con < frame_len;con++)
*(S + frame_con) = *(S + frame_con) + fabs(*(frame + con));
for (con = 0;con < frame_len - 1;con++)
{
if (*(frame + con) >= noise_thl)
last_sig = 1;
else if (*(frame + con) < (0.0 - noise_thl))
last_sig = -1;
if (last_sig == -1)
{
if (*(frame + con + 1) >= noise_thl)
*(Z + frame_con) = *(Z + frame_con) + 1;
}
else if (last_sig == 1)
{
if (*(frame + con + 1) < (0.0 - noise_thl))
*(Z + frame_con) = *(Z + frame_con) + 1;
}
}
//printf("完成短时过零率和短时幅度值\n");
}
效果图:
2、状态转换检验
刚刚提到在语音中出现清音时有可能出现无法满足双阈值的情况。同时,语音中出现的脉冲噪声则有可能恰恰相反,出现了满足双阈值之一的可能。如果仅仅在当前帧中对双阈值进行判断就判断是否为语音帧,则以上两种情况都有可能发生误判。因此选择使用状态转换判决,给声音设置4个状态:0——无声段 1——前端过渡段 2——有声段 3——后端过渡段。流程如下(初始状态为0——无声段):
(1)判断当前语音帧是否满足双阈值其一,如果满足进入(2)-(5),如果不满足()
(2)如果当前(即上一帧)状态为2—有声段,则保持不变。
(3)如果当前(即上一帧)状态为0—无声段,则状态变为1—前端过渡段,且前端过渡帧数加一,回到(1)。
(4)如果当前(即上一帧)状态为1—前端过渡段,如果前端过渡段帧数到达了最大帧数,则状态变为2—有声段,否则前端过渡帧数加一,回到(1)。
(5)如果当前(即上一帧)状态为3—后端过渡段,即在声音消失的时候又检测到有声音,认为是中间极短暂停顿,状态变为2—有声段,后端过渡帧数置0,回到(1)
(6)如果当前(即上一帧)状态为0—无声段,则保持不变,回到(1)。
(7)如果当前(即上一帧)状态为2—有声段,则状态变为3—后端过渡段,且后端过渡帧数加一,回到(1)。
(8)如果当前(即上一帧)状态为3—后端过渡段,如果后端过渡段帧数到达了最大帧数,则状态变为0—无声段,否则后端过渡帧数加一,回到(1)。
(9)如果当前(即上一帧)状态为1—前端过渡段,即在无声的时候又检测到有声音,认为是中间极短暂脉冲噪声,状态变为0—无声段,前端过渡段帧数置0,回到(1)。
代码处理:
for (con2 = 0;con2 <= (voice_length - frame_len);con2 = con2 + (frame_len - frame_mov))
{
frame_con = frame_con + 1;
if (*(S + frame_con) > s_thl || *(Z + frame_con) > z_thl)
{
if (cur_stus == 2)
;
else if (cur_stus == 0)
{
cur_stus = 1;
font_duration = 1;
}
else if (cur_stus == 1)
{
font_duration = font_duration + 1;
if (font_duration >= v_durmin_f)
{
cur_stus = 2;
valid_con = valid_con + 1;
//printf("第%d段语音", valid_con+1);
valid_start[valid_con] =( frame_con - v_durmin_f + 1);
//printf("当前帧为%d,起始帧为%d ", frame_con,(frame_con - v_durmin_f - 1));
font_duration = 0;
}
}
else if (cur_stus == 3)
{
back_duration = 0;
cur_stus = 2;
}
}
else
{
if (cur_stus == 0)
;
else if (cur_stus == 2)
{
cur_stus = 3;
back_duration = 1;
}
else if (cur_stus == 3)
{
back_duration = back_duration + 1;
if (back_duration >= s_durmax_f)
{
cur_stus = 0;
valid_end[valid_con] = (frame_con - s_durmax_f + 1);
back_duration = 0;