AnyLearn
All lessons
Roboticsintermediate

Coordinate Frames and Homogeneous Transforms

Master how roboticists describe rigid-body pose. Build rotation matrices from scratch, pack them into 4x4 homogeneous transforms, and compose multiple frames with NumPy to track every link in a robot arm.

Not signed in โ€” your progress and quiz score won't be saved.
Lesson progress1 / 8

Why we need coordinate frames

A robot arm has many moving parts โ€” base, links, end-effector, tool, and the object being grasped. Each part has its own natural reference frame. To say "the gripper is 0.3 m above the table" you need to express a point in one frame relative to another. In robotics we attach a right-handed Cartesian frame {x,y,z}\{x, y, z\} to every rigid body. The world frame {0}\{0\} (or base frame) is fixed; link frames {1},{2},โ€ฆ\{1\}, \{2\}, \ldots move with their links. A pose is the full description of where a frame sits: both its orientation (how it is rotated) and its position (where its origin is). Getting this bookkeeping right is 80% of kinematics โ€” the rest is just multiplying matrices.

Full lesson text

All 8 steps on one page โ€” for reading, reference, and search.

Show

1. Why we need coordinate frames

A robot arm has many moving parts โ€” base, links, end-effector, tool, and the object being grasped. Each part has its own natural reference frame. To say "the gripper is 0.3 m above the table" you need to express a point in one frame relative to another. In robotics we attach a right-handed Cartesian frame {x,y,z}\{x, y, z\} to every rigid body. The world frame {0}\{0\} (or base frame) is fixed; link frames {1},{2},โ€ฆ\{1\}, \{2\}, \ldots move with their links. A pose is the full description of where a frame sits: both its orientation (how it is rotated) and its position (where its origin is). Getting this bookkeeping right is 80% of kinematics โ€” the rest is just multiplying matrices.

2. Rotation matrices

The orientation of frame {B}\{B\} relative to frame {A}\{A\} is captured by a rotation matrix RโˆˆR3ร—3R \in \mathbb{R}^{3 \times 3}, whose columns are the unit vectors of {B}\{B\}'s axes expressed in {A}\{A\}. Two non-negotiable properties:

RโŠคR=Idetโก(R)=+1R^\top R = I \qquad \det(R) = +1

The set of all such matrices is the Special Orthogonal group SO(3)SO(3). A planar rotation by angle ฮธ\theta about the zz-axis:

Rz(ฮธ)=[cosโกฮธโˆ’sinโกฮธ0sinโกฮธcosโกฮธ0001]R_z(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix}

Because RโŠค=Rโˆ’1R^\top = R^{-1}, inverting an orientation is free โ€” just transpose. Composite rotations multiply: RAC=RABRBCR_{AC} = R_{AB} R_{BC}. Read subscripts right to left, just like function composition.

3. Translation and the homogeneous transform

Orientation alone is not enough; we also need the position pโˆˆR3p \in \mathbb{R}^3 of a frame's origin. Stacking them into one 4ร—44 \times 4 matrix gives the homogeneous transform:

T=[Rp0โŠค1]T = \begin{bmatrix} R & p \\ 0^\top & 1 \end{bmatrix}

To transform a point qBq_B in frame {B}\{B\} into frame {A}\{A\}:

[qA1]=TAB[qB1]\begin{bmatrix} q_A \\ 1 \end{bmatrix} = T_A^B \begin{bmatrix} q_B \\ 1 \end{bmatrix}

The appended "1" makes translation linear โ€” a clean trick that collapses the affine map into one matrix multiply. The set of valid TT matrices forms the Special Euclidean group SE(3)SE(3). The efficient inverse is:

Tโˆ’1=[RโŠคโˆ’RโŠคp0โŠค1]T^{-1} = \begin{bmatrix} R^\top & -R^\top p \\ 0^\top & 1 \end{bmatrix}

4. Composing transforms along a chain

If you know the pose of frame {B}\{B\} in {A}\{A\}, and the pose of frame {C}\{C\} in {B}\{B\}, then {C}\{C\} in {A}\{A\} is:

TAC=TABโ€…โ€ŠTBCT_A^C = T_A^B \; T_B^C

Read right to left: first express things in {B}\{B\}, then in {A}\{A\}. Order matters โ€” matrix multiplication is not commutative. For a 3-link arm with transforms T01,T12,T23T_0^1, T_1^2, T_2^3, the end-effector pose in the world is:

T03=T01โ€…โ€ŠT12โ€…โ€ŠT23T_0^3 = T_0^1 \; T_1^2 \; T_2^3

This chain is exactly forward kinematics. The associativity of matrix multiplication means you can evaluate left-to-right or right-to-left; the numerics are identical (within floating-point precision). A common bug: accidentally using T01T02T_0^1 T_0^2 instead of T01T12T_0^1 T_1^2 โ€” check your superscript/subscript conventions.

5. Frames attached to robot links

Convention: frame {i}\{i\} is rigidly attached to link ii and moves with it. When joint ii rotates by qiq_i, the transform Tiโˆ’1iT_{i-1}^{i} changes โ€” the rotation part absorbs qiq_i while the translation encodes the fixed link geometry.

FrameAttached toChanges when
{0}\{0\}Ground / baseNever
{1}\{1\}Link 1Joint 1 moves
{2}\{2\}Link 2Joints 1 or 2 move
{n}\{n\}End-effectorAny joint moves

Choosing frame origins and axes haphazardly creates ugly algebra. The Denavit-Hartenberg convention (next lesson) gives a systematic 4-parameter recipe that places frames consistently along the kinematic chain.

6. NumPy: building and composing transforms

Here is the everyday pattern for a robotics engineer:

import numpy as np

def rot_z(theta):
    c, s = np.cos(theta), np.sin(theta)
    return np.array([[c, -s, 0],
                     [s,  c, 0],
                     [0,  0, 1]])

def make_T(R, p):
    """4x4 homogeneous transform from 3x3 R and 3-vector p."""
    T = np.eye(4)
    T[:3, :3] = R
    T[:3,  3] = p
    return T

def T_inv(T):
    R, p = T[:3, :3], T[:3, 3]
    Ti = np.eye(4)
    Ti[:3, :3] = R.T
    Ti[:3,  3] = -R.T @ p
    return Ti

# Link 1: rotate 45 deg about z, translate 1 m along x
T01 = make_T(rot_z(np.pi/4), [1.0, 0, 0])
# Link 2: rotate 30 deg about z, translate 0.5 m along x
T12 = make_T(rot_z(np.pi/6), [0.5, 0, 0])

T02 = T01 @ T12   # end-effector in world frame
print(T02[:3, 3]) # position of EE

Always use np.eye(4) + slice assignment โ€” it is readable and immune to sign errors in the bottom row.

7. Transform chain for a 2-link planar arm

Each joint introduces one transform; they multiply left to right:

flowchart LR
  W["World frame {0}"]
  J1["Joint 1 applies rot_z(q1)"]
  F1["Link 1 frame {1}"]
  J2["Joint 2 applies rot_z(q2)"]
  F2["Link 2 frame {2}"]
  EE["End-effector {EE}"]
  W --> J1
  J1 --> F1
  F1 --> J2
  J2 --> F2
  F2 --> EE

8. Sanity-checking your matrices

Two checks catch most bugs before they spread down the kinematic chain:

def check_T(T, label="T"):
    R = T[:3, :3]
    assert np.allclose(R @ R.T, np.eye(3), atol=1e-9), \
        f"{label}: rotation not orthogonal"
    assert np.isclose(np.linalg.det(R), 1.0, atol=1e-9), \
        f"{label}: det(R) != 1"
    assert np.allclose(T[3], [0, 0, 0, 1]), \
        f"{label}: bad bottom row"

Numerical drift accumulates when you compose many transforms. If you multiply 50+ matrices, re-orthogonalize via SVD:

U, _, Vt = np.linalg.svd(R)
R_clean = U @ Vt

This projects the drift-corrupted matrix back onto SO(3)SO(3). In production code, run check_T after every FK solve โ€” it costs microseconds and saves hours of debugging.

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. A rotation matrix $R$ satisfies $R^\top R = I$. What does $\det(R) = -1$ indicate?
    • A valid rotation combined with uniform scaling.
    • A reflection โ€” not a proper rigid-body rotation.
    • A 180-degree rotation about some axis.
    • The matrix is numerically ill-conditioned.
  2. Given $T_A^B$ and $T_B^C$, which expression gives the pose of frame $\{C\}$ in frame $\{A\}$?
    • $T_A^C = T_B^C \; T_A^B$
    • $T_A^C = T_A^B + T_B^C$
    • $T_A^C = T_A^B \; T_B^C$
    • $T_A^C = (T_A^B)^{-1} \; T_B^C$
  3. What is the efficient formula for $T^{-1}$ when $T = \begin{bmatrix}R & p \\ 0^\top & 1\end{bmatrix}$?
    • $\begin{bmatrix} R^{-1} & -p \\ 0^\top & 1 \end{bmatrix}$
    • $\begin{bmatrix} R^\top & -R^\top p \\ 0^\top & 1 \end{bmatrix}$
    • $\begin{bmatrix} -R & p \\ 0^\top & 1 \end{bmatrix}$
    • $\begin{bmatrix} R^\top & p \\ 0^\top & 1 \end{bmatrix}$
  4. Frame $\{2\}$ is attached to link 2 of a 2-DOF arm. Which joints affect $T_0^2$?
    • Only joint 2, because frame $\{2\}$ is defined relative to frame $\{1\}$.
    • Only joint 1, because it rotates the entire arm.
    • Both joints 1 and 2.
    • Neither โ€” $T_0^2$ is constant once the arm is assembled.
  5. Which NumPy expression correctly checks that a $3\times3$ matrix R is a valid rotation matrix?
    • `np.allclose(R @ R, np.eye(3)) and np.linalg.det(R) == 1`
    • `np.allclose(R @ R.T, np.eye(3)) and np.isclose(np.linalg.det(R), 1.0)`
    • `np.allclose(R + R.T, np.zeros((3,3))) and np.linalg.det(R) > 0`
    • `np.allclose(R, R.T) and np.linalg.det(R) == 1`

Related lessons