4、数字滤波算法:滑动平均滤波、中值滤波、卡尔曼滤波在称重信号中的应用

说到称重信号处理,我第一个想聊的就是滤波。为什么?因为电梯里的称重传感器,说白了就是个“受气包”。电梯启动的震动、乘客走动的冲击、甚至风吹轿厢的晃动,都会叠加到信号上。你想想看,如果直接把原始信号拿去判断超载,那电梯门一关一开,报警器能响个不停。

所以,数字滤波就是给信号“洗个澡”,把那些乱七八糟的噪声去掉,留下真实的重量信息。今天咱们就聊聊三种最常用的滤波算法:滑动平均、中值滤波、卡尔曼滤波。这三种我都用过,各有各的脾气。

4.1 滑动平均滤波:简单粗暴,但管用

滑动平均滤波,说白了就是“取个平均值”。把最近N次采样值存起来,每次新数据进来,就扔掉最老的那个,然后算个平均数。这招对付高频噪声特别有效。

我在项目中遇到过这样的情况:电梯启动瞬间,传感器输出会有一个尖峰。如果用滑动平均,这个尖峰就会被“平均”掉,不会误触发超载报警。

核心公式:

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

代码实现也很简单:

#define FILTER_LEN 8
static uint16_t buffer[FILTER_LEN];
static uint8_t index = 0;
static uint32_t sum = 0;

uint16_t moving_average(uint16_t new_sample) {
    sum -= buffer[index];
    buffer[index] = new_sample;
    sum += new_sample;
    index = (index + 1) % FILTER_LEN;
    return sum / FILTER_LEN;
}

我的经验:N值选多大?我一般取4到16之间。N太小,滤波效果差;N太大,响应变慢。电梯称重我习惯用8,平衡了平滑度和实时性。

4.2 中值滤波:专治“野点”

中值滤波的思路更直接:把N个采样值排序,取中间那个。这招对付脉冲噪声(比如突然的电磁干扰)特别灵。

我曾经遇到过一个问题:电梯运行时,变频器产生的电磁干扰会偶尔让传感器输出一个离谱的值。滑动平均虽然能压一压,但那个“野点”还是会拉偏平均值。换成中值滤波后,这种干扰直接被“踢出局”了。

注意:中值滤波的N值必须是奇数,不然中间两个数取哪个?我一般用3或5。N越大,对脉冲噪声的抑制越强,但计算量也越大。

代码示例:

#define MEDIAN_LEN 5
uint16_t median_filter(uint16_t new_sample) {
    static uint16_t buf[MEDIAN_LEN];
    static uint8_t idx = 0;
    uint16_t temp[MEDIAN_LEN];
    uint8_t i, j;
    
    buf[idx] = new_sample;
    idx = (idx + 1) % MEDIAN_LEN;
    
    // 复制并排序
    for(i = 0; i < MEDIAN_LEN; i++) temp[i] = buf[i];
    for(i = 0; i < MEDIAN_LEN - 1; i++) {
        for(j = i + 1; j < MEDIAN_LEN; j++) {
            if(temp[i] > temp[j]) {
                uint16_t t = temp[i];
                temp[i] = temp[j];
                temp[j] = t;
            }
        }
    }
    return temp[MEDIAN_LEN / 2];
}

4.3 卡尔曼滤波:有点“智能”的滤波

卡尔曼滤波就高级一些了。它不光看当前测量值,还根据上一时刻的估计值和系统模型,预测当前应该是什么值。然后,它用测量值来“修正”这个预测。说白了,它是在“猜”和“测”之间找个平衡。

我刚开始学卡尔曼滤波时,觉得公式太复杂。后来发现,在嵌入式里用一维卡尔曼就够了,没那么吓人。

一维卡尔曼滤波核心步骤:

  1. 预测:根据上一时刻估计值,预测当前值
  2. 计算卡尔曼增益:根据预测误差和测量误差,算出一个权重
  3. 更新:用测量值修正预测值
  4. 更新误差:为下一轮做准备

代码实现:

typedef struct {
    float Q;  // 过程噪声协方差
    float R;  // 测量噪声协方差
    float P;  // 估计误差协方差
    float K;  // 卡尔曼增益
    float X;  // 估计值
} Kalman_1D;

void Kalman_Init(Kalman_1D *kf, float init_val) {
    kf->Q = 0.01f;   // 我一般设0.01,根据实际情况调
    kf->R = 0.1f;    // 测量噪声,传感器手册上能找到
    kf->P = 1.0f;
    kf->X = init_val;
}

float Kalman_Update(Kalman_1D *kf, float measurement) {
    // 预测
    float P_pred = kf->P + kf->Q;
    
    // 计算卡尔曼增益
    kf->K = P_pred / (P_pred + kf->R);
    
    // 更新估计值
    kf->X = kf->X + kf->K * (measurement - kf->X);
    
    // 更新误差
    kf->P = (1.0f - kf->K) * P_pred;
    
    return kf->X;
}

调参心得:Q和R怎么设?Q越大,系统越相信测量值;R越大,系统越相信预测值。我一般先让R大一点(比如0.5),让滤波平滑些,再慢慢减小R,直到响应速度满足要求。

4.4 三种滤波的对比与选择

这三种滤波各有千秋,我整理了个表格,方便你对比:

算法 优点 缺点 适用场景
滑动平均 简单、计算量小、平滑高频噪声 响应慢、对脉冲噪声效果一般 电梯平稳运行时的称重
中值滤波 有效抑制脉冲噪声、实现简单 需要排序、N大时计算量大 电磁干扰严重的环境
卡尔曼滤波 自适应、平滑且响应快、精度高 需要调参、计算量稍大 动态称重、需要高精度场景

避坑指南:我曾经在一个项目里只用滑动平均,结果电梯启动时信号延迟太大,导致超载判断滞后。后来我改成“滑动平均+中值滤波”组合:先用中值滤波去掉野点,再用滑动平均平滑。效果好了很多。

4.5 实战建议:怎么选?

如果你刚入门,我建议先从滑动平均开始。它简单、可靠,能解决80%的问题。如果发现偶尔有异常跳变,就加上中值滤波。如果对精度和响应速度都有要求,再考虑卡尔曼滤波。

嗯,这里要注意一点:滤波不是越复杂越好。我见过有人用卡尔曼滤波处理静态称重,结果调参调了三天,最后发现滑动平均就够用了。所以,先搞清楚你的噪声是什么类型,再选合适的算法。

最后说一句:滤波算法选好了,别忘了在硬件上也下点功夫。比如传感器供电加个LC滤波、信号线用屏蔽线,这些都能让数字滤波事半功倍。