Usando cuaternión en lugar de rollo, tono y guiñada para rastrear el movimiento del dispositivo

Por favor, aguanto mi larga pregunta, estoy tratando de hacerlo lo más claro posible.

Lo que estoy tratando de hacer es get la actitud (roll pitch y yaw) cuando se toma una foto usando la camera y luego save los valores de actitud a nsuserdefaults. Después de save, se cambia la orientación y luego se intenta llevar el teléfono a la misma actitud en que se tomó la image comparando constantemente los valores de actitud (guardados y actualizados).

A los efectos de la interfaz, la interfaz de usuario tiene 3 puntos (uno para cada parámetro de actitud) en la pantalla que guían la orientación correcta de la image tomada. Al alcanzar la actitud correcta, el indicador de coincidencias se muestra en la pantalla.

He estado encontrando los valores de roll, pitch y yaw como:

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

Cuando noté que había cierta disparidad en los valores de guiñada, busqué y pude encontrar desde aquí: enlace , que

yaw, pitch y roll de un cuaternion tendrás el mismo problema que si estuvieras usando solo guiñada, pitch y roll. Tienes que usar quaternions en CUALQUIER lugar en tu código y olvidarte de guiñada, pitch y roll

Así que ahora creo que tengo que codificar todo de nuevo … ¿Podrías ser tan amable de apuntarme a un código de ejemplo para usar Quaternion para este propósito?

Aquí está el código en el que estoy trabajando:

En ViewController.m, dentro de la image: 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)]; } }]; 

Por favor, avíseme si necesita más información al respecto.

Cualquier sugerencia o consejo es siempre bienvenido, 🙂 Soy un noob a la progtwigción y a ios.

¡¡Gracias!!

    Creo que las siguientes cosas son necesarias para administrar la tarea:

    1. En primer lugar, necesita una buena comprensión de los cuaterniones (omita esto si ya hizo amigos con ellos). Recomiendo OpenGL: Tutoriales: Uso de Quaternions para representar la rotation o Preguntas frecuentes sobre Matrix y Quaternions . Es útil tener en count que (x, y, z) representan el eje para girar alnetworkingedor (no normalizado) y w = cos (alpha / 2), es decir, aproximadamente para la cantidad de rotation.

    2. Como CMQuaternion es solo una estructura, es difícil hacer todos los cálculos. Use una class de cuaternión funcional completa en lugar de cocoamath (necesita al less Quaternion.h, .m y QuaternionOperation.m del tronco ).

    3. Ahora las consideraciones básicas:

      1. La diferencia (o, a veces, expresada como split) entre dos cuaterniones definidas como el desplazamiento angular de una orientación a otra podría ser el path a seguir. Se define como
        d = a -1 * b
        Entonces, esto expresa el delta desde la position actual hasta la position objective.

      2. Al tener este delta, debe definir las condiciones a cumplir para considerar la orientación del objective como alcanzada. Mi primera idea es usar el ángulo delta. Esto se puede recuperar fácilmente del componente w de la cuaternión d calculada anteriormente mediante:
        alfa = 2 * arccos (w)
        El dominio de arccos está restringido, pero este no debería ser un problema en este caso ya que estamos especialmente interesados ​​en pequeños valores.

    Tal vez valga la pena enfatizar que cada rotation en 3D tiene dos representaciones de cuaterniones, q y -q . Esto puede ser confuso pero no importa.


    Actualización: Entonces, un poco de pseudo código sería algo así:

     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 stonetworking 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 restnetworking (radians) if (fabs (alpha) < maxDeltaAngle) { // do my fancy camera things } 

    Sí, no es tan trivial al principio. Como Ali dijo en su respuesta, la visualización también es un tema importante. Mi solución solo resuelve la parte de matemáticas sin procesar.

    No estoy diciendo que la siguiente es la solución a su problema, no tengo idea de lo fácil que será este uso, pero creo que vale la pena. Veo dos cuestiones: cómo almacena la actitud y cómo la visualiza.

    Almacenamiento. Salvaría la actitud como un cuaternión o como una matriz de rotation; CMAttitude proporciona ambos. (Yo personalmente prefiero las matrices de rotation a cuaterniones ya que las matrices de rotation son más fáciles de entender en mi opinión. Es solo una preference personal).

    Visualización. Está utilizando 3 marcadores para llevar el teléfono a la misma actitud que el guardado. Estos 3 marcadores provienen de guiñada, tono y rollo, arruinando efectivamente la estabilidad de su aplicación. En lugar de estos 3 marcadores, visualizaría la rotation entre la actitud guardada y la actual. Una forma de hacerlo es proyectar los 3 vectores base girados en la pantalla del teléfono. Lo bueno de las matrices de rotation es que te dan exactamente esto sin ningún cálculo extra. Por ejemplo, para visualizar el

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

    rotation que representa esta matriz de rotation, solo necesito trazar 3 flechas de [0, 0, 0] a (i) [1, 0, 0] , (ii) [0, 1, 0] y (iii) [0, 0, 1] . Es decir, solo necesito trazar las filas o las columnas de la matriz (también depende de lo que quieras si las filas o las columnas son mejores).

    Para get la rotation entre una matriz de rotation guardada S y la matriz de rotation actual C , necesita calcular C T S (en palabras: C times de transposition S ). Ha alineado el teléfono con la actitud guardada si su matriz de rotation es la que se muestra arriba.

    Un excelente tutorial sobre matrices de rotation es el Matriz de Dirección de Cosina IMU: Teoría manuscrita.

    Otra forma de visualizar la rotation entre la actitud guardada y la actual es transformarla en forma de eje angular. Entonces, proyectaría el eje a la pantalla del teléfono. El usuario primero alinea el teléfono con el eje, que necesita girar alnetworkingedor del eje para que coincida con la actitud guardada. Los cuaterniones representan básicamente la forma del ángulo del eje, aunque de una manera no trivial.


    Requiere un trabajo significativo adicional de su parte para alterar esta idea según sus necesidades; Definitivamente, esta no es una respuesta completa. Sin embargo, espero que esto ayude un poco.