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:低通滤波原理流程图
下面这张图展示了原始数据经过低通滤波后的变化过程:
代码实现:一阶低通滤波器
直接上代码。我在项目中常用的一个工具类,专门处理加速度数据:
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 值对数据的影响差异很大。我整理了一张对比表:
| alpha 值 | 滤波强度 | 响应速度 | 适用场景 |
|---|---|---|---|
| 0.05 ~ 0.1 | 极强 | 极慢 | 静态姿态检测、倾斜角计算 |
| 0.1 ~ 0.2 | 较强 | 较慢 | 计步器、睡眠监测 |
| 0.2 ~ 0.3 | 适中 | 适中 | 手势识别、方向判断 |
| 0.3 ~ 0.5 | 较弱 | 较快 | 游戏控制、实时交互 |
| 0.5 ~ 1.0 | 极弱 | 极快 | 几乎不滤波,保留原始数据 |
进阶:自适应滤波
固定 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 开始,调出感觉
- 再根据场景需求,尝试自适应方案
- 别忘了测试不同手机——不同型号的传感器噪声水平差异很大
嗯,数据过滤这块就聊到这儿。下一节我们会深入另一个重要话题——如何用加速度数据判断手机的姿态。到时候见。