在数字信号处理领域,傅里叶变换和Laplace变换就像工程师的"瑞士军刀"——它们能将复杂的时域信号转换为更易分析的频域表示。但对于初学者来说,这些数学工具常常显得抽象难懂。本文将带你用Python代码亲手"触摸"这些变换,通过可视化演示和实际案例,让理论变得直观可操作。
工欲善其事,必先利其器。我们首先配置Python环境并理解核心概念:
python复制# 安装必要库(如果尚未安装)
!pip install numpy scipy matplotlib ipywidgets
# 导入标准工具包
import numpy as np
import matplotlib.pyplot as plt
from scipy.fft import fft, fftfreq
from scipy.integrate import quad
from scipy.signal import convolve
%matplotlib inline
傅里叶变换的本质是将时域信号分解为不同频率的正弦波组合,其数学定义为:
$$
F(\omega) = \int_{-\infty}^{\infty} f(t)e^{-j\omega t}dt
$$
而Laplace变换则是傅里叶变换的推广,特别适合分析系统稳定性:
$$
F(s) = \int_{0}^{\infty} f(t)e^{-st}dt, \quad s = \sigma + j\omega
$$
提示:傅里叶变换适用于稳态信号分析,Laplace变换更适合研究系统的瞬态响应和稳定性。
让我们从最简单的正弦波开始,观察其时域和频域表示:
python复制# 生成采样信号
fs = 1000 # 采样率(Hz)
T = 1.0/fs # 采样间隔
t = np.arange(0, 1, T) # 1秒时间轴
# 创建复合信号:5Hz正弦波 + 20Hz余弦波
f1, f2 = 5, 20
signal = 0.5*np.sin(2*np.pi*f1*t) + 0.3*np.cos(2*np.pi*f2*t)
# 快速傅里叶变换
n = len(signal)
yf = fft(signal)[:n//2]
xf = fftfreq(n, T)[:n//2]
# 绘制结果
plt.figure(figsize=(12, 6))
plt.subplot(121)
plt.plot(t, signal)
plt.title('时域信号')
plt.xlabel('时间(s)')
plt.subplot(122)
plt.plot(xf, 2.0/n * np.abs(yf))
plt.title('频域谱')
plt.xlabel('频率(Hz)')
plt.grid()
plt.tight_layout()
这段代码会生成两个清晰的峰值,分别位于5Hz和20Hz处,直观展示了傅里叶变换的频率分解能力。
傅里叶变换有几个重要性质,我们可以用代码验证:
python复制# 验证时移性质
delay = 0.1 # 100ms延迟
delayed_signal = 0.5*np.sin(2*np.pi*f1*(t-delay)) + 0.3*np.cos(2*np.pi*f2*(t-delay))
# 计算频谱
yf_delayed = fft(delayed_signal)[:n//2]
# 绘制相位变化
plt.figure(figsize=(10, 4))
plt.plot(xf, np.angle(yf), label='原始相位')
plt.plot(xf, np.angle(yf_delayed), label='延迟后相位')
plt.title('时移导致的相位变化')
plt.xlabel('频率(Hz)')
plt.ylabel('相位(rad)')
plt.legend()
运行这段代码,你会观察到相位谱的线性变化,这正是时移性质的直观体现。
Laplace变换在控制系统分析中尤为重要。考虑一个简单的RC低通滤波器:
python复制from scipy.signal import lti, impulse, step
# 定义系统传递函数:H(s) = 1/(RCs + 1)
R = 1e3 # 1kΩ
C = 1e-6 # 1μF
system = lti([1], [R*C, 1]) # 分子和分母系数
# 计算脉冲响应和阶跃响应
t_imp, y_imp = impulse(system)
t_step, y_step = step(system)
# 绘制响应曲线
plt.figure(figsize=(12, 5))
plt.subplot(121)
plt.plot(t_imp, y_imp)
plt.title('脉冲响应')
plt.xlabel('时间(s)')
plt.subplot(122)
plt.plot(t_step, y_step)
plt.title('阶跃响应')
plt.xlabel('时间(s)')
plt.tight_layout()
这个例子展示了如何用scipy.signal.lti建模线性时不变系统,并分析其动态特性。
系统的极点位置决定了其稳定性。我们可以可视化极点分布:
python复制# 定义二阶系统:H(s) = 1/(s^2 + 2ζωn s + ωn^2)
zeta = 0.3 # 阻尼比
omega_n = 10 # 自然频率(rad/s)
sys = lti([1], [1, 2*zeta*omega_n, omega_n**2])
# 获取极点
poles = sys.poles
# 绘制极点图
plt.figure(figsize=(6, 6))
plt.scatter(poles.real, poles.imag, marker='x', color='r', s=100)
plt.axhline(0, color='k', linestyle='--', alpha=0.3)
plt.axvline(0, color='k', linestyle='--', alpha=0.3)
plt.title('极点分布图')
plt.xlabel('实部')
plt.ylabel('虚部')
plt.grid()
当所有极点位于左半平面时,系统是稳定的——这是Laplace变换在工程中的核心应用之一。
卷积定理是信号处理的重要基石,它指出时域卷积等价于频域乘法:
python复制# 创建两个测试信号
rect = np.where((t > 0.2) & (t < 0.5), 1, 0) # 矩形脉冲
tri = np.maximum(0, 1 - np.abs(t - 0.5)/0.2) # 三角脉冲
# 计算卷积
conv_result = convolve(rect, tri, mode='same') * T # 乘以dt近似积分
# 频域验证
fft_rect = fft(rect)
fft_tri = fft(tri)
fft_conv = fft_rect * fft_tri
conv_theorem = np.real(fft(fft_conv, inverse=True)) / n # 逆变换
# 绘制结果对比
plt.figure(figsize=(12, 6))
plt.plot(t, conv_result, label='时域卷积')
plt.plot(t, conv_theorem, '--', label='卷积定理结果')
plt.title('卷积定理验证')
plt.xlabel('时间(s)')
plt.legend()
plt.grid()
卷积定理可用于高效实现滤波操作:
python复制# 添加高斯噪声
noisy_signal = signal + 0.1*np.random.randn(len(t))
# 设计简单低通滤波器
cutoff = 30 # Hz
kernel = np.sinc(2*cutoff*(t - t.mean())) # 理想低通核
kernel /= kernel.sum() # 归一化
# 时域卷积滤波
filtered = convolve(noisy_signal, kernel, mode='same')
# 频域滤波
freq_response = np.where(np.abs(xf) <= cutoff, 1, 0) # 理想滤波器
filtered_freq = np.real(fft(fft(noisy_signal) * freq_response, inverse=True))
# 结果对比
plt.figure(figsize=(12, 6))
plt.plot(t, signal, label='原始信号')
plt.plot(t, noisy_signal, alpha=0.3, label='含噪信号')
plt.plot(t, filtered, '--', label='时域滤波')
plt.plot(t, filtered_freq, '-.', label='频域滤波')
plt.legend()
plt.xlabel('时间(s)')
plt.grid()
对于长信号,直接卷积计算量很大。利用FFT可以显著加速:
python复制def fft_convolve(x, y):
n = len(x) + len(y) - 1
N = 2 ** int(np.ceil(np.log2(n))) # 补零到2的幂次
X = fft(x, N)
Y = fft(y, N)
return np.real(fft(X * Y, inverse=True))[:n] * (1.0/N)
# 性能对比
long_signal = np.random.randn(100000)
long_kernel = np.random.randn(1000)
%timeit convolve(long_signal, long_kernel, mode='same') # 直接卷积
%timeit fft_convolve(long_signal, long_kernel) # 基于FFT的卷积
在我的测试中,FFT方法对于长信号(>1000点)通常快10倍以上。
实际应用中,有限采样会导致频谱泄漏。窗函数可以缓解这个问题:
python复制windows = {
'矩形窗': np.ones_like(t),
'汉宁窗': np.hanning(len(t)),
'布莱克曼窗': np.blackman(len(t))
}
plt.figure(figsize=(12, 8))
for i, (name, window) in enumerate(windows.items()):
windowed_signal = signal * window
yf_windowed = fft(windowed_signal)[:n//2]
plt.subplot(3, 2, 2*i+1)
plt.plot(t, window)
plt.title(f'{name}时域')
plt.subplot(3, 2, 2*i+2)
plt.plot(xf, 2.0/n * np.abs(yf_windowed))
plt.title(f'{name}频谱')
plt.ylim(0, 0.6)
plt.tight_layout()
这个对比清晰地展示了不同窗函数对频谱泄漏的抑制效果。