3、语音特征提取(下):梅尔频率倒谱系数(MFCC)详解、滤波器组(Fbank)特征、动态特征(Δ、ΔΔ)
上一讲我们把 FFT 和梅尔滤波器组聊透了。今天咱们继续往下走,把语音特征提取最后三板斧讲清楚:MFCC、Fbank,还有那些带「Δ」的动态特征。
说实话,这三个东西在实际工程里天天见。我刚开始做语音识别时,总觉得 MFCC 和 Fbank 差不多,随便选一个就行。后来被坑过一次——嗯,具体哪次后面说。总之,它们各有各的脾气,选错了真会掉点。
3.1 滤波器组(Fbank)特征
Fbank,全称 Filter Bank,中文叫滤波器组特征。说白了,它就是梅尔滤波器组输出的能量值。
还记得上节课我们怎么做的吗?把频谱通过一组三角滤波器,每个滤波器输出一个能量值。把这些能量值取个对数,就是 Fbank 特征。
Fbank 特征提取流程:
- 预加重 → 分帧 → 加窗
- FFT → 功率谱
- 梅尔滤波器组滤波
- 取对数(log)
输出:每帧一个 M 维向量(M 是滤波器个数,通常 40 或 80)
为什么取对数?两个原因。第一,人耳对声音强度的感知是对数的——响度翻倍,你感觉没翻那么多。第二,取对数能把乘性噪声变成加性噪声,后面做归一化好处理。
我的经验:Fbank 特征在深度学习时代越来越流行。因为 DNN 不需要 MFCC 那步 DCT 去相关,原始 Fbank 信息更完整。我在做唤醒词检测时,用 Fbank 比 MFCC 高了 2 个点。当然,这跟任务有关,不是绝对的。
3.2 梅尔频率倒谱系数(MFCC)
MFCC 就是在 Fbank 基础上多了一步:离散余弦变换(DCT)。
为什么要做 DCT?你想想看,Fbank 各个维度之间是相关的——相邻滤波器有重叠,能量自然有冗余。DCT 能把它们去相关,让信息更紧凑地集中在前几个系数上。
# MFCC 提取核心步骤(Python 伪代码)
import numpy as np
from scipy.fftpack import dct
# 假设 fbank 已经是 log 后的梅尔能量,形状 (帧数, 滤波器数)
fbank = np.log(mel_energies + 1e-10) # 加小常数防 log(0)
# DCT 变换,通常保留 2-13 阶系数
mfcc = dct(fbank, type=2, axis=1, norm='ortho')
mfcc = mfcc[:, 1:13] # 去掉第 0 阶(直流分量),保留 12 维
这里有个细节:为什么去掉第 0 阶?第 0 阶代表整帧的平均能量,它受麦克风增益、说话人距离影响很大。去掉它,模型对录音条件的鲁棒性会好很多。
我曾经踩过的坑:有次做说话人识别,MFCC 的 DCT 阶数我选了 20。结果模型在测试集上表现很好,一上线就崩。后来发现,高阶 DCT 系数对噪声特别敏感,泛化能力差。建议语音识别用 12-13 阶,说话人识别可以适当多几阶,但别超过 20。
3.3 MFCC vs Fbank:怎么选?
很多新手会问:到底用哪个?我直接给结论:
| 对比维度 | Fbank | MFCC |
|---|---|---|
| 信息完整性 | 高(保留全部能量分布) | 低(DCT 丢弃了高阶信息) |
| 维度大小 | 40-80 维 | 12-13 维 |
| 去相关性 | 弱(相邻维度相关) | 强(DCT 去相关) |
| 适用模型 | DNN / CNN / Transformer | GMM-HMM(传统方法) |
| 噪声鲁棒性 | 中等 | 较好(去除了冗余) |
我个人习惯:如果是深度学习模型,优先用 Fbank。信息更全,模型自己会学去相关。如果是传统 GMM-HMM 系统,或者特征维度敏感的场景,用 MFCC。
3.4 动态特征:Δ 和 ΔΔ
静态特征只描述当前帧,但语音是连续的——前一帧和后一帧有关系。Δ 特征就是用来捕捉这种时序变化的。
Δ 的计算很简单:当前帧减去前一帧,或者用更平滑的回归公式。
# Δ 特征计算(一阶差分)
def delta(feats, window=2):
"""feats: (帧数, 特征维度)"""
num_frames = feats.shape[0]
delta_feats = np.zeros_like(feats)
for t in range(num_frames):
# 用前后 window 帧做线性回归
numerator = 0
denominator = 0
for w in range(1, window + 1):
if t - w >= 0:
numerator += w * feats[t - w]
denominator += w * w
if t + w < num_frames:
numerator -= w * feats[t + w]
denominator += w * w
if denominator > 0:
delta_feats[t] = numerator / denominator
return delta_feats
# ΔΔ 就是再对 Δ 做一次差分
delta_delta = delta(delta_feats)
为什么需要 Δ 和 ΔΔ?你想想看,静态 MFCC 只告诉你「这帧长什么样」,Δ 告诉你「它在往哪个方向变」,ΔΔ 告诉你「变化的速度是快还是慢」。三个合起来,模型才能感知到语音的动态模式。
标准做法:最终特征 = [静态特征, Δ, ΔΔ],维度变成原来的 3 倍。
例如:13 维 MFCC → 39 维(13 + 13Δ + 13ΔΔ)
40 维 Fbank → 120 维(40 + 40Δ + 40ΔΔ)
3.5 完整特征提取流程
把今天讲的串起来,完整的流程是这样的:
实用建议:实际工程中,我通常这样配:
- 语音识别:40 维 Fbank + Δ + ΔΔ = 120 维
- 说话人识别:20 维 MFCC + Δ + ΔΔ = 60 维
- 情感识别:80 维 Fbank + Δ + ΔΔ = 240 维(情感需要更细的频谱信息)
3.6 避坑指南
最后分享几个我踩过的坑,希望你能绕开:
- 归一化顺序:先做特征提取,再做全局归一化。千万别在分帧前归一化——每帧的均值方差会变,特征就不一致了。
- Δ 窗口大小:默认 window=2 够用。窗口太大,动态信息被平滑掉了;窗口太小,噪声会被放大。
- DCT 阶数选择:别贪多。12 阶 MFCC 在大多数任务上已经够用。多出来的阶数往往是噪声。
- 采样率匹配:16kHz 语音用 40 个滤波器,8kHz 语音用 20-25 个。滤波器个数要和频率范围匹配。
好了,特征提取这块就讲完了。这三种特征——Fbank、MFCC、动态特征——是语音算法的基本功。你把它吃透了,后面无论是做识别、唤醒还是声纹,都会顺手很多。