☰
MFCC特征提取全链路解析:从原理到手写实现
2026/10/29 3:56:16 网站建设 项目流程

音频特征提取这个圈子里,MFCC 几乎是绕不开的一个词。不管你是做语音识别、说话人辨认,还是搞环境声音分类,翻开源码或者论文,十有八九会撞见它。但很多人对它的理解停留在“调个库、拿个 13 维向量喂模型”这一步,真要问一句“这 13 个数到底是怎么来的、每一步在干什么、为什么非得这么绕”,就卡壳了。我自己最早也是这样,直到有一次模型效果死活上不去,回头把特征提取链路从头捋了一遍,才发现问题出在对 MFCC 某个环节的理解偏差上。

这篇东西就是想把 MFCC 从信号到特征向量的完整生成链路拆开讲透,顺带聊聊它几个容易被忽略的特性,以及在实际项目里到底该怎么用、什么时候不该用。适合已经上手过音频处理、但想把底层逻辑补扎实的读者,也适合刚接触语音方向、想搞明白“为什么大家都用这个”的新手。我会尽量用生活化的类比配合具体参数,把每一步的动机和计算过程都摆出来,让你看完能自己手写一版 MFCC,而不是只会调包。

1. 从人耳的非线性感知说起:MFCC 到底在模拟什么

要理解 MFCC,得先接受一个前提:人耳对频率的感知不是线性的。同样是 100 Hz 的间隔,从 100 Hz 到 200 Hz 你能明显听出音高变化,但从 10000 Hz 到 10100 Hz,你大概率分辨不出来。这说明人耳对低频变化敏感、对高频变化迟钝,感知刻度更接近对数关系而非线性关系。

MFCC 的核心思路,就是把这套“人耳感知特性”用数学方式近似出来。它不直接拿原始频谱做特征,而是先把频率轴按人耳的敏感度重新划分,再在这个重划的刻度上提取包络信息。所以 MFCC 本质上是一种感知驱动的特征表示,而不是纯粹的数学变换。这一点很关键,它解释了后面每一步设计背后的动机。

1.1 梅尔刻度:把 Hz 轴“掰弯”的那把尺子

梅尔刻度(Mel Scale)就是用来量化这种非线性感知的。它的定义方式有很多版本,工程上最常用的是下面这个近似公式:

mel(f) = 2595 * log10(1 + f / 700)

反过来,从梅尔值换算回频率:

f = 700 * (10^(mel / 2595) - 1)

你可以拿几个点感受一下这个映射的弯曲程度。1000 Hz 对应的梅尔值大约是 1000,这个点被设计成两种刻度重合的锚点;而 10000 Hz 对应的梅尔值只有大约 3073。也就是说,从 1 kHz 往上走 9 kHz 的巨大频率跨度,在梅尔刻度上只占了约 2000 个单位,而 0 到 1 kHz 这段就占了 1000 个单位。低频被“拉伸”、高频被“压缩”,这正是人耳感知的写照。

提示:不同教材和库对梅尔公式的常数取值略有差异(比如有的用 2595,有的用 1127 配合自然对数),换算结果在小数级别上有出入,但对特征提取的整体效果影响可以忽略。选一套用到底就行,别中途混用。

理解了这把“掰弯的尺子”,后面的滤波器组设计就顺理成章了——所谓梅尔滤波器组,无非就是在这把非线性尺子上等间距地摆一排三角窗。

1.2 倒谱:为什么要在“频谱的频谱”上做文章

MFCC 名字里的“倒谱”(Cepstrum)是另一个容易让人懵的概念。倒谱的操作对象是频谱本身,相当于对频谱再取一次对数再做一次逆变换。为什么要这么绕?

打个比方。一段语音信号可以粗略看成“声源激励”和“声道滤波”两部分的卷积结果:声带振动产生基频及其谐波(激励),口腔鼻腔的形状对这部分能量进行调制(滤波)。我们真正想拿到的、能区分不同发音内容的,是声道滤波的特性,也就是频谱的包络;而基频谐波表现为频谱上的细密波纹。问题在于,这两者在原始频谱里是乘性叠加在一起的,直接看频谱很难把包络和波纹分开。

取对数之后,乘性关系变成加性关系,包络和波纹在对数频谱上变成两个不同“节奏”的成分:包络变化缓慢(低频成分),谐波波纹变化剧烈(高频成分)。这时候再做一次逆变换(通常是 DCT,离散余弦变换),就把“节奏快慢”映射成了倒谱域里的“位置高低”——缓慢的包络集中在倒谱的低位,剧烈的谐波波纹被推到高位。我们只取低位的那些系数,就相当于把声道包络单独拎了出来,把基频谐波的干扰甩掉了。

这就是 MFCC 里“倒谱”二字的全部意义:用一次对数加一次逆变换,把卷积关系解耦成加性关系,从而分离激励与声道。理解了这一层,你就明白为什么 MFCC 对音高变化相对鲁棒,而对音色(声道特性)敏感——它天生就是冲着声道包络去的。

2. 一步步手推 MFCC:从波形到特征向量的完整链路

这一节是全文的重头戏。我把 MFCC 的标准生成流程拆成六个阶段,每个阶段都讲清楚“做什么、为什么这么做、参数怎么定”。整个链路是:预加重 → 分帧加窗 → FFT 求频谱 → 梅尔滤波器组 → 取对数 → DCT。下面逐个拆。

2.1 预加重:先把高频“提”一把

预加重(Pre-emphasis)是链路里的第一道工序,作用对象是原始时域波形。它做的事情很简单,用一个一阶高通滤波器对信号做处理:

y[n] = x[n] - α * x[n-1]

α 通常取 0.97 或 0.95。这个操作的动机有两层。第一,语音信号的能量主要集中在低频,高频部分能量衰减很快,预加重把高频抬起来,能让频谱更平坦,后续分析时高频细节不至于被低频淹没。第二,从信号产生的物理过程看,声门激励的频谱本身有大约 -6 dB/oct 的下降趋势,预加重正好补偿这个倾斜,让声道特性更清晰地暴露出来。

α 的取值不是随便定的。0.97 对应的高通转折点大约在几十 Hz 量级,能有效压制低频但保留语音主要频段。如果你处理的不是语音而是音乐或环境声,α 可以适当调小甚至跳过这一步,因为那些信号的频谱倾斜特性和语音不同,盲目预加重反而会破坏原始信息。我见过有人做鸟鸣分类时照搬语音的 0.97,结果高频噪声被放大,特征质量反而下降,这就是没考虑场景差异。

2.2 分帧与加窗:把非平稳信号切成“准平稳”小段

语音信号整体上是非平稳的(统计特性随时间变化),但在极短时间(10~30 ms)内可以近似看作平稳。分帧就是基于这个假设,把长信号切成短段,每段单独分析。

关键参数有两个:帧长和帧移。帧长通常取 20~40 ms,对应 16 kHz 采样率下就是 320~640 个采样点。帧移一般取帧长的一半,也就是 10~20 ms,这样相邻帧之间有 50% 的重叠,避免帧边界处的信息丢失。为什么要有重叠?因为如果帧与帧首尾相接不重叠,正好落在边界上的音素过渡信息会被切断,特征序列会出现跳变。50% 重叠是工程上的经典折中,既保证连续性又不至于计算量翻倍。

分完帧还要加窗。直接对一帧信号做 FFT 会产生频谱泄漏——因为 FFT 隐含假设信号是周期延拓的,而一帧信号的起点和终点幅值通常不连续,这个突变会在频域产生拖尾。加窗就是让帧两端平滑衰减到接近零,减小不连续。最常用的是汉明窗(Hamming):

w[n] = 0.54 - 0.46 * cos(2πn / (N-1)), 0 ≤ n ≤ N-1

汉明窗的主瓣宽度适中、旁瓣衰减较好,是语音处理的默认选择。汉宁窗(Hann)旁瓣衰减更快但主瓣更宽,频率分辨率略低。矩形窗基本不用,泄漏太严重。这里有个实操细节:加窗会让帧两端的信息被削弱,所以帧移取 50% 重叠不只是为了连续性,也是为了补偿加窗带来的边缘信息损失。

2.3 FFT 与功率谱:把时域搬到频域

对每一帧加窗后的信号做 N 点 FFT,得到复数频谱,再取模平方得到功率谱。N 的取值一般是 2 的整数次幂,且不小于帧长,比如帧长 400 点就取 512 点 FFT,多出来的部分补零。补零不增加真实频率分辨率,但能让频谱采样更密,方便后续滤波器组对齐。

功率谱的每个点对应一个频率 bin,频率分辨率是fs / N。以 16 kHz 采样、512 点 FFT 为例,分辨率是 31.25 Hz。这个分辨率对低频来说偏粗(低频相邻谐波可能挤在一个 bin 里),但 MFCC 后续会用梅尔滤波器组做频带合并,所以对单点分辨率的要求没那么苛刻。

注意:这里用的是功率谱(模平方)而不是幅度谱。取平方会让能量差异更突出,配合后面的对数操作,正好构成“能量→对数能量”的压缩链路,这也是 MFCC 对音量变化有一定鲁棒性的来源之一。

2.4 梅尔滤波器组:在弯掉的频率轴上摆三角窗

这是 MFCC 最具辨识度的一步。做法是在梅尔刻度上等间距地放置一组三角形滤波器,每个滤波器覆盖一段频率范围,对功率谱做加权求和,把高维的频谱压缩成低维的频带能量。

具体设计流程是这样的:先确定滤波器个数 M(常用 20~40 个,取 26 是很多经典实现的默认值),再确定覆盖的频率范围(通常 0 到奈奎斯特频率,语音场景常截到 8 kHz 或 4 kHz)。把这 M 个滤波器的中心频率在梅尔刻度上等间距排布,然后换算回 Hz,就得到每个三角窗的起点、中心、终点。

每个三角滤波器的响应是:

H_m(k) = 0 , k < f(m-1) = (k - f(m-1)) / (f(m) - f(m-1)) , f(m-1) ≤ k ≤ f(m) = (f(m+1) - k) / (f(m+1) - f(m)) , f(m) ≤ k ≤ f(m+1) = 0 , k > f(m+1)

其中 f(m) 是第 m 个滤波器的中心频率。因为中心频率在梅尔刻度上等间距,换算到 Hz 后,低频的滤波器窄而密、高频的滤波器宽而疏,正好匹配人耳的敏感度分布。

滤波器个数 M 的选择是个权衡:M 太小,频带划分太粗,丢失频谱细节;M 太大,频带太窄,各滤波器输出高度相关,冗余增加且计算量上升。26 这个数字之所以流行,是因为它大致对应人耳临界带宽的数量级,在 0~8 kHz 范围内能给出合理的频带划分。实际项目里 20~40 都可以试,做说话人识别时有人会加到 40 甚至更多以保留更多细节。

2.5 取对数:把乘性关系变成加性

对每个滤波器的输出能量取自然对数。这一步看着不起眼,作用却很大。前面讲倒谱时提过,取对数是为了把卷积(乘性)关系转成加性关系,为后面的 DCT 解耦做准备。除此之外,对数还有压缩动态范围的作用——语音能量的动态范围可以跨越好几个数量级,取对数后变成相对平缓的数值,数值稳定性更好,也符合人耳对响度的对数感知特性。

这里有个容易踩的坑:如果某个滤波器输出能量为 0(静音段或极端频段),取对数会得到负无穷。工程实现里通常加一个极小的常数(如 1e-10)兜底,或者对能量设一个下限。这个细节在调库时被封装掉了,但自己手写时一定要处理,否则会冒出 NaN 污染整个特征矩阵。

2.6 DCT:把频带能量转成倒谱系数

最后一步,对 M 个对数能量做离散余弦变换(DCT),得到倒谱系数。DCT 的公式(常用第二种形式):

C(n) = Σ_{m=0}^{M-1} log(E_m) * cos(π * n * (m + 0.5) / M), n = 0, 1, ..., M-1

变换后得到 M 个系数,通常只保留前 12~13 个(不含第 0 个,或者第 0 个单独处理),这就是最终的 MFCC 特征。为什么只取前几个?因为 DCT 把能量按“变化快慢”重新排列,低阶系数对应频谱包络的缓慢变化(声道特性),高阶系数对应快速变化(谐波细节和噪声)。丢掉高阶系数相当于做了一次低通滤波,保留了最稳定的声道信息,同时降维去冗余。

第 0 个系数 C(0) 比较特殊,它正比于所有对数能量的和,反映的是整帧的总体能量水平,和音色关系不大。很多实现会把它单独拿出来当作“对数能量特征”,或者干脆丢弃,只保留 C(1) 到 C(12) 这 12 维。这也是为什么你常看到“13 维 MFCC”和“12 维 MFCC”两种说法——前者含 C(0),后者不含。

3. MFCC 的几个关键特性:它擅长什么、怕什么

把生成链路走完,再回头看 MFCC 的特性就清楚多了。这些特性不是凭空来的,每一条都能在生成步骤里找到对应的根源。理解这些,你才能在项目里判断该不该用它、该怎么调它。

3.1 对音高变化鲁棒,对音色敏感

这是 MFCC 最核心的特性,根源在倒谱解耦那一步。因为 MFCC 提取的是频谱包络(声道特性),而基频谐波被推到了倒谱高位丢弃了,所以同一个说话人用不同音高说同一个字,MFCC 的差异不会太大;但不同说话人说同一个字,声道形状不同导致包络不同,MFCC 差异明显。这个特性让 MFCC 在说话人识别和语音识别里都表现不错——前者要的就是对音色敏感,后者要的是对音高变化不敏感(不同人说同一个词音高各异,但内容相同)。

反过来说,如果你的任务恰恰依赖音高信息,比如音乐旋律提取或声调语言的四声识别,纯 MFCC 就不够用了,得额外补充基频(F0)特征或者用能保留音高信息的表示。我做过一个普通话声调分类的小实验,单靠 MFCC 准确率卡在七成多上不去,加上 F0 轮廓后直接跳到九成,这就是特征和任务匹配度的直观体现。

3.2 维度低、去相关,适合喂给浅层模型

MFCC 把一帧几百上千维的频谱压缩到 12~13 维,降维幅度惊人,而且 DCT 本身有去相关的作用(不同阶系数之间近似不相关)。这两个特点让它特别适合喂给 GMM、HMM、SVM 这类对特征维度和相关性敏感的浅层模型。传统语音识别时代 MFCC 能统治那么多年,和当时主流模型的计算能力、建模方式是分不开的。

但到了深度学习时代,这个优势就没那么绝对了。神经网络本身能从高维、相关的特征里自动学表示,MFCC 的强压缩反而可能丢掉一些有用信息。所以现在不少端到端语音模型直接用滤波器组特征(Filter Bank,简称 FBANK,就是 MFCC 链路里 DCT 之前的那一步)甚至原始波形,把降维和去相关的活儿交给网络自己做。这是个值得注意的趋势:MFCC 不是万能的,它的优势在特定模型和场景下才成立。

3.3 对加性噪声敏感,需要额外处理

MFCC 对背景噪声的鲁棒性一般。原因在于噪声会改变频谱包络,而 MFCC 恰恰是在包络上做文章,噪声直接污染了它要提取的核心信息。尤其是平稳噪声(如风扇声、空调声),会在整个频谱上抬升能量,扭曲滤波器组的输出。

工程上的应对手段有几类:一是前端做降噪或语音增强,把干净信号再送进 MFCC;二是用倒谱均值归一化(CMN)或均值方差归一化(MVN),把不同环境的整体偏移抹掉;三是改用对噪声更鲁棒的特征,比如基于相位或基于深度学习的特征。CMN 是最简单有效的一招,做法是对每个倒谱系数维度减去整段语音的均值,能抵消信道和稳态噪声带来的加性偏移,几乎零成本,建议默认加上。

3.4 帧间独立,丢失了时序动态

标准 MFCC 是逐帧计算的,每帧得到一个向量,帧与帧之间没有显式关联。但语音的动态信息(比如音素的过渡、语速变化)恰恰藏在帧间的变化里。为了补这个短板,工程上常用差分特征:对 MFCC 序列沿时间轴求一阶差分(Δ)和二阶差分(ΔΔ),拼到原特征后面,形成 39 维(13+13+13)的特征向量。差分特征刻画了倒谱系数的变化速度,能显著提升识别效果。

差分的计算通常用一个回归公式,窗口宽度取 2 左右:

Δc_t = Σ_{k=1}^{K} k * (c_{t+k} - c_{t-k}) / (2 * Σ_{k=1}^{K} k^2)

K 一般取 2,也就是用前后各两帧来估计当前帧的变化率。这个操作简单但有效,是传统语音特征工程里的标配。需要注意的是,差分会让特征在序列两端出现边界效应,实现时通常对两端做复制填充。

4. 参数怎么调:一份来自实操的配置参考

理论讲完,落到实操。MFCC 的参数不少,每个都有惯用取值,但惯用不等于最优。这一节我把关键参数的影响和调整思路整理出来,配合一张对照表,方便你按自己的场景改。

4.1 采样率与频率范围的取舍

采样率决定了能分析的频率上限(奈奎斯特频率 = 采样率/2)。语音识别常用 16 kHz,覆盖 0~8 kHz,足够容纳语音的主要信息(语音能量和可懂度贡献主要集中在 4 kHz 以下,但 4~8 kHz 对辅音清晰度有贡献)。如果只做关键词检测或对计算量敏感,8 kHz 采样、0~4 kHz 分析也够用,特征维度可以相应减少。

这里有个常被忽略的点:滤波器组的覆盖范围要和采样率匹配。如果你用 16 kHz 采样却把滤波器组只铺到 4 kHz,那 4~8 kHz 的信息就白白浪费了;反过来,用 8 kHz 采样却把滤波器铺到 8 kHz,最高几个滤波器会落在没有有效信号的空频段,输出接近零,取对数后全是极小值,等于引入了噪声维度。所以采样率和滤波器范围要一起定。

4.2 帧长帧移与滤波器个数的联动

帧长影响频率分辨率,帧移影响时间分辨率,两者是一对矛盾。帧长太短,频率分辨率不够,低频谐波分不开;帧长太长,平稳假设不成立,特征会糊掉。25 ms 帧长 + 10 ms 帧移是语音领域的黄金组合,对应 16 kHz 下 400 点帧长、160 点帧移。做音乐分析时帧长可以拉长到 40~50 ms,因为音乐信号的平稳性维持得更久。

滤波器个数 M 和帧长也有隐性关联。帧长决定了 FFT 点数,进而决定频率 bin 的数量,滤波器个数不能超过有效 bin 数太多,否则每个滤波器覆盖的 bin 太少,加权求和的统计意义不足。一般 M 取 20~40,配合 512 点 FFT 是安全的。

下面这张表汇总了常用参数和调整方向,可以直接对照自己的场景改:

参数常用取值调大的影响调小的影响
预加重系数 α0.95~0.97高频提升更强,低频压制更狠高频补偿不足
帧长20~40 ms频率分辨率高,时间分辨率低时间分辨率高,频率分辨率低
帧移帧长的 50%特征帧数少,计算省特征帧数多,时间粒度细
FFT 点数≥帧长,2 的幂频谱采样密,计算量增频谱采样粗
滤波器个数 M20~40频带细,细节多,冗余增频带粗,细节少
保留倒谱系数12~13保留更多高频倒谱信息更强调包络,更平滑

4.3 归一化与差分:两个几乎必加的收尾操作

前面提过 CMN 和差分,这里强调一下它们在实操中的地位。CMN(倒谱均值归一化)几乎是零成本地提升跨环境鲁棒性,做法就是沿时间轴对每个维度减均值。如果数据里不同样本的录制条件差异大,还可以做方差归一化(MVN),把每个维度除以标准差。这两个操作在特征矩阵层面就能完成,不需要改提取链路。

差分特征则是补时序动态的标准手段。13 维 MFCC 加一阶二阶差分变 39 维,是传统语音识别的经典配置。做深度学习时,如果用的是 CNN 或 RNN,网络本身能建模时序,差分可以不加;但如果用简单的全连接网络或传统分类器,差分基本是必加的。

提示:做 CMN 时要注意,均值应该在整段语音上算,而不是逐帧算。逐帧减均值等于把每帧的能量信息也抹掉了,会损失有用信息。另外,如果一段语音里包含长段静音,静音帧会拉偏均值,最好先做语音活动检测(VAD)把静音剔掉再算均值。

5. 典型应用场景与选型判断

MFCC 的应用面很广,但不同场景下它的表现和用法差异不小。这一节挑几个典型场景聊聊具体怎么用,以及什么情况下该考虑换别的特征。

5.1 语音识别与关键词检测

这是 MFCC 最经典的主场。传统 HMM-GMM 时代,MFCC 加差分是标配;即便到了深度学习时代,很多轻量级关键词检测模型仍然用 MFCC 或 FBANK 作为输入,因为计算量小、维度低,适合部署在资源受限的设备上。

实操上,做关键词检测时我一般会用 13 维 MFCC 加差分,配合 CMN,帧长 25 ms、帧移 10 ms。如果目标词很短(比如单音节唤醒词),帧移可以缩到 5~10 ms 提高时间粒度。滤波器个数用 26 或 40 都行,40 在噪声环境下略稳一些。

5.2 说话人识别与声纹

说话人识别要的是音色信息,正好是 MFCC 擅长的。这里通常会用到更多滤波器(40 左右)和更多倒谱系数(16~20 维),因为要保留更多声道细节。另外,说话人识别里 CMN 几乎是必须的,用来抵消信道差异——同一个人的录音在不同设备上录,信道特性不同,不归一化的话模型容易把信道当成说话人特征学进去。

有个经验:做声纹时单纯用 MFCC 的静态特征效果有限,配合差分和长时统计量(比如对整段语音的 MFCC 求均值和方差,形成超向量)效果会好很多。这也是 i-vector 时代的基本思路。

5.3 环境声音分类与音乐信息检索

环境声音分类(比如识别狗叫、汽车鸣笛、玻璃破碎)用 MFCC 也能做,但要注意环境声的频谱特性和语音差别很大,预加重那一步往往可以跳过或减弱,滤波器组的频率范围也要按具体声音调整。比如识别鸟鸣,高频信息很重要,滤波器组就得铺到更高的频率。

音乐信息检索里 MFCC 用得相对少一些,因为音乐的音高和和声信息很关键,而 MFCC 恰恰弱化了这部分。做音乐流派分类或乐器识别时,MFCC 可以作为辅助特征,但通常要配合色度特征(Chroma)、频谱对比度等一起用。

5.4 什么时候该考虑放弃 MFCC

说了这么多 MFCC 的好,也得说说什么情况下它不合适。第一,任务强依赖音高或细粒度频谱结构时(声调识别、旋律提取、部分音乐任务),MFCC 的强压缩会丢信息。第二,用端到端深度模型且数据量充足时,直接喂 FBANK 或原始波形往往效果更好,让网络自己学表示。第三,极低信噪比环境下,MFCC 对噪声敏感,可能需要先用深度降噪前端处理。

判断标准其实很简单:看你的任务需要的是“包络”还是“细节”。需要包络(音色、声道、整体频谱形状)就用 MFCC;需要细节(音高、谐波、瞬态)就换或补别的特征。这个判断比盲目试参数有效得多。

6. 手写一版 MFCC:把链路串起来的实操记录

光看公式容易飘,动手写一遍才踏实。这一节我用 Python 把整条链路串起来,不依赖专门的音频特征库,只用 numpy 和 scipy 的基础函数,帮你把前面每一节的内容落到实处。写的过程中我会标注几个容易出错的点。

6.1 核心函数逐段实现

先定义梅尔换算和滤波器组构建这两个基础件:

import numpy as np def hz_to_mel(f): return 2595 * np.log10(1 + f / 700) def mel_to_hz(mel): return 700 * (10 ** (mel / 2595) - 1) def mel_filterbank(n_filters, n_fft, fs, f_min=0, f_max=None): if f_max is None: f_max = fs / 2 mel_min = hz_to_mel(f_min) mel_max = hz_to_mel(f_max) mel_points = np.linspace(mel_min, mel_max, n_filters + 2) hz_points = mel_to_hz(mel_points) bins = np.floor((n_fft + 1) * hz_points / fs).astype(int) fb = np.zeros((n_filters, n_fft // 2 + 1)) for m in range(1, n_filters + 1): left, center, right = bins[m-1], bins[m], bins[m+1] for k in range(left, center): fb[m-1, k] = (k - left) / (center - left) for k in range(center, right): fb[m-1, k] = (right - k) / (right - center) return fb

这里有个细节值得说:bins用(n_fft + 1) * hz / fs换算而不是n_fft * hz / fs,是因为 FFT 输出的频率 bin 对应关系在不同实现里有细微差异,用n_fft + 1是很多参考实现的做法,能避免最高频滤波器越界。另外要保证center > left且right > center,否则会出现除零,滤波器个数太多或 FFT 点数太小时容易触发,加个保护更稳妥。

然后是主流程:

def mfcc(signal, fs=16000, frame_len=400, frame_shift=160, n_fft=512, n_filters=26, n_ceps=13, alpha=0.97): # 预加重 emphasized = np.append(signal[0], signal[1:] - alpha * signal[:-1]) # 分帧 n_frames = 1 + (len(emphasized) - frame_len) // frame_shift frames = np.stack([ emphasized[i*frame_shift : i*frame_shift + frame_len] for i in range(n_frames) ]) # 加窗 window = np.hamming(frame_len) frames = frames * window # FFT 与功率谱 spectrum = np.fft.rfft(frames, n=n_fft) power = np.abs(spectrum) ** 2 # 梅尔滤波器组 fb = mel_filterbank(n_filters, n_fft, fs) filter_energy = power @ fb.T # 取对数(加小常数防 log(0)) log_energy = np.log(filter_energy + 1e-10) # DCT from scipy.fftpack import dct ceps = dct(log_energy, type=2, axis=1, norm='ortho') return ceps[:, :n_ceps]

这段代码跑通后,拿一段语音试一下,输出的形状应该是(帧数, 13)。如果出现 NaN,八成是log_energy那步没加常数;如果滤波器组报除零,检查滤波器个数和 FFT 点数是否匹配。

6.2 验证与调试:怎么确认自己写对了

手写实现最容易出的问题是“看起来能跑但结果不对”。验证方法有几个。第一,和成熟库(比如 librosa 的feature.mfcc)对比,注意对齐参数(帧长、帧移、滤波器个数、是否含 C(0)),允许小幅数值差异,但整体趋势要一致。第二,拿一段纯音或白噪声输入,观察特征是否符合预期——纯音应该在特定频带能量集中,白噪声应该各频带能量均匀。第三,可视化几帧的 MFCC 热图,正常语音的 MFCC 应该能看到清晰的时序结构,而不是一片噪声。

我调试时踩过的一个坑是 DCT 的norm参数。不同库对 DCT 的归一化方式不同,norm='ortho'是正交归一化,和某些参考实现的缩放系数不一致,导致特征幅值差一个常数因子。这个差异对模型训练影响不大(归一化后会被抹平),但如果你要和别人的数值严格对比,就得注意对齐。

6.3 从特征到模型:几个衔接上的注意事项

特征算出来只是第一步,喂给模型前还有几件事要做。首先是特征归一化,前面说的 CMN 在这一步做,沿时间轴减均值。其次是静音处理,长段静音会产生大量低能量帧,干扰模型,建议用能量阈值或 VAD 剔除。第三是序列长度对齐,不同语音的帧数不同,喂给固定输入尺寸的模型时需要截断或填充,填充值一般用零或该数据集的均值。

还有一点:如果你的模型对输入尺度敏感(比如 SVM 或某些距离度量),记得对每个特征维度做标准化(减均值除标准差),而不是只做 CMN。CMN 只减均值不除方差,对尺度敏感的模型不够。

7. 几个反复被问到的细节问题

写到这儿,把平时被问得最多的几个问题集中答一下,都是些文档里不常写、但实操中绕不开的点。

MFCC 和 FBANK 到底选哪个?简单说,FBANK 是 MFCC 去掉 DCT 那一步的结果,保留了更多原始频带信息,维度更高(等于滤波器个数)。传统模型用 MFCC,深度模型用 FBANK 更常见。如果你不确定,两个都试,看验证集表现。经验上,数据量小、模型浅时 MFCC 更稳;数据量大、模型深时 FBANK 上限更高。

为什么我算出来的 MFCC 和库里的对不上?大概率是参数没对齐。重点检查:预加重系数、帧长帧移、窗函数类型、FFT 点数、滤波器个数和频率范围、DCT 类型和归一化、是否含 C(0)。这些里任何一个不同,结果都会有差异。建议先固定一套参数,和库逐一对比中间结果(功率谱、滤波器组输出、对数能量),定位到具体哪一步开始分叉。

倒谱系数的个数怎么定?12~13 是语音的经典值,因为语音的声道特性用这么多系数基本能描述清楚,再多就是谐波和噪声了。做说话人识别可以加到 16~20,做音乐或环境声可以适当增加。但也不是越多越好,高阶系数噪声大,加多了反而拖累模型。

MFCC 能不能用于非语音信号?能,但要调整。MFCC 的设计假设(预加重补偿语音频谱倾斜、滤波器组覆盖语音频段)是针对语音的,用在振动信号、心电、机械声上时,这些假设不成立,参数得重新设计。我见过把 MFCC 直接套到轴承故障诊断上的,效果时好时坏,后来把预加重去掉、滤波器范围按故障特征频率重设,才稳定下来。所以跨领域用时,别照搬语音的默认参数。

实时场景下 MFCC 的计算开销大吗?不大。一帧 512 点 FFT 加 26 个滤波器的加权求和,在现代处理器上是微秒级的事,嵌入式设备也能实时跑。真正的开销往往在前后处理(降噪、VAD、归一化)上,而不是 MFCC 本身。如果要做低延迟,帧长和帧移可以适当缩小,代价是频率分辨率下降。

这些问题的共同点是:答案都藏在生成链路的某个具体环节里。把链路吃透,大部分疑问自己就能推出来。这也是我写这篇东西的初衷——与其记一堆结论,不如把推导过程装进脑子里,遇到新场景能自己判断该怎么调。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询