Skip to content
AI360XpertBeta

Homogeneous Coordinates and Projective Geometry

Append one extra number w to each point, so translation and perspective become plain matrix multiplies. Dividing by w returns to pixels, and w equal to zero names the vanishing points where parallels meet.

Point 3 4 written as 3 4 1 shifts by 5 minus 2 to land at 8 2, while direction 1 0.5 0 with w zero never lands and marks the vanishing point
Point 3 4 written as 3 4 1 shifts by 5 minus 2 to land at 8 2, while direction 1 0.5 0 with w zero never lands and marks the vanishing point

Why Does This Exist?

Translation moves every pixel by addition, while rotation and scaling multiply. That split forces vision code into two paths: matrices for most things, plus a stray addition for camera position. With thousands of points per frame, the split also blocks chaining poses into one matrix, and perspective division by depth has nowhere to live.

Homogeneous coordinates fix both by appending ww. A 2D point (x,y)(x, y) becomes (x,y,1)(x, y, 1), translation becomes a 3×33 \times 3 matrix, and projection becomes a matrix multiply followed by one divide. Parallel railway lines then meet at w=0w = 0, which is why one framework covers both ordinary pixels and vanishing points. This page covers the encoding and the divide; one camera using it lives in camera models and matrices, and two cameras using it live in epipolar geometry.

The one idea you need first

A linear transformation is a matrix times a vector: it rotates, scales, or shears, but it always fixes the origin. Translation moves the origin, so it is not linear in two dimensions. Adding ww lifts the plane into 3D rays through the origin, where translation is linear again, and dividing by ww drops back to the plane.

Think of It Like This

Sun rays through a slide projector

A slide sits in a projector and its image lands on the wall. Each bright dot on the wall comes from one straight ray leaving the bulb, passing through one slide dot, and hitting the wall. Double every coordinate of the slide dot and you name the same ray, so the wall dot does not move. Slide the slide sideways and the wall image slides too, which is translation done by moving the ray family.

The bulb is the origin, each ray is a homogeneous point (x,y,w)(x, y, w), and the wall is the plane w=1w = 1 where we divide. A ray running parallel to the wall never hits it: that is w=0w = 0, the vanishing point. Where the analogy stops: real projectors have one bulb and one wall, while projective geometry allows any plane and any center, including points behind the camera.

How It Actually Works

Write a 2D point as p~=[x,y,w]\tilde{p} = [x, y, w], where xx and yy are scaled coordinates and ww is the extra scale. The Euclidean return is the divide:

(x,y)=(xw,yw),w≠0.(x, y) = \left(\frac{x}{w}, \frac{y}{w}\right), \quad w \ne 0.

In plain words: (3,4,1)(3, 4, 1) and (6,8,2)(6, 8, 2) name the same pixel (3,4)(3, 4), because 6/2=36/2 = 3 and 8/2=48/2 = 4. Scaling the triple changes nothing, which is why matrices can act on rays and still mean pixels. When w=0w = 0 the divide is illegal: (1,0.5,0)(1, 0.5, 0) is a direction, not a place, and parallel lines share it as their meeting point at infinity.

Translation becomes linear. With shift tx=5t_x = 5 and ty=−2t_y = -2:

T=[10501−2001],T[341]=[3+54−21]=[821].T = \begin{bmatrix} 1 & 0 & 5 \\ 0 & 1 & -2 \\ 0 & 0 & 1 \end{bmatrix}, \quad T \begin{bmatrix} 3 \\ 4 \\ 1 \end{bmatrix} = \begin{bmatrix} 3 + 5 \\ 4 - 2 \\ 1 \end{bmatrix} = \begin{bmatrix} 8 \\ 2 \\ 1 \end{bmatrix}.

In plain words: TT holds the shift in its last column, TT times the homogeneous point adds it, and dividing 8/18/1 and 2/12/1 returns (8,2)(8, 2). Rotation, scale, and translation now compose by matrix multiplication alone, which is what lets a whole camera pose collapse into [R∣t][R \mid t].

Worked example

Start at (3,4)(3, 4). Homogenize to (3,4,1)(3, 4, 1) and double to (6,8,2)(6, 8, 2): 6/2=36/2 = 3 and 8/2=48/2 = 4, same pixel, confirming scale invariance. Apply TT with (5,−2)(5, -2):

u=3+5=8,v=4−2=2,w=1.u = 3 + 5 = 8, \quad v = 4 - 2 = 2, \quad w = 1.

Divide: 8/1=88/1 = 8 and 2/1=22/1 = 2, so the translated pixel is (8,2)(8, 2). The direction (1,0.5,0)(1, 0.5, 0) has w=0w = 0 and never divides: it is the vanishing point for slope 0.50.5 lines, which is why two parallel lane edges with that slope meet there in the image while staying parallel in 3D.

Code

This runs the worked example with plain Python: homogenize, translate by matrix, then divide, plus the w=0w = 0 guard.

def to_homogeneous(x: float, y: float) -> tuple[float, float, float]:    return (x, y, 1.0)  # Euclidean (x, y) lifts to ray (x, y, 1)

def translate(p: tuple[float, float, float], tx: float, ty: float) -> tuple[float, float, float]:    x, y, w = p  # x and y are scaled coords, w is the extra scale    return (x + tx * w, y + ty * w, w)  # last column of T times w

def to_euclidean(p: tuple[float, float, float]) -> tuple[float, float]:    x, y, w = p    if w == 0:        raise ValueError("w is zero: a direction, not a pixel")  # vanishing point    return (x / w, y / w)

p = to_homogeneous(3.0, 4.0)print(to_euclidean((6.0, 8.0, 2.0)))  # -> (3.0, 4.0) same ray, same pixelprint(to_euclidean(translate(p, 5.0, -2.0)))  # -> (8.0, 2.0)

Both prints match the hand calculation: (6,8,2)(6, 8, 2) returns (3,4)(3, 4), and shifting (3,4,1)(3, 4, 1) by (5,−2)(5, -2) lands at (8,2)(8, 2).

Watch Out For

Forgetting the divide by w after the matrix

The symptom is pixels that drift with scale: (6,8,2)(6, 8, 2) used directly as (6,8)(6, 8) lands twice as far from the origin as (3,4)(3, 4), even though they name the same ray. Every matrix multiply in homogeneous space returns [a,b,w][a, b, w], not pixels. Always divide by the third coordinate before comparing against image positions, exactly as the camera divide does in camera models and matrices.

Treating a w equals zero point as an ordinary pixel

Passing (1,0.5,0)(1, 0.5, 0) through the divide gives infinity or a crash, and normalizing it to (1,0.5,1)(1, 0.5, 1) silently moves the vanishing point into the frame. The symptom is parallel lines that should meet far away but intersect near the center after a bad normalize. Branch on ww: if ww is zero, keep it as a direction for vanishing-point work; only divide when ww is nonzero.

The Quick Version

  • Homogeneous point p~=[x,y,w]\tilde{p} = [x, y, w] returns to pixels as (x/w,y/w)(x/w, y/w); w=0w = 0 is a direction at infinity.
  • (3,4,1)(3, 4, 1) and (6,8,2)(6, 8, 2) are the same pixel, since 6/2=36/2 = 3 and 8/2=48/2 = 4.
  • Translation by (5,−2)(5, -2) is matrix TT, and TT times (3,4,1)(3, 4, 1) is (8,2,1)(8, 2, 1), which divides to (8,2)(8, 2).
  • Rotation, scale, and translation compose as matrices only in homogeneous form.
  • Parallel lines share one w=0w = 0 point, which is the vanishing point in the image.
  • Always divide by ww, and never normalize a w=0w = 0 direction into the frame.