信号处理之频域分析

2026-07-09 23:53:11

既然我们已经聊了 [[信号处理之清洗滤波]](时域上的平均),今天我们要升级维度。我要带你进入信号处理的**“上帝视角”——频域分析 (Frequency Domain Analysis)。

我们要聊聊两个核心概念:FFT (快速傅里叶变换) 和 数字陷波器 (Notch Filter)。这能解决一个让无数工程师抓狂的问题:“我知道信号里有干扰,但我怎么知道干扰是从哪来的?我又该怎么精准地干掉它?”

第一章:从“看波形”到“看成分” —— FFT 的魔法

我们之前的示波器或绘图,都是“时域” (Time Domain) 的。

X 轴: 时间 (Time)

Y 轴: 电压 (Amplitude)

看到的是: 一条乱糟糟的曲线。

但傅里叶(Fourier)告诉我们:任何复杂的波形,都可以拆解成无数个正弦波的叠加。

FFT (Fast Fourier Transform) 就是那个拆解波形的“棱镜”。

X 轴: 频率 (Frequency)

Y 轴: 强度 (Magnitude)

看到的是: 信号的“配方表”**。

1. 生活中的类比:果汁与配方

时域信号: 一杯混合果汁(你只能喝出它酸酸甜甜,看到它是红色的)。

FFT 变换: 一张配方表。

草莓:50%

香蕉:30%

苦瓜:5%(这就是干扰!)

2. CEMS 里的侦探故事

假设你的 $\text{SO}_2$ 读数一直在抖动,幅度 $\pm 1$ ppm。你用滤波怎么也滤不掉。

你对数据做了一个 FFT 分析,发现频谱图上有一根尖刺:

案例 A:尖刺在 50Hz。

破案: 50Hz 是市电频率。说明你的信号线没有屏蔽好,或者是电源地线没接好,工频干扰串进来了。

案例 B:尖刺在 30Hz。

破案: 你的切光轮电机转速正好是 1800rpm ($1800/60 = 30Hz$)。说明切光轮震动太大,引起了光路抖动。

案例 C:尖刺在 0.5Hz。

破案: 你的采样泵是不是每 2 秒钟“喘”一次气?这是蠕动泵或隔膜泵的脉动干扰。

结论: 在时域上,它们都是“乱跳”;但在频域上,它们都有“指纹”。FFT 是诊断仪器故障的神器。

第二章:手术刀式的切割 —— 数字陷波器 (Notch Filter)

当你用 FFT 找到了那个讨厌的 50Hz 工频干扰,下一步怎么办?

用普通的低通滤波(求平均)?

缺点: 为了滤掉 50Hz,你会把 1Hz、2Hz 的有用信号也压制住,导致响应变慢。

这时候,你需要一把“激光手术刀”,只切除 50Hz 这一块肉,保留其他所有部分。 这就是 带阻滤波器 (Band-stop Filter),如果阻带极窄,我们叫它 陷波器 (Notch Filter)。

原理: 在数学上设计一个公式,让信号通过时,唯独 50Hz 的正弦波会被完全抵消(增益为 0),而 10Hz 或 100Hz 的信号无损通过。

第三章:Python 视觉盛宴 —— 抓出干扰并消灭它

我们来模拟一个真实的 CEMS 事故现场:

真实信号: 一个缓慢变化的 $\text{SO}_2$ 浓度。

干扰源: 50Hz 的强烈工频干扰(电源没接好)。

任务: 用 FFT 揪出它,然后用陷波器干掉它。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

import numpy as np

import matplotlib.pyplot as plt

from scipy.fft import fft, fftfreq

from scipy import signal

# 配置中文

plt.rcParams['font.sans-serif'] = ['SimHei']

plt.rcParams['axes.unicode_minus'] = False

# ==========================================

# 1. 制造案发现场 (生成信号)

# ==========================================

fs = 1000 # 采样率 1000Hz (每秒采1000个点)

T = 2.0 # 总时长 2秒

N = int(fs * T) # 总点数

t = np.linspace(0, T, N, endpoint=False)

# 真实信号:一个 2Hz 的缓慢波动 (模拟气体浓度变化)

sig_clean = 1.0 * np.sin(2 * np.pi * 2 * t)

# 干扰信号:50Hz 的强噪音 (模拟工频干扰)

noise_50hz = 0.5 * np.sin(2 * np.pi * 50 * t)

# 白噪声:随机背景噪音

noise_white = np.random.normal(0, 0.2, N)

# 混合在一起:这就是 ADC 读到的烂数据

sig_corrupted = sig_clean + noise_50hz + noise_white

# ==========================================

# 2. 侦探环节 (FFT 频谱分析)

# ==========================================

yf = fft(sig_corrupted) # 做傅里叶变换

xf = fftfreq(N, 1 / fs) # 计算频率轴

# 我们只看前半部分 (正频率)

yf_abs = np.abs(yf[:N//2]) * 2 / N

xf_axis = xf[:N//2]

# ==========================================

# 3. 手术环节 (IIR 陷波器设计)

# ==========================================

# 设计一个针对 50Hz 的陷波器

# 质因数 Q 决定了切口有多窄。Q=30 表示切得很窄,不伤及无辜。

b, a = signal.iirnotch(w0=50.0, Q=30.0, fs=fs)

# 应用滤波器

sig_filtered = signal.filtfilt(b, a, sig_corrupted)

# ==========================================

# 4. 绘图展示

# ==========================================

plt.figure(figsize=(12, 10))

# 图1:时域波形对比 (乱糟糟 vs 干净)

plt.subplot(3, 1, 1)

plt.plot(t[:500], sig_corrupted[:500], 'r-', alpha=0.5, label='受污染的信号 (ADC原始)')

plt.plot(t[:500], sig_clean[:500], 'k--', linewidth=2, label='真实信号 (理想)')

plt.title("【时域】 案发现场:根本看不清真实信号", fontsize=14)

plt.legend(loc='upper right')

plt.grid(True)

# 图2:频域频谱 (破案证据)

plt.subplot(3, 1, 2)

plt.plot(xf_axis, yf_abs, 'b-')

plt.xlim(0, 100) # 只看 0-100Hz

plt.title("【频域】 侦探视角:FFT 抓到了!50Hz 处有巨大尖峰", fontsize=14)

plt.xlabel("频率 (Hz)")

plt.ylabel("强度 (Amplitude)")

plt.axvline(50, color='r', linestyle='--', label='发现 50Hz 干扰')

plt.axvline(2, color='g', linestyle='--', label='发现 2Hz 有用信号')

plt.legend()

plt.grid(True)

# 图3:滤波后效果 (手术成功)

plt.subplot(3, 1, 3)

plt.plot(t[:500], sig_filtered[:500], 'g-', linewidth=2, label='陷波器处理后')

plt.plot(t[:500], sig_clean[:500], 'k--', alpha=0.5, label='真实信号')

plt.title("【结果】 手术后:精准切除 50Hz,保留有用信号", fontsize=14)

plt.legend(loc='upper right')

plt.grid(True)

plt.tight_layout()

plt.show()

运行结果解读

当你运行这段代码,你会看到三张图,讲了一个完整的故事:

第一张图(时域): 你会看到红色的线毛毛糙糙的,上面叠加了密密麻麻的锯齿。你很难看清那条原本平滑的正弦波。这就是我们在现场看到的原始数据。

第二张图(频域 - 上帝视角): 这是最震撼的一张。

在 2Hz 的地方有一根柱子(这是我们要的信号)。

在 50Hz 的地方有一根巨大的红柱子。

真相大白: 原来让信号变脏的罪魁祸首就是 50Hz!

第三张图(滤波后): 绿色的线(处理后)和黑色的虚线(真实值)几乎完美重合。

注意:我们并没有用强力的平均滤波(那样会让波形滞后),我们只是“挖掉”了 50Hz。所以信号的反应速度丝毫没有变慢,但毛刺全没了。

总结

FFT (傅里叶变换): 是诊断工具。它不改变信号,但能告诉你信号里有什么鬼(干扰源频率)。

陷波器 (Notch Filter): 是手术工具。当你用 FFT 确诊后,用陷波器可以精准切除特定频率的干扰,而不影响信号的灵敏度。