Question regarding quaternion conventions; particularly with respect to point vs frame rotations

I've been using the quaternion class from the sensor fusion toolbox and I just want to be sure that I haven't made a misunderstanding with the conventions.
At first glance, I guessed that this is following the Hamilton convention for quaternions: in that the scalar part comes first, and so on. But I am slightly in doubt when it comes to the "point" vs "frame" rotation terminology. I am assuming point corresponds to passive and frame corresponds to active due to the results of this
q = quaternion([0.42718, 0.24083, 0.59322, 0.63844]);
v = [1,0,0];
vQuat = quaternion([0,v]);
q * vQuat * conj(q)
rotatepoint(q, v)
conj(q) * vQuat * q
rotateframe(q, v)
the result of which is
ans =
quaternion
0 - 0.51903i + 0.83119j - 0.19931k
ans =
-0.5190 0.8312 -0.1993
ans =
quaternion
0 - 0.51903i - 0.25973j + 0.81433k
ans =
-0.5190 -0.2597 0.8143
which suggests that rotatepoint corresonds to where quaternion multiplication.
"Realistic" Example for body to local frame rotation
If we a consider a "real-life" example instead, say the quaternion q represents the orientation of a body frame in a local frame (like e.g. if I want to construct the rotation matrix from body -> local then my guess is that would be rotmat(quat, 'point'). My implementation of this "real-life" example is here
drawArrow = @(p,varargin) quiver( 0,0,p(1),p(2),0,varargin{:} );
% original point
p1 = [1; 0; 0];
% axis-angle rotation, 45 degrees around positive x axis
theta = deg2rad(45);
a = [0, 0, 1];
q = quaternion([cos(theta/2), sin(theta/2)*a]);
% i think rotation corresponds to
p2 = rotatepoint(q, p1'); % body to local frame
p3 = rotateframe(q, p1'); % local frame to body
figure(1)
clf
hold on
drawArrow(p1);
drawArrow(p2);
drawArrow(p3);
grid on
xlabel('x')
ylabel('y')
xlim([-0.5,2])
ylim([-1,1])
legend({'orignal','b->l','l->b'})
where the resulting plot is attached. My understanding here is that blue is the original vector in body frame, red is the same vector in the local frame when the rotation from body to local is given by the specific axis-angle rotation.

 Accepted Answer

I'd like to revisit this to be sure there is no lingering confusion.
The quaternion class uses the Hamilton convention, with the real part first. So
quaternion(a,b,c,d) or equivalently quaternion([a b c d])
produce a quaternion
a + bi + cj + dk where ijk = -1.
From a rotation point of view, it supports two conventions:
  • "frame" or "passive" - typically used to describe to angular displacement of one frame of reference relative to another.
  • "point" or "active" - typically used to describe how to rotate a point or vector relative to it's frame of reference.
Typically we've seen frame/passive used in aerospace and navigation domains, and point/active used in robotics domains - but that's far from absolute and there are counterexamples in each domain.
It's important also to specify what we mean by "orientation." In these toolboxes, Orientation is defined as the frame rotation that takes the parent frame to the child frame. See Orientation, Position, and Coordinate Convention in the documentation.
To clarify the rotatepoint and rotateframe functions:
  • rotateframe(q,v) performs a frame/passive rotation. It is a shorthand way of computing conj(q) * v * q where v is converted to a pure (0 real part) quaternion prior to multiplication, and the output is converted to a 3-vector.
  • rotationpoint(q, v) performs a point/active rotation. It is shorthand for q * v * conj(q) with the same conversions to/from a 3-vector.
For the rotmat function:
  • R = rotmat(q,'frame') produces a 3-by-3 matrix R for which (R*(v.')).' == rotateframe(q,v) for a 1-by-3 v. The Aerospace Toolbox calls this a DCM. rotmat(q, 'frame') is equivalent in meaning to quat2dcm(q);
  • R = rotmat(q, 'point') produces a 3-by-3 matrix R for which (R*(v.')).' == rotatepoint(q,v). This is equivalent in meaning to the Robotics Toolbox function quat2rotm(q)
The euler (and eulerd) conversions use the point and frame convention also and are described in detail in this answer https://www.mathworks.com/matlabcentral/answers/1726515-why-do-the-eul2quat-and-quaternion-functions-differ-on-the-calculated-results#answer_972230

1 Comment

Another source of confusion is that some toolboxes expect that the "rotation matrix" be post-multiplied by a column vector whereas others expect it to be pre-multiplied by a row vector. So even if the rotation matrices from different toolboxes have the same intended use they can be transposes of each other.
For example, we have angle2dcm in the Aerospace Toolbox. That doc page states that the direction cosine matrix (dcm) output is a passive, or frame, rotation. Unfortunately, that doc page does not explicitly state the dcm is intended to be post-multiplied by a Matlab column vector to transform coordinates of a math vector from the A-frame to the B-frame.
For example ...
define some angles and compute the dcm
rng(100);
angles = rand(1,3);
dcm = angle2dcm(angles(1),angles(2),angles(3),'ZYX')
dcm = 3×3
0.8230 0.4971 -0.2748 -0.3743 0.8385 0.3960 0.4273 -0.2231 0.8762
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
Define coordinates of math vector v resolved in the A-frame:
v_a = rand(3,1);
Get the coordinates of the math vector v (i.e., the same vector) resolved in the B-frame:
v_bdcm = dcm*v_a
v_bdcm = 3×1
0.6642 -0.2641 0.4664
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
In contrast, we have the ubiquitous eul2rotm that is used in several toolboxes. That doc page does not explicitly state that its rotation matrix (rotm) output is a passive, or frame, rotation, though it is. However, the doc page does say that the output rotm is intended to be pre-multiplied by a Matlab row vector.
Hence we have
rotm = eul2rotm(angles,'ZYX')
rotm = 3×3
0.8230 -0.3743 0.4273 0.4971 0.8385 -0.2231 -0.2748 0.3960 0.8762
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
which is the transpose of dcm. And we get v resolved in the B-frame by pre-multiplying by the row of coordinates of v resolved the A-frame
v_brotm = (v_a.'*rotm)
v_brotm = 1×3
0.6642 -0.2641 0.4664
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
which is the same result as above except for the shape
norm(v_brotm.' - v_bdcm)
ans = 1.1102e-16
I assume that there is good reason that eul2rotm uses a different convention than angle2dcm as far as the pre- and post-multiplication, perhaps due to different conventions by different communities of practice. However, there's no reason why the associcated doc pages can't contain the same information. angle2dcm clearly states that its output is a passive, or frame, rotation, whereas eul2rotm does not. OTOH, eul2rotm clearly states that its output is intended to be pre-multiplied by a row, whereas angle2dcm does not state that its output is intended to be post-multiplied by a column.

Sign in to comment.

More Answers (3)

Your understanding seems to be correct.
If the quaternion q reprsents a 3D rotation that rotates frame Local into frame Body, then the rotation matrix acquired through R = q.rotmat('frame') represents the same 3D rotation, but keep in mind that the same R can also be interpreted as "the rotation that re-expresses a point in body frame into local frame".
To express a point that is originally given in local frame into the body frame, you need R', which is equivalent to q.rotmat('point')
>> q.rotmat('frame') * q.rotmat('point')
ans =
1.0000 0 0
0 1.0000 0
0 0 1.0000

1 Comment

And I suppose the same is true for the quaternion which represents the orientation of the body frame with respect to the local?
Like for example, a body which sits at with some heading (yaw) ψ corresponds to the quaternion by the axis-angle to quaternion conversion. The rotation from {b} to {l} is then
which in this case would be given by rotmat(q, 'point').
I think I get it.

Sign in to comment.

4 Comments

I have seen these posts, but they leave me with some questions: What quaternion convention does the sensor fusion/navigation toolbox follow, because for me it seems as though it's a combination.
It's clear enough to see quaternion()for me follows the aerospace order, i.e. qw + i*qx + j*qy + k*qz, I see the rotation a mentioned previously as being a frame rotation, as I am rotating the perspective from which I view the vector. This would lead me to believe the 'frame' rotation is appropriate, but this infact produces the incorrect rotation matrix if compared with the known yaw rotation from body to a local frame.
Note: When I said "local" in the original I was attempting to stay general, I think I should've perhaps said "tangent frame" or "ned" instead as this is what I was actually thinking of.
I don't have the Sensor Fusion Toolbox, so can't investigate this directly. But just reading the doc it appears you may be correct that it can support both passive and active conventions. I write this because I see the rotateframe( ) and rotatepoint( ) functions in this toolbox. So it seems you can choose functions to support either of these conventions.
The confusing part is it seems as if rotatepoint is the "passive" rotation:
% describing {b} orientation in {l} as 45 deg about z axis
theta = deg2rad(45);
a = [0, 0, 1];
q = quaternion([cos(theta/2), sin(theta/2)*a]); % axis-angle conversion
rotmat(q, 'point')
ans =
0.7071 -0.7071 0
0.7071 0.7071 0
0 0 1.0000
which is the rotation matrix I would expect for R_{b}^{l} following the previous comment.
So is point here really the passive rotation?
Edit: In addition, considering the quat to rotation matrix conversion as desribed by Solas, link,
function [ R ] = rot( q )
q = compact(q);
w = q(1); x = q(2); y = q(3); z = q(4);
R = [
w^2+x^2-y^2-z^2, 2*(x*y-w*z), 2*(x*z+w*y);
2*(x*y+w*z), w^2-x^2+y^2-z^2, 2*(y*z-w*x);
2*(x*z-w*y), 2*(y*z+w*x), w^2-x^2-y^2+z^2
];
end
also results in the same as the point rotmat
I am not seeing the confusion here. If you look at your explicit rotation matrix
0.7071 -0.7071 0
0.7071 0.7071 0
0 0 1.0000
this sure looks like an active rotation of 45 degrees to me. E.g., take a vector along the x-axis [1;0;0] and multiply it by that matrix and you will get the first column. This is what you would expect by actively rotating a [1;0;0] vector by 45 degrees within the same coordinate frame. It should end up halfway between the x-axis and the y-axis, and that is what we got. And take a y-axis vector [0;1;0] and do the same multiplication and you get the 2nd column, which is a vector halfway between the y-axis and the negative x-axis. Again, this is what I would expect from an active rotation of 45 degrees within the same coordinate frame.
As for the quaternion to direction cosine matrix conversion formula, this does look like a Scalar-Vector order Left Chain Active Hamilton convention.

Sign in to comment.

The quaternion class does follow the Hamilton convention – the scalar part is first.
Point rotations are active rotations and correspond to q * v * conj(q).
Frame rotations are passive rotations and corresponds to conj(q) * v * q

2 Comments

All of this up til passive vs active makes sense to me. Actually applying these functions to generate rotation matrices in comparison to a known rotation around an axis is where the active/passive distinction seems backwards to me.
As in an earlier comment suppose there is a quantity, e.g. velocity, given in body frame as well as the orientation of the body frame with respect to NED frame. Suppose also that the orientation of the body is given by rotation 45 deg about [0,0,1] (x,y,z). The rotation of velocity in {b} to velocity in {n} is thus given by
where I would expect
Repeating this with quaternions results in the "point" rotmat being the same and the "frame" rotmat being the transposed. This is what seems off to me.
Code for quaternion caluclation
% describing {b} orientation in {n} as 45 deg about z axis
theta = deg2rad(45);
a = [0, 0, 1];
q = quaternion([cos(theta/2), sin(theta/2)*a]); % axis-angle conversion
rotmat(q, 'point')
ans =
0.7071 -0.7071 0
0.7071 0.7071 0
0 0 1.0000
We might be using the same definition of orientation, but just to be sure, our orientation definition is :
“Orientation is defined as the frame rotation that takes the parent frame to the child frame.”
The equation you’ve written with the rotation matrix takes the body (child) frame to the parent (navigation) frame. Your rotation matrix is what we’d call a point rotation matrix. Really it’s the transpose of what we call orientation in the toolboxes. But that’s why it works in your equation – you are going body to nav so using the transpose is correct.
The quaternion you’ve built is the same one you’d get if you did this:
quaternion([45 0 0], 'eulerd', 'ZYX', 'point')
which is to say, “make a quaternion q such that q * v * conj(q) will rotate a point 45 degrees around the Z axis.”
That’s why you get the same answer as the matrix you’ve defined above which rotates a point around the Z axis. The transpose of that matrix rotates the frame of reference.

Sign in to comment.

Asked:

on 22 Apr 2021

Edited:

on 25 Jun 2026 at 23:06

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!