Getting the rotation angle along a single local axis from a quaternion

quaternionsrotations

In short: How can I get a single angle along a local (body-fixed) axis from a quaternion?

Rotations (Click for 3D view)

In not-so-short: I am using an IMU with filtering to track the orientation of someone's upper leg. I end up with a quaternion describing the rotation from world frame to the leg frame (local frame, or body-fixed frame). But I only need a single angle from this quaternion: the swing angle (hip flexion). This is the angle around local +z (not as in the picture).

In more general terms: imagine I have an arbitrary rotation (see picture) and some local axis (the translucent blue line), how can I find the angle between the rotated frame and the original frame along this axis?

In the posted example I would expect to find about +170 degrees (sign based on right-hand rule from the selected rotation axis).

Best Answer

I think I found a solution. The procedure:

  1. Choose a world axis perpendicular to the interesting axis (in the picture I want to know the angle around local +Z, so I now chose the X axis.
  2. Express the unit vector of the chosen axis in the local frame, here: $\hat{x}^{bf} = \mathrm{R}\hat{x}^W$
  3. Project the unit vector onto the plane perpendicular to the rotation axis, here: $v = \begin{pmatrix}\hat{x}^{bf}_x \\ \hat{x}^{bf}_y \\ 0\end{pmatrix}$
  4. Now get the angle from this projected vector, relative to the local +x axis: $\theta = \mathrm{atan2}(v_y, v_x)$

enter image description here

You will get a singularity when the rotated axis is opposite the world axis. You can choose a unit vector on the axis where this is least likely.

Rough code example (assuming some rotation library is present):

float getLegAngle(const float quat[4]) {
    float v[3] = {0.0, 0.0, -1.0}; // Get angle between vector pointing
    // down and the same vector but rotated

    float quat_conj[4]; // `quat` rotates the frame to the world
    quaternionConjugate(quat, quat_conj);
    
    rotateVectorByQuaternion(v, quat_conj);

    return -atan2f(v[1], v[0]) * RAD_TO_DEG;
}