9、计步器原理:用加速度传感器实现简单计步逻辑
说到计步器,大家肯定不陌生。手机里那个每天提醒你「今天走了多少步」的功能,其实就是靠加速度传感器实现的。我当年第一次接触这个需求时,心想:不就是数数走路颠了几下吗?结果一上手才发现,这里面的坑还真不少。
今天我们就来聊聊,怎么用加速度传感器实现一个最简单的计步逻辑。说白了,就是让手机学会「识别」你走路这件事。
9.1 计步的核心思想:检测「上下颠簸」
你走路的时候,身体会怎么动?
嗯,其实是一个周期性的上下起伏。脚落地时身体下沉,脚抬起时身体上升。这个起伏的幅度虽然不大,但加速度传感器能捕捉到。
我习惯把计步逻辑拆成三步:
- 采集数据:从加速度传感器拿到 x、y、z 三个轴的数值
- 合成幅值:把三个轴的数据合成为一个总加速度值
- 判断步数:检测这个总加速度的波峰波谷,数出步数
你想想看,如果只用一个轴的数据,手机横着放和竖着放,结果会差很多。所以我们需要一个「方向无关」的算法。
核心公式:合成加速度 = sqrt(x² + y² + z²)
这个值去掉了重力分量后,剩下的就是「动态加速度」,也就是你走路时产生的上下颠簸。
9.2 数据预处理:先滤波再分析
原始数据长什么样?我直接说吧,非常「毛躁」。传感器本身有噪声,加上你走路时手臂晃动、手机在口袋里碰撞,数据里全是高频杂波。
我曾经在项目里直接拿原始数据做波峰检测,结果走十步给我数出三十步来。后来学乖了,先做一步滤波。
常用的方法有两种:
- 低通滤波:去掉高频噪声,保留走路的主频率(大约 1~2 Hz)
- 滑动平均:取最近 N 个点的平均值,平滑曲线
我个人习惯用滑动平均,简单有效。代码大概长这样:
// 滑动平均滤波
private float[] buffer = new float[10];
private int bufferIndex = 0;
public float smooth(float rawValue) {
buffer[bufferIndex % buffer.length] = rawValue;
bufferIndex++;
float sum = 0;
for (float v : buffer) {
sum += v;
}
return sum / buffer.length;
}
嗯,这里要注意:窗口大小别设太大。我试过 20 个点的窗口,结果走路都走出两米了,步数还没更新。一般 5~10 个点就够用。
9.3 波峰检测:找到每一步
滤波之后的数据,看起来就像一条波浪线。每走一步,就会产生一个波峰和一个波谷。我们的任务就是数波峰。
但问题来了——不是所有的波峰都是「一步」。比如你转身、掏手机、甚至咳嗽一下,都可能产生一个波峰。
所以我们需要加几个判断条件:
- 阈值过滤:波峰的高度必须超过某个值(比如 2 m/s²),太小的波动忽略
- 时间间隔:两个步之间的时间不能太短(比如小于 200ms 的忽略),防止抖动
- 波谷确认:检测到波峰后,必须等到波谷出现,才算完成一步
我给你们画个流程图,一看就明白:
小技巧:阈值不要设死。我习惯根据最近 1 秒的数据动态调整阈值。比如取最近 10 个波峰的平均值乘以 0.6,这样走路快慢都能适配。
9.4 完整代码示例
说了这么多,直接上代码吧。这是一个最简单的计步器实现:
public class SimpleStepDetector {
private static final float THRESHOLD = 2.0f; // 波峰阈值
private static final long MIN_TIME_MS = 200; // 最小步间隔
private static final int WINDOW_SIZE = 8; // 滑动窗口大小
private float[] buffer = new float[WINDOW_SIZE];
private int index = 0;
private long lastStepTime = 0;
private int stepCount = 0;
private boolean isPeak = false;
public void onSensorChanged(SensorEvent event) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
// 1. 合成加速度
float raw = (float) Math.sqrt(x*x + y*y + z*z);
// 2. 滑动平均滤波
buffer[index % WINDOW_SIZE] = raw;
index++;
float sum = 0;
for (float v : buffer) {
sum += v;
}
float smoothed = sum / WINDOW_SIZE;
// 3. 波峰检测
long now = System.currentTimeMillis();
if (smoothed > THRESHOLD && !isPeak) {
isPeak = true;
if (now - lastStepTime > MIN_TIME_MS) {
stepCount++;
lastStepTime = now;
}
} else if (smoothed < THRESHOLD - 0.5f) {
isPeak = false;
}
}
public int getStepCount() {
return stepCount;
}
}
注意:这个代码只是教学演示,实际产品中还需要处理更多情况。比如手机放在包里、骑车时、坐车时,都会产生类似走路的波形。我曾经在公交车上测试,结果计步器给我数了 200 步——我明明坐着没动。
9.5 实际项目中的避坑指南
做计步器,有几个坑我踩过,你们注意一下:
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 走路不计数 | 阈值设太高,或者滤波太强 | 降低阈值,减小窗口大小 |
| 坐着也计数 | 抖动被误判为波峰 | 增加时间间隔限制,提高阈值 |
| 跑步和走路混在一起 | 频率不同但算法没区分 | 检测波峰频率,区分跑步/走路 |
| 手机横竖屏影响 | 只用了单轴数据 | 使用合成加速度,方向无关 |
我曾经在一个项目里,用户反馈说「手机放裤兜里走 100 步只计了 30 步」。排查后发现,是因为裤兜里手机晃动幅度小,波峰不够高。后来我把阈值改成了动态自适应,问题就解决了。
9.6 总结一下
计步器的原理,说白了就是三步:
- 用加速度传感器采集数据
- 滤波去掉噪声
- 检测波峰,加上阈值和时间限制
嗯,听起来简单吧?但要做好,细节真的不少。我建议你们先拿这个基础版本跑一跑,看看效果。然后试着调调参数,感受一下不同阈值对结果的影响。
做技术就是这样,纸上得来终觉浅。动手跑一遍代码,比看十遍文章都管用。