4、窗函数的选择(上):矩形窗、汉宁窗、海明窗,到底该用哪个?

聊到FFT,就绕不开窗函数。很多工程师一看到“加窗”两个字就头大,觉得这是搞理论研究的人才需要操心的事。其实不然。

我刚开始用示波器做FFT时,也踩过不少坑。有一次测一个开关电源的噪声,频谱上莫名其妙多出一堆“假峰”,折腾了半天才发现是窗函数没选对。从那以后,我对窗函数就格外上心。

今天咱们先聊三个最常用的窗:矩形窗、汉宁窗、海明窗。它们到底有什么区别?实际项目中该怎么选?

4.1 为什么需要加窗?

先问一个问题:FFT的本质是什么?

说白了,FFT假设你采集的信号是无限长且周期性重复的。但现实世界哪有那么完美?你只能截取一段有限长度的信号。这一截,就出问题了。

你想想看,如果截取的起始点和结束点信号幅值不连续,FFT就会认为这里有一个“跳变”。这个跳变会在频谱上产生额外的频率分量——这就是我们常说的频谱泄漏

加窗的目的,就是让截取的那段信号在两端平滑地衰减到零,减少这种不连续性。嗯,就是这么回事。

4.2 矩形窗:最“诚实”但也最“危险”

矩形窗其实就等于没加窗。它直接把信号截断,两端不做任何处理。

矩形窗的特点:

  • 主瓣最窄——频率分辨率最高
  • 旁瓣最高——频谱泄漏最严重
  • 适合:信号能量集中在几个离散频率点,且信噪比很高

我在项目中遇到过一位同事,用示波器测一个10MHz的晶振信号。他直接用矩形窗做FFT,结果在9.8MHz和10.2MHz附近看到了两个小峰。他以为是晶振有问题,换了三个晶振还是一样。后来我让他换成汉宁窗,那两个小峰立马消失了——原来是矩形窗的旁瓣泄漏在捣鬼。

注意:除非你非常确定信号是周期性的,且采样时长刚好是信号周期的整数倍,否则别轻易用矩形窗。大多数示波器默认的FFT窗函数都不是矩形窗,原因就在这里。

4.3 汉宁窗:最通用的“万金油”

汉宁窗(Hanning,也叫Hann窗)是我个人最常用的窗函数。为什么?因为它平衡了频率分辨率和泄漏抑制。

汉宁窗的形状像半个余弦波,两端平滑地衰减到零。它的旁瓣比矩形窗低很多,但主瓣稍微宽了一点。

汉宁窗的特点:

  • 主瓣宽度:矩形窗的2倍(频率分辨率降低一倍)
  • 旁瓣衰减:-32dB左右(比矩形窗好太多)
  • 适合:大多数通用信号分析,尤其是噪声信号、随机信号

我建议刚接触FFT的工程师,默认就用汉宁窗。等你用熟了,再根据具体情况去调整。这就像开车,先学会自动挡,再学手动挡。

4.4 海明窗:汉宁窗的“改良版”

海明窗(Hamming)和汉宁窗长得很像,但有一个关键区别:海明窗在两端没有完全衰减到零,而是保留了一个小幅度。

这样做的好处是:旁瓣更低(约-43dB),但代价是旁瓣衰减速度变慢

窗函数 主瓣宽度 最高旁瓣 旁瓣衰减速率 适用场景
矩形窗 最窄 -13dB -6dB/倍频程 周期信号、高信噪比
汉宁窗 较宽(2倍) -32dB -18dB/倍频程 通用、噪声信号
海明窗 较宽(2倍) -43dB -6dB/倍频程 窄带信号、需要低旁瓣

你看这个表格,海明窗的最高旁瓣确实比汉宁窗低,但衰减速度慢。这意味着什么呢?

如果你关心的频率分量离得很近,海明窗的慢衰减可能会导致它们互相干扰。而汉宁窗虽然最高旁瓣高一点,但衰减快,远距离的频率分量影响小。

4.5 到底该用哪个?我的选择原则

这个问题没有标准答案,但我可以分享一些经验:

  1. 测正弦波、方波等周期信号:如果信号干净、信噪比高,可以试试矩形窗。但一定要确认采样时长是周期的整数倍。
  2. 测噪声、振动、随机信号:无脑选汉宁窗。这是最稳妥的选择。
  3. 测两个频率很近的信号:比如要区分100kHz和101kHz,矩形窗分辨率最高,但泄漏严重。这时候可以先用矩形窗看个大概,再用汉宁窗确认。
  4. 测窄带信号,要求低旁瓣:海明窗更合适。我曾经用它分析一个无线通信的载波泄漏,效果比汉宁窗好不少。

小技巧:很多示波器允许你实时切换窗函数。我习惯先用汉宁窗看整体频谱,如果发现某个频率点附近有可疑的“小峰”,再切换到矩形窗看看是不是泄漏造成的。两个窗一对比,真假立现。

4.6 一个简单的代码示例

如果你用Python做FFT分析,可以这样对比三种窗的效果:

import numpy as np
import matplotlib.pyplot as plt

# 生成一个10MHz的正弦波 + 噪声
fs = 100e6  # 采样率100MHz
t = np.arange(0, 10e-6, 1/fs)
x = np.sin(2*np.pi*10e6*t) + 0.1*np.random.randn(len(t))

# 三种窗函数
rect = np.ones(len(x))
hann = np.hanning(len(x))
hamm = np.hamming(len(x))

# 加窗后做FFT
X_rect = np.fft.fft(x * rect)
X_hann = np.fft.fft(x * hann)
X_hamm = np.fft.fft(x * hamm)

# 画频谱对比
freq = np.fft.fftfreq(len(x), 1/fs)
plt.plot(freq/1e6, 20*np.log10(np.abs(X_rect)), label='矩形窗')
plt.plot(freq/1e6, 20*np.log10(np.abs(X_hann)), label='汉宁窗')
plt.plot(freq/1e6, 20*np.log10(np.abs(X_hamm)), label='海明窗')
plt.xlabel('频率 (MHz)')
plt.ylabel('幅度 (dB)')
plt.legend()
plt.grid(True)
plt.show()

跑一下这段代码,你会看到矩形窗的频谱底部明显“脏”很多,而汉宁窗和海明窗干净不少。这就是窗函数的作用。

4.7 小结

今天聊了三种最基础的窗函数。总结一下:

  • 矩形窗:分辨率最高,泄漏最严重。慎用。
  • 汉宁窗:最均衡,最通用。新手老手都适合。
  • 海明窗:旁瓣更低,但衰减慢。特定场景好用。

下节课我们继续聊其他窗函数,比如布莱克曼窗、凯泽窗,以及一些更高级的选择技巧。到时候我会分享一个我踩过的“大坑”——那次差点把一块价值两万的板子给判了死刑,结果只是窗函数没选对。

咱们下期见。