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.
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 . A 2D point becomes , translation becomes a matrix, and projection becomes a matrix multiply followed by one divide. Parallel railway lines then meet at , 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 lifts the plane into 3D rays through the origin, where translation is linear again, and dividing by 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 , and the wall is the plane where we divide. A ray running parallel to the wall never hits it: that is , 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 , where and are scaled coordinates and is the extra scale. The Euclidean return is the divide:
In plain words: and name the same pixel , because and . Scaling the triple changes nothing, which is why matrices can act on rays and still mean pixels. When the divide is illegal: is a direction, not a place, and parallel lines share it as their meeting point at infinity.
Translation becomes linear. With shift and :
In plain words: holds the shift in its last column, times the homogeneous point adds it, and dividing and returns . Rotation, scale, and translation now compose by matrix multiplication alone, which is what lets a whole camera pose collapse into .
Worked example
Start at . Homogenize to and double to : and , same pixel, confirming scale invariance. Apply with :
Divide: and , so the translated pixel is . The direction has and never divides: it is the vanishing point for slope 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 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: returns , and shifting by lands at .
Watch Out For
Forgetting the divide by w after the matrix
The symptom is pixels that drift with scale: used directly as lands twice as far from the origin as , even though they name the same ray. Every matrix multiply in homogeneous space returns , 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 through the divide gives infinity or a crash, and normalizing it to 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 : if is zero, keep it as a direction for vanishing-point work; only divide when is nonzero.
The Quick Version
- Homogeneous point returns to pixels as ; is a direction at infinity.
- and are the same pixel, since and .
- Translation by is matrix , and times is , which divides to .
- Rotation, scale, and translation compose as matrices only in homogeneous form.
- Parallel lines share one point, which is the vanishing point in the image.
- Always divide by , and never normalize a direction into the frame.