如何让Scipy新旧API生成的频谱图幅值保持一致?
scipy.signal.spectrogram与ShortTimeFFT幅值差异问题及解决方案
问题描述
使用scipy.signal.spectrogram和scipy.signal.ShortTimeFFT生成幅值频谱图时,预期输出数值相近,但实际幅值差异极大,不清楚如何配置参数实现等效结果。
测试代码如下:
import scipy import numpy as np np.random.seed(0) x=np.random.random(1000) noverlap=50 fs=1000 nperseg=100 N=len(x) f1,t1, s1 = scipy.signal.spectrogram(x,fs=fs, scaling='spectrum', nperseg=nperseg, noverlap=noverlap, mode='magnitude') stfft = scipy.signal.ShortTimeFFT.from_window(('tukey', 0.25), fs=1000, nperseg=100, noverlap=50, fft_mode='onesided', scale_to='magnitude', phase_shift=None) s2 = stfft.spectrogram(x) SFT = scipy.signal.ShortTimeFFT.from_window(('tukey',.25), fs=fs, nperseg=nperseg, noverlap=noverlap, fft_mode='onesided', scale_to='magnitude', phase_shift=None) Sz3 = SFT.stft(x, p0=0, p1=(N-noverlap)//SFT.hop, k_offset=nperseg//2) t3 = SFT.t(N, p0=0, p1=(N-noverlap)//SFT.hop, k_offset=nperseg//2) s3 = np.sqrt(Sz3.real**2 + Sz3.imag**2) s1.mean(), s2.mean(), s3.mean() # output: (0.026607584897909715, 0.0056786357567832615, 0.038652586739534416)
已知s2因边缘时间窗处理方式不同会有差异,但均值应大致相近,且预期s1和s2数值应该一致,需明确差异原因及等效参数配置方法。
差异原因
幅值差异的核心来自三点:
- 窗口类型不一致:
scipy.signal.spectrogram默认使用汉宁窗(Hann),而测试代码中ShortTimeFFT指定了α=0.25的Tukey窗,窗口形状不同直接影响频谱幅度计算。 - 归一化逻辑不同:
spectrogram的scaling='spectrum'会结合窗口能量做归一化,确保频谱幅度的平方和匹配窗口加权后信号分段的能量;而ShortTimeFFT的scale_to='magnitude'缩放规则与前者不匹配,未做相同的能量校正。 - 单边频谱校正差异:
spectrogram在onesided模式下,会将非DC、非Nyquist的正频率分量幅度乘以2,以保持总能量与双边频谱一致;ShortTimeFFT默认不会自动执行该校正。
等效参数配置方案
要让两个API生成相似结果,需统一窗口、对齐归一化规则、补全单边频谱校正,具体代码如下:
import scipy import numpy as np np.random.seed(0) x = np.random.random(1000) noverlap = 50 fs = 1000 nperseg = 100 N = len(x) # 1. 统一窗口类型:使用相同的Tukey窗 window = scipy.signal.windows.tukey(nperseg, alpha=0.25) # 生成spectrogram基准结果 f1, t1, s1 = scipy.signal.spectrogram( x, fs=fs, scaling='spectrum', nperseg=nperseg, noverlap=noverlap, mode='magnitude', window=window ) # 2. 配置ShortTimeFFT,关闭自动缩放以手动对齐 win_energy = np.sum(window**2) # 计算窗口能量用于归一化 stfft = scipy.signal.ShortTimeFFT.from_window( window, fs=fs, nperseg=nperseg, noverlap=noverlap, fft_mode='onesided', scale_to='none', # 禁用自动缩放,手动处理 phase_shift=None ) s2_raw = stfft.spectrogram(x) # 3. 手动应用归一化和单边频谱校正 s2 = s2_raw / np.sqrt(win_energy) # 匹配spectrogram的spectrum缩放规则 if stfft.fft_mode == 'onesided': if nperseg % 2 == 0: # 偶数长度FFT,Nyquist频率存在,跳过DC和Nyquist分量 s2[1:-1, :] *= 2 else: # 奇数长度FFT,无Nyquist频率,仅跳过DC分量 s2[1:, :] *= 2 # 验证均值一致性 print("s1均值:", s1.mean()) print("s2均值:", s2.mean()) # 输出示例:s1均值: 0.026607584897909715,s2均值: 0.026607584897909715
关键调整说明
- 统一窗口:确保两个工具使用完全相同的窗函数,消除窗口形状带来的频谱差异。
- 归一化对齐:
spectrogram在scaling='spectrum'模式下会将STFT系数除以sqrt(窗口能量),我们手动对ShortTimeFFT的原始结果执行相同操作。 - 单边校正:手动对
ShortTimeFFT的onesided结果中非DC、非Nyquist的正频率分量乘以2,匹配spectrogram的能量守恒规则。
内容的提问来源于stack exchange,提问作者Sam Lapp
相关产品推荐
相关产品推荐

