7、数据过滤:为什么原始数据这么抖?低通滤波原理与实现

如果你已经动手写过加速度传感器的代码,大概率会遇到一个让人头疼的问题——数据怎么这么抖?明明手机放在桌上一动不动,打印出来的数值却像心跳图一样上下跳动。我第一次遇到这情况时,差点以为传感器坏了。

其实不是传感器的问题。加速度计的原始数据里,混杂着大量高频噪声。这些噪声来自手机震动、电路干扰、甚至你呼吸时桌面微小的形变。说白了,传感器太灵敏了,连它不想看到的东西都一并收了进来。

为什么原始数据会抖?

先看一个实际例子。我拿一台测试机,静止放在桌面上,采集了100个加速度X轴数据。理论上应该是一条直线(约0 m/s²),但实际打印出来是这样的:

0.12, -0.08, 0.21, -0.15, 0.33, -0.05, 0.18, -0.22, 0.09, -0.11
0.27, -0.03, 0.14, -0.19, 0.06, -0.25, 0.31, -0.07, 0.16, -0.13
...

看到没?数值在 -0.25 到 0.33 之间来回跳。这个抖动幅度,对于需要精确判断姿态的应用来说,完全不可接受。

抖动的来源主要有三个:

  • 热噪声:传感器内部的电子元件会因温度产生随机波动
  • 机械噪声:手机马达、扬声器振动会传导到传感器
  • 量化噪声:模数转换时,模拟信号变成数字信号会丢失精度

嗯,这里要注意:不是所有抖动都是坏事。高频抖动里其实也包含真实信号,但大部分情况下,我们需要的是平滑的趋势,而不是毛刺。

低通滤波的核心思想

怎么去掉这些高频噪声?最经典的方法就是低通滤波。它的原理特别简单——让低频信号通过,阻挡高频信号

你想想看,手机在桌面上移动,这个动作产生的加速度变化是相对缓慢的(低频)。而噪声是快速跳变的(高频)。如果我们把快速变化的部分滤掉,剩下的就是平滑的真实信号。

我个人习惯用一阶低通滤波器,也叫指数移动平均。它的公式长这样:

output = alpha * input + (1 - alpha) * previous_output

其中 alpha 是滤波系数,范围 0 到 1。alpha 越小,滤波效果越强,但响应也越慢。

说白了,就是让新数据只贡献一部分,大部分保留之前的结果。这样数据就不会突然跳变。

SVG:低通滤波原理流程图

下面这张图展示了原始数据经过低通滤波后的变化过程:

低通滤波原理流程图 原始数据 含高频噪声 一阶低通滤波 output = α·input + (1-α)·prev α 越小,滤波越强 平滑数据 去除高频噪声 数据对比(静止状态) 原始数据(抖动明显) 滤波后数据(平滑稳定)

代码实现:一阶低通滤波器

直接上代码。我在项目中常用的一个工具类,专门处理加速度数据:

public class LowPassFilter {
    private float alpha;
    private float[] filteredValues;
    private boolean initialized = false;

    /**
     * @param alpha 滤波系数,推荐 0.1 ~ 0.3
     */
    public LowPassFilter(float alpha) {
        this.alpha = alpha;
    }

    public float[] filter(float[] input) {
        if (!initialized) {
            // 第一次直接赋值
            filteredValues = input.clone();
            initialized = true;
            return filteredValues;
        }

        for (int i = 0; i < input.length; i++) {
            filteredValues[i] = alpha * input[i] + (1 - alpha) * filteredValues[i];
        }
        return filteredValues;
    }

    public void setAlpha(float alpha) {
        this.alpha = alpha;
    }
}

使用方式也很简单:

LowPassFilter filter = new LowPassFilter(0.2f);

// 在传感器回调中调用
public void onSensorChanged(SensorEvent event) {
    float[] raw = event.values.clone();
    float[] smooth = filter.filter(raw);
    // smooth 就是滤波后的数据
}
💡 我的经验:alpha 值怎么选?如果你做的是计步器,alpha 建议 0.15~0.2,响应慢但稳定。如果是游戏手柄控制,alpha 可以调到 0.3~0.4,响应更快。我曾经在做一个体感游戏时,alpha 设成 0.1,结果用户甩手机半秒后画面才动,被吐槽到不行。

alpha 值的影响对比

不同 alpha 值对数据的影响差异很大。我整理了一张对比表:

alpha 值 滤波强度 响应速度 适用场景
0.05 ~ 0.1 极强 极慢 静态姿态检测、倾斜角计算
0.1 ~ 0.2 较强 较慢 计步器、睡眠监测
0.2 ~ 0.3 适中 适中 手势识别、方向判断
0.3 ~ 0.5 较弱 较快 游戏控制、实时交互
0.5 ~ 1.0 极弱 极快 几乎不滤波,保留原始数据
⚠️ 避坑指南:我曾经在项目里犯过一个低级错误——每次 new 一个 LowPassFilter 对象,导致滤波状态丢失,数据反而更抖了。记住:一个传感器通道只创建一个滤波器实例,保持状态持续更新。

进阶:自适应滤波

固定 alpha 值有个问题——它不能适应不同场景。比如手机静止时,我们希望滤波强一点;手机快速移动时,又希望响应快一点。

我后来在项目中用了一个小技巧:根据数据变化幅度动态调整 alpha。变化大时用大 alpha(快速跟踪),变化小时用小 alpha(平滑稳定)。

public float[] adaptiveFilter(float[] input, float[] prev) {
    float[] output = new float[input.length];
    for (int i = 0; i < input.length; i++) {
        float diff = Math.abs(input[i] - prev[i]);
        // 变化越大,alpha 越大
        float dynamicAlpha = Math.min(0.5f, 0.1f + diff * 2.0f);
        output[i] = dynamicAlpha * input[i] + (1 - dynamicAlpha) * prev[i];
    }
    return output;
}

这个思路说白了就是:让滤波器自己判断该不该相信新数据。变化剧烈时多信一点,平稳时多平滑一点。效果比固定 alpha 好不少。

总结一下

低通滤波是处理传感器数据最基础、最实用的手段。它的原理不复杂,但用好了能解决大问题。我个人建议:

  • 先从固定 alpha 开始,调出感觉
  • 再根据场景需求,尝试自适应方案
  • 别忘了测试不同手机——不同型号的传感器噪声水平差异很大

嗯,数据过滤这块就聊到这儿。下一节我们会深入另一个重要话题——如何用加速度数据判断手机的姿态。到时候见。


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