LBS AR开发实录(1):手机位姿数据的实时获取
轉(zhuǎn)載請(qǐng)聲明出處:https://blog.csdn.net/AndrExpert/article/details/80253875
1.?計(jì)算機(jī)視覺(jué)中的坐標(biāo)系
?計(jì)算機(jī)視覺(jué)中,有四大坐標(biāo)系:世界坐標(biāo)系、攝像機(jī)坐標(biāo)系、圖像坐標(biāo)系以及像素坐標(biāo)系。在此之前,還是很有必要先了解下傳說(shuō)中的笛卡爾坐標(biāo)系,因?yàn)?#xff0c;這是接下來(lái)闡述相關(guān)原理的基礎(chǔ)。
(1)?笛卡爾坐標(biāo)系
?在笛卡爾坐標(biāo)系下,無(wú)論是二維(平面)坐標(biāo)系還是三維坐標(biāo)系,通過(guò)變換坐標(biāo)軸的正向方向,都能夠得到兩種不同的坐標(biāo)系,即左手坐標(biāo)系和右手坐標(biāo)系。
- 右手坐標(biāo)系(以三維為例)
?在空間直角坐標(biāo)系中,讓右手拇指指向x軸的正方向,食指指向y軸的正方向,如果中指能指向z軸的正方向,則稱這個(gè)坐標(biāo)系為右手直角坐標(biāo)系。
- 左手坐標(biāo)系
?在空間直角坐標(biāo)系中,讓左手拇指指向x軸的正方向,食指指向y軸的正方向,如果中指能指向z軸的正方向,則稱這個(gè)坐標(biāo)系為左手直角坐標(biāo)系。
(2)?計(jì)算機(jī)視覺(jué)中的坐標(biāo)系
圖像坐標(biāo)系
?圖像坐標(biāo)系是以攝像機(jī)拍攝的二維照片為基準(zhǔn)建立的坐標(biāo)系,用于指定物體在照片中的位置,它表征物體從攝像機(jī)坐標(biāo)系向圖像坐標(biāo)系的透視投影關(guān)系。該坐標(biāo)系的原點(diǎn)位于攝像機(jī)光軸和成像平面的交點(diǎn)O上,通常為照片的中心處,X軸為水平向右方向,Y軸為垂直向上方向。圖像坐標(biāo)系示意圖如下:
像素坐標(biāo)系
?由于每張數(shù)字圖像在計(jì)算機(jī)中是以一個(gè)二維數(shù)組(矩陣,M行xN列)的形式存儲(chǔ),數(shù)組(矩陣)中每一個(gè)元素稱為像素。引入像素坐標(biāo)系的目的是為了便于訪問(wèn)圖像中的像素,該坐標(biāo)系以圖像最左上角的像素(點(diǎn))為原點(diǎn)O,以水平向右為xo軸,垂直向下為yo軸。在圖像的像素坐標(biāo)系中,每一個(gè)像素的坐標(biāo)為(xo,yo),其中,xo,yo分別表示該像素在數(shù)組中的列數(shù)和行數(shù)。像素坐標(biāo)系示意圖如下:
攝像機(jī)坐標(biāo)系(右手系坐標(biāo))
?攝像機(jī)坐標(biāo)系是其站在自身的角度上衡量物體的坐標(biāo)系,它的原點(diǎn)為攝像機(jī)的光心,Xc軸、Yc軸與圖像坐標(biāo)系的x軸和y軸平行,Zc軸為攝像機(jī)光軸,它與圖像平面垂直,且經(jīng)過(guò)圖像坐標(biāo)系的原點(diǎn)(攝像機(jī)光軸與圖像平面垂直交點(diǎn))。攝像機(jī)坐標(biāo)系示意圖如下:
- 世界坐標(biāo)系(右手系坐標(biāo))
?世界坐標(biāo)系現(xiàn)實(shí)空間中的所有坐標(biāo)系的參考坐標(biāo)系,在計(jì)算機(jī)視覺(jué)中可以用來(lái)描述攝像機(jī)和物體的位置,并且不會(huì)因攝像機(jī)或物體狀態(tài)變化而變化,它永遠(yuǎn)是客觀存在的。世界坐標(biāo)系以地球質(zhì)心為原點(diǎn)Ow,Yw軸指向地磁北極(向下),Z軸與重力方向相反(指向天空)、X軸是Y與Z的叉積(可由右手法則確定)。示意圖如下:
?假設(shè)現(xiàn)實(shí)中有一個(gè)點(diǎn)P(x,y,z),它位于世界坐標(biāo)系中,那么,p(x,y)則為在圖像中成像的點(diǎn),即位于圖像坐標(biāo)系中。OO’為相機(jī)的焦距。
?
?攝像機(jī)坐標(biāo)系和世界坐標(biāo)系之間的關(guān)系可用旋轉(zhuǎn)矩陣R與平移向量t來(lái)描述
2.?位姿數(shù)據(jù)獲取及其原理剖析
?位姿是指一個(gè)物體的位置和方向(The pose of an object refers to its location and orientation)其中, 位置數(shù)據(jù)指緯度、經(jīng)度、海拔高度;方向?yàn)榉较蚪恰⒀龈┙恰M滾角。一個(gè)物體的位置可以用(x,y,z)來(lái)表示。而方向可以用(α,β,γ)來(lái)表示,它們是表示圍繞三個(gè)坐標(biāo)軸旋轉(zhuǎn)的角度。
2.1?傳感器坐標(biāo)系統(tǒng)
?傳感器坐標(biāo)系,也稱設(shè)備(攝像機(jī))坐標(biāo)系或屏幕坐標(biāo)系,是指當(dāng)設(shè)備處于自然放置狀態(tài)(即手機(jī)豎屏portrait或平板橫屏landspace),對(duì)于大多數(shù)傳感器(加速度、重力、陀螺儀、磁場(chǎng)傳感器)來(lái)說(shuō),它相對(duì)于設(shè)備屏幕的坐標(biāo)系以手機(jī)屏幕中心為原點(diǎn)O,X軸指向水平向右方向,Y軸指向垂直向上方向(即設(shè)備頂端),Z軸指向設(shè)備屏幕由里向外方向。傳感器坐標(biāo)系是設(shè)備的傳感器框架用來(lái)展示傳感器數(shù)據(jù)值的三軸坐標(biāo)系統(tǒng),它的三個(gè)軸方向是客觀存在,不會(huì)因?yàn)樵O(shè)備擺放方向或狀態(tài)的變化而變化,即傳感器坐標(biāo)系是基于設(shè)備自然方向設(shè)定的,這與OpenGL的坐標(biāo)系統(tǒng)原理一致。傳感器坐標(biāo)系示意圖如下:
注意:由于設(shè)備在使用過(guò)程中,我們并不能保證設(shè)備總是處于自然放置狀態(tài),且處于非自然方向的設(shè)備屏幕顯示的是基于傳感器坐標(biāo)系設(shè)定的數(shù)據(jù),而非傳感器實(shí)際的數(shù)據(jù),這就需要我們將傳感器實(shí)際坐標(biāo)數(shù)據(jù)映射到標(biāo)準(zhǔn)的屏幕傳感器坐標(biāo)系中。通常,我們使用getRotation()方法(手機(jī))屏幕的方向,使用remapCoordinateSystem()方法實(shí)現(xiàn)傳感器實(shí)際坐標(biāo)數(shù)據(jù)到屏幕傳感器坐標(biāo)系系統(tǒng)的映射。
2.2?位姿數(shù)據(jù)獲取及其原理分析
/*** 傳感器數(shù)據(jù)獲取* Created by jiangdongguo on 2018/5/9.*/public class SensorActivity extends AppCompatActivity implements SensorEventListener {// 加速傳感器private Sensor mGravitySensor;// 磁場(chǎng)傳感器private Sensor mMagneticSensor;private SensorManager mSensorManager;// 加速傳感器數(shù)據(jù)private float[] mGravityValues = new float[3];// 磁場(chǎng)傳感器數(shù)據(jù)private float[] mMagneticValues = new float[3];// 方向結(jié)果private float[] orientationValues = new float[3];private boolean isPhoneVertical = true;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);List<Sensor> accelers = mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);if (accelers != null) {mGravitySensor = accelers.get(0);}List<Sensor> magnetics = mSensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD);if (magnetics != null) {mMagneticSensor = magnetics.get(0);}}@Overrideprotected void onStart() {super.onStart();// 注冊(cè)傳感器數(shù)據(jù)監(jiān)聽(tīng)器// 設(shè)置數(shù)據(jù)采樣頻率為SENSOR_DELAY_UImSensorManager.registerListener(this, mGravitySensor, SENSOR_DELAY_UI);mSensorManager.registerListener(this, mMagneticSensor, SENSOR_DELAY_UI);}@Overrideprotected void onStop() {super.onStop();// 注銷傳感器數(shù)據(jù)監(jiān)聽(tīng)器mSensorManager.unregisterListener(this, mGravitySensor);mSensorManager.unregisterListener(this, mMagneticSensor);}@Overrideprotected void onDestroy() {super.onDestroy();}@Overridepublic void onSensorChanged(SensorEvent event) {// 緩存加速傳感器數(shù)據(jù)if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {mGravityValues = event.values;}// 緩存磁場(chǎng)傳感器數(shù)據(jù)if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {mMagneticValues = event.values;}// 通過(guò)加速傳感器和磁場(chǎng)傳感器計(jì)算方位calculateOrientation();}private void calculateOrientation() {float[] rotateTmp = new float[9];float[] outR = new float[9];// 將機(jī)身坐標(biāo)映射到世界坐標(biāo)系,rotateTmp為旋轉(zhuǎn)矩陣SensorManager.getRotationMatrix(rotateTmp, null, mGravityValues, mMagneticValues);// 將機(jī)身坐標(biāo)系映射到世界坐標(biāo)系// 如果不處理,獲得是當(dāng)手機(jī)水平放置的值,即手機(jī)屏幕與地平線平行if (isPhoneVertical) {// 豎屏方向SensorManager.remapCoordinateSystem(rotateTmp, SensorManager.AXIS_X, SensorManager.AXIS_Z, outR);} else {// 橫屏方向SensorManager.remapCoordinateSystem(rotateTmp, SensorManager.AXIS_Z, SensorManager.AXIS_MINUS_X, outR);}// 在世界坐標(biāo)系里,機(jī)器繞Z軸、X軸、Y軸旋轉(zhuǎn)的角度。SensorManager.getOrientation(outR, orientationValues);float azimuth = (float) Math.toDegrees(orientationValues[0]);if(azimuth < 0) {azimuth += 360;}float pitch = (float)Math.toDegrees(orientationValues[1]);float roll = (float)Math.toDegrees(orientationValues[2]);}@Overridepublic void onAccuracyChanged(Sensor sensor, int accuracy) {} }● 核心代碼講解
1.?SensorManager.getRotationMatrix方法
// 將設(shè)備坐標(biāo)轉(zhuǎn)換成世界坐標(biāo) // R:9維float類型矩陣,當(dāng)將設(shè)備坐標(biāo)系調(diào)整到與世界坐標(biāo)一致時(shí), // R為一個(gè)單位矩陣: [ 1,0,0 ] // [ 0,1,0 ] // [ 0,0,1 ] // I:9維float類型矩陣,當(dāng)將地磁矢量轉(zhuǎn)換成與重力相同的坐標(biāo)空間(世界坐標(biāo)空間), // I為一個(gè)沿X軸旋轉(zhuǎn)的旋轉(zhuǎn)矩陣,傾斜的角度可通過(guò)geiInclination(float[])計(jì)算 // gravity:加速傳感器在傳感器坐標(biāo)系中的重力加速度數(shù)值(x,y,z) // geomagnetic:磁場(chǎng)傳感器在傳感器坐標(biāo)系中的磁場(chǎng)數(shù)值(x,y,z) boolean getRotationMatrix (float[] R, float[] I, float[] gravity, float[] geomagnetic)?getRotationMatrix方法的作用是將傳感器坐標(biāo)轉(zhuǎn)換成為世界坐標(biāo)系,即通過(guò)加速傳感器和磁場(chǎng)傳感器來(lái)計(jì)算相對(duì)于世界坐標(biāo)系的旋轉(zhuǎn)矩陣,進(jìn)而通過(guò)getOrientation方法獲取手機(jī)的方位數(shù)據(jù)。其中,X軸是Y和Z軸的矢量積,它與大地表面相切且方向大致指向正東;Y軸與大地表面相切,方向指向磁場(chǎng)北極;Z軸與大地表面相垂直,方向指向天空。需要注意的是,該方法只有在設(shè)備不處于加速狀態(tài)和強(qiáng)磁場(chǎng)中計(jì)算出來(lái)的值才會(huì)有意義。下圖為世界坐標(biāo)系示意圖:
◇ 函數(shù)源碼及其原理分析
?從源碼可知,getRotationMatrix方法首先使用磁感應(yīng)器的方向和重力的方向(均為傳感器坐標(biāo)系)做叉乘,當(dāng)手機(jī)水平放置(屏幕與地面平行,方向朝向天空)時(shí),此時(shí)磁感應(yīng)方向由水平南指向北方向和重力方向垂直地面指向地心,根據(jù)叉乘右手規(guī)則,會(huì)得到一個(gè)新的水平指向西的方向;接著,對(duì)重力方向和水平向西的方向做歸一化變?yōu)閱挝幌蛄?#xff0c;然后再用重力方向和水平向西的方向做叉乘得到由水平南向北的方向與地球相切。過(guò)程如下:
?經(jīng)過(guò)兩次叉乘后,我們最終由一個(gè)平面的向量,獲得三個(gè)三維立體平面的向量,同時(shí)將一個(gè)矢量從設(shè)備坐標(biāo)系轉(zhuǎn)換到世界坐標(biāo)系統(tǒng),從而獲得傾斜矩陣I和旋轉(zhuǎn)矩陣R。關(guān)于旋轉(zhuǎn)矩陣,我們?cè)谙乱徊ɡ^續(xù)講解,這里你只需要只要設(shè)備的方向就是通過(guò)這個(gè)旋轉(zhuǎn)矩陣計(jì)算出來(lái)的。
2.?SensorManager.getOrientation()方法
// 使用旋轉(zhuǎn)矩陣計(jì)算設(shè)備的方位 // R:旋轉(zhuǎn)矩陣 // values:方位值 float[] getOrientation (float[] R, float[] values)?getOrientation方法的作用是將getRotationMatrix方法得到的旋轉(zhuǎn)矩陣(以世界坐標(biāo)為參考系,即相對(duì)于地球)來(lái)計(jì)算設(shè)備在的方位值(注:以下提到到X、Y、Z坐標(biāo)系均以地球?yàn)閰⒖枷?#xff0c;即設(shè)備的世界坐標(biāo))。假設(shè)手機(jī)水平放置在地球表面,在世界坐標(biāo)系中,方位角、仰俯角和轉(zhuǎn)動(dòng)角示意圖如下:
?
?valuse[0]:方位角(Azimuth) ,設(shè)備圍繞Z軸旋轉(zhuǎn)的角度,范圍為-π~π 。方位角表示的是設(shè)備坐標(biāo)系的Y軸(相對(duì)于地球)與磁場(chǎng)北極之間的夾角。當(dāng)設(shè)備指向北(地理北極)時(shí),方位角為0度;指向東,方位角為π/2度;指向南,方位角為π度;指向西,方位角為-π/2度;
?valuse[1]:仰俯角(Pitch),設(shè)備圍繞X軸旋轉(zhuǎn)的角度,范圍為-π/2~π/2。仰俯角表示的是平行于設(shè)備屏幕的平面和與地面平行的平面之間的夾角。 假設(shè)設(shè)備平行于地面水平放置、底部邊緣面向用戶且屏幕是朝上,將設(shè)備的頂部邊緣向地面傾斜會(huì)產(chǎn)生一個(gè)正的俯仰角度,將設(shè)備的底部邊緣向地面傾斜產(chǎn)生一個(gè)負(fù)的仰俯角度。以手機(jī)為例:當(dāng)手機(jī)頂部固定在地面(相切),尾部慢慢向上翹起來(lái)直到手機(jī)屏幕與地面垂直,此時(shí)仰俯角從0~π/2度之間變動(dòng);當(dāng)手機(jī)尾部固定在地面(相切),頂部慢慢向上翹起來(lái)直到手機(jī)屏幕與地面垂直,此時(shí)仰俯角從0~-π/2度之間變動(dòng)。
?valuse[2]:衡傾角(Roll),設(shè)備圍繞Y軸旋轉(zhuǎn)的角度,范圍為-π~π。轉(zhuǎn)動(dòng)角表示垂直于設(shè)備屏幕的平面與垂直于地面的平面之間的夾角。假設(shè)設(shè)備平行于地面水平放置、底部邊緣面向用戶且屏幕是朝上,將設(shè)備的左邊緣向地面傾斜會(huì)產(chǎn)生一個(gè)正橫傾角,將設(shè)備右邊緣向地面傾斜會(huì)產(chǎn)生一個(gè)負(fù)衡傾角。以手機(jī)為例:當(dāng)手機(jī)左邊框不動(dòng),右邊框慢慢向上翹起來(lái)直到翻轉(zhuǎn)180度,橫傾角從0~-π度之間變動(dòng);當(dāng)手機(jī)右邊框不懂,左邊框慢慢向上翹起來(lái)翻轉(zhuǎn)180度,橫傾角從0~π度之間變動(dòng)。
注意: 如果使用方向傳感器(Sensor.TYPE_ORIENTATION)這種老方式,三種角度范圍和變化趨勢(shì)與上述新方法會(huì)有一定的出入。
◇ 函數(shù)源碼及其原理分析
public static float[] getOrientation(float[] R, float values[]) {/* 齊次坐標(biāo)* 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]);values[1] = (float)Math.asin(-R[7]);values[2] = (float)Math.atan2(-R[6], R[8]);} 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; }?從源碼中可知,getOrientation方法基于旋轉(zhuǎn)矩陣R計(jì)算得到設(shè)備的方位角values[0]、仰俯角values[0]以及橫滾角values[1],至于上述結(jié)果是如何計(jì)算出來(lái)的,這里還是有必要講解下旋轉(zhuǎn)矩陣。在這篇文章中,介紹了旋轉(zhuǎn)矩陣的相關(guān)概念和性質(zhì),所謂旋轉(zhuǎn)矩陣,即假設(shè)有一個(gè)三維笛卡爾坐標(biāo)系,當(dāng)以它的X軸為軸逆時(shí)針旋轉(zhuǎn)ω度時(shí),通過(guò)計(jì)算可以得到繞X軸旋轉(zhuǎn)矩陣分量Rrotx(或Rω);當(dāng)以它的Y軸為軸逆時(shí)針旋轉(zhuǎn)δ度時(shí),通過(guò)計(jì)算可以得到繞Y軸旋轉(zhuǎn)矩陣分量Rroty(或Rδ);當(dāng)以它的Z軸為軸逆時(shí)針旋轉(zhuǎn)κ度時(shí),通過(guò)計(jì)算可以得到繞Z軸旋轉(zhuǎn)矩陣分量Rrotz(或κ)。最后,得到旋轉(zhuǎn)矩陣R=Rrotx*Rroty*Rrotz,計(jì)算公式如下:
?再進(jìn)一步計(jì)算,得到各分量的旋轉(zhuǎn)角度值:
?最后,根據(jù)上面對(duì)設(shè)備方位角、仰俯角以及橫滾角的描述,它們分別為設(shè)備繞Z軸、X軸、Y軸旋轉(zhuǎn)的角度,即為κ、ω、δ,與getOrientation源碼一致。
旋轉(zhuǎn)矩陣有一個(gè)很重要的特性就是它是一個(gè)正交矩陣,即矩陣的逆等于矩陣的轉(zhuǎn)置,矩陣的逆*矩陣的轉(zhuǎn)置等于單位矩陣。
3?SensorManager.remapCoordinateSystem()方法
// 變換輸入的旋轉(zhuǎn)矩陣,使其能夠在不同坐標(biāo)系統(tǒng)中表示 // inR:要變換的旋轉(zhuǎn)矩陣; // X:定義新坐標(biāo)系中與原坐標(biāo)系X軸一致(重合)的軸線 // Y:定義新坐標(biāo)系中與原坐標(biāo)系Y軸一致(重合)的軸線 // outR:變換后的矩陣 boolean remapCoordinateSystem (float[] inR, int X, int Y, float[] outR)?由于手機(jī)在使用過(guò)程中,我們并不能保證設(shè)備總是處于豎屏方向(自然方向),當(dāng)旋轉(zhuǎn)手機(jī)屏幕后,由于手機(jī)屏幕顯示的傳感器數(shù)據(jù)是以標(biāo)準(zhǔn)傳感器坐標(biāo)系(默認(rèn)手持設(shè)備永遠(yuǎn)處于自然方向,是客觀存在的且不會(huì)因設(shè)備狀態(tài)的改變而變換)為準(zhǔn)的,而不是傳感器實(shí)際的數(shù)據(jù),這就需要我們結(jié)合getRotation()方法和remapCoordinateSystem()方法實(shí)現(xiàn)傳感器實(shí)際坐標(biāo)數(shù)據(jù)到屏幕傳感器坐標(biāo)系系統(tǒng)的映射,其中,getRotation()用于獲取手機(jī)屏幕當(dāng)前的旋轉(zhuǎn)狀態(tài)(角度)。
◇ 函數(shù)源碼及其原理分析
public static boolean remapCoordinateSystem(float[] inR, int X, int Y,float[] outR){if (inR == outR) {final float[] temp = mTempMatrix;synchronized(temp) {// we don't expect to have a lot of contentionif (remapCoordinateSystemImpl(inR, X, Y, temp)) {final int size = outR.length;for (int i=0 ; i<size ; i++)outR[i] = temp[i];return true;}}}return remapCoordinateSystemImpl(inR, X, Y, outR); }◇ 旋轉(zhuǎn)
- getRotation屏幕旋轉(zhuǎn)方向:“順時(shí)針”方向旋轉(zhuǎn),每次遞增90度
?getRotation方法只有在Activity沒(méi)有被android:screenOrientation=”portrait
/landspace”且手機(jī)設(shè)置中開(kāi)啟自動(dòng)旋轉(zhuǎn)時(shí)才有效,并且該方法只能檢測(cè)水平方向與垂直方向的切換,無(wú)法檢測(cè)180度的旋轉(zhuǎn)。當(dāng)我們通過(guò)screenOrientation固定屏幕方向時(shí),這里建議使用傳感器來(lái)檢測(cè)屏幕旋轉(zhuǎn)的方向,即OrientationEventListener。
- Camera預(yù)覽旋轉(zhuǎn)方向: “順時(shí)針”方向旋轉(zhuǎn),每次遞增90度
- 傳感器獲取屏幕旋轉(zhuǎn)方向: OrientationEventListener
對(duì)于Sensor,默認(rèn)的手機(jī)方向是:豎屏Home鍵在下面,這個(gè)是Sensor的0度方向。
總結(jié)
以上是生活随笔為你收集整理的LBS AR开发实录(1):手机位姿数据的实时获取的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 服务器列表修复工具,rpc服务器不可用修
- 下一篇: 鲁大师最新笔记本排行榜,联想最受欢迎,微