下面说说Core Motion具体负责的采集,计算和处理。CoreMotion的使用就是一三部曲:初始化,获取数据,处理后事。
在初始化阶段,不管你要获取的是什么数据,首先需要做的就是
motionManager =[[CMMotionManager alloc] init];
所有的操作都会由这个manager接管。后面的初始化操作相当直观,以加速度的pull方式为例
if(!motionManager.accelerometerAvailable) {
// fail code // 检查传感器到底在设备上是否可用
}
motionManager.accelerometerUpdateInterval = 0.01; //告诉manager,更新频率是100Hz
[motionManager startAccelerometerUpdates]; //开始更新,后台线程开始运行。这是pull方式。
如果是push方式,更新的代码可以写成这样
[motionManagerstartAccelerometerUpdatesToQueue:[NSOperationQueuecurrentQueue] 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在CoreMotion中是以结构体形式定义的
typedef struct{
double x;
double y;
double z;
}
对应的motion信息,比如加速度或者旋转速度,就可以直接从这三个成员变量中得到。
最后是处理后事,就是在你不需要Core Motion进行处理的时候,释放资源
[motionManagerstopAccelerometerUpdates];
//[motionManager stopGyroUpdates];
//[motionManager stopDeviceMotionUpdates];
[motionManager release];
你看,就是这么简单。当然,如果这么Core Motion这么简单,就太无趣了。实际上,CoreMotion最好玩的地方,既不是加速度,也不是角速度,而是经过sensor fusing算法处理的DeviceMotion信息的提供。CoreMotion里面提供了一个叫做CMDeviceMotion的类,用来把下图所示的这些数据封装成Device Motion信息:
我们来看看这些被封装数据的介绍。
第一个attitude,就是刚才说到的三维attitude,通俗来讲,就是告诉你手机在当前空间的位置和姿势。
第二个是重力信息,其本质是重力加速度矢量在当前设备的参考坐标系中的表达,开发不再需要通过滤波来提取这个信息了,因为CoreMotion已经给你了。
第三个是加速度信息。同样,滤波在这里不再需要(根据程序需求而加的滤波算法自然是可以保留的)。
第四个是即时的旋转速率,也就是rotation rate,是陀螺仪的输出。
下面就来详细介绍一下这四种数据。
1.Attitude。在CMDeviceMotion对象中,attitude是以
@property(readonly, nonatomic) CMAttitude *attitude;
属性定义的。一个CMAttitude的实例,封装了关于当前设备在空间中的姿态信息。这个信息是由下面集中数学表达式定义的:
四元数是一种AttitudeDeterminationSystem经常使用的数据保存形式,我不是很清楚。而欧拉角和变换矩阵则是相辅相成的,两者之间可以相互推导。所以这里主要介绍一下对虚拟现实或者游戏都大有帮助的变换矩阵。
这个rotation变换矩阵究竟有什么用呢?我们来看看下面这张图
本质上讲,变换矩阵给我们阐述了从一个向量空间到另一个向量空间的映射关系。举个例子,在很多应用中都需要对加速度信息进行判断,但是,用户在使用手机的过程中,姿势是不断变换的,我们可以采集到某个设备在t1时间点的加速度以及重力信息,也可以采集到t2时间点的信息,我们却不能直接拿他们做运算。为什么?因为由于手机各个轴方向的变化,加速度和重力信息在t1时间点属于一个向量空间,在t2时间点,就属于另一个向量空间了,如果你硬拿acc.x1和acc.x2求设备的运动模式,自然不可能准的。
所以,现在的问题是,我们要找到两个三维空间的线性变换T,让这个变换关系帮我们把某个空间的值变换到另一个空间去,这样就可以在同一个空间做比较或者任何计算了。CoreMotion如何解决这个问题呢?它首先让你可以在程序开始的初始时间点t1(比如你画第一祯的时候)采集一个attitude的值作为参照坐标系,我们假定这个向量是v_ref。在任何时间点,比如t2,采集一个attitude的值,假定这个向量是v_dev,位于当前设备的坐标系,那么我们有以下关系:
其中R就是rotationmatrix。由于v_ref是正交基向量,所以刚才说到了,v_ref和v_dev都是其对应向量空间的正交基,而这个R矩阵正好是正交矩阵,所有的列向量线性独立。所以,R所对应的变换,正是我们要找的这两个空间的线性变换,而且这是一对一变换。
好,上面这个结论告诉我们什么呢?你在当前时刻t2采集的当前坐标系下的加速度信息,不仅在t1时刻的参照坐标系下有对应的向量,而且仅有一个对应向量!如果我们定义a_dev是当前的加速度向量,那么它在参照坐标系里面对应的加速度向量只有一个,而且肯定可以由下面式子求出
这个式子不存在无解的情况,因为正交矩阵永远都是有逆矩阵的。经此变换,你就可以随意比较和计算不同时间点的加速度和重力信息,从而得出精确的用户运动模式了。
比较有趣的一点是,R变换矩阵的表达形式,正好表明了R和rotationrate的关系: 当前时间点的坐标系和参照坐标系的变换矩阵,是由陀螺仪提供的yaw,pitch和roll三个轴上角度信息推断的。于是,再一次,我们感受到了新加的陀螺仪强大的地方。更强大的地方在于,CoreMotion直接就把R矩阵提供给开发者了,省去了开发者很多易错而繁琐的工作。
说了这么多铺垫,还是简单介绍一下获得当前时间点的R矩阵信息的步骤吧:
首先,获得参考矩阵信息
if (motionManager!= nil) {
CMDeviceMotion *deviceMotion = motionManager.deviceMotion;
referenceAttitude = [deviceMotion.attitude retain];
}
然后在希望得到R矩阵的时候,执行下列操作:
CMRotationMatrixrotation;
CMDeviceMotion *deviceMotion = motionManager.deviceMotion;
CMAttitude *attitude = deviceMotion.attitude;
if (referenceAttitude != nil) {
[attitudemultiplyByInverseOfAttitude:referenceAttitude];
}
rotation = attitude.rotationMatrix;
很简单,也很直观,一个multiplyByInverseOfAttitude的调用,正好反应了我们刚才推导的矩阵运算关系。至此,rotationMatrix被我们拿到,接下来的事情就只有想不到,没有做不到了。
2.Gravity和UserAcceleration。之所以把他们放在一起讲,是因为他们本质上比较类似,而且原始的加速度(就是通过[motionManagerstartAccelerometerUpdates]获得的那个值)本来就是他们的叠加和,换句话说,将原始加速度分解就得到了他们俩,只不过现在苹果帮你把这个滤波分解给做了。他俩在CoreMotion中的属性定义是
@property(readonly, nonatomic) CMAcceleration gravity;
@property (readonly, nonatomic) CMAccelerationUserAcceleration;
都是CMAcceleration所包装的结构体。而且,两者的参考坐标系都是一样的,以设备的外框架为准:
得到这两种数据的方式比较简单,就是直接通过读取motionManager.deviceMotion.userAcceleration/gravity的三个成员变量即可。
3. Rotationrate。旋转速率是通过叫CMRotationRate的结构体封装的,其内部变量定义和CMAcceleration一模一样。正负的确定,由右手法则判断。看到这里,不少朋友可能会有问题:这个数据,和之前介绍的直接通过motionManager获得的CMGyroData有什么区别呢?通过Device Motion封装处理后的Rotationrate,去掉了原始的CMGyroData所有的bias。举个例子,如果我们把设备放在桌上静止不动,理想情况下,陀螺仪的输出应该是0。问题在于,你直接从陀螺仪获得的原始数据并不是0,而是由很多不确定因素导致的非0值,这其中就包括了很多的漂移误差等等,比如陀螺仪温漂,就会影响到我们的读数。Core Motion经过一些算法的处理,帮开发者消除了这种bias,极大方便了motion相关的开发工作。
说到Rotationrate,要讲一下这个输出数值的特点。如果你写一个简单的测试程序,把三个轴的数值都输出到屏幕上来看,会发现一个很有意思的现象:pitch和roll的值,和你读数时候的手机的attitude完全对应,而yaw的值,则是从0开始显示,手机的attitude在之后变了的话,yaw的值才有对应的变化。这是因为,对于pitch和roll来讲,他们都有明确的参照面,就是水平面,而且这个值肯定是在出厂之前就校正过。但yaw呢?没有,用户在刚打开app的时候,可能会朝向任何不同的方向。所以此时,CoreMotion干脆就给你输出相对的初始值0,之后你再根据yaw方向上的相对变化来判断设备的位置变化。
另外一点要补充的是,对于设备的旋转,如果三个轴上都有变化,那么默认角度计算的顺序是先roll,再pitch,最后yaw。