首先我们要了解一下概念
(有些问题不是很清楚,手机坐标系和加速计坐标系什么关系,加速计坐标系和磁感应计的坐标系是一样的吗?)
1、手机机身坐标系
加速计,重力感应计,磁感应计均使用上面的坐标系
设大地坐标系重力轴,西向轴和北向轴分别为G,W,N。 下面提到的角度是相对于大地坐标系的角度
Roll:翻滚角,这个翻译可能不准确但是便于理解,就是左右翻滚而不是左右摇摆,当手机处于某状态静止时,手机x轴和世界坐标系WN平面夹角
pitch:俯仰角,手机顶端垂直于面的上下仰头和低头,当手机处于静止状态时,手机y正轴和世界坐标系W,N平面的夹角
azimuth:航向角,头的左右摇摆,当手机处于某状态静止的时候,手机y轴与世界坐标系GN之间的夹角
上面的夹角是个人理解有可能不正确,可以参考另一篇博文。
官方文档上的解释也与我的理解不同,那就相信官方文档吧,尽管我觉得在考虑下面的问题后会很奇怪
上述角度的理解可能稍微有点困难,比如手机头不是水平地面朝向正北的,而是偏向东北,这时候俯仰角pitch就应该不能仅仅是手机y轴和大地坐标系北极轴的夹角,应该是和WN,即地面的夹角。这样的话计算这些角度好像只能是用手机坐标系的某个轴与大地坐标系的某个面的角度才会比较准确。
2、world coordinate system,世界坐标系,关于世界坐标系的概念不是很清楚,尤其是坐标的方向问题,目前找到一个图例,x轴由东向西与地球相切,y轴由南向北有球面相切,z轴是重力轴指向地心。
上面的坐标系是用于计算角度的坐标系原文中也说明这和安卓定义的坐标系不同,android定义的世界坐标系如下
3、下面我们看看获得旋转矩阵的方法
首先调用该方法时的一般参数为
SensorManager.getRotationMatrix(R, null, accAverager.returnAverage(), magAverager.returnAverage());
public static boolean getRotationMatrix(float[] R, float[] I,
float[] gravity, float[] geomagnetic) {
// TODO: move this to native code for efficiency
float Ax = gravity[0];//重力在手机坐标系下的三个轴坐标值
float Ay = gravity[1];
float Az = gravity[2];
final float Ex = geomagnetic[0];//磁感应计在手机坐标系下的三个轴坐标值
final float Ey = geomagnetic[1];
final float Ez = geomagnetic[2];
float Hx = Ey*Az - Ez*Ay;//磁感应和重力叉乘得到水平向西的方向,在手机坐标系下的坐标值
float Hy = Ez*Ax - Ex*Az;
float Hz = Ex*Ay - Ey*Ax;
final float normH = (float)Math.sqrt(Hx*Hx + Hy*Hy + Hz*Hz);//用于归一化
if (normH < 0.1f) {//手机做自由落体运动或者指向正北(指向正北什么意思?手机哪个轴指向正北)
// device is close to free fall (or in space?), or close to
// magnetic north pole. Typical values are > 100.
return false;
}
final float invH = 1.0f / normH;
Hx *= invH;
Hy *= invH;//水平方向坐标值的归一化
Hz *= invH;
final float invA = 1.0f / (float)Math.sqrt(Ax*Ax + Ay*Ay + Az*Az);
Ax *= invA;
Ay *= invA;//重力方向归一化
Az *= invA;
final float Mx = Ay*Hz - Az*Hy;
final float My = Az*Hx - Ax*Hz;//用归一化的重力和水平向西方向叉乘得到归一化的正北方向与地球相切(为什么不直接使用磁感应计方向呢?)
final float Mz = Ax*Hy - Ay*Hx;
if (R != null) {
if (R.length == 9) {
R[0] = Hx; R[1] = Hy; R[2] = Hz;//得到旋转矩阵,x:水平向西的手机坐标表示,而且是归一化数值
R[3] = Mx; R[4] = My; R[5] = Mz;//y:由南向北风向
R[6] = Ax; R[7] = Ay; R[8] = Az;//重力轴方向
} else if (R.length == 16) {
R[0] = Hx; R[1] = Hy; R[2] = Hz; R[3] = 0;
R[4] = Mx; R[5] = My; R[6] = Mz; R[7] = 0;
R[8] = Ax; R[9] = Ay; R[10] = Az; R[11] = 0;
R[12] = 0; R[13] = 0; R[14] = 0; R[15] = 1;
}
}
if (I != null) {
// compute the inclination matrix by projecting the geomagnetic
// vector onto the Z (gravity) and X (horizontal component
// of geomagnetic vector) axes.
final float invE = 1.0f / (float)Math.sqrt(Ex*Ex + Ey*Ey + Ez*Ez);
final float c = (Ex*Mx + Ey*My + Ez*Mz) * invE;//磁感应计单位向量与新得到的南北方向做点乘,其实这里就是求cos角,因为分母都是1
final float s = (Ex*Ax + Ey*Ay + Ez*Az) * invE;//磁感应计单位向量和重力向量做点乘,这里也是求cos角
if (I.length == 9) {
I[0] = 1; I[1] = 0; I[2] = 0;
I[3] = 0; I[4] = c; I[5] = s;
I[6] = 0; I[7] =-s; I[8] = c;
} else if (I.length == 16) {
I[0] = 1; I[1] = 0; I[2] = 0;
I[4] = 0; I[5] = c; I[6] = s;
I[8] = 0; I[9] =-s; I[10]= c;
I[3] = I[7] = I[11] = I[12] = I[13] = I[14] = 0;
I[15] = 1;
}
}
return true;
}
再大体上说一下思路流程,首先是用磁感应计的方向和重力方向(均是手机坐标系表示)做叉乘得到水平向西的方向,然后对重力方向和水平向西的方向做归一化变为单位向量,接着再用重力方向和水平向西的方向做叉乘得到由南向北的方向与地球相切,这样我们就获得了,东西,南北,重力轴的方向(单位向量)在手机坐标系下的表示
By definition:
[0 0 g] = R * gravity (g = magnitude of gravity)
[0 m 0] = I * R * geomagnetic (m = magnitude of geomagnetic field)
The matrices returned by this function are meaningful only when the device is not free-falling and it is not close to the magnetic north. If the device is accelerating, or placed into a strong magnetic field, the returned matrices may be inaccurate.
即当手机做自由落体运动或者靠近北极点的时候,计算结果会受影响
还有一个getInclination不知道做什么的
上面的代码求出了旋转矩阵这个矩阵就是正交基,但是对于旋转矩阵感到难以理解,旋转矩阵有什么用,这个旋转矩阵很像是世界坐标系在手机坐标系的表示,那么世界坐标系的三个轴分别表示什么
至于旋转矩阵为什么有时候是三维有时候是四位这与其次坐标有关,其次坐标主要用于变换是的平移缩放等操作,可以看看这个链接,便于理解
https://wenku.baidu.com/view/5f3c4d4e26d3240c844769eae009581b6bd9bd24
4、关于getOrientation
先看看源码
public static float[] getOrientation(float[] R, float values[]) {
/*
* 4x4 (length=16) case:
* / R[ 0] R[ 1] R[ 2] 0 \
* | R[ 4] R[ 5] R[ 6] 0 |
* | R[ 8] R[ 9] R[10] 0 |
* \ 0 0 0 1 /
*
* 3x3 (length=9) case:
* / R[ 0] R[ 1] R[ 2] \
* | R[ 3] R[ 4] R[ 5] |
* \ R[ 6] R[ 7] R[ 8] /
*
*/
if (R.length == 9) {
values[0] = (float)Math.atan2(R[1], R[4]);//azimuth航向角arctan(Hy,My)
values[1] = (float)Math.asin(-R[7]);//pitch俯仰角arcsin(-Ay)???
values[2] = (float)Math.atan2(-R[6], R[8]);//roll横滚角arctan(-Ax,Az)????不清楚为什么这么算,R的值都是 世界坐标系在手机坐标系上的分量
} else {
values[0] = (float)Math.atan2(R[1], R[5]);
values[1] = (float)Math.asin(-R[9]);
values[2] = (float)Math.atan2(-R[8], R[10]);
}
return values;
}
参数R是通过getRotationMatrix获得的旋转矩阵,values是输出结果,不明白上述求法的原理,正常理解应该是用手机的坐标系和世界坐标系的角度计算,这里为什么只是用旋转矩阵,这个旋转矩阵还是用手机坐标系表示的
说了这么多,那有什么用呢?用处就是标准化磁场或传感器数值。我们知道设备坐标系下的数值基本是没有意义的,因为xyz三个轴的数值完全和设备当前的朝向有关系。而世界坐标系就提供了一个标准化数值的方法。
一个例子就是将计步器的值通过这个办法标准化之后,就可以无视手机朝向来获取比较稳定的世界坐标系下Z轴(即重力方向)的加速度值,对于计步是很有帮助的。但是实际上根据[0 0 g] = R * gravity (g = magnitude of gravity)这句话来看我们如果直接使用R乘以加速计的读数实际得到的还是合加速度的标量值,如果手机做随机的加速运动,这个值就应该不是重力分量值,除非手机静止不动,这个时候不需要管手机方向的问题,但是这种情况下为何不直接取模呢?公式中R * gravity的gravity有可能不是直接的加速计读数,而是过滤掉了其他加速分量,因此旋转矩阵本身对于获得重力分量应该没有意义。这是我个人的观点
一些有用的连接
http://blog.csdn.net/newcoderzZ/article/details/60955974
http://tool.oschina.net/uploads/apidocs/android/guide/topics/sensors/sensors_overview.html#sensors-coords
http://tool.oschina.net/apidocs/apidoc?api=android/reference
http://blog.csdn.net/octobershiner/article/details/6641942
http://blog.csdn.net/pw4work/article/details/72784417
http://blog.csdn.net/zhang11wu4/article/details/49761121
http://bbs.csdn.net/topics/390499143
http://www.cnblogs.com/zuoxiaofei/p/4244238.html
http://blog.csdn.net/u010476094/article/details/44839347