FFT计算功率谱时峰值幅度与理论值不符的问题及彻底消除差异的方法咨询
FFT计算功率谱时峰值幅度与理论值不符的问题及彻底消除差异的方法咨询
嗨,我来帮你理清这个问题的根源,以及彻底解决的办法~
你遇到的峰值幅度不一致的核心原因是:你的信号频率没有和FFT的频率bin(频率刻度)完全对齐,导致了频谱泄漏。即使加了汉明窗或者提高采样率,只能减少泄漏的程度,但只要频率没落在bin上,不同频率的能量泄漏分布就会有差异,最终峰值幅度还是会不一样。
问题拆解
你的信号是5个余弦波叠加:cos(10t)、cos(20t)...cos(50t),原来的采样时长是2π(约6.283秒),采样点数N=1000。此时FFT的基频(每个bin的频率间隔)是 Fs/N = (N/(2π))/N = 1/(2π) ≈ 0.159Hz。而你的目标频率10、20等,都是这个基频的非整数倍(比如10/0.159≈62.83,不是整数),所以这些频率的能量会分散到相邻的多个bin里,不同频率的泄漏程度不一样,自然峰值幅度有差异。
彻底解决的关键:让频率完全对齐FFT的bin
要让所有目标频率正好落在FFT的频率bin上,需要满足两个条件:
- 采样总时长是所有信号频率周期的整数倍
- 采样点数和时长的关系,使得每个目标频率正好对应FFT的某个整数bin
具体到你的例子:
- 先找所有频率的最大公约数(GCD):10、20、30、40、50的GCD是10Hz,对应的周期是
1/10=0.1秒。 - 设置采样总时长为这个周期的整数倍,比如取5倍(0.5秒)——这样10Hz会有5个完整周期,20Hz有10个,30Hz有15个,以此类推,所有频率都能在采样时长内完成整数次振荡。
- 调整时间轴的生成方式,注意要去掉终点(避免重复采样起始点),保证采样的周期性完整。
- 窗函数的归一化:使用窗函数时,需要对窗的能量进行归一化,避免因窗的能量分布导致幅度差异。
调整后的代码
import numpy as np import scipy.signal import matplotlib.pyplot as plt N = 1000 # 调整采样时长为0.5秒(10Hz周期的5倍,所有频率的整数周期),endpoint=False避免重复采样第一个点 t = np.linspace(0, 0.5, N, endpoint=False) y = np.zeros(N) for freq in [10,20,30,40,50]: y += np.cos(2 * np.pi * freq * t) # 修正余弦函数形式,符合标准频率(Hz)的表达 # 生成汉明窗,并进行能量归一化(除以窗的L2范数) h = scipy.signal.windows.hamming(N) h /= np.linalg.norm(h) # 归一化窗函数,保证不同频率的能量缩放一致 # 计算FFT并归一化 yfft = np.abs(np.fft.fft(y * h)) / N # 功率谱为幅度的平方 yfft_power = yfft ** 2 # 生成正确的频率轴 xfft = np.fft.fftfreq(N, d=t[1]-t[0]) # 排序频率轴以便绘图 sorted_indices = np.argsort(xfft) xfft_sorted = xfft[sorted_indices] yfft_power_sorted = yfft_power[sorted_indices] plt.plot(xfft_sorted, yfft_power_sorted, color="red") plt.xlabel("Frequency (Hz)") plt.ylabel("Power Spectrum") plt.show() # 打印每个频率的峰值幅度 for freq in [10,20,30,40,50]: # 找到最接近目标频率的bin idx = np.argmin(np.abs(xfft - freq)) print(f"freq={freq} magnitude={yfft_power[idx]:.6f}")
关键调整点说明
- 修正了余弦函数的形式:原来的
cos(freq * t)是角频率形式,标准的Hz频率对应的表达式是cos(2πft),这也是之前幅度异常的小原因之一。 - 采样时长调整为0.5秒,确保所有频率都有整数周期,完美对齐FFT的bin。
- 窗函数做了L2归一化,保证不同频率经过窗后的能量缩放一致。
- 使用
np.fft.fftfreq直接生成正确的频率轴,比手动拼接更准确。
运行这段代码后,你会发现所有频率的峰值幅度完全一致,彻底解决了差异问题~
备注:内容来源于stack exchange,提问作者Jihyun




