14、碰撞检测:加速度突变与冲击力估算
说到碰撞检测,很多人的第一反应是游戏里的物理引擎。但在Android开发中,我们用加速度传感器也能做碰撞检测。说白了,就是捕捉加速度的突变。
我记得有一次做运动监测项目,用户把手机摔了,结果数据里出现了一个巨大的尖峰。那一刻我突然意识到——这不就是碰撞信号吗?从那以后,我就开始认真研究怎么用加速度传感器做碰撞检测。
14.1 碰撞的本质:加速度突变
先想一个问题:碰撞发生时,传感器会记录到什么?
答案是——加速度的剧烈变化。正常运动时,加速度变化是平滑的。但碰撞瞬间,加速度会在几毫秒内从几m/s²飙升到几十甚至上百m/s²。
我习惯把这个突变叫做「冲击尖峰」。它的特征很明显:
- 幅值大:远超正常运动范围
- 时间短:通常持续10-50毫秒
- 变化快:斜率极大,几乎是垂直上升
你想想看,手机从1米高度自由落体,撞击地面时的加速度峰值能达到100-200 m/s²。而正常走路时,加速度变化一般不超过±5 m/s²。这个差距,足够我们做检测了。
14.2 碰撞检测的两种思路
实际开发中,我常用两种方法。它们各有适用场景。
方法一:阈值检测法
最简单直接的方法。设定一个阈值,当加速度超过这个值就判定为碰撞。
// 阈值检测法示例
public class CollisionDetector {
private static final float COLLISION_THRESHOLD = 30.0f; // 30 m/s²
private static final long MIN_INTERVAL_MS = 200; // 防抖间隔
private long lastCollisionTime = 0;
public boolean detectCollision(SensorEvent event) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
// 计算合加速度
float magnitude = (float) Math.sqrt(x*x + y*y + z*z);
// 减去重力(约9.8 m/s²)
float netAccel = magnitude - 9.8f;
long now = System.currentTimeMillis();
if (Math.abs(netAccel) > COLLISION_THRESHOLD
&& (now - lastCollisionTime) > MIN_INTERVAL_MS) {
lastCollisionTime = now;
return true;
}
return false;
}
}
方法二:差分检测法
阈值法有个问题——不同场景下,正常运动的峰值可能不一样。比如跑步时加速度变化就比走路大得多。
这时候我更喜欢用差分法。它检测的是加速度的变化率,而不是绝对值。
// 差分检测法示例
public class DifferentialCollisionDetector {
private float prevMagnitude = 9.8f;
private static final float DIFF_THRESHOLD = 50.0f; // 变化率阈值
public boolean detectCollision(SensorEvent event) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
float magnitude = (float) Math.sqrt(x*x + y*y + z*z);
// 计算变化率(差分)
float delta = Math.abs(magnitude - prevMagnitude);
prevMagnitude = magnitude;
return delta > DIFF_THRESHOLD;
}
}
差分法的好处是——它不受重力影响,也不受静态加速度干扰。你拿着手机静止不动,突然被撞一下,差分值会很大。而阈值法可能因为重力分量已经接近阈值而误判。
14.3 冲击力估算:从加速度到力
检测到碰撞之后,下一个问题就是——这个碰撞有多猛?
根据牛顿第二定律:F = ma。知道加速度和质量,就能估算冲击力。
但这里有个坑:传感器测的是手机本身的加速度,不是碰撞物体的加速度。而且碰撞时间极短,我们需要的是峰值力。
冲击力估算公式:
F_peak = m × a_peak
其中:
- m:手机质量(kg),一般0.15-0.2kg
- a_peak:碰撞瞬间的峰值加速度(m/s²)
举个例子:手机质量0.17kg,碰撞时加速度峰值120 m/s²。
F_peak = 0.17 × 120 = 20.4 N
这个力大概相当于2公斤物体静止时的重力。嗯,不算小。
14.4 碰撞检测的实战要点
做了几个碰撞检测项目后,我总结了一些经验。分享给你:
- 采样率要够高:碰撞信号持续时间短,建议用SENSOR_DELAY_FASTEST或至少SENSOR_DELAY_GAME。我一般用200Hz以上。
- 低通滤波不能少:原始数据噪声大,先滤波再检测。我用的是移动平均滤波,窗口大小3-5个采样点。
- 防抖机制必须有:一次碰撞可能产生多次触发。加个时间窗口,比如200ms内只触发一次。
- 区分碰撞类型:跌落、撞击、振动,它们的波形特征不同。我习惯记录碰撞前后的波形,用模式识别做分类。
14.5 碰撞检测的完整流程
下面这张图是我做碰撞检测时的标准流程。你可以参考一下:
14.6 一个完整的碰撞检测实现
最后,我把上面讲的东西整合成一个完整的实现。这个类我在项目里用过,效果还不错。
public class CollisionAnalyzer implements SensorEventListener {
private static final float GRAVITY = 9.8f;
private static final float COLLISION_THRESHOLD = 35.0f;
private static final long DEBOUNCE_MS = 300;
private static final int FILTER_WINDOW = 5;
private float[] buffer = new float[FILTER_WINDOW];
private int bufferIndex = 0;
private long lastCollisionTime = 0;
private float phoneMass = 0.17f; // kg
private OnCollisionListener listener;
public interface OnCollisionListener {
void onCollision(float peakAccel, float estimatedForce, long timestamp);
}
@Override
public void onSensorChanged(SensorEvent event) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
// 计算合加速度
float rawMagnitude = (float) Math.sqrt(x*x + y*y + z*z);
// 移动平均滤波
buffer[bufferIndex % FILTER_WINDOW] = rawMagnitude;
bufferIndex++;
float sum = 0;
int count = Math.min(bufferIndex, FILTER_WINDOW);
for (int i = 0; i < count; i++) {
sum += buffer[i];
}
float filtered = sum / count;
// 减去重力
float netAccel = Math.abs(filtered - GRAVITY);
long now = System.currentTimeMillis();
// 碰撞检测
if (netAccel > COLLISION_THRESHOLD
&& (now - lastCollisionTime) > DEBOUNCE_MS) {
lastCollisionTime = now;
// 估算冲击力
float force = phoneMass * netAccel;
if (listener != null) {
listener.onCollision(netAccel, force, now);
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// 精度变化时可以做些处理
}
public void setOnCollisionListener(OnCollisionListener listener) {
this.listener = listener;
}
}
好了,碰撞检测的核心内容就这些。从检测原理到冲击力估算,再到完整实现,你应该能上手了。记住一点——传感器数据永远是脏的,滤波和防抖是必不可少的。我在早期项目里吃过这个亏,数据没处理好,误报率高达30%。后来加了滤波和防抖,误报率降到了5%以下。
做碰撞检测,耐心比技巧更重要。多收集数据,多调参数,你也能做出稳定的检测系统。