前段时间参与了一个小组项目,其中涉及到了MATLAB进行“上”、“下”、“左”、“右”的语音识别。查了一些资料、进行些许调试算是把问题解决了。
目录
一、识别原理
对于语音识别,首先确定基本原理是利用MFCC算法提取声音特征,然后将特征转化成的向量进行分类,根据训练特点可以考虑采用基于SVM算法的纠错输出码(Error-Correcting Output Codes, ECOC)算法。
1.MFCC算法
MFCC(Mel Frequency Cepstral Coefficients)是一种用于特征提取的算法,其通过傅立叶变换、梅尔滤波和DCT等,从时域转换到梅尔频率倒谱空间,提取声音的形状特征。
1. 傅立叶变换(FFT):对音频信号进行快速傅立叶变换,从时域转换到频域。
2. 梅尔滤波器组(Mel Filterbank):将频域信号通过一个梅尔滤波器组进行过滤。梅尔滤波器组模拟人耳对频率的感知,低频分辨率高,高频分辨率低。
3. 对梅尔滤波器组输出取对数:为了弱化高能成分的影响。
4. 离散余弦变换(DCT):将对数梅尔频率能量谱转换到梅尔频率倒谱空间。DCT可以将相关性信息压缩到少数系数中。
5. 取前N个系数。这些系数即为MFCC特征。
2.SVM算法
SVM(支持向量机)算法的基本思想和工作原理是将数据映射到高维空间,在这个空间中找到最大间隔超平面进行分类,其基本原理是:
1. 将原始输入空间中的数据映射到一个高维特征空间,使得原来不线性可分的两个类在这个新空间中可以线性分开。这种映射是通过核函数来实现的。
2. 在这个新空间中找到一个超平面,使得这个超平面可以将两个类隔开,且离这两个类最近的样本点与超平面的距离最大。这两个离超平面最近的样本点称为支持向量。
3. 找到的这个最大间隔超平面就是SVM的决策面。新样本点将根据它与这个超平面之间的位置关系来判断属于哪一类。
3.ECOC(纠错输出码)
SVM在解决本问题中存在一个不可忽视的问题,那就是SVM算法是一种二分类的算法,然而实际上本问题的语音指令识别要求识别“上”、“下”、“左”、“右”四种指令。此时,纠错输出码(Error-Correcting Output Codes, ECOC)将作为一个很好的解决思路,ECOC的工作原理如下:
- ECOC将问题分解成多个二分类子问题。对于K类分类问题,ECOC的二值编码矩阵,其中每一行对应一个类,每一列对应一个二分类器。
- 二值编码矩阵中的每个元素都取0或1,用来表示该类是否属于该二分类器正例或反例集合。
![](https://i-blog.csdnimg.cn/blog_migrate/95311eee0d661d1f63fa952e91422208.png)
- 然后根据编码矩阵训练出L个二分类器。在预测阶段,每个样本被每个二分类器判断,获得一个长为L的二值代码向量。
- 最后将该样本的代码向量与每一行编码进行汉明距离计算,预测属于距离最小的那一行对应的类。
二、代码原理
在完成原理设计的确认后,就开始完成代码的编写。代码总体流程设计如下:
1.训练集的收集
getVoice.m用于实现基于能量特征的语音关键词触发录音功能,录音片段会自动命名并保存到文件,可以用于后续语音识别模型训练,函数流程如下:
1. 定义了关键词列表和能量阈值。
2. 初始化麦克风录音设备,设置采样参数。
3. 输入需要录制的每个类别的样本数量。
4. 开始循环录制每个类别的语音:
- 实时从麦克风读取音频数据。
- 计算音频能量特征,判断是否超过阈值。
- 如果超过阈值,则将该段音频保存到文件。
- 循环录制,直到录制数量达到设定值。
function getVoice()
% 该函数用于录入音频
% 音频格式满足voiceTrain
% 关键词
keywords={'up','down','left','right'};
% 定义触发阈值,根据实际情况调整
thresholds = 0.0008;
% 定义声音频率
fs = 44100;
% 定义采样参数SamplesPerFrame
SPF = 1024*32;
% 定义文件位置
filePath = strcat('./voice',num2str(fs),'/');
% 获取麦克风设备信息
% devices = audiodevinfo;
% 创建麦克风输入对象
reader = audioDeviceReader('Device',"Default",'SamplesPerFrame',SPF);
% setup(reader);
% 输入每个类别训练数量
numVoice = input('请输入每个类别训练数量');
% while(~isnumerictype(numVoice))
% numVoice = input('请输入每个类别训练数量');
% end
% 定义文件号
n = 0;
% 开始语音录制
disp('开始实时语音识别...');
for k = 1:size(keywords,2)
kw = cell2mat(keywords(k)); % 定义当前类别
disp(strcat('请说:',kw));
firstAudio = reader();
% 当前类别的理论最大文件序号
knumVoice = numVoice;
while(n < knumVoice)
% 待提取的二倍长度原始音频
rawAudio = [firstAudio;reader()];
% 后半段音频将作为下次的前半段
firstAudio = rawAudio(SPF+1:end,:);
% 音频的振幅最高点作为中点
[~,maxPoint] = max(rawAudio);
% 将rawAudio振幅最高点的两侧作为判断的音频
if(maxPoint - SPF/2 >= 1 && maxPoint + SPF/2 - 1 <= size(rawAudio,1))
audio = rawAudio(maxPoint-SPF/2:maxPoint+SPF/2-1,:);
else % 可以认为音频不完整
continue;
end
% 计算音频能量特征
energy = sum(abs(audio).^2) / length(audio); % 计算音频能量特征
if(energy > thresholds) % 能量超过阈值
% 完整文件路径
fullPath = strcat(filePath,kw,int2str(n),'.wav');
while(exist(fullPath,'file') == 2) % 文件已存在
% 设置文件序号的偏移
knumVoice = knumVoice + 1;
n = n + 1;
fullPath = strcat(filePath,kw,int2str(n),'.wav');
end
audiowrite(fullPath,audio,fs);
disp(strcat('获得数据',kw,int2str(n)));
n = n +1;
end
end
n = 0;
end
% release(reader);
disp('识别结束,请自行清理不良音频并注意文件命名');
end
2.模型的训练
voiceTrain.m用于实现基于SVM的语音分类模型训练,函数流程如下:
1. 定义关键词列表和声音文件路径。
2. 初始化训练数据矩阵和标签向量。
3. 开始循环每个类别:
- 按文件名顺序读取该类别下所有语音文件。
- 对读取的音频数据进行预处理,提取特征。
- 将特征数据添加到训练数据矩阵,同时将类别标签添加到标签向量。
4. 循环读取完一个类别后,标签值加1,表示下一个类别。
5. 循环读取完所有类别后,训练SVM多分类模型。
6. 将训练好的SVM模型保存到文件。
function voiceTrain()
% 该函数用于训练SVM模型
% 声音的录取应遵循相应格式
% 关键词
keywords={'up','down','left','right'};
% 声音频率
fs = 44100;
% 定义文件位置
filePath = strcat('./voice',num2str(fs),'/');
% 训练数据,每行代表一个样本,每列代表一个特征
trainData = [];
% 训练标签,是一个列向量
trainLable = [];
% 训练类
classTag = 1;
for kw = keywords
k = 0; % 该类别下声音的序号
fileName = strcat(filePath,cell2mat(kw),num2str(k),'.wav'); % 文件名
while(exist(fileName,"file") == 2) % 直到文件不存在
tempData = dataProcess(audioread(fileName),fs); % 读取并处理音频
trainData = [trainData;tempData]; % 添加训练数据
trainLable = [trainLable;classTag]; % 添加训练标签
k = k + 1;
fileName = strcat(filePath,cell2mat(kw),num2str(k),'.wav'); %下一个文件名
end
disp(strcat(kw,num2str(k)));
classTag = classTag + 1;
end
% 训练并保存SVM模型
voiceModel = fitcecoc(trainData, trainLable);
save('voiceModel.mat',"voiceModel",'-mat');
end
3.收集待分类音频样本并进行分类
voiceClassifier用于实现基于训练好的SVM模型进行语音实时识别的功能,代码流程如下:
1. 定义必要参数,如关键词、采样率等。
2. 初始化麦克风录音设备。
3. 设置能量阈值,加载已训练好的SVM模型。
4. 开始实时循环录音和识别:
- 从麦克风读取音频数据。
- 判断音频能量是否超过阈值。
- 如果超过,则提取语音片段,进行预处理得到测试特征。
- 将测试特征输入训练好的SVM模型进行分类。
- 输出分类结果。
function voiceClassifier()
% 定义声音频率
fs = 44100;
% 定义采样参数SamplesPerFrame
SPF = 1024*32;
% 关键词
keywords={'up','down','left','right'};
% 获取麦克风设备信息
% devices = audiodevinfo;
% 创建麦克风输入对象
reader = audioDeviceReader('Device',"Default",'SamplesPerFrame',SPF);
setup(reader);
% 定义触发阈值
thresholds = 0.0008; % 根据实际情况调整阈值
% 加载训练好的SVM模型
voiceModel = load('voiceModel.mat');
voiceModel = voiceModel.voiceModel;
% 开始实时语音识别
disp('开始实时语音识别...');
firstAudio = reader(); % 第一段音频
while true
% 待提取的二倍长度原始音频
rawAudio = [firstAudio;reader()];
% 后半段音频将作为下次的前半段
firstAudio = rawAudio(SPF+1:end,:);
% 音频的振幅最高点作为中点
[~,maxPoint] = max(rawAudio);
% 将rawAudio振幅最高点的两侧作为判断的音频
if(maxPoint - SPF/2 >= 1 && maxPoint + SPF/2 - 1 <= size(rawAudio,1))
audio = rawAudio(maxPoint-SPF/2:maxPoint+SPF/2-1,:);
else
continue;
end
% 计算音频能量特征
energy = sum(abs(audio).^2) / length(audio);
if(energy > thresholds) % 能量超过阈值
% plot(audio);
% drawnow;
% 音频的预处理
testData = dataProcess(audio,fs);
% 使用训练好的模型对测试数据进行分类
[predictedLabels,scores] = predict(voiceModel, testData);
% 输出测试结果
if(scores(predictedLabels)>-0.3)
disp(keywords(predictedLabels));
disp(num2str(scores));
end
end
end
end
4.音频的向量化处理
dataProcess.m用于实现语音信号的预处理以及提取MFCC特征,其同时被voiceTrain和voiceClassifier调用,代码流程如下:
1. 对原始语音信号进行低通滤波,滤除低频噪声。
2. 再进行高通滤波,滤除高频噪声。
3. 调用mfcc函数计算语音信号的MFCC特征。
4. 将提取的MFCC特征矩阵转化为行向量,作为分类模型的输入特征。
function result = dataProcess(voiceData,fs)
% 输入一个声音信号,以行向量形式返回处理结果
% voiceData(abs(voiceData)<0.005)=0;
% MFCC参数个数
num_ceps_coeffs = 40;
% 设置截止频率
fcutLow = 300;
fcutHigh = 50;
% 调用wavfilter函数对声音信号进行滤波
result = wavfilter(voiceData);
% 调用mfcc函数计算MFCC特征
result = mfcc(result,fs,'NumCoeffs', num_ceps_coeffs);
% 将特征矩阵转换为行向量
result = result(:).';
function out = wavfilter(signal)
% out = signal;
% return;
% signal = medfilt1(signal);
% 低通滤波函数
nyq = fs/2; % 奈奎斯特频率
wc = 1000/nyq; % 归一化截止频率
b = fir1(fcutLow,wc); % 设计低通滤波器
signal = filter(b,1,signal); % 对信号进行滤波
% 高通滤波器函数
order = 4;
[b, a] = butter(order, fcutHigh/(fs/2), 'high'); % 设计
out = filter(b, a, signal); % 对信号进行滤波
end
end
三、其它问题
在声音提取的过程中,出现声音采集不完整的情况,这是收集声音采用定时采集定长音频导致的。对此,采取了对相邻两段取有效区间的办法,即每次识别会先在获得的音频前加入前一段区间的音频,然后取振幅最高点为中点,取出音频区间作为判断依据。
采取这一方案后,音频的训练集和测试集样本质量得到较大的改善,识别准确度有了显著提升。