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;
}
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];
}
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
4.5 三种滤波器的对比
| 滤波器 | 适用噪声类型 | 计算量 | 延迟 | 典型应用 |
|---|---|---|---|---|
| 滑动平均 | 高斯白噪声 | 低 | 中 | 步数检测、心率平滑 |
| 中值滤波 | 脉冲噪声 | 中 | 中 | 加速度计去毛刺 |
| 低通滤波 | 高频噪声 | 极低 | 低 | 姿态解算、呼吸检测 |
4.6 实战建议
最后,我给大家几个实战中的小建议:
- 先分析噪声类型:是随机噪声还是脉冲噪声?这决定了选哪种滤波器。
- 参数要可调:在产品开发阶段,把窗口大小、α 值做成可配置的。我习惯在 Flash 里存一份默认参数,调试时通过串口修改。
- 注意边界条件:设备刚上电时,滤波器的初始值怎么处理?我一般用前几个采样点的均值做初始化,避免输出跳变。
- 组合使用:中值滤波 + 滑动平均,或者中值滤波 + 低通滤波,效果往往 1+1 > 2。
好了,这一章的内容就到这里。下一章我们会讲频域滤波,也就是 FFT 和 FIR 滤波器在可穿戴设备中的应用。到时候你会发现,时域滤波只是基本功,频域才是真正的利器。
有什么问题,欢迎在课程群里讨论。我们下章见。