Homogeneous Coordinates in Vision
Homogeneous coordinates add one extra number to every point so that projection, rotation, and translation all become matrix multiplies. The rule is simple: scaled versions name the same point.
Why Does This Exist?
Translation cannot be written as a matrix acting on a -vector, and perspective projection needs a division by depth that no matrix can do either. Vision pipelines chain dozens of rotations, translations, and projections, so expressing each step in a different language would be unworkable. Appending one coordinate fixes both gaps at once: translations become matrix multiplies, and the projection runs as a single multiply followed by one final divide.
The pure algebra of this trick is developed in homogeneous coordinates and projective geometry. This page covers what vision does with it: the projection pipeline, back-projection into rays, and the line arithmetic that later pages build on.
Think of It Like This
A recipe with servings attached
A recipe card reading "2 cups flour, 1 spoon sugar, serves 1" and another reading "4 cups flour, 2 spoons sugar, serves 2" describe the same dish. Divide by servings and both reduce to the same per-person recipe. The servings number is : it carries no flavor, only scale.
The analogy stops at zero servings: a card serving nobody is nonsense in a kitchen, but in geometry is a legitimate point at infinity, a pure direction with no location.
How It Actually Works
A 2D image point becomes the homogeneous triple , and in general with names the point . Any nonzero multiple names the same point, so and are two spellings of . Projection exploits this: computes a triple whose first two entries are only proportional to pixels, and the final divide by the third entry finishes the job. Going backwards, a pixel plus known recovers not a point but a ray: in camera coordinates is the whole line through the optical center, which is exactly why one view cannot fix depth.
Lines fit the same scheme. The line through points and is their cross product , and the intersection of lines and is , all in homogeneous triples.
Worked numbers
Divide by to get , and by to get : same point confirmed. For lines, take and :
which is the line , or . Check: gives and gives . Both lie on it, as constructed.
Code
def to_cart(h: tuple[float, float, float]) -> tuple[float, float]: return (h[0] / h[2], h[1] / h[2]) # divide by w
print(to_cart((2.0, 1.0, 1.0))) # -> (2.0, 1.0)print(to_cart((4.0, 2.0, 2.0))) # -> (2.0, 1.0)Watch Out For
Forgetting the final divide
After the triple's third entry is depth, not . Using the raw triple as pixels scales every coordinate by depth, so near points explode off the frame and far points huddle at the origin. Always divide by the third entry before comparing against pixels.
Solving geometry in raw pixel coordinates
Direct solvers like DLT build matrices from raw pixel values in the thousands, which makes the system ill-conditioned: tiny noise steers the answer. Normalize first, translating points to zero mean and scaling to unit spread, solve, then map back. This is the Hartley trick, and homography estimation applies it before every fit.
The Quick Version
- Appending turns translations and projections into matrix multiplies.
- Nonzero scalings name the same point: is is .
- Cartesian coordinates come back by dividing by .
- encodes a direction, a point at infinity with no location.
- Lines come from point cross products, and intersections from line cross products.
- Back-projecting a pixel yields a ray, never a point: depth stays unknown.