使用四元数代替滚动,俯仰和偏航来跟踪设备运动

时间:2013-10-08 05:05:00

标签: iphone ios objective-c quaternions core-motion

请忍受我的长期问题,我想尽可能清楚。

我想要做的是,在使用相机拍摄照片时获得姿势(滚动倾斜和偏航),然后将姿态值保存到nsuserdefaults。保存后,方向会改变,然后通过不断比较姿态值(保存和当前)来尝试使手机处于相同的姿态。

出于界面的目的,用户界面在屏幕上有3个点(每个姿态参数一个),指导拍摄照片的正确方向。达到正确的态度后,屏幕上会显示匹配标志。

我一直在寻找滚动,俯仰和偏航值:

CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
myRoll = radiansToDegrees(atan2(2*(quat.y*quat.w - quat.x*quat.z), 1 - 2*quat.y*quat.y - 2*quat.z*quat.z)) ;
myPitch = radiansToDegrees(atan2(2*(quat.x*quat.w + quat.y*quat.z), 1 - 2*quat.x*quat.x - 2*quat.z*quat.z));
myYaw = radiansToDegrees(2*(quat.x*quat.y + quat.w*quat.z));

当我注意到偏航值存在一些差异时,我搜索并可以从这里找到:link

  从四元数中偏转,俯仰和滚动你将拥有相同的东西   问题好像你只是使用偏航,俯仰和滚动。你必须使用   代码中的四元数无处不在,忘记了偏航,音高和偏差   辊

所以现在我想我必须再次对所有内容进行编码...请您指点一下为此目的使用 Quaternion 的示例代码?

以下是我正在处理的代码:

在ViewController.m中,在图像中:didFinishSavingWithError:

[self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {

        CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
        double tempYaw = radiansToDegrees(asin(2*(quat.x*quat.y + quat.w*quat.z)));
        double tempRoll = radiansToDegrees(atan2(2*(quat.y*quat.w - quat.x*quat.z), 1 - 2*quat.y*quat.y - 2*quat.z*quat.z)) ;
        double tempPitch = radiansToDegrees(atan2(2*(quat.x*quat.w + quat.y*quat.z), 1 - 2*quat.x*quat.x - 2*quat.z*quat.z));

        if (savingGyroOrientation == YES) {

            NSLog(@"Roll = %f degrees",tempRoll);
            NSLog(@"Pitch = %f degrees",tempPitch);
            NSLog(@"Yaw = %f degrees",tempYaw);

            [self.deviceStatus setDouble:tempRoll forKey:@"DeviceRoll"];
            [self.deviceStatus setDouble:tempPitch forKey:@"DevicePitch"];
            [self.deviceStatus setDouble:tempYaw forKey:@"DeviceYaw"];
            [self.deviceStatus synchronize];

            savingGyroOrientation = NO;
            checkingGyroOrientation = YES;
            self.savingLabel.hidden = YES;
            self.startTimerButton.hidden = NO;

        }
        savingGyroOrientation = NO;
        checkingGyroOrientation = YES;
        self.savingLabel.hidden = YES;
        self.startTimerButton.hidden = NO;
    }

     if (timerRunning == YES) {
         if (checkingGyroOrientation == YES) {

             self.takePicButton.hidden = YES;

             int xRoll, yPitch, xYaw, yYaw;
             // Roll Checking
             if (tempRoll >= [self.deviceStatus doubleForKey:@"DeviceRoll"]-1 && tempRoll <= [self.deviceStatus doubleForKey:@"DeviceRoll"]+1 ) {

                 [self.rollDot setFrame:CGRectMake(150, 195, 20, 20)];
                 self.rollToR.hidden = YES;
                 self.rollToL.hidden = YES;
                 self.rollDot.hidden = NO;
                 rollOk = YES;
             }else{
                 rollOk = NO;
                 self.rollDot.hidden = YES;
                 self.rollToR.hidden = NO;
                 self.rollToL.hidden = NO;

                 if (tempRoll - [self.deviceStatus doubleForKey:@"DeviceRoll"] < 0) {
                     xRoll = 150 + (tempRoll - [self.deviceStatus doubleForKey:@"DeviceRoll"]);
                     self.rollToR.hidden = YES;
                     if (xRoll <= 0) {
                         [self.rollToL setFrame:CGRectMake(0, 195, 20, 20)];
                     }else if (xRoll>= 300){
                         [self.rollToL setFrame:CGRectMake(300, 195, 20, 20)];
                     }else{
                         [self.rollToL setFrame:CGRectMake(xRoll, 195, 20, 20)];
                     }
                 }
                 if (tempRoll - [self.deviceStatus doubleForKey:@"DeviceRoll"] > 0){
                     xRoll = 150 + (tempRoll - [self.deviceStatus doubleForKey:@"DeviceRoll"]);
                     self.rollToL.hidden = YES;
                     if (xRoll <= 0) {
                         [self.rollToR setFrame:CGRectMake(0, 195, 20, 20)];
                     }else if (xRoll>=300){
                         [self.rollToR setFrame:CGRectMake(300, 195, 20, 20)];
                     }else{
                         [self.rollToR setFrame:CGRectMake(xRoll, 195, 20, 20)];
                     }
                 }
                 if (tempRoll - [self.deviceStatus doubleForKey:@"DeviceRoll"] > 180){
                     xRoll = 150 + (tempRoll - [self.deviceStatus doubleForKey:@"DeviceRoll"]-360);
                     self.rollToR.hidden = YES;
                     if (xRoll <= 0) {
                         [self.rollToL setFrame:CGRectMake(0, 195, 20, 20)];
                     }else if (xRoll>=300){
                         [self.rollToL setFrame:CGRectMake(300, 195, 20, 20)];
                     }else{
                         [self.rollToL setFrame:CGRectMake(xRoll, 195, 20, 20)];
                     }
                 }
                 if (tempRoll - [self.deviceStatus doubleForKey:@"DeviceRoll"] < -180){
                     xRoll = 150 + (tempRoll - [self.deviceStatus doubleForKey:@"DeviceRoll"]+360);
                     self.rollToL.hidden = YES;
                     if (xRoll <= 0) {
                         [self.rollToR setFrame:CGRectMake(0, 195, 20, 20)];
                     }else if (xRoll >= 300){
                         [self.rollToR setFrame:CGRectMake(300, 195, 20, 20)];
                     }else{
                         [self.rollToR setFrame:CGRectMake(xRoll, 195, 20, 20)];
                     }
                 }
             }
             //Pitch Checking
             if (tempPitch >= [self.deviceStatus doubleForKey:@"DevicePitch"]-1 && tempPitch <= [self.deviceStatus doubleForKey:@"DevicePitch"]+1) {
                 [self.pitchDot setFrame:CGRectMake(150, 195, 20, 20)];
                 self.pitchDot.hidden = NO;
                 self.pitchToDown.hidden = YES;
                 self.pitchToUp.hidden = YES;
                 pitchOk = YES;
             }else{
                 pitchOk = NO;
                 self.pitchDot.hidden = YES;
                 self.pitchToDown.hidden = NO;
                 self.pitchToUp.hidden = NO;
                 if (tempPitch - [self.deviceStatus doubleForKey:@"DevicePitch"] < 0) {
                     yPitch = 195+(tempPitch - [self.deviceStatus doubleForKey:@"DevicePitch"]);
                     //                         NSLog(@"tempPitch is %0.02f Difference is %0.02f, yPitch is %0.02f",tempPitch, tempPitch-[self.deviceStatus doubleForKey:@"DevicePitch"],195+(tempPitch - [self.deviceStatus doubleForKey:@"DevicePitch"]));
                     self.pitchToDown.hidden = YES;
                     if (yPitch <= 0) {
                         [self.pitchToUp setFrame:CGRectMake(150, 0, 20, 20)];
                     }else if (yPitch >= 390) {
                         [self.pitchToUp setFrame:CGRectMake(150, 390, 20, 20)];
                     }else{
                         [self.pitchToUp setFrame:CGRectMake(150, yPitch, 20, 20)];
                     }
                 }
                 if (tempPitch - [self.deviceStatus doubleForKey:@"DevicePitch"] > 0){
                     yPitch = 195+(tempPitch - [self.deviceStatus doubleForKey:@"DevicePitch"]);
                     //                         NSLog(@"tempPitch is %0.02f Difference is %0.02f, yPitch is %0.02f",tempPitch, tempPitch-[self.deviceStatus doubleForKey:@"DevicePitch"],195+(tempPitch - [self.deviceStatus doubleForKey:@"DevicePitch"]));
                     self.pitchToUp.hidden = YES;
                     if (yPitch <= 0) {
                         [self.pitchToDown setFrame:CGRectMake(150, 0, 20, 20)];
                     }else if (yPitch >= 390) {
                         [self.pitchToDown setFrame:CGRectMake(150, 390, 20, 20)];
                     }else{
                         [self.pitchToDown setFrame:CGRectMake(150, yPitch, 20, 20)];
                     }
                 }
                 if (tempPitch - [self.deviceStatus doubleForKey:@"DevicePitch"] < -180){
                     yPitch = 195+tempPitch - [self.deviceStatus doubleForKey:@"DevicePitch"] + 360;
                     //                         NSLog(@"tempPitch is %0.02f Difference is %0.02f, yPitch is %0.02f",tempPitch, tempPitch-[self.deviceStatus doubleForKey:@"DevicePitch"],195+(tempPitch - [self.deviceStatus doubleForKey:@"DevicePitch"]));
                     //                         NSLog(@"*yPitch is %d",yPitch);
                     self.pitchToUp.hidden = YES;
                     self.pitchToDown.hidden = NO;
                     if (yPitch <= 0 ) {
                         [self.pitchToDown setFrame:CGRectMake(150, 0, 20, 20)];
                     }else if (yPitch >= 390) {
                         [self.pitchToDown setFrame:CGRectMake(150, 390, 20, 20)];
                     }else{
                         [self.pitchToDown setFrame:CGRectMake(150, yPitch, 20, 20)];
                     }
                 }
             }
             if (tempYaw >= [self.deviceStatus doubleForKey:@"DeviceYaw"]-2 && tempYaw <= [self.deviceStatus doubleForKey:@"DeviceYaw"]+2) {

                 [self.yawDot setFrame:CGRectMake(150, 195, 20, 20)];
                 self.yawDot.hidden = NO;
                 self.rotateRight.hidden = YES;
                 self.rotateLeft.hidden = YES;
                 yawOk = YES;
             }else{
                 yawOk = NO;
                 self.yawDot.hidden = YES;
                 self.rotateRight.hidden = NO;
                 self.rotateLeft.hidden = NO;

                 if (tempYaw - [self.deviceStatus doubleForKey:@"DeviceYaw"] < 0 ) {
                     xYaw = 150+(tempYaw - [self.deviceStatus doubleForKey:@"DeviceYaw"]);
                     yYaw = 195-1.3*(tempYaw - [self.deviceStatus doubleForKey:@"DeviceYaw"]);
                     NSLog(@"current yaw is %0.02f Difference is %0.02f",tempYaw, tempYaw-[self.deviceStatus doubleForKey:@"DeviceYaw"]);
                     NSLog(@"saved Yaw is %0.02f",[self.deviceStatus doubleForKey:@"DeviceYaw"]);
                     NSLog(@"xYaw is %d, yYaw is %d",xYaw,yYaw);
                     self.rotateRight.hidden = YES;
                     if (xYaw <=0 && yYaw >=390) {
                         [self.rotateLeft setFrame:CGRectMake(0, 390, 20, 20)];
                     }else{
                         [self.rotateLeft setFrame:CGRectMake(xYaw, yYaw, 20, 20)];
                     }

                 }if (tempYaw - [self.deviceStatus doubleForKey:@"DeviceYaw"] > 0){
                     xYaw = 150+(tempYaw - [self.deviceStatus doubleForKey:@"DeviceYaw"]);
                     yYaw = 195-1.3*(tempYaw - [self.deviceStatus doubleForKey:@"DeviceYaw"]);
                     NSLog(@"current yaw is %0.02f Difference is %0.02f",tempYaw, tempYaw-[self.deviceStatus doubleForKey:@"DeviceYaw"]);
                     NSLog(@"saved Yaw is %0.02f",[self.deviceStatus doubleForKey:@"DeviceYaw"]);
                     NSLog(@"*xYaw is %d, yYaw is %d",xYaw,yYaw);
                     self.rotateLeft.hidden = YES;
                     if (xYaw >=300 && yYaw <=0) {
                         [self.rotateRight setFrame:CGRectMake(300, 0, 20, 20)];
                     }else{
                         [self.rotateRight setFrame:CGRectMake(xYaw, yYaw, 20, 20)];
                     }
                 }
             }

             if (rollOk == YES && pitchOk == YES && yawOk ==YES) {
                 self.orientationOkay.hidden = NO;
                 self.centerCircle.hidden = YES;
                 self.rollDot.hidden = YES;
                 self.pitchDot .hidden =YES;
                 self.yawDot.hidden = YES;
                 [self.clickTimer invalidate];
                 self.clickTimer = nil;
                 self.takePicButton.hidden = NO;
                 timerRunning = NO;
                 [self.motionManager stopDeviceMotionUpdates];
                 [self.deviceStatus removeObjectForKey:@"DeviceRoll"];
                 [self.deviceStatus removeObjectForKey:@"DevicePitch"];
                 [self.deviceStatus removeObjectForKey:@"DeviceYaw"];
                 [self.deviceStatus removeObjectForKey:@"DeviceAngle"];

             }else{

                 self.orientationOkay.hidden = YES;
                 if (flagger == YES) {
                     self.centerCircle.hidden = NO;
                 }
                 else{
                     self.centerCircle.hidden = YES;
                 }
             }
         }
     }else{
         self.rotateRight.hidden = YES;
         self.rotateLeft.hidden = YES;
         self.rollToL.hidden = YES;
         self.rollToR.hidden = YES;
         self.pitchToDown.hidden = YES;
         self.pitchToUp.hidden = YES;
         self.rollDot.hidden = NO;
         self.pitchDot .hidden =NO;
         self.yawDot.hidden = NO;
         [self.yawDot setFrame:CGRectMake(0, 390, 20, 20)];
         [self.rollDot setFrame:CGRectMake(0, 195, 20, 20)];
         [self.pitchDot setFrame:CGRectMake(150, 0, 20, 20)];

     }
     }];

如果需要进一步的详细信息,请告诉我。

任何建议或建议总是受欢迎的,:)我是编程和ios的菜鸟。

谢谢!

2 个答案:

答案 0 :(得分:5)

我认为以下事情对于完成任务是必要的:

  1. 首先,你需要对四元数有一个很好的理解(如果你已经与他们交了朋友,请跳过这个)。我推荐OpenGL:Tutorials:Using Quaternions to represent rotationThe Matrix and Quaternions FAQ。有助于记住,(x,y,z)代表绕其旋转的轴(未标准化)和w = cos(α/ 2),即大致代表旋转量。

  2. 由于CMQuaternion只是一个结构,因此很难进行所有计算。使用完整的函数式四元数类,如cocoamath(至少需要来自trunk的Quaternion.h,.m和QuaternionOperation.m。)

  3. 现在基本考虑因素:

    1. 两个四元数之间的差异(或有时称为除法)被定义为从一个方向到另一个方位的角位移可能是要走的路。它被定义为
      d = a -1 * b
      因此,这表示从当前位置到目标位置的增量。

    2. 拥有此增量后,您需要定义满足的条件,以便考虑达到的目标方向。我的第一个想法是使用delta角。这可以通过以下方式从上面计算的d四元数的w分量中容易地检索:
      alpha = 2 * arccos(w)
      arccos的域名受到限制,但在这种情况下这不应成为问题,因为我们对小值特别感兴趣。

  4. 也许值得强调的是,每个3D旋转都有两个单位四元数表示, q -q 。这可能会令人困惑,但并不重要。


    <强>更新 所以一些伪代码看起来像:

    CMQuaternion cmQ = attitude.quaternion;
    // Get an instance of cocoamath's Quaternion for our currently reported quaternion
    Quaternion current = [Quaternion initWithRe:(double)cmQ.w i:(double)cmQ.x j:(double)cmQ.y k:(double)cmQ.z];
    // the complex conjugate and normalised to be on the safe side
    Quaternion inverse = [current inverse];
    // build the delta, assuming you have your stored direction as class member targetQuaternion
    Quaternion diff = [inverse multiply:targetQuaternion];
    float alpha = 2 * acos (diff.Re);
    // maxDeltaAngle is your class member variable defining the angle from which you assume the position as restored (radians)
    if (fabs (alpha) < maxDeltaAngle) {
        // do my fancy camera things
    }
    

    是的,一开始并不是那么微不足道。正如阿里在回答中所说,可视化也是一个重要问题。我的解决方案只是解决了原始数学部分。

答案 1 :(得分:1)

我不是说以下是你问题的解决方案,我不知道这将是多么友好,但我相信它值得一试。我看到两个问题:你如何存储态度以及如何将它们可视化。

存储。我会将态度保存为四元数或旋转矩阵; CMAttitude提供两者。 (我个人更喜欢旋转矩阵到四元数,因为我认为旋转矩阵更容易理解。这只是个人偏好。)

可视化。您正在使用3个标记将手机置于与保存手机相同的姿态。这3个标记来自偏航,俯仰和滚动,有效地破坏了应用的稳定性。我会想象保存的态度和当前的态度之间的旋转,而不是这3个标记。一种方法是将旋转的3个基矢量投影到手机的屏幕上。关于旋转矩阵的好处是它们无需任何额外的计算即可完全满足这一要求。例如,可视化

| 1 0 0 |
| 0 1 0 |
| 0 0 1 |

旋转此旋转矩阵表示,我只需绘制从[0, 0, 0]到(i)[1, 0, 0],(ii)[0, 1, 0]和(iii)[0, 0, 1]的3个箭头。也就是说,我只需要绘制矩阵的行或列(也取决于你想要的行或列是否更好)。

要获取已保存的旋转矩阵S与当前旋转矩阵C之间的旋转,您需要计算C T S(单词:C转置时间S)。如果您的旋转矩阵如上所示,您已将手机与保存的姿势对齐。

关于旋转矩阵的优秀教程是Direction Cosine Matrix IMU: Theory手稿。

另一种可视化保存和当前姿态之间旋转的方法是将其转换为角轴形式。然后,我会将轴投影到手机的屏幕上。用户首先将手机与轴对齐,而不是围绕轴旋转以匹配保存的姿势。四元数基本上代表轴角形式,尽管是以非平凡的方式。


根据您的需要,您需要进一步的工作来改变这个想法;这绝对不是一个完整的答案。不过,我希望这有点帮助。