如何缩放scipy.signal.stft输出以满足Parseval能量守恒要求
能量偏差的核心原因
- scipy内置STFT的缩放逻辑是为谱分析场景设计的:默认
scaling='spectrum'模式下,代码会自动除以窗的求和值,让STFT输出的幅度和单块时域正弦的幅度直接对齐,同时单边谱模式会自动给非直流、非奈奎斯特的频点乘2,把双边谱的能量合并到单边输出,这和Parseval定理要求的能量守恒缩放逻辑完全不同 - 50%重叠的汉宁窗存在双计数偏差:相邻帧的重叠区域会被两次纳入窗加权范围,没有做重叠域归一的话,直接加总时频点能量会引入固定比例的偏差
- 手动推导的缩放系数没有考虑单边谱的2倍因子和重叠效应,只是简单用窗的和做归一,无法匹配能量
最可靠的能量匹配方案
不需要手动反推缩放公式,利用scipy STFT/ISTFT的完美可逆特性做校准即可:
汉宁窗50%重叠是经典的COLA(常数重叠相加)窗,满足完美重建条件,也就是说用相同窗参数调用scipy.signal.istft(Zxx)得到的重建信号,和原始输入信号的平方和完全相等,没有重建误差。
具体步骤:
- 按照原有参数调用STFT得到Zxx
- 用相同参数调用istft得到重建信号
x_rec,计算两个基准值:t_rec, x_rec = scipy.signal.istft(Zxx, fs, window=window, nperseg=frame_size) E_t = np.sum(x**2) E_rec = np.sum(x_rec**2) - 因为STFT到重建信号是线性变换,给Zxx乘上固定的缩放系数后,时频域总能量就会和时域完全匹配,最终计算的时频域能量和时域能量偏差会小于0.1%,完全满足能量守恒验证的要求:
Zxx_scaled = Zxx * np.sqrt(E_rec / np.sum(np.abs(Zxx)**2)) Ef = np.sum(np.abs(Zxx_scaled)**2)
固定参数下的简化缩放公式
如果固定使用当前参数(1000点非对称汉宁窗、50%重叠、单边谱),不需要每次做ISTFT校准,可以直接用推导好的系数:
- 时频域总能量折算公式为
Ef = np.sum(np.abs(Zxx)**2) * (np.sum(win)**2 / (2 * np.sum(win**2)) * hop / nperseg) - 代入当前测试参数(win为1000点hann窗,hop=500,nperseg=1000),计算得到的折算系数约为166.7,直接用
sum(abs(Zxx)**2)*166.7就可以和时域的sum(x**2)匹配
原测试代码的问题点
- 缩放系数方向搞反了:之前使用的
scale = np.sqrt(1/(sum(window)**2))是对Zxx做反向缩放,实际应该乘对应的归一因子 - 强行除以总信号长度N没有理论依据:重叠帧的能量折算和整段信号长度无关,只和窗的平方和、帧移有关
- 没有考虑单边谱的2倍乘值,导致能量计算时少算了一半的双边谱贡献
内容的提问来源于stack exchange,提问作者Daan Ratering
相关产品推荐
相关产品推荐

