Fix QuaternionToAngleAxis to ensure rotations are between -pi and pi. Thanks to Guoxuan Zhang for reporting this. Change-Id: I2831ca3a04d5dc6467849c290461adbe23faaea3
diff --git a/include/ceres/rotation.h b/include/ceres/rotation.h index 7f05187..0d8a390 100644 --- a/include/ceres/rotation.h +++ b/include/ceres/rotation.h
@@ -148,9 +148,9 @@ template<typename T> inline void AngleAxisToQuaternion(const T* angle_axis, T* quaternion) { - const T &a0 = angle_axis[0]; - const T &a1 = angle_axis[1]; - const T &a2 = angle_axis[2]; + const T& a0 = angle_axis[0]; + const T& a1 = angle_axis[1]; + const T& a2 = angle_axis[2]; const T theta_squared = a0 * a0 + a1 * a1 + a2 * a2; // For points not at the origin, the full conversion is numerically stable. @@ -177,16 +177,35 @@ template<typename T> inline void QuaternionToAngleAxis(const T* quaternion, T* angle_axis) { - const T &q1 = quaternion[1]; - const T &q2 = quaternion[2]; - const T &q3 = quaternion[3]; - const T sin_squared = q1 * q1 + q2 * q2 + q3 * q3; + const T& q1 = quaternion[1]; + const T& q2 = quaternion[2]; + const T& q3 = quaternion[3]; + const T sin_squared_theta = q1 * q1 + q2 * q2 + q3 * q3; // For quaternions representing non-zero rotation, the conversion // is numerically stable. - if (sin_squared > T(0.0)) { - const T sin_theta = sqrt(sin_squared); - const T k = T(2.0) * atan2(sin_theta, quaternion[0]) / sin_theta; + if (sin_squared_theta > T(0.0)) { + const T sin_theta = sqrt(sin_squared_theta); + const T& cos_theta = quaternion[0]; + + // If cos_theta is negative, theta is greater than pi/2, which + // means that angle for the angle_axis vector which is 2 * theta + // would be greater than pi. + // + // While this will result in the correct rotation, it does not + // result in a normalized angle-axis vector. + // + // In that case we observe that 2 * theta ~ 2 * theta - 2 * pi, + // which is equivalent saying + // + // theta - pi = atan(sin(theta - pi), cos(theta - pi)) + // = atan(-sin(theta), -cos(theta)) + // + const T two_theta = + T(2.0) * ((cos_theta < 0.0) + ? atan2(-sin_theta, -cos_theta) + : atan2(sin_theta, cos_theta)); + const T k = two_theta / sin_theta; angle_axis[0] = q1 * k; angle_axis[1] = q2 * k; angle_axis[2] = q3 * k;
diff --git a/internal/ceres/rotation_test.cc b/internal/ceres/rotation_test.cc index 65421fa..8e40507 100644 --- a/internal/ceres/rotation_test.cc +++ b/internal/ceres/rotation_test.cc
@@ -87,14 +87,43 @@ return false; } + // Quaternions are equivalent upto a sign change. So we will compare + // both signs before declaring failure. + bool near = true; for (int i = 0; i < 4; i++) { if (fabs(arg[i] - expected[i]) > kTolerance) { - *result_listener << "component " << i << " should be " << expected[i]; - return false; + near = false; + break; } } - return true; + if (near) { + return true; + } + + near = true; + for (int i = 0; i < 4; i++) { + if (fabs(arg[i] + expected[i]) > kTolerance) { + near = false; + break; + } + } + + if (near) { + return true; + } + + *result_listener << "expected : " + << expected[0] << " " + << expected[1] << " " + << expected[2] << " " + << expected[3] << " " + << "actual : " + << arg[0] << " " + << arg[1] << " " + << arg[2] << " " + << arg[3]; + return false; } // Use as: @@ -259,6 +288,23 @@ EXPECT_THAT(axis_angle, IsNearAngleAxis(expected)); } +TEST(Rotation, QuaternionToAngleAxisAngleIsLessThanPi) { + double quaternion[4]; + double angle_axis[3]; + + const double half_theta = 0.75 * kPi; + + quaternion[0] = cos(half_theta); + quaternion[1] = 1.0 * sin(half_theta); + quaternion[2] = 0.0; + quaternion[3] = 0.0; + QuaternionToAngleAxis(quaternion, angle_axis); + const double angle = sqrt(angle_axis[0] * angle_axis[0] + + angle_axis[1] * angle_axis[1] + + angle_axis[2] * angle_axis[2]); + EXPECT_LE(angle, kPi); +} + static const int kNumTrials = 10000; // Takes a bunch of random axis/angle values, converts them to quaternions,