4. 特征提取(上):预加重、分帧、加窗、端点检测(VAD)

好,我们进入语音算法里最基础也最关键的一环——特征提取。

很多人觉得特征提取就是调个库,几行代码搞定。但说实话,我在项目里踩过的坑,十有八九都跟这步有关。你想想看,后面不管是做识别、唤醒还是降噪,吃的都是特征提取的“奶”。奶不对,孩子肯定长不好。

这一章我们先搞定前半部分:预加重、分帧、加窗,再加上端点检测(VAD)。这些都是信号层面的基本功,但每一个都有门道。

4.1 预加重:把高频“扶”起来

先问一个问题:为什么语音信号的高频能量通常比低频低?

原因很简单——发声时,声门脉冲的辐射特性决定了能量随频率衰减。说白了,就是嘴唇和空气的阻抗不匹配,高频能量传不出去。如果不处理,后面提取的特征会严重偏向低频,高频信息几乎被淹没。

核心公式:

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

其中 α 通常取 0.95 ~ 0.99。α 越接近 1,高频提升越猛。

我个人习惯用 0.97。为什么?试过几次,0.97 在大多数场景下既不会让高频过冲,又能把 4kHz 以上的能量拉起来。当然,如果你做的是窄带电话语音(8kHz 采样),α 可以适当调小一点,比如 0.95。

一个小技巧:

预加重其实就是一个一阶高通滤波器。你可以用 scipy.signal.lfilter 一行实现。但注意,滤波前最好检查一下信号里有没有直流偏置,否则预加重会把直流放大,导致后续分帧后能量异常。

我曾经在一个车载项目里遇到过这个问题——麦克风有轻微直流偏移,预加重后每帧能量都偏高,VAD 死活检测不准。排查了半天才发现是硬件 bias 的问题。

4.2 分帧:把连续信号切成“小段”

语音信号是非平稳的,但它在短时间内(10~30ms)可以看作是平稳的。这个假设是傅里叶变换能用的前提。所以我们要把长信号切成一段一段的“帧”。

分帧有两个关键参数:

  • 帧长:通常 20~30ms。太短了频率分辨率不够,太长了平稳性假设不成立。
  • 帧移:相邻两帧起始点之间的偏移,通常取帧长的一半或三分之一。
采样率 帧长(ms) 帧长(采样点) 帧移(采样点)
16 kHz 25 400 160
16 kHz 30 480 160
8 kHz 25 200 80

嗯,这里要注意:帧移决定了时间分辨率。帧移越小,帧与帧之间重叠越多,特征序列越长,计算量也越大。但重叠能保证帧间连续性,避免在帧边界处丢失信息。

避坑指南:

我曾经在嵌入式平台上做实时语音识别,为了省算力把帧移设成了帧长的一半(50% 重叠)。结果发现端点检测的延迟大了不少,因为每帧覆盖的时间跨度变短了,VAD 需要更多帧才能确认语音起始。后来改成了 75% 重叠,延迟才降下来。

4.3 加窗:消除频谱泄漏

分帧本质上是把信号截断。截断在频域上会引入什么?——频谱泄漏。说白了,就是本来集中在某个频率的能量,扩散到了旁边的频点上。

加窗就是为了缓解这个问题。窗函数在帧的两端平滑地衰减到零,减少截断带来的突变。

4.3.1 汉明窗(Hamming Window)

汉明窗是最常用的窗之一。它的特点是旁瓣衰减比较大,能有效抑制频谱泄漏。

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

我在做语音识别时,90% 的场景都用汉明窗。它的主瓣宽度适中,旁瓣衰减约 40dB,对大多数语音任务来说够用了。

4.3.2 汉宁窗(Hanning Window)

汉宁窗和汉明窗长得很像,但系数不同:

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

区别在哪?汉宁窗在两端衰减到零,而汉明窗在两端还保留了一点幅度(0.08)。所以汉宁窗的旁瓣衰减更大(约 60dB),但主瓣稍微宽一点。

窗类型 主瓣宽度 旁瓣衰减 适用场景
矩形窗 最窄 ~13 dB 几乎不用(泄漏严重)
汉明窗 中等 ~40 dB 语音识别、说话人识别
汉宁窗 稍宽 ~60 dB 需要更高旁瓣抑制的场景

我的选择建议:

如果你不确定用哪个,先上汉明窗。它是最稳妥的选择。汉宁窗更适合那些对频谱纯净度要求极高的场景,比如音乐信号分析或精细的谐波提取。

4.4 端点检测(VAD):找到语音的“起止点”

VAD 的全称是 Voice Activity Detection。它的任务很简单:判断一段信号里,哪些部分是语音,哪些是静音或噪声。

为什么重要?因为后续的特征提取、识别、编码,都不希望处理无意义的噪声段。VAD 做得好,能大幅降低计算量,还能提升识别准确率。

4.4.1 基于能量的 VAD

最朴素的方法:计算每帧的短时能量,跟一个阈值比。

E = sum(x[n]^2) / N

如果能量高于阈值,判为语音;否则判为静音。

但这个方法有个致命问题——阈值怎么定?环境噪声一变,阈值就得跟着调。我在一个户外项目里试过,白天和晚上的噪声能量差了 10dB,固定阈值根本没法用。

4.4.2 基于过零率的 VAD

过零率(ZCR)是信号穿过零轴的次数。清音(如 /s/、/f/)的过零率很高,浊音(如 /a/、/i/)的过零率较低。结合能量和过零率,可以更鲁棒地判断语音段。

常用的策略是:

  1. 先计算背景噪声的能量和过零率基线。
  2. 每帧的能量和过零率同时超过基线一定倍数时,判为语音。
  3. 连续多帧低于基线时,判为静音。

一个简单的 VAD 流程:

1. 取前 10 帧计算噪声能量均值 E_noise 和过零率均值 Z_noise
2. 对每一帧:
   if E_frame > E_noise * 2.0 and Z_frame > Z_noise * 1.5:
       标记为语音
   else:
       标记为静音
3. 用 hangover 机制(延迟判决)避免语音中间短暂中断

4.4.3 基于谱熵的 VAD

能量和过零率在低信噪比下容易失效。这时候可以考虑谱熵。语音信号的频谱分布比较集中(熵低),而噪声的频谱分布比较均匀(熵高)。通过计算每帧的谱熵,可以更鲁棒地分离语音和噪声。

我在一个工厂噪声环境(SNR 约 5dB)里试过,基于能量的 VAD 基本废了,但谱熵 VAD 还能保持 80% 以上的准确率。

注意:

谱熵 VAD 计算量比能量法大不少,在低功耗设备上要谨慎使用。另外,谱熵对音乐、警报声等非语音信号也敏感,容易误判。

4.5 本章知识体系

下面这张图把本章的核心流程串起来了。你可以看到,原始语音信号经过预加重、分帧、加窗后,变成了一帧一帧的加窗信号,然后通过 VAD 筛选出有效的语音帧,送入后续的特征提取模块。

特征提取(上)知识体系流程图 特征提取(上)核心流程 原始语音信号 预加重 分帧 加窗 端点检测(VAD) 有效语音帧 VAD 常用方法 • 基于能量:简单快速 • 基于过零率:区分清浊音 • 基于谱熵:抗噪能力强 • 综合法:能量+ZCR+谱熵 • 深度学习法:DNN/VGG

嗯,这张图里我把 VAD 的几种常用方法也列出来了。你可以根据自己的场景选——低信噪比用谱熵,低算力用能量法,追求效果用综合法或深度学习法。

4.6 小结

这一章我们聊了预加重、分帧、加窗和 VAD。这些都是语音特征提取的“地基”。

  • 预加重:用一阶高通滤波器把高频能量补回来,α 取 0.97 是个好起点。
  • 分帧:帧长 20~30ms,帧移取帧长的一半到四分之一,重叠能保证帧间连续性。
  • 加窗:汉明窗是万金油,汉宁窗适合高要求场景。别用矩形窗,除非你想看频谱泄漏的“烟花”。
  • VAD:从简单的能量法到复杂的谱熵法,选哪种取决于你的噪声环境和算力预算。