Android驱动笔记(4)——Android Sensor概述
Android平台支持三大类传感器:
- 运动传感器:沿三个轴测量加速力和旋转力。包括:加速度传感器, 重力传感器, 陀螺仪, 旋转矢量传感器。
- 环境传感器:测量各种环境参数,例如:温度和压力,照明和湿度。包括:气压计, 光度计, 温度计。
- 位置传感器:测量设备的物理位置。包括:方向传感器, 地磁传感器。
其中一些传感器基于硬件,另一些基于软件实现。
Sensor Type | Description | Common Uses |
---|---|---|
TYPE_ACCELEROMETER | 硬件 | 三个轴向上设备的加速度m / s2,包括重力。用于动作检测(抖动,倾斜等)。 |
TYPE_ACCELEROMETER_UNCALIBRATED | 硬件 | 三个轴向上设备的加速度m / s2,不包括重力。 |
TYPE_AMBIENT_TEMPERATURE | 硬件 | 以摄氏度(°C)为单位测量环境室温。 |
TYPE_GRAVITY | 软件、硬件 | 三个轴向上设备的加速度m / s2,包括重力。用于动作检测(抖动,倾斜等)。 |
TYPE_GYROSCOPE | 硬件、软件 | 三个轴向中的每一个以rad / s为单位的旋转速率。用于旋转检测(旋转,转动等)。 |
TYPE_LIGHT | 硬件 | 以lux为单位测量环境光水平(照度)。 |
TYPE_LINEAR_ACCELERATION | 软件、硬件 | 三个轴向上设备的加速度m / s2,不包括重力。监控单轴加速度。 |
TYPE_MAGNETIC_FIELD | 硬件 | 以μT为单位测量所有三个物理轴(x,y,z)的环境地磁场,创建指南针。 |
TYPE_ORIENTATION | 软件 | 测量设备围绕所有三个物理轴(x,y,z)旋转的度数。从API级别3开始,可以通过使用重力传感器和地磁场传感器以及getRotationMatrix()方法获得设备的倾斜矩阵和旋转矩阵。确定设备位置。 |
TYPE_PRESSURE | 硬件 | 以hPa或mbar测量环境空气压力。监测气压变化。 |
TYPE_PROXIMITY | 硬件 | 测量相对于设备视图屏幕的对象的接近度(cm)。该传感器通常用于确定手机是否被握在人的耳朵上。通话期间的电话位置。 |
TYPE_RELATIVE_HUMIDITY | 硬件 | 以百分比(%)测量相对环境湿度。监测露点,绝对和相对湿度。 |
TYPE_ROTATION_VECTOR | 软件、硬件 | 通过提供设备旋转矢量的三个元素来测量设备的方向。运动检测和旋转检测。 |
TYPE_TEMPERATURE | 硬件 | 以摄氏度(°C)为单位测量设备的温度。此传感器实现因设备而异,并且此传感器已替换为API级别14中的TYPE_AMBIENT_TEMPERATURE传感器。监测温度。 |
TYPE_STEP_COUNTER | 软件 | 已激活传感器最后一次重新启动以来用户迈出的步数。 |
4.1、运动传感器
Android平台提供了几个传感器监视设备的运动。加速度传感器和陀螺仪传感器始终基于硬件,重力、线性加速度、旋转矢量、有效运动、计步器和步测器传感器可能基于硬件,也可能基于软件。
所有运动传感器都为每个Sensorevent返回传感器值的多维数组。例如,在单个传感器事件期间,加速计返回三个坐标轴的加速度数据,而陀螺仪返回三个坐标轴的旋转速率数据。这些数据值与其他传感器事件参数一起以浮点数组(值)形式返回。
4.1.1、重力加速度传感器
重力传感器提供一个三维矢量,指示重力的方向和大小。通常,该传感器用于确定设备在空间中的相对方向。以下代码说明如何获取默认重力传感器的实例:
private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
- 当设备静止时,重力传感器的输出应与加速计的输出相同。
4.1.2、线性加速度计
线性加速度传感器提供一个三维矢量,表示沿每个设备轴的加速度,不包括重力。可以使用此值执行手势检测。当你想在不受重力影响的情况下获得加速度数据时,通常使用这个传感器。该值也可以作为使用航位推算的惯性导航系统的输入。下面的代码演示如何获取默认线性加速度传感器的实例:
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
4.1.3、旋转矢量传感器
旋转矢量表示设备的角度,该组合为角度和轴线的组合,其中该设备通过围绕轴(x,y,z)的角度θ旋转。旋转矢量的三个元素表示如下:
- x*sin(θ/2)
- y*sin(θ/2)
- z*sin(θ/2)
其中旋转矢量的大小等sin(θ/2),并且旋转矢量的方向等于旋转轴的方向。
旋转矢量的三个元素等于一个旋转单位的最后三个分量(cos(θ/2)、xsin(θ/2)、ysin(θ/2)、z*sin(θ/2))。旋转向量的元素是无单位的。x、y和z轴的定义方式与加速度传感器相同。参考坐标系具有以下特点:
X被定义为矢量乘积y*z,它在设备的当前位置与地面相切,正方向是右手握手机向身体倾斜。Y与设备当前位置的地面相切,Y正即手机正常使用方向。Z指向天空并垂直于地平面。
4.1.4、有效运动传感器(significant motion sensor)
每次检测到有效运动时,有效运动传感器都会触发一个事件,然后它会禁用自身。重要运动是可能导致用户位置改变的运动,例如步行、骑自行车或坐在移动的汽车中。下面的代码演示如何获取默认有效运动传感器的实例以及如何注册事件侦听器:
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);
4.1.5、计步器
计步器提供自上次启动传感器以来用户所采取的步骤数。步进计数器比步进检测器传感器有更多的延迟(最多10秒)但更精确。必须声明 ACTIVITY_RECOGNITION,以便应用程序在运行Android 10(API级别29)或更高版本的设备上使用此传感器。
4.1.6、使用原始数据
以下传感器为应用程序提供有关应用于设备的线性和旋转力的原始数据。为了有效地使用这些传感器的值,需要过滤掉环境中的因素,例如重力。可能还需要对值的趋势应用平滑算法以减少噪声。
概念上,加速度传感器通过使用以下关系测量施加到传感器本身(Fs)的力来确定施加到设备(Ad)上的加速度:
然而,重力总是根据以下关系影响测量加速度:
因此,当设备在桌子上时,加速计读数为g=9.81m/s2。类似地,当设备处于自由落体状态,其加速度计的读数为g=0m/s2。因此,要测量装置的实际加速度,必须从加速度计数据中去除重力的贡献。这可以通过应用高通滤波器来实现。相反,可以使用低通滤波器来隔离重力。下面的示例演示如何执行此操作:
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和Z轴周围的RAD/S中的旋转速率。传感器的坐标系与加速度传感器的坐标系相同。通常,陀螺仪的输出随时间积分,以计算描述角度随时间变化的旋转。
// 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;
}
标准陀螺仪提供原始旋转数据,无需对噪声和漂移(偏差)进行任何滤波或校正。实际上,陀螺仪的噪声和漂移会引入需要补偿的误差。通常通过监视其他传感器(如重力传感器或加速计)来确定漂移(偏差)和噪声。未标定陀螺仪与陀螺仪相似,只是没有对旋转速率应用陀螺仪漂移补偿。旋转速率仍采用工厂校准和温度补偿。未标定陀螺仪可用于方位数据的后处理和融合。
4.2、位置传感器
4.2.1、游戏旋转矢量传感器
除了不使用地磁场,游戏旋转矢量传感器与旋转矢量传感器完全相同。只是Y 轴不会指向北方,而会指向其他参照。当陀螺仪绕 Z 轴漂移时,该参照可以漂移相同的数量级。游戏旋转矢量传感器不使用磁场,因此相对旋转更准确,且不受磁场变化的影响。以下代码展示如何获取默认游戏旋转矢量传感器的实例:
private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR);
4.2.2、计算设备的屏幕方向
通过计算设备的屏幕方向,可以监测设备相对于地球参照系(具体为磁北极)的位置。以下代码展示如何计算设备的屏幕方向:
private SensorManager sensorManager;
...
// Rotation matrix based on current readings from accelerometer and magnetometer.
final float[] rotationMatrix = new float[9];
SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerReading, magnetometerReading);
// Express the updated rotation matrix as three orientation angles.
final float[] orientationAngles = new float[3];
SensorManager.getOrientation(rotationMatrix, orientationAngles);
系统使用设备的地磁场传感器和加速度计来计算屏幕方向的角度。通过使用这两个硬件传感器,系统可提供以下三个屏幕方向角度的数据:
- 方位角(绕 z 轴旋转的角度)。此为设备当前指南针方向与磁北向之间的角度。如果设备的上边缘面朝磁北向,则方位角为 0 度;如果上边缘朝南,则方位角为 180 度。与之类似,如果上边缘朝东,则方位角为 90 度;如果上边缘朝西,则方位角为 270 度。
- 俯仰角(绕 x 轴旋转的角度)。此为平行于设备屏幕的平面与平行于地面的平面之间的角度。如果将设备与地面平行放置,且其下边缘最靠近您,同时将设备上边缘向地面倾斜,则俯仰角将变为正值。沿相反方向倾斜(将设备上边缘向远离地面的方向移动)将使俯仰角变为负值。值的范围为 -180 度到 180 度。
- 倾侧角(绕 y 轴旋转的角度)。此为垂直于设备屏幕的平面与垂直于地面的平面之间的角度。如果将设备与地面平行放置,且其下边缘最靠近您,同时将设备左边缘向地面倾斜,则侧倾角将变为正值。沿相反方向倾斜(将设备右边缘移向地面)将使侧倾角变为负值。值的范围为 -90 度到 90 度。
建议不要使用来自屏幕方向传感器的原始数据,而是结合使用 getRotationMatrix() 方法和 getOrientation() 方法来计算屏幕方向值,如以下代码示例中所示。在这一过程中,您可以使用 remapCoordinateSystem() 方法,将屏幕方向值转换为应用的参照系。
public class SensorActivity extends Activity implements SensorEventListener {
private SensorManager sensorManager;
private final float[] accelerometerReading = new float[3];
private final float[] magnetometerReading = new float[3];
private final float[] rotationMatrix = new float[9];
private final float[] orientationAngles = new float[3];
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Do something here if sensor accuracy changes.
// You must implement this callback in your code.
}
@Override
protected void onResume() {
super.onResume();
// Get updates from the accelerometer and magnetometer at a constant rate.
// To make batch operations more efficient and reduce power consumption,
// provide support for delaying updates to the application.
// In this example, the sensor reporting delay is small enough such that
// the application receives an update before the system checks the sensor
// readings again.
Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (accelerometer != null) {
sensorManager.registerListener(this, accelerometer,
SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI);
}
Sensor magneticField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
if (magneticField != null) {
sensorManager.registerListener(this, magneticField,
SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI);
}
}
@Override
protected void onPause() {
super.onPause();
// Don't receive any more updates from either sensor.
sensorManager.unregisterListener(this);
}
// Get readings from accelerometer and magnetometer. To simplify calculations,
// consider storing these readings as unit vectors.
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
System.arraycopy(event.values, 0, accelerometerReading,
0, accelerometerReading.length);
} else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
System.arraycopy(event.values, 0, magnetometerReading,
0, magnetometerReading.length);
}
}
// Compute the three orientation angles based on the most recent readings from
// the device's accelerometer and magnetometer.
public void updateOrientationAngles() {
// Update rotation matrix, which is needed to update orientation angles.
SensorManager.getRotationMatrix(rotationMatrix, null,
accelerometerReading, mMagnetometerReading);
// "mRotationMatrix" now has up-to-date information.
SensorManager.getOrientation(rotationMatrix, mOrientationAngles);
// "mOrientationAngles" now has up-to-date information.
}
}
4.2.3、使用距离传感器
private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
通常用于确定人的头部距手持设备表面的距离(例如,当用户拨打或接听电话时)。大多数距离传感器返回以厘米为单位的绝对距离,但有些仅返回近距离和远距离值。以下代码展示如何使用近程传感器:
public class SensorActivity extends Activity implements SensorEventListener {
private SensorManager sensorManager;
private Sensor proximity;
@Override
public final void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Get an instance of the sensor service, and use that to get an instance of
// a particular sensor.
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
proximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
}
@Override
public final void onAccuracyChanged(Sensor sensor, int accuracy) {
// Do something here if sensor accuracy changes.
}
@Override
public final void onSensorChanged(SensorEvent event) {
float distance = event.values[0];
// Do something with this sensor data.
}
@Override
protected void onResume() {
// Register a listener for the sensor.
super.onResume();
sensorManager.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onPause() {
// Be sure to unregister the sensor when the activity pauses.
super.onPause();
sensorManager.unregisterListener(this);
}
}
4.3、环境传感器
环境传感器主要就是光线传感器,其他在设备上并不常见。
4.3.1、光线传感器
从光、压力和温度传感器获取的原始数据通常不需要校准、滤波或修改。以下代码说明了如何操作:
public class SensorActivity extends Activity implements SensorEventListener {
private SensorManager sensorManager;
private Sensor pressure;
@Override
public final void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Get an instance of the sensor service, and use that to get an instance of
// a particular sensor.
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
pressure = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
}
@Override
public final void onAccuracyChanged(Sensor sensor, int accuracy) {
// Do something here if sensor accuracy changes.
}
@Override
public final void onSensorChanged(SensorEvent event) {
float millibarsOfPressure = event.values[0];
// Do something with this sensor data.
}
@Override
protected void onResume() {
// Register a listener for the sensor.
super.onResume();
sensorManager.registerListener(this, pressure, SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onPause() {
// Be sure to unregister the sensor when the activity pauses.
super.onPause();
sensorManager.unregisterListener(this);
}
}
4.4、传感器坐标系
通常,传感器框架使用标准的3轴坐标系统来表示数据值。对于大多数传感器,当设备保持在默认方向时,坐标系是相对于设备屏幕定义的。当设备保持在其默认方向上时,X轴是水平的并且指向右边,Y轴是垂直的并且指向上,Z轴指向屏幕表面的外侧。在这个系统中,屏幕后面的坐标有负的Z值。
了解这个坐标系最重要的一点是,当设备的屏幕方向改变时,轴不会交换,也就是说,传感器的坐标系永远不会随着设备的移动而改变。此行为与OpenGL坐标系的行为相同。另一点需要理解的是,应用程序不能假定设备的自然(默认)方向是纵向。许多平板电脑设备的自然定位是横向的。传感器的坐标系总是基于设备的自然方位。
最后,如果应用程序将传感器数据与屏幕显示匹配,则需要使用getRotation()方法确定屏幕旋转,然后使用remapCoordinateSystem()方法将传感器坐标映射到屏幕坐标。即使清单指定仅显示纵向,也需要执行此操作。