You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何从频谱图计算功率谱密度?代码与Welch法差异的疑问

嘿,我来帮你拆解这个问题——从频谱图算功率谱密度(PSD),还有你代码里那些除以2、除以nperseg的疑惑,咱们一步步捋清楚:

从频谱图计算PSD的核心逻辑

首先得明确:频谱图(比如你用scipy.signal.specgram生成的Sxx)说白了就是短时傅里叶变换(STFT)的幅度平方,是信号在“时间-频率”维度的能量分布;而PSD是信号功率随频率的整体分布,所以核心操作是把频谱图的时间轴做平均,再做正确的能量归一化。

先补全你提到的代码片段(应该是用specgram生成频谱图):

import numpy as np
from scipy import signal as si

fs = 10e3
N = int(1e5)
amp = 2*np.sqrt(2)
freq = 1234.0
noise_power = 0.001 * fs / 2
time = np.arange(N) / fs
x = amp*np.sin(2*np.pi*freq*time)
x += np.random.normal(scale=np.sqrt(noise_power), size=time.shape)

# Welch法计算PSD
_, pxx_welch = si.welch(x, fs, nperseg=1024)

# 生成频谱图
f, t, Sxx = si.specgram(x, fs=fs, nperseg=1024)

为啥你的结果和Welch法差这么大?

Welch法本质是优化版的周期图法:它自动把信号分段、加窗、计算每段的周期图,再做平均,还悄悄处理了窗函数带来的能量损失。而你直接转PSD时,大概率没做这几个关键步骤:

  • 没对频谱图的时间轴取平均(频谱图是时频图,PSD是纯频域图,得丢掉时间维度)
  • 没修正窗函数导致的能量损耗
  • 归一化的方式不对(比如没考虑采样频率、分段长度的影响)

正确从频谱图转PSD的步骤

按照下面的代码调整,你得到的结果就会和Welch法几乎一致:

# 1. 对时间轴取平均:每个频率点取所有时间帧的平均功率
psd_from_spec = np.mean(Sxx, axis=1)

# 2. 修正窗函数的能量损失(specgram默认用汉宁窗,得把窗的能量影响消掉)
window = si.get_window('hann', 1024)
window_correction = np.sum(window**2) / len(window)
psd_from_spec /= window_correction

# 3. 归一化到PSD的标准单位(W/Hz):把能量转换为功率密度
psd_from_spec /= (fs * len(window))

拆解你代码里的除以2和nperseg

假设你之前的代码里有类似psd = Sxx.mean(axis=1)/(2*nperseg)的操作,咱们来唠唠这俩数的作用:

  • 除以npersegnperseg是STFT的分段长度(每帧的样本数)。Sxx是STFT的幅度平方,单位是能量,除以nperseg就得到每帧的平均功率,再对时间平均就是整个信号的平均功率。
  • 除以2:这是针对实信号的双边频谱的修正——实信号的频谱是左右对称的,正频率和负频率的能量一模一样,所以只取正频率部分时,要把能量乘以2(直流和Nyquist分量除外)。但!specgram默认返回的是单边频谱(频率从0到fs/2),这时候它已经帮你做了乘以2的操作,你再除以2就会直接让功率减半,这也是你和Welch结果偏差大的核心原因之一!

关键差异总结

你的代码和Welch法的核心区别在于:

  • Welch法自动处理了窗函数的能量修正,你没做
  • 归一化的细节没对齐(比如是否正确转换到W/Hz单位)
  • 错误地在单边频谱上除以2,导致功率被人为减半

按照上面的正确步骤调整后,你从频谱图得到的PSD就会和Welch法的结果几乎重合啦。

内容的提问来源于stack exchange,提问作者user307380

火山引擎 最新活动