不管是写代码还是做硬件,都经常需要用到滤波这个东西,有高频低频滤波,有高通低通滤波.在这里记录一下我认识滤波的过程
定义
信号系统里有一个基本的核心认识,就是信号是由不同频率成分组成的,任何随时间变化的信号,本质上是由不同频率的正弦波叠加而成

而滤波,则是指在一个信号系统中,根据频率特征,对输入的信号进行过滤和筛选,其根本目的只有两个
- 保留特定频率范围内的成分(我们需要的)
- 抑制或衰减特定频率范围外的成分(所谓的噪声)
通俗理解,假设面前有一个土堆,其构成只有沙子和石块,而你手里有一个筛子,那么原始信号就是一堆混在一起的沙子(高频信号)和石头(低频信号)的土堆,滤波器则是手里的那个筛网.截止频率就是筛网孔的大小.
为什么是频率
此处如果直接理解会很困难,特别是时域和频域,我们引入代码,假设一个干净的信号被高频噪声污染了
import numpy as np
import matplotlib.pyplot as plt
from scipy.fft import fft, fftfreq
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 模拟参数
N = 1000 # 采样点数
T = 1.0 / 200.0 # 采样间隔 200Hz
x = np.linspace(0.0, N*T, N, endpoint=False) # 时间轴
# 假设原始信号:5Hz的正弦波
signal_pure = np.sin(5.0 * 2.0 * np.pi * x)
# 假设噪声信号:50Hz的高频干扰
noise = 0.5 * np.sin(50.0 * 2.0 * np.pi * x)
# 混合信号
y = signal_pure + noise
# 执行傅里叶变换
yf = fft(y)
xf = fftfreq(N, T)[:N//2] # 获取频率轴 (只取正半轴)
amplitude = 2.0/N * np.abs(yf[0:N//2]) # 计算幅度
# 绘图对比
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
# 图1:时域视角
ax1.plot(x, y, 'b')
ax1.set_title('视角一:时域 (随着时间变化的电压)', fontsize=14)
ax1.set_xlabel('时间 (秒)')
ax1.set_ylabel('幅度')
ax1.grid(True)
# 图2:频域视角
ax2.plot(xf, amplitude, 'r-o')
ax2.set_title('视角二:频域 (信号的成分表)', fontsize=14)
ax2.set_xlabel('频率 (Hz)')
ax2.set_ylabel('幅度')
ax2.set_xlim(0, 60)
ax2.grid(True)
# 标注
ax2.annotate('这里是我们要的信号 (5Hz)', xy=(5, 1), xytext=(10, 1.2),
arrowprops=dict(facecolor='black', shrink=0.05), fontsize=12)
ax2.annotate('这里是讨厌的噪声 (50Hz)', xy=(50, 0.5), xytext=(30, 0.7),
arrowprops=dict(facecolor='black', shrink=0.05), fontsize=12)
plt.tight_layout()
plt.show()
执行后得到结果

为什么要转化为在频域里面去看信号,因为在时域中我们采集到的信号是混杂在一起的,我们很难分清楚究竟存在什么信号,而使用傅里叶变换后得到的频域图,就能够很明显地得到.
在时域中,信号 $f(t)$ 仅表现为随时间变化的幅度.虽然直观,但它掩盖了信号的组成成分。
傅里叶变换的核心思想是利用正交基函数,通常是正弦波或复指数函数 $e^{j\omega t}$,将任意信号分解为这些基函数的线性组合。
$$F(\omega) = \int_{-\infty}^{\infty} f(t) e^{-j\omega t} dt$$
频域直接展示了信号包含哪些频率成分(频谱),各成分的能量分布(功率谱密度)以及相位信息,而滤波的本质是对不同频率成分进行选择性衰减或增强.在时域设计一个“去除50Hz工频干扰”的滤波器非常复杂,因为你需要设计一个卷积核(Kernel),使其与信号卷积后恰好抵消特定周期分量.
滤波器的分类
A. 低通滤波器 (Low-Pass Filter, LPF)
- 定义: 允许低于截止频率 $\omega_c$ 的分量通过,衰减高于 $\omega_c$ 的分量。
- 理想数学模型:
$$|H(j\omega)| = \begin{cases} 1, & |\omega| < \omega_c \ 0, & |\omega| > \omega_c \end{cases}$$ - 应用场景: 去除高频噪声、信号平滑、抗混叠滤波(Anti-aliasing)。
B. 高通滤波器 (High-Pass Filter, HPF)
- 定义: 允许高于截止频率 $\omega_c$ 的分量通过,衰减低于 $\omega_c$ 的分量。
- 理想数学模型:
$$|H(j\omega)| = \begin{cases} 0, & |\omega| < \omega_c \ 1, & |\omega| > \omega_c \end{cases}$$ - 应用场景: 去除直流偏置(DC Offset)、提取边缘特征。
C. 带通滤波器 (Band-Pass Filter, BPF)
- 定义: 仅允许特定频段 $\omega_{c1} < \omega < \omega_{c2}$ 内的分量通过。
- 理想数学模型:
$$|H(j\omega)| = \begin{cases} 1, & \omega_{c1} < |\omega| < \omega_{c2} \ 0, & \text{otherwise} \end{cases}$$ - 应用场景: 无线通信中的选频、特定频率信号提取(如提取心电图中的QRS波群能量)。
D. 带阻滤波器 (Band-Stop Filter, BSF) / 陷波器 (Notch Filter)
- 定义: 抑制特定频段 $\omega_{c1} < \omega < \omega_{c2}$ 内的分量,其余频率通过。
- 理想数学模型:
$$|H(j\omega)| = \begin{cases} 0, & \omega_{c1} < |\omega| < \omega_{c2} \ 1, & \text{otherwise} \end{cases}$$ - 应用场景: 去除特定的干扰频率(如去除 50Hz/60Hz 工频干扰)。
实现和应用
讲了一大堆,好像和实际的工程应用没什么关系,下面来讲讲滤波在实际工程中的作用
电路
在模拟电路中,滤波器的物理实现依赖于电容和电感的频率响应特性.电容和电感的阻抗随频率变化,通过与电阻组合构成的分压电路,实现对不同频率成分的选择性衰减
- 电阻 (Resistor): 阻抗恒定,不随频率变化。$$Z_R = R$$
- 电容 (Capacitor): 阻抗随频率增加而减小。$$Z_C = \frac{1}{j\omega C}$$
- 电感 (Inductor): 阻抗随频率增加而增加$$Z_L = j\omega L$$
一个经典的去噪电路,就是利用电容“通高频,阻低频”的特性将高频信号短路到地

根据基尔霍夫定律和分压原理,频域下的输出电压 $V_{out}(j\omega)$ 与输入电压 $V_{in}(j\omega)$ 的关系为:
$$V_{out} = V_{in} \cdot \frac{Z_C}{Z_R + Z_C}$$
代入阻抗公式:
$$H(j\omega) = \frac{V_{out}}{V_{in}} = \frac{\frac{1}{j\omega C}}{R + \frac{1}{j\omega C}}$$
分子分母同乘 $j\omega C$,整理得标准形式:
$$H(j\omega) = \frac{1}{1 + j\omega RC}$$
根据以上这个公式,可以推算出,当$1 = \omega RC$的时候,即为截至频率点,当频率趋近于0,那么电容就视为开路,输出直接传递到输出,反之,电容视为短路,输出被导向地.这就是设计电路的时候为什么需要滤波的原因,电网的交流电经过整流桥后,输出的并非是恒定的直线直流电,而是带有脉动的直流,如果不滤波,电压会周期性跌落至 0V,导致芯片复位,电路无法工作.而开关电源芯片等在工作中也会产生噪声,寄生电感,最后导致电源不干净.不过在实际工程中,一般采用的是纯电容或电容-电感组合滤波
程序
那电路上有滤波,程序上也会有滤波,究竟是怎么想到的?
首先在数学上是定义变化的方式是求导,信号 $y(t)$ 的变化率是其对时间的导数 $\frac{dy}{dt}$,对于正弦信号 $A \sin(\omega t)$,其导数为 $A\omega \cos(\omega t)$,可以看到,频率 $\omega$ 越高,变化率的幅值就越大。那么此时的滤波器其实就是限制变化率.
还是看硬件的一阶RC低通滤波器,根据基尔霍夫电压定律,在一个闭合回路中,所有电压升等于所有电压降之和。 输入电压等于电阻两端的电压 $V_R(t)$ 加上电容两端的电压 $V_C(t)$,于是有
$$x(t) = V_R(t) + y(t)$$
有可知对于电阻 $R$,电压与电流成正比
$$V_R(t) = i(t) \cdot R$$
将此代入 KVL 方程:
$$x(t) = i(t)R + y(t)$$
对于电容,流过它的电流 $i(t)$ 与其两端电压的变化率成正比:
$$i(t) = C \frac{dV_C(t)}{dt}$$
因为输出 $y(t)$ 就是电容电压 $V_C(t)$,所以:
$$i(t) = C \frac{dy(t)}{dt}$$
所以整合方程,可得$$RC \frac{dy(t)}{dt} + y(t) = x(t)$$
在这里定义时间常数 $\tau = RC$,即得到最终的线性常微分方程:
$$\tau \frac{dy(t)}{dt} + y(t) = x(t)$$
观察这个式子可以得知,当前的输出 $y(t)$ 不能立刻等于输入 $x(t)$,它受到自身变化率 $\frac{dy}{dt}$ 的牵制。$\tau$ 越大,变化率对系统的阻碍作用越明显,由于计算机是离散的系统,无法处理连续的$dt$,因此我们可以将导数近似为差分为$$\frac{dy(t)}{dt} \approx \frac{y[n] - y[n-1]}{\Delta t}$$
将差分带入,可得$$\tau \left( \frac{y[n] - y[n-1]}{\Delta t} \right) + y[n] = x[n]$$
我们对现有式子进行简化.首先两边同乘 $\Delta t$:$$\tau (y[n] - y[n-1]) + y[n] \Delta t = x[n] \Delta t$$展开并合并 $y[n]$ 项:$$\tau y[n] - \tau y[n-1] + y[n] \Delta t = x[n] \Delta t$$$$y[n](\tau + \Delta t) = x[n] \Delta t + \tau y[n-1]$$
移项得到最终表达式:
$$y[n] = \frac{\Delta t}{\tau + \Delta t} x[n] + \frac{\tau}{\tau + \Delta t} y[n-1]$$
定义一个系数 $\alpha$(滤波系数):
$$\alpha = \frac{\Delta t}{\tau + \Delta t}$$
那么,$1 - \alpha = \frac{\tau}{\tau + \Delta t}$。
于是:
$$y[n] = \alpha \cdot x[n] + (1 - \alpha) \cdot y[n-1]$$
其中 $0 < \alpha < 1$。这就是低通滤波的公式,本质上这是一个加权平均,$\alpha \cdot x[n]$是表示对当前数据的信任程度,而$(1 - \alpha) \cdot y[n-1]$则表示对历史积累数据的保留程度.如果 $\alpha$ 很小,例如 0.01,那么当前的输出 99% 继承自上一次的输出,只有 1% 来自新的输入.即使输入 $x[n]$ 突然从 0 跳变到 100,输出 $y[n]$ 只会增加1 .结果就是输出曲线变得极其平滑,对突变反应迟钝,高频噪声被大幅削弱。反之,输出紧跟输入变化,滤波效果弱,但反应快.
甚至我们可以进一步推导,根据$\alpha = \frac{\Delta t}{RC + \Delta t}$ 和 $RC = \frac{1}{2\pi f_c}$,于是
$$\alpha = \frac{2\pi f_c \Delta t}{1 + 2\pi f_c \Delta t}$$
当采样频率远大于截止频率 ($f_s \gg f_c$) 时,可以简化为:
$$\alpha \approx 2\pi f_c \Delta t$$
这样就可以在实际工程中,精确算出$\alpha$ 值作为截至频率,但很少用
至于高通滤波,在高通RC电路中,输出电压取自电阻两端 $V_R(t)$。根据 $V_R(t) = i(t)R$ 且 $i(t) = C\frac{d(V_{in} - V_{out})}{dt}$,可推导出输出直接与输入电压的变化率相关,微分方程为:
$$RC \frac{dy(t)}{dt} + y(t) = RC \frac{dx(t)}{dt}$$
注意等式右边是输入信号的导数 $\frac{dx}{dt}$。这意味着只有当输入 $x(t)$ 发生变化时,系统才会有响应。
对其进行离散化,得到高通滤波器的差分方程:
$$y[n] = \alpha \cdot y[n-1] + \alpha \cdot (x[n] - x[n-1])$$
应用及代码
低通滤波
#include <stdio.h>
#include <stdlib.h>
typedef struct {
float alpha;//滤波系数
float y_prev;//上一次输出值
} LPF_Handle_t;
void LPF_Init(LPF_Handle_t *filter, float alpha) {
filter->alpha = alpha;
filter->y_prev = 0.0f;
}
float LPF_Update(LPF_Handle_t *filter, float x_input) {
float y_out = filter->alpha * x_input + (1.0f - filter->alpha) * filter->y_prev;
filter->y_prev = y_out;
return y_out;
}//核心算法实现,就是加权平均,然后输出计算后的值
int main() {
LPF_Handle_t lpf;
float alpha = 0.1f;
float signal_ideal = 10.0f;
float noise;
float input;
float output;
LPF_Init(&lpf, alpha);
printf("Index, Input, Output\n");
for (int i = 0; i < 20; i++) {
noise = ((float)rand() / RAND_MAX) * 4.0f - 2.0f;
input = signal_ideal + noise;
output = LPF_Update(&lpf, input);
printf("%d, %.4f, %.4f\n", i, input, output);
}
return 0;
}
高通滤波
#include <stdio.h>
typedef struct {
float alpha;
float y_prev;
float x_prev;
} HPF_Handle_t;
//高通滤波不仅关注下一次输入,也关注上一次输入
void HPF_Init(HPF_Handle_t *filter, float alpha) {
filter->alpha = alpha;
filter->y_prev = 0.0f;
filter->x_prev = 0.0f;
}
float HPF_Update(HPF_Handle_t *filter, float x_input) {
float y_out = filter->alpha * (filter->y_prev + x_input - filter->x_prev);
filter->x_prev = x_input;
filter->y_prev = y_out;
return y_out;
}
int main() {
HPF_Handle_t hpf;
float alpha = 0.8f;
float input;
float output;
HPF_Init(&hpf, alpha);
float data[] = {5.0, 5.0, 5.0, 5.0, 10.0, 10.0, 5.0, 5.0};
printf("Input, Output\n");
for (int i = 0; i < 8; i++) {
input = data[i];
output = HPF_Update(&hpf, input);
printf("%.2f, %.2f\n", input, output);
}
return 0;
}
评论区