3. 加速度计与陀螺仪数据采集

Wear OS 上的加速度计与陀螺仪属于 Sensor.TYPE_ACCELEROMETER 与 Sensor.TYPE_GYROSCOPE 两类连续型传感器,它们以固定频率持续输出三轴数据。采集链路可以概括为:获取 SensorManager → 选择传感器与采样率 → 注册监听器 → 在回调中读取三轴值 → 校准与去噪 → 交付给业务逻辑。本节围绕这条链路展开,重点讲清每一步的实际做法与判断依据。

一、注册监听器:从 SensorManager 到回调

采集的入口是 SensorManager,通过 getSystemService(SENSOR_SERVICE) 获取。注册监听器时,采样率以微秒为单位的“期望周期”传入,系统会就近取整到硬件支持的档位。下面是一段可直接参考的注册代码:

val sm = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val acc = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
val gyr = sm.getDefaultSensor(Sensor.TYPE_GYROSCOPE)

// 期望 20ms 周期(约 50Hz)
val periodUs = SensorManager.SENSOR_DELAY_GAME // 约 20000us

sm.registerListener(accListener, acc, periodUs, 0)
sm.registerListener(gyrListener, gyr, periodUs, 0)

private val accListener = object : SensorEventListener {
    override fun onSensorChanged(e: SensorEvent) {
        val ax = e.values[0]; val ay = e.values[1]; val az = e.values[2]
        val tNs = e.timestamp   // 纳秒,单调时钟
    }
    override fun onAccuracyChanged(s: Sensor, a: Int) {}
}

几个容易踩坑的点:回调运行在主线程,高频采样会拖慢 UI,建议把处理逻辑放到 HandlerThread 或协程中;必须成对调用 unregisterListener,通常在 onPause 或 onStop 中注销,否则会持续耗电;timestamp 是纳秒级单调时钟,用于计算真实采样间隔,不要用 System.currentTimeMillis() 代替。

二、采样率选择:档位与真实频率

Android 提供四个标准档位,它们只是“期望值”,实际频率由硬件决定,需通过 timestamp 差值反推:

档位常量期望周期典型频率适用场景
SENSOR_DELAY_FASTEST0最高(可达 200Hz+)短时高频动作捕捉
SENSOR_DELAY_GAME20000us约 50Hz手势、步态、运动识别
SENSOR_DELAY_UI66667us约 15Hz屏幕方向、低功耗姿态
SENSOR_DELAY_NORMAL200000us约 5Hz粗略状态判断

选择原则是“够用即最低”。步频检测通常 25~50Hz 足够;手势识别 50Hz 较稳妥;只有需要捕捉快速冲击(如敲击、跌落)时才用 FASTEST。频率越高,功耗与噪声同步上升,且高频噪声会掩盖真实信号。判断实际频率的方法:连续记录 100 个 timestamp,计算平均间隔 Δt = (t100 - t1) / 99,则 f = 1e9 / Δt(Hz)。若实测频率明显低于期望,说明硬件不支持该档位,应下调期望值。

三、坐标系定义:设备坐标与重力方向

Wear OS 传感器使用设备坐标系:以屏幕为参照,X 轴沿屏幕水平向右,Y 轴沿屏幕垂直向上,Z 轴垂直于屏幕向外(指向用户)。加速度计静止时输出的是比力,即“加速度减去重力”,因此平放桌面时 Z 轴约为 +9.81 m/s²(指向天空方向的反作用),这是判断姿态的基准。陀螺仪输出的是绕三轴的角速度,单位为 rad/s,正负号遵循右手定则:绕 X 轴正转(Y 向 Z 转)为正。

需要特别说明:加速度计与陀螺仪的坐标系一致,但手表佩戴在手腕上时,设备坐标会随手臂姿态变化。若业务需要“世界坐标”或“人体坐标”,必须先用重力方向做倾斜补偿,或结合磁力计/旋转矢量传感器做姿态解算。本节所有示例均以设备坐标系为准,切换参照系时会明确标注。

加速度计与陀螺仪采集链路与坐标系 SensorManager 获取传感器实例 registerListener 周期 20000us onSensorChanged 读取 values[0..2] 校准 + 去噪 零偏/低通/滑动平均 业务数据 设备坐标系(屏幕朝外为 +Z) Wear OS 设备 +X 水平向右 +Y 垂直向上 +Z 垂直屏幕向外 重力 g ≈ 9.81 m/s² 静止平放时 az ≈ +9.81 绕 X 轴角速度 ωx 单位 rad/s,右手定则

四、数据校准:零偏与尺度误差

MEMS 传感器存在两类系统性误差:零偏(静止时输出不为零)和尺度误差(输出与真实值存在比例偏差)。工程上最实用的校准是零偏校准:将设备静止放置数秒,采集 N 个样本求均值,作为零偏 b,后续每次读数减去 b。示例:静止采集 200 个陀螺仪样本,计算得 bx = 0.012 rad/s,则校正后 ωx' = ωx - 0.012。加速度计的零偏通常较小,但 Z 轴可能因装配应力出现 0.1~0.3 m/s² 的偏差,同样可用静止均值扣除。

尺度误差需要已知参考量才能标定,例如用已知角速度的转台或已知倾角的斜面。在消费级应用中,若精度要求不高,可只做零偏校准;若用于步态或姿态融合,建议结合旋转矢量传感器做在线估计。校准参数应持久化保存,避免每次启动重新标定。

五、噪声处理:低通滤波与滑动平均

加速度计噪声主要来自机械振动,陀螺仪噪声则表现为随机游走。两种最常用的实时去噪方法:

  • 一阶低通滤波:y[n] = α·x[n] + (1-α)·y[n-1],α 越小越平滑但延迟越大。50Hz 采样下,α 取 0.2~0.3 可有效抑制高频抖动,同时保留步态等低频成分。
  • 滑动平均:维护长度 N 的窗口,输出窗口均值。N=5 时延迟约 2 个采样周期,适合对实时性要求不高的场景。

选择依据:若信号本身变化缓慢(如倾角),优先低通;若需要抑制突发尖峰(如敲击),可先做中值滤波再低通。注意不要过度滤波,否则会抹掉真实的高频动作特征,例如快速挥手产生的角速度峰值。

校准与滤波的顺序建议:先减零偏,再做滤波。若先滤波,零偏会被平滑但不会被消除,仍会引入稳态误差。

六、一个可复用的采集参数示例

下表给出一组面向步态检测的参考参数,可直接作为起点,再根据实测调整:

参数取值说明
采样档位SENSOR_DELAY_GAME约 50Hz,兼顾步频与功耗
加速度零偏静止 200 样本均值启动时自动标定
陀螺仪零偏静止 200 样本均值启动时自动标定
低通 α0.25截止频率约 2Hz
滑动窗口 N5用于加速度幅值平滑
注销时机onPause避免后台耗电

验证采集是否正常,可以观察三个指标:静止时加速度模长应接近 9.81 m/s²,陀螺仪三轴应接近 0;缓慢翻转设备时,加速度各轴应按重力投影规律变化;快速晃动时,陀螺仪应出现明显非零峰值。若静止时模长偏离超过 0.5 m/s²,或陀螺仪零偏超过 0.05 rad/s,应重新校准或检查传感器是否被系统降频。

Wear OS 部分设备在息屏或低电量模式下会限制传感器采样率,甚至暂停回调。若发现数据中断,应检查是否处于 ambient 模式,并考虑使用 SensorManager.registerListener 的唤醒版本或前台服务保持采集。