4、快速傅里叶变换:DFT与FFT原理、频谱分析、窗函数的作用
各位同学,今天我们聊一个硬核话题——快速傅里叶变换。
说实话,我刚入行那会儿,看到“傅里叶”三个字就头疼。大学课本里全是积分、级数,看得人眼花。但后来做航空电台项目,天天跟频谱打交道,才真正明白这东西有多重要。
你想想看,电台信号在空中飞,你总得知道它里面藏着什么频率吧?是语音、是数据、还是干扰?傅里叶变换就是干这个的。
4.1 从DFT到FFT:一个“笨办法”的进化
先说说DFT,离散傅里叶变换。
它的数学公式长这样:
X(k) = Σ x(n) * e^(-j*2π*k*n/N) (n=0 to N-1)
说白了,就是把时域信号跟一堆不同频率的复指数相乘,再累加。每个频率点算一次,N个点就要算N²次乘法。
我在项目中遇到过一个问题:采样率48kHz,做4096点的DFT。你猜要算多久?大概几秒钟。这在实时系统里根本没法用。
所以就有了FFT,快速傅里叶变换。
FFT的核心思想就四个字:分而治之。
它把一个大DFT拆成两个小DFT,再拆成四个更小的DFT……一直拆到只剩2点DFT。计算量从N²降到了N*log₂(N)。
举个例子:
- N=1024时,DFT需要约100万次运算
- FFT只需要约1万次运算
差了整整100倍!
关键点:FFT不是新的变换,只是DFT的一种高效算法。结果一模一样,只是算得更快。
4.2 基2时间抽取FFT:我常用的套路
最常见的FFT实现是基2时间抽取法。
它的思路是这样的:
- 把输入序列按奇偶分成两组
- 分别对两组做N/2点DFT
- 用旋转因子W_N^k把结果组合起来
嗯,这里要注意:旋转因子W_N^k = e^(-j*2π*k/N),它是有周期性的。利用这个周期性,可以进一步减少计算量。
我习惯用C语言实现一个简单的FFT:
void fft(complex* x, int N) {
// 位反转排序
for (int i = 0; i < N; i++) {
int j = bit_reverse(i, log2(N));
if (i < j) swap(&x[i], &x[j]);
}
// 蝶形运算
for (int len = 2; len <= N; len <<= 1) {
complex w = exp(-j * 2 * PI / len);
for (int i = 0; i < N; i += len) {
complex wk = 1;
for (int k = 0; k < len/2; k++) {
complex u = x[i + k];
complex v = x[i + k + len/2] * wk;
x[i + k] = u + v;
x[i + k + len/2] = u - v;
wk *= w;
}
}
}
}
这段代码我用了十几年了。核心就是那个蝶形运算,你看它像不像蝴蝶的两只翅膀?
小技巧:实际项目中,别自己写FFT。用现成的库,比如FFTW、CMSIS-DSP。它们针对特定处理器做了优化,比自己写的快得多。
4.3 频谱分析:从时域到频域的“翻译”
有了FFT,频谱分析就简单了。
基本步骤:
- 采集N点信号
- 做N点FFT
- 计算幅度谱:|X(k)| = sqrt(Re² + Im²)
- 换算频率轴:f(k) = k * fs / N
举个例子,你采集了1024点,采样率48kHz:
- 频率分辨率 = 48000 / 1024 ≈ 46.875 Hz
- 能分析的频率范围 = 0 ~ 24kHz(奈奎斯特频率)
我曾经调试一个航空电台,发现接收到的语音信号有奇怪的“嗡嗡”声。用FFT一看,在50Hz处有个尖峰。原来是电源工频干扰。加个陷波滤波器就解决了。
这就是频谱分析的价值——让你“看见”信号里有什么。
4.4 窗函数:为什么需要它?
好,问题来了。
你采集信号时,只能采有限长的一段。这相当于在无限长的信号上,加了一个矩形窗。
矩形窗有什么问题?
它会在频域产生“频谱泄漏”。
说白了,就是一个频率的能量,泄漏到了其他频率上。你看到的频谱不再是干净的谱线,而是带“裙边”的。
为什么会这样?
因为矩形窗在边界处突然截断,产生了高频分量。这些高频分量在频域表现为旁瓣。
解决办法就是——用其他形状的窗函数。
常用的窗函数有:
| 窗函数 | 主瓣宽度 | 旁瓣衰减 | 适用场景 |
|---|---|---|---|
| 矩形窗 | 最窄 | -13dB | 瞬态信号、频率分辨率要求高 |
| 汉宁窗 | 较宽 | -32dB | 一般频谱分析 |
| 海明窗 | 较宽 | -43dB | 语音信号处理 |
| 布莱克曼窗 | 最宽 | -58dB | 需要极低旁瓣 |
我个人的习惯是:
- 做频率分辨率要求高的分析,用矩形窗
- 做一般频谱分析,用汉宁窗
- 做语音信号处理,用海明窗
注意:加窗会损失信号能量。因为窗函数在两端衰减了信号。所以加窗后,幅度谱的幅值会变小。需要做幅度校正。
校正系数很简单:
幅度校正因子 = N / Σ w(n)
其中w(n)是窗函数序列。
4.5 避坑指南:我踩过的几个坑
做频谱分析这么多年,我踩过不少坑。分享几个给你:
坑一:忘记去直流
我曾经分析一个信号,发现0Hz处有个巨大的尖峰。查了半天,原来是信号有直流偏置。后来我习惯先减去均值再做FFT。
坑二:频率轴搞错
FFT输出的频率顺序是:0, fs/N, 2fs/N, ..., (N/2-1)fs/N, -fs/2, ..., -fs/N。很多人直接画图,结果频谱是镜像的。记得用fftshift函数把负频率移到左边。
坑三:窗函数选错
有一次分析一个微弱信号,用了矩形窗。结果信号被旁瓣淹没了。换成布莱克曼窗,信号就出来了。选窗函数要看你的需求——是要频率分辨率,还是要动态范围。
总结一下:
- DFT是理论,FFT是实践
- 频谱分析让你“看见”信号
- 窗函数是必备工具,选对了事半功倍
好了,这一章就到这里。下一章我们聊聊数字滤波器的设计,那也是个有意思的话题。