4、信号处理基础:低通滤波、高通滤波、卡尔曼滤波、滑动平均滤波、陷波滤波器

做力控机器人这些年,我踩过最大的坑,就是信号噪声。

你想想看,力传感器采集回来的数据,要是直接拿去用,那机器人基本就是在抽风。抖动、误触发、甚至撞机,都是家常便饭。所以,信号处理这块,是力控安全保护的「第一道防线」。

今天我就把这五种最常用的滤波器,掰开了揉碎了讲给你听。每个我都亲手调过参数,有些坑,希望你不用再走一遍。

核心观点:没有万能的滤波器。选型的关键,在于你清楚「要保留什么信号,要滤掉什么噪声」。

信号处理滤波器 低通滤波 高通滤波 卡尔曼滤波 滑动平均 陷波滤波器 应用场景:力控碰撞检测、安全保护、信号去噪 保留低频,滤除高频 保留高频,滤除低频 最优估计,预测+更新 N点平均,平滑数据 定点消除特定频率

4.1 低通滤波:力控最常用的「老黄牛」

低通滤波,说白了就是让慢变信号通过,把快变信号拦住。力传感器采集到的碰撞力,本质上是低频信号。而电机振动、电磁干扰,都是高频噪声。

一阶RC低通滤波(最经典):

// 一阶低通滤波,C语言实现
float lowPassFilter(float input, float prevOutput, float alpha) {
    // alpha = T / (T + RC),T为采样周期
    // alpha越小,滤波越强,但延迟越大
    return alpha * input + (1 - alpha) * prevOutput;
}

我的经验:alpha值一般取0.1~0.3。我习惯先设0.2,然后看波形。如果噪声还明显,就降到0.1。但注意,alpha太小,碰撞响应会变慢,安全保护可能来不及。

实际应用场景:机器人末端力传感器,采样率1kHz,我一般用alpha=0.15。这样既能滤掉50Hz以上的振动,又能保证碰撞检测延迟在5ms以内。

4.2 高通滤波:提取「突变」信号

高通滤波和低通正好相反。它保留快速变化的信号,滤掉缓慢变化的直流分量。

什么时候用?比如你要检测「瞬间碰撞力」,但力传感器本身有静态偏置(比如重力分量)。这时候高通滤波一上,基线直接归零,只留下碰撞冲击。

// 一阶高通滤波
float highPassFilter(float input, float prevInput, float prevOutput, float alpha) {
    // alpha = RC / (RC + T)
    return alpha * (prevOutput + input - prevInput);
}

注意:高通滤波会引入相位超前。在力控闭环里,相位变化可能导致系统不稳定。我曾经在一个项目中,因为高通滤波参数没调好,机器人开始自激振荡...嗯,那场面挺吓人的。

4.3 卡尔曼滤波:力控界的「最优估计器」

卡尔曼滤波,听起来高大上,其实核心思想就两句话:预测 + 更新

它不像低通滤波那样「一刀切」,而是根据你对系统的信任程度,动态调整滤波强度。说白了,它知道什么时候该信传感器,什么时候该信模型。

// 一维卡尔曼滤波(简化版)
typedef struct {
    float Q;  // 过程噪声协方差
    float R;  // 测量噪声协方差
    float x;  // 估计值
    float P;  // 估计误差协方差
    float K;  // 卡尔曼增益
} KalmanFilter;

void kalmanInit(KalmanFilter *kf, float initX) {
    kf->Q = 0.01;  // 根据实际调整
    kf->R = 0.1;   // 传感器噪声方差
    kf->x = initX;
    kf->P = 1.0;
}

float kalmanUpdate(KalmanFilter *kf, float measurement) {
    // 预测
    kf->P = kf->P + kf->Q;
    
    // 更新
    kf->K = kf->P / (kf->P + kf->R);
    kf->x = kf->x + kf->K * (measurement - kf->x);
    kf->P = (1 - kf->K) * kf->P;
    
    return kf->x;
}

关键参数调优:

  • Q值(过程噪声):越大,滤波器越「相信」测量值,响应快但噪声大
  • R值(测量噪声):越大,滤波器越「平滑」,但延迟增加

我个人习惯:先让Q=R=0.01,然后观察。如果噪声大,增大R;如果响应慢,减小Q。

4.4 滑动平均滤波:简单粗暴但有效

这个滤波器,我愿称之为「性价比之王」。不需要复杂的数学,不需要调参,一个循环队列搞定。

// 滑动平均滤波
#define WINDOW_SIZE 10

float slidingAverage(float newValue) {
    static float buffer[WINDOW_SIZE] = {0};
    static int index = 0;
    static float sum = 0;
    
    sum -= buffer[index];
    buffer[index] = newValue;
    sum += buffer[index];
    index = (index + 1) % WINDOW_SIZE;
    
    return sum / WINDOW_SIZE;
}

避坑指南:窗口大小选多少?我一般遵循「3倍原则」——窗口长度至少是噪声周期的3倍。比如50Hz噪声,周期20ms,采样周期1ms,窗口就取60。太小了滤不干净,太大了延迟受不了。

缺点也很明显:它需要存储N个数据,内存敏感的场景要小心。另外,它对脉冲噪声(比如突然的尖峰)反应迟钝,因为要等N个点才能「消化」掉。

4.5 陷波滤波器:定点清除「顽固噪声」

有时候,噪声不是宽频带的,而是集中在某个特定频率。比如50Hz工频干扰,或者机器人关节的共振频率。这时候,低通滤波会把有用信号也滤掉,高通滤波又放不过去。

怎么办?上陷波滤波器。它就像一把手术刀,精准切除某个频率,其他频率几乎不受影响。

// 二阶IIR陷波滤波器(双线性变换法)
// 中心频率 f0 = 50Hz, 采样率 fs = 1000Hz
// 带宽 BW = 5Hz

float notchFilter(float input) {
    static float x1 = 0, x2 = 0;
    static float y1 = 0, y2 = 0;
    
    float omega = 2 * 3.14159 * 50 / 1000;  // 归一化频率
    float alpha = sin(omega) * sinh(log(2)/2 * 5 * omega / sin(omega));
    
    float b0 = 1;
    float b1 = -2 * cos(omega);
    float b2 = 1;
    float a0 = 1 + alpha;
    float a1 = -2 * cos(omega);
    float a2 = 1 - alpha;
    
    float output = (b0 * input + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2) / a0;
    
    x2 = x1;
    x1 = input;
    y2 = y1;
    y1 = output;
    
    return output;
}

我曾经踩过的坑:陷波滤波器用不好,会引入「振铃效应」。就是滤波后的信号在陷波频率附近出现震荡。原因通常是带宽设置太窄,或者滤波器阶数太高。我建议带宽至少设为中心频率的10%,比如50Hz陷波,带宽取5~10Hz比较安全。

4.6 如何选择?一张表说清楚

滤波器类型 适用场景 优点 缺点 我常用的参数
低通滤波 力传感器去噪、碰撞检测 实现简单,实时性好 有相位延迟 alpha=0.1~0.3
高通滤波 提取碰撞冲击、去除直流偏置 消除基线漂移 放大高频噪声 alpha=0.05~0.2
卡尔曼滤波 高精度力控、传感器融合 最优估计,自适应 计算量大,调参复杂 Q=0.01, R=0.1
滑动平均 简单去噪、数据平滑 零算法复杂度 内存占用,延迟固定 窗口=10~60
陷波滤波 消除特定频率干扰(50Hz、共振) 精准打击,不影响其他频率 可能引入振铃 带宽=10%中心频率

4.7 实战建议:滤波器的「组合拳」

在实际的力控安全保护系统中,我很少只用一种滤波器。通常是组合使用:

  1. 第一级:滑动平均滤波(快速去除毛刺)
  2. 第二级:陷波滤波器(干掉50Hz工频干扰)
  3. 第三级:低通滤波(平滑输出,控制延迟)

我的血泪教训:滤波器的顺序很重要!一定要先做滑动平均,再做陷波,最后低通。如果顺序反了,低通滤波会把陷波引入的振铃「放大」,导致信号完全没法看。

好了,信号处理这块就讲到这里。记住一句话:滤波不是越强越好,而是在「噪声抑制」和「信号保真」之间找到平衡点。这个平衡点,需要你在实际项目中一点点试出来。别怕调参,每个参数背后,都是你对系统更深的理解。


公众号:蓝海资料掘金营,微信deep3321