DTFT、DFT和FFT傻傻分不清?5分钟图解帮你理清关系(附Python代码示例)

从“连续”到“离散”再到“快速”:用Python可视化彻底理清信号频谱分析的核心脉络

第一次接触数字信号处理的朋友,面对DTFT、DFT和FFT这三个缩写,多半会感到一阵眩晕。它们都带着“傅里叶变换”的标签,名字又如此相似,到底有什么区别和联系?是三种完全不同的东西,还是同一个核心思想在不同场景下的变体?更让人困惑的是,为什么我们实际编程时,比如用Python的numpy.fft库,调用的总是fft,却很少直接见到dtftdft的函数?

这种困惑非常普遍,根源在于教科书往往从严谨的数学定义出发,层层推导,却忽略了从“物理图像”和“计算实现”角度去串联这些概念。今天,我们不打算重复那些复杂的公式推导,而是换一种方式:用Python代码生成直观的图形,并配合动画演示,带你亲眼看看信号在时域和频域之间穿梭时,到底发生了什么。 我们的目标很明确:当你读完这篇文章,不仅能清晰地区分这三者,更能理解为什么FFT如此重要,以及在实际项目中如何正确选择和使用这些工具。

1. 起点:我们为什么需要“离散”的傅里叶变换?

在理想的世界里,我们处理的信号都是连续的,就像一条平滑变化的曲线。经典的傅里叶变换(Fourier Transform, FT)就是为这种连续时间、连续频率的信号准备的强大数学工具,它能将一个时域信号完美地分解成一系列不同频率的连续正弦波之和。

然而,计算机是数字世界的霸主,它只能处理离散的、有限长度的数据序列。我们通过ADC(模数转换器)从现实世界采集到的音频、图像、传感器读数,无一不是一串离散的数值。这就引出了根本矛盾:如何用处理离散数据的计算机,去分析本质上需要连续运算的傅里叶变换?

答案就是引入“离散化”的傅里叶变换。但离散化可以发生在两个维度:时域和频域。不同的离散化组合,就催生出了DTFT和DFT。

为了建立直观感受,我们先创建一个简单的复合信号作为贯穿全文的示例。这个信号由两个正弦波叠加而成,方便我们观察其频率成分。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
import warnings
warnings.filterwarnings('ignore')

# 设置采样参数
fs = 1000  # 采样频率,1000 Hz
T = 1.0    # 信号总时长,1秒
t = np.arange(0, T, 1/fs) # 离散时间点,从0到T,间隔1/fs秒

# 构造一个包含两个频率分量的信号
f1 = 50   # 第一个频率分量,50 Hz
f2 = 120  # 第二个频率分量,120 Hz
signal = 0.7 * np.sin(2 * np.pi * f1 * t) + np.sin(2 * np.pi * f2 * t)

# 可视化原始信号
fig, ax = plt.subplots(2, 1, figsize=(10, 6))
ax[0].plot(t, signal, 'b-', linewidth=1.5, label=f'Signal: 0.7*sin(2π*{f1}t) + sin(2π*{f2}t)')
ax[0].set_xlabel('Time [s]')
ax[0].set_ylabel('Amplitude')
ax[0].set_title('Original Continuous-time Signal (Sampled)')
ax[0].grid(True)
ax[0].legend()
ax[0].set_xlim([0, 0.1]) # 只看前0.1秒,细节更清晰

# 在下方绘制采样点(强调离散性)
ax[1].stem(t[:50], signal[:50], linefmt='C3-', markerfmt='C3o', basefmt='C3-', label='Discrete Samples')
ax[1].plot(t[:50], signal[:50], 'C3:', alpha=0.5) # 用虚线连接采样点,暗示连续波形
ax[1].set_xlabel('Time [s]')
ax[1].set_ylabel('Amplitude')
ax[1].set_title('Emphasizing the Discrete Nature of Sampled Data')
ax[1].grid(True)
ax[1].legend()
ax[1].set_xlim([0, 0.05])

plt.tight_layout()
plt.show()

这段代码生成了一个包含50Hz和120Hz正弦波的信号,并绘制了两幅子图。第一幅图看起来像连续信号,但实际上它是由1000个离散点(每秒采样1000次)连接而成的。第二幅图用茎状图明确展示了这些离散的采样点。这是我们所有分析的起点:一个有限长的离散时间序列 x[n]

注意:采样频率 fs 必须大于信号最高频率的两倍(奈奎斯特采样定理),这里120Hz是最高频率,1000Hz的采样率远高于240Hz,因此不会发生混叠。

2. DTFT:离散时间,连续频率的桥梁

现在,我们有了离散时间序列 x[n]。离散时间傅里叶变换(Discrete-Time Fourier Transform, DTFT)要回答的问题是:这个无限长(或至少在我们观察窗口内)的离散序列,其频率成分是怎样的?

DTFT的数学定义是: X(ω) = Σ_{n=-∞}^{∞} x[n] * e^{-jωn}

这里的关键在于:

  1. 输入是离散的n 是整数索引,代表离散时间点。
  2. 输出是连续的ω 是连续的角频率变量。这意味着DTFT的结果 X(ω) 是一个连续的、以2π为周期的复函数

“连续”意味着在频域上,频率分辨率是无限高的,理论上可以分辨任意接近的两个频率。但这在计算机上无法直接实现,因为计算机无法存储或计算一个连续函数在所有点上的值。我们只能计算X(ω)在有限个频率点上的值来近似观察它。

让我们用Python来近似计算并绘制上面示例信号的DTFT幅度谱。由于原信号是有限长N点,我们将其视为无限长序列中截取的一段(即假设其余点均为0),这被称为“加矩形窗”。

def dtft_manual(x, omega):
    """
    手动计算有限长序列x在给定频率点omega上的DTFT近似值。
    这本质上是计算DFT公式,但将频率点扩展到任意连续的omega。
    """
    N = len(x)
    n = np.arange(N)
    # 利用向量化计算,对于每个omega,计算与所有n的内积
    # 结果是一个与omega长度相同的复数数组
    X = np.sum(x * np.exp(-1j * omega[:, np.newaxis] * n), axis=1)
    return X

# 生成一段更短的信号以便清晰观察频谱
N_short = 64  # 使用64个点
x_short = signal[:N_short]
t_short = t[:N_short]

# 在频率轴上一个周期[0, 2π)内,密集地选取频率点来近似连续谱
num_freq_points = 1024
omega = np.linspace(0, 2 * np.pi, num_freq_points, endpoint=False) # 角频率,0到2π
# 转换为实际频率(Hz),便于理解。关系:f = (ω / (2π)) * fs
f_Hz = omega / (2 * np.pi) * fs

# 计算DTFT近似值
X_dtft = dtft_manual(x_short, omega)
magnitude_dtft = np.abs(X_dtft
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值