Every network in this series so far has treated an image as a grid of numbers. But an image is also a measurement of the 3D world: light from a point in a scene passes through a lens and lands on one pixel. Where it lands follows a few lines of geometry, and that geometry is what makes it possible to stitch panoramas, place virtual objects in a camera view, measure distances from photos, or rebuild a scene in 3D.

It also shows up inside deep learning more often than one might think. The random crops, flips and rotations used for augmentation in Learning Without Labels are 2D transformations from this post, and the training code there applied them with affine_grid and grid_sample, which is exactly the inverse warping described in §3. Neural radiance fields and 3D Gaussian splatting start from the camera model in §5–6.

This post builds that model step by step, from homogeneous coordinates to the full projection , with an interactive lab for each step. The lab’s maths is checked against OpenCV (§10). The following posts use it to match features between images and to reconstruct 3D scenes.


1. Homogeneous Coordinates

A point in an image is normally written . Homogeneous coordinates add a third number: , with the rule that two vectors describe the same point if one is a multiple of the other, so for any . To get the ordinary coordinates back, divide by the last entry: . A tilde marks a homogeneous vector: .

This looks like bookkeeping, but it has a geometric meaning. Each image point corresponds to a ray through the origin, and the image is where the rays cross the plane :

xwthe image: the line w = 10(2, 1)(4, 2): the same point(1, 0): w = 0, parallel to the image line, never meets itEvery image point is a ray through the origin.(x, w) and (kx, kw) name the same point for any k ≠ 0.To read off the coordinate, divide by w.Rays with w = 0 are points at infinity.In 2D images the same holds one dimension up:(x, y, w) with w = 0 is a direction, the point whereparallel lines meet. A projective transformationcan turn it into an ordinary point: a vanishing point.

Three things become simple with this extra coordinate:

  1. Translation becomes a matrix product. is not linear in , but in homogeneous coordinates it is: . So every transformation in this post, including the camera itself, is one matrix, and a chain of them is one matrix product.
  2. Lines are vectors too. The line is , and a point lies on it if . The intersection of two lines is their cross product, , and the line through two points is .
  3. Points at infinity are ordinary vectors. Two parallel lines, for example and , are and ; their cross product is . This vector has : it is the point at infinity in the direction , where the two parallel lines “meet”.

The third point is what a camera makes visible. Parallel lines on a road meet at a vanishing point in a photo, because the camera’s projective transformation maps their point at infinity to an ordinary point:


2. The 2D Transformations

Every 2D transformation in this post is a 3 × 3 matrix acting on homogeneous coordinates. They form a hierarchy: each class adds freedom and preserves less.

translation[I | t]2 degrees of freedompositionkeeps: lengths, anglesEuclidean[R | t]3 degrees of freedom+ orientationkeeps: lengths, anglessimilarity[sR | t]4 degrees of freedom+ sizekeeps: angles, ratiosaffine[A]6 degrees of freedom+ shear, unequal stretchingkeeps: parallel linesprojective[H] (3 × 3)8 degrees of freedom+ perspectivekeeps: straight lineseach step adds freedom and keeps less; every class contains all the classes to its left
Transformation (top two rows of the matrix)DOFPreserves
Translation 2orientation, lengths, angles
Euclidean (rigid) 3lengths, angles
Similarity 4angles, ratios of lengths
Affine , any 2 × 2 6parallel lines, ratios of areas
Projective (homography) , all 9 entries, up to scale8straight lines, cross-ratios

For the first four the last row is , so a point at infinity () stays at infinity: parallel lines stay parallel. Only a homography, whose last row can be anything, moves points at infinity into the image, which is why it can model perspective. A homography has 9 entries but only 8 degrees of freedom, since and give the same mapping.

The degrees of freedom tell you how many point correspondences are needed to estimate a transformation. Each point pair gives 2 equations, so a translation needs 1 pair, a similarity 2, an affine transformation 3, and a homography 4 (no three of them on a line). With more pairs than that, the parameters are fitted by least squares. Drag the corners and switch between the models:

Estimating a homography from 4 points

For a pair , the equation says , and similarly for . Multiplying out the denominator gives two equations that are linear in the 9 entries of :

Stacking these for all pairs gives , and the solution is the right singular vector of with the smallest singular value: the direct linear transform (DLT). With exactly 4 pairs it is exact; with more noisy pairs it minimises an algebraic error, and it is much more accurate if the points are first normalised (shifted to their centroid and scaled to an average distance of , Hartley 1997). With wrong matches among the pairs, least squares breaks down completely; that is the job of RANSAC, in the next post.


3. Warping an Image: Backwards, Not Forwards

A transformation matrix multiplies coordinates, not images. To produce a warped image, there are two options:

  • Forward mapping: send every source pixel to and round. Where the image is enlarged, several target pixels receive nothing, leaving holes (tick “map pixels forward” in the lab above to see them), and where it shrinks, several source pixels land on the same target.
  • Inverse mapping: for every target pixel, compute where it comes from, , and interpolate the source image there (bilinear interpolation uses the 4 nearest pixels). Every target pixel gets exactly one value.

Image libraries (OpenCV’s warpPerspective, PyTorch’s grid_sample) all use inverse mapping. It is also what makes warping differentiable: in a spatial transformer network (Jaderberg et al., 2015), a small network predicts the 6 parameters of an affine transformation, affine_grid computes the source location of every output pixel, and grid_sample interpolates there, so the gradient flows back into the predicted parameters.


4. Transformations in 3D

The same hierarchy exists in 3D, with 4 × 4 matrices acting on : translation (3 degrees of freedom), Euclidean (6), similarity (7), affine (12) and projective (15).

The important one for cameras is the rigid (Euclidean) transformation . A 3D rotation matrix has 9 entries but only 3 degrees of freedom, because it must satisfy and . It can be written in several ways:

  • Euler angles (yaw, pitch, roll): intuitive, but the order matters, and at certain angles two of them describe the same motion (gimbal lock).
  • Axis–angle (a rotation of about a unit axis , stored as the 3-vector ): what OpenCV calls rvec, converted to a matrix by Rodrigues’ formula.
  • Unit quaternions (4 numbers with norm 1): no singularities, easy to interpolate, common in robotics and graphics.

5. The Pinhole Camera

Light from a scene point passes through a tiny hole and hits the sensor behind it. The image on the sensor is upside down; mathematically it is easier to put a virtual image plane in front of the pinhole, at the same distance :

optical axissensor (image upside down)virtual image plane at distance fpinhole C3D point (X, Y, Z)y = f · Y / ZYyfZ (depth)similar triangles: y / f = Y / Zso twice as far looks half as big,and depth is lost: every point on thisray lands on the same pixel.

By similar triangles, a point at in the camera’s coordinate frame (with along the optical axis) appears at

The division by is perspective: an object twice as far away appears half as large. It is also where depth is lost: every point on the same ray through the pinhole lands on the same pixel. In homogeneous coordinates the division becomes a matrix product followed by the usual division by the last entry:

The projection matrix is 3 × 4: it maps 3D (4 homogeneous coordinates) to 2D (3 homogeneous coordinates). Computer graphics pipelines use a 4 × 4 version that keeps a depth value for the z-buffer, but for forming an image, one row is dropped.

Orthographic projection drops the depth instead of dividing by it: , with matrix rows , , . It is a good approximation (as scaled orthographic or weak perspective, ) when an object’s depth range is small compared with its distance, for example a telephoto shot.


6. Intrinsics: From the Image Plane to Pixels

The pinhole equation gives positions in the image plane, measured from the optical axis. Pixel coordinates start at the top-left corner, and pixels have a size. The intrinsic matrix converts one into the other:

  • : the focal length in pixels: the physical focal length divided by the pixel width and height. They differ only for non-square pixels.
  • : the principal point, where the optical axis meets the sensor, usually close to the image centre but not exactly at it.
  • : skew, non-zero only if the pixel grid is not rectangular; zero for practically every modern camera.

Focal length from the field of view

If only the field of view (FOV) is known, the focal length in pixels follows from the triangle formed by half the image and half the angle:

The catch is the word extent. A field of view of 100° on a 640 × 480 image gives px if the 100° is measured across the diagonal (800 px), px across the width, and px across the height. Specifications are given in all three ways:


7. Extrinsics and the Full Camera Matrix

The pinhole equation needs points in the camera’s coordinate frame. Points in a scene are usually given in a world frame, so first they are moved into the camera frame with a rigid transformation, the extrinsics . Then the whole chain is one 3 × 4 matrix:

world pointX = (X, Y, Z, 1)metres, world framecamera frameR X + tmetres; z = depthnormalised image(x/z, y/z)divide by depthpixelsK · (x/z, y/z, 1)focal length, centre[R | t]extrinsics, 6 DOF÷ ZprojectionKintrinsics, 5 DOFAll in one 3 × 4 matrix: x̃ = K [R | t] X̃ = P X̃, then (u, v) = (x̃₁/x̃₃, x̃₂/x̃₃)P has 12 entries but only 11 degrees of freedom (overall scale does not matter). OpenCV convention: camera x right, y down, z forward.

Two details trip up almost everyone:

  • is not the camera’s position. maps world points into the camera frame, . The camera centre is the world point that lands at the origin, so , which gives , and .
  • Axis conventions differ. OpenCV uses right, down, forward (into the scene). A world frame often has up. The rows of are the camera’s right, down and forward directions written in world coordinates; for a camera looking straight along the world axis they are , and . OpenGL and many graphics tools use up and backwards instead, a common source of upside-down or mirrored results.

has 12 entries but 11 degrees of freedom (scale does not matter): 5 intrinsic, 3 for the rotation and 3 for the translation. Move the camera and change its intrinsics, and follow one corner of the blue box through every step of the chain:


8. From One Camera to Another

Given a pixel in one image, where does the same scene point appear in a second image? Take the second camera’s frame relative to the first: . A pixel only fixes the ray ; the point on it depends on its depth . Projecting into camera 1:

So in general you need each pixel’s depth (or its inverse depth , which is how many depth-estimation and SLAM methods store it). The second term is the parallax: it shifts a point by about pixels for a sideways baseline , a lot for near points and little for far ones. That is how two eyes, or two cameras, perceive depth.

There are two cases where the depth drops out and one 3 × 3 homography maps one image onto the other:

  • Pure rotation (, the same camera centre): for every point at every depth. This is why a panorama shot by turning a camera on a tripod can be stitched perfectly, and why hand-held panoramas show ghosting on nearby objects. The condition is the same centre, ; equal translation vectors are not the same thing, since also depends on .
  • A planar scene: for points on the plane , the factor equals 1, so and . This is the homography behind document scanning and the checkerboard used for calibration.

Turn the second camera, then move it sideways:

With rotation only, the predicted rings sit exactly on the true points (the error is about px, rounding). Once the camera moves, no single homography fits both the near box and the far wall: with a 0.8 m baseline, the plane homography is exact on the wall and about 80 px off on the box. Recovering the depth that is missing here is the subject of 3D reconstruction.


9. Real Lenses: Distortion

A pinhole lets through too little light, so real cameras use lenses, and lenses bend straight lines, most visibly near the image border. The standard model (Brown, 1966; used by OpenCV) acts on the normalised coordinates , before . With :

The terms are radial distortion: negative gives barrel distortion (wide-angle lenses), positive gives pincushion (telephoto lenses), and a mix gives a wavy “mustache” shape. The terms are tangential distortion, from a lens that is not exactly parallel to the sensor.

Distortion is not a matrix: no 3 × 3 transformation can bend a straight line. So in practice the coefficients are estimated together with by calibration: photographing a checkerboard of known size from several angles and fitting all parameters so that the projected corners match the detected ones (Zhang, 2000). Images are then undistorted before the pinhole equations are applied. Fisheye lenses (field of view near or beyond 180°) need a different model altogether, since goes to infinity at 90° (Kannala & Brandt, 2006).


10. Checking the Lab Against OpenCV

Every formula in the lab is reimplemented in plain JavaScript. To make sure it matches the standard tools, a Python script computes the same quantities with OpenCV 5.0 and numpy, and the lab’s functions are compared with them in a browser:

WhatReferenceLargest difference
homography from 4 cornerscv2.getPerspectiveTransform7 × 10⁻¹²
affine and similarity least-squares fitsnumpy.linalg.lstsq1 × 10⁻¹²
Euclidean fitProcrustes (SVD) in numpy1 × 10⁻⁶ (float32 input)
projection of 3D points with K, R, tcv2.projectPoints1 × 10⁻¹³ px
the same with lens distortion (k₁, k₂, p₁, p₂)cv2.projectPoints with distCoeffs9 × 10⁻¹⁴ px
rotation-only homography at zero baselineexact projection1 × 10⁻¹³ px
plane homography on the far wallexact projection1 × 10⁻¹³ px

11. Common Claims, Checked

ClaimVerdict
”Homogeneous coordinates just add a 1.”The 1 is only the default. The point is the scale equivalence : it turns translation and perspective division into matrix products, and it gives points at infinity () a normal representation.
”Under affine transformations, parallel lines remain parallel.”True. The last row of an affine matrix is , so points at infinity stay at infinity. Under a homography they need not, and parallel lines can meet at a vanishing point.
”All 2D transformations are done by multiplying the image with a matrix.”The matrix multiplies coordinates. The image itself is produced by inverse warping and interpolation (§3), and lens distortion is not a matrix at all.
”In an affine matrix, and are the scale and , the shear.”Only if there is no rotation. In general rotation, scaling and shear mix in all four entries; to separate them, decompose (for example by the SVD).
”The perspective projection is a 4 × 4 matrix.”For image formation it is 3 × 4: (times ). A 4 × 4 version, with a zero or depth row, only appears when a depth value has to be kept, as in graphics pipelines.
”To get pixel coordinates, it is enough to transform world points into the camera frame.”No, that gives metres in the camera frame. Dividing by the depth and applying are still needed.
”The camera matrix is .” alone gives normalised coordinates (a camera with and the principal point at 0). The pixel projection is .
” is the position of the camera.”The camera centre is . is the world origin expressed in camera coordinates.
”A 100° camera at 640 × 480 has px.”Only if the 100° is the diagonal field of view. Horizontal gives 268.5 px, vertical 201.4 px.
”Two cameras with the same are related by a homography .”The condition is the same camera centre. With different rotations, equal vectors mean different centres.
”4 point pairs are enough to estimate a homography.”4 pairs in general position (no 3 collinear) determine it exactly. Real matches are noisy and some are wrong, so in practice many pairs, normalisation and RANSAC are used.

Summary

IdeaWhy
Homogeneous coordinatesTranslation and projection become matrix products; points at infinity are ordinary vectors
2D hierarchy: 2, 3, 4, 6, 8 degrees of freedomTells you what is preserved and how many correspondences are needed (1, 2, 2, 3, 4)
DLT, with normalisationEstimates a homography from point pairs by linear algebra
Inverse warping + interpolationHow images (and spatial transformer networks) are actually warped
Pinhole: Perspective, and the loss of depth
, 11 degrees of freedomThe whole camera in one 3 × 4 matrix;
Rotation-only or planar scene → homographyPanoramas, document scans; otherwise parallax needs depth
Lens distortion + calibrationReal lenses bend lines; estimate and distortion, then undistort

Next: Finding the Same Point Twice: SIFT, the Ratio Test and RANSAC: detecting and describing keypoints with SIFT, matching them between images, and estimating a homography robustly with RANSAC, even when many matches are wrong.


References

Books

  • Hartley, R., & Zisserman, A. (2004). Multiple View Geometry in Computer Vision (2nd ed.). Cambridge University Press.
  • Szeliski, R. (2022). Computer Vision: Algorithms and Applications (2nd ed.). Springer. Free at szeliski.org/Book.

Papers

  • Hartley, R. I. (1997). In defense of the eight-point algorithm. IEEE TPAMI, 19(6).
  • Brown, D. C. (1966). Decentering distortion of lenses. Photogrammetric Engineering, 32(3).
  • Zhang, Z. (2000). A flexible new technique for camera calibration. IEEE TPAMI, 22(11).
  • Kannala, J., & Brandt, S. S. (2006). A generic camera model and calibration method for conventional, wide-angle, and fish-eye lenses. IEEE TPAMI, 28(8).
  • Jaderberg, M., Simonyan, K., Zisserman, A., & Kavukcuoglu, K. (2015). Spatial transformer networks. NeurIPS.
  • Mildenhall, B., Srinivasan, P. P., Tancik, M., Barron, J. T., Ramamoorthi, R., & Ng, R. (2020). NeRF: Representing scenes as neural radiance fields for view synthesis. ECCV.
  • Kerbl, B., Kopanas, G., Leimkühler, T., & Drettakis, G. (2023). 3D Gaussian splatting for real-time radiance field rendering. ACM TOG (SIGGRAPH).

Tools

  • OpenCV camera calibration and 3D reconstruction module (calib3d), whose conventions this post follows.