Python 案例详解,如何用 SciPy 做傅里叶变换(附完整代码)
目录导读
-
傅里叶变换是什么?为什么需要用 SciPy?

-
环境准备与核心库安装
-
简单正弦波的频谱分析
-
含噪信号的降噪与特征提取
-
实际音频文件的频谱可视化
-
常见问题与避坑指南(Q&A)
-
SciPy 傅里叶变换的实战要点
傅里叶变换是什么?为什么需要用 SciPy?
傅里叶变换是信号处理领域的基石技术,它能将一个时间域(随时间变化的幅度)的信号,转换到频率域(各频率成分的强度),让我们看到信号里包含哪些“频率分量”,以及各自有多强。
问题一:为什么不用 NumPy 自带的 np.fft?
答:NumPy 提供了基础的 FFT 功能,而 SciPy 的 scipy.fft 模块在接口一致性、支持类型、以及对实际工程场景(如非均匀采样、多维度处理)的覆盖上更全面,尤其对于需要逆变换、带通滤波、希尔伯特变换等进阶操作,SciPy 是更专业的选择,本文案例全部基于 scipy.fft 展开。
环境准备与核心库安装
在开始之前,请确保已安装以下 Python 库:
pip install numpy matplotlib scipy
我们会在代码中用到:
numpy:生成模拟信号、数组运算matplotlib:绘制时域与频域图scipy.fft:核心傅里叶变换函数scipy.io.wavfile:读取音频文件(案例三)
案例一:简单正弦波的频谱分析
1 生成一个 50Hz 与 120Hz 混合的正弦波
import numpy as np
import matplotlib.pyplot as plt
from scipy.fft import fft, fftfreq
# 参数设置
Fs = 1000 # 采样频率 1000 Hz
T = 1.0 / Fs # 采样间隔
L = 1000 # 采样点数
t = np.linspace(0, L*T, L, endpoint=False)
# 合成信号:50Hz + 120Hz 正弦波
f1, f2 = 50, 120
y = 0.7 * np.sin(2 * np.pi * f1 * t) + 1.2 * np.sin(2 * np.pi * f2 * t)
# 绘制时域图
plt.figure(figsize=(12, 4))
plt.plot(t[:100], y[:100]) # 只显示前100个点'时域信号 (混合正弦波)')
plt.xlabel('时间 (秒)')
plt.ylabel('幅度')
plt.grid(True)
plt.show()
2 执行傅里叶变换并绘制频谱
# 执行 FFT
Y = fft(y)
# 计算频率轴(单侧频谱)
N = L // 2
freq = fftfreq(L, T)[:N] # 只取正频率部分
amplitude = 2.0 / L * np.abs(Y[:N])
# 绘制频谱
plt.figure(figsize=(12, 4))
plt.stem(freq, amplitude, basefmt=" ", use_line_collection=True)'单侧幅值频谱')
plt.xlabel('频率 (Hz)')
plt.ylabel('幅值')
plt.grid(True)
plt.xlim(0, 200) # 只看0-200Hz范围
plt.show()
关键解读:
- 频谱图中在 50Hz 和 120Hz 处出现明显尖峰,幅值分别为 0.7 和 1.2,与输入信号完全吻合。
fftfreq生成频率轴,0/L * np.abs(Y[:N])是将幅度归一化为真实幅值的标准做法。
案例二:含噪信号的降噪与特征提取
1 给信号添加随机噪声
# 在上个案例的信号中加入高斯白噪声 np.random.seed(42) noise = 0.5 * np.random.randn(len(y)) y_noisy = y + noise # 绘制含噪信号 plt.figure(figsize=(12, 4)) plt.plot(t[:300], y_noisy[:300], label='含噪信号') plt.plot(t[:300], y[:300], 'r--', label='原始信号', alpha=0.8) plt.legend() plt.grid(True) plt.show()
2 通过频域滤波恢复信号
# 对含噪信号进行 FFT Y_noisy = fft(y_noisy) freq_all = fftfreq(L, T) # 设计带通滤波器:保留 40~130 Hz 的成分(覆盖50和120Hz) filter_mask = (np.abs(freq_all) > 40) & (np.abs(freq_all) < 130) Y_filtered = Y_noisy * filter_mask # 其他频率置零 # 逆变换回到时域 from scipy.fft import ifft y_clean = np.real(ifft(Y_filtered)) # 对比原始与恢复信号 plt.figure(figsize=(12, 4)) plt.plot(t[:200], y[:200], 'k', label='原始信号') plt.plot(t[:200], y_clean[:200], 'g--', label='降噪后信号', linewidth=2) plt.legend() plt.grid(True)'频域滤波降噪效果') plt.show()
效果:降噪后的波形几乎与原始信号重叠,说明通过频域掩膜(带通滤波)成功去除了带外噪声。
案例三:实际音频文件的频谱可视化
1 读取 WAV 文件
from scipy.io import wavfile
# 读取一个实际音频(请替换为自己的文件路径)
# 若没有,可用下面代码生成一个测试音频
sample_rate, audio_data = wavfile.read('example.wav')
# 如果音频是立体声,取单声道
if len(audio_data.shape) == 2:
audio_data = audio_data[:, 0]
# 截取前1秒进行分析
duration = 1 # 秒
num_samples = int(sample_rate * duration)
audio_segment = audio_data[:num_samples]
提示:如果没有现成 wav 文件,可以用
from scipy.io.wavfile import write; write('test.wav', 44100, y_noisy.astype(np.int16))创建。
2 计算并绘制频谱图
# 执行 FFT
Y_audio = fft(audio_segment)
N_audio = len(audio_segment) // 2
freq_audio = fftfreq(len(audio_segment), 1/sample_rate)[:N_audio]
amp_audio = 2.0 / len(audio_segment) * np.abs(Y_audio[:N_audio])
# 绘制
plt.figure(figsize=(12, 4))
plt.semilogy(freq_audio, amp_audio) # 用对数纵轴显示弱信号'音频频谱 (对数幅值)')
plt.xlabel('频率 (Hz)')
plt.ylabel('幅值 (对数)')
plt.grid(True, which='both', linestyle='--')
plt.xlim(0, sample_rate/2) # 奈奎斯特频率
plt.show()
用途:此法可直观分析音乐的人声、乐器频率范围,或检测设备噪声的峰值。
常见问题与避坑指南(Q&A)
Q1:为什么我变换后的幅值不是原始信号的幅值?
A:默认的 fft 结果是未归一化的,需要手动除以采样点数 L,再乘以 2(单侧谱),公式:amplitude = 2.0 / L * np.abs(Y[:N]),对于直流分量(0Hz),幅值应为 0 / L * abs(Y[0])。
Q2:FFT 后频率分辨率是多少?
A:分辨率 = Fs / L,若采样率 1000Hz、采样点 1000,则分辨率为 1Hz,提高分辨率需增加采样点数(即延长采样时间)。
Q3:fftfreq 和 rfftfreq 有何区别?
A:fftfreq 返回完整的正负频率(长度为 L),rfftfreq 只返回正频率(用于实信号的快速计算),一般用 rfftfreq 和 rfft 更高效。
Q4:如何避免频谱泄露?
A:在 FFT 前对信号加窗(如汉宁窗):from scipy.signal import windows; windowed_y = y * windows.hann(L),加窗可减少由于信号非整数周期截断引起的能量扩散。
SciPy 傅里叶变换的实战要点
- 使用
scipy.fft.fft执行正变换,ifft进行逆变换。 - 理解幅值归一化是确保频谱可读性的关键。
- 频域滤波是信号降噪的核心手段,通过构造掩膜(布尔数组)实现。
- 处理真实音频时,注意采样率与奈奎斯特频率(采样率的一半)。
- 结合
fftfreq与windows可大幅提升频谱质量。
掌握了上述案例,你就能处理大多数工程场景下的频域分析了,无论是传感器数据、音频处理还是振动信号,SciPy 的傅里叶工具都能帮你快速“透视”信号的频率组成。