Android平台提供了一些传感器让我们可以监测设备的运动情况. 其中的两种总是基于硬件的(加速度计和陀螺仪), 其中的三个是能软能硬的(重力计, 线性加速计和旋转矢量传感器). 比如, 在一些设备上基于软件的传感器会从加速度计和磁力计来计算它们的数据, 但是在其它设备上它们可能也使用陀螺仪来作为它们的数据来源. 大多数Android设备都拥有一个加速度计, 很多都已经包含陀螺仪. 基于软件的传感器的可用性更加多样, 因为它们通常依赖于一个或一个以上的硬件传感器来作为数据来源.
运动传感器顾名思义是用于监测设备移动的, 比如倾斜, 摇动, 旋转或摆动等. 这种运动通常来源于用户的输入(栗如, 用户在汽车类的游戏中转动设备或者控制球类游戏等), 但是它也可以来自于设备本身的位置(比如设备跟着汽车一起运动). 第一个栗子中, 监测的是相对于设备作为参考框架. 第二个例子中则是相对于全世界做参考框架. 运动传感器本身通常不用来监测设备位置, 但是可以同其它传感器一起使用, 比如地磁传感器, 来确定设备相对于世界中的位置.
所有的运动传感器都会在SensorEvent中返回传感器值的多维数组. 比如, 在一个传感器事件中, 加速计会为坐标系的三个方向返回加速度数据, 陀螺仪返回三维旋转速率数据. 这些数据值以float数组格式同其它SensorEvent参数一起返回. 下表总结了Android平台上可用的运动传感器:
传感器 |
SensorEvent数据 |
描述 |
测量单位 |
TYPE_ACCELEROMETER |
SensorEvent.values[0] |
沿x轴的加速度(包括重力) |
m/s2(平方) |
SensorEvent.values[1] |
沿y轴的加速度(包括重力) |
||
SensorEvent.values[2] |
沿z轴的加速度(包括重力) |
||
TYPE_GRAVITY |
SensorEvent.values[0] |
沿x轴的重力 |
m/s2 |
SensorEvent.values[1] |
沿y轴的重力 |
||
SensorEvent.values[2] |
沿z轴的重力 |
||
TYPE_GYROSCOPE |
SensorEvent.values[0] |
绕x轴旋转的速率 |
rad/s |
SensorEvent.values[1] |
绕y轴旋转的速率 |
||
SensorEvent.values[2] |
绕z轴旋转的速率 |
||
TYPE_GYROSCOPE_UNCALIBRATED |
SensorEvent.values[0] |
绕x轴的旋转速率(无偏移补偿) |
rad/s |
SensorEvent.values[1] |
绕y轴的旋转速率(无偏移补偿) |
||
SensorEvent.values[2] |
绕z轴的旋转速率(无偏移补偿) |
||
SensorEvent.values[3] |
绕x轴的预计偏移 |
||
SensorEvent.values[4] |
绕y轴的预计偏移 |
||
SensorEvent.values[5] |
绕z轴的预计偏移 |
||
TYPE_LINEAR_ACCELERATION |
SensorEvent.values[0] |
沿x轴的加速度(不包括重力) |
m/s2 |
SensorEvent.values[1] |
沿y轴的加速度(不包括重力) |
||
SensorEvent.values[2] |
沿z轴的加速度(不包括重力) |
||
TYPE_ROTATION_VECTOR |
SensorEvent.values[0] |
沿x轴旋转矢量分量(x*sin(θ/2)). |
无单位 |
SensorEvent.values[1] |
沿y轴旋转矢量分量(y*sin(θ/2)). |
||
SensorEvent.values[2] |
沿z轴旋转矢量分量(z*sin(θ/2)). |
||
SensorEvent.values[3] |
旋转向量的标量分量((cos(θ/2)).(标量分量是可选的) |
||
TYPE_SIGNIFICANT_MOTION |
N/A |
N/A |
N/A |
TYPE_STEP_COUNTER |
SensorEvent.values[0] |
从传感器激活那一刻起的步数 |
步数 |
TYPE_STEP_DETECTOR |
N/A |
N/A |
N/A |
旋转矢量传感器和重力传感器是运动检测中使用频率最高的传感器. 旋转矢量传感器是多功能的, 可以广泛的用于运动相关的任务, 比如检测手势, 检测角度改变, 检测相关的方向变化. 在开发游戏的时候经常用到旋转矢量传感器, 还有增强现实的应用, 二维或者三维的罗盘, 或者一个相机稳定的APP. 在大多数的情况下, 使用这些传感器会比用加速度计和磁力计或者方向传感器更好.
Android Open Source Project(AOSP)提供了三种基于软件的运动传感器: 一个重力传感器, 一个线性加速度传感器, 还有一个旋转矢量传感器. 这些传感器在Android 4.0中添加, 现在使用设备的陀螺仪来提高稳定性和性能. 如果想要尝试一下这些传感器, 可以使用getVendor()和getVersion()方法(vendor是Google公司, 版本号是3). 使用vendor和version来识别这些传感器是必要的, 因为Android将这三个传感器作为辅助传感器. 比如如果一个设备供应商提供了它们的重力传感器, 那么AOSP重力传感器将会作为一个辅助备用的传感器来显示. 所有的这三个传感器都依赖于陀螺仪: 如果设备没有陀螺仪, 这些传感器将不会显示也不能用.
加速度传感器用来测量设备的加速度, 包括重力. 下面的代码演示了如何获取一个加速度传感器的实例:
private SensorManager mSensorManager;
private Sensor mSensor;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
理论上讲, 一个加速度传感器确定加速度通过测量应用在传感器本身的力(Fs)来计算出应用在设备上的力(Ad), 然后使用这个公式一套:
Ad = - ∑Fs / mass
然而根据这个公式重力会总是影响到测量的结果:
Ad = -g - ∑F / mass
根据这个原因, 当设备放在桌子上(没有被加速), 加速度传感器会读取到一个值为g=9.81m/s2. 同样, 当设备在自由落体的时候, 加速度读取到的值为g=0m/s2. 因此想要测量到真实的加速度, 必须将重力移除. 这可以通过一个high-pass过滤器实现. 相反, 一个low-pass过滤器可以被用来隔离出重力. 栗子:
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)来创建一个low-pass过滤器. 过滤器常量是从一个时间常量衍生的(t). 如果想要使用该方法, 那么可能需要更换alpha.
加速度计使用标准的传感器坐标系统. 在实践中, 这意味着当设备被放在水平的桌子上并处于自然方向的时候下面的条件适用:
l 如果将设备往右边推, x加速度值为正.
l 如果从设备底部推(这样设备会远离你), y加速度为正.
l 如果将设备往天上推, 并且加速度为Am/s2,z加速度将会等于A+9.81, 也就是设备加速度加重力加速度.
l 静止的设备拥有一个等于9.81的加速度.
通常如果想要监测设备的运动的话, 加速度计是一个很好的传感器. 几乎每个Android的设备都带有一个加速度计, 它比其它的传感器消耗更少的能量(10倍). 缺陷则是我们得自己实现对重力的过滤和降噪.
重力传感器提供了三维矢量来描述重力的方向和大小. 下面代码可以让我们取得重力传感器的实例:
private SensorManager mSensorManager;
private Sensor mSensor;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
单位和坐标系跟加速度计一样. 设备静止的时候, 重力传感器和加速度传感器输出的结果应该相同.
陀螺仪测量的是设备x,y,z轴的旋转速度, 单位是rad/s. 同样, 获取陀螺仪的实例代码如下:
private SensorManager mSensorManager;
private Sensor mSensor;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
陀螺仪的坐标系跟加速度计一样. 逆时针旋转为正; 这意味着如果观察者从x, y, 或者z轴的正方向看过去, 设备逆时针旋转, 则为正向. 这是正方向的标准定义, 不同于方向传感器的定义. 通常陀螺仪的输出是整数, 随着时间的推移按照时间戳计算旋转的度数, 栗子:
// 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) {
// Thistimestep's delta rotation to be multiplied by the current rotation
// aftercomputing it from the gyro sample data.
if (timestamp != 0) {
final float dT = (event.timestamp - timestamp) * NS2S;
// Axis of therotation sample, not normalized yet.
float axisX = event.values[0];
float axisY = event.values[1];
float axisZ = event.values[2];
// Calculate theangular speed of the sample
float omegaMagnitude = sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ);
// Normalize therotation 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;
}
// Integratearound this axis with the angular speed by the timestep
// in order toget a delta rotation from this sample over the timestep
// We willconvert this axis-angle representation of the delta rotation
// into aquaternion 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 codeshould concatenate the delta rotation we computed with the current rotation
// in order toget the updated rotation.
//rotationCurrent = rotationCurrent * deltaRotationMatrix;
}
}
标准陀螺仪提供原始旋转数据而不经任何过滤, 也不去纠正噪音和偏差. 实践中, 陀螺仪噪音和偏差将会引发错误, 所以需要纠正它们. 我们经常检查通过检查其它的传感器来计算偏差和噪音, 比如重力传感器或者加速度计.
为校准的陀螺仪跟陀螺仪相似, 不同的是它没有对旋转速率进行陀螺偏移补偿. 工厂校准和温度补偿依然应用于旋转速率. 为校准陀螺仪对后期处理和融合方向数据很有用. 通常, gyroscope_event.value[0]将会跟uncalibrated_gyroscope_event.values[0]- uncalibrated_gyroscope_event.values[3]接近. 就是,
calibrated_x ~= uncalibrated_x -bias_estimate_x
注意: 为校准陀螺仪提供了更多的原始数据并存在一些偏差, 但是它们的测量通过校准可以包含更少的跳变. 一些APP可能更喜欢这些为校准的结果, 因为更加的平滑和可靠. 栗如, 如果一个APP尝试实现自己的传感器合成, 引入校准实际上会扭曲结果.
除了旋转速率, 为校准陀螺仪还为各轴提供了预计的偏差. 下面的代码演示了如何获取一个陀螺仪实例:
private SensorManager mSensorManager;
private Sensor mSensor;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED);
线性加速传感器提供了一个代表加速度的三维矢量, 但是不包含重力. 下面代码演示了如何获取一个它的实例:
private SensorManager mSensorManager;
private Sensor mSensor;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
从概念上讲, 该传感器提供的加速数据是根据这个公式:
linear acceleration = acceleration - acceleration due to gravity
我们通常在想要获取不包含重力的加速数据的时候使用这个传感器. 栗如, 可以使用这个传感器来看汽车行驶的多快. 线性加速传感器总是有一个偏移, 我们应该移除这个偏移. 最简单的实现这个操作的方法是在APP中创建一个校准步骤. 校准过程中我们可以请求用户将设备放置于一个桌子上, 然后读取三个轴的偏移. 然后减去这个偏移来获得真实的线性加速度. 它的坐标系和加速度传感器一样, 单位也是m/s2.
旋转矢量(向量)用角度和数值的组合表示设备的方向, 设备绕轴旋转的角度为θ. 下面的代码展示了如何获取默认的旋转矢量传感器:
private SensorManager mSensorManager;
private Sensor mSensor;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
旋转矢量的三个元素表达如下:
x*sin(θ/2)
y*sin(θ/2)
z*sin(θ/2)
其中旋转矢量的大小为sin(θ/2), 方向为各轴的方向.
旋转矢量传感器使用的坐标器. 这个球是地球.
旋转矢量的三个元素等于一个四元数的最后三个组成部分(cos(θ/2), x*sin(θ/2), y*sin(θ/2), z*sin(θ/2)). 旋转矢量元素是无单位的. X,y和z轴的定义跟加速度计一样. 坐标系定义为一个标准正交基(上图). 该坐标系统有下面三个特性:
X定义为矢量积Y乘以Z. 它在设备当前所处的位置与地面相切, 并指向东边.
Y也在设备当前的位置与地面相切, 并指向地磁北极.
Z则朝向天空并垂直于地面.
Android sample中包含使用旋转矢量传感器的栗子.
每次检测到重要运动的时候出发传感器然后关闭自己. 一个重要运动是指可以导致用户坐标改变的运动. 栗如, 走路, 骑自行车, 或者坐在一辆运行的汽车中. 下面的代码演示了如何获得一个该传感器的实例, 还有如何注册一个事件监听器:
private SensorManager mSensorManager;
private Sensor mSensor;
private TriggerEventListener mTriggerEventListener;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
mTriggerEventListener = new TriggerEventListener() {
@Override
public void onTrigger(TriggerEvent event) {
// Do work
}
};
mSensorManager.requestTriggerSensor(mTriggerEventListener, mSensor);
步数传感器可以从上一次重启之后(步数传感器被激活)用户走过的总步数. 步数传感器有更多的延迟(最多可以达到10s)但是比步检测传感器(step detector sensor, 下面介绍)更加精确. 栗子:
private SensorManager mSensorManager;
private Sensor mSensor;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
步检测传感器当每次用户迈出一步的时候触发一次. 延迟通常低于2s. 栗子:
private SensorManager mSensorManager;
private Sensor mSensor;
...
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
参考: https://developer.android.com/guide/topics/sensors/sensors_motion.html