Robotics

Forward Kinematics (DH Parameters)

From joint angles to end-effector pose, by 4×4 matrix multiplication

Forward kinematics computes the position and orientation of a robot's end effector from joint angles. The Denavit–Hartenberg convention turns each joint into a 4×4 homogeneous transform; multiply them together and you have the end-effector pose. Always solvable, always fast — the inverse problem is the one that hurts.

  • DOF, typical industrial arm6
  • DH parameters per joint4 (α, a, θ, d)
  • Transform per joint4×4 matrix
  • Solution countAlways exactly 1
  • Compute cost (6-DOF)≈400 floating-point ops

Interactive visualization

Press play, or step through manually. The visualization is yours to drive — try it before reading on.

Open visualization fullscreen ↗

Watch the 60-second explainer

A condensed visual walkthrough — narrated, captioned, under a minute.

From joint angles to a pose

A robot arm is a chain of rigid links connected by joints. Each joint adds one degree of freedom — typically a rotation angle (revolute) or a sliding distance (prismatic). Given the current value of every joint, what's the position and orientation of the gripper at the end of the chain? That's the forward kinematics problem.

The brute-force answer is "compose the geometric transformations from base to end effector". A rotation here, a translation there, repeat for every link. Done by hand for a 6-joint arm, this is bookkeeping hell — every author chooses different axes, and signs flip in places nobody can audit. The DH convention is a discipline that fixes this: it forces you to lay out the link frames in a specific way, so the transform between consecutive frames always takes the same parameterized form. Then forward kinematics is "fill in the table, multiply the matrices".

The DH rules in plain language

Pick a coordinate frame at every joint (standard DH):

  1. z-axis at joint i is the axis of motion (rotation axis for revolute, translation axis for prismatic).
  2. x-axis at joint i points along the common perpendicular from zi−1 to zi.
  3. y-axis follows from the right-hand rule.

Four parameters describe how to get from frame i−1 to frame i:

  • θi — rotate around zi−1 (joint variable for revolute).
  • di — translate along zi−1 (joint variable for prismatic).
  • ai — translate along the new xi axis (link length).
  • αi — rotate around the new xi axis (link twist).

Two of these vary with the joint state; the other two are fixed by the geometry of the robot.

The DH transform matrix

The transform from frame i−1 to frame i, given the four DH parameters, is:

          | cos θ      -sin θ cos α      sin θ sin α      a cos θ |
T(i-1, i) = | sin θ       cos θ cos α     -cos θ sin α      a sin θ |
          | 0           sin α             cos α            d       |
          | 0           0                 0                1       |

Forward kinematics for an n-joint arm is the chain product:

T0,n = T0,1 · T1,2 · T2,3 · ... · Tn−1,n

The result is a 4×4 matrix whose upper-left 3×3 block is the orientation of the end effector and whose upper-right 3×1 column is its position, all expressed in the base frame.

DH and competing parametrizations

ParametrizationYearParams/jointStrengthWeakness
Standard DH (Denavit-Hartenberg)19554Most compact, ubiquitous in textbooksFrames not at the joint physically; non-unique parametrization
Modified DH (Craig)19864Frame i at proximal end of link i; cleaner for branch chainsDifferent matrix form than standard; mixing conventions silently breaks code
Hayati parametrization19855 (per joint when needed)Adds a fifth param to handle parallel consecutive joint axesMore complex; only needed when standard DH degenerates
Zero-reference / POE19946 (twist coords)Singularity-free, no per-joint frame placementLarger memory footprint; requires Lie-algebra background
URDF (XML link tree)20096 (xyz, rpy)Tool-friendly, used by ROS; trivial to authorBulkier than DH; ambiguous if not paired with joint axis convention
Raw 4×4 per link16 (6 independent)No conventions to memorizeNo structure to exploit; can't tell rotation from translation in the data

Worked example: 2-link planar arm

A planar arm with two revolute joints, link lengths a1 = 0.5 m and a2 = 0.3 m, joint angles θ1 and θ2. The DH table:

iαiaiθidi
100.5θ10
200.3θ20

With αi = 0, each transform is a rotation about z by θ followed by translation along x by a. The chain product with θ1 = 30°, θ2 = 45° gives:

xtip = a1cos(θ1) + a2cos(θ12) = 0.5·cos(30°) + 0.3·cos(75°) = 0.511 m
ytip = a1sin(θ1) + a2sin(θ12) = 0.5·sin(30°) + 0.3·sin(75°) = 0.540 m

End-effector orientation (z-rotation only for a planar arm) is θ1 + θ2 = 75°. The full 4×4 result encodes all three numbers in standard form.

DH product loop in code

import numpy as np

def dh_transform(alpha, a, theta, d):
    """Return the 4x4 standard-DH transform for one joint."""
    ct, st = np.cos(theta), np.sin(theta)
    ca, sa = np.cos(alpha), np.sin(alpha)
    return np.array([
        [ct,  -st * ca,   st * sa,   a * ct],
        [st,   ct * ca,  -ct * sa,   a * st],
        [0,        sa,         ca,        d],
        [0,         0,          0,        1],
    ])

def forward_kinematics(dh_table, joint_values):
    """dh_table: list of (alpha, a, theta_offset, d_offset, joint_type).
       joint_values: list of theta (revolute) or d (prismatic) values."""
    T = np.eye(4)
    for params, q in zip(dh_table, joint_values):
        alpha, a, theta_off, d_off, kind = params
        theta = theta_off + (q if kind == 'R' else 0.0)
        d     = d_off     + (q if kind == 'P' else 0.0)
        T = T @ dh_transform(alpha, a, theta, d)
    return T

# UR5 (Universal Robots) standard-DH parameters from manufacturer datasheet:
# (alpha,           a,        theta_off, d_off,    kind)
ur5 = [
    ( np.pi/2,  0.0,      0.0,  0.089159, 'R'),
    ( 0.0,     -0.425,    0.0,  0.0,      'R'),
    ( 0.0,     -0.39225,  0.0,  0.0,      'R'),
    ( np.pi/2,  0.0,      0.0,  0.10915,  'R'),
    (-np.pi/2,  0.0,      0.0,  0.09465,  'R'),
    ( 0.0,      0.0,      0.0,  0.0823,   'R'),
]

# Home pose: all joints at zero
T_end = forward_kinematics(ur5, [0, 0, 0, 0, 0, 0])
print(T_end[:3, 3])  # end-effector position in meters

The loop is twelve lines and runs in microseconds even with NumPy's overhead. Production robot controllers compute the same thing in C with hand-unrolled trigonometric calls, hitting tens of nanoseconds per call.

Real-world specs

  • Universal Robots UR5 — 6-DOF collaborative arm, 5 kg payload, ±360° on every joint, ±0.03 mm repeatability, 850 mm reach. Public DH table widely cited in academic papers.
  • ABB IRB 6700 — heavy industrial 6-DOF, 235 kg payload, 3.2 m reach, ±0.05 mm repeatability. Uses modified DH internally.
  • Franka Emika Panda — 7-DOF research arm, redundant joint configuration enables null-space motion (the 7th joint allows changing arm posture without moving the end effector).

Common failure modes

  • Mixing standard and modified DH conventions. A datasheet using Craig's modified DH plugged into code that expects standard DH gives wrong end-effector poses with no error message — you discover the bug only when the gripper smashes into a workpiece.
  • Singularities at full extension. When a 6-DOF arm reaches near its kinematic limit, multiple joint configurations produce the same Cartesian motion and the inverse Jacobian becomes ill-conditioned. Forward kinematics works fine here; only the inverse problem fails.
  • Joint backlash compounding. Each gearbox has a few arc-minutes of backlash. In a 6-DOF chain, the lever-arm-amplified end-effector error can reach millimeters even with sub-arc-minute joint encoders. Calibration measures and compensates this.
  • Ambiguous zero positions. Two different "θ = 0" conventions can produce DH parameters that look different but describe the same arm. When porting parameters between codebases, always verify the home pose visually before commanding any motion.
  • Frame numbering off-by-one. Some sources number frames 1..n, others 0..n with the base as frame 0. A loop that multiplies T1,2 through Tn,n+1 instead of T0,1 through Tn−1,n drops the base transform and gives a relative pose.

Variants

  • Modified DH (Craig 1986) — frame i at proximal end of link i; dominant in modern textbooks and ROS-based libraries.
  • Hayati parametrization — adds a fifth parameter to handle consecutive parallel joint axes, where standard DH becomes singular in parameter space.
  • Product of Exponentials (POE) — built on screw theory and Lie groups; avoids per-joint frame placement. Each joint contributes a constant twist vector; the forward map is a product of matrix exponentials.
  • Closed-form vs iterative inverse — arms with three intersecting wrist axes (Pieper's criterion) have closed-form 8-solution Pieper inverse. Otherwise, numerical methods (Jacobian-pseudoinverse, Levenberg-Marquardt, FABRIK) solve iteratively.

Frequently asked questions

Why use DH parameters instead of just describing each joint with x, y, z, roll, pitch, yaw?

DH parameters compress each joint's pose change into four numbers (α, a, θ, d) instead of six. The compression is possible because Denavit and Hartenberg observed that you can always choose your axis conventions so that two of the six parameters are zero. The resulting product is more compact, easier to debug, and is the convention every robotics paper assumes when they hand you a parameter table.

What's the difference between standard DH and modified DH?

Standard DH (1955) attaches frame i at the distal end of link i. Modified DH (Craig, 1986) attaches frame i at the proximal end. The matrices differ, and mixing conventions gives wrong answers silently — the most common bug in robot kinematics code.

What is a kinematic singularity?

A configuration where the robot loses one or more degrees of freedom — multiple joint motions produce the same end-effector velocity, so the inverse Jacobian becomes ill-conditioned. The classic example is a 6-DOF arm fully extended. Forward kinematics still works at singularities; inverse kinematics blows up there.

Why is each transform 4×4 and not 3×3?

A 3×3 rotation matrix only handles orientation. To compose rotation and translation in a single matrix you need a 4×4 homogeneous transform — the upper-left 3×3 block is rotation, the upper-right 3×1 column is translation, and the bottom row [0 0 0 1] makes the matrix multiplication chain commute correctly. The 4×4 representation is what makes 'multiply all the joint matrices and you get end-effector pose' work.

Are DH parameters unique for a given robot?

No. The choice of axes between consecutive joints leaves several degrees of freedom — different valid DH tables describe the same physical arm. The end-effector pose is unique; the table that produces it is not.

How does forward kinematics relate to inverse kinematics?

Forward kinematics is easy: given joint angles, compute pose by matrix multiplication. Always one answer, always fast. Inverse kinematics is hard: given pose, find joint angles. May have zero, one, eight, or infinitely many solutions, and may not have a closed form.