4. 信号预处理技术:预加重、分帧与加窗、端点检测(VAD)、降噪滤波
各位好,欢迎来到第四章。前面几章我们把麦克风阵列的物理基础、声学模型和信号模型都过了一遍。从这章开始,咱们要真正动手处理音频信号了。
预处理这一步,说白了就是给原始音频信号“洗个澡”。麦克风采集到的原始信号,直接拿去做波束成形或者声源定位,效果往往惨不忍睹。为什么?因为信号里有太多“杂质”——低频能量太强、噪声太大、静音段太长。我刚开始做阵列算法时,就吃过这个亏,以为算法牛逼就能搞定一切,结果被实测数据狠狠教育了一顿。
好,咱们一个一个来看。
4.1 预加重:给高频信号“撑腰”
先问一个问题:为什么语音信号的高频部分总是“软绵绵”的?
原因在于声门脉冲的频谱特性——语音信号的能量主要集中在低频区域,高频部分衰减得厉害。你想想看,如果直接拿这样的信号去做后续处理,高频细节很容易被噪声淹没。
预加重的作用,就是用一个一阶高通滤波器,把高频部分抬起来。公式很简单:
y[n] = x[n] - α · x[n-1]
其中 α 通常取 0.95 到 0.97。我个人习惯用 0.97,这个值在大多数场景下表现都不错。
Python 实现就几行:
def pre_emphasis(signal, alpha=0.97):
"""一阶高通预加重"""
return np.append(signal[0], signal[1:] - alpha * signal[:-1])
嗯,这里要注意:第一个样本点要保持原值,否则会丢失起始信息。
4.2 分帧与加窗:把连续信号切成“小段”
语音信号是非平稳的,但在极短的时间尺度内(10-30ms),它可以近似看作平稳信号。这就是分帧的理论基础。
分帧有两个关键参数:
| 参数 | 典型值 | 说明 |
|---|---|---|
| 帧长 | 20-30 ms | 每帧包含的样本点数 |
| 帧移 | 10-15 ms | 相邻帧起始点之间的间隔 |
帧移通常取帧长的一半,这样相邻帧之间有 50% 的重叠。为什么要重叠?因为加窗后帧边缘的信号会被削弱,重叠可以补偿这部分信息损失。
说到加窗,最常用的是汉明窗(Hamming Window)。它的形状像一口钟,中间高两边低:
def hamming_window(N):
"""生成N点汉明窗"""
n = np.arange(N)
return 0.54 - 0.46 * np.cos(2 * np.pi * n / (N - 1))
完整的帧处理流程:
- 根据帧长和帧移计算总帧数
- 逐帧从信号中截取数据
- 每帧乘以窗函数
- 得到加窗后的帧序列,用于后续处理
4.3 端点检测(VAD):区分“说话”和“安静”
VAD 的全称是 Voice Activity Detection,说白了就是判断当前这一段信号里有没有人说话。
为什么需要 VAD?因为麦克风阵列采集到的信号,大部分时间可能是静音或者背景噪声。如果把这些无语音段也送去做处理,不仅浪费算力,还会引入噪声干扰。
经典的 VAD 方法基于能量和过零率:
- 短时能量: 语音段的能量通常高于噪声段
- 短时过零率: 清音(如 /s/、/f/)的过零率高,浊音(如 /a/、/i/)的过零率低
我常用的一个简单 VAD 实现:
def simple_vad(frame, energy_thresh, zcr_thresh):
energy = np.sum(frame ** 2)
zcr = np.sum(np.abs(np.diff(np.sign(frame)))) / 2
return (energy > energy_thresh) or (zcr > zcr_thresh)
当然,实际项目中很少用这么简单的阈值法。噪声环境变化时,阈值需要自适应调整。我记得有一次在工厂车间做测试,背景噪声突然增大,固定阈值的 VAD 直接“失聪”了——把所有信号都判成了噪声。后来改用基于统计模型的 VAD(比如 G.729B 标准里的方法),才解决了这个问题。
4.4 降噪滤波:维纳滤波与谱减法
降噪是预处理的重头戏。这里介绍两种经典方法:维纳滤波和谱减法。
4.4.1 维纳滤波
维纳滤波的核心思想是:在最小均方误差准则下,估计出纯净语音信号。它假设噪声是平稳的,并且与语音不相关。
频域维纳滤波器的表达式:
H(ω) = P_s(ω) / [P_s(ω) + P_n(ω)]
其中 P_s(ω) 是语音功率谱,P_n(ω) 是噪声功率谱。你看,当信噪比高时(P_s >> P_n),H(ω) ≈ 1,滤波器几乎不衰减;当信噪比低时(P_s << P_n),H(ω) ≈ 0,滤波器把这段频率切掉。
实际实现时,噪声功率谱需要在无语音段估计——这就用到了前面说的 VAD。
def wiener_filter(noisy_spectrum, noise_psd):
"""维纳滤波频域实现"""
speech_psd = np.maximum(np.abs(noisy_spectrum)**2 - noise_psd, 0)
H = speech_psd / (speech_psd + noise_psd + 1e-10)
return noisy_spectrum * H
4.4.2 谱减法
谱减法更直接:从带噪信号的幅度谱中减去噪声的幅度谱。公式:
|Ŝ(ω)| = max(|Y(ω)| - |N(ω)|, 0)
谱减法实现简单,计算量小,但有个臭名昭著的副作用——音乐噪声(Musical Noise)。就是降噪后的信号里会出现一些“叮叮当当”的残留噪声,听起来像背景里有人在弹钢琴。
为什么会有音乐噪声?因为谱减法是逐帧独立处理的,帧与帧之间的噪声估计有随机波动,这些波动在重建信号时就变成了离散的频点噪声。
缓解方法:
- 使用过减因子(over-subtraction factor),多减一点噪声
- 加一个频谱平滑操作
- 引入噪声估计的更新机制
维纳滤波和谱减法对比:
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 维纳滤波 | 理论最优(MMSE准则),噪声残留少 | 需要准确估计噪声功率谱 | 平稳噪声环境 |
| 谱减法 | 实现简单,计算量小 | 容易产生音乐噪声 | 实时性要求高的场景 |
好了,这一章的内容就到这里。预加重、分帧加窗、VAD、降噪滤波,这四个步骤构成了麦克风阵列信号处理的第一道防线。把这些基础打牢,后面做波束成形和声源定位时,你会发现数据干净了很多,算法的效果也会上一个台阶。