Is the conversion between quaternion and rotation matrix a one-to-one mapping when $\theta \neq 0$

1.4k Views Asked by At

Could anyone please help me with the rotation representation? I tried to convert a rotation matrix to quaternion in Eigen. But when I converted the quaternion back to rotation matrix, I got a completely different matrix.

Here is my code:

#include <Eigen/Geometry>
#include <iostream>

void Print_Quaternion(Eigen::Quaterniond &q){
    std::cout<<"["<<q.w()<<" "<<q.x()<<" "<<q.y()<<" "<<q.z()<<"]"<<std::endl;
}
void Verify_Orthogonal_Matrix(Eigen::Matrix3d &m)
{
    std::cout<<"|c0|="<<m.col(0).norm()<<",|c1|="<<m.col(1).norm()<<",|c2|="<<m.col(2).norm()<<std::endl;
    std::cout<<"c0c1="<<m.col(0).dot(m.col(1))<<",c1c2="<<m.col(1).dot(m.col(2))<<",c0c2="<<m.col(0).dot(m.col(2))<<std::endl;
}
int main()
{
    Eigen::Matrix3d m; m<<0.991601,0.102421,-0.078975,0.125398,-0.611876,0.78095,-0.0316631,0.784294,0.619581;

    std::cout<<"Input matrix:"<<std::endl<<m<<std::endl;
    std::cout<<"Verify_Orthogonal_Matrix:"<<std::endl;
    Verify_Orthogonal_Matrix(m);
    std::cout<<"Convert to quaternion q:"<<std::endl;
    Eigen::Quaterniond q(m);
    Print_Quaternion(q);
    std::cout<<"Convert back to rotation matrix m1="<<std::endl;
    Eigen::Matrix3d m1=q.normalized().toRotationMatrix();
    std::cout<<m1<<std::endl;
    std::cout<<"Verify_Orthogonal_Matrix:"<<std::endl;
    Verify_Orthogonal_Matrix(m1);
    std::cout<<"Convert again to quaternion q1="<<std::endl;
    Eigen::Quaterniond q1(m1);
    Print_Quaternion(q1);
}

And this is the output I got:

Input matrix:
  0.991601   0.102421  -0.078975
  0.125398  -0.611876    0.78095
-0.0316631   0.784294   0.619581
Verify_Orthogonal_Matrix:
|c0|=1,|c1|=1,|c2|=1
c0c1=-4.39978e-07,c1c2=4.00139e-07,c0c2=2.39639e-08
Convert to quaternion q:
[0.706984 0.00118249 -0.0167302 0.00812501]
Convert back to rotation matrix m1=
   0.998617  -0.0230481   -0.047257
  0.0228899     0.99973 -0.00388638
  0.0473339   0.0027993    0.998875
Verify_Orthogonal_Matrix:
|c0|=1,|c1|=1,|c2|=1
c0c1=1.73472e-18,c1c2=-4.33681e-19,c0c2=6.93889e-18
Convert again to quaternion q1=
[0.999653 0.001672 -0.0236559 0.0114885]

In my case the $\theta$ term in the rotation vector representation is non zero so I guess that the mapping should be a one-to-one mapping. I feel that this should be a well-known problem but I was so confused and got stuck here. Can someone help me out?

1

There are 1 best solutions below

1
On

The problem is that your original matrix is not in $SO(3)$!

I ran this quick check in Python:

>>> m
array([[ 0.991601 ,  0.102421 , -0.078975 ],
   [ 0.125398 , -0.611876 ,  0.78095  ],
   [-0.0316631,  0.784294 ,  0.619581 ]])
>>> np.linalg.det(m)    # numpy's determinant function
-0.99999985062244667    # Orthogonal... but not special orthogonal!

I'm not familiar with Eigen, but I can make a pretty good guess that its implementation of the conversion does not validate that that the input is actually a rotation. (It apparently doesn't even validate that its conversion to quaternion is a unit quaternion. Probably an efficiency measure.) However it implements the conversion, it apparently can do so without the validation, and that probably results in problems.

If you normalize $q$, you will find it is equivalent to $q1$, and if you check $m1$, you will find it is actually a rotation:

>>> m1
array([[ 0.99861699, -0.0230481 , -0.047257  ],
   [ 0.0228899 ,  0.99972999, -0.00388638],
   [ 0.0473339 ,  0.0027993 ,  0.99887502]], dtype=float32)
>>> np.linalg.det(m1)
0.99999952

If you continue to alternate conversions, I bet you'll find you bounce back and forth between (equvalents of) $q$ and $m1$ as you expected.


The mapping of elements from unit-quaternions to $SO(3)$ is 2-to-1. However, one can easily check that every quaternion produces a rigid rotation on $\mathbb R^3$, so among general quaternions one could say the map is $\infty$-to-1.

In the other direction, any correct matrix-to-quaternion implementation should give you a solution that is unique up to scaling and a sign. To be precise, if you got two answers and normalized them, they should be either equal or one is $-1$ times the other.