3. 声学与语音基础

好,咱们进入第三章。前两章我们把语音唤醒的整体框架和音频基础聊完了。这一章,我带你亲手触碰语音信号处理里最核心的一环——特征提取。

说白了,语音信号就是一堆随时间变化的空气压力值。你没法直接把几万、几十万个采样点扔给神经网络去训练,那样计算量太大,而且原始波形里有很多冗余信息。我们需要把它压缩、提炼成一组更紧凑、更有代表性的参数。这就是特征提取干的事。

在语音唤醒领域,最经典的特征就是 MFCC(梅尔频率倒谱系数)。我当年刚入行时,第一个上手的项目就是调 MFCC 的参数,踩了不少坑。今天我把这些经验都摊开来讲。

3.1 为什么需要特征提取?

想象一下,你录了一段 1 秒钟的语音,采样率 16kHz,那就是 16000 个点。如果直接拿这 16000 个点去训练模型,不仅维度高,而且这些点对噪声、说话人差异、音量大小都很敏感。模型很难学到真正有用的模式。

特征提取的目标就是:保留对语音识别有用的信息,丢掉无关的干扰。比如,我们关心的是“这个音是‘a’还是‘i’”,而不关心“说话的人是男是女”或者“录音音量是大是小”。

核心思路:人耳对声音的感知不是线性的。它对低频信号的分辨能力很强,对高频信号则比较粗糙。MFCC 就是模仿人耳的这种听觉特性来设计的。

3.2 MFCC 提取流程

MFCC 的提取不是一步到位的,它是一套流水线。我习惯把它分成 6 个步骤,每一步都有它的道理。

  1. 预加重:提升高频部分的能量。因为语音信号的高频能量通常比低频弱,预加重可以平衡一下,让后续处理更稳定。
  2. 分帧:把长信号切成短段,每段 20-40 毫秒。语音信号在短时间尺度上是平稳的,可以假设它不变。
  3. 加窗:对每一帧加一个汉明窗,减少帧边缘的频谱泄漏。
  4. FFT(快速傅里叶变换):把时域信号转换到频域,得到频谱。
  5. 梅尔滤波器组:用一组三角滤波器对频谱进行加权求和,得到 FBank 特征。
  6. DCT(离散余弦变换):对 FBank 取对数后做 DCT,得到 MFCC。

嗯,这里要注意:第 5 步得到的是 FBank,第 6 步才得到 MFCC。很多人搞混这两个概念,我刚开始也犯过这个错。

3.3 FBank 与 MFCC 对比

FBank 和 MFCC 是“近亲”,但用途不同。我直接用一个表格来对比,这样更清楚。

对比项 FBank(梅尔滤波器组特征) MFCC(梅尔频率倒谱系数)
提取步骤 梅尔滤波器组输出 → 取对数 FBank → DCT 变换
特征维度 通常 40-80 维(与滤波器数量一致) 通常 13-40 维(取 DCT 前若干系数)
相关性 相邻维度之间高度相关 DCT 去相关,各维度相对独立
对噪声的鲁棒性 一般,噪声会直接体现在频谱上 较好,DCT 能分离噪声成分
适用场景 深度学习模型(DNN/CNN/RNN) 传统 GMM-HMM 模型、部分轻量级模型

我的经验:如果你用深度学习做唤醒词识别,我个人更推荐直接用 FBank。因为神经网络自己有能力去学习特征之间的相关性,不需要 DCT 去强行解耦。而且 FBank 保留了更多原始频谱信息,模型能学到更丰富的模式。我在一个项目里对比过,用 FBank 比用 MFCC 的唤醒率高了 2-3 个百分点。

3.4 Python 实现 MFCC 提取(librosa 库)

理论说完了,咱们直接上代码。Python 里最方便的工具就是 librosa,它把整个 MFCC 提取流程封装得特别好。

先安装:

pip install librosa numpy matplotlib

然后看代码:

import librosa
import numpy as np
import matplotlib.pyplot as plt

# 1. 加载音频文件
# 我习惯用 sr=None 保持原始采样率,但这里显式设为 16000
audio_path = 'wake_word_sample.wav'
y, sr = librosa.load(audio_path, sr=16000)

# 2. 提取 MFCC
# n_mfcc=13 是经典配置,我一般取 13 维
mfcc = librosa.feature.mfcc(
    y=y, 
    sr=sr, 
    n_mfcc=13, 
    n_fft=512,      # FFT 窗口大小,对应 32ms(512/16000)
    hop_length=160, # 帧移,对应 10ms
    win_length=400, # 窗长,对应 25ms
    n_mels=40       # 梅尔滤波器数量
)

# 3. 提取 FBank(用于对比)
fbank = librosa.feature.melspectrogram(
    y=y, 
    sr=sr, 
    n_fft=512, 
    hop_length=160, 
    win_length=400, 
    n_mels=40
)
fbank_db = librosa.power_to_db(fbank, ref=np.max)

print(f'MFCC 形状: {mfcc.shape}')   # (13, 帧数)
print(f'FBank 形状: {fbank_db.shape}')  # (40, 帧数)

避坑指南:我曾经在一个项目里直接用了 librosa 的默认参数,结果发现 n_fft=2048,对于 16kHz 的采样率来说,窗口长度是 128ms,太长了!语音信号在 128ms 内变化很大,不满足“短时平稳”的假设。记住:n_fft 一般取 512 或 1024,对应 32ms 或 64ms。

3.5 特征可视化

光看数字不够直观。我习惯把特征画出来,一眼就能看出语音的节奏和能量分布。

# 绘制对比图
plt.figure(figsize=(12, 6))

# 子图1:MFCC
plt.subplot(2, 1, 1)
librosa.display.specshow(
    mfcc, 
    x_axis='time', 
    sr=sr, 
    hop_length=160
)
plt.colorbar(format='%+2.0f dB')
plt.title('MFCC 特征')
plt.ylabel('MFCC 系数')

# 子图2:FBank
plt.subplot(2, 1, 2)
librosa.display.specshow(
    fbank_db, 
    x_axis='time', 
    sr=sr, 
    hop_length=160
)
plt.colorbar(format='%+2.0f dB')
plt.title('FBank 特征')
plt.ylabel('梅尔频带')

plt.tight_layout()
plt.show()

运行这段代码,你会看到两张图。MFCC 的图看起来更“干净”,因为 DCT 把能量集中到了前几个系数上。FBank 的图则更“丰富”,每个频带的能量变化都看得清清楚楚。

你想想看,如果你要检测一个唤醒词,比如“小爱同学”,它的发音在时间轴上的能量分布是有固定模式的。特征可视化能帮你直观地验证:你的特征是否捕捉到了这个模式。

3.6 本章知识体系

为了让你对整个流程有个全局印象,我画了一张图。它把 MFCC 提取的流水线、FBank 和 MFCC 的关系、以及可视化都串起来了。

MFCC 特征提取知识体系 原始语音信号 预加重 → 分帧 → 加窗 FFT → 频谱 梅尔滤波器组 取对数 → FBank 取对数 → DCT → MFCC FBank 特征 (40维) MFCC 特征 (13维) FBank 保留更多频谱细节,适合深度学习;MFCC 去相关后维度更低,适合传统模型

这张图把整个流程串起来了。你从最上面的原始信号开始,一路往下走,到梅尔滤波器组之后分叉:左边是 FBank,右边是 MFCC。记住这个分叉点,后面很多调优工作都围绕它展开。

3.7 本章小结

这一章我们干了三件事:理解了 MFCC 的提取原理,对比了 FBank 和 MFCC 的差异,用 Python 亲手实现了特征提取和可视化。这些东西是语音唤醒的“基本功”,后面所有模型训练都建立在这些特征之上。

我个人建议你拿一段自己的录音跑一遍代码,看看不同参数(比如 n_mfcc、n_mels、n_fft)对特征图的影响。动手试过,印象才深。