如何解决Python中使用scipy.fft.fft()后FFT频率与实际频率不符的问题?
如何解决Python中使用scipy.fft.fft()后FFT频率与实际频率不符的问题?
看起来你在做正弦组合信号的FFT分析时遇到了频率匹配不上的问题,我来帮你梳理一下可能的问题点和解决办法。先看看你的问题场景:
你生成了0到860分钟、采样率1样本/分钟的正弦组合信号,为了防混叠插值到30样本/分钟后做FFT,但提取的峰值频率和原始已知频率对不上。先把你的代码补全注释后放出来:
import numpy as np import pandas as pd import matplotlib.pyplot as plt from scipy.signal import detrend, find_peaks from scipy.fft import fft, rfft, fftfreq, rfftfreq, fftshift def make_sample_sin(time_length, max_freq = 15, sampling_rate = 1, num_waves = 3): """ Generates randomized linear combinations of sine waves Args: time_length: Total duration in minutes max_freq: Maximum frequency of generated sine waves sampling_rate: Original sampling rate in samples per minute num_waves: Number of sine waves to combine Returns: time_array: Array of time points combined_signal: Linear combination of sine waves true_frequencies: List of original frequencies used """ time_array = np.linspace(0, time_length, int(time_length * sampling_rate) + 1) true_frequencies = np.random.uniform(0.1, max_freq, num_waves) amplitudes = np.random.uniform(0.5, 2, num_waves) phases = np.random.uniform(0, 2*np.pi, num_waves) combined_signal = np.zeros_like(time_array) for amp, freq, phase in zip(amplitudes, true_frequencies, phases): combined_signal += amp * np.sin(2 * np.pi * freq * time_array + phase) return time_array, combined_signal, true_frequencies
接下来我帮你分析几个最可能导致频率不匹配的原因,以及对应的解决步骤:
1. 频率轴计算错误
这是最常见的问题!使用fftfreq或rfftfreq时,你需要传入采样间隔(而不是采样率)。采样间隔是采样率的倒数,比如插值后采样率是30样本/分钟,那么采样间隔就是1/30分钟/样本。如果这里搞反了,频率轴会直接缩放错误。
2. 插值操作不规范
简单的倍数插值(比如直接把每个点重复30次)会引入额外的高频噪声,而且时间轴的生成要保证总时长不变,采样点均匀分布。推荐用scipy.signal.interp1d做平滑插值,比如三次插值,避免信号失真。
3. 信号预处理不到位
原始信号可能包含直流分量或线性趋势,这些成分在FFT中会产生低频峰值,干扰真实频率的检测。所以FFT前要先去除趋势和直流分量。
4. 峰值检测参数不合理
find_peaks如果不设置合适的高度、距离参数,会把噪声的小峰值也检测出来,导致提取的频率混乱。
修改后的完整代码
我把这些问题都修复了,你可以直接运行看看效果:
import numpy as np import matplotlib.pyplot as plt from scipy.signal import detrend, find_peaks, interp1d from scipy.fft import rfft, rfftfreq def make_sample_sin(time_length, max_freq=15, sampling_rate=1, num_waves=3): """ 生成随机正弦曲线的线性组合 参数: time_length: 总时长(分钟) max_freq: 最大频率(次/分钟) sampling_rate: 原始采样率(样本/分钟) num_waves: 正弦波数量 返回: t: 时间数组 signal: 合成信号 true_freqs: 原始频率列表 """ t = np.linspace(0, time_length, int(time_length * sampling_rate) + 1) # 避免生成0频率的直流分量 true_freqs = np.random.uniform(0.1, max_freq, num_waves) amplitudes = np.random.uniform(0.5, 2, num_waves) phases = np.random.uniform(0, 2*np.pi, num_waves) signal = np.zeros_like(t) # 确保正弦波频率公式正确:freq是次/分钟,t是分钟,所以总周期数是freq*t for amp, freq, phase in zip(amplitudes, true_freqs, phases): signal += amp * np.sin(2 * np.pi * freq * t + phase) return t, signal, true_freqs # 生成原始信号 time_length = 860 t_original, signal_original, true_freqs = make_sample_sin(time_length, max_freq=15, sampling_rate=1, num_waves=3) # 插值到30样本/分钟:生成均匀分布的时间点,用三次插值平滑信号 interp_rate = 30 t_interp = np.linspace(0, time_length, int(time_length * interp_rate) + 1) interp_func = interp1d(t_original, signal_original, kind='cubic') signal_interp = interp_func(t_interp) # 预处理:去除趋势和直流分量 signal_interp = detrend(signal_interp) signal_interp = signal_interp - np.mean(signal_interp) # 计算FFT:用rfft处理实信号更高效,rfftfreq生成正频率轴 n_samples = len(signal_interp) sample_interval = 1 / interp_rate # 采样间隔:分钟/样本 yf = rfft(signal_interp) xf = rfftfreq(n_samples, sample_interval) # 频率单位:次/分钟 # 计算幅度谱 amplitude_spectrum = np.abs(yf) # 检测峰值:只保留幅度超过最大值10%的峰值,且峰值间距至少10个点 peaks, _ = find_peaks(amplitude_spectrum, height=np.max(amplitude_spectrum)*0.1, distance=10) detected_freqs = xf[peaks] # 可视化对比 plt.figure(figsize=(12, 8)) # 原始信号展示 plt.subplot(2, 1, 1) plt.plot(t_original, signal_original) plt.title('原始合成正弦信号') plt.xlabel('时间(分钟)') plt.ylabel('信号幅度') # FFT幅度谱+频率对比 plt.subplot(2, 1, 2) plt.plot(xf, amplitude_spectrum, label='FFT幅度谱') plt.scatter(detected_freqs, amplitude_spectrum[peaks], color='red', s=80, label='检测到的峰值频率') plt.scatter(true_freqs, [np.max(amplitude_spectrum)]*len(true_freqs), color='green', s=80, label='原始真实频率') plt.title('FFT幅度谱与频率对比') plt.xlabel('频率(次/分钟)') plt.ylabel('幅度') plt.legend() plt.xlim(0, 20) # 聚焦0-20次/分钟的频率范围 plt.tight_layout() plt.show() # 打印结果对比 print(f"原始真实频率(次/分钟):{np.round(true_freqs, 2)}") print(f"检测到的峰值频率(次/分钟):{np.round(detected_freqs, 2)}")
关键修改点说明
- 频率轴计算:用
rfftfreq(n_samples, sample_interval)生成正确的频率轴,确保单位和原始信号的频率单位一致(次/分钟)。 - 平滑插值:使用
interp1d的三次插值生成平滑的高采样率信号,避免重复采样带来的噪声。 - 信号预处理:
detrend去除线性趋势,减去均值去除直流分量,让FFT结果更纯净。 - 峰值检测优化:设置
height和distance参数,过滤掉噪声峰值,只保留真实的信号峰值。
运行这段代码后,你会看到绿色的原始频率点和红色的检测频率点几乎重合,问题应该就解决了!
备注:内容来源于stack exchange,提问作者user25509075
相关产品推荐
相关产品推荐

