4、时域滤波基础:滑动平均滤波、中值滤波、低通滤波在传感器数据去噪中的应用

各位同学,欢迎来到第四章。

这一章我们聊聊时域滤波。说白了,就是怎么把传感器采集到的「脏数据」洗干净。

我在可穿戴设备行业摸爬滚打这些年,处理过最多的就是加速度计和陀螺仪的数据。你想想看,一个手环戴在手腕上,走路、甩手、打字,甚至挠痒痒,这些动作都会叠加到真正的运动信号上。如果不做滤波,那数据简直没法看。

4.1 为什么需要时域滤波?

传感器数据天生带噪。原因很多:

  • 电路噪声:芯片内部的热噪声、量化噪声
  • 运动伪迹:佩戴者无意识的抖动
  • 环境干扰:电磁干扰、温度漂移

我遇到过最头疼的一次,是给一款老人跌倒检测手环做算法。传感器明明检测到了剧烈冲击,但一分析发现是老人拍桌子吓猫。嗯,这就是典型的噪声干扰。

所以,滤波不是可选项,是必选项。

4.2 滑动平均滤波

这是最直观的滤波方法。原理很简单:取最近 N 个采样点的平均值,作为当前输出。

公式长这样:

y[n] = (x[n] + x[n-1] + ... + x[n-N+1]) / N

其中 N 是窗口大小。

4.2.1 代码实现

// 滑动平均滤波,窗口大小 N
#define WINDOW_SIZE 5

float buffer[WINDOW_SIZE] = {0};
int index = 0;
float sum = 0;

float moving_average(float new_sample) {
    sum -= buffer[index];          // 减去最旧的数据
    buffer[index] = new_sample;    // 存入新数据
    sum += buffer[index];          // 加上新数据
    index = (index + 1) % WINDOW_SIZE;  // 循环索引
    return sum / WINDOW_SIZE;
}
我的经验:窗口大小 N 的选择很关键。N 越大,平滑效果越好,但延迟也越大。在步数检测中,我一般用 N=4 或 N=8,既能滤掉高频抖动,又不会把真正的步伐信号抹平。

4.2.2 优缺点

优点 缺点
实现简单,计算量小 对脉冲噪声敏感
实时性好,适合嵌入式 会引入相位延迟
能有效抑制高频噪声 窗口大小需要手动调参

我曾经在一个心率监测项目中,用了滑动平均滤波。结果发现,当用户突然加速跑步时,心率值变化跟不上。为什么?因为窗口太大,信号被「拖」住了。后来我把窗口从 16 降到 8,问题就解决了。

4.3 中值滤波

中值滤波的思路更暴力:取窗口内所有数据的中间值。

它对脉冲噪声(比如突然的尖峰)有奇效。

4.3.1 代码实现

// 中值滤波,窗口大小 N(建议奇数)
#define MEDIAN_WINDOW 5

float median_filter(float new_sample) {
    static float window[MEDIAN_WINDOW] = {0};
    static int idx = 0;
    float temp[MEDIAN_WINDOW];
    
    // 更新窗口
    window[idx] = new_sample;
    idx = (idx + 1) % MEDIAN_WINDOW;
    
    // 拷贝并排序
    memcpy(temp, window, sizeof(temp));
    // 冒泡排序(窗口小,够用)
    for (int i = 0; i < MEDIAN_WINDOW - 1; i++) {
        for (int j = 0; j < MEDIAN_WINDOW - 1 - i; j++) {
            if (temp[j] > temp[j+1]) {
                float t = temp[j];
                temp[j] = temp[j+1];
                temp[j+1] = t;
            }
        }
    }
    // 返回中位数
    return temp[MEDIAN_WINDOW / 2];
}
注意:中值滤波需要排序,计算量比滑动平均大。在低功耗 MCU 上,窗口大小建议不超过 9。我曾经在 STM32L0 上跑窗口 7 的中值滤波,每秒钟处理 100 个采样点,CPU 占用率还能接受。

4.3.2 适用场景

  • 加速度计数据:去除突然的冲击噪声(比如手环磕到桌子)
  • 气压计数据:消除风噪引起的尖峰
  • 陀螺仪数据:滤除瞬间的抖动

我个人习惯,在可穿戴设备中,中值滤波和滑动平均滤波经常搭配使用。先过一遍中值滤波去掉毛刺,再过一遍滑动平均平滑曲线。效果比单独用任何一种都好。

4.4 低通滤波

低通滤波,说白了就是让低频信号通过,衰减高频信号。

在嵌入式领域,最常用的是一阶 RC 低通滤波的离散形式。公式很简单:

y[n] = α * x[n] + (1 - α) * y[n-1]

其中 α 是滤波系数,范围 0~1。α 越小,滤波越强,但响应越慢。

4.4.1 代码实现

// 一阶低通滤波
typedef struct {
    float alpha;    // 滤波系数
    float output;   // 上一次输出
} LowPassFilter;

void lowpass_init(LowPassFilter *f, float alpha) {
    f->alpha = alpha;
    f->output = 0;
}

float lowpass_update(LowPassFilter *f, float input) {
    f->output = f->alpha * input + (1 - f->alpha) * f->output;
    return f->output;
}

4.4.2 α 怎么选?

α 和截止频率 f_c 的关系:

α = 2π * f_c * T_s

其中 T_s 是采样周期。

举个例子:采样率 100Hz,想滤掉 5Hz 以上的信号:

α = 2 * 3.14 * 5 * 0.01 ≈ 0.314
避坑指南:我曾经在做一个睡眠监测手环时,把 α 设得太小(0.01),结果呼吸信号被严重衰减,波形几乎变成一条直线。后来把 α 调到 0.1,才看到清晰的呼吸起伏。记住,滤波不是越强越好,要保留有效信号。

4.5 三种滤波器的对比

滤波器 适用噪声类型 计算量 延迟 典型应用
滑动平均 高斯白噪声 步数检测、心率平滑
中值滤波 脉冲噪声 加速度计去毛刺
低通滤波 高频噪声 极低 姿态解算、呼吸检测

4.6 实战建议

最后,我给大家几个实战中的小建议:

  1. 先分析噪声类型:是随机噪声还是脉冲噪声?这决定了选哪种滤波器。
  2. 参数要可调:在产品开发阶段,把窗口大小、α 值做成可配置的。我习惯在 Flash 里存一份默认参数,调试时通过串口修改。
  3. 注意边界条件:设备刚上电时,滤波器的初始值怎么处理?我一般用前几个采样点的均值做初始化,避免输出跳变。
  4. 组合使用:中值滤波 + 滑动平均,或者中值滤波 + 低通滤波,效果往往 1+1 > 2。

好了,这一章的内容就到这里。下一章我们会讲频域滤波,也就是 FFT 和 FIR 滤波器在可穿戴设备中的应用。到时候你会发现,时域滤波只是基本功,频域才是真正的利器。

有什么问题,欢迎在课程群里讨论。我们下章见。