4、时域滤波方法:滑动平均滤波、中值滤波、卡尔曼滤波在振动抑制中的应用
各位工程师朋友,咱们接着聊加速度计的振动误差。
上一章讲了频域滤波,说白了就是把信号拉到频率域里“切一刀”。但实际项目中,很多时候我们没条件做FFT,或者实时性要求极高。这时候,时域滤波就成了最趁手的工具。
我个人习惯把时域滤波分成三类:简单粗暴型、稳健抗噪型、智能预测型。正好对应滑动平均、中值滤波和卡尔曼滤波。今天我就把这三种方法的“脾气秉性”给你们捋清楚。
4.1 滑动平均滤波:最朴素的“平滑器”
滑动平均滤波,原理简单到令人发指——就是把最近N个采样点加起来求个平均值。
你想想看,振动噪声通常是高频的、随机抖动的。而真实加速度变化相对缓慢。一平均,高频抖动就被“抹平”了。
核心公式:
y[n] = (x[n] + x[n-1] + ... + x[n-N+1]) / N
其中N是窗口长度,x是原始数据,y是滤波后的数据。
我在项目中遇到过一个问题:用滑动平均滤波处理无人机悬停时的加速度数据。窗口长度N取小了,噪声压不住;取大了,真实信号被严重滞后。后来我总结出一个经验——窗口长度N一般取信号主周期采样点数的1/5到1/3。
举个例子,如果你的系统采样率是100Hz,振动主频率是10Hz(周期10个点),那N取2~3最合适。再大,真实加速度的突变就被“吃”掉了。
// 滑动平均滤波实现(C语言伪代码)
#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 += new_sample;
index = (index + 1) % WINDOW_SIZE;
return sum / WINDOW_SIZE;
}
我的小技巧:如果内存够用,可以用环形缓冲区实现,避免频繁的数据搬移。上面这段代码就是环形缓冲区的标准写法。
4.2 中值滤波:专治“野点”和脉冲噪声
滑动平均有个致命弱点——怕“野点”。
什么叫野点?就是传感器偶尔蹦出来的一个离谱值,比如正常读数0.1g,突然跳成10g。滑动平均会把野点“平均”进结果里,导致输出瞬间跳变。
中值滤波就是专门治这个的。它把窗口内的数据排序,取中间那个值作为输出。
核心思想:
排序 → 取中位数 → 输出
窗口长度N通常取奇数,比如3、5、7。
我曾经处理过一个工业机械臂的加速度数据。机械臂启动瞬间,电机产生的电磁干扰会让加速度计偶尔输出一个“尖峰”。滑动平均根本压不住,但中值滤波一上,野点直接消失。
不过要注意,中值滤波对持续性的振动噪声效果一般。它擅长的是“稀疏的、幅度大的”异常值。如果你的振动是连续的高斯白噪声,中值滤波不如滑动平均。
// 中值滤波实现(C语言伪代码)
#define WINDOW_SIZE 5
float median_filter(float new_sample) {
static float window[WINDOW_SIZE];
static int index = 0;
float temp[WINDOW_SIZE];
// 更新窗口
window[index] = new_sample;
index = (index + 1) % WINDOW_SIZE;
// 拷贝并排序
memcpy(temp, window, sizeof(temp));
// 冒泡排序(实际项目建议用快速排序)
for(int i = 0; i < WINDOW_SIZE - 1; i++) {
for(int j = 0; j < WINDOW_SIZE - 1 - i; j++) {
if(temp[j] > temp[j+1]) {
float t = temp[j];
temp[j] = temp[j+1];
temp[j+1] = t;
}
}
}
return temp[WINDOW_SIZE / 2]; // 取中位数
}
避坑指南:我曾经在STM32上跑中值滤波,窗口长度取9,结果发现排序耗时太长,导致系统实时性不达标。后来把窗口改成5,效果够用,速度也上来了。记住:窗口长度不是越大越好,够用就行。
4.3 卡尔曼滤波:带“预测”的智能滤波
前面两种方法都是“事后诸葛亮”——等数据来了再处理。卡尔曼滤波不一样,它一边预测下一时刻的状态,一边用实际测量值修正预测。
说白了,卡尔曼滤波就是一个“预测-修正”的闭环。它特别适合处理有规律的运动,比如匀速直线运动、简谐振动。
卡尔曼滤波五步法:
- 状态预测:根据上一时刻的状态,预测当前时刻的状态
- 协方差预测:预测状态的不确定性
- 卡尔曼增益计算:决定“相信预测”还是“相信测量”
- 状态更新:用测量值修正预测值
- 协方差更新:更新不确定性
我在做车载惯性导航时,车辆经过减速带会产生剧烈的冲击振动。滑动平均和中值滤波都试过,效果不理想。后来上了卡尔曼滤波,把加速度作为状态量,振动噪声作为观测噪声,效果立竿见影。
卡尔曼滤波有两个关键参数需要调:过程噪声协方差Q和观测噪声协方差R。Q越大,滤波器越“相信测量”;R越大,滤波器越“相信预测”。
// 一维卡尔曼滤波实现(C语言伪代码)
float kalman_filter(float measurement) {
static float x = 0; // 状态估计
static float P = 1; // 估计误差协方差
float Q = 0.01; // 过程噪声协方差
float R = 0.1; // 观测噪声协方差
// 预测
float x_pred = x;
float P_pred = P + Q;
// 更新
float K = P_pred / (P_pred + R); // 卡尔曼增益
x = x_pred + K * (measurement - x_pred);
P = (1 - K) * P_pred;
return x;
}
我的调参经验:刚开始调卡尔曼滤波时,可以把Q设小一点(比如0.001),R设大一点(比如1)。这样滤波器会“慢热”,但输出很平滑。如果响应太慢,再逐步增大Q、减小R。记住:Q和R的比值决定了滤波器的“性格”。
4.4 三种方法的对比与选择
说了这么多,到底该用哪一种?我给你们整理了一个对比表:
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 滑动平均 | 简单、计算量小、实时性好 | 有滞后、怕野点 | 噪声平稳、无脉冲干扰的场景 |
| 中值滤波 | 抗野点能力强、边缘保持好 | 计算量大(排序)、对连续噪声效果一般 | 有脉冲噪声、尖峰干扰的场景 |
| 卡尔曼滤波 | 自适应、无滞后、精度高 | 需要调参、计算量中等 | 运动规律已知、需要高精度的场景 |
我个人建议:先试滑动平均,不行再上中值滤波,最后才考虑卡尔曼滤波。因为卡尔曼滤波虽然效果好,但调参是个“玄学”,搞不好反而会引入新的误差。
核心结论:
- 振动噪声平稳 → 滑动平均
- 有脉冲干扰 → 中值滤波
- 需要高精度、无滞后 → 卡尔曼滤波
- 实在拿不准 → 三种都试,看效果选
好了,时域滤波的三种方法就讲到这里。记住:没有最好的滤波方法,只有最合适的。下次遇到振动问题,先别急着上复杂算法,从最简单的滑动平均开始试,往往能解决80%的问题。