基于动态时间规整(DTW)的孤立字语音识别


注意!这个博客里给出的文件是 在matlab2012版本里使用的,而我学习时使用的时matlab2018b,因此发现了很多新版本不兼容的问题,但我没有给出修改后能直接在新版本中用的代码。

另外,matlab要安装voicebox才可以正常进行实验!

还有一件事,录音文件要修改好文件名和文件目录,不然会检测不到。

   

这个实验在没有同学和老师的指导和帮助下完成,一共花了大概两周多(也没有每天做啦),请珍惜劳动成果,不要直接拿去装逼。

   

有个关键函数提前说一下:

DTW函数:myDTW

功能:利用DTW完成模式匹配

调用格式:

[cost] = myDTW(F,R)

参数说明:输入参数F为模板的MFCC特征;R为当前待识别语音的MFCC。输出参数cost为F和R之间的匹配距离。

   

看看目录

         

下面是具体的程序清单:

 
function [cost] = myDTW(F,R)
[r1,c1] = size(F); %模板的维度
[r2,c2] = size(R); %当前待识别语音的维度
distance = zeros(r1,r2);
for n = 1:r1
for m=1:r2
FR = F(n,:)-R(m,:);
FR = FR.^2;
distance(n,m) = sqrt(sum(FR))/c1; %采用欧式距离度量F和R的相似程度
end
end
D=zeros(r1+1,r2+1);
D(1,:)=inf;
D(:,1)=inf;
D(1,1)=0;
D(2:(r1+1),2:(r2+1)) =distance;
%寻找整个过程的最短匹配距离
for i=1:r1;
for j= 1:r2;
[dmin] = min([D(i,j),D(i,j+1),D(i+1,j)]);
D(i+1,j+1) =D(i+1,j+1) +dmin;
end
end
cost =D(r1+1,r2+1); %返回最终距离,没有反向回溯
       

这个基于动态时间规整(DTW)的孤立字语音识别的实验,具体步骤有很多,都写在下面了:

(1)训练过程:运行setTemplates.m文件

1)实验的语音来自三个人,每人说一遍0~9这十个数字,记录下相应的语音,保存在 目录“SpeechData”中。以第一个人的语音“1”为例,首先,调用[speechIn1,FS1] =audioread(q)读入语音“1”,保存在speechIn1中,并获得采样率FS1。

2)对speechIn1进行预处理,首先调用my_vad函数进行端点检测,提取出有效语音段,然后调用mfccf函数对有效语音段提取mfcc特征。

3)以同样的方式处理其他训练语音,把第一/二/三个说话人的十个数字0~9语音分别保存s1/s2/s3结构体中,并最终保存在Vectors1.mat/Vectors2.mat/Vectors3.mat文件中。

(2)识别过程:运行matchTemplates.m文件

1) 首先程序提示“Press any key to start 2 seconds of speech recording…”,在MATLAB主窗口按任意键开始录入待识别的语音,程序将录入的语音保存在speechIn变量中。

2) 对speechIn进行预处理,包括端点检测和mfcc特征提取,这一过程和训练中完全相同,得到(NP)矩阵。其中N为帧数,P为维度,接着调用CMN函数,对每一维度分量进行标准化,结果保存在rMatrix中。

3) 调用DTWScores函数,将当前待识别语音的rMatrix与训练过程中保存的s1、s2、s3中 的0~9各数字匹配,此函数中调用myDTW的匹配过程,返回rMarix和每个说话人说的每一个数字的匹配分值,保存在Sco中((3*10)的矢量)。

4) 将Sco从大到小进行排序,首先得到每一个模板匹配的两个最低分值对应的数字序号(有2*3=6个序号),而后返回出现频率最高的序号作为判决结果,从而完成过程。

       

但是讲这么多,还是很难把实验完成对不对,现在我把全部用到的都放出来

 

识别过程matchTemplates.m程序清单:

 
clear all; 
close all;
ncoeff = 12; %MFCC参数阶数
N = 10; %10个数字
fs=16000; %采样频率 
duration2 = 2; %录音时长
k = 3; %训练样本的人数
speech = audiorecorder(fs,16,1);
disp('Press any key to start 2 seconds of speech recording...'); 
pause
disp('Recording speech...'); 
recordblocking(speech,duration2) % duration*fs 为采样点数 
speechIn=getaudiodata(speech);
disp('Finished recording.');
disp('System is trying to recognize what you have spoken...');
speechIn = my_vad(speechIn); %端点检测 
rMatrix1 = mfccf(ncoeff,speechIn,fs); %采用MFCC系数作为特征矢量
rMatrix = CMN(rMatrix1); %归一化处理 
Sco = DTWScores(rMatrix,N); %计算DTW值
[SortedScores,EIndex] = sort(Sco,2); %按行递增排序,并返回对应的原始次序
Nbr = EIndex(:,1:2) %得到每个模板匹配的2个最低值对应的次序
[Modal,Freq] = mode(Nbr(:)); %返回出现频率最高的数Modal及其出现频率Freq
Word = char('zero','One','Two','Three','Four','Five','Six','Seven','Eight','Nine'); 
if mean(abs(speechIn)) < 0.01
 fprintf('No microphone connected or you have not said anything.\n');
else if (Freq <2) %频率太低不确定
 fprintf('The word you have said could not be properly recognised.\n');
else
 fprintf('You have just said %s.\n',Word(Modal,:)); 
end
 

 

训练过程setTemplates----程序清单:

 
%设置模板
ncoeff=12; %mfcc系数的个数
fMatrix1 = cell(1,10);
fMatrix2 = cell(1,10);
fMatrix3 = cell(1,10); 
for i = 1:10
 q = ['F:\ SpeechData\p1\' num2str(i-1) '.wav'];
 [speechIn1,FS1] = audioread(q);
 speechIn1 = my_vad(speechIn1); 
 fMatrix1(1,i) = {mfccf(ncoeff,speechIn1,FS1)}; 
  
end
for j = 1:10
 q = ['F: \SpeechData\p2\' num2str(j-1) '.wav'];
 [speechIn2,FS2] = audioread(q);
 speechIn2 = my_vad(speechIn2); 
 fMatrix2(1,j) = {mfccf(ncoeff,speechIn2,FS2)}; 
end
for k = 1:10
 q = ['F: \SpeechData\p2\' num2str(k-1) '.wav'];
 [speechIn3,FS3] = audioread(q);
 speechIn3 = my_vad(speechIn3); 
 fMatrix3(1,k) = {mfccf(ncoeff,speechIn3,FS3)};
end
%将数据保存为mat文件
fields = {'zero','One','Two','Three','Four','Five','Six','Seven','Eight','nine'};
s1 = cell2struct(fMatrix1, fields, 2); %fields项作为行
save Vectors1.mat -struct s1;
s2 = cell2struct(fMatrix2, fields, 2);
save Vectors2.mat -struct s2;
s3 = cell2struct(fMatrix3, fields, 2);
save Vectors3.mat -struct s3;
 

 

端点检测my_vad()函数----程序清单:

 
function trimmed_X = my_vad(x)
%端点检测;输入为录入语音,输出为有用信号
Ini = 0.1; %初始静默时间
Ts = 0.01; %窗的时长
Tsh = 0.005; %帧移时长
Fs = 16000; %采样频率
counter1 = 0; %以下四个参数用来寻找起始点和结束点
counter2 = 0;
counter3 = 0;
counter4 = 0;
ZCRCountf = 0; %用于存储过零率检测结果
ZCRCountb = 0; 
ZTh = 40; %过零阈值
w_sam = fix(Ts*Fs); %窗口长度
o_sam = fix(Tsh*Fs); %帧移长度
lengthX = length(x);
segs = fix((lengthX-w_sam)/o_sam)+1; %分帧数
sil = fix((Ini-Ts)/Tsh)+1; %静默时间帧数
win = hamming(w_sam);
Limit = o_sam*(segs-1)+1; %最后一帧的起始位置
FrmIndex = 1:o_sam:Limit; %每一帧的起始位置
ZCR_Vector = zeros(1,segs); %记录每一帧的过零点数 
%短时过零点
for t = 1:segs
 ZCRCounter = 0; 
 nextIndex = (t-1)*o_sam+1;
 for r = nextIndex+1:(nextIndex+w_sam-1)
 if (x(r) >= 0) && (x(r-1) >= 0)
 
 elseif (x(r) > 0) && (x(r-1) < 0)
 ZCRCounter = ZCRCounter + 1;
 elseif (x(r) < 0) && (x(r-1) < 0)
 
 elseif (x(r) < 0) && (x(r-1) > 0)
 ZCRCounter = ZCRCounter + 1;
 end
 end
 ZCR_Vector(t) = ZCRCounter;
end
%短时平均幅度
Erg_Vector = zeros(1,segs);
for u = 1:segs
 nextIndex = (u-1)*o_sam+1;
 Energy = x(nextIndex:nextIndex+w_sam-1).*win;
 Erg_Vector(u) = sum(abs(Energy));
end
IMN = mean(Erg_Vector(1:sil));  %静默能量均值(噪声均值)
IMX = max(Erg_Vector); %短时平均幅度的最大值
I1 = 0.03 * (IMX-IMN) + IMN;  %I1,I2为初始能量阈值
I2 = 4 * IMN;
ITL = 100*min(I1,I2); %能量阈值下限,前面系数根据实际情况更改得到合适结果
ITU = 10* ITL;  %能量阈值上限
IZC = mean(ZCR_Vector(1:sil)); 
stdev = std(ZCR_Vector(1:sil));  %静默阶段过零率标准差
IZCT = min(ZTh,IZC+2*stdev);  %过零率阈值
indexi = zeros(1,lengthX); 
indexj = indexi; 
indexk = indexi;
indexl = indexi;
%搜寻超过能量阈值上限的部分
for i = 1:length(Erg_Vector)
 if (Erg_Vector(i) > ITU)
 counter1 = counter1 + 1;
 indexi(counter1) = i;
 end
end
ITUs = indexi(1); %第一个能量超过阈值上限的帧
%搜寻能量超过能量下限的部分
for j = ITUs:-1:1
 if (Erg_Vector(j) < ITL)
 counter2 = counter2 + 1;
 indexj(counter2) = j;
 end
end
start = indexj(1)+1; %第一级判决起始帧
 
Erg_Vectorf = fliplr(Erg_Vector);%将能量矩阵关于中心左右对称,若是一行向量相当于逆序 
%重复上面过程相当于找结束帧
for k = 1:length(Erg_Vectorf)
 if (Erg_Vectorf(k) > ITU)
 counter3 = counter3 + 1;
 indexk(counter3) = k;
 end
end
ITUf = indexk(1);
for l = ITUf:-1:1
 if (Erg_Vectorf(l) < ITL)
 counter4 = counter4 + 1;
 indexl(counter4) = l;
 end
end
finish = length(Erg_Vector)-indexl(1)+1;%第一级判决结束帧 
%从第一级判决起始帧开始进行第二判决(过零率)端点检测
BackSearch = min(start,25);
for m = start:-1:start-BackSearch+1
 rate = ZCR_Vector(m);
 if rate > IZCT
 ZCRCountb = ZCRCountb + 1;
 realstart = m;
 end
end
if ZCRCountb > 3
 start = realstart; 
end
FwdSearch = min(length(Erg_Vector)-finish,25);
for n = finish+1:finish+FwdSearch
 rate = ZCR_Vector(n);
 if rate > IZCT
 ZCRCountf = ZCRCountf + 1;
 realfinish = n;
 end
end
if ZCRCountf > 3
 finish = realfinish; 
end
x_start = FrmIndex(start); %最终的起始位置
x_finish = FrmIndex(finish-1); %最终的结束位置
trimmed_X = x(x_start:x_finish);
 

 

特征提取mfccf()函数----程序清单

   
function FMatrix=mfccf(num,s,Fs)
%计算并返回信号s的mfcc参数及其一阶和二阶差分参数
%参数说明: num 是mfcc系数阶数,Fs为采样频率
N=512; % FFT 数
Tf=0.02;  %窗口的时长
n=Fs*Tf;  %每个窗口的长度
M=24; %M为滤波器组数
l=length(s); 
Ts=0.01; %帧移时长
FrameStep=Fs*Ts; %帧移
a=1;
b=[1, -0.97];   %预加重处理的一阶FIR高通滤波器系数
noFrames=fix((l-n)/FrameStep)+1; %帧数
FMatrix=zeros(noFrames-2, num); %倒谱系数初始矩阵,一阶差分的首尾2帧为0,
lifter=1:num; %倒谱加权初始矩阵
lifter=1+floor(num/2)*(sin(lifter*pi/num));%倒谱加权函数
if mean(abs(s)) > 0.01
 s=s/max(s); %初始化
end

 
%计算 MFCC 系数

for i=1:noFrames
 frame=s((i-1)*FrameStep+1:(i-1)*FrameStep+n); %frame用于存储每一帧数据
 framef=filter(b,a,frame); %预加重滤波器
 F=framef.*hamming(n); %加汉明窗
 FFTo=fft(F,n); %计算FFT
 melf=melbankm(M,N,Fs); %计算mel滤波器组系数 
 halfn=1+floor(N/2); 
 spectr=log(melf*(abs(FFTo(1:halfn)).^2)+1e-22);%三角滤波器进行滤波加上1e-22防止进行对数运算后变成无穷

 
%进行DCT变换

c=zeros(1,num);
 for p=1:num
 for m=1:M
 c(p)=c(p)+spectr(m)*cos(p*(m-0.5)*pi/M);
 end
 end
 ncoeffs=c.*lifter; %对MFCC参数进行倒谱加权
 FMatrix(i, :)=ncoeffs; 
end
 
%调用deltacoeff函数计算MFCC差分系数

d=deltacoeff(FMatrix); %计算一阶差分系数
d1=deltacoeff(d); %计算二阶差分系数
FMatrix=[FMatrix,d,d1]; %将三组数据作为特征向量
   

 

melbankm()函数---程序清单:

 
function H=melbankm(M,N,fs,fl,fh)
%Mel滤波器组
% 输入参数: M 滤波器组数量
%  N FFT长度
%  fs 采样频率
%  fl -fh 为线性功率谱的有用频带(默认为0-0.5*fs)
%  
%输出参数: H返回滤波器组,每一行为一个滤波器
if nargin<4
 fl=0*fs;
 fh=0.5*fs;
end
%计算每个滤波器的中心频率
f=zeros(1,M+1);
for m=1:M+2
 f(m)=floor((N/fs)*mel2freq(freq2mel(fl)...
 +(m-1)*(freq2mel(fh)-freq2mel(fl))/(M+1)));
end
%求滤波器组H
c=floor(N/2)+1;
y=zeros(1,c);
H=zeros(M,c);
for m=2:M+1
 for k=1:c %由于fh最高为fs/2,那么最多需c位就能存储H
 if f(m-1)<=k&&k<=f(m)
 y(k)=(k-f(m-1))/(f(m)-f(m-1));
 elseif f(m)<=k&&k<=f(m+1)
 y(k)=(f(m+1)-k)/(f(m+1)-f(m));
 else
 y(k)=0;
 end
 end
 H(m,:)=y;
end
 

 

freq2mel()函数----程序清单:

 
function b=freq2mel(f)
%%实现线性频域到mel频域的转换
%输入参数:f为线性频域频率
%输出参数:b为mel频域频率
b=2595*log10(1+f/700);
 

 

mel2freq()函数----程序清单:

 
function f=mel2freq(b)
%%实现mel频域转换到线性频域
%输入参数:b为mel频域内频率
%输出参数:f为线性频域内频率
f=700*(10^(b/2595)-1);
 
deltacoeff()函数----程序清单:
function diff = deltacoeff(x)
%计算MFCC差分系数
[nr,nc]=size(x);
N=2;
diff=zeros(nr,nc);
for t=3:nr-2
 for n=1:N
 diff(t,:)=diff(t,:)+n*(x(t+n,:)-x(t-n,:));
 end
 diff(t,:)=diff(t,:)/10; %10=2*(1^2+2^2)
end
 

 

CMN()函数----程序清单

 
function NormMatrix = CMN(Matrix)
%归一化处理
[r,c]=size(Matrix);
NormMatrix=zeros(r,c);
for i=1:c
 MatMean=mean(Matrix(:,i)); 
 NormMatrix(:,i)=Matrix(:,i)-MatMean;
end
 

 

DTWScores()函数----程序清单

 
function AllScores = DTWScores(rMatrix,N)
%动态时间规整(DTW)寻找最小失真
%输入参数:rMatrix为当前读入语音的MFCC参数矩阵,N为每个模板数量词汇数
%输出参数:AllScores
%初始化DTW判别矩阵
Scores1 = zeros(1,N); 
Scores2 = zeros(1,N);
Scores3 = zeros(1,N);
%加载模板数据
s1 = load('Vectors1.mat');
fMatrixall1 = struct2cell(s1);
s2 = load('Vectors2.mat');
fMatrixall2 = struct2cell(s2);
s3 = load('Vectors3.mat');
fMatrixall3 = struct2cell(s3);
%计算DTW
for i = 1:N
 fMatrix1 = fMatrixall1{i,1};
 fMatrix1 = CMN(fMatrix1);
 Scores1(i) = myDTW(fMatrix1,rMatrix);
end
for j = 1:N
 fMatrix2 = fMatrixall2{j,1};
 fMatrix2 = CMN(fMatrix2);
 Scores2(j) = myDTW(fMatrix2,rMatrix);
end
for k= 1:N
 fMatrix3 = fMatrixall3{k,1};
 fMatrix3 = CMN(fMatrix3);
 Scores3(k) = myDTW(fMatrix3,rMatrix);
end
AllScores = [Scores1;Scores2;Scores3];