MATLAB语音识别实战:LPCC特征提取与孤立词识别系统
简介这是一份面向语音识别研究者与初学者的MATLAB实现资源聚焦LPCC线性预测编码系数特征提取方法。LPCC在语音识别系统中能反映频谱与时域动态特性常与SVM、HMM等分类模型配合用于构建完整识别流程。压缩包仅含1个m文件大小891B即lpcc.m脚本。脚本覆盖信号预加重、加窗分帧、功率谱估计、线性预测系数求解、对数倒谱转换以及一阶二阶差分计算等LPCC提取关键环节代码量精简可直接在MATLAB中调用也可嵌入现有语音识别框架作为前端特征模块。尽管包体极小但脚本逻辑完整便于逐行理解算法原理在此基础上可调整LPC阶数、窗函数或归一化策略进一步优化系统识别率。目前已有165人学习下载对于希望快速掌握LPCC实现细节或开展语音特征对比实验的开发者而言是一份小巧实用的参考代码。1. 为什么 LPCC 在 MATLAB 语音识别里还是绕不开的特征做语音识别的人多半会默认 MFCC 是起点但只要你搜一次「LPCC MATLAB 语音识别」就会看到课程设计、毕业设计和不少嵌入式老项目里LPCC 依然被大批量地使用。LPCCLinear Prediction Cepstral Coefficients线性预测倒谱系数是从 LPC 系数递推出来的倒谱特征描述的是声道冲激响应的谱包络。它的计算量比 MFCC 小一个量级参数含义直接对应发音生理模型在干净语音、孤立词识别和资源受限的离线语音识别模块里优势非常具体。这篇文章从原理讲起给出能在 MATLAB 里直接复现的提取代码、一套可跑的孤立词识别系统以及参数调试时真正会踩的坑。新手可以照着敲完熟手可以直接跳到第 5 章看边界条件。2. LPCC 的原理与参数LPC、倒谱和声道模型怎么串起来2.1 全极点模型与倒谱递推LPCC 不依赖 FFT很多人的误区是「LPCC 就是对 LPC 系数做 FFT 再取模」。实际上 LPC 的建模对象是声道传递函数用全极点模型近似H(z) G / (1 Σ_{k1}^{p} a_k z^{-k})其中 a_k 是线性预测系数G 是增益。对这个模型的幅度谱取对数再做逆变换得到的系数 c_n 就是倒谱。关键性质在于全极点模型的倒谱根本不需要做 FFT可以直接从 a_k 递推出来% 递推关系公式索引从 0 开始MATLAB 实现时注意偏移 % c_0 ln(E) % n 在 1..p 之间: c_n a_n sum_{k1}^{n-1} (k/n)*c_k*a_{n-k} % n 大于 p 之后: c_n sum_{kn-p}^{n-1} (k/n)*c_k*a_{n-k}这里 E 是线性预测误差能量。这个递推式告诉你两件事第一LPCC 的计算量由 LPC 阶数 p 决定和 FFT 长度无关第二倒谱系数随 n 增大衰减很快取前 12 到 16 个就足够描述谱包络。还有一个容易被忽略的好性质c_0 的取值只影响自身递推从 c_1 开始就不依赖 c_0所以不同实现里 c_0 是取 ln(E) 还是取 0.5*ln(E)完全不影响后面系数的数值识别任务里把它丢掉是安全的。2.2 LPC 阶数 p、倒谱个数 Q 和采样率怎么搭配LPC 阶数不是越大越好。阶数过低共振峰展不开谱包络过度平滑阶数过高模型开始拟合基频的精细谐波结构反而丢掉了「声道滤波特性」这个物理含义。经验上 p 大约等于采样率kHz加 2 到 48 kHz 语音取 10 到 1216 kHz 宽带语音取 16 到 20。参数常见取值依据LPC 阶数 p10~128 kHz16~2016 kHz每个共振峰约需 2 阶再加 2~4 阶裕量LPCC 个数 Q12~16高阶倒谱系数衰减快截断即可帧长20~25 ms短时平稳假设的工程折中帧移8~10 ms相邻帧重叠 50%~60%保证轨迹平滑倒谱个数 Q 和 p 的关系经常被搞混。递推式在 n 超过 p 之后只有前 p 个 a_k 参与所以 Q 可以大于 p但高阶系数幅度很小取太多只会把噪声带进特征。我一般取 Q 等于 p 或者 p 加 2超过 20 个基本没有正向收益。2.3 LPCC 与 MFCC 的边界什么时候该用 LPCCMFCC 用 Mel 滤波器组模拟人耳感知对噪声和频谱细节更鲁棒是绝大多数现代识别系统的默认特征。LPCC 的优势在三个方面一是计算量小只有一次自相关和一次 Levinson-Durbin 递推适合在 MATLAB 里跑实时原型也适合移植到 ESP32 这类资源受限的板子上做离线命令词识别二是系数和 LPC 模型直接对应做共振峰分析、声道长度归一化时顺手三是在干净语音、单说话人的孤立词任务里LPCC 的识别率并不比 MFCC 差而实现代码短得多。短板也要说清楚LPCC 对加性噪声敏感因为噪声会污染自相关序列它对声道模型假设的依赖强女声和童声的共振峰位置偏高同样阶数下拟合效果不如男声这时需要适当抬高 p。我的选择标准是资源受限或做课程验证用 LPCC复杂环境识别用 MFCC两边都不舍得丢就拼成混合特征。3. 用 MATLAB 复现 LPCC 提取从递推公式到可运行函数3.1 先写 Levinson-Durbin 递推求解 LPC 系数LPCC 的前置条件是 LPC 系数 a_k 和预测误差能量 E它们从帧信号的自相关序列解出来。自相关法配合 Levinson-Durbin 递推是最稳的组合保证求解出来的全极点滤波器稳定。下面的函数输入自相关序列 r 和阶数 p输出 LPC 系数和误差能量function [a, E] levinson_durbin(r, p) % r: 自相关序列长度 p1r(1) 对应 r(0) % a: LPC 系数长度 pa(i) 对应 a_i % E: 预测误差能量递推过程中单调不增 a zeros(p, 1); E r(1); for i 1:p % 计算第 i 步反射系数 k_i s 0; for j 1:i-1 s s a(j) * r(i-j1); end k_i -(r(i1) s) / E; % 由前一步系数更新当前阶系数 a_prev a; for j 1:i-1 a(j) a_prev(j) k_i * a_prev(i-j); end a(i) k_i; E E * (1 - k_i^2); end end核心是反射系数 k_i 的求解它等于当前阶预测误差与上一步误差能量的比值取负。E 每步乘以 (1 - k_i^2)只要 |k_i| 小于 1E 就单调下降滤波器稳定。信号里如果有直流分量r(1) 偏大最后得到的系数会整体偏移所以分帧前最好先做预加重或者去直流。3.2 用递归关系把 LPC 转成 LPCC有了 a 和 ELPCC 就是合并两个循环的事。注意 c_0 是 log(E)它携带的是帧能量信息受录音距离和增益影响太大识别任务里通常丢弃function ceps lpc2lpcc(a, E, Q) % a: LPC 系数长度 p % E: 预测误差能量 % Q: 需要提取的 LPCC 个数不含 c_0 p length(a); ceps zeros(Q 1, 1); % 索引 1 存放 c_0 ceps(1) log(E); % n 1..pc_n a_n sum(k/n)*c_k*a_{n-k} for n 1:min(p, Q) s a(n); for k 1:n-1 s s (k / n) * ceps(k1) * a(n-k); end ceps(n1) s; end % n p只用前 p 个 LPC 系数参与递推 for n p1:Q s 0; for k n-p:n-1 s s (k / n) * ceps(k1) * a(n-k); end ceps(n1) s; end ceps ceps(2:end); % 丢弃 c_0长度变为 Q end这里索引错一位是最常见的 bugMATLAB 数组从 1 开始公式里的 c_n 从 0 开始所以 ceps(k1) 存的是 c_k。第二个循环的下界 n-p 保证只用已经算出来的系数如果 p 比 n 大第二个循环根本不会进入由第一个循环覆盖。返回值去掉 c_0 后长度正好是 Q。3.3 封装成分帧特征提取函数 lpcc_extract把整条链路串起来预加重、分帧加窗、逐帧自相关、LPC、LPCC。帧长 25 ms、帧移 10 ms 是语音识别里最常用的组合function feats lpcc_extract(x, fs, p, Q) % 提取整段语音的 LPCC 特征矩阵 % x: 单声道语音double 类型 % fs: 采样率 % p: LPC 阶数 % Q: 每帧 LPCC 个数 if size(x, 2) 1 x mean(x, 2); % 立体声转单声道 end x x / max(abs(x)); % 幅值归一化 x filter([1, -0.97], 1, x); % 预加重系数 0.97 frame_len round(0.025 * fs); % 25 ms frame_shift round(0.010 * fs); % 10 ms N length(x); n_frames floor((N - frame_len) / frame_shift) 1; feats zeros(n_frames, Q); hamm 0.54 - 0.46 * cos(2 * pi * (0:frame_len-1) / (frame_len - 1)); for i 1:n_frames idx (i-1) * frame_shift 1 : (i-1) * frame_shift frame_len; frame x(idx) .* hamm; r zeros(p 1, 1); for k 1:p1 r(k) sum(frame(1:end-k1) .* frame(k:end)); % r(τ) end [a, E] levinson_durbin(r, p); if E 1e-12 E 1e-12; % 防止 log(0) 出现 NaN end feats(i, :) lpc2lpcc(a, E, Q); end end自相关那行frame(1:end-k1) .* frame(k:end)对应 r(τ) Σ x(n)x(nτ)比双重循环快得多。预加重系数 0.97 提升高频段抵消语音频谱随频率下降的趋势如果你的录音设备高频已经很亮降到 0.95 即可。汉明窗我用手写公式替代hamming这样没有 Signal Processing Toolbox 也能跑filter和audioread都是基础函数不受工具箱限制。3.4 最小验证跑一段语音看特征[x, fs] audioread(your_wav.wav); % 换成你自己的录音 feats lpcc_extract(x, fs, 12, 12); plot(feats(:, 1:3), LineWidth, 1); % 只看前 3 阶避免图太乱 xlabel(帧号); ylabel(LPCC 值);如果 feats 里出现 NaN说明存在能量接近 0 的帧log(E) 溢出原因通常是录音首尾的静音段太长解决方案是给 E 加下限或者先做端点检测。正常语音的特征曲线应该在中段平稳起伏、首尾接近 0如果整段都很平先检查是不是整段都录成了噪声。手头没有现成 wav 文件时Signal Processing Toolbox 自带的load mtlb可以直接用采样率 7418 Hz对应 p 取 12 没问题。4. 在 MATLAB 里搭一个 LPCC 孤立词识别系统4.1 端点检测与模板生成孤立词识别是最适合 LPCC 的场景每个词 0.3 到 1 秒先用端点检测截出有效段再提 LPCC最后用 DTW 匹配。端点检测我用短时能量加连续段筛选能量门限去掉静音取最长的连续命中段作为语音。简化的双门限版本如下function [idx_s, idx_e] simple_vad(x, fs) % 基于短时能量的端点检测返回语音段起止样本点 framelen round(0.020 * fs); shift round(0.010 * fs); nf floor((length(x) - framelen) / shift) 1; energy zeros(nf, 1); for i 1:nf idx (i-1)*shift 1 : (i-1)*shift framelen; energy(i) sum(x(idx).^2) / framelen; end e_th max(energy) * 0.1; % 门限取最大能量的 10% on energy e_th; % 找最长的连续命中段 max_len 0; max_start 1; max_end 1; i 1; while i nf if on(i) j i; while j nf on(j1) j j 1; end if j - i 1 max_len max_len j - i 1; max_start i; max_end j; end i j 1; else i i 1; end end idx_s (max_start - 1) * shift 1; idx_e min((max_end - 1) * shift framelen, length(x)); end能量门限取最大值的 10%对单人、固定距离的录音够用。注意循环里用 while 而不是 for因为要在找到一段连续区间后跳过整段MATLAB 的 for 循环变量无法在循环体内修改。模板生成时每类词录 5 到 10 遍每遍经过端点检测后提 LPCC得到的特征矩阵行数随时长变化所以模板是变长的后面必须用 DTW。4.2 DTW 距离变长特征的匹配DTW 的核心是构造帧间欧氏距离矩阵再沿三条路径累积找最小代价。我做了一点归一化处理用路径长度去除累积距离避免读得慢的样本因为帧数多而系统性增大距离function d dtw_dist(t, r) % t: 模板特征矩阵帧数 x 维数 % r: 测试特征矩阵帧数 x 维数 Mt size(t, 1); Mr size(r, 1); d zeros(Mt, Mr); for i 1:Mt for j 1:Mr d(i, j) sqrt(sum((t(i,:) - r(j,:)).^2)); end end D zeros(Mt, Mr); D(1, 1) d(1, 1); for i 2:Mt D(i, 1) D(i-1, 1) d(i, 1); end for j 2:Mr D(1, j) D(1, j-1) d(1, j); end for i 2:Mt for j 2:Mr D(i, j) d(i, j) min([D(i-1,j), D(i,j-1), D(i-1,j-1)]); end end d D(Mt, Mr) / (Mt Mr); % 路径长度归一化 end三条允许的移动分别对应「测试帧多读一帧」「模板帧多读一帧」「正常对齐」相当于允许语速在一定范围内伸缩。归一化用 MtMr 而不是真正的路径步数是一个常用近似好处是计算简单、对端点误差不敏感。如果你的词表里存在读音长度差异极大的类别可以给 dtw_dist 加一个弯曲窗口限制比如只允许路径在对角线附近 ±10 帧内移动能防止错误对齐。4.3 识别与留一法评估小数据集上我一般用留一法每条录音轮流当测试样本其余全部当模板统计识别率。这样 30 条数据也能得到稳定估计% data{c}{t} 是第 c 类第 t 条录音的 LPCC 特征矩阵已提好 C length(data); correct 0; total 0; for c 1:C for t 1:length(data{c}) templates data; test_feat templates{c}{t}; templates{c}(t) []; % 留出当前样本 best_d Inf; pred 0; for cc 1:C for tt 1:length(templates{cc}) dist dtw_dist(templates{cc}{tt}, test_feat); if dist best_d best_d dist; pred cc; end end end if pred c correct correct 1; end total total 1; end end fprintf(留一法识别率%.1f%% (%d/%d)\n, 100*correct/total, correct, total);注意MATLAB 里删除 cell 数组中的一项要写templates{c}(t) []用小括号而不是花括号。写成templates{c}{t} []只会把内容置空cell 里留一个空矩阵DTW 时会直接报维度错误。干净环境下10 类词、每类 5 遍训练LPCC 加 DTW 做到 95% 以上是正常的。如果低于 90%先查端点检测是否把环境噪声截了进来再查每类内部是否有明显音量差异。5. LPCC 参数微调、易混分析与一种进阶用法5.1 三个必调参数p、Q、预加重系数p 是唯一需要和采样率联动的参数。8 kHz 电话语音取 10 到 1216 kHz 宽带语音取 16 到 20。判断依据是共振峰拟合画出 LPC 谱包络如果低频段出现无意义的窄尖峰p 偏高如果第二、第三共振峰糊成一团p 偏低。女声和童声的共振峰整体偏高同一份数据换成女性说话人时p 需要比男声高 2 到 4 阶这也是「女声语音识别比男声更低」的一个直接原因。Q 取 12 就够大部分命令词超过 20 后高阶系数由噪声主导DTW 距离被噪声项拉平识别率反而下降。预加重系数 0.95 到 0.97 之间影响的是谱包络倾斜而不是共振峰位置不值得反复试固定 0.97 就行。5.2 用混淆矩阵定位易混类别识别率只是一个数混淆矩阵才能告诉你哪两类在互相混。有 Statistics and Machine Learning Toolbox 的话直接一行代码cm confusionchart(true_labels, pred_labels);没有工具箱就手动统计C 类建 C 乘 C 矩阵行是真值、列是预测。如果发现「二」和「四」这类音节结构接近的词互相混优先检查端点截取是否一致其次才是调 p。易混对通常集中在音素结构相似的数字上这时候加差分特征比加阶数更有效。5.3 差分 LPCC 和向量量化往深度学习过渡的两种做法静态 LPCC 只描述当前帧的谱包络丢失了语音的动态变化。一阶差分把相邻帧的变化趋势加进来是提升识别率性价比最高的做法function dfeats lpcc_delta(feats, win) % 一阶差分倒谱win 常见取 2即前后各 2 帧 dfeats zeros(size(feats)); for n 1:size(feats, 1) num 0; den 0; for k -win:win idx min(max(n k, 1), size(feats, 1)); % 边界复制 num num k * feats(idx, :); den den k^2; end dfeats(n, :) num / den; end end边界处理用复制而不是补零语音首尾本就是静音帧复制几帧对距离影响极小补零反而会引入虚假的剧烈变化。把 dfeats 拼到静态特征后面再走 DTW或者喂给 LSTM就是从 LPCC 往 MATLAB 深度学习路线最短的升级路径。另一种做法是向量量化把训练集所有帧的 LPCC 用kmeans(feats_all, K)聚成 K 个码本中心每段语音用码本序号序列表示再接 HMM 或直接做直方图匹配这是早期说话人识别系统的经典结构代码量不大适合在 MATLAB 里快速验证特征区分度。本文还有配套的精品资源点击获取