NumPy 傅里叶变换
傅里叶变换是信号处理、图像分析和科学计算的核心工具。NumPy 的 numpy.fft 模块提供了离散傅里叶变换(DFT)的高效实现,基于快速傅里叶变换(FFT)算法。本章将深入讲解傅里叶变换的原理和在 NumPy 中的应用。
傅里叶变换基础
什么是傅里叶变换?
傅里叶变换将信号从时域(时间空间)转换到频域(频率空间)。简单来说,任何周期函数都可以表示为不同频率的正弦波和余弦波的叠加。
对于离散信号,我们使用离散傅里叶变换(DFT)。其数学定义为:
其中 是输入信号, 是频域表示, 是信号长度。
为什么需要傅里叶变换?
频谱分析:了解信号包含哪些频率成分,这在音频处理、振动分析中非常重要。
滤波:在频域中去除不需要的频率分量,如去除高频噪声。
卷积加速:时域卷积等于频域乘积,利用这一点可以大幅加速卷积运算。
图像处理:图像压缩、边缘检测、去噪等都离不开傅里叶变换。
时域与频域的关系
import numpy as np
# 创建一个简单信号:两个正弦波的叠加
sampling_rate = 1000 # 采样率:每秒1000个样本
t = np.linspace(0, 1, sampling_rate, endpoint=False) # 1秒的时间点
# 信号 = 50Hz正弦波 + 120Hz正弦波
signal = np.sin(2 * np.pi * 50 * t) + 0.5 * np.sin(2 * np.pi * 120 * t)
print(f"信号长度: {len(signal)}")
print(f"采样间隔: {t[1] - t[0]:.6f} 秒")
print(f"时间范围: {t[0]} 到 {t[-1]} 秒")
一维傅里叶变换
fft 函数
np.fft.fft 计算一维离散傅里叶变换:
import numpy as np
# 创建信号
sampling_rate = 1000
t = np.linspace(0, 1, sampling_rate, endpoint=False)
signal = np.sin(2 * np.pi * 50 * t) + 0.5 * np.sin(2 * np.pi * 120 * t)
# 计算FFT
fft_result = np.fft.fft(signal)
print(f"FFT结果形状: {fft_result.shape}")
print(f"FFT结果类型: {fft_result.dtype}") # 复数类型
# FFT结果是复数,包含幅度和相位信息
print(f"前5个FFT值: {fft_result[:5]}")
频率轴
使用 np.fft.fftfreq 获取对应的频率值:
import numpy as np
sampling_rate = 1000
n = 1000 # 采样点数
# 获取频率轴
frequencies = np.fft.fftfreq(n, d=1/sampling_rate)
print(f"频率范围: {frequencies.min():.1f} Hz 到 {frequencies.max():.1f} Hz")
print(f"频率间隔: {frequencies[1] - frequencies[0]:.4f} Hz")
# 频率轴的特点:
# [0, 1, 2, ..., n/2-1, -n/2, ..., -2, -1] * (采样率/n)
print(f"\n前10个频率: {frequencies[:10]}")
print(f"后10个频率: {frequencies[-10:]}")
幅度谱和相位谱
FFT 结果是复数,可以分解为幅度和相位:
import numpy as np
sampling_rate = 1000
t = np.linspace(0, 1, sampling_rate, endpoint=False)
signal = np.sin(2 * np.pi * 50 * t) + 0.5 * np.sin(2 * np.pi * 120 * t)
fft_result = np.fft.fft(signal)
frequencies = np.fft.fftfreq(len(signal), d=1/sampling_rate)
# 幅度谱(取绝对值)
amplitude = np.abs(fft_result)
# 功率谱(幅度的平方)
power = amplitude ** 2
# 相位谱
phase = np.angle(fft_result)
# 只看正频率部分
positive_mask = frequencies >= 0
positive_freqs = frequencies[positive_mask]
positive_amplitude = amplitude[positive_mask]
print("幅度谱峰值分析:")
# 找到幅度最大的几个频率
top_indices = np.argsort(positive_amplitude)[-5:][::-1]
for idx in top_indices:
if positive_freqs[idx] > 0:
print(f"频率 {positive_freqs[idx]:.1f} Hz: 幅度 {positive_amplitude[idx]:.2f}")
ifft 函数
np.fft.ifft 是逆傅里叶变换,将频域信号转回时域:
import numpy as np
# 原始信号
t = np.linspace(0, 1, 1000, endpoint=False)
original = np.sin(2 * np.pi * 50 * t)
# FFT
fft_result = np.fft.fft(original)
# IFFT
reconstructed = np.fft.ifft(fft_result)
# 验证重建
print(f"原始信号前5个值: {original[:5]}")
print(f"重建信号前5个值: {reconstructed.real[:5]}") # 取实部
print(f"重建误差: {np.max(np.abs(original - reconstructed.real)):.2e}")
# 注意:IFFT结果可能是复数(由于数值误差),通常只取实部
fftshift 和 ifftshift
FFT 结果的默认顺序是:零频率在开头,然后是正频率,最后是负频率。fftshift 将零频率移到中心:
import numpy as np
sampling_rate = 1000
n = 1000
frequencies = np.fft.fftfreq(n, d=1/sampling_rate)
print("原始频率顺序:")
print(f"前5个: {frequencies[:5]}")
print(f"后5个: {frequencies[-5:]}")
# 使用 fftshift 重新排列
shifted_freqs = np.fft.fftshift(frequencies)
print("\nshift后频率顺序:")
print(f"前5个: {shifted_freqs[:5]}")
print(f"后5个: {shifted_freqs[-5:]}")
print(f"中心频率: {shifted_freqs[n//2]}") # 0 Hz
可视化示例
import numpy as np
sampling_rate = 1000
t = np.linspace(0, 1, sampling_rate, endpoint=False)
signal = np.sin(2 * np.pi * 50 * t) + 0.5 * np.sin(2 * np.pi * 120 * t)
# 计算FFT并shift
fft_result = np.fft.fft(signal)
fft_shifted = np.fft.fftshift(fft_result)
frequencies = np.fft.fftshift(np.fft.fftfreq(len(signal), d=1/sampling_rate))
# 幅度谱(归一化)
amplitude = np.abs(fft_shifted) / len(signal)
print("幅度谱(中心化):")
# 找到主要频率成分
threshold = 0.1
significant = amplitude > threshold
for freq, amp in zip(frequencies[significant], amplitude[significant]):
if abs(freq) > 0:
print(f"频率 {freq:6.1f} Hz: 幅度 {amp:.3f}")
实数信号的 FFT
对于实数信号,FFT 结果具有对称性:负频率分量是正频率分量的共轭。因此可以使用更高效的 rfft:
rfft 函数
rfft 只计算正频率部分,节省计算和存储:
import numpy as np
sampling_rate = 1000
t = np.linspace(0, 1, sampling_rate, endpoint=False)
signal = np.sin(2 * np.pi * 50 * t) # 实数信号
# 标准FFT
fft_result = np.fft.fft(signal)
print(f"fft 结果长度: {len(fft_result)}") # 1000
# 实数FFT(更高效)
rfft_result = np.fft.rfft(signal)
print(f"rfft 结果长度: {len(rfft_result)}") # 501
# 频率轴
rfft_freqs = np.fft.rfftfreq(len(signal), d=1/sampling_rate)
print(f"rfft 频率范围: {rfft_freqs[0]:.1f} 到 {rfft_freqs[-1]:.1f} Hz")
# 对比计算结果
print(f"\n幅度对比:")
print(f"fft 第一个值: {np.abs(fft_result[0]):.2f}")
print(f"rfft 第一个值: {np.abs(rfft_result[0]):.2f}")
irfft 函数
irfft 是 rfft 的逆变换:
import numpy as np
# 原始实数信号
t = np.linspace(0, 1, 1000, endpoint=False)
original = np.sin(2 * np.pi * 50 * t)
# rfft -> irfft
rfft_result = np.fft.rfft(original)
reconstructed = np.fft.irfft(rfft_result, n=len(original))
print(f"原始长度: {len(original)}")
print(f"重建长度: {len(reconstructed)}")
print(f"重建误差: {np.max(np.abs(original - reconstructed)):.2e}")
二维 FFT
二维 FFT 常用于图像处理:
fft2 函数
import numpy as np
# 创建一个简单的二维图案
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
# 正弦波图案
image = np.sin(2 * np.pi * X / 3) + np.sin(2 * np.pi * Y / 5)
print(f"图像形状: {image.shape}")
# 二维FFT
fft2d = np.fft.fft2(image)
fft2d_shifted = np.fft.fftshift(fft2d)
# 幅度谱
amplitude_2d = np.abs(fft2d_shifted)
print(f"FFT结果形状: {fft2d.shape}")
print(f"幅度谱最大值: {amplitude_2d.max():.2f}")
# 逆变换
reconstructed = np.fft.ifft2(fft2d)
print(f"重建误差: {np.max(np.abs(image - reconstructed.real)):.2e}")
图像滤波示例
import numpy as np
# 创建带噪声的图像
np.random.seed(42)
x = np.linspace(-5, 5, 128)
y = np.linspace(-5, 5, 128)
X, Y = np.meshgrid(x, y)
clean_image = np.sin(2 * np.pi * X / 3) + np.sin(2 * np.pi * Y / 5)
# 添加高频噪声
noisy_image = clean_image + 0.5 * np.random.randn(*clean_image.shape)
# 频域滤波:去除高频噪声
fft_noisy = np.fft.fft2(noisy_image)
fft_shifted = np.fft.fftshift(fft_noisy)
# 创建低通滤波器
rows, cols = noisy_image.shape
crow, ccol = rows // 2, cols // 2
radius = 30 # 截止频率
Y_grid, X_grid = np.ogrid[:rows, :cols]
mask = (X_grid - ccol)**2 + (Y_grid - crow)**2 <= radius**2
# 应用滤波器
fft_filtered = fft_shifted * mask
# 逆变换
filtered_image = np.fft.ifft2(np.fft.ifftshift(fft_filtered)).real
print("图像滤波结果:")
print(f"原始图像噪声方差: {np.var(noisy_image - clean_image):.4f}")
print(f"滤波后噪声方差: {np.var(filtered_image - clean_image):.4f}")
FFT 的实际应用
应用1:频谱分析
分析信号的频率成分:
import numpy as np
def analyze_spectrum(signal, sampling_rate):
"""分析信号的频谱"""
n = len(signal)
# 计算FFT
fft_result = np.fft.rfft(signal)
frequencies = np.fft.rfftfreq(n, d=1/sampling_rate)
# 计算幅度谱(归一化)
amplitude = np.abs(fft_result) / n * 2
return frequencies, amplitude
# 示例:分析音乐音符
sampling_rate = 44100 # CD音质
duration = 1.0 # 1秒
t = np.linspace(0, duration, int(sampling_rate * duration), endpoint=False)
# A4音符 (440 Hz) + E5音符 (659 Hz)
note_signal = np.sin(2 * np.pi * 440 * t) + 0.7 * np.sin(2 * np.pi * 659 * t)
freqs, amps = analyze_spectrum(note_signal, sampling_rate)
# 找到主要频率
threshold = 0.1
significant = amps > threshold
print("检测到的频率成分:")
for f, a in zip(freqs[significant], amps[significant]):
if f > 0 and f < 2000: # 只显示 2kHz 以下的
print(f" {f:.1f} Hz: 幅度 {a:.3f}")
应用2:快速卷积
利用 FFT 加速大数组的卷积运算:
import numpy as np
import time
def convolve_fft(a, b):
"""使用FFT进行卷积"""
# 计算输出长度
n = len(a) + len(b) - 1
# 找到合适的FFT长度(2的幂次效率最高)
n_fft = 2 ** int(np.ceil(np.log2(n)))
# FFT
fft_a = np.fft.fft(a, n_fft)
fft_b = np.fft.fft(b, n_fft)
# 频域相乘
fft_result = fft_a * fft_b
# IFFT
result = np.fft.ifft(fft_result)[:n].real
return result
# 性能测试
np.random.seed(42)
a = np.random.rand(10000)
b = np.random.rand(1000)
# 使用FFT卷积
start = time.time()
result_fft = convolve_fft(a, b)
time_fft = time.time() - start
# 使用np.convolve
start = time.time()
result_np = np.convolve(a, b, mode='full')
time_np = time.time() - start
print(f"FFT卷积时间: {time_fft:.4f}s")
print(f"np.convolve时间: {time_np:.4f}s")
print(f"结果误差: {np.max(np.abs(result_fft - result_np)):.2e}")
应用3:信号去噪
import numpy as np
def denoise_signal(signal, threshold_ratio=0.1):
"""使用频域滤波去噪"""
# FFT
fft_result = np.fft.fft(signal)
# 计算阈值
amplitude = np.abs(fft_result)
threshold = threshold_ratio * np.max(amplitude)
# 软阈值:去除小的频率分量
mask = amplitude > threshold
fft_denoised = fft_result * mask
# 逆变换
denoised = np.fft.ifft(fft_denoised).real
return denoised
# 创建带噪声的信号
np.random.seed(42)
t = np.linspace(0, 2, 1000, endpoint=False)
clean = np.sin(2 * np.pi * 5 * t) + 0.5 * np.sin(2 * np.pi * 10 * t)
noisy = clean + 0.3 * np.random.randn(len(t))
# 去噪
denoised = denoise_signal(noisy, threshold_ratio=0.05)
print("去噪效果:")
print(f"原始噪声方差: {np.var(noisy - clean):.4f}")
print(f"去噪后方差: {np.var(denoised - clean):.4f}")
应用4:相关性计算
FFT 可用于快速计算相关函数:
import numpy as np
def correlate_fft(a, b):
"""使用FFT计算互相关"""
n = len(a) + len(b) - 1
n_fft = 2 ** int(np.ceil(np.log2(n)))
# FFT(注意b需要共轭)
fft_a = np.fft.fft(a, n_fft)
fft_b = np.fft.fft(b, n_fft)
# 频域相乘(相关是共轭相乘)
fft_result = fft_a * np.conj(fft_b)
# IFFT
result = np.fft.ifft(fft_result)[:n].real
return result
# 示例:信号检测
t = np.linspace(0, 1, 1000, endpoint=False)
template = np.sin(2 * np.pi * 10 * t[:100]) # 模板信号
# 在信号中嵌入模板
signal = np.zeros(1000)
signal[500:600] = template
signal += 0.1 * np.random.randn(1000) # 添加噪声
# 计算相关
correlation = correlate_fft(signal, template)
# 找到最大相关位置
max_pos = np.argmax(correlation)
print(f"模板检测位置: {max_pos} (实际嵌入位置: 500)")
FFT 参数详解
n 参数:输出长度
import numpy as np
signal = np.array([1, 2, 3, 4, 5])
# 默认:输出长度等于输入长度
fft_default = np.fft.fft(signal)
print(f"默认长度: {len(fft_default)}")
# 指定更长的输出(零填充)
fft_padded = np.fft.fft(signal, n=10)
print(f"填充后长度: {len(fft_padded)}")
# 零填充的效果:频谱更密集
freqs_default = np.fft.fftfreq(len(signal))
freqs_padded = np.fft.fftfreq(10)
print(f"\n默认频率间隔: {freqs_default[1] - freqs_default[0]:.4f}")
print(f"填充后频率间隔: {freqs_padded[1] - freqs_padded[0]:.4f}")
norm 参数:归一化方式
import numpy as np
signal = np.array([1, 2, 3, 4])
# 默认(backward):正向变换不归一化,逆向变换除以n
fft_backward = np.fft.fft(signal, norm='backward')
ifft_backward = np.fft.ifft(fft_backward, norm='backward')
# 正交(ortho):正逆变换都除以 sqrt(n)
fft_ortho = np.fft.fft(signal, norm='ortho')
ifft_ortho = np.fft.ifft(fft_ortho, norm='ortho')
# 前向(forward):正向变换除以n,逆向变换不归一化
fft_forward = np.fft.fft(signal, norm='forward')
print("不同归一化方式:")
print(f"backward FFT[0]: {fft_backward[0]:.2f}")
print(f"ortho FFT[0]: {fft_ortho[0]:.2f}")
print(f"forward FFT[0]: {fft_forward[0]:.2f}")
# 验证逆变换
print(f"\nbackward 逆变换验证: {np.allclose(ifft_backward, signal)}")
print(f"ortho 逆变换验证: {np.allclose(ifft_ortho, signal)}")
常见问题与技巧
频率分辨率
频率分辨率由信号长度和采样率决定:
import numpy as np
sampling_rate = 1000 # Hz
duration = 1.0 # 秒
n = int(sampling_rate * duration)
# 频率分辨率
freq_resolution = sampling_rate / n
print(f"频率分辨率: {freq_resolution} Hz")
# 要分辨更接近的频率,需要更长的信号
duration_long = 2.0
n_long = int(sampling_rate * duration_long)
freq_resolution_long = sampling_rate / n_long
print(f"更长时间分辨率: {freq_resolution_long} Hz")
# 或者增加采样率(但不能提高分辨率,只有更长的观测时间才能)
频谱泄漏
当信号不是完整周期时会发生频谱泄漏:
import numpy as np
sampling_rate = 1000
t = np.linspace(0, 1, sampling_rate, endpoint=False)
# 整周期信号(50 Hz 在 1 秒内有 50 个完整周期)
signal_exact = np.sin(2 * np.pi * 50 * t)
# 非整周期信号(50.5 Hz)
signal_leak = np.sin(2 * np.pi * 50.5 * t)
# 对比频谱
fft_exact = np.abs(np.fft.rfft(signal_exact))
fft_leak = np.abs(np.fft.rfft(signal_leak))
freqs = np.fft.rfftfreq(sampling_rate, d=1/sampling_rate)
print("频谱泄漏对比:")
exact_peak_idx = np.argmax(fft_exact)
leak_peak_idx = np.argmax(fft_leak)
print(f"整周期峰值频率: {freqs[exact_peak_idx]:.1f} Hz")
print(f"非整周期峰值频率: {freqs[leak_peak_idx]:.1f} Hz")
# 使用窗函数可以减少泄漏
window = np.hanning(sampling_rate)
signal_windowed = signal_leak * window
fft_windowed = np.abs(np.fft.rfft(signal_windowed))
print(f"加窗后峰值频率: {freqs[np.argmax(fft_windowed)]:.1f} Hz")
窗函数
import numpy as np
# 常用窗函数
n = 100
# 矩形窗(无窗)
rectangular = np.ones(n)
# 汉宁窗
hanning = np.hanning(n)
# 汉明窗
hamming = np.hamming(n)
# 布莱克曼窗
blackman = np.blackman(n)
print("窗函数特性:")
print(f"汉宁窗最大值: {hanning.max():.4f}")
print(f"汉明窗最大值: {hamming.max():.4f}")
print(f"布莱克曼窗最大值: {blackman.max():.4f}")
FFT 函数参考
| 函数 | 说明 |
|---|---|
fft(a) | 一维FFT |
ifft(a) | 一维逆FFT |
fft2(a) | 二维FFT |
ifft2(a) | 二维逆FFT |
fftn(a) | N维FFT |
ifftn(a) | N维逆FFT |
rfft(a) | 实数FFT(只返回正频率) |
irfft(a) | 实数逆FFT |
fftfreq(n) | 返回FFT频率轴 |
rfftfreq(n) | 返回rfft频率轴 |
fftshift(a) | 将零频率移到中心 |
ifftshift(a) | fftshift的逆操作 |
小结
本章介绍了 NumPy 中傅里叶变换的核心功能:
- 基础概念:理解时域与频域的转换关系
- 一维FFT:
fft、ifft、fftfreq的使用 - 实数FFT:
rfft和irfft的高效用法 - 二维FFT:图像处理中的
fft2和ifft2 - 实际应用:频谱分析、快速卷积、信号去噪
- 技巧:频率分辨率、频谱泄漏、窗函数
傅里叶变换是信号处理和科学计算的基础工具,理解其原理和正确使用方法对于数据分析至关重要。
练习
- 分析一段音频信号,找出其中的主频率成分
- 使用 FFT 实现图像的低通滤波和高通滤波
- 比较不同窗函数对频谱泄漏的影响
- 使用 FFT 实现两个大数组的快速卷积
- 实现一个简单的频谱分析仪,显示信号的时域波形和频域频谱