4、声学特征提取:梅尔频谱、MFCC、基频与能量特征
好,咱们进入第四章。这一章讲的是声学特征提取,说白了,就是怎么把一段语音波形,变成计算机能听懂、能建模的数字特征。
我刚开始做语音合成时,总觉得特征提取是个“体力活”——不就是算个频谱嘛。后来踩的坑多了才明白,特征选得好不好,直接决定了合成音质的上限。你想想看,模型再牛,喂进去的是垃圾特征,出来的只能是垃圾声音。
4.1 从波形到频谱:我们为什么要“折腾”
原始音频波形,是一维的时间序列。它包含了所有信息,但太原始了。直接拿它做语音合成,模型很难学到声道的物理特性。
为什么会这样?因为人耳对频率的感知不是线性的。我们对低频变化很敏感,对高频变化相对迟钝。梅尔刻度(Mel scale)就是模拟这种听觉特性设计的。
我个人习惯,把特征提取分成三步走:
- 分帧加窗:把长波形切成短段,每段20-50毫秒
- 傅里叶变换:把每帧从时域变到频域
- 特征映射:根据需求,映射到梅尔域或倒谱域
4.2 梅尔频谱(Mel-spectrogram)
梅尔频谱是目前语音合成领域最常用的特征之一。它的计算过程,我简单梳理一下:
# 伪代码示意
def compute_mel_spectrogram(audio, sample_rate=22050):
# 1. 分帧:帧长25ms,帧移10ms
frames = framing(audio, frame_length=0.025, hop_length=0.010)
# 2. 加窗:汉明窗,减少频谱泄露
windowed = frames * hamming_window
# 3. STFT:短时傅里叶变换
stft = np.fft.rfft(windowed)
# 4. 功率谱:取模平方
power = np.abs(stft) ** 2
# 5. 梅尔滤波:用三角滤波器组映射到梅尔刻度
mel_basis = mel_filter_banks(sample_rate, n_mels=80)
mel_spec = np.dot(mel_basis, power)
# 6. 取对数:压缩动态范围
log_mel_spec = np.log(mel_spec + 1e-6)
return log_mel_spec
这里有个关键参数——梅尔滤波器数量。我见过有人用40个,有人用128个。怎么选?
| 应用场景 | 推荐滤波器数 | 说明 |
|---|---|---|
| 语音识别 | 40-80 | 保留主要音素信息即可 |
| 语音合成 | 80-128 | 需要更多细节重建波形 |
| 情感识别 | 64-96 | 兼顾音色和韵律 |
我的经验:做中文语音合成时,我建议用80-100个滤波器。中文有大量元音和声调信息,滤波器太少会丢失细节,太多又增加计算量。我在一个项目中试过128个,效果提升微乎其微,训练时间却长了30%。
4.3 梅尔频率倒谱系数(MFCC)
MFCC是语音识别领域的“老将”了。它和梅尔频谱的区别在于,多了一步离散余弦变换(DCT)。
为什么要做DCT?说白了,就是去相关。梅尔频谱的各个频带之间有很强的相关性,DCT可以把能量集中到前几个系数上。
# MFCC计算流程
def compute_mfcc(log_mel_spec, n_mfcc=13):
# 对梅尔频谱做DCT
mfcc = scipy.fftpack.dct(log_mel_spec, axis=-1)
# 只保留前n_mfcc个系数
mfcc = mfcc[..., :n_mfcc]
return mfcc
嗯,这里要注意:MFCC通常只保留13-20个系数。为什么?因为DCT之后,后面的系数代表的是频谱的精细结构,对语音识别来说往往是噪声。
避坑指南:我曾经在一个语音克隆项目中,直接拿MFCC做特征输入。结果合成的声音像“机器人”,完全没有自然感。后来才发现,MFCC丢弃了太多高频细节,而这些细节恰恰是音色和情感的关键。所以,做语音合成时,我更推荐用梅尔频谱,而不是MFCC。
4.4 基频(F0)——语音的“灵魂”
基频,就是声带振动的频率。它决定了音高,也承载了语调、情感和声调信息。
你想想看,同样一句话“你好”,用陈述语气和疑问语气,基频曲线完全不同。中文的四声,本质上就是基频的四种变化模式。
基频提取常用的方法有:
- 自相关法:简单稳定,适合干净语音
- 倒谱法:抗噪性好,但计算量大
- 深度学习法:如CREPE、PENN,精度高但需要GPU
# 使用librosa提取F0
import librosa
audio, sr = librosa.load('speech.wav', sr=16000)
f0, voiced_flag, voiced_probs = librosa.pyin(
audio,
fmin=librosa.note_to_hz('C2'), # 65Hz
fmax=librosa.note_to_hz('C7') # 2093Hz
)
关键点:基频提取的难点在于“清音”和“静音”段。清音(如s、sh)没有基频,但算法容易误判。我建议用voiced_flag做掩码,只保留有声段的F0值,其余用线性插值填充。
4.5 能量特征——别小看这个“配角”
能量特征,通常指每帧的短时能量。它反映的是音量大小,看似简单,但作用不小。
我记得有一次做情感语音合成,模型合成的悲伤语音听起来“有气无力”。后来分析发现,是能量特征没有处理好。悲伤语音的能量曲线应该是平缓下降的,而模型学到的却是随机波动。
能量特征的计算:
# 短时能量
def frame_energy(frame):
return np.sum(frame ** 2)
# 对数能量(更常用)
def log_energy(frame):
return np.log(np.sum(frame ** 2) + 1e-10)
在实际项目中,我通常把能量特征和梅尔频谱拼接在一起,作为模型的输入。这样模型既能学到频谱细节,又能感知音量变化。
4.6 特征融合:实战中的“组合拳”
单个特征往往不够用。我常用的特征组合方案:
| 任务类型 | 特征组合 | 维度 |
|---|---|---|
| 语音合成(TTS) | 梅尔频谱(80) + F0(1) + 能量(1) | 82维 |
| 语音克隆 | 梅尔频谱(100) + F0(1) + 能量(1) + 说话人嵌入(256) | 358维 |
| 语音识别 | MFCC(13) + Δ(13) + ΔΔ(13) | 39维 |
这里有个细节:F0和能量是标量特征,维度低但信息密度高。我建议在输入模型前,对它们做归一化处理,让数值范围落在[-1, 1]之间。
我的习惯:做特征融合时,我会先单独训练一个只使用梅尔频谱的基线模型。然后逐步加入F0和能量,观察效果提升。如果提升不明显,说明特征可能没对齐,或者模型容量不够。这个方法帮我排过不少雷。
4.7 本章小结
声学特征提取,是语音合成的基础工程。选对特征,事半功倍;选错特征,事倍功半。
最后送你三句话:
- 梅尔频谱:合成任务的首选,信息保留最完整
- MFCC:识别任务的老将,但合成慎用
- F0+能量:看似简单,却是自然度的关键
下一章,我们会把这些特征喂给模型,看看它们到底能发挥多大作用。