4、数字滤波算法:均值滤波、中值滤波、卡尔曼滤波在触控中的应用

数字滤波,说白了就是给传感器信号“洗个澡”。

光学触控系统里,红外或激光信号经过光电转换后,混进去的噪声五花八门——电源纹波、环境光突变、手指抖动、甚至隔壁工位的电磁干扰。我早年做第一版触控屏时,就吃过这个亏:信号明明看着挺干净,一上整机测试,坐标满屏乱跳。

嗯,从那以后我养成了一个习惯——任何触控信号,必须先过一道数字滤波器。

4.1 均值滤波:最简单的平滑器

均值滤波的原理,一句话就能讲清楚:取连续N个采样值的算术平均,作为当前输出。

核心公式:

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

其中 N 为窗口长度,通常取 4、8、16 等 2 的幂次,方便移位运算加速。

我在项目中遇到过一种情况:某款红外触摸框,在荧光灯下坐标抖动幅度达到 3 个像素。用 8 点均值滤波后,抖动直接压到 0.5 像素以内。为什么?因为 50Hz 的工频干扰被窗口平均掉了。

窗口长度 N 平滑效果 响应延迟 适用场景
4 较弱 3 个采样周期 快速滑动操作
8 中等 7 个采样周期 通用触控
16 较强 15 个采样周期 静止或慢速点击

注意:均值滤波对脉冲噪声(比如静电放电瞬间的尖峰)几乎无效。一个异常大值会污染整个窗口的输出。

4.2 中值滤波:脉冲噪声的克星

中值滤波的思路更直接:把窗口内的采样值排序,取中间那个数作为输出。

你想想看,如果采样序列是 [10, 12, 11, 255, 13],均值会算出 60.2,完全失真。但中值滤波只取排序后的第 3 个数——12,几乎不受那个 255 的影响。

我个人的经验:在光学触控中,中值滤波的窗口长度取 3 或 5 就够了。窗口太大,排序计算量会翻倍,而且会抹掉手指快速移动时的边缘细节。

我曾经调试过一台户外触摸一体机,阳光直射时传感器偶尔出现饱和尖峰。均值滤波完全扛不住,换成 5 点中值滤波后,坐标曲线瞬间就稳了。

中值滤波的代价是计算量。嵌入式平台上,每次排序都要消耗几十个微秒。我建议用“冒泡法半排序”——只排到中间位置就停,能省一半时间。

// 5点中值滤波示例(只排到第3个)
uint16_t median_filter(uint16_t *buf) {
    uint16_t tmp[5];
    memcpy(tmp, buf, 5 * sizeof(uint16_t));
    // 冒泡3轮,第3个位置就是中值
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4 - i; j++) {
            if (tmp[j] > tmp[j+1]) {
                uint16_t t = tmp[j];
                tmp[j] = tmp[j+1];
                tmp[j+1] = t;
            }
        }
    }
    return tmp[2];
}

4.3 卡尔曼滤波:动态系统的状态估计器

均值和中值滤波都是“静态”的——它们假设信号在窗口内基本不变。但手指在触摸屏上滑动时,位置是连续变化的。这时候,卡尔曼滤波就派上用场了。

卡尔曼滤波的核心思想是:用上一刻的状态预测下一刻,再用实际测量值修正预测。它不只看当前采样,还“记住”了运动趋势。

触控场景的简化模型:

  • 状态向量:位置 p 和速度 v
  • 测量值:当前采样坐标 z
  • 预测方程:p' = p + v * dt
  • 更新方程:p_new = p' + K * (z - p')

其中 K 是卡尔曼增益,它根据测量噪声和过程噪声动态调整。

我记得第一次在 Cortex-M4 上实现卡尔曼滤波时,心里有点打鼓——矩阵运算会不会太慢?实际测下来,一维卡尔曼滤波(只处理单轴坐标)只需要几十条指令,完全可以在 1ms 的采样周期内跑完。

特性 均值滤波 中值滤波 卡尔曼滤波
噪声类型 高斯噪声 脉冲噪声 混合噪声
动态跟踪 滞后明显 滞后中等 滞后小
计算量 极低 中等 较高
参数调优 只需调 N 只需调 N 需调 Q、R 矩阵

避坑指南:卡尔曼滤波的 Q(过程噪声协方差)和 R(测量噪声协方差)不能随便设。我曾经把 R 设得太小,结果滤波器完全相信测量值,噪声一点没滤掉。反过来,Q 设太大,滤波器又太“懒”,手指动了半天坐标才跟上。

4.4 三种滤波的协同策略

实际产品中,我很少只用一种滤波器。更常见的做法是级联:

  1. 第一级:中值滤波,窗口长度 3,专门干掉脉冲尖峰。
  2. 第二级:均值滤波,窗口长度 4,平滑残余的高频抖动。
  3. 第三级:卡尔曼滤波,做最终的位置和速度估计。

这样搭配下来,脉冲噪声被中值挡在门外,高斯噪声被均值削弱,动态滞后被卡尔曼补偿。我在一个 72MHz 的 STM32 上跑过这套流程,总耗时不到 200μs,完全不影响触控的实时性。

数字滤波级联流程图 原始采样信号 中值滤波 (N=3) 均值滤波 (N=4) 卡尔曼滤波 各阶段效果 ① 中值滤波:消除静电/阳光饱和等脉冲尖峰 ② 均值滤波:平滑电源纹波、环境光高频抖动 ③ 卡尔曼滤波:补偿动态滞后,输出平滑轨迹

4.5 工程实现要点

  • 定点化:嵌入式 MCU 没有 FPU 时,卡尔曼滤波的浮点运算要转成 Q15 或 Q31 定点格式。我习惯用 Q15,精度够用,运算只用乘法器和移位器。
  • 窗口管理:均值和中值滤波的缓冲区用环形队列实现,避免频繁 memmove。
  • 参数自适应:触控静止时增大滤波强度,快速滑动时减小窗口长度。我做过一个方案:根据最近 5 个点的方差动态调整卡尔曼滤波的 R 值,效果比固定参数好很多。

一个小技巧:调试滤波参数时,先把原始数据和滤波后的数据通过串口打印出来,用 Excel 或 Python 画成曲线对比。肉眼一看就知道参数合不合理,比盯着寄存器值猜快多了。