ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

FIR滤波器窗函数怎么选:主瓣旁瓣、阻带衰减与凯泽窗实践

FIR滤波器窗函数怎么选:主瓣旁瓣、阻带衰减与凯泽窗实践

做音频采集那几年,我最怕听到的一句话是“滤波器怎么出来的声音发毛”。明明按公式把低通滤波器的系数算出来了,截止频率也卡得死死的,可一上电试听,高频是压下去了,低频却像蒙了一层纱,人声齿音还带着一股金属味。后来把频响图拉出来一看才明白:问题根本不在截止频率,而在窗函数。FIR滤波器的设计,说到底就是拿一个“时间之窗”去截断无限长的理想冲激响应,你选哪扇窗、窗开多宽,直接决定了主瓣有多宽、旁瓣压得多低、通带纹波有多大。这个标题里的“时间之窗”不是修辞,它指的就是窗函数在时域上的那一段有限长度,而“最佳”二字也没有标准答案——测量场景要的是幅度平坦,通信场景要的是旁瓣够低,音频场景要的是过渡带够顺滑。这篇内容我就把这些年踩过的窗函数选择问题拆开讲,从主瓣旁瓣的物理直觉,到凯泽窗的参数估算,再到Python里跑一遍频响验证,适合刚接触数字滤波器设计的朋友,也适合那些已经会调firwin但总调不到点上的同行。

1. 先想清楚:窗函数在FIR设计里到底动了哪块“蛋糕”

1.1 截断这件事,本质是把无限长理想冲激响应“拍扁”

理想低通滤波器的冲激响应是无限长的,数学上是一个sinc函数,左右对称地向两边拖尾,理论上要算到无穷远才能得到完美的矩形频响。工程上不可能真的做无限长,只能取中间一段,比如取N+1个点,两边直接砍掉,这个动作就叫截断。问题在于,时域上“咔嚓”一刀,频域里就会产生卷积效应:你截断用的那个窗函数,它的频谱会跟理想矩形频响做卷积,结果就是原来笔直的过渡带被抹圆了,原来为零的阻带冒出了一串旁瓣。这个现象不用背,拿一张纸画个矩形,再在脑子里把它跟一个窄脉冲卷积一下,边缘变模糊、旁边起波纹,就是同一个道理。

窗函数的选择,其实就是在“截断得狠不狠”和“截断得顺不顺”之间做权衡。矩形窗相当于一刀切,边缘没有任何渐变,时域上最“硬”,频域上主瓣最窄,但旁瓣最高,第一旁瓣只比主瓣低13dB左右。汉宁窗在两端做了余弦渐变,像把刀口磨圆了,旁瓣一下子掉到-31dB,代价是主瓣宽度翻倍。你要是把窗做成布莱克曼那种更平滑的曲线,旁瓣能压到-57dB以下,但主瓣宽度要占到矩形窗的三倍。所以每次选窗之前,我都会先问自己一句:这个系统最怕的是过渡带太宽,还是最怕阻带里漏过去的那点能量?

这里还有一个容易被忽略的点:窗函数的长度N和窗的类型是两个独立的旋钮。很多人一上来就纠结“汉宁还是海明”,却忘了先把N估对。N太小,再好的窗也救不回来,主瓣宽得能把整个过渡带吞掉;N给大了,计算量和群延迟都上去了,实时系统里可能直接跑不动。我一般的顺序是先用指标估一个最小阶数,再在这个阶数附近试两三种窗,看哪一种在同等N下最接近指标,而不是反过来先定窗再凑阶数。

1.2 主瓣与旁瓣:一对永远在打架的兄弟

主瓣宽度和旁瓣衰减是窗函数最核心的一对矛盾,理解它俩的关系,选窗就不会盲目。主瓣宽度决定了过渡带的陡峭程度:主瓣越窄,频响从通带掉到阻带就越快,滤波器越“锋利”。旁瓣电平决定了阻带里的泄漏程度:旁瓣越高,本来该被压死的频率成分就越容易从旁边漏过来,尤其在强干扰附近有弱信号时,高旁瓣会把弱信号直接淹掉。

拿矩形窗和汉宁窗做一个具体对比。同样取65点长度、截止频率0.3(归一化到奈奎斯特频率),矩形窗的主瓣大约只占4个频点宽,过渡带很陡,但第一旁瓣在-13dB,阻带衰减只有-21dB左右,也就说阻带里最大还有差不多十分之一的幅度漏过去。汉宁窗的主瓣宽度翻到8个频点,过渡带明显变缓,可旁瓣掉到-31dB,阻带衰减约-44dB,泄漏小了不止一个数量级。这个交换在频响图上看得非常清楚:矩形窗的通带边缘像刀切,但阻带里全是毛刺;汉宁窗的通带边缘圆润,阻带却干净很多。

我自己的经验是,先把“阻带衰减”当成硬指标来筛窗,再用“过渡带宽度”去反推阶数。因为阻带衰减基本由窗型决定,阶数再怎么加也突破不了窗型的天花板,而过渡带宽度是可以靠加阶数来收窄的。顺序搞反了,就会出现“阶数加到几百,阻带还是压不下去”的尴尬局面,这时候换窗比加阶数管用得多。

1.3 一张取舍地图:四种常用窗的定位

下面这张表是我平时贴在工位旁边的速查表,参数是归一化后的典型值,实际设计时还要结合具体长度和截止频率微调,但用来做第一轮筛选足够了。

窗类型主瓣宽度(近似)第一旁瓣阻带最小衰减过渡带宽度(近似)典型用途
矩形窗4π/N-13dB-21dB0.9/N短时分析、对陡度要求极高且能容忍泄漏
汉宁窗8π/N-31dB-44dB3.1/N音频、通用低通、频谱分析加窗
海明窗8π/N-41dB-53dB3.3/N通信基带、需要较低旁瓣的通用场景
布莱克曼窗12π/N-57dB-74dB5.5/N高动态范围测量、强干扰抑制

表里的“过渡带宽度”单位是归一化频率(cycles/sample),N是滤波器阶数。比如海明窗要做出0.02的过渡带,粗算N ≈ 3.3/0.02 = 165阶,这个数跟后面凯泽公式估出来的通常在一个量级,可以先拿来心里有个底。矩形窗那一行的“0.9/N”看起来很美,但别忘了它的阻带只有-21dB,真要拿它做抗混叠滤波,混叠分量能从旁瓣大摇大摆走进来。

这张表还有一个用法:当你发现指标里阻带要求是-60dB时,表里前三种窗直接出局,只能往布莱克曼或者凯泽窗方向走。凯泽窗的妙处在于它不在这张固定表里,它有一个可调的β参数,能把旁瓣从-30dB一路压到-80dB以下,代价是主瓣随之变宽。所以实际项目中我大多数时候用的都是凯泽窗,因为它把“选窗”变成了“调参”,连续可调比离散选择好把控。

2. 窗函数家族逐个拆解:别只看公式,看它适合干什么活

2.1 矩形窗:最“陡”也最“吵”的老实人

矩形窗的定义最简单,窗内全为1,窗外全为0,等于没加窗。它的频域是一个sinc函数,主瓣窄、旁瓣高,而且旁瓣衰减速度很慢,第一个旁瓣-13dB,第二个大概-17dB,往后缓慢下降。这种特性放在频谱分析里其实是优点:矩形窗的主瓣最窄,频率分辨率最高,两个靠得很近的谱线不容易糊在一起。所以做FFT频谱分析时,如果信号是瞬态的、持续时间正好落在窗内,矩形窗反而是首选,因为它不会把能量摊到旁边去。

但拿它做FIR滤波器设计就是另一回事了。FIR设计关心的是阻带泄漏,矩形窗-21dB的阻带衰减意味着截止频率之外不远处还有百分之几的能量能透过来。我早年做过一个简单的工频干扰抑制,想着“不就是个50Hz陷波附近的高通嘛”,随手用了矩形窗,结果截止频率设在80Hz,到了120Hz处干扰仍然清晰可见,后来换成海明窗才压下去。那次之后我记住一条:矩形窗只适合做“分析”,不适合做“过滤”。

还有一个小坑,矩形窗在时域两端的突变会产生吉布斯现象,频响在通带和阻带边缘都会出现振铃,通带内最大纹波能到9%左右。有些对通带平坦度敏感的场景,比如传感器信号调理,这个纹波会直接变成测量误差,所以看到“通带纹波”这个指标时,矩形窗基本可以直接划掉。

2.2 汉宁、海明、布莱克曼:三角家族的三档力度

这三个窗可以看成同一家族的三档力度,都是用余弦函数在两端做渐变,只是渐变的“深度”不同。汉宁窗是0.5 - 0.5cos(2πn/N),两端降到零,过渡最柔和;海明窗是0.54 - 0.46cos(2πn/N),两端不完全到零,而是停在0.08左右,这个细微差别让海明窗的第一旁瓣比汉宁窗低10dB左右,代价是旁瓣衰减速度稍慢;布莱克曼窗再加一项二次余弦,0.42 - 0.5cos + 0.08cos(2πn/N),旁瓣压到-57dB,但主瓣宽度扩大到矩形窗的三倍。

选这三兄弟的时候,我一般看两个数:阻带指标和过渡带预算。如果阻带只要求-40dB出头,汉宁窗最划算,过渡带比海明窗略窄一点点,计算也简单;如果要求-50dB到-60dB,海明窗是甜点区,它的旁瓣衰减曲线在远离主瓣的地方下降得比汉宁窗更平缓,实际阻带里的最大泄漏往往比标称值还低一些;如果要求-70dB以上,那就只能上布莱克曼窗,或者转投凯泽窗。布莱克曼窗的问题是过渡带太宽,同样阶数下它的过渡带是海明窗的1.7倍左右,做窄过渡带时阶数会飙得比较厉害。

有一个实际项目里的细节值得说:海明窗的两端不为零,这会让滤波器系数在首尾有一个小跳变,理论上频响里会带一点点高频残余。多数场景无所谓,但在高精度直流测量里,我会在系数首尾各补一个零,或者干脆改用汉宁窗,让端点严格归零,避免这个跳变耦合进后级。这个操作没有公式要求,纯粹是调试时对比出来的经验。

2.3 凯泽窗:带一个“可调旋钮”的万能选手

凯泽窗是我用得最多的一种,因为它把窗的形状参数化成了一个β值,β越大,窗越“胖”,旁瓣压得越低,主瓣越宽。β=0时退化成矩形窗,β≈5时接近海明窗的性能,β≈8.6时阻带能到-80dB以上。这个连续可调的特性让它特别适合“指标卡得比较死”的项目,你不用在几个固定窗里挑,而是可以直接算出需要的β。

凯泽给出的经验公式很实用,我把它抄在这里,省得每次翻手册:

  • 当阻带衰减A > 50dB时,β = 0.1102 × (A - 8.7)
  • 当21 < A ≤ 50dB时,β = 0.5842 × (A - 21)^0.4 + 0.07886 × (A - 21)
  • 当A ≤ 21dB时,β = 0

阶数估算用这个式子:

N ≈ (A - 7.95) / (2.285 × Δω)

其中Δω = 2π × Δf / fs,Δf是过渡带宽度(单位Hz),fs是采样率。这个公式估出来的是滤波器阶数,实际滤波器长度是N+1。我一般会在这个结果上浮10%到20%,因为公式是近似,而且实际设计时截止频率的位置也会影响过渡带的对称性。

举个实数例子:采样率48kHz,通带截止8kHz,阻带起始10kHz,要求阻带衰减60dB。过渡带Δf = 2kHz,Δω = 2π×2000/48000 ≈ 0.2618。代入公式,N ≈ (60 - 7.95) / (2.285 × 0.2618) ≈ 52.05 / 0.598 ≈ 87阶,取整到88阶,长度89点。β = 0.1102 × (60 - 8.7) ≈ 5.65。这个结果跟用海明窗估算的3.3/0.0417 ≈ 79阶在一个量级,但凯泽窗的阻带衰减是精确可控的,海明窗只能保证-53dB左右,要60dB就得再加阶数或者换窗。所以指标明确的时候,我直接走凯泽。

2.4 切比雪夫与平顶窗:两个极端场景的特种兵

切比雪夫窗(Dolph-Chebyshev)的特点是通带和阻带的纹波等幅分布,它能在给定主瓣宽度下把旁瓣压到最低,数学上是最优的。但它的旁瓣是等高的,不像凯泽窗那样越远越低,所以如果干扰恰好落在靠近主瓣的旁瓣上,切比雪夫窗反而没有优势。我用它比较多的是天线阵列的权值设计,FIR里用得少,因为它的时域窗形不太直观,调试时不好“手感”调整。

平顶窗(Flat-top)是另一个极端,它的通带幅度平坦度极高,几乎是一条直线,代价是主瓣非常宽、旁瓣也不低。这个窗不是用来做滤波的,而是用来做幅度测量的。比如校准一个信号源的输出电平,你需要准确读出某个频点的幅度,这时候频率分辨率不重要,幅度精度才重要,平顶窗能把泄漏引起的幅度误差压到0.01dB量级。我做过一个音频设备的频响测试,用汉宁窗测出来的低频段有0.3dB左右的起伏,换成平顶窗后曲线立刻平了,后来才知道那点起伏就是窗函数主瓣形状带来的系统误差。

高斯窗在时频分析里更常见,它的时宽带宽积最小,适合做短时傅里叶变换的窗。纳托尔窗(Nuttall)则是布莱克曼窗的加强版,旁瓣压得更低,但主瓣也更宽,属于“要么不用,要用就是指标极其苛刻”的场合。这些窗平时不常碰,但知道它们的存在有好处:当你发现凯泽窗的旁瓣衰减曲线在某个频点不够理想时,可能换个平顶窗或者纳托尔窗就绕过去了。

2.5 一张关键指标对照表,免得记混

窗类型主瓣宽度旁瓣峰值阻带衰减通带纹波可调参数
矩形4π/N-13dB-21dB约0.74dB无
汉宁8π/N-31dB-44dB约0.05dB无
海明8π/N-41dB-53dB约0.02dB无
布莱克曼12π/N-57dB-74dB约0.001dB无
凯泽随β变化随β变化可调可调β
平顶约16π/N-90dB以下不适用极平坦无

表里凯泽窗那行没写死数值,因为它的性能完全由β决定,这也正是它的价值。平顶窗的“阻带衰减”标成不适用,是因为它压根不是为阻带设计的,它的旁瓣衰减曲线在远端会翘起来,用错场景会出问题。这张表我建议跟前面的取舍地图配合看,先看指标落在哪个区间,再去对应行里挑。

3. 从指标到代码:一次完整的设计与验证过程

3.1 先把指标翻译成滤波器语言

拿到一个需求,别急着写代码,先把它翻译成四个数:通带截止频率、阻带起始频率、通带最大纹波、阻带最小衰减。这四个数缺一个,设计就没法收敛。我见过最多的返工,就是需求方只说“把高频滤掉”,没说“滤到多少”,结果做出来一听,觉得“还是有点毛”,但不知道往哪个方向调。

假设现在有一个语音采集模块,采样率16kHz,想要保留0到3.4kHz的语音频带,把4kHz以上的成分压到-55dB以下。翻译过来就是:通带截止3.4kHz,阻带起始4kHz,过渡带600Hz,阻带衰减55dB。通带纹波没有硬要求,但语音场景一般控制在0.1dB以内比较稳妥。

先估阶数。Δf = 600Hz,fs = 16000,Δω = 2π×600/16000 ≈ 0.2356。用凯泽公式,N ≈ (55 - 7.95) / (2.285 × 0.2356) ≈ 47.05 / 0.5383 ≈ 87.4,取88阶比较稳。β = 0.5842 × (55-21)^0.4 + 0.07886 × (55-21),先算34^0.4 ≈ 4.09,0.5842 × 4.09 ≈ 2.39,0.07886 × 34 ≈ 2.68,所以β ≈ 5.07。这个β值对应的旁瓣大概在-56dB附近,跟指标吻合。

同样指标如果换成海明窗,过渡带估算N ≈ 3.3 / (600/16000) = 3.3 / 0.0375 = 88阶,看起来阶数差不多,但海明窗的阻带衰减只有-53dB,达不到55dB的要求,要么加阶数到100以上,要么换窗。这就是为什么我说“指标明确时直接上凯泽”,它能让你少试错两轮。

3.2 用Python跑一遍:设计、画图、读数

下面这段代码是我平时验证窗函数方案的模板,改改参数就能用。它做了三件事:用firwin设计滤波器、用freqz算频响、把不同窗的结果画在一起对比。

import numpy as np import matplotlib.pyplot as plt from scipy import signal fs = 16000 f_pass = 3400 f_stop = 4000 numtaps = 89 # 阶数88,长度89 windows = { 'hamming': 'Hamming', 'hann': 'Hann', 'blackman': 'Blackman', ('kaiser', 5.07): 'Kaiser (beta=5.07)' } plt.figure(figsize=(10, 6)) for win, label in windows.items(): taps = signal.firwin(numtaps, f_pass, window=win, fs=fs, pass_zero='lowpass') w, h = signal.freqz(taps, worN=4096, fs=fs) h_db = 20 * np.log10(np.maximum(np.abs(h), 1e-10)) plt.plot(w, h_db, label=label) plt.axvline(f_pass, color='gray', linestyle='--', linewidth=0.8) plt.axvline(f_stop, color='gray', linestyle='--', linewidth=0.8) plt.axhline(-55, color='red', linestyle=':', linewidth=0.8) plt.xlim(0, 8000) plt.ylim(-100, 5) plt.xlabel('Frequency (Hz)') plt.ylabel('Magnitude (dB)') plt.legend() plt.grid(True, alpha=0.3) plt.tight_layout() plt.show()

跑完这张图,几个窗的差别一目了然。海明窗在4kHz处大概-48dB,还没降到-55dB,要到4.3kHz才达标;汉宁窗更慢,4kHz处只有-42dB左右;布莱克曼窗在4kHz处已经到-60dB以下,但它在3.4kHz通带边缘的衰减也更早,通带有效带宽被吃掉一部分;凯泽窗β=5.07落在中间,4kHz处约-56dB,正好卡线。这个结果跟公式估算对得上,说明估阶那一步没有跑偏。

这里有一个读图技巧:不要只看阻带某个点的衰减,要看整条阻带曲线的最低点。有些窗在第一旁瓣之后会回升,比如矩形窗的第二个旁瓣就比第一个高一点点(实际是第一个旁瓣最高,后面递减,但汉宁窗在远端有轻微回升)。用np.min(h_db[w > f_stop])直接取阻带最小值,比肉眼扫图靠谱。

3.3 参数微调的手感记录

firwin的numtaps和cutoff两个参数会互相影响。cutoff设的是-6dB点,不是通带边缘,所以实际通带边缘会比cutoff低一些,具体低多少取决于窗型和阶数。我的做法是先把cutoff设在通带和阻带的中间,比如3.7kHz,跑一遍看通带边缘3.4kHz处衰减了多少,如果超过0.5dB就把cutoff往上抬一点,反复两三次就能把通带纹波压进0.1dB。

还有一个细节:firwin默认的scale=True会把通带增益归一化到1,这个对大多数场景是好事,但在多级级联滤波器里,每一级都归一化会让总增益偏离设计值。我一般在单级设计时保持默认,级联时手动设scale=False,最后统一乘一个增益系数。

窗长numtaps取奇数还是偶数也有讲究。奇数长度的线性相位FIR是Type I,适合任意低通、高通、带通;偶数长度是Type II,在高频处(奈奎斯特频率)频响强制为零,做高通和带阻时会出问题。所以除非明确知道自己在做什么,我默认取奇数长度,numtaps = N + 1,N取偶数,这样numtaps就是奇数。上面例子里N=88,numtaps=89,就是按这个规则来的。

3.4 把系数导出去之前,再检查三件事

代码跑通不等于能落地。导出系数之前,我固定检查三样东西。第一,群延迟。线性相位FIR的群延迟是(numtaps-1)/2个采样点,例子里是44个点,在16kHz采样率下就是2.75ms。语音场景无所谓,但如果是闭环控制系统,这个延迟可能直接影响稳定性,得提前算进去。第二,系数的动态范围。凯泽窗的系数首尾很小,中间很大,如果后级是定点DSP,要确认系数量化到16位之后旁瓣有没有明显抬升。我遇到过量化后阻带从-56dB退化到-48dB的情况,原因是系数动态范围超过了定点格式的表示能力,后来把β降了一点、阶数加了几阶才平衡回来。第三,通带内的纹波分布。有些窗的通带纹波在靠近截止频率处最大,有些则在直流附近最大,用freqz把0到f_pass这一段放大看,确认最大纹波出现的位置是不是系统敏感区。

4. 踩坑实录:窗函数选择里最容易翻车的几个地方

4.1 过渡带定得太窄,阶数直接失控

新手最容易犯的错是把过渡带设得极窄,比如采样率48kHz、通带20kHz、阻带20.5kHz,过渡带只有500Hz。按凯泽公式估,Δω = 2π×500/48000 ≈ 0.0654,N ≈ (60-7.95)/(2.285×0.0654) ≈ 52.05/0.1494 ≈ 348阶。348阶的FIR,在通用MCU上单次卷积就要几百个乘加,实时性直接崩掉。更麻烦的是,阶数越高,系数量化的字长要求也越高,定点实现时很容易出现极限环。

我的处理办法是回头跟需求方确认“过渡带能不能放宽”。多数时候“20kHz通带、20.5kHz阻带”是一个拍脑袋的数,实际系统在20kHz到22kHz之间并没有强干扰,过渡带放到2kHz完全够用,阶数立刻从348降到87。如果确实需要窄过渡带,那就得考虑多级级联或者换IIR,而不是硬扛一个超长FIR。窗函数解决不了“指标本身不合理”的问题。

4.2 只看阻带衰减,忘了通带纹波

阻带衰减是窗函数最显眼的指标,很多人选窗时只盯这一个数,结果通带纹波超标。布莱克曼窗的阻带能到-74dB,很诱人,但它的通带纹波虽然标称很小,过渡带却宽得厉害,在截止频率附近有一段缓慢的滚降,如果你把截止频率设得离通带边缘太近,这段滚降就会吃掉通带的有效带宽。我见过一个设计,通带要求到1kHz平坦,截止频率设1.05kHz,用布莱克曼窗,结果900Hz处已经衰减了0.4dB,原因是过渡带太宽,滚降提前开始了。

解决办法是把截止频率往阻带方向推,给过渡带留足空间。具体推多少,看窗型和阶数:布莱克曼窗的过渡带大约是海明窗的1.7倍,截止频率要相应外移。我一般先用freqz把通带边缘的衰减读出来,如果超过指标,就把cutoff往阻带方向挪Δf/4左右,再跑一遍,通常两轮就能收敛。

4.3 归一化频率的单位陷阱

scipy.signal.firwin的cutoff参数,如果不带fs,默认是归一化到奈奎斯特频率,取值范围0到1;如果带了fs,单位就是Hz。这两个用法混用是高频错误。我有一次从旧代码里复制了一段,旧代码用的是归一化频率cutoff=0.4,新代码加了fs=48000但忘了改cutoff,结果滤波器截止频率变成了0.4Hz,输出几乎全是直流。更隐蔽的是,归一化频率下cutoff=0.4对应的是0.4×fs/2 = 9.6kHz(fs=48k),而有人会误以为它对应0.4×fs = 19.2kHz,差了一倍。

我的习惯是永远带fs参数,让cutoff直接用Hz,代码可读性高,也不容易错。如果必须用归一化频率,就在变量名里写清楚,比如cutoff_norm = 0.4,并且在注释里标明它对应多少Hz。

4.4 定点实现时,窗函数的选择会影响系数精度

浮点仿真跑出来的频响很漂亮,一到定点DSP上就变样,问题往往出在系数量化。不同窗函数的系数分布不一样:矩形窗的系数在截止频率附近有较大的正负交替,动态范围大;汉宁窗的系数两端趋近于零,中间集中,动态范围相对小;凯泽窗的系数分布跟β有关,β越大,两端越小,动态范围越大。动态范围越大,定点量化时小系数越容易被量化成零,等效于把窗的两端截掉了,旁瓣就会抬升。

我的做法是在定方案之前先做一次量化仿真:把浮点系数乘以2^15取整,再除回去,重新算频响,看阻带衰减掉了多少。如果掉超过3dB,就说明这个窗型在当前字长下不合适,要么换窗,要么加字长,要么把β调小一点让系数分布更均匀。这一步花不了十分钟,但能避免后期在硬件上反复调试。

4.5 常见问题速查表

现象可能原因排查动作处理办法
阻带衰减达不到指标窗型旁瓣太高用freqz读阻带最小值换布莱克曼或凯泽,或加大β
通带边缘衰减过早过渡带太窄或截止频率设得太靠里读通带边缘的衰减值外移截止频率,或加阶数
阶数高到跑不动过渡带指标过严用凯泽公式反算阶数放宽过渡带,或改多级级联
定点后频响变差系数量化误差做一次量化仿真换窗型、加字长、降β
高通滤波器高频处异常用了偶数长度(Type II)检查numtaps奇偶改成奇数长度
输出有直流偏置窗两端不为零或系数未归一化检查scale参数和窗端点用汉宁窗或手动去均值

这张表里“高通滤波器高频处异常”这一条值得多说一句。Type II的偶数长度FIR在高频处频响强制为零,做高通时奈奎斯特频率附近会塌下去,听起来像是“高频突然没了”。这个现象在低通设计里不明显,做高通和带阻时才会暴露。我第一次遇到时以为是窗函数选错了,换了三种窗都没用,最后查numtaps才发现是偶数长度。所以现在不管设计什么滤波器,我先把长度定成奇数,省得后面排查。

5. 进阶玩法:窗函数还能怎么用得更“巧”

5.1 多带设计里的折中思路

实际系统里经常需要同时处理多个频带,比如语音增强里既要保留300Hz到3.4kHz的语音,又要压掉50Hz工频和8kHz以上的开关噪声。这时候可以用一个多带FIR,也可以用多个单带滤波器级联。多带FIR的好处是只算一次卷积,延迟小;坏处是窗函数要在多个过渡带之间做折中,主瓣宽度由最窄的那个过渡带决定,整体阶数下不来。

我的经验是,如果各频带的过渡带宽度相差不超过两倍,多带设计划算;如果相差很大,比如工频陷波只需要几十Hz的过渡带,而高频低通需要2kHz的过渡带,那就拆成两级,陷波用IIR或者窄带FIR单独做,低通用窗函数法做宽的,这样总计算量反而小。窗函数法在多带设计里还有一个限制:它只能保证整体阻带衰减,不能给每个阻带单独设指标,所以如果某个频带要求特别严,还是拆开做更可控。

5.2 窗函数与频率采样法的组合

频率采样法是另一种FIR设计思路,先在频域等间隔采样理想频响,再做IDFT得到时域系数。它的问题是频域采样点之间的响应不受控,过渡带会出现振铃。解决办法是在频域采样值上加一个过渡点,或者干脆在时域对IDFT结果再加一次窗。后者我试过,用汉宁窗对频率采样法的结果做二次加窗,能把振铃压下去不少,但主瓣也会变宽,相当于用过渡带换平滑度。

这种组合用法在需要精确控制某些频点响应时比较有用,比如设计一个梳状滤波器,要求某些频点严格为零。纯窗函数法很难保证某一点精确为零,频率采样法可以,但它需要二次加窗来补救。我一般只在特殊需求下用这个组合,常规低通高通还是firwin加窗更直接。

5.3 平顶窗在测量场景的特殊价值

前面提过平顶窗,这里展开说一个实际用法。做音频设备的频响曲线测量时,如果用汉宁窗,测出来的曲线在低频段会有明显的起伏,不是设备的问题,是窗函数主瓣形状带来的幅度误差。平顶窗的主瓣虽然宽,但顶部非常平,对单频信号的幅度估计几乎无偏,所以扫频测量时用平顶窗,读出来的电平值更可信。

代价是频率分辨率下降,两个靠得很近的峰会被平顶窗糊成一个。所以我的做法是:测幅度用平顶窗,测频率间隔用汉宁窗,两种窗各跑一遍,互相印证。这个思路在振动分析、电源谐波测量里也适用,凡是“读数值”比“分辨频率”更重要的场景,平顶窗都值得一试。

5.4 我个人的调窗顺序

最后分享我自己的调窗顺序,不一定最优,但能少走弯路。第一步,用凯泽公式根据阻带指标和过渡带估一个阶数和β,直接跑firwin,看频响是否达标。第二步,如果通带边缘衰减太多,把截止频率往阻带方向挪,而不是急着加阶数。第三步,如果阻带某个频点翘起来,先检查是不是阶数不够导致主瓣太宽、旁瓣混进来了,再考虑换窗。第四步,定点实现前做量化仿真,确认字长够用。第五步,实测。仿真再漂亮,也要用实际信号跑一遍,听听有没有异响、看看时域有没有过冲。我见过仿真频响完美、实测却因为群延迟导致闭环振荡的例子,所以最后一步不能省。

窗函数的选择没有“最佳”,只有“在当前指标和资源下最合适”。矩形窗最锋利但漏得厉害,汉宁海明是通用甜点区,布莱克曼干净但迟钝,凯泽窗把选择变成了调参,平顶窗专攻幅度测量。把这些窗的脾气摸清楚,比背公式有用得多。我到现在设计新滤波器时,还是会先把两三种窗的频响画在一张图上对比着看,那个直观的差别,比任何表格都让人记得住。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进