Skip to content

Quaternion

Robotics / math

Quaternions are an alternate way to describe orientation or rotations in 3D space using an ordered set of four numbers. They have the ability to uniquely describe any three-dimensional rotation about an arbitrary axis and do not suffer from gimbal lock

quaternion

Unit quaternion

A unit quaternion like unit vector is simply a vector with length (magnitude) equal to 1, but pointing in the same direction as the original vector.

quaternion: \(\(q = w + xi + yj + zk\)\)

as a vector \(\(q = (w, x, y, z)\)\)

calc the norm \(\(\|q\| = \sqrt{w^2 + x^2 + y^2 + z^2}\)\)

calc unit quaternion \(\(q_{unit} = \frac{q}{\|q\|} = \left(\frac{w}{\|q\|}, \frac{x}{\|q\|}, \frac{y}{\|q\|}, \frac{z}{\|q\|}\right)\)\)

code example
import numpy as np

def unit_quaternion(q):
    q = np.array(q, dtype=float)  # q = [w, x, y, z]
    norm = np.linalg.norm(q)
    if norm == 0:
        raise ValueError("Zero quaternion cannot be normalized")
    return q / norm

# Example
q = [2, 3, 1, 4]
q_unit = unit_quaternion(q)
print("Unit quaternion:", q_unit)
print("Norm:", np.linalg.norm(q_unit))  # should be 1

Conjugate

A quaternion is:

\[ q = (x, y, z, w) \]

The conjugate is:

\[ q^* = (-x, -y, -z, w) \]

flip the signs of x, y, z, keep w the same.


Slerp

Spherical Linear intERPolation (SLERP) is a method to smoothly interpolate between two orientations (unit quaternions). - SLERP traces the shortest arc along that sphere between them

\[SLERP(q_0, q_1, t) = \frac{\sin((1-t)\theta)}{\sin(\theta)} q_0 + \frac{\sin(t\theta)}{\sin(\theta)} q_1\]
  • At t=0 → result = q0
  • At t=1 → result = q1
  • At t=0.5 → halfway rotation between them.
Demo: using scipy
import numpy as np
from scipy.spatial.transform import Rotation as R, Slerp

# Define two orientations (Euler angles in degrees)
r0 = R.from_euler('xyz', [0, 0, 0], degrees=True)     # facing forward
r1 = R.from_euler('xyz', [0, 90, 0], degrees=True)    # rotate 90° around Y

# Define key times and rotations
key_times = [0, 1]              # start=0, end=1
key_rots = R.concatenate([r0, r1])  # list of rotations

# Create SLERP object
slerp = Slerp(key_times, key_rots)

# Interpolation at multiple times
times = np.linspace(0, 1, 5)    # t=0.0,0.25,0.5,0.75,1.0
interp_rots = slerp(times)

# Print quaternions
print("Interpolated quaternions [x, y, z, w]:")
for q in interp_rots.as_quat():
    print(q.round(3))

Inverse

unit quaternion

If q is a unit quaternion (∥q∥=1), then the conjugate is also the inverse.

when not unit length

\[q^{-1} = \frac{q^*}{\|q\|^2}\]
\[q \cdot q^{-1} = 1\]

Multiple

by vector

\[\mathbf{v}' = q \, \mathbf{v} \, q^{-1}\]

we treat the vector as pure quaternion

\[\mathbf{v} = (v_x, v_y, v_z, 0)\]
Numpy Example
import numpy as np

def q_mult(q1, q2):
    x1, y1, z1, w1 = q1
    x2, y2, z2, w2 = q2
    return np.array([
        w1*x2 + x1*w2 + y1*z2 - z1*y2,
        w1*y2 - x1*z2 + y1*w2 + z1*x2,
        w1*z2 + x1*y2 - y1*x2 + z1*w2,
        w1*w2 - x1*x2 - y1*y2 - z1*z2
    ])

def q_conjugate(q):
    return np.array([-q[0], -q[1], -q[2], q[3]])

# Rotation quaternion: 90° about Z
theta = np.pi/2
q = np.array([0, 0, np.sin(theta/2), np.cos(theta/2)])

# Conjugate (inverse)
q_conj = q_conjugate(q)

# Our vector (as pure quaternion)
v = np.array([1, 0, 0, 0])

# Rotate vector: v' = q * v * q^-1
v_rot = q_mult(q_mult(q, v), q_conj)

print("Original vector:", v[:3])
print("Rotated vector :", v_rot[:3])

by quaternion

Quaternion multiplication is used to combine rotations in 3D space

\[q_1 = (w_1, x_1, y_1, z_1), \quad q_2 = (w_2, x_2, y_2, z_2)\]
\[q = q_2 \cdot q_1 = \Big( w_2 w_1 - x_2 x_1 - y_2 y_1 - z_2 z_1, \; w_2 x_1 + x_2 w_1 + y_2 z_1 - z_2 y_1, \; w_2 y_1 - x_2 z_1 + y_2 w_1 + z_2 x_1, \; w_2 z_1 + x_2 y_1 - y_2 x_1 + z_2 w_1 \Big)\]

Warning

\[q_2 q_1 \neq q_1 q_2\]

Reference

watch again