iOS4中Core Motion框架的介紹和使用

http://www.kunli.info/2010/07/30/motion/

iOS4中Core Motion框架的介紹和使用

作爲一個剛學習iPhone編程一週的人來說,其實寫這篇文章的目的不是傳道授業解惑,而是爲了知識的總結。如果能吸引到各位爲我傳道授業解惑那再好不過了。文章基本的內容,整個流程是參照WWDC 2010上的session 423: Sensing Device Motion in iOS4寫的,同時參考了開發文檔Event Handling Guide for iPhone OS: Motion Events。當然,我在介紹的時候會添加一些個人的總結和原理的補充。文章可能有錯誤的敘述,發現後請指出,謝謝。

我之前的文章:移動設備智能化的基石–從iPhone4的傳感器談起曾經寫過,iPhone4增加的陀螺儀彌補了很多現有motion sensor的不足,也介紹了很多背景知識。加速度計最大的缺陷,就是無法檢測沿着重力加速度軸方面的旋轉變化,而且,如果僅僅有加速度計,無法避免重力的干擾。傳統的方法是怎麼做的呢?先高通濾波,把近似於直流分量的重力加速度隔離出來,再低通濾波,把因爲手機顫抖產生的高頻噪聲去掉。大量的濾波,不僅會影響到原本的加速度信號,還會嚴重減緩處理速度,並隨之影響程序相應速度。電子羅盤在這方面毫無幫助,這玩意本身的讀數都需要很長的穩定時間,還特別容易受環境干擾。所以,陀螺儀的引入,解決了這幾個大問題,我們可以用它來測量沿重力加速度的旋轉,也可以測量快速的旋轉,大大增強了motion相關的處理工作,更重要的是,通過陀螺儀可以判斷手機當前的擺放位置和姿勢,然後通過這個信息能夠得到相當準確的重力加速度分量,而不是從加速計採集的包含很多噪聲的加速度值中提取。所以說,陀螺儀對於motion sensing方面的工作,是最關鍵的一個部件。陀螺儀的具體原理,請參考之前那篇文章


在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的推出,讓很多以前在智能手機上無法實現或者難以實現的應用成爲了可能。這是機遇,想象力的機遇,也是挑戰,執行能力的挑戰。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章