Image Transformations and Homography
Affine maps resize, rotate, and shear images while keeping parallel lines parallel, and a homography adds perspective so tilted planes flatten back out.
Why Does This Exist?
Cameras rarely face their subjects squarely. A photo of a document from an angle turns its rectangle into a trapezoid, a rotated photo needs straightening, and a thumbnail needs resizing. The naive fix treats each case separately, and it breaks the moment views combine: rotation plus perspective cannot be undone by shifting pixels around. All of these edits are one family of geometric maps from pixel coordinates to pixel coordinates, which is why panorama stitching and 3D pipelines like structure from motion build on them. Point fitting details live in homography estimation; this page covers what each map can and cannot do.
Think of It Like This
Stretching rubber over a frame
Think of an image as a flexible rubber sheet with a picture drawn on it. Affine moves stretch the sheet uniformly, rotate it, or slide it across the table: parallel lines stay parallel, so a square becomes a parallelogram but never tapers. A homography tilts the sheet into the third dimension before photographing it, so far parts shrink and parallels converge.
Where it stops: real rubber also stretches unevenly and wrinkles, while these maps are globally rigid formulas with 6 and 8 degrees of freedom. They model one flat plane seen from two viewpoints, never curved surfaces or lens bulge.
How It Actually Works
Affine maps with six degrees of freedom
Affine maps cover translation, scaling, rotation, and shear. They preserve straight lines and parallelism: a rectangle becomes a parallelogram, never a trapezoid. In homogeneous coordinates every affine map is one matrix :
The entries control rotation, scale, and shear while and translate. Six unknowns need exactly three point pairs to solve.
Homography with the perspective divide
A homography maps one plane to another across viewpoints with a matrix and eight degrees of freedom (nine entries minus one overall scale):
Dividing by is the perspective step: points farther away get larger and shrink. Four point pairs determine , solved in practice with RANSAC over many candidate matches.
Interpolation fills the grid
Mapped coordinates land between pixels, for example , so the warper must estimate values on the integer grid. Nearest-neighbor snaps to the closest pixel (fast, blocky), bilinear averages the neighbors (the standard default), and bicubic fits the neighbors (smoothest, costliest). Libraries loop over the destination grid and pull from the source (inverse warping) so no holes remain.
Worked example
Translate the point by with the affine matrix whose rows are and . Then and , so lands on . Now apply the uniform scale homography : it sends to . Dividing gives and : uniform scale holds the position after the divide, and the lesson is the mechanism, not the motion. Change to rows , , and the same yields , so and : depth-dependent shifts the coordinate, which is exactly the perspective effect affine maps cannot produce. The snippet below runs the second case.
Code
The point of this snippet is the worked example's two maps in plain Python: the affine translation and the homography with its divide by .
# -> Affine translate of (10, 20), then a homography with divide by w.def affine(pt, m): (x, y) = pt return (m[0][0] * x + m[0][1] * y + m[0][2], m[1][0] * x + m[1][1] * y + m[1][2])
def homography(pt, h): (x, y) = pt xp = h[0][0] * x + h[0][1] * y + h[0][2] yp = h[1][0] * x + h[1][1] * y + h[1][2] w = h[2][0] * x + h[2][1] * y + h[2][2] return (xp / w, yp / w)
print(affine((10, 20), [[1, 0, 5], [0, 1, -3]]))# -> (15, 17)print(homography((60, 40), [[2, 0, 0], [0, 2, 0], [0.01, 0, 1]]))# -> (75.0, 50.0)The affine call returns and the homography call returns : with , and .
Watch Out For
Collinear points collapsing the solve
Three collinear points never determine an affine map and four near-collinear points produce a wild homography, because the equations stop being independent. The symptom is extreme skew or numeric blowup from innocent-looking clicks. Fix: spread calibration points across the plane, covering corners rather than lining one edge.
Forward warping leaving holes
Looping over source pixels and pushing them forward lands values between destination grid points, leaving speckled holes. The symptom is a warped image with scattered empty pixels. Fix: loop over the destination grid and pull through the inverse map, which is what library warp functions do internally.
The Quick Version
- Image warps map pixel coordinates to pixel coordinates with matrix multiplication in homogeneous form.
- Affine maps use a matrix with 6 degrees of freedom, need 3 point pairs, and keep parallels parallel.
- Homographies use a matrix with 8 degrees of freedom, need 4 point pairs, and divide by for perspective.
- The worked translate sends to , and the worked homography sends to .
- Non-integer landing spots need interpolation, and inverse warping avoids holes.