4. 数字信号处理基础(下):滤波器设计基础(FIR/IIR)。窗函数的作用与选择。

好,咱们接着聊。上一节我们把傅里叶变换和Z变换这些“数学工具”捋了一遍。这一节,咱们要动真格的了——设计滤波器

说实话,滤波器是语音降噪的“心脏”。你想想看,噪声和语音在频域上混在一起,怎么把它们分开?靠的就是滤波器。我个人习惯把滤波器分成两大类:FIR(有限冲激响应)IIR(无限冲激响应)。这两兄弟性格完全不同,用好了是神器,用不好就是灾难。

4.1 滤波器设计:FIR 与 IIR 的“性格”差异

先说说它们最本质的区别。

  • FIR 滤波器:它的输出只取决于当前和过去的输入。没有反馈回路。所以它天生就是稳定的。你给它一个脉冲,它“响”一阵子就停了,不会没完没了。
  • IIR 滤波器:它的输出不仅取决于输入,还取决于过去的输出。有反馈回路。你给它一个脉冲,它可能会“响”很久很久,理论上无限长。

嗯,这里要注意:稳定性是IIR的一个大坑。我在项目中遇到过,用IIR做高通滤波器,参数没调好,结果低频自激了,输出直接爆表。那声音,刺耳得很。

核心对比:

特性 FIR IIR
稳定性 绝对稳定 可能不稳定(需谨慎)
相位特性 可实现严格线性相位 非线性相位(有群延迟失真)
计算量 较大(阶数高) 较小(阶数低)
设计难度 相对简单(窗函数法) 需要模拟滤波器原型知识
应用场景 语音、通信、需要保相位的场景 音频均衡、低功耗嵌入式设备

说白了,如果你对相位不敏感,比如只是做简单的频段均衡,IIR 性价比很高。但如果你做的是语音增强,要保留语音的波形形状,那 FIR 的线性相位特性就太重要了。

4.2 窗函数:为什么需要它?

好,假设我们想设计一个 FIR 低通滤波器。理想情况下,它的频率响应是一个矩形。但理想是丰满的,现实是骨感的。这个理想滤波器在时域上对应的是 sinc 函数,它是无限长的。

我们只能截取一段。怎么截?直接“咔嚓”一刀剪断?

这就是问题所在。直接截断,相当于在时域上加了一个矩形窗。矩形窗在频域上会带来严重的吉布斯现象——通带和阻带边缘会出现剧烈的振荡,也就是“振铃效应”。

我刚开始学的时候,觉得这没什么大不了的。直到有一次,我用矩形窗设计了一个带通滤波器,结果在通带边缘出现了好几个 dB 的起伏,把语音的共振峰都给扭曲了。嗯,从那以后,我再也不敢随便用矩形窗了。

所以,窗函数的作用就是:平滑截断,抑制旁瓣,减少频谱泄露。

4.3 常见窗函数的选择与对比

不同的窗函数,就像不同性格的人。有的“主瓣窄”(频率分辨率高),但“旁瓣高”(泄露严重)。有的“旁瓣低”(泄露少),但“主瓣宽”(分辨率下降)。

这是一个典型的“鱼与熊掌”问题。你需要根据实际需求来权衡。

窗函数 主瓣宽度 旁瓣衰减 特点与用途
矩形窗 最窄 -13 dB 频率分辨率最高,但泄露最严重。慎用。
汉宁窗 较宽 -31 dB 通用性好,旁瓣衰减不错。我常用它做语音分帧。
海明窗 较宽 -41 dB 旁瓣更低,适合需要抑制噪声的场景。
布莱克曼窗 最宽 -57 dB 旁瓣衰减极大,但主瓣很宽。适合对泄露极度敏感的场景。
凯泽窗 可调 可调 通过调节 β 参数,灵活控制主瓣和旁瓣。我最喜欢用这个。

我的个人经验:

在语音降噪中,如果你做的是短时傅里叶变换(STFT),我建议用汉宁窗或海明窗。它们能很好地平衡时间分辨率和频率分辨率。如果你做的是滤波器设计,凯泽窗是首选,因为它可以让你精确控制阻带衰减。

4.4 实战:用窗函数法设计一个 FIR 低通滤波器

光说不练假把式。咱们直接上代码。假设我们要设计一个采样率 16kHz,截止频率 4kHz 的低通滤波器,阶数 64。

import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import firwin, freqz

# 参数设置
fs = 16000       # 采样率
fc = 4000        # 截止频率
numtaps = 65     # 滤波器阶数(通常为奇数,保证对称)

# 使用汉宁窗设计
b_hanning = firwin(numtaps, fc, window='hann', fs=fs)

# 使用凯泽窗设计(beta=8,阻带衰减约60dB)
b_kaiser = firwin(numtaps, fc, window=('kaiser', 8), fs=fs)

# 计算频率响应
w, h_hanning = freqz(b_hanning, worN=8000, fs=fs)
w, h_kaiser = freqz(b_kaiser, worN=8000, fs=fs)

# 绘图
plt.figure(figsize=(10, 6))
plt.plot(w, 20 * np.log10(np.abs(h_hanning)), label='汉宁窗')
plt.plot(w, 20 * np.log10(np.abs(h_kaiser)), label='凯泽窗 (β=8)')
plt.axvline(fc, color='red', linestyle='--', label='截止频率')
plt.xlabel('频率 (Hz)')
plt.ylabel('幅度 (dB)')
plt.title('FIR 低通滤波器频率响应对比')
plt.legend()
plt.grid()
plt.show()

你运行一下就会发现,汉宁窗的过渡带比较宽,但阻带衰减大概在 -40dB 左右。凯泽窗的过渡带更陡峭,阻带衰减可以轻松做到 -60dB 以下。

避坑指南:

我曾经犯过一个错误:为了追求极致的阻带衰减,把凯泽窗的 β 值调得很大。结果滤波器阶数不够,导致通带边缘出现了严重的“过冲”。记住,窗函数法设计的滤波器,阶数越高,效果越好,但计算量也越大。 你需要找到一个平衡点。

4.5 本章知识体系:一张图看懂

为了帮你理清思路,我画了一张图。它把这一节的核心逻辑串起来了。

滤波器设计基础:核心知识体系 FIR 滤波器 线性相位 · 绝对稳定 · 阶数高 IIR 滤波器 非线性相位 · 可能不稳定 · 阶数低 窗函数法设计 模拟滤波器原型转换 主瓣宽度 vs 旁瓣衰减 稳定性 · 相位失真 · 计算量 选择合适窗函数(汉宁/海明/凯泽) · 调整阶数 · 权衡性能

你看,从 FIR 和 IIR 的选择,到具体的窗函数设计,再到最后的权衡取舍,这是一条清晰的路径。你想想看,掌握了这个体系,以后遇到任何滤波需求,你都能快速找到切入点。

好了,这一节的内容就到这儿。窗函数的选择,说白了就是一场“主瓣”和“旁瓣”的博弈。多动手试试,你会有自己的手感。


公众号:蓝海资料掘金营,微信deep3321