Android NDK开发详解传感器之运动传感器

Android 平台提供多种传感器,可让您监视设备的运动。

传感器的可能架构因传感器类型而异:

重力、线性加速度、旋转矢量、有效运动、计步器和步测器传感器可能基于硬件,也可能基于软件。
加速度计传感器和陀螺仪传感器始终基于硬件。

大多数 Android 设备都配有加速度计,而现在许多设备都带有陀螺仪。基于软件的传感器的可用性更具可变性,因为其通常依赖一个或多个硬件传感器来获取其数据。根据设备的不同,这些基于软件的传感器可以从加速度计和磁力计或陀螺仪中获取其数据。

运动传感器在监控设备运动方面(例如倾斜、晃动、旋转或摆动)非常有用。该移动通常是用户直接输入的反映(例如,用户在游戏中驾驶汽车,或在游戏中控制球),但也可能反映设备所处的物理环境(例如,在开车时与您一起移动)。在第一种情况下,您正在监控相对于设备参照系或应用参照系的运动;在第二种情况下,您正在监控相对于世界参照系的运动。运动传感器本身通常不用于监视设备位置,但可以与其他传感器(例如地磁场传感器)一起使用,以确定设备相对于世界参照系的位置(如需了解详细信息,请参阅位置传感器)。

所有运动传感器都为每个 SensorEvent 返回传感器值的多维数组。例如,在单个传感器事件期间,加速度计返回三个坐标轴的加速力数据,而陀螺仪返回三个坐标轴的旋转速率数据。这些数据值与其他 SensorEvent 参数一起在 float 数组中 (values) 返回。表 1 总结了在 Android 平台上可用的运动传感器。

表 1. Android 平台支持的运动传感器。
在这里插入图片描述
在这里插入图片描述

1 标量分量是一个可选值。

旋转矢量传感器和重力传感器是运动检测和监控的最常用传感器。旋转矢量传感器极具通用性,可用于各种与运动有关的任务,例如检测手势,监控角度变化,以及监控相对屏幕方向变化。例如,旋转矢量传感器是您开发游戏、增强现实应用、二维或三维指南针,或者相机稳定应用的理想选择。在大多数情况下,使用这些传感器比使用加速度计和地磁场传感器或方向传感器更好。

Android 开源项目传感器

Android 开源项目 (AOSP) 提供以下三个基于软件的运动传感器:重力传感器、线性加速传感器和旋转矢量传感器。这些传感器已 Android 4.0 中完成更新,现在使用设备的陀螺仪(除其他传感器之外)来提高稳定性和性能。如果您要试用这些传感器,则可以使用 getVendor() 方法和 getVersion() 方法(供应商为 Google LLC;版本号为 3)来识别它们。您必须通过供应商和版本号来识别这些传感器,因为 Android 系统将这三个传感器视为辅助传感器。例如,如果设备制造商提供其自己的重力传感器,则 AOSP 重力传感器将显示为辅助重力传感器。所有这三个传感器都依赖陀螺仪:如果设备没有陀螺仪,这些传感器将不会显示并且无法使用。

使用重力传感器

重力传感器提供指示重力方向和大小的三维矢量。通常,此传感器用于确定设备在空间中的相对屏幕方向。以下代码展示如何获取默认重力传感器的实例:
Kotlin

val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);

单位与加速度传感器所用的单位 (m/s2) 相同,坐标系与加速传感器使用的坐标系相同。

注意:当设备处于静止状态时,重力传感器的输出应与加速度计的输出相同。

使用线性加速度计

线性加速传感器为您提供了一个三维矢量,表示沿着每个设备轴的加速度(不包括重力)。您可以使用此值执行手势检测。该值还可以用作惯性导航系统的输入值,该系统使用航位推测法。以下代码展示如何获取默认线性加速传感器的实例:
Kotlin

val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);

从概念上讲,此传感器根据以下关系为您提供加速度数据:

linear acceleration = acceleration - acceleration due to gravity

当您想获取加速度数据而不受重力影响时,通常会使用此传感器。例如,您可以使用此传感器查看汽车行驶的速度。线性加速度传感器始终具有一个偏移量,您需要将其删除。最简单的方法是在应用中构建一个校准步骤。在校准期间,您可以要求用户将设备放在桌子上,然后读取所有三个轴的偏移量。然后,您可以从加速传感器的直接读数中减去该偏移量,以获得实际的线性加速度。

传感器坐标系与加速度传感器使用的坐标系相同,计量单位 (m/s2) 也相同。

使用旋转矢量传感器

旋转矢量将设备的屏幕方向表示为角度和轴的组合,其中设备已围绕轴(x、y 或 z)旋转了 θ 度。以下代码展示如何获取默认旋转矢量传感器的实例:
Kotlin

val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);

旋转矢量的三个元素表示如下:
在这里插入图片描述

其中旋转矢量的大小等于 sin(θ/2),并且旋转矢量的方向等于旋转轴的方向。

表 1. 旋转矢量传感器使用的坐标系。
在这里插入图片描述

旋转矢量的三个元素等于单位四元数(cos(θ/2)、xsin(θ/2)、ysin(θ/2)、z*sin(θ/2))的最后三个分量。旋转矢量的元素没有单位。x、y 和 z 轴的定义方法与加速传感器的定义方法相同。参考坐标系被定义为直接正交基础(见图 1)。该坐标系具有以下特征:

X 定义为矢量积 Y x Z。其在设备当前位置与地面相切,并大约指向东。
Y 在设备当前位置与地面相切,并指向地磁北极。
Z 指向天空并与地平面垂直。

有关展示如何使用旋转矢量传感器的示例应用,请参阅 RotationVectorDemo.java。

使用有效运动传感器

每次检测到有效运动时,有效运动传感器都会触发事件,然后将其禁用。有效运动是可能导致用户位置发生变化的运动;例如步行、骑自行车或坐在行驶的车上。以下代码展示如何获取默认有效运动传感器的实例以及如何注册事件侦听器:
Kotlin

val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val mSensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION)
val triggerEventListener = object : TriggerEventListener() {
    override fun onTrigger(event: TriggerEvent?) {
        // Do work
    }
}
mSensor?.also { sensor ->
    sensorManager.requestTriggerSensor(triggerEventListener, sensor)
}

Java

private SensorManager sensorManager;
private Sensor sensor;
private TriggerEventListener triggerEventListener;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);

triggerEventListener = new TriggerEventListener() {
    @Override
    public void onTrigger(TriggerEvent event) {
        // Do work
    }
};

sensorManager.requestTriggerSensor(triggerEventListener, mSensor);

如需了解详细信息,请参阅 TriggerEventListener。

使用计步器传感器

计步器传感器提供自已激活传感器后最后一次重启以来用户迈出的步数。与步测器传感器相比,计步器的延迟时间更长(最多 10 秒),但精确度更高。

注意:您必须声明 ACTIVITY_RECOGNITION 权限,以便您的应用在运行 Android 10 (API 级别 29) 或更高版本的设备上使用此传感器。

以下代码展示如何获取默认计步器传感器的实例:
Kotlin

val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);

如要保护运行应用的设备上的电池,您应使用 JobScheduler 类,从而以特定的时间间隔从计步器传感器检索当前值。尽管不同类型的应用需要不同的传感器读取间隔,但是除非您的应用需要来自传感器的实时数据,否则应尽可能延长此间隔。

使用步测器传感器

每次用户迈步时,步测器传感器都会触发事件。延迟时间预计将低于 2 秒。

注意:您必须声明 ACTIVITY_RECOGNITION 权限,以便您的应用在运行 Android 10 (API 级别 29) 或更高版本的设备上使用此传感器。

以下代码展示如何获取默认步测器传感器的实例:
Kotlin

val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);

使用原始数据

以下传感器可为您的应用提供有关施加到设备的线性力和旋转力的原始数据。为了有效使用这些传感器的值,您需要从环境中滤除重力等因素。您可能还需要对值趋势应用平滑算法以减少噪声。

使用加速度计

加速度传感器测量施加到设备的加速度,包括重力。以下代码展示如何获取默认加速传感器的实例:
Kotlin


val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)

Java

private SensorManager sensorManager;
private Sensor sensor;
  ...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

从概念上讲,加速度传感器通过使用以下关系测量施加到传感器本身的力 (Fs) 来确定施加到设备的加速度 (Ad):
在这里插入图片描述

但是,重力始终根据以下关系影响测量的加速度:
在这里插入图片描述

因此,当设备位于桌子上(不加速)时,加速度计的读数为 g = 9.81 m/s2。同样,当设备自由落体并因此以 9.81 m/s2 的速度快速向地面加速时,其加速度计的读数为 g = 0 m/s2。因此,要测量设备的实际加速度,必须从加速度计数据中移除重力的作用。这可以通过应用高通滤波器来实现。相反,您可以使用低通滤波器来隔离重力。以下示例展示如何执行此操作:
Kotlin

override fun onSensorChanged(event: SensorEvent) {
    // In this example, alpha is calculated as t / (t + dT),
    // where t is the low-pass filter's time-constant and
    // dT is the event delivery rate.

    val alpha: Float = 0.8f

    // Isolate the force of gravity with the low-pass filter.
    gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0]
    gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1]
    gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2]

    // Remove the gravity contribution with the high-pass filter.
    linear_acceleration[0] = event.values[0] - gravity[0]
    linear_acceleration[1] = event.values[1] - gravity[1]
    linear_acceleration[2] = event.values[2] - gravity[2]
}

Java

public void onSensorChanged(SensorEvent event){
    // In this example, alpha is calculated as t / (t + dT),
    // where t is the low-pass filter's time-constant and
    // dT is the event delivery rate.

    final float alpha = 0.8;

    // Isolate the force of gravity with the low-pass filter.
    gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0];
    gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1];
    gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2];

    // Remove the gravity contribution with the high-pass filter.
    linear_acceleration[0] = event.values[0] - gravity[0];
    linear_acceleration[1] = event.values[1] - gravity[1];
    linear_acceleration[2] = event.values[2] - gravity[2];
}


注意:您可以使用许多不同的技术来过滤传感器数据。以上代码示例使用简单的过滤器常数 (alpha) 来创建低通滤波器。此过滤器常数来自于一个时间常数 (t),该常数大致表示过滤器添加到传感器事件的延迟时间,以及传感器的事件传输率 (dt)。该代码示例使用 0.8 的 alpha 值进行演示。如果您使用此过滤方法,则可能需要选择其他 alpha 值。

加速度计使用标准的传感器坐标系。实际上,这意味着当设备以自然屏幕方向平放在桌子上时,以下条件适用:

如果将设备推向左侧(因此向右移动),则 x 加速度值为正。
如果将设备推到底部(因此它向远离您的方向移动),则 y 加速度值为正。
如果您以 A m/s2 的加速度将设备推向天空,则 z 加速度值等于 A + 9.81,该值对应设备的加速度 (+A m/s2) 减去重力 (-9.81 m/s2)。
固定设备的加速度值为 +9.81,该值对应设备的加速度(0 m/s2 减去重力 -9.81 m/s2)。

通常,如果要监控设备的运动,加速度计是一个很好的传感器。几乎所有运行 Android 的手机和平板电脑都具有加速度计,其功耗比其他运动传感器低约 10 倍。一个缺点是您可能必须实现低通和高通滤波器,以消除重力并降低噪声。

Android SDK 提供一个示例应用,展示如何使用加速传感器 (Accelerometer Play)。

使用陀螺仪

陀螺仪测量围绕设备的 x、y 和 z 轴的旋转速率(弧度/秒)。以下代码展示如何获取默认陀螺仪的实例:
Kotlin

val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);

传感器的坐标系与用于加速传感器的坐标系相同。逆时针方向旋转为正;也就是说,如果观察者从 x、y 或 z 轴上某个正位置看向位于原点的设备,则在该设备看起来是逆时针旋转的情况下,该观察者将报告正旋转。这是正向旋转的标准数学定义,与方向传感器使用的侧倾定义不同。

通常,陀螺仪的输出会随时间积分,以计算描述角度随时间步长变化的旋转。例如:
Kotlin

// Create a constant to convert nanoseconds to seconds.
private val NS2S = 1.0f / 1000000000.0f
private val deltaRotationVector = FloatArray(4) { 0f }
private var timestamp: Float = 0f

override fun onSensorChanged(event: SensorEvent?) {
    // This timestep's delta rotation to be multiplied by the current rotation
    // after computing it from the gyro sample data.
    if (timestamp != 0f && event != null) {
        val dT = (event.timestamp - timestamp) * NS2S
        // Axis of the rotation sample, not normalized yet.
        var axisX: Float = event.values[0]
        var axisY: Float = event.values[1]
        var axisZ: Float = event.values[2]

        // Calculate the angular speed of the sample
        val omegaMagnitude: Float = sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ)

        // Normalize the rotation vector if it's big enough to get the axis
        // (that is, EPSILON should represent your maximum allowable margin of error)
        if (omegaMagnitude > EPSILON) {
            axisX /= omegaMagnitude
            axisY /= omegaMagnitude
            axisZ /= omegaMagnitude
        }

        // Integrate around this axis with the angular speed by the timestep
        // in order to get a delta rotation from this sample over the timestep
        // We will convert this axis-angle representation of the delta rotation
        // into a quaternion before turning it into the rotation matrix.
        val thetaOverTwo: Float = omegaMagnitude * dT / 2.0f
        val sinThetaOverTwo: Float = sin(thetaOverTwo)
        val cosThetaOverTwo: Float = cos(thetaOverTwo)
        deltaRotationVector[0] = sinThetaOverTwo * axisX
        deltaRotationVector[1] = sinThetaOverTwo * axisY
        deltaRotationVector[2] = sinThetaOverTwo * axisZ
        deltaRotationVector[3] = cosThetaOverTwo
    }
    timestamp = event?.timestamp?.toFloat() ?: 0f
    val deltaRotationMatrix = FloatArray(9) { 0f }
    SensorManager.getRotationMatrixFromVector(deltaRotationMatrix, deltaRotationVector);
    // User code should concatenate the delta rotation we computed with the current rotation
    // in order to get the updated rotation.
    // rotationCurrent = rotationCurrent * deltaRotationMatrix;
}

Java

// Create a constant to convert nanoseconds to seconds.
private static final float NS2S = 1.0f / 1000000000.0f;
private final float[] deltaRotationVector = new float[4]();
private float timestamp;

public void onSensorChanged(SensorEvent event) {
    // This timestep's delta rotation to be multiplied by the current rotation
    // after computing it from the gyro sample data.
    if (timestamp != 0) {
      final float dT = (event.timestamp - timestamp) * NS2S;
      // Axis of the rotation sample, not normalized yet.
      float axisX = event.values[0];
      float axisY = event.values[1];
      float axisZ = event.values[2];

      // Calculate the angular speed of the sample
      float omegaMagnitude = sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ);

      // Normalize the rotation vector if it's big enough to get the axis
      // (that is, EPSILON should represent your maximum allowable margin of error)
      if (omegaMagnitude > EPSILON) {
        axisX /= omegaMagnitude;
        axisY /= omegaMagnitude;
        axisZ /= omegaMagnitude;
      }

      // Integrate around this axis with the angular speed by the timestep
      // in order to get a delta rotation from this sample over the timestep
      // We will convert this axis-angle representation of the delta rotation
      // into a quaternion before turning it into the rotation matrix.
      float thetaOverTwo = omegaMagnitude * dT / 2.0f;
      float sinThetaOverTwo = sin(thetaOverTwo);
      float cosThetaOverTwo = cos(thetaOverTwo);
      deltaRotationVector[0] = sinThetaOverTwo * axisX;
      deltaRotationVector[1] = sinThetaOverTwo * axisY;
      deltaRotationVector[2] = sinThetaOverTwo * axisZ;
      deltaRotationVector[3] = cosThetaOverTwo;
    }
    timestamp = event.timestamp;
    float[] deltaRotationMatrix = new float[9];
    SensorManager.getRotationMatrixFromVector(deltaRotationMatrix, deltaRotationVector);
    // User code should concatenate the delta rotation we computed with the current rotation
    // in order to get the updated rotation.
    // rotationCurrent = rotationCurrent * deltaRotationMatrix;
}

标准陀螺仪可提供原始旋转数据,而无需对噪声和漂移(偏差)进行任何过滤或校正。实际上,陀螺仪的噪声和漂移会引入需要补偿的误差。通常,您可以通过监控其他传感器(例如重力传感器或加速度计)来确定漂移(偏差)和噪声。
使用未经校准的陀螺仪

未经校准的陀螺仪与陀螺仪类似,不同之处在于没有陀螺漂移补偿应用于旋转速率。出厂校准和温度补偿仍应用于旋转速率。未经校准的陀螺仪可用于后期处理和融合屏幕方向数据。通常,gyroscope_event.values[0] 将接近 uncalibrated_gyroscope_event.values[0] - uncalibrated_gyroscope_event.values[3]。即,

calibrated_x ~= uncalibrated_x - bias_estimate_x

注意:未经校准的传感器可提供更多的原始结果,并且可能会包含一定偏差,但其测量值包含的从应用的校正到校准的跳跃次数更少。某些应用可能更喜欢这些未经校准的结果,因为它们更平滑、更可靠。例如,如果应用尝试进​​行自己的传感器融合,则引入校准实际上可能会扭曲结果。

除了旋转速率外,未经校准的陀螺仪还会提供围绕每个轴的估算漂移。以下代码展示如何获取默认未经校准的陀螺仪的实例:
Kotlin

val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED);

其他代码示例

AccelerometerPlay 示例和 Android BatchStepSensor 示例进一步演示了本页所涵盖的 API 的用法。

另请阅读

传感器
传感器概览
位置传感器
环境传感器

本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。

最后更新时间 (UTC):2023-11-02。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五一编程

程序之路有我与你同行

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值