1. 程式人生 > >加速度計、螺旋儀

加速度計、螺旋儀

在iOS4之前,加速度計由UIAccelerometer類來負責採集工作,而電子羅盤則由Core Location接管。而iPhone4的推出,由於加速度計的升級(有訊息說使用的是這款晶片) 和陀螺儀的引入,與motion相關的程式設計成為重頭戲,所以,蘋果在iOS4中增加一個一個專門負責該方面處理的框架,就是Core Motion Framework。這個Core Motion有什麼好處呢?簡單來說,它不僅僅提供給你獲得實時的加速度值和旋轉速度值,更重要的是,蘋果在其中集成了很多演算法,可以直接給你輸出把重力 加速度分量剝離的加速度,省去你的高通濾波操作,以及提供給你一個專門的裝置的三維attitude資訊!

有這麼一個好東西,我們自然就要好好利用了。下面就介紹一下,如何利用Core Motion Framework,來獲得對應的motion資訊。

Core Motion在iOS4.0主要負責三種資料:加速度值,陀螺儀值,裝置motion值。實際上,這個裝置motion值就是通過加速度和旋轉速度進行 fusing變換算出來的,基本原理後面會介紹。Core Motion在系統中以單獨的後臺執行緒的方式去獲得原始資料,並同時執行一些motion演算法來提取更多的資訊,然後呈獻給應用層做進一步處理。Core Motion框架包含有一個專門的Manager類,CMMotionManager,然後由這個manager去管理三種和運動相關的資料封裝類,而 且,這些類都是CMLogItem類的子類,所以相關的motion資料都可以和發生的時間資訊一起儲存到對應檔案中,有了時間戳,兩個相鄰資料之間的實 際更新時間就很容易得到了。這個東西是非常有用的,比如有些時候,你得到的是50Hz的取樣資料,但希望知道的是每一秒加速度的平均值。

從Core Motion中獲取資料主要是兩種方式,一種是Push,就是你提供一個執行緒管理器NSOperationQueue,再提供一個Block(有點像C中 的回撥函式),這樣,Core Motion自動在每一個取樣資料到來的時候回撥這個Block,進行處理。在這中情況下,block中的操作會在你自己的主執行緒內執行。另一種方式叫做 Pull,在這個方式裡,你必須主動去像Core Motion Manager要資料,這個資料就是最近一次的取樣資料。你不去要,Core Motion Manager就不會給你。當然,在這種情況下,Core Motion所有的操作都在自己的後臺執行緒中進行,不會有任何干擾你當前執行緒的行為。

那接下來的問題就是,我在什麼時候選擇什麼方式呢?蘋果官方推薦了一個使用指南,比較了兩種方式的優劣,並做出了使用場景的推薦。如下圖所示。應該說,兩種方式各自的優缺點還是很鮮明的,使用場景也大不一樣,很好區分。

Core Motion的大體介紹就是這些。下面說說Core Motion具體負責的採集,計算和處理。Core Motion的使用就是一三部曲:初始化,獲取資料,處理後事。

在初始化階段,不管你要獲取的是什麼資料,首先需要做的就是

motionManager = [[CMMotionManager alloc] init];

所有的操作都會由這個manager接管。後面的初始化操作相當直觀,以加速度的pull方式為例

if (!motionManager.accelerometerAvailable) {
// fail code // 檢查感測器到底在裝置上是否可用
}
motionManager.accelerometerUpdateInterval = 0.01; // 告訴manager,更新頻率是100Hz
[motionManager startAccelerometerUpdates]; // 開始更新,後臺執行緒開始執行。這是pull方式。

如果是push方式,更新的程式碼可以寫成這樣

[motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMAccelerometerData *latestAcc, NSError *error)
{
// Your code here
}];

接下來就是獲取資料了。Again,很簡單的程式碼

CMAccelerometerData *newestAccel = motionManager.accelerometerData;
filteredAcceleration[0] = newestAccel.acceleration.x;
filteredAcceleration[1] = newestAccel.acceleration.y;
filteredAcceleration[2] = newestAccel.acceleration.z;

通過定義的CMAccelerometerData變數,獲取CMAcceleration資訊。和以前的UIAccelerometer類的使用方式一樣,CMAcceleration在Core Motion中是以結構體形式定義的

typedef struct {
double x;
double y;
double z;
}

對應的motion資訊,比如加速度或者旋轉速度,就可以直接從這三個成員變數中得到。

最後是處理後事,就是在你不需要Core Motion進行處理的時候,釋放資源

[motionManager stopAccelerometerUpdates];
//[motionManager stopGyroUpdates];
//[motionManager stopDeviceMotionUpdates];
[motionManager release];

你看,就是這麼簡單。當然,如果這麼Core Motion這麼簡單,就太無趣了。實際上,Core Motion最好玩的地方,既不是加速度,也不是角速度,而是經過sensor fusing演算法處理的Device Motion資訊的提供。Core Motion裡面提供了一個叫做CMDeviceMotion的類,用來把下圖所示的這些資料封裝成Device Motion資訊:

我們來看看這些被封裝資料的介紹。

第一個attitude,就是剛才說到的三維attitude,通俗來講,就是告訴你手機在當前空間的位置和姿勢。
第二個是重力資訊,其本質是重力加速度向量在當前裝置的參考座標系中的表達,開發不再需要通過濾波來提取這個資訊了,因為Core Motion已經給你了。
第三個是加速度資訊。同樣,濾波在這裡不再需要(根據程式需求而加的濾波演算法自然是可以保留的)。
第四個是即時的旋轉速率,也就是rotation rate,是陀螺儀的輸出。

下面就來詳細介紹一下這四種資料。

1. Attitude。在CMDeviceMotion物件中,attitude是以

@property (readonly, nonatomic) CMAttitude *attitude;

屬性定義的。一個CMAttitude的例項,封裝了關於當前裝置在空間中的姿態資訊。這個資訊是由下面集中數學表示式定義的:

  1. 一個四元數。
  2. 一個變換rotation矩陣
  3. 三個尤拉角 (roll, pitch, 和yaw)。關於尤拉角,again,請參考我上一篇文章

四元數是一種Attitude Determination System經常使用的資料儲存形式,我不是很清楚。而尤拉角和變換矩陣則是相輔相成的,兩者之間可以相互推導。所以這裡主要介紹一下對虛擬現實或者遊戲都大有幫助的變換矩陣。

這個rotation變換矩陣究竟有什麼用呢?我們來看看下面這張圖

本質上講,變換矩陣給我們闡述了從一個向量空間到另一個向量空間的對映關係。舉個例子,在很多應用中都需要對加速度資訊進行判斷,但是,使用者在使用 手機的過程中,姿勢是不斷變換的,我們可以採集到某個裝置在t1時間點的加速度以及重力資訊,也可以採集到t2時間點的資訊,我們卻不能直接拿他們做運 算。為什麼?因為由於手機各個軸方向的變化,加速度和重力資訊在t1時間點屬於一個向量空間,在t2時間點,就屬於另一個向量空間了,如果你硬拿 acc.x1和acc.x2求裝置的運動模式,自然不可能準的。

所以,現在的問題是,我們要找到兩個三維空間的線性變換T,讓這個變換關係幫我們把某個空間的值變換到另一個空間去,這樣就可以在同一個空間做比較 或者任何計算了。Core Motion如何解決這個問題呢?它首先讓你可以在程式開始的初始時間點t1(比如你畫第一禎的時候)採集一個attitude的值作為參照座標系,我們 假定這個向量是v_ref。在任何時間點,比如t2,採集一個attitude的值,假定這個向量是v_dev,位於當前裝置的座標系,那麼我們有以下關 系:

其中R就是rotation matrix。由於v_ref是正交基向量,所以

剛才說到了,v_ref和v_dev都是其對應向量空間的正交基,而這個R矩陣正好是正交矩陣,所有的列向量線性獨立。所以,R所對應的變換,正是我們要找的這兩個空間的線性變換,而且這是一對一變換。

好,上面這個結論告訴我們什麼呢?你在當前時刻t2採集的當前座標系下的加速度資訊,不僅在t1時刻的參照座標系下有對應的向量,而且僅有一個對應 向量!如果我們定義a_dev是當前的加速度向量,那麼它在參照座標系裡面對應的加速度向量只有一個,而且肯定可以由下面式子求出

這個式子不存在無解的情況,因為正交矩陣永遠都是有逆矩陣的。經此變換,你就可以隨意比較和計算不同時間點的加速度和重力資訊,從而得出精確的使用者運動模式了。

比較有趣的一點是,R變換矩陣的表達形式,正好表明了R和rotation rate的關係: 當前時間點的座標系和參照座標系的變換矩陣,是由陀螺儀提供的yaw, pitch和roll三個軸上角度資訊推斷的。於是,再一次,我們感受到了新加的陀螺儀強大的地方。更強大的地方在於,Core Motion直接就把R矩陣提供給開發者了,省去了開發者很多易錯而繁瑣的工作。

說了這麼多鋪墊,還是簡單介紹一下獲得當前時間點的R矩陣資訊的步驟吧:

首先,獲得參考矩陣資訊

if (motionManager != nil) {
CMDeviceMotion *deviceMotion = motionManager.deviceMotion;
referenceAttitude = [deviceMotion.attitude retain];
}

然後在希望得到R矩陣的時候,執行下列操作:

CMRotationMatrix rotation;
CMDeviceMotion *deviceMotion = motionManager.deviceMotion;
CMAttitude *attitude = deviceMotion.attitude;
if (referenceAttitude != nil) {
[attitude multiplyByInverseOfAttitude:referenceAttitude];
}
rotation = attitude.rotationMatrix;

很簡單,也很直觀,一個multiplyByInverseOfAttitude的呼叫,正好反應了我們剛才推導的矩陣運算關係。至此,rotationMatrix被我們拿到,接下來的事情就只有想不到,沒有做不到了。

2. Gravity和UserAcceleration。之所以把他們放在一起講,是因為他們本質上比較類似,而且原始的加速度(就是通過 [motionManager startAccelerometerUpdates]獲得的那個值)本來就是他們的疊加和,換句話說,將原始加速度分解就得到了他們倆,只不過現在蘋果 幫你把這個濾波分解給做了。他倆在Core Motion中的屬性定義是

@property (readonly, nonatomic) CMAcceleration gravity;
@property (readonly, nonatomic) CMAcceleration UserAcceleration;

都是CMAcceleration所包裝的結構體。而且,兩者的參考座標系都是一樣的,以裝置的外框架為準:

得到這兩種資料的方式比較簡單,就是直接通過讀取motionManager.deviceMotion.userAcceleration/gravity的三個成員變數即可。

3. Rotation rate。旋轉速率是通過叫CMRotationRate的結構體封裝的,其內部變數定義和CMAcceleration一模一樣。正負的確定,由右手法 則判斷。看到這裡,不少朋友可能會有問題:這個資料,和之前介紹的直接通過motionManager獲得的CMGyroData有什麼區別呢?通過 Device Motion封裝處理後的Rotation rate,去掉了原始的CMGyroData所有的bias。舉個例子,如果我們把裝置放在桌上靜止不動,理想情況下,陀螺儀的輸出應該是0。問題在於, 你直接從陀螺儀獲得的原始資料並不是0,而是由很多不確定因素導致的非0值,這其中就包括了很多的漂移誤差等等,比如陀螺儀溫漂,就會影響到我們的讀數。 Core Motion經過一些演算法的處理,幫開發者消除了這種bias,極大方便了motion相關的開發工作。

說到Rotation rate,要講一下這個輸出數值的特點。如果你寫一個簡單的測試程式,把三個軸的數值都輸出到螢幕上來看,會發現一個很有意思的現象:pitch和 roll的值,和你讀數時候的手機的attitude完全對應,而yaw的值,則是從0開始顯示,手機的attitude在之後變了的話,yaw的值才有 對應的變化。這是因為,對於pitch和roll來講,他們都有明確的參照面,就是水平面,而且這個值肯定是在出廠之前就校正過。但yaw呢?沒有,使用者 在剛開啟app的時候,可能會朝向任何不同的方向。所以此時,Core Motion乾脆就給你輸出相對的初始值0,之後你再根據yaw方向上的相對變化來判斷裝置的位置變化。

另外一點要補充的是,對於裝置的旋轉,如果三個軸上都有變化,那麼預設角度計算的順序是先roll,再pitch,最後yaw。

總結

講到現在,基本的Core Motion知識就都總結完了。在智慧手機出現之前,我們都說,手機就是用來打電話的。智慧手機改變了這一切。使用者總是有各種各樣的需求等待開發者滿足, 而關鍵就在於,開發者能不能理解使用者,認識使用者,做好個人化。Gyroscope的整合和Core Motion的推出,讓很多以前在智慧手機上無法實現或者難以實現的應用成為了可能。這是機遇,想象力的機遇,也是挑戰,執行能力的挑戰。