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 卡尔曼滤波:带“预测”的智能滤波

前面两种方法都是“事后诸葛亮”——等数据来了再处理。卡尔曼滤波不一样,它一边预测下一时刻的状态,一边用实际测量值修正预测。

说白了,卡尔曼滤波就是一个“预测-修正”的闭环。它特别适合处理有规律的运动,比如匀速直线运动、简谐振动。

卡尔曼滤波五步法:

  1. 状态预测:根据上一时刻的状态,预测当前时刻的状态
  2. 协方差预测:预测状态的不确定性
  3. 卡尔曼增益计算:决定“相信预测”还是“相信测量”
  4. 状态更新:用测量值修正预测值
  5. 协方差更新:更新不确定性

我在做车载惯性导航时,车辆经过减速带会产生剧烈的冲击振动。滑动平均和中值滤波都试过,效果不理想。后来上了卡尔曼滤波,把加速度作为状态量,振动噪声作为观测噪声,效果立竿见影。

卡尔曼滤波有两个关键参数需要调:过程噪声协方差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%的问题。