频域分析入门:傅里叶变换(FFT)的核心思想,频谱泄露与窗函数
各位同学,今天我们聊一个绕不开的话题——傅里叶变换。
说实话,我刚入行那会儿,看到FFT的公式就头疼。一堆复数、求和、指数,感觉像天书。后来做项目做多了,才慢慢悟出来:FFT说白了就是把信号从时间轴掰到频率轴上去看。你想想看,一个振动信号在时域里乱糟糟的,但到了频域,几个峰值一出来,问题就清楚了。
一、傅里叶变换到底在干什么?
我习惯用一个比喻来理解:傅里叶变换就像一台“频率分光镜”。白光通过三棱镜,分解成红橙黄绿蓝靛紫。信号通过FFT,分解成不同频率的正弦波分量。
数学上,连续傅里叶变换的公式长这样:
X(f) = ∫ x(t) · e^(-j2πft) dt
但在实际工程中,我们处理的是离散采样信号。所以用的是离散傅里叶变换(DFT),而FFT是它的快速算法。
DFT公式:
X[k] = Σ x[n] · e^(-j2πkn/N) (n=0 to N-1)
这里N是采样点数,k对应频率索引。每个X[k]代表该频率分量的幅度和相位。
核心要点:FFT的输出是复数数组。取模得到幅度谱,取角度得到相位谱。我们平时看频谱,看的都是幅度谱。
二、频谱泄露——一个让人头疼的问题
我在项目中遇到过这样一个情况:采集一个50Hz的纯正弦波,采样率1000Hz,采样1秒。理论上频谱应该只在50Hz处有一个尖峰。但实际做FFT后,发现50Hz附近出现了很多“小尾巴”,能量散开了。
这就是频谱泄露。
为什么会这样?
原因很简单:FFT假设信号是周期延拓的。如果你的采样长度不是信号周期的整数倍,那么首尾连接处就会产生“突变”。这个突变在频域里表现为额外的频率分量。
举个例子:
- 你采了1000个点,信号正好是50个完整周期 → 完美,频谱干净
- 你采了1000个点,信号是50.3个周期 → 首尾不连续,频谱泄露
注意:频谱泄露不是算法错误,而是有限长截断带来的固有现象。你无法完全消除它,只能尽量抑制。
三、窗函数——给信号“加个框”
既然泄露是因为截断引起的,那能不能让截断变得“温柔”一点?
可以。加窗就是干这个的。
窗函数的核心思想:让信号在两端平滑地衰减到零,减少首尾突变。
我常用的窗函数有几种:
| 窗函数 | 主瓣宽度 | 旁瓣衰减 | 适用场景 |
|---|---|---|---|
| 矩形窗 | 最窄 | -13dB | 频率分辨率要求高,泄露不敏感 |
| 汉宁窗 | 较宽 | -31dB | 通用场景,平衡分辨率与泄露 |
| 海明窗 | 较宽 | -41dB | 旁瓣抑制更好,适合弱信号检测 |
| 布莱克曼窗 | 最宽 | -58dB | 强干扰下的微弱信号提取 |
嗯,这里要注意:加窗不是免费的午餐。它抑制了泄露,但会降低频率分辨率(主瓣变宽)。所以选窗函数是个权衡。
四、汉宁窗 vs 海明窗——我个人的选择
这两个窗长得像,公式也像:
汉宁窗:w[n] = 0.5 - 0.5·cos(2πn/N)
海明窗:w[n] = 0.54 - 0.46·cos(2πn/N)
区别在哪?
- 汉宁窗:两端严格归零,旁瓣衰减快,但主瓣稍宽
- 海明窗:两端不归零(0.08左右),旁瓣更低,但衰减慢
我个人的习惯是:
- 做通用频谱分析,用汉宁窗。它最“老实”,不会引入额外假象
- 做弱信号检测,用海明窗。它的旁瓣更低,能压住强信号的干扰
小技巧:如果你不确定用哪个窗,先试试汉宁窗。它在大多数场景下表现都不错。我曾经在一个振动分析项目中,用汉宁窗成功分离了两个间隔仅0.5Hz的模态频率。
五、实战代码——加窗FFT怎么做
下面给一段Python代码,演示加窗FFT的完整流程:
import numpy as np
import matplotlib.pyplot as plt
# 生成测试信号:50Hz + 120Hz,加噪声
fs = 1000 # 采样率
t = np.arange(0, 1, 1/fs)
x = 0.7*np.sin(2*np.pi*50*t) + np.sin(2*np.pi*120*t)
x += 0.3*np.random.randn(len(t))
# 加汉宁窗
window = np.hanning(len(x))
x_windowed = x * window
# FFT
X = np.fft.fft(x_windowed)
freqs = np.fft.fftfreq(len(x), 1/fs)
# 只取正频率部分
half = len(x)//2
X_mag = np.abs(X[:half]) * 2 / len(x)
freqs = freqs[:half]
# 绘图
plt.figure(figsize=(10, 4))
plt.plot(freqs, X_mag)
plt.xlabel('频率 (Hz)')
plt.ylabel('幅度')
plt.title('加汉宁窗后的频谱')
plt.grid(True)
plt.show()
这段代码里有个细节:np.abs(X[:half]) * 2 / len(x)。为什么要乘以2?因为FFT输出是双边谱,我们只取正频率,幅度要加倍才能恢复真实幅值。另外除以N是为了归一化。
避坑指南:我曾经犯过一个错误——加窗后忘记做幅度补偿。结果频谱的幅度比实际小了一半。汉宁窗的幅度恢复系数是2,海明窗是1.85左右。记得查一下你用的窗函数的恢复系数。
六、知识体系总览
下面这张图总结了本章的核心逻辑:
这张图把整个流程串起来了:时域信号 → FFT → 频域 → 遇到泄露问题 → 加窗解决 → 不同窗函数的选择。
七、总结与避坑
最后,我把自己踩过的坑总结一下:
- 不要直接用矩形窗(也就是不加窗)。除非你确定采样长度是周期的整数倍,否则泄露会让你怀疑人生。
- 加窗后记得幅度补偿。不同窗的补偿系数不同,查表或计算一下。
- 窗函数会影响频率分辨率。如果你需要分辨两个很近的频率,用矩形窗或汉宁窗;如果信号很弱,用海明窗或布莱克曼窗。
- FFT点数不够时,补零不会提高分辨率。补零只是插值,真正的分辨率由采样时长决定。
一句话总结:FFT让你看到信号的频率成分,窗函数让你看得更清楚。选对窗,事半功倍;选错窗,事倍功半。
好了,这一章就到这里。记住,理论是死的,信号是活的。多动手试试,你会有自己的体会。
公众号:蓝海资料掘金营,微信deep3321