Quaternions And How to Really Use Them
Quaternions are often used in attitude and heading systems to represent orientation. This post describes quaternions and how to use them in orientation and navigation systems.
Quaternions
If you type ‘quaternion’ into google, it’ll tell you something like this
a complex number of the form w + xi + yj + zk, where w, x, y, z are real numbers and i, j, k are imaginary units that satisfy certain conditions.
That’s a pretty frightening description, but with any luck, you’re a persistent type of person because once you’ve masterd quaternions (at least, unit quaternions used to represent orientation) you’ll probably find they are actually nowhere near as difficult as you may have been led to believe. For a start, you can ignore the imaginary components. They’re just a figment of your imagination, anyway.
From here on, we’ll use a representation with only four components. These are w, x, y and z.
Normalising
These four components make quaternions similar to four-dimensional vectors in a lot of ways. For a start, normalising a quaternion is done in the same way.
[math]\left | q \right | = \sqrt{w^2 + x^2 + y^2 + z^2}[/math]
[math]\hat{q} = \frac{q}{\left | q \right | }[/math]
In pseudo-code
1 2 |
magnitude = sqrt(w*w + x*x + y*y + z*z) unit_q = q / magnitude |
The quaternion library in ETK has magnitude and normalise functions
1 2 |
real_t magnitude = q.magnitude(); q.normalize(); |
Multiplying
One of the most import quaternion operation is multiplication. It’s used to add the amount of rotation stored in one quaternion to another. For example, quaternion A represents straight, level and east facing ( 90 degrees). Quaternion B is straight, level and facing north east ( 45 degrees ). Multiplying A and B will give a quaternion that has a heading of 135 degrees – because multiplying A by B adds the rotation of B to A. Make sense?
Here’s the proof
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#include <iostream> #include <etk/etk.h> using namespace std; int main() { etk::Quaternion A, B; //heading is 90, zero pitch or roll etk::Vector<3> a(90, 0, 0); a.toRadians(); //heading is 45 etk::Vector<3> b(45, 0, 0); b.toRadians(); //make quaternions A and B from euler angle representations A.fromEuler(a); B.fromEuler(b); //multiply A by B etk::Quaternion C = A*B; //convert C to an euler angle, because it's easier for mere mortals to comprehend etk::Vector<3> euler = C.toEuler(); euler.toDegrees(); cout << euler.x() << " " << euler.y() << " " << euler.z() << endl; } |
The output from this program is 135 0 0.
One very important thing to note is that quaternion multiplication is non-communicative. That means
[math]{A*B} \neq {B*A}[/math]
The Conjugate
For unit quaternions, the conjugate is the same as the inverse. This is great because the conjugate is a lot easier to calculate than the inverse. To find the conjugate, simply invert the x, y and z components.
[math]\bar{q} = {(w, -x, -y, -z)}[/math]
etk::Quaternion has a conjugate function
1 |
etk::Quaternion conj = q.conjugate(); |
The conjugate is great because it allows you to unrotate or substract a rotation from another quaternion. Multiplying quaternions adds rotation but multiplying by the conjugate subtracts it.
Rotating Vectors
Quaternions can be used to rotate vectors. This is really useful for converting vectors to a different frame of reference. Example:
A pilot experiences acceleration, but a large component of this is due to gravity. We know that gravity always measures 9.8m/s/s upwards in the world frame of reference and we know the orientation of the aircraft. We can use the aircrafts orientation to rotate a gravity vector into the aircraft frame of reference, then simply subtract it from total acceleration felt by the pilot.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
etk::Quaternion q; etk::Vector<3> euler(285, 2.5, 1.54); euler.toRadians(); q.fromEuler(euler); //this is the orientation quaternion //total acceleration is force felt by accelerometers in the aircraft etk::Vector<3> total_acceleration(1.5, 0.2, 10.5); //gravity makes things feel like they are accelerating up at 9.8m/s/s on the vertical (z) axis etk::Vector<3> gravity(0.0, 0.0, 9.8); //rotate gravity so it's in the pilots frame of reference auto gravity_pilot_frame = q.rotateVector(gravity); //now we can subtract gravity from total acceleration etk::Vector<3> acceleration = total_acceleration-gravity_pilot_frame; |
Angular Rates To Quaternion
Converting angular velocity to a quaternion is easily done with ETK. Simply use the function fromAngularVelocity().
If angular velocity is regular velocity, then a quaternion represents position. It simply doesn’t make sense to convert velocity into a position unless you have a time delta. Velocity equates to a change in distance over a period of time. fromAngularVelocity() requires angular velocity in radians per second, and the sample rate of the gyroscopes in seconds. It will calculate a quaternion that represents that change in angular position.
You can also convert the quaternion back into an angular rate using toAngularVelocity().
Converting To Other Orientation Forms
etk::Quaternions can be converted to and from NED orientation matrices, axis angles and euler angles.
Helpful Resources
1 thought on “Quaternions And How to Really Use Them”