傅里叶变换实战:用Python对比连续(FT)和离散(DFT)的5个关键差异点
很多刚开始接触信号处理的同学,都会对傅里叶变换(FT)和离散傅里叶变换(DFT)感到困惑。教科书上往往从严谨的数学定义出发,推导出一堆公式,但看完之后,对于“为什么有了FT还要DFT?”、“DFT算出来的频谱和FT的频谱到底是不是一回事?”这类问题,依然云里雾里。我自己在初学阶段也踩过不少坑,比如曾经试图用DFT的结果去直接解释一个连续信号的物理频率,结果闹了笑话。
这篇文章,我想换一种方式,不堆砌公式,而是通过两个具体的实验场景——音频信号处理和图像滤波——用Python代码带大家直观地感受FT和DFT的核心差异。我们会用numpy和librosa等工具,在Jupyter Notebook里一步步操作,从现象观察入手,通过代码验证,最后归纳出五个你必须理解的差异点。这种“现象-代码-结论”的路径,或许能帮你绕过那些抽象的数学障碍,建立起更牢固的直觉。
1. 实验准备:搭建你的信号处理工作台
在开始对比之前,我们需要一个统一的环境来生成信号、进行计算和可视化。这里我推荐使用Anaconda来管理Python环境,它能很好地处理科学计算库的依赖关系。
首先,创建一个新的conda环境并安装必要的包。我习惯为不同的项目创建独立的环境,避免版本冲突。
conda create -n signal_processing python=3.9
conda activate signal_processing
pip install numpy scipy matplotlib jupyter librosa opencv-python
接下来,在Jupyter Notebook中,我们导入核心库,并设置一些全局的绘图样式,让我们的图表更美观、更专业。
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
import librosa
import librosa.display
import cv2
# 设置中文字体和绘图样式
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
plt.style.use('seaborn-v0_8-darkgrid')
# 定义一些常用的颜色,方便后续绘图区分
COLOR_FT = '#FF6B6B' # 红色代表连续FT相关
COLOR_DFT = '#4ECDC4' # 青色代表离散DFT相关
为了后续的对比实验,我们还需要编写几个工具函数。一个是生成标准测试信号,比如正弦波、方波、chirp信号(频率随时间变化的信号);另一个是计算并绘制频谱的辅助函数。这里先展示一个生成复合正弦信号的例子,它由两个不同频率的正弦波叠加而成。
def generate_test_signal(duration=1.0, sample_rate=1000, freqs=[50, 120]):
"""
生成一个由多个正弦波叠加的测试信号。
参数:
duration: 信号时长(秒)
sample_rate: 采样率(Hz)
freqs: 正弦波频率列表(Hz)
返回:
t: 时间轴数组
x: 信号值数组
"""
t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
x = np.zeros_like(t)
for f in freqs:
x += np.sin(2 * np.pi * f * t)
return t, x
提示:采样率
sample_rate的选择至关重要,它必须大于信号中最高频率成分的两倍(奈奎斯特频率),否则会发生混叠,导致频率分析完全错误。这是我们后面要讨论的第一个关键差异的伏笔。
有了这些准备,我们就可以开始第一个实验了。我们将从最根本的“频域分辨率”差异说起,这是理解FT和DFT分野的起点。
2. 差异一:连续谱与离散谱——分辨率的天壤之别
这是FT和DFT最本质、也最直观的区别。连续时间傅里叶变换(CTFT)处理的是理论上无限长、连续的模拟信号,其频谱是连续的。这意味着在频率轴上,你可以找到任意一个频率点对应的频谱值。而离散傅里叶变换(DFT)处理的是有限长、离散的数字信号,其频谱是离散的,你只能得到在特定频率点(称为频率“bin”)上的频谱值。
为了直观感受,我们构造一个简单的连续时间信号:一个持续1秒的50Hz正弦波。在理论上,它的FT应该是在±50Hz处有两个冲激(Dirac Delta函数)。但计算机无法处理真正的连续信号和连续频谱,我们只能通过高密度采样来近似。
# 模拟“连续”信号的高精度采样
t_continuous = np.linspace(0, 1, 10000, endpoint=False) # 用10000个点模拟连续
x_continuous = np.sin(2 * np.pi * 50 * t_continuous)
# 计算其“近似”的连续频谱(通过高密度DFT)
freqs_high_res = np.fft.fftfreq(len(x_continuous), d=1/10000)
fft_high_res = np.fft.fft(x_continuous)
magnitude_high_res = np.abs(fft_high_res) / len(x_continuous) # 归一化幅度
# 现在,模拟对这个连续信号进行“离散采样”,采样率500Hz
sample_rate = 500
duration = 1
num_samples = int(sample_rate * duration)
t_discrete = np.linspace(0, duration, num_samples, endpoint=False)
# 注意:这里是从“连续”信号中抽取样本,模拟ADC过程
x_discrete = np.sin(2 * np.pi * 50 * t_discrete)
# 计算DFT
freqs_dft = np.fft.fftfreq(num_samples, d=1/sample_rate)
fft_dft = np.fft.fft(x_discrete)
magnitude_dft = np.abs(fft_dft) / num_samples
# 绘图对比
fig, axes = plt.subplots(2, 1, figsize=(12, 8))
# 上图:高分辨率近似的连续谱
axes[0].plot(freqs_high_res[:5000], magnitude_high_res[:5000], color=COLOR_FT, linewidth=1.5, label='高分辨率近似连续谱 (FT概念)')
axes[0].axvline(x=50, color='gray', linestyle='--', alpha=0.7, label='真实频率 50Hz')
axes[0].set_xlabel('频率 (Hz)')
axes[0].set_ylabel('幅度')
axes[0].set_title('连续傅里叶变换 (FT) 频谱概念:连续函数')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# 下图:DFT得到的离散谱
axes[1].stem(freqs_dft[:num_samples//2], magnitude_dft[:num_samples//2], linefmt=COLOR_DFT, markerfmt='o', basefmt=' ', label='DFT离散谱')
axes[1].axvline(x=50, color='gray', linestyle='--', alpha=0.7)
axes[1].set_xlabel('频率 (Hz)')
axes[1].set_ylabel('幅度')
axes[1].set_title('离散傅里叶变换 (DFT) 频谱:离散点')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
运行这段代码,你会看到上图呈现出一条在50Hz处有尖锐峰值的连续曲线(尽管它本质还是由很多点连成的),而下图则是在一系列离散的频率点上有值的“ stems ”图。DFT的频谱点之间的间隔是固定的,这个间隔Δf = 采样率 / 点数 = 1 / 信号时长。这意味着,DFT的频率分辨率完全由信号的时间长度决定。如果你的信号只记录了0.1秒,那么Δf就是10Hz,你根本无法区分50Hz和55Hz的成分。
| 特性 | 连续傅里叶变换 (FT) | 离散傅里叶变换 (DFT) |
|---|---|---|
| 信号类型 | 连续时间,无限长(理论上) | 离散时间,有限长 |
| 频谱形式 | 连续函数 | 离散序列 |
| 频率轴 | 连续实数频率 | 离散的频率点 (bins) |
| 分辨率 | 理论上无限 |


215

被折叠的 条评论
为什么被折叠?



