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

请耐心解决我长期以来的问题,我想尽可能使之清楚。

我想要做的是,当使用相机拍摄照片时,获取姿态(滚动音高和偏航),然后将姿态值保存到nsuserdefaults。 保存后,方向改变,然后尝试通过不断比较姿态值(保存的和当前的)来使手机达到与拍摄图像相同的姿态。

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

我一直在find滚动,俯仰和偏航的值:

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)); 

当我注意到在偏航值有一些差距,我search,可以从这里find: 链接 ,那

从四元数的偏航,俯仰和滚转,你将会遇到同样的问题,就好像你正在使用偏航,俯仰和滚转。 你必须在你的代码中四处使用四元数,并忘记偏航,俯仰和滚转

所以现在我想我必须再次编码一切…请你能如此友好的指向我为此目的使用四元数的示例代码?

这是我正在处理的代码:

在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)]; } }]; 

请让我知道是否需要进一步的细节。

任何build议或build议总是欢迎,:)我是编程和iOS的noob。

谢谢!!

我认为以下的事情是必要的任务:

  1. 首先你需要对四元数有一个很好的理解(如果你已经和他们交朋友,就跳过这个)。 我build议使用OpenGL:教程:使用四元数表示旋转 matrix和四元数常见问题 。 记住(x,y,z)代表旋转的轴(未归一化)和w = cos(alpha / 2),即代表旋转量的大约值。

  2. 由于CMQuaternion只是一个结构,所以很难做所有的计算。 使用一个全function的四元数类,而不是像cocoamath (你至less需要Quaternion.h,.m和QuaternionOperation.m从主干 )。

  3. 现在基本考虑:

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

    2. 有了这个三angular洲,你需要定义满足的条件来考虑到目标的方向。 我的第一个想法是使用德尔塔angular。 这可以很容易地通过以下方式从上述计算的四元数的w分量中检索出来:
      alpha = 2 * arccos(w)
      arccos的领域是有限的,但在这种情况下这不应该成为一个问题,因为我们对小的价值特别感兴趣。

也许值得强调的是,每个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.wi:(double)cmQ.xj:(double)cmQ.yk:(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 } 

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

我并不是说下面是解决你的问题的方法,我不知道这将如何用户友好,但我相信这是值得一试的。 我看到两个问题:你如何存储态度以及如何形象化他们。

存储。 我可以把这种态度保存为四元数或旋转matrix; CMAttitude提供了两个。 (我个人更喜欢旋转matrix,因为旋转matrix在我看来更容易理解,这只是个人偏好。)

可视化。 你正在使用3个标记来使手机与保存的手机保持相同的态度。 这三个标记来自偏航,俯仰和滚转,有效地破坏了你的应用程序的稳定性。 而不是这三个标记,我将形象化保存的态度和当前的态度之间的旋转。 一种方法是将旋转的3个基本vector投影到手机的屏幕上。 关于旋转matrix的好处是,他们给你完全没有任何额外的计算。 例如,可视化的

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

我只需要从[0, 0, 0]到(i) [1, 0, 0] ,(ii) [0, 1, 0]和(iii) [0, 0, 1] 。 也就是说,我只需要绘制matrix的行或列(也取决于您想要的行或列是否更好)。

为了获得保存的旋转matrixS和当前旋转matrixC之间的旋转,需要计算C T S(用词: C转置时间S )。 如果您的旋转matrix如上所示,则您已将手机与已保存的姿态alignment。

旋转matrix的一个很好的教程是方向余弦matrixIMU:理论手稿。

另一种可视化保存的和当前的态度之间的旋转的方式是将其转换成angular度轴的forms。 然后,我将轴投影到手机的屏幕上。 用户首先将手机与轴alignment,而不是围绕轴旋转以匹配保存的姿态。 四元数基本上代表了轴angular的forms,尽pipe是非平凡的方式。


根据你的需要,你需要做更多的工作来改变这个想法。 这绝对不是一个完整的答案。 不过,我希望这有所帮助。