侧边栏壁纸
博主头像
Embedded General Store 博主等级

长安乐,多喜宁

  • 累计撰写 29 篇文章
  • 累计创建 3 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

滤波器认识和理解

Administrator
2025-12-31 / 0 评论 / 0 点赞 / 7 阅读 / 0 字

不管是写代码还是做硬件,都经常需要用到滤波这个东西,有高频低频滤波,有高通低通滤波.在这里记录一下我认识滤波的过程


定义

信号系统里有一个基本的核心认识,就是信号是由不同频率成分组成的,任何随时间变化的信号,本质上是由不同频率的正弦波叠加而成
image.png
而滤波,则是指在一个信号系统中,根据频率特征,对输入的信号进行过滤和筛选,其根本目的只有两个

  • 保留特定频率范围内的成分(我们需要的)
  • 抑制衰减特定频率范围外的成分(所谓的噪声)
    通俗理解,假设面前有一个土堆,其构成只有沙子和石块,而你手里有一个筛子,那么原始信号就是一堆混在一起的沙子(高频信号)和石头(低频信号)的土堆,滤波器则是手里的那个筛网.截止频率就是筛网孔的大小.

为什么是频率

此处如果直接理解会很困难,特别是时域和频域,我们引入代码,假设一个干净的信号被高频噪声污染了

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()

执行后得到结果
image.png
为什么要转化为在频域里面去看信号,因为在时域中我们采集到的信号是混杂在一起的,我们很难分清楚究竟存在什么信号,而使用傅里叶变换后得到的频域图,就能够很明显地得到.
在时域中,信号 $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$$
    一个经典的去噪电路,就是利用电容“通高频,阻低频”的特性将高频信号短路到地
    licensed-image.jpg

根据基尔霍夫定律和分压原理,频域下的输出电压 $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;
}
0

评论区