频谱、功率谱和功率谱密度

@toc频谱、功率谱和功率谱密度

所有代码都可以直接复制到 Python 环境里运行。

一、基本概念

我们有一个时域信号,比如一段随时间变化的电压:

  • 频谱(幅度谱)

    告诉我们:信号里有哪些频率成分,每个频率的振幅有多大。

    比如"50 Hz的正弦波振幅是1,120 Hz的正弦波振幅是0.5"。

  • 功率谱

    告诉我们:每个频率成分的功率(与振幅的平方成正比)有多大。

    物理上功率 ≈ 振幅²,所以功率谱 ≈ 频谱的平方。它不关心相位,只看能量大小。

  • 功率谱密度(PSD)

    与功率谱类似,但它把功率分摊到单位频率(每Hz)上。

    当你改变频率分辨率时,功率谱的数值会变,而功率谱密度近似不变,更"物理"。通常用 dB/Hz 表示。

一句话对比:

频谱(幅度)→ 振幅大小;功率谱 → 功率大小;功率谱密度 → 每Hz含有多少功率。

二、生成仿真信号

我们构造一个 1 秒长的信号,采样率 1000 Hz,包含:

  • 50 Hz 正弦波,振幅 1

  • 120 Hz 正弦波,振幅 0.5

  • 一些高斯白噪声

    import numpy as np
    import matplotlib.pyplot as plt
    from scipy.signal import periodogram

    参数设置

    fs = 1000 # 采样频率 1000 Hz
    T = 1.0 # 时长 1 秒
    N = int(fs * T) # 总采样点数
    t = np.linspace(0, T, N, endpoint=False) # 时间轴

    构造信号:两个正弦波 + 噪声

    f1, A1 = 50, 1.0 # 频率50Hz,振幅1
    f2, A2 = 120, 0.5 # 频率120Hz,振幅0.5
    noise_amp = 0.8 # 噪声标准差
    signal = A1 * np.sin(2 * np.pi * f1 * t)
    + A2 * np.sin(2 * np.pi * f2 * t)
    + noise_amp * np.random.randn(N)

三、计算频谱、功率谱、功率谱密度

复制代码
# ---- 1. 频谱(幅度谱)----
# 用 FFT 计算,并做归一化让峰值等于正弦波的振幅
fft_vals = np.fft.fft(signal)                # 复数FFT
freqs = np.fft.fftfreq(N, 1/fs)              # 频率轴

# 取正频率部分(单边谱)
positive_freqs = freqs[:N//2]
# 幅度谱:取绝对值,乘以2(单边能量),再除以N
magnitude = (2 / N) * np.abs(fft_vals[:N//2])

# ---- 2. 功率谱(|FFT|^2,未归一化为密度)----
# 直接取FFT模的平方,同样处理单边
power_spectrum = (2 / N**2) * np.abs(fft_vals[:N//2])**2

# ---- 3. 功率谱密度(PSD)----
# 使用周期图法,自动完成:分段 / 加窗 / 归一化到 V^2/Hz
f_psd, psd = periodogram(signal, fs, scaling='density')
# 转换成 dB/Hz 方便观察
psd_db = 10 * np.log10(psd)

为什么有不同的归一化?

  • 幅度谱 2*|FFT|/N:让正弦波的峰高刚好等于它的振幅(1.0 和 0.5)。

  • 功率谱 2*|FFT|²/N²:让正弦波的峰高等于振幅²(1.0 和 0.25)。

  • 功率谱密度(PSD):periodogram 把总功率分摊到 Hz,使得噪声基底不会随采样点数的变化而大幅改变。

四、画图对比

复制代码
plt.figure(figsize=(12, 10))

# 子图1:原始信号(只画前200个点,否则太密)
plt.subplot(4, 1, 1)
plt.plot(t[:200], signal[:200])
plt.title('原始信号(前 0.2 秒)')
plt.xlabel('时间 (s)')
plt.ylabel('幅度')

# 子图2:幅度谱
plt.subplot(4, 1, 2)
plt.plot(positive_freqs, magnitude)
plt.title('频谱(幅度谱)')
plt.xlabel('频率 (Hz)')
plt.ylabel('振幅')
plt.xlim(0, 200)   # 只看0~200 Hz
plt.grid(True)

# 子图3:功率谱
plt.subplot(4, 1, 3)
plt.plot(positive_freqs, power_spectrum)
plt.title('功率谱(|FFT|² 未归一化密度)')
plt.xlabel('频率 (Hz)')
plt.ylabel('功率 (幅度²)')
plt.xlim(0, 200)
plt.grid(True)

# 子图4:功率谱密度(PSD),单位 dB/Hz
plt.subplot(4, 1, 4)
plt.plot(f_psd, psd_db)
plt.title('功率谱密度(PSD)')
plt.xlabel('频率 (Hz)')
plt.ylabel('功率谱密度 (dB/Hz)')
plt.xlim(0, 200)
plt.grid(True)

plt.tight_layout()
plt.show()

图形解读:

  • 原始信号:看不出频谱,只能看到波形。

  • 幅度谱:在 50 Hz 处有一个尖峰,高度≈1.0;在 120 Hz 处高度≈0.5;其他地方是噪声引起的低矮"草"。

  • 功率谱:同样 50 Hz≈1.0²=1;120 Hz≈0.5²=0.25。数值变化更"剧烈"。

  • 功率谱密度:纵轴是 dB/Hz,峰值位置相同,但整个曲线的"基底"变得平坦且不随采样点数变化。噪声整体呈一条水平线(白噪声的特点)。

五、核心区别小结

名称 物理意义 单位(若信号是V) 与频率分辨率的关系
频谱(幅度) 频率对应的振幅 V 不改变高度,只改变点数
功率谱 频率对应的功率 数值随频率分辨率改变
功率谱密度 每 Hz 的功率 V²/Hz 或 dB/Hz 近似不变,反映真实密度
记住:
  • 想看振幅大小 → 用 幅度谱。

  • 想看能量分布、比较不同长度信号 → 用 功率谱密度。

  • 功率谱是中间产物,工程上常用 PSD 替代。

你可以把上面代码里的 f1, A1, f2, A2, noise_amp 改成别的数值试试,几个图会立刻反映出变化。