1. 程式人生 > >Android感測器 裝置座標系到世界座標系的轉換 分析以及應用

Android感測器 裝置座標系到世界座標系的轉換 分析以及應用

首先分別介紹兩種座標系的不同之處:

世界座標系:

Y軸:和地球表面正切,並且指向磁北極
Z軸:和地球表面垂直,然後指向地球的中心
X軸:和Y,Z垂直,並且幾乎是直接指向磁東
關於X軸和Y軸的圖例

裝置座標系:

這個很好理解,垂直手機螢幕那一面向上為Z軸,向右為x,垂直於XZ面的即為Y軸。看圖應該更清楚了。
裝置座標系圖片

由裝置座標轉換為世界座標:

將得到的原始感測器值(即裝置座標下的值)和座標軸的旋轉矩陣相乘即可。

如何得到旋轉矩陣?

Android提供了一個現成的方法:
SensorManager.getRotationMatrix(R,I,gravityRawValues,magFieldRawValue);

官方這麼寫的

Computes the inclination matrix I as well as the rotation matrix R transforming a vector from the device coordinate system to the world’s coordinate system which is defined as a direct orthonormal basis, where:

就是傳入裝置座標系下重力向量和磁場向量,就能得到一個矩陣R,能夠從裝置座標系轉換到世界座標系的矩陣,以及一個矩陣I(傾斜矩陣與今天話題無關,不提)。

重力和磁力向量怎麼來呢?

Android提供了現成的重力感測器TYPE_GRAVITY,可以直接註冊監聽來獲取。不過其實這個重力感測器和方向感測器一樣,是一個虛擬感測器,實際也是通過加速度計低通濾波之後拿到的。
(低通濾波過濾加速度計的非重力因素導致的加速度變化)

磁場向量比較容易,系統直接可以提供。

getRotationMatrix核心方法解析

旋轉矩陣的計算思路:其實這個是“變基”了,從原來的裝置座標系的基轉換為世界座標系,那麼計算出世界座標系的三個座標軸的單位向量在裝置座標系下的向量值,然後按行順序依次填入3*3或者4*4的矩陣內,即可得到相應的旋轉矩陣。
之所以可以這樣求出旋轉矩陣,可以這麼理解:由矩陣運算規則,表示世界座標系X的矩陣和任意一個向量A做積的和,幾何上看,這就是投影啊,求的就是向量A投影到世界座標系X上的長度,也就是向量A在世界座標系下的x分量的值,同理求出y分量和z分量,就完成了裝置座標系和世界座標系的轉換啦

那麼現在的問題就是如何得到在裝置座標系下的世界座標系的三個座標軸的單位向量

這裡有兩個假設
1. 傳入的地磁場的向量在由重力和磁北極構成的平面之內
2. 傳入的重力向量指向的是地心
官方文件也給出這樣的說明:

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.

不允許裝置處於自由落體並且不是接近磁北極。如果裝置處於加速狀態或者強磁場中,返回的旋轉矩陣就不可靠了。

為什麼有這樣的限制?

主要和計算原理有關係:
首先由地磁場向量和重力向量可以得到一個平面A,而地磁場向量和重力向量叉乘積可以得到垂直於平面A的一個向量,這個向量代表的就是地磁以東這個向量(在裝置座標系之下的地磁東),也就是世界座標系X軸在裝置座標系下的向量。
同理,再由地磁東向量和重力向量向量的叉乘積就可以得到指向磁南極的向量了。也就是世界座標系Y軸在裝置座標系下的向量。
而Z軸就是重力向量了。
然後把單位化之後的三個世界座標系的向量值依次填入矩陣內就可以得到“裝置座標系到世界座標系”的旋轉矩陣了。

加了註釋的原始碼:

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 normsqA = (Ax*Ax + Ay*Ay + Az*Az);
    final float g = 9.81f;
    final float freeFallGravitySquared = 0.01f * g * g;
    if (normsqA < freeFallGravitySquared) {
        // gravity less than 10% of normal value
 return false;
    }

    final float Ex = geomagnetic[0];
    final float Ey = geomagnetic[1];
    final float Ez = geomagnetic[2];
    float Hx = Ey*Az - Ez*Ay;//H是東。計算磁場和地面兩個向量的的叉乘積,就是東了
    float Hy = Ez*Ax - Ex*Az;
    float Hz = Ex*Ay - Ey*Ax;
    final float normH = (float)Math.sqrt(Hx*Hx + Hy*Hy + Hz*Hz);//得到東方向量的大小normH

    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;
            R[3] = Mx;     R[4] = My;     R[5] = Mz;
            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;
        }
    }
    .....
    return true;
}

一些現實的坑

上面的方法的確看上去挺完美的呢,但是!
磁力和加速度感測器的數值還是很容易收到外界干擾的。裝置又沒有額外的方式來區分到底是地磁還是電腦造成的干擾(就像人類不能抓著頭髮進行自舉)。
磁場感測器倒是提供了一個獲取磁場“可信度”的方法,但是目測只是根據數值的合理程度來給出評價,而不是我們想要的區分地磁和其他磁場干擾。
繞八字校準?個人推測照樣也是看可信度而已。
而加速度的話,前面已經提到了,用低通或者系統自帶的重力感測器,倒是還好。
所以應該要考慮從資料處理上解決這個問題呢。
Android:“我能怎麼辦,我也很絕望呀”

具體應用:

說了這麼多,那有什麼用呢?用處就是標準化磁場或感測器數值。我們知道裝置座標系下的數值基本是沒有意義的,因為xyz三個軸的數值完全和裝置當前的朝向有關係。而世界座標系就提供了一個標準化數值的方法。

一個例子就是將計步器的值通過這個辦法標準化之後,就可以無視手機朝向來獲取比較穩定的世界座標系下Z軸(即重力方向)的加速度值,對於計步是很有幫助的。

另外在標準化的磁場的時候,還碰到了一件趣事,就是轉換為世界座標系之後磁場三個軸座標,X軸基本都是0是隻有10^-6的一個極小的接近0的數值,其他YZ兩個軸數值正常 。
來說下原因:
設MAG是待轉換的裝置座標下的磁場向量,R是3*3的由上面方法得到的一個裝置座標到世界座標的一個旋轉矩陣,X是世界座標下的的磁場向量的指向磁東的分量。那麼根據矩陣乘法運算規則:
X = R[0] * MAG[0] + R[1] * MAG[1] + R[2] * MAG[2];
而由getRotationMatrix的原始碼可知R[0] R[1] R[2]的值就是裝置座標系下指向磁東的向量A,而MAG是指向地心和指向地磁南極的平面B內的一個向量C。
向量A垂直於平面B,所以向量A垂直於向量C,所以兩向量積X=A*C就應該是0。
一個相對直觀的理解就是,因為前提假設是傳入的原始磁場向量是在地心和磁南極的平面內,所以就不可能有指向磁東的向量存在。所以經過變換之後的世界座標系下也不會有磁東的向量存在了。