十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于MATLAB的0-9数字语音识别系统:MFCC特征提取与DTW匹配算法实战

基于MATLAB的0-9数字语音识别系统:MFCC特征提取与DTW匹配算法实战 简介一套基于MATLAB与隐马尔可夫模型HMM的0-9中文数字语音识别系统面向语音识别入门开发者、课程设计与毕设使用者。资源覆盖孤立词语音识别的完整链路包含信号预处理、端点检测、MFCC特征提取、HMM模型训练与解码识别并提供可视化分析模块辅助理解各阶段结果。压缩包共244个文件以200个wav中文数字语音样本、25个m脚本为主还有mat数据、json配置、png可视化图及md说明文档整体大小8.41MB。已有246人学习下载。借助这些内容使用者可以直接运行或改造现有代码观察波形图、声谱图、状态转移图也能基于自带数据集重新训练模型适合作为教学演示与动手实践素材。 做语音识别这个方向第一次接触数字语音识别的时候我也没觉得多复杂不就是0到9十个数字嘛。但真正把MATLAB环境跑起来、录音、预处理、提特征、训练模板、再拿去识别才发现这条链路每一环都有讲究。尤其当你拿到一段自己录的语音画出来的波形乱七八糟结果最后还能识别对那种感觉还是很奇妙的。这个基于MATLAB的0-9数字语音识别系统核心是MFCC特征提取加DTW动态时间规整匹配。整套代码我已经整理成可直接运行的工程训练、识别、可视化分析都能一键出结果。无论你是课程设计、毕业设计还是纯粹想入门语音信号处理的实际应用这套代码和文章里的思路都可以直接抄作业。1. 项目概述与整体设计思路1.1 数字语音识别能做什么数字语音识别听起来很窄但实际应用场景一点不少。比如电话语音拨号、仓库语音录入货号、门禁系统的语音口令甚至一些嵌入式设备上的简单人机交互核心依赖都是0到9这十个数字的可靠识别。从技术角度看数字集合虽小但它覆盖了语音识别最基本的完整链路做好了数字识别往后扩词汇表、做命令词识别、甚至做连续数字串识别都是在这个底盘上叠加复杂度。我见过不少初学者一上来就奔着大词表识别模型去结果被数据预处理和各种算法理论劝退。数字语音识别最友好的地方在于词汇量小、边界清晰、实时性要求不高非常适合用来建立对语音识别到底是怎么跑通的整体认知。这也是我为什么推荐把数字识别当作语音方向第一个练手项目的原因。1.2 为什么选择MFCC加DTW这套组合语音识别方案现在可选的很多HMM、GMM、深度学习模型都能做。但如果你只是想快速、稳定地搞定一个中小词汇量的孤立词识别系统MFCC特征加上DTW匹配依然是最经典、最可靠的选择之一。我的理由拆开讲有三点。第一MFCCMel频率倒谱系数是目前语音特征提取的工业标准它模拟人耳对不同频率的非线性感知特性对数字发音的区分能力很强。第二DTW动态时间规整天然适合处理同一人读同一数字每次发音时长不一样的情况通过动态规划做非线性对齐能把节奏差异消掉只对比内容本身。第三这套方案代码量可控、不需要大型训练集每个人录几遍就能建出可用的模板库而且参数调整的影响可以直接观察到对理解原理特别有帮助。选型时还有个小考虑MATLAB本身信号处理工具箱非常成熟波段生成、FFT、DCT这些都有现成函数MFCC完全可以自己手工实现不依赖第三方工具包避免了环境折腾的时间。1.3 系统整体架构整个系统的处理流程可以分成四个环节语音采集录制0到9每个数字的语音样本建立训练用模板库和待识别测试库。预处理对原始语音做预加重、分帧、加窗把一段连续语音变成一帧帧短时平稳信号。特征提取对每一帧提取MFCC特征参数得到该语音的特征向量序列。训练与识别训练阶段把每个数字的多次发音特征存入模板库识别阶段将待测语音的特征序列与所有模板逐一计算DTW距离距离最小的那个数字就是识别结果。可视化分析层面系统会输出原始波形图、预加重后的波形对比图、语谱图、MFCC特征热力图以及DTW匹配路径图每个环节都能直观看到信号长什么样子、特征是怎么分布的。整条链路设计得清楚调试时才不会两眼一抹黑。2. 语音信号预处理与特征提取详解2.1 采样参数与录音环境设置录音参数直接决定后面特征的质量。这个系统里我用的采样率是16000Hz16bit量化单声道。为什么不选8000Hz虽然电话语音就是8k采样但8k的带宽上限只有4kHz数字发音里很多辅音的高频细节会被削掉影响区分度。16k是语音识别项目事实上的默认采样率既能覆盖发音主要频段又不至于文件太大、计算量太高。MATLAB里录音可以用audiorecorder对象fs 16000; recorder audiorecorder(fs, 16, 1); % 采样率16k16bit单声道 recordblocking(recorder, 1); % 录音1秒 audio getaudiodata(recorder);录音环境我是强烈建议找一个安静的房间离麦克风15到20厘米左右语速自然不要拖音。训练时每个数字录3到5遍识别时另录测试。如果训练和测试环境差异太大比如一个安静一个嘈杂识别率掉得会非常明显。这块没有太多技巧就是录制的时候人别动保持距离稳定就好。2.2 预加重、分帧与加窗拿到raw audio之后第一件事是预加重。语音信号存在一个现象高频分量的能量远低于低频分量而发音的细节恰恰很多藏在高频区域。预加重本质是一个高通滤波器用一个系数a通常取0.97让高频分量相对提升a 0.97; audio_pre filter([1, -a], 1, audio);做完预加重接下来是分帧。语音信号是非平稳的但在很短的时间内比如20到30毫秒可以认为平稳。系统里设置的帧长是25ms帧移10ms。以16k采样率计算帧长就是0.025 × 16000 400个采样点帧移160个采样点。相邻帧之间有一半多的重叠这样特征序列过渡平滑不会因为分帧产生突变。分帧之后每一帧还要加窗。直接对一帧信号做FFT会因为在边界处截断产生频谱泄漏。加汉明窗可以让帧两端幅度平滑趋于0减少截断效应。MATLAB里一行搞定frame_len 400; h hamming(frame_len);这里有个细节容易忽略分帧后得到的矩阵每一列是一帧特征提取循环是逐列处理的。如果直接用矩阵运算加速记得维度要对应好初学者在这一块踩坑的特别多。2.3 MFCC特征的完整提取流程MFCC的提取是整个系统的核心环节我一步步说清楚。对每一帧信号先做FFT得到频谱然后计算功率谱幅度谱模的平方接着经过一组梅尔滤波器取对数再做离散余弦变换DCT取前若干个系数就是MFCC。梅尔频率刻度模拟的是人耳对频率的感知特性——人耳在低频段分辨能力强在高频段分辨能力弱。把普通频率f转换成梅尔频率的经典公式是mel 2595 × log10(1 f / 700)实际操作中我先在梅尔刻度上等距取若干个点再映射回普通频率构造一组三角滤波器。滤波器数量我用的24个覆盖从0到奈奎斯特频率采样率的一半即8kHz的范围。每个三角滤波器在对应频段的中心响应最强两侧线性衰减这样把频谱压缩成24个频带的能量值。取对数是为了模拟人耳对能量的对数感知同时把乘性噪声变成加性噪声后续处理更友好。最后做DCT的作用是去相关把滤波器组输出的对数能量向量变换到一个更紧凑的表示一般取前12到13个系数。语音信号的大部分信息集中在低阶系数上高阶系数反映的是细节变化取太少丢信息取太多反而引入噪声。我实际测试下来13维MFCC12个静态系数加1个能量项对数字识别的效果就已经很好了。如果还想提升鲁棒性可以再算一阶差分系数把动态变化信息加进去。不过初版系统先不用保持简单原则。这里放一段MFCC提取的核心代码逻辑清晰且可以直接运行function coeffs mfcc_extract(frame, fs, p) % frame: 分帧加窗后的信号矩阵每列一帧 % fs: 采样率 % p: 返回的MFCC系数个数 if nargin 3 p 13; end nfft 512; power_spec abs(fft(frame, nfft)).^2; % 功率谱 nfilters 24; mel_min 2595 * log10(1 0/700); mel_max 2595 * log10(1 (fs/2)/700); mel_points linspace(mel_min, mel_max, nfilters2); hz_points 700 * (10.^(mel_points/2595) - 1); bin floor((nfft1) * hz_points / fs); filterbank zeros(nfilters, nfft/21); for k 1:nfilters for f bin(k):bin(k1) if f1 nfft/21 filterbank(k, f1) (f - bin(k)) / (bin(k1) - bin(k)); end end for f bin(k1):bin(k2) if f1 nfft/21 filterbank(k, f1) (bin(k2) - f) / (bin(k2) - bin(k1)); end end end mel_spec filterbank * power_spec(1:nfft/21, :); log_mel log(mel_spec eps); % 防止log(0) coeffs dct(log_mel); coeffs coeffs(1:p, :); end注意代码末尾加了一个eps防止出现log(0)导致NaN。这类小细节在实际运行中往往就是bug的来源。3. 模板训练与DTW识别算法实现3.1 模板库怎么构建训练阶段做的事情不复杂对每个数字多次录音每次录音都提取一整套MFCC特征序列把它当作该数字的一个模板存起来。比如数字0录3遍就会得到3个特征序列模板每个序列的帧数可能不同因为每次发音长度不完全一样。模板在MATLAB里我用cell数组存储这样每个元素可以容纳不同长度的特征矩阵不会出现矩阵维度不一致的报错templates cell(10, 3); % 10个数字每个数字3个模板 for digit 0:9 for rep 1:3 % 录音并提取特征存入templates{digit1, rep} end end每个模板特征矩阵的尺寸是维度数×帧数比如13×35表示13维MFCC、35帧。模板的帧数完全取决于发音长度长短无所谓DTW正是用来处理这种不齐整序列的。训练数据建议让同一个人完成初版不需要做多说话人鲁棒性那是后面才考虑的事情。如果训练时录了男声测试时换成女声识别率会下降一些这种情况在预期内不必惊慌。3.2 DTW算法原理与实现DTW的核心思想可以用一句话概括把两个长度不同的特征序列通过允许一对多或多对一的对应关系在满足时序约束的条件下找到一条累积距离最小的对齐路径。这个思路落地到代码就是构建一个累积距离矩阵用动态规划逐步填充。假设模板特征序列是T有M帧待测特征序列是R有N帧。先计算一个M×N的距离矩阵d其中d(i,j)表示模板第i帧和测试第j帧之间的欧氏距离。然后定义一个M×N的累积距离矩阵D递推公式D(i,j) d(i,j) min(D(i-1,j), D(i-1,j-1), D(i,j-1))边界条件是D(1,1) d(1,1)。最终D(M,N)就是两个序列的DTW距离。路径约束保证了时间轴只会往前走不会倒退也就是动态时间规整名字里动态二字的含义。最后把距离除以(MN)做长度归一化消除序列长度对距离尺度的影响。完整实现代码function dist dtw_dist(template, test) % template: 模板特征序列每列一帧 % test: 待测特征序列每列一帧 m size(template, 2); n size(test, 2); d zeros(m, n); for i 1:m for j 1:n d(i,j) sqrt(sum((template(:,i) - test(:,j)).^2)); end end D inf(m1, n1); D(1,1) 0; for i 1:m for j 1:n D(i1,j1) d(i,j) min([D(i,j1), D(i1,j), D(i,j)]); end end dist D(m1, n1) / (m n); end这里有个小技巧D矩阵初始化为inf比实际维度多了一行一列可以自然处理边界条件不需要单独写for循环处理第一行第一列。代码跑起来会快很多也简洁很多。我自己刚开始写DTW时用了一堆if判断边界后面才发现这种padding方式才是正解。3.3 识别决策与拒识阈值识别阶段的做法非常直接待测语音提取MFCC特征序列后与模板库中所有模板逐一计算DTW距离距离最小的那个数字编号就是识别结果。如果每个数字有多个模板就取该数字所有模板中距离最小的那一份作为该数字的代表距离再进行数字间比较。还可以考虑加一个拒识机制如果最小距离也超过某个阈值说明待测语音和所有模板的匹配程度都太低可能是环境噪声或者非目标数字输入此时可以拒绝识别输出未知语音提示。阈值怎么定我建议先收集一批不同数字的DTW距离分布画一个柱状图看看正常的类内距离大概是多少取1.5到2倍类内距离作为拒识阈值这样比较有依据。4. 可视化分析与核心代码实现4.1 波形、语谱图与MFCC特征可视化可视化不是可选项这是调试识别系统最有力的工具。我每次做语音识别都强制自己先画图再调参不然就是在黑盒里瞎猜。第一步看波形。原始语音的时域波形能直接反映发音起止位置、振幅变化配合短时能量曲线可以判断端点检测是否准确。系统里用subplot把原始波形和预加重后的波形放一起画可以看到预加重后高频细节明显增强波形显得更锐利。第二步看语谱图。MATLAB的spectrogram函数可以直接输出语谱图横轴是时间纵轴是频率颜色深浅代表能量大小。数字发音的共振峰迁移、辅音爆发点、元音段在语谱图上都有非常典型的形态。比如卷舌音和爆破音的频谱特征一眼就能看出来。我强烈建议你把自己录的10个数字的语谱图全部打印出来并排观察一定会有直观的收获。figure; spectrogram(audio_pre, hamming(256), 128, 512, fs, yaxis); title(预加重后语谱图);第三步看MFCC特征。把整个语音的MFCC特征矩阵用imagesc画成热力图横轴是帧序号纵轴是MFCC维度颜色代表数值大小。你会发现不同数字的热力图形状有明显差异比如带鼻音的数字和带塞音的数字静态系数和动态变化模式都不太一样。这个步骤对理解特征为什么有效特别有帮助。4.2 DTW路径可视化除了上述常规可视化我还额外画了DTW匹配路径图把累积距离矩阵的前50个点切开用黑色标注最优路径。这能直观看到模板和测试序列是如何对齐的——路径在哪个点压缩了时间、哪里拉伸了时间一目了然。代码大致是figure; imagesc(D); hold on; % 从D(m1,n1)回溯路径 i m1; j n1; path_x []; path_y []; while i 1 || j 1 path_x(end1) j-1; path_y(end1) i-1; [~, idx] min([D(i-1,j-1), D(i-1,j), D(i,j-1)]); switch idx case 1 i i-1; j j-1; case 2 i i-1; case 3 j j-1; end end plot(path_x, path_y, w, LineWidth, 2);这个图非常直观的展示了DTW为什么能处理语速变化。比如有人读0读得很拖拉中间有一部分长时间停留在同一个状态DTW路径就会在相应位置出现水平段来吸收这种时间差异。4.3 完整代码框架说明整个系统的代码按功能模块拆分主程序负责流程调度其他函数各司其职。我列举一下核心文件的组织方式% 主程序脚本 digital_speech_demo % 依赖的核心函数 record_digit.m % 录制单个数字语音 preprocess.m % 预加重、分帧、加窗 mfcc_extract.m % 提取MFCC特征 dtw_dist.m % 计算两个特征序列的DTW距离 train_templates.m % 训练模板库 recognize_digit.m % 识别数字 visualize_analysis.m % 可视化分析这一步的实现思路是先用一个小demo脚本串起整个流程音频文件读入或者直接录音都支持。在主程序中你可以看到每个环节的调用方式方便替换成自己的录音文件批量测试。把识别函数单独拎出来意味着你可以把这个引擎嵌入到更大的系统里比如做一个有界面的数字识别工具。代码整体不依赖外部工具箱用基础的MATLAB信号处理函数就能跑通。如果你是新版MATLAB部分函数名可能稍有调整比如audioinfo、audioread的推荐用法注意看版本文档即可。5. 常见问题与排查技巧实录5.1 端点检测不准语音前后静音太多这个是我做项目时最先遇到的问题。录音时候总是会带出来零点几秒的静音直接做分帧特征提取前面的静音帧和后面的静音帧根本不是有效发音内容却参与了DTW距离计算会明显拉高匹配误差。我的解决方案是做一个简单的能量端点检测计算短时能量超过阈值的部分才认为是有效语音段只对有效段提取特征。阈值可以取整个语音最大能量的10%左右这样能自动适应音量大小。实测下来加上这套端点检测之后识别率提升非常明显。如果对精度要求更高还可以配合过零率检测把清音、擦音的开头结尾切得更准。5.2 识别率低哪些因素影响最大识别率低一般从几个方向排查。先看训练数据和测试数据是否来自同一环境、同一个人。如果训练时是台式机麦克风测试时用笔记本内置麦频响差异就会导致特征偏移。再看模板数量每个数字只录一遍基本不够稳定我建议最少录3遍录5遍更好。录制时要求语速自然不要刻意拖长或加速。还有一个常被忽略的因素是采样率的一致性。训练模板用的是16kHz采样测试语音如果来自别的设备是48kHz采样而没有重采样频谱结构就完全变了。MATLAB里用resample函数统一到16kHz或者采集时就直接控制采样率这个问题可以提前规避。5.3 运行报错怎么定位代码运行遇到报错我建议按照数据维度—函数输入—算法逻辑三层顺序排查。MATLAB最常见的报错是矩阵维度不一致检查特征提取函数返回的矩阵尺寸是否和预期一致。分帧时如果音频长度不足以凑满整帧会在末尾丢弃多余的采样点这会导致帧数比自己预期少但不影响整体逻辑。另一个常见问题是用audiorecord时没有设置采样率就默认用了8000Hz后面MFCC的mel滤波器按16kHz生成两者不一致识别率自然崩。排查时在代码里加一句断言检查assert(round(recorder.SampleRate) fs, 采样率不一致);还有一个小技巧每写完一个模块就单独画图验证一下。比如分帧后画出前几帧波形MFCC提取后画出特征热力图确认数据形态正常再继续下一步。全流程写完再debug出错的定位成本高很多。写在最后的体会数字语音识别这个项目我前后做过好几版从最初简陋的模板匹配到现在这个带完整可视化的版本最大的体会是语音识别不是玄学每一步处理都有它对应的信号学依据。你理解了预加重为什么存在、MFCC为什么取对数、DTW为什么允许非线性对齐整个系统的行为就变得完全可预测。这也是我特别推荐用MATLAB来做这类项目的原因——它的可视化能力强信号处理的每一步都能画出来验证学习曲线比纯Python跑深度学习要平滑得多。如果你按着这篇文章把系统跑通我建议你做一个扩展实验录两遍0到9的连续数字串看看用当前的模板匹配能不能识别出来。连续数字串会引入数字之间的过渡音和边界模糊问题你会发现单纯靠孤立词模板已经不够用这时候再去了解HMM或者有限状态模型就顺理成章了。技术学习就是这样每一步的疑问都会把你引向更深的地方。本文还有配套的精品资源点击获取
返回列表