简介这份MATLAB代码资源聚焦梅尔倒谱系数MFCC的完整实现面向语音识别、情感分析与语音合成方向的学习者和开发者尤其适合希望从零理解特征提取流程的初学者也可作为专业人士的可靠工具。代码按预加重、分帧、加窗、FFT、梅尔滤波器组、对数运算、DCT及动态特征计算等步骤逐步展开注释详细便于对照原理逐段调试。资源包共23个文件以m脚本与函数文件为主辅以mfc特征数据、png示意图、pdf说明文档、txt文本、wav示例音频及conf配置文件压缩包约291KB结构紧凑、便于快速上手。目前已有3029人学习下载读者可据此掌握帧长、帧移、滤波器数量、DCT系数等参数调整方法并借助示例音频与对比结果验证特征提取效果为后续语音处理任务打下基础。1. 从一段“听起来不对劲”的语音说起MFCC 到底在算什么有个做语音采集的朋友找我说设备录回来的音频人耳听着正常可丢进识别模型里准确率就是上不去换了几版分类器也没用。我让他把特征提取那段代码发过来一看MFCC 的滤波器组数量、预加重系数、帧移全是拍脑袋填的连采样率都没跟音频对齐。问题根本不在模型在特征。梅尔倒谱系数MFCC这套东西本质上是把一段时域波形压成几十维、贴近人耳感知的特征向量前端提得糙后端再花哨也是白搭。这份 MATLAB 代码资源就是把 MFCC 从分帧、加窗、FFT、梅尔滤波到 DCT 取倒谱的完整链路摊开给你看适合正在做语音识别、声纹比对、音频分类又不想被工具箱黑盒糊弄的开发者。下面我按“它怎么算 → 怎么跑 → 怎么改”的顺序拆一遍。2. 核心链路拆解一帧语音是怎么变成 13 维倒谱的2.1 预加重、分帧与加窗先把连续波形切成可分析的短片段语音信号整体是非平稳的但 20~30 毫秒的短窗内可以近似看成平稳所以第一步永远是切帧。切之前通常先做预加重用一个一阶高通把高频抬起来补偿发音时声门脉冲带来的高频衰减。常见做法是y[n] x[n] - 0.97 * x[n-1]这个 0.97 就是预加重系数取值一般在 0.9~0.98 之间。% preprocess.m —— 预加重 分帧 汉明窗 function frames preprocess(x, fs, frame_ms, hop_ms, preemph) % x: 单声道列向量, fs: 采样率 frame_len round(frame_ms * fs / 1000); % 帧长采样点数 hop_len round(hop_ms * fs / 1000); % 帧移采样点数 % 1) 预加重一阶高通抬高频 x [x(1); x(2:end) - preemph * x(1:end-1)]; % 2) 分帧不足一帧的尾部直接丢弃避免补零引入假能量 n_frames floor((length(x) - frame_len) / hop_len) 1; frames zeros(frame_len, n_frames); for i 1:n_frames start_idx (i-1) * hop_len 1; frames(:, i) x(start_idx : start_idx frame_len - 1); end % 3) 加汉明窗减小频谱泄漏 win hamming(frame_len); frames frames .* win; end这段是整个 MFCC 的地基。frame_ms一般取 25hop_ms取 10也就是帧长 25 毫秒、帧移 10 毫秒相邻帧有 15 毫秒重叠保证特征在时间上平滑过渡。preemph我一般固定 0.97除非音频本身高频噪声很重才降到 0.95。注意分帧时尾部不足一帧直接丢掉比补零更干净——补零会在频谱上引入虚假的低频能量后面梅尔滤波一压就污染了倒谱。加窗用汉明窗是标配如果你做的是端点检测前的能量分析可以换成矩形窗保留真实能量但做 MFCC 还是汉明窗稳。2.2 FFT 与功率谱把时域帧搬到频域加完窗的每一帧做 N 点 FFTN 通常取 512 或 1024要大于帧长多出来的部分补零。补零在这里是合理的它让频域采样更密方便后面梅尔滤波器组对齐。取模平方得到功率谱只保留正频率那一半。% powerspectrum.m —— 逐帧 FFT 取功率谱 function P powerspectrum(frames, nfft) frame_len size(frames, 1); n_frames size(frames, 2); % 补零到 nfft 点 X fft(frames, nfft, 1); % 只取正频率部分 (nfft/2 1 个点) half floor(nfft / 2) 1; X X(1:half, :); % 功率谱 幅度平方 / nfft归一化避免数值随 nfft 漂移 P (abs(X).^2) / nfft; endnfft取 512 时正频率有 257 个点对应 0 到 fs/2。这里除以nfft做归一化是为了让不同nfft设置下功率谱量级一致否则你换一次 FFT 点数后面所有阈值都得重调。这一步没什么玄学但很多人漏掉归一化导致滤波器组输出的对数能量整体偏移DCT 之后第一维能量维直接失真。2.3 梅尔滤波器组按人耳感知重新分配频带人耳对低频的分辨能力远强于高频梅尔刻度就是模拟这个特性低频密、高频疏。滤波器组一般用 24~40 个三角滤波器覆盖 0 到 fs/2中心频率在梅尔刻度上等间距分布再映射回赫兹。% melbank.m —— 构造梅尔三角滤波器组 function [fb, fb_centers] melbank(n_filters, nfft, fs, low_freq, high_freq) % 赫兹转梅尔 hz2mel (f) 1125 * log(1 f / 700); mel2hz (m) 700 * (exp(m / 1125) - 1); mel_low hz2mel(low_freq); mel_high hz2mel(high_freq); % 滤波器中心在梅尔刻度上等间距 mel_points linspace(mel_low, mel_high, n_filters 2); hz_points mel2hz(mel_points); % 映射到 FFT bin 索引 bin floor((nfft 1) * hz_points / fs); fb zeros(n_filters, floor(nfft/2) 1); for m 2:n_filters 1 f_m_minus bin(m - 1); f_m bin(m); f_m_plus bin(m 1); for k f_m_minus:f_m fb(m-1, k1) (k - f_m_minus) / (f_m - f_m_minus); end for k f_m:f_m_plus fb(m-1, k1) (f_m_plus - k) / (f_m_plus - f_m); end end fb_centers hz_points(2:end-1); endn_filters是滤波器个数语音识别里 26 是经典值声纹场景常用 40。low_freq一般设 0 或 20Hzhigh_freq设 fs/2。这里有个容易翻车的点bin索引可能重复当滤波器个数太多、nfft太小时相邻中心会落到同一个 FFT bin 上三角滤波器退化成一条竖线特征直接废掉。所以nfft至少要是滤波器个数的 4 倍以上26 个滤波器配 512 点 FFT 是安全组合。滤波器组矩阵fb是n_filters × (nfft/21)每行是一个三角窗乘上功率谱就得到每个频带的能量。2.4 对数能量与 DCT压成低维倒谱去掉频带相关性滤波器组输出取对数再做离散余弦变换DCT把能量集中到前几维同时去掉各频带之间的相关性。通常取前 12~13 维加上一维对数总能量凑成 13 维特征向量。% mfcc.m —— 主流程从波形到 13 维 MFCC function C mfcc(x, fs, varargin) p inputParser; addParameter(p, frame_ms, 25); addParameter(p, hop_ms, 10); addParameter(p, nfft, 512); addParameter(p, n_filters,26); addParameter(p, n_ceps, 13); addParameter(p, preemph, 0.97); parse(p, varargin{:}); opt p.Results; frames preprocess(x, fs, opt.frame_ms, opt.hop_ms, opt.preemph); P powerspectrum(frames, opt.nfft); fb melbank(opt.n_filters, opt.nfft, fs, 0, fs/2); % 滤波器组加权 取对数 mel_energy fb * P; log_energy log(mel_energy eps); % eps 防止 log(0) % DCT-II取前 n_ceps 维 C dct(log_energy); C C(1:opt.n_ceps, :); % 用第 0 维总能量替换第 1 维是常见做法 C(1, :) log(sum(frames.^2, 1) eps); endn_ceps取 13 是语音识别的惯例取 12 维倒谱加 1 维能量。log_energy里加eps是防止静音帧能量为零导致log(0)变-Inf这个坑我踩过模型训练时 loss 直接 NaN。DCT 之后第一维是直流分量代表整体能量有些实现会把它替换成帧的对数总能量两种做法都行看你后端模型怎么接。到这里一帧 25 毫秒的波形就变成了 13 个数整段音频输出一个13 × n_frames的矩阵这就是喂给识别模型的特征。3. 跑起来环境、数据与第一次出图3.1 环境与依赖MATLAB 版本和工具箱要求这份代码只依赖基础 MATLAB 和信号处理工具箱里的hamming、dct、fft没有用到 Audio Toolbox 的mfcc函数——这点很关键用自带函数就失去拆解意义了。MATLAB R2016b 及以上都能跑inputParser那套参数解析在更老的版本里也支持。如果你只有基础版没装信号处理工具箱hamming可以自己用0.54 - 0.46*cos(2*pi*(0:N-1)/(N-1))替代dct也能用 FFT 手动实现不影响主流程。3.2 准备一段测试音频并加载拿一段 16kHz 单声道 wav 就行自己用手机录一段“一二三四五”也可以但记得转成单声道、16bit PCM。MATLAB 里读音频% run_demo.m —— 最小可运行示例 [x, fs] audioread(test.wav); % 多声道转单声道 if size(x, 2) 1 x mean(x, 2); end % 归一化到 [-1, 1]避免不同录音设备增益差异 x x / max(abs(x)); C mfcc(x, fs, n_filters, 26, n_ceps, 13); % 画出倒谱图横轴时间纵轴倒谱维 imagesc(C); xlabel(帧序号); ylabel(倒谱维); title(MFCC 特征); colorbar;audioread返回的fs一定要传给mfcc采样率错了帧长和滤波器中心频率全错位。归一化那步不是必须但强烈建议做否则不同设备录的音频能量量级差几个数量级第一维能量特征会主导距离计算。跑完imagesc(C)应该能看到一张横条纹理图低频维上面几行变化平缓高频维下面几行噪声感更强这是正常形态。如果整张图几乎全黑或全白多半是log那步出了问题检查mel_energy是不是有零。3.3 参数怎么调一张对照表参数常用值调大后果调小后果frame_ms25频率分辨率高时间分辨率低时间分辨率高频谱模糊hop_ms10特征帧数少可能丢动态帧数多计算量和冗余上升nfft512频域采样密计算量增滤波器可能重叠到同一 binn_filters26频带细特征维度高频带粗区分度下降n_ceps13保留更多倒谱细节信息压缩过度preemph0.97高频抬升强高频补偿不足这张表不是让你全调一遍而是出问题时知道往哪个方向拧。语音识别任务里frame_ms25、hop_ms10、nfft512、n_filters26、n_ceps13这套组合是经过大量实践验证的起点先跑通再谈优化。4. 避坑与排查MFCC 提取里最容易翻车的五件事4.1 现象特征图全黑或全白模型完全不收敛原因基本锁定在log(mel_energy eps)这一步。如果音频开头有长段静音mel_energy接近零即使加了epslog出来的值也可能在 -30 以下把整张图的动态范围拉爆。解决方法是做端点检测把静音段裁掉再提特征或者对mel_energy设一个下限比如max(mel_energy, 1e-10)。我一般会在mfcc里加一个可选的能量门限低于门限的帧直接标记为静音帧不参与后续距离计算。4.2 现象换一段音频后特征维度对不上原因通常是采样率变了但代码里fs没更新或者音频是立体声没转单声道。audioread读立体声返回N×2矩阵直接丢进preprocess会按列分帧结果完全错乱。解决方法是加载后强制检查size(x, 2)大于 1 就取均值。另外如果音频采样率和mfcc调用时传的fs不一致滤波器中心频率会整体偏移特征分布完全变样这种错误不会报错只会让模型悄悄变差。4.3 现象相邻帧特征几乎一样看不出动态帧移设得太大或者分帧时重叠算错了。hop_ms如果等于frame_ms相邻帧没有重叠特征在时间上就是跳变的如果hop_ms大于frame_ms还会丢样本。标准做法是hop_ms取frame_ms的 40% 左右25 毫秒帧长配 10 毫秒帧移。检查n_frames的计算公式floor((length(x) - frame_len) / hop_len) 1这个写法在length(x)刚好等于frame_len时返回 1不会越界。4.4 现象DCT 之后第一维数值异常大第一维是直流分量等于所有滤波器组对数能量之和量级本来就比后面几维大。如果你直接拿这 13 维算欧氏距离第一维会主导结果。常见做法是对每一维做均值方差归一化CMVN或者把第一维替换成帧对数能量后单独处理。我在声纹比对项目里习惯对倒谱系数做倒谱均值归一化CMN减去每维的帧均值能有效消除信道影响。4.5 现象滤波器组矩阵出现全零行n_filters太大而nfft太小某些三角滤波器的中心频率映射到同一个 FFT bin导致该行全零。log(0)即使加了eps也会得到一个极小的常数该维特征恒定不变等于浪费一个维度。解决办法是保证nfft 4 * n_filters26 个滤波器至少配 128 点 FFT实际用 512 更稳。构造完fb后可以加一句assert(all(sum(fb, 2) 0))做自检。5. 二次开发从能跑到好用我改过的三个地方5.1 加一阶、二阶差分补上动态信息原始 MFCC 只描述当前帧的静态频谱语音里的动态变化比如辅音到元音的过渡靠差分捕捉。标准做法是算一阶差分Delta和二阶差分Delta-Delta和原始 13 维拼成 39 维。差分公式不复杂但边界处理有讲究% deltas.m —— 一阶差分边界用复制填充 function D deltas(C, N) % C: n_ceps × n_frames, N: 差分窗口半宽通常取 2 if nargin 2, N 2; end n_ceps size(C, 1); n_frames size(C, 2); % 边界复制填充避免差分在首尾失真 C_pad [repmat(C(:,1), 1, N), C, repmat(C(:,end), 1, N)]; D zeros(n_ceps, n_frames); denom 2 * sum((1:N).^2); for t 1:n_frames acc zeros(n_ceps, 1); for n 1:N acc acc n * (C_pad(:, t N n) - C_pad(:, t N - n)); end D(:, t) acc / denom; end endN取 2 是惯例窗口太宽会过度平滑太窄对噪声敏感。边界用复制填充而不是补零是因为补零会让首尾帧的差分值异常大模型会学到虚假的边界模式。拼成 39 维后特征矩阵变成39 × n_frames后端模型输入维度记得同步改。5.2 用 CMVN 做特征归一化消除信道差异同一句话用不同麦克风录MFCC 分布会整体平移这是信道卷积效应。倒谱域里信道影响表现为加性偏置减去均值就能抵消大部分。我一般做倒谱均值归一化CMN对每一维减去该维在所有帧上的均值如果数据够多再做方差归一化CVN除以标准差。实现就三行% cmvn.m —— 倒谱均值方差归一化 function C_norm cmvn(C) mu mean(C, 2); sigma std(C, 0, 2); sigma(sigma 1e-8) 1; % 防止除零 C_norm (C - mu) ./ sigma; end注意std第二个参数 0 表示按 N-1 归一化第三个参数 2 表示沿帧维度算。归一化要在差分之前做还是之后做两种流派都有我习惯先对静态 MFCC 做 CMVN再算差分这样差分反映的是归一化后的动态更干净。如果你的数据里静音帧很多均值会被拉偏最好先做端点检测再归一化。5.3 把主流程封装成可配置函数方便批量处理原始代码如果是一段脚本从头跑到尾换一批音频就得改路径重跑效率很低。我一般把它重构成一个extract_mfcc_dir(input_dir, output_dir, config)函数遍历目录下所有 wav逐个提特征存成 mat 或 csv配置项用结构体传。这样调参时只改 config不用动主逻辑。验证方法也简单拿同一段音频分别用原始脚本和封装函数跑对比输出矩阵的max(abs(diff))应该为零。改完记得用两三段不同采样率、不同时长的音频各跑一遍确认没有维度错位或越界。从那以后我每次接手语音特征相关的代码都强制先拿一段已知内容的音频跑通全流程把中间每一步的矩阵尺寸打印出来核对一遍再谈调参和优化。特征提取这层不出错后面的模型才有得谈。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?