You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决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)}")

关键修改点说明

  1. 频率轴计算:用rfftfreq(n_samples, sample_interval)生成正确的频率轴,确保单位和原始信号的频率单位一致(次/分钟)。
  2. 平滑插值:使用interp1d的三次插值生成平滑的高采样率信号,避免重复采样带来的噪声。
  3. 信号预处理:detrend去除线性趋势,减去均值去除直流分量,让FFT结果更纯净。
  4. 峰值检测优化:设置height和distance参数,过滤掉噪声峰值,只保留真实的信号峰值。

运行这段代码后,你会看到绿色的原始频率点和红色的检测频率点几乎重合,问题应该就解决了!

备注:内容来源于stack exchange,提问作者user25509075

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.16 11:19:38