@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 | 不改变高度,只改变点数 |
| 功率谱 | 频率对应的功率 | V² | 数值随频率分辨率改变 |
| 功率谱密度 | 每 Hz 的功率 | V²/Hz 或 dB/Hz | 近似不变,反映真实密度 |
| 记住: |
-
想看振幅大小 → 用 幅度谱。
-
想看能量分布、比较不同长度信号 → 用 功率谱密度。
-
功率谱是中间产物,工程上常用 PSD 替代。
你可以把上面代码里的 f1, A1, f2, A2, noise_amp 改成别的数值试试,几个图会立刻反映出变化。