4、经典滤波算法(一):限幅滤波法、中位值滤波法、算术平均滤波法原理与实现

各位同学,欢迎来到滤波算法的实战环节。

说实话,传感器数据融合这个领域,滤波算法就是基本功。就像练武要先扎马步一样,这些经典算法虽然看起来简单,但我在点钞机项目里,80%的噪声问题都是用它们解决的。今天咱们就聊聊三种最基础、也最实用的滤波方法。

4.1 限幅滤波法:最朴素的防抖手段

先说说限幅滤波。这玩意儿说白了就是:如果这次采样值和上次差太多,我就认为它是干扰,直接扔掉

我在做点钞机的时候,遇到过一种情况:钞票高速通过时,传感器偶尔会跳出一个离谱的数值,比如厚度突然变成5mm。这明显是机械振动导致的误触发。限幅滤波就是专门治这种毛病的。

原理

设定一个最大允许偏差值 ΔY。每次新采样值 Y(n) 和上次有效值 Y(n-1) 比较:

  • 如果 |Y(n) - Y(n-1)| ≤ ΔY,则本次值有效
  • 如果 |Y(n) - Y(n-1)| > ΔY,则本次值无效,用上次值代替

核心思想:物理世界的变化是连续的,突变大概率是噪声。

代码实现

// 限幅滤波
#define MAX_DELTA 10  // 最大允许偏差,根据实际传感器调整

int16_t Limiter_Filter(int16_t new_value, int16_t last_valid_value) {
    if (abs(new_value - last_valid_value) > MAX_DELTA) {
        // 超出范围,丢弃,返回上次值
        return last_valid_value;
    } else {
        return new_value;
    }
}

我的经验:ΔY 怎么选?我一般会先让传感器空跑,记录正常波动范围,然后取这个范围的1.5倍作为阈值。太小了容易误判,太大了又起不到滤波效果。

4.2 中位值滤波法:对付脉冲噪声的利器

限幅滤波只能处理单个的跳变,但如果连续出现几个坏点呢?比如点钞机在清点旧钞时,传感器会间歇性地被灰尘遮挡。这时候,中位值滤波就派上用场了。

原理

连续采样 N 次(N 通常取奇数),排序后取中间值作为本次有效值。

为什么取中位数?因为中位数对异常值不敏感。你想想看,就算有3个坏点混在5个数据里,排序后它们要么在最左边,要么在最右边,中间那个大概率是好的。

代码实现

// 中位值滤波
#define FILTER_SIZE 5  // 采样次数,建议3、5、7

int16_t Median_Filter(int16_t *buffer) {
    int16_t temp[FILTER_SIZE];
    int16_t i, j, tmp;
    
    // 复制数据,避免破坏原数组
    for (i = 0; i < FILTER_SIZE; i++) {
        temp[i] = buffer[i];
    }
    
    // 冒泡排序(数据量小,够用)
    for (i = 0; i < FILTER_SIZE - 1; i++) {
        for (j = 0; j < FILTER_SIZE - 1 - i; j++) {
            if (temp[j] > temp[j+1]) {
                tmp = temp[j];
                temp[j] = temp[j+1];
                temp[j+1] = tmp;
            }
        }
    }
    
    // 返回中位数
    return temp[FILTER_SIZE / 2];
}

注意:N 值越大,滤波效果越好,但延迟也越大。点钞机每秒要处理几十张钞票,我一般用5次采样,再多就来不及了。

4.3 算术平均滤波法:平滑噪声的万金油

如果说前两种算法是「特种兵」,那算术平均滤波就是「常规部队」。它不挑场景,几乎所有传感器数据都能用,就是代价有点大——需要存一堆历史数据。

原理

连续采样 N 次,求算术平均值作为本次输出。

公式很简单:Y = (X1 + X2 + ... + XN) / N

但这里有个坑:N 的选择直接影响响应速度。N 越大,曲线越平滑,但信号变化时反应也越慢。

代码实现

// 算术平均滤波
#define AVG_SIZE 10  // 平均次数

int16_t Average_Filter(int16_t *buffer) {
    uint32_t sum = 0;
    uint8_t i;
    
    for (i = 0; i < AVG_SIZE; i++) {
        sum += buffer[i];
    }
    
    return (int16_t)(sum / AVG_SIZE);
}

避坑指南:我曾经在点钞机项目里把 AVG_SIZE 设成 100,结果钞票都过去了,滤波后的数据还没跟上。后来我总结了一个经验:N 不要超过信号变化周期的采样点数。比如一张钞票通过传感器需要20个采样点,那 N 最好小于20。

4.4 三种算法的对比与选型

好了,三种算法都讲完了。你可能会问:到底该用哪个?

我的建议是:不要纠结,组合使用。点钞机里我通常是这么干的:

算法 适用场景 优点 缺点
限幅滤波 突发性大噪声(如机械振动) 简单、实时性好 无法处理连续噪声
中位值滤波 脉冲噪声、偶发坏点 抗干扰能力强 有延迟,N 不能太大
算术平均滤波 周期性噪声、随机噪声 平滑效果好 响应慢,占用内存

实际项目中,我经常这样搭配:先用限幅滤波干掉明显的跳变,再用中位值滤波处理残留的脉冲,最后用算术平均滤波把曲线磨平。三层下来,数据基本就干净了。

记住:滤波不是越复杂越好,而是越合适越好。有时候一个简单的限幅滤波,比跑个卡尔曼滤波还管用。

嗯,今天就先聊到这儿。下一节咱们继续讲剩下的经典算法——递推平均滤波和加权递推平均滤波。到时候我会分享一个点钞机里真实踩过的坑,保证让你印象深刻。