iphoneで端末を始点から終点まで動かした際の角度をオイラー角を取得したいと考えています。
当初は直接オイラー角を取得していましたが、ジンバルロックのため、特定の動きで正確な値が得られませんでした。
そこで、クォータニオンからオイラー角を計算したいと考えています。
PythonやC++のコードを参考に、以下のコードを作成してみたのですが、直接取得したオイラー角と、クォータニオンを元に計算したオイラー角の数値が一致しませんでした。
(参考にしたコード:Pythonでクォータニオンをz-y-x系オイラー角に変換するコード書いてみた、Quaternion to Euler angles conversion)
ここ数週間、自分なりに色々と試行錯誤してみたのですが、門外漢なこともあり、全く見通しがつかず困り果てています。
swiftでのクォータニオンからオイラー角への変換コードについて、ご助言願えれば幸いです。
swift5
1 func startMotionManager(){ 2 3// MotionManagerスタート 4 motionManager.deviceMotionUpdateInterval = 0.1 5 6 motionManager.startDeviceMotionUpdates( 7 to: OperationQueue.current!, 8 withHandler: { 9 10 deviceManager, error in 11 12//オイラー角を取得 13 let attitude: CMAttitude = deviceManager!.attitude 14 self.roll = attitude.roll 15 self.pitch = attitude.pitch 16 self.yaw = attitude.yaw 17 18//クォータニオンを取得 19 let quaternion: CMQuaternion = attitude.quaternion 20 self.qw = quaternion.w 21 self.qx = quaternion.x 22 self.qy = quaternion.y 23 self.qz = quaternion.z 24 }) 25 26// クォータニオンからオイラー角への変換 27// roll : x軸回転 28 let sinr_cosp = 2 * ((qw * qx) + (qy * qz)) 29 let cosr_cosp = 1 - 2 * ((qx * qx) + (qy * qy)) 30 roll2 = atan2(sinr_cosp, cosr_cosp) 31 32// pitch : y軸回転 33 let sinp = 2 * ((qw * qy) - (qz * qx)) 34 if fabs(sinp) >= 1 { 35 pitch2 = copysign(Double.pi / 2, sinp) 36 }else{ 37 pitch2 = asin(sinp) 38 } 39 40// yaw : z軸回転 41 let siny_cosp = 2 * ((qw * qz) + (qx * qy)) 42 let cosy_cosp = 1 - 2 * ((qy * qy) + (qz * qz)) 43 yaw2 = atan2(siny_cosp, cosy_cosp) 44 45 }
得られた数値:
roll: -0.10607059671615462
pitch: 0.025374394903295194
yaw: 0.0013470739423799597
qw: 0.012704718066535153
qx: -0.052997630007723055
qy: 0.0
qz: 0.9985137777995401
roll2: 0.025934772364812272
pitch2: -0.10552734591014738
yaw2: -0.0028793097335274853
######[追記]修正したコードを以下に記載します。
Swift5
1// クォータニオン取得までは上述の通り 2// クォータニオンからオイラー角への変換 3//pitch 4 let sinr_cosp = 2 * (qw * qx + qy * qz) 5 let cosr_cosp = 1 - 2 * (qx * qx + qy * qy) 6 pitch2 = atan2(sinr_cosp, cosr_cosp) 7 8//roll 9 let sinp = 2 * (qw * qy - qz * qx) 10 if fabs(sinp) >= 1{ 11 roll2 = copysign(Double.pi / 2, sinp) 12 }else{ 13 roll2 = asin(sinp) 14 } 15 16//yaw 17 let siny_cosp = 2 * (qw * qz + qx * qy) 18 let cosy_cosp = 1 - 2 * (qy * qy + qz * qz) 19 yaw2 = atan2(siny_cosp, cosy_cosp) 20 21//ラジアンから度(degree)への変換 22 self.currentXAngle = Int(pitch2 * 180 / Double.pi) 23 self.currentYAngle = Int(roll2 * 180 / Double.pi) 24 self.currentZAngle = Int(yaw2 * 180 / Double.pi)
近くはなりましたが、一致しない値も多いです。
得られた値:
測定1
rad:
roll 1,2= 0.04704940478649479 0.047029632217478634
pitch 1,2= 0.028981734561906995 0.029013823832668956
yaw 1,2= -0.7736049074468174 -0.7722405186353041
degree:
roll 1,2= 1 1
pitch 1,2= 2 2
yaw 1,2= -44 -44
測定2
rad:
roll 1,2= 1.0113515659438153 0.5706119049349081
pitch 1,2= 0.8798014278180685 1.1572053442575996
yaw 1,2= -0.12041428202827054 0.7680234373632876
degree:
roll 1,2= 50 66
pitch 1,2= 57 32
yaw 1,2= -6 44
測定3
rad:
roll 1,2= -2.735484920517521 -0.08937369139539292
pitch 1,2= 1.3428872701500676 1.780730424715864
yaw 1,2= 2.8914778638136567 0.14657073038362464
degree:
roll 1,2= 76 102
pitch 1,2= -156 -5
yaw 1,2= 165 8
測定4
rad:
roll 1,2= -0.3030519733024809 -0.08215438603880901
pitch 1,2= 1.2922329531814778 1.3043213207876034
yaw 1,2= 1.5531307326353834 1.26109598325311
degree:
roll 1,2= 74 74
pitch 1,2= -17 -4
yaw 1,2= 88 72