4、声学特征提取:梅尔频谱、MFCC、基频与能量特征

好,咱们进入第四章。这一章讲的是声学特征提取,说白了,就是怎么把一段语音波形,变成计算机能听懂、能建模的数字特征。

我刚开始做语音合成时,总觉得特征提取是个“体力活”——不就是算个频谱嘛。后来踩的坑多了才明白,特征选得好不好,直接决定了合成音质的上限。你想想看,模型再牛,喂进去的是垃圾特征,出来的只能是垃圾声音。

4.1 从波形到频谱:我们为什么要“折腾”

原始音频波形,是一维的时间序列。它包含了所有信息,但太原始了。直接拿它做语音合成,模型很难学到声道的物理特性。

为什么会这样?因为人耳对频率的感知不是线性的。我们对低频变化很敏感,对高频变化相对迟钝。梅尔刻度(Mel scale)就是模拟这种听觉特性设计的。

我个人习惯,把特征提取分成三步走:

  1. 分帧加窗:把长波形切成短段,每段20-50毫秒
  2. 傅里叶变换:把每帧从时域变到频域
  3. 特征映射:根据需求,映射到梅尔域或倒谱域

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+能量:看似简单,却是自然度的关键

下一章,我们会把这些特征喂给模型,看看它们到底能发挥多大作用。