来源:Amphenol(安费诺)
发布时间:2026-07-20
做频谱分析时,很多人第一次接触 离散傅里叶变换(DFT)都会遇到一个问题:为什么明明是一个简单的正弦波,做出来的频谱却“不干净”,旁边还拖着一串尾巴?
这其实不是 FFT 算错了,而是截断带来的典型现象:频谱泄漏。
为了解决这个问题,工程上通常会在做 FFT 之前给信号乘上一个窗函数。
常见的窗函数里,最典型的就是:
矩形窗,频谱泄露的典型
汉宁窗(Hann)
汉明窗(Hamming)
这三种窗函数在频谱分析中非常常见,
汉明窗和汉宁窗很相像,后面会提到,但它们各自的特点、适用场景、优缺点并不一样。
然而,即使是汉宁窗或者汉明窗,也有“对称”和“周期”两种,周期式的才适合频谱分析。本文将系统梳理它们的差异,并重点推导两种汉宁窗加窗DFT比较,并提供周期式汉宁窗处理的FFT信号的汉宁窗增益推理过程。最后给出可直接运行的 Python 示例代码,把理论和实际观察对应起来。
1. 为什么 FFT 需要加窗?
1.1 FFT 的前提:有限长度采样
FFT 处理的是离散、有限长度的数据。而现实中的信号往往是连续的,采样时只是截取其中一段。这就相当于:
原始信号 × 一个有限长度的“截断窗”。
如果加窗,最常见的就是截断窗:
区间内全是 1
区间外全是 0
这就是矩形窗。

1.2 截断为什么会导致频谱泄漏?
不是矩形窗截取的数据进行FFT都会导致频谱泄露,比如,一个正弦信号刚好在采样窗口内是整数个周期,那么它在输出的频域上会比较“干净”。然而这仅仅是特例,实际应用中,更多信号的频率并不刚好落在 FFT 的频点上,这时截断后的边界不连续,会导致频域能量扩散到其他频点,形成所谓的频谱泄漏。某种意义上,加窗后的频谱泄露是常态,只是多少不同而已。
可以简单理解为:
时域边界越突兀
频域旁瓣越高,泄漏越严重
窗函数的作用,就是让信号在边界处平滑过渡,从而减小泄漏。
2. 三种常见窗函数的定义
2.1 矩形窗
矩形窗是最简单的窗函数:

它的特点非常直接:
窗内不改变幅值
频域主瓣最窄
旁瓣最高
最容易产生频谱泄漏
优点
频率分辨率最好
计算最简单
若信号整周期截断,效果很好
缺点
旁瓣高,泄漏最严重
对非整周期信号不友好(碰上整周期的,实际应用中那叫狗屎运)
2.2 汉宁窗(Hanning / Hann Window)
汉宁窗(对称)定义为:

它的两端都趋近于 0,中间最大值为 1。
特点
边界平滑
明显降低旁瓣
比矩形窗更适合频谱分析
主瓣比矩形窗宽,因此频率分辨率略有下降
适用场景
通用频谱分析
频谱泄漏不能接受时
工程中最常见的窗之一
如果把N-1替换为N,数据长度仍然为N,那么这个窗就是周期汉宁窗,它的首尾数值就不再相等。

2.3 汉明窗(Hamming Window)
汉明窗(对称)定义为:

前面提到过,它和汉宁窗非常像,但两端不完全为 0,而是0.08。
特点
旁瓣抑制较好
主瓣宽度与汉宁窗接近
在某些场景下,对幅值和泄漏平衡得更好
适用场景
通用工程分析
希望在泄漏抑制和分辨率之间取得平衡
某种程度上,小编也是因为这个汉明窗和汉宁窗算法几乎一致,拿来比较陈述也容易一些。实际上有很多种类的窗函数。下面几组窗函数及其对应的幅频图供各位参考。

3. 三种窗函数的核心差异
下面先给出一个直观总结:
窗函数 |
两端值 |
主瓣宽度 |
旁瓣高度 |
泄漏抑制能力 |
幅值影响 |
|---|---|---|---|---|---|
矩形窗 |
1 |
最窄 |
最高 |
最弱 |
基本不衰减 |
汉宁窗 |
0 |
较宽 |
较低 |
较强 |
会明显衰减 |
汉明窗 |
约 0.08 |
较宽 |
较低 |
较强 |
会明显衰减 |
3.1 从频域角度看
矩形窗
主瓣最窄
但旁瓣很高
容易把能量“撒”到其他频点
汉宁窗
主瓣更宽
旁瓣较低
能有效减少泄漏
汉明窗
频域表现与汉宁窗相近
在某些指标上略有不同
工程上常被当作汉宁窗的另一种选择
4. 汉宁窗加窗后为什么需要增益修正?
这是本文的重点。
当我们对信号加汉宁窗后,信号会被“压低”:

如果原始信号是单频正弦波,那么加窗后,在频域中主峰幅值会比原来小。

因此,做幅值分析时必须进行修正,否则会低估真实幅值。
这个修正通常与窗函数的平均值或相干增益(coherent gain)有关。
5. 汉宁窗增益证明
下面给出一个证明过程,尽量做到清晰、易懂。
5.1 汉宁窗的表达式
我们再次搬出对称汉宁窗定义:

其中:
N 为窗长
n=0, 1, … , N−1
5.2 对称汉宁窗的平均值
我们先计算窗函数的平均值:

代入汉宁窗公式:

拆开求和:

令:

利用复指数形式:

取实部,得到这里的余弦项累加和为1,于是两部分相加,得到对称汉宁窗的均值是[0.5+1/(2N)],当N足够大时,汉宁窗的均值~0.5。这部分的汉宁窗叫对称hann。
大家可以证明以下:当汉宁窗的周期数从N-1改为N时,称之为周期hann,后部分的累加和为0,汉宁窗的均值=0.5。这个称之为周期汉宁窗,很显然,它的一个周期的首尾值不相等。
MATLAB 文档明确指出,周期Hann用于频谱分析,对称Hann用于滤波器设计;周期的构造方式是“先做 (L+1) 点 对称Hann (在本文中指N),再去掉最后一个点”,这样更符合 DFT 的周期延拓假设。NumPy的汉宁窗给出的是 (N-1) 的对称定义。
5.3 两种汉宁窗的DFT的差异
我们知道傅里叶级数,这样就可以把汉宁窗对一个复杂信号的作用变成对其中一个基本正余弦信号的作用。原理上,信号其余部分可以也进行同样的处理后,再进行累加就可以得到最后的结果了。
这里令长度为N的余弦信号:

其中:
A是幅值
k0是整数频点,不等于0
Ø是初始相位
这里假设k0正好落在FFT的栅格点上(因为,如果离散信号在一个 FFT 分析周期 (N) 内不落在 FFT 栅格点上,那么对有限长度数据做 FFT 时,一般都会发生频谱泄露;加窗只能减轻泄露旁瓣,不能从根本上消除它。)
加窗后的信号:

我们需要比较:
不加窗的离散傅里叶变换(DFT):X[k]
加窗的DFT:
DFT定义
长度为N的DFT:

长度为N的加窗DFT:

把余弦信号写成复指数形式:

代入DFT得:

这里k0不等于0。当k=k0时,跟进公式:

X[k]中的第一项等于
,余弦信号在其对应的频点上幅值为NA/2
X[k]中的第二项等于0
对应施加窗函数后的数据的DFT的公式有:

其中W[]是窗函数的DFT。
对信号加周期汉宁窗(Periodic Hann)的DFT
前面提到的都是对称汉宁窗,这里我们需要先提周期汉宁窗。把周期汉宁窗展开为指数形式后得到:

其DFT为:

利用正交性:

DFT中代入
得:

使用这个窗函数后,满足前面初始条件的余弦信号DFT结果,就可以得到3个漂亮的离散脉冲。利用加周期汉宁窗函数后的余弦信号的DFT公式:

把两组公式代入上式后得到周期汉宁窗函数下特定余弦信号的DFT:

若k=k0,且0<k0<N/2,则通常主频点幅值:

这个结果刚好是余弦的DFT结果的1/2,所以要恢复到该理想原信号DFT的结果的幅值,增益就是2。而对那些信号频率与采样记录长度不满足整数周期的,就不再是2倍关系,不过用大约2倍还是可以接受的。
在k=k0-1和k0+1两处,还分别有两个值,这是由窗引起的频谱扩展项,它们不是原信号多出来的频率成分,而是原来只在一个bin上的理想谱线,经过窗函数后,被窗的频谱“卷积”成了具有一定宽度的主瓣裙边采样。
这时候再看汉明窗,是不是豁然开朗了。不过仍然有差异和侧重点:
汉明窗是为了压低第一旁瓣做的折中,工程上常被用来减轻近邻强信号对弱信号的泄漏影响。(mathworks.com)
汉宁窗的旁瓣不是最低,但它的旁瓣包络衰减通常更快,因此在“离主瓣更远”的区域,幅值可能比汉明窗更低。(en.wikipedia.org)

对信号加对称汉宁窗(Symmetric Hann)后的DFT
下面分别给出对称汉宁窗指数形式展开后的公式:

它的DFT也类似:

对满足前面初始条件的余弦信号加对称汉宁窗后的DFT公式简化等式和前面加周期汉宁窗后的DFT相似:

把对称汉宁窗代入,得到对称汉宁窗的DFT:

我们已经假设这个余弦信号的频率就是落在 DFT的栅格k0上的整栅格频率:

同样的采样频率下,周期汉宁窗对应的离散角频率是(2*Pi/N),而对称汉宁窗对应的离散角频率是[2*Pi/(N-1)]。这种情况下,我们引入 Dirichlet 核函数。定义:

简化后得到(从等比数列求和公式再化简,方便DFT运算中指数的加减):

于是对称汉宁窗的DFT有:

代入信号加窗的DFT公式,得到:

若k=k0,且0<k0<N/2,这个时候,前半部分中的后两项,以及后半部分的数值不再是0了(泄露项)。如果忽略这几个非0项,公式中的第一项得到的结果是:

5.5 两种汉宁窗对特定余弦信号DFT结果的影响比较
参数: (N = 16, 频率点位k_0 = 5, 幅值A = 10.0, 初始相位phi = 0.0)。单纯从输出的DFT幅频图并不容易看出差异,尤其是后面两个,但是从输出值中却一目了然。
Bin |
Unwindowed |
Symmetric Hann |
Periodic Hann |
|---|---|---|---|
0 |
0.000000 |
0.102933 |
0.000000 |
1 |
0.000000 |
0.155105 |
0.000000 |
2 |
0.000000 |
0.330425 |
0.000000 |
3 |
0.000000 |
0.967280 |
0.000000 |
4 |
0.000000 |
20.504275 |
20.000000 |
5 |
80.000000 |
37.519560 |
40.000000 |
6 |
0.000000 |
20.477774 |
20.000000 |
7 |
0.000000 |
1.042818 |
0.000000 |
这里顺便把矩形窗也放进来进行了比较。特别需要再次提起,处理的余弦信号频率刚好是测试采样频率的整数倍,所以才会卡着点出现数据点。而也从上面这两个红色标记的幅值关系中,看到汉宁窗后的DFT应该乘以多大的增益才可以恢复到原先的幅值。这就是汉宁窗增益修正的本质。
表中的对称汉宁窗对应的DFT幅值没有一个等于0,是因为分母是(N-1),它的周期性并不和DFT的(N)点网格天然对齐,所以零点通常不会刚好落在 DFT 栅格上。但是因为其它方面的特性,为如前所述,对称汉宁窗多用于滤波器设计,这里不再多讲。
6. 对信号施加周期汉明窗后的DFT和矩形窗比较
还是用图说话。

矩形窗和汉宁窗后的DFT幅频图
在上图中,其实有2个频率相近的信号,但是其中一个是弱信号。
# Strong tone, intentionally off-binx1 = 10.0 * np.cos(2 * np.pi * 30.25 * t + 0.0)# Weak tone close to the strong tone, also off-binx2 = 0.7 * np.cos(2 * np.pi * 33.10 * t + 1.1)
但是很显然,矩形框处理后,弱信号被淹没在了旁瓣中;而汉宁窗处理后,从峰值上判断,还是可以看出有2个信号的。此时两个频率相差~3Hz,但是其中一个信号的幅值明显要小于另外一个。那是不是说明使用汉宁窗的分辨率更高?这个要看情况。
严格来说:
主瓣越窄,理论频率分辨能力越强
旁瓣越低,弱信号检测能力越好
我们把模拟信号再次调整,让两个频率靠得更近,但是幅值相当。
# Strong tone, intentionally off-binx1 = 10.0 * np.cos(2 * np.pi * 33.25 * t + 0.0)# Weak tone close to the strong tone, also off-binx2 = 10.7 * np.cos(2 * np.pi * 32.20 * t + 1.1)

所以说:
汉宁窗在强弱信号并存时,可能具有更好的可检测性
汉宁窗虽然主瓣更宽,但由于旁瓣更低,弱信号更不容易被强信号泄漏掩盖
因此在某些场景下,汉宁窗的实际可分辨性可能优于矩形窗,但是矩形窗在信号较强的情况下,分辨率还是要高于汉宁窗的。
下面是测试代码,可供测试。代码中两个模拟信号的频率很近,幅值相当,是要验证哪个加窗方式下的DFT对信号的分辨率更高。请留意python中,Scipy的汉宁窗可以选择哪种类型。
import numpy as npimport matplotlib.pyplot as pltfrom scipy.signal.windows import hanndef make_signal(N, fs):"""Construct a signal with:1) one strong off-bin tone2) one weak nearby off-bin tone3) one distant toneso that rectangular-window sidelobes become obvious."""n = np.arange(N)t = n / fs# Strong tone, intentionally off-binx1 = 10.0 * np.cos(2 * np.pi * 33.25 * t + 0.0)# Weak tone close to the strong tone, also off-binx2 = 10.7 * np.cos(2 * np.pi * 32.20 * t + 1.1)# Another tone farther awayx3 = 2.5 * np.cos(2 * np.pi * 90.70 * t + 0.4)return x1 + x2 + x3def spectrum_linear_and_db(x, fs, nfft=8192):"""Return one-sided spectrum in both linear amplitude and dB."""N = len(x)X = np.fft.rfft(x, n=nfft)f = np.fft.rfftfreq(nfft, d=1/fs)# amplitude scaling for one-sided spectrummag = np.abs(X) * 2.0 / Nmag[0] /= 2.0if nfft % 2 == 0:mag[-1] /= 2.0mag_db = 20 * np.log10(mag + 1e-15)return f, mag, mag_dbdef compare_rect_vs_hann(N=256, fs=256.0, nfft=16384):x = make_signal(N, fs)w_rect = np.ones(N)w_hann = hann(N, sym=False) # SciPy periodic Hann windowx_rect = x * w_rectx_hann = x * w_hannf_rect, mag_rect, mag_rect_db = spectrum_linear_and_db(x_rect, fs, nfft=nfft)f_hann, mag_hann, mag_hann_db = spectrum_linear_and_db(x_hann, fs, nfft=nfft)print(f"N = {N}, fs = {fs} Hz")print("Signal components:")print(" 10.0*cos(2*pi*33.25*t + 0.0)")print(" 10.7*cos(2*pi*32.20*t + 1.1)")print(" 2.5*cos(2*pi*90.70*t + 0.4)")print(f"FFT zero-padding length = {nfft}")print(f"Bin spacing = {fs/N:.6f} Hz")# Figure 1: Linear magnitudeplt.figure(figsize=(13, 6))plt.plot(f_rect, mag_rect, color='black', linewidth=1.1, label='Rectangular window')plt.plot(f_hann, mag_hann, color='red', linewidth=1.1, label='Periodic Hann window')plt.title("Linear Magnitude Spectrum: Rectangular vs Periodic Hann")plt.xlabel("Frequency (Hz)")plt.ylabel("Amplitude")plt.grid(True, alpha=0.3)plt.xlim([0, fs/2])plt.legend()plt.tight_layout()plt.show()# Figure 2: dB magnitudeplt.figure(figsize=(13, 6))plt.plot(f_rect, mag_rect_db, color='black', linewidth=1.1, label='Rectangular window')plt.plot(f_hann, mag_hann_db, color='red', linewidth=1.1, label='Periodic Hann window')plt.title("dB Magnitude Spectrum: Rectangular vs Periodic Hann")plt.xlabel("Frequency (Hz)")plt.ylabel("Magnitude (dB)")plt.grid(True, alpha=0.3)plt.ylim([-120, 30])plt.xlim([0, fs/2])plt.legend()plt.tight_layout()plt.show()# Figure 3: Linear magnitude zoomedplt.figure(figsize=(13, 6))plt.plot(f_rect, mag_rect, color='black', linewidth=1.1, label='Rectangular window')plt.plot(f_hann, mag_hann, color='red', linewidth=1.1, label='Periodic Hann window')plt.title("Zoomed Linear Magnitude Around the Strong Tone")plt.xlabel("Frequency (Hz)")plt.ylabel("Amplitude")plt.grid(True, alpha=0.3)plt.xlim([10, 55])plt.legend()plt.tight_layout()plt.show()# Figure 4: dB magnitude zoomedplt.figure(figsize=(13, 6))plt.plot(f_rect, mag_rect_db, color='black', linewidth=1.1, label='Rectangular window')plt.plot(f_hann, mag_hann_db, color='red', linewidth=1.1, label='Periodic Hann window')plt.title("Zoomed dB Magnitude Around the Strong Tone")plt.xlabel("Frequency (Hz)")plt.ylabel("Magnitude (dB)")plt.grid(True, alpha=0.3)plt.ylim([-120, 30])plt.xlim([10, 55])plt.legend()plt.tight_layout()plt.show()if __name__ == "__main__":compare_rect_vs_hann(N=256, fs=256.0, nfft=16384)
7. 结尾
FFT 本身并不会“出错”,真正影响频谱结果的,往往是我们对有限长度信号的处理方式。窗函数的本质,就是帮助我们在“分辨率”和“泄漏抑制”之间做出权衡。
想要最窄主瓣,可以考虑矩形窗;
想要更干净的频谱,汉宁窗通常是首选;
想要更平衡的工程表现,汉明窗也非常常用。
而对于汉宁窗、汉明窗,以及类似的其它函数,容易被忽略的一点就是:
加窗以后幅值会变小,因此 FFT 的结果不能直接拿来当原始幅值,需要做增益修正。
理解了这一点,你对 FFT 的认识就不再只是“会调用函数”,而是能真正知道结果为什么会这样。