← Notes

Notes

Interactive

Camera Geometry Playground

Nearly everything in geometric computer vision is one equation wearing different clothes. A point in the world, a camera somewhere looking at it, and a pixel where the two meet. The equation is short enough to write on a napkin, and it takes about a semester to actually believe.

This page is eight instruments built on that one equation. Every panel is live: move a slider and the matrices, the 3D frustum, and the image plane all update from the same code path, because they are all the same computation viewed from different angles. Nothing is precomputed and nothing is faked.

Optical bench

One equation, eight ways to break it

08 bays
live Β· client-side
x̃ the pixel, in homogeneous coordinates — known only up to scale
K intrinsics: focal length, principal point, pixel shape
[R | t] extrinsics: where the camera is and which way it faces
X̃ the world point, also homogeneous
∼ equality up to scale β€” the perspective divide hides here
[ 01 ]

The projection chain, end to end

live

Left: the world, with the modelled camera drawn as a frustum. Right: what that camera records. Every number below is computed from the sliders β€” K from the lens and sensor, [R | t] from the pose, and P = K[R | t] from both. Drag inside the 3D view to orbit; that moves your viewpoint, not the camera being modelled.

world Β· orbit to look around
image plane Β· 640 Γ— 480

Position Cm

Cx
Cy
Cz

Where the camera sits in world coordinates. Moving it rewrites t and therefore P, but never touches K β€” a lens does not know where it is.

Orientationdeg

yaw about Y
pitch
roll optical axis

Yaw turns about world Y, pitch about the camera's own right axis, roll about the optical axis. Rolling changes what is level without changing what is visible.

Lens & sensor

f focal length
sensor width
cx principal pt
cy
perspective β†’ ortho
fxβ€”
horiz. fovβ€”
centre Cβ€”
K =
[R | t] =
P = K[R | t] =
ptXYZuvdepth

hover a table row to find that point in the imagered rows are behind the camera

  • Push Cz until points go red: they are behind the image plane, and the projection returns a pixel that is geometrically meaningless.
  • Slide principal point and watch only the last column of K move β€” it translates the image, nothing else.
  • Drag perspective β†’ ortho to 1. The cube's far face stops being smaller than its near face.
Takeaway P has 11 degrees of freedom, not 12 β€” it is only defined up to scale, which is exactly what the ∼ in the governing relation is telling you. Multiply every entry of P by two and every pixel stays where it was.
[ 02 ]

Why a longer lens sees less

live

Focal length is a distance, not an angle β€” but it induces an angle, because the sensor has a fixed width. The half-angle of the wedge on the left is atan(W / 2fβ‚“). Doubling f halves the tangent of that angle, so the wedge closes and fewer posts survive into the frame.

top-down Β· view wedge
what the camera sees

Lens

f on a 36 mm sensor

Compensation

With this on, the camera walks backwards as you zoom in, keeping the amber ring the same size. The subject is pinned β€” but the background compresses dramatically. That is the Vertigo shot, and it is pure geometry.

focal lengthβ€”
fxβ€”
horiz. fovβ€”
vert. fovβ€”
distanceβ€”
view wedge subject at z = 0 post in frame post outside frame
  • Click through 14 β†’ 135. The wedge on the left closes steadily while the posts crowd toward the centre of the frame β€” the same scene, sampled through a narrower cone.
  • At 14 mm the nearest posts are enormous and the far ones tiny. At 135 mm they are almost the same size. Nothing moved: a narrow cone spans a smaller range of distances, so depth stops registering.
  • Turn on hold subject size and sweep the lens. The amber ring stays put while the background swells and collapses behind it β€” the camera is walking backwards as it zooms.
Takeaway "Zoom" and "move closer" are not the same operation. Zooming changes f and leaves [R | t] alone; walking closer changes [R | t] and leaves f alone. They produce the same subject size and completely different backgrounds β€” which is why perspective is a property of position, never of the lens.
[ 03 ]

The two matrices everybody mixes up

live

[R | t] is world-to-camera. Its translation column t is not where the camera is β€” it is where the world origin lands in camera coordinates. The camera centre is C = βˆ’Rα΅€t, and the pair [Rα΅€ | C] is the camera-to-world transform. Watch the two panels disagree as you move the camera. Drag the amber point.

top-down Β· Y = 0 slice

Camera pose

Cx
Cz
yaw

Point P

P in worldβ€”
P in cameraβ€”
depth (Zcam)β€”

Drag the amber point in the panel to move it. Both readouts below describe that same location, in two different frames.

Compare

t (w2c column)β€”
C = βˆ’Rα΅€tβ€”

These are the two numbers people confuse. t is where the world origin lands in camera coordinates; C is where the camera actually is.

world β†’ camera  [R | t] =
camera β†’ world  [Rα΅€ | C] =
  • Set Cx = 2.5, Cz = 3.5, yaw = 0. Note that t reads roughly (βˆ’2.5, 0, 3.5) while C reads (2.5, 0, 3.5) β€” same numbers, different signs and different meaning.
  • Now spin yaw. C does not move at all; t swings wildly. The camera stayed put β€” only the frame it reports coordinates in rotated.
Takeaway If your reconstruction is mirrored, inside-out, or orbiting the wrong point, you have almost certainly fed a camera-to-world matrix into something expecting world-to-camera. The two agree only when the camera sits at the origin with zero rotation, which is exactly the case people test with.
[ 04 ]

The part K cannot express

live

A projective camera maps straight lines to straight lines. Real glass does not. Distortion is applied to the normalised coordinates, after [R | t] and before K β€” which is why it is radially symmetric about the principal point rather than about the image centre. Move cx in bay 01 and you would see the whole distortion pattern travel with it.

image plane

Radial

k₁
kβ‚‚
k₃

k₁ dominates. kβ‚‚ and k₃ only matter near the corners of a very wide lens, which is why most calibrations fit k₁ and kβ‚‚ and leave k₃ at zero.

Tangential

p₁
pβ‚‚

Non-zero only when the lens is not parallel to the sensor. Small in anything well assembled, and often pinned to zero outright.

Presets

Real lens signatures. Barrel is what wide-angle and phone lenses do; pincushion is a telephoto trait.

Overlays

Turn off the warped grid to see the true projection alone, or the cube to judge the plane by itself.

max displacementβ€”
distortion vector [k₁ kβ‚‚ k₃ p₁ pβ‚‚]
  • Set k₁ negative for barrel (GoPro, wide phone lenses); positive for pincushion (older telephotos).
  • Turn on the displacement field and note the arrows grow with rΒ² β€” the corners move far more than the centre. Distortion is a corner problem.
  • Try Tilted lens: the pattern loses its symmetry entirely. That is what p₁, pβ‚‚ buy you, and why they are small in a well-built lens.
Takeaway Distortion is not invertible in closed form β€” undistorting is a fixed-point iteration, not a formula. That is why calibration toolboxes hand you a remap table rather than an inverse matrix, and why the honest place to compare a model against measurements is always in distorted pixel space.
[ 05 ]

Break the calibration, then watch it heal

live

The amber circles are measurements β€” pixels a real camera recorded, fixed forever. The cyan dots are where your current parameters say those points should have landed. The red segments between them are the residuals, and their RMS is the single number every calibration routine exists to minimise. Perturb anything, then press Re-calibrate and watch Gauss–Newton crawl back.

image plane Β· residuals

Intrinsic error

Ξ” focal length
Ξ” cx
Ξ” cy

Extrinsic error

Ξ” yaw
Ξ” pitch
Ξ” roll
Ξ” range

Pose errors. These shift the whole image rather than reshaping it, which is exactly why they can hide behind an intrinsic error.

rms reprojectionβ€”px
worst pointβ€”px
observationsβ€”
stateβ€”
observed pixel predicted by current model residual
  • Nudge Ξ”cx to +30. RMS reads almost exactly 30 px, and every residual points the same way β€” a pure translation of the image.
  • Now try Ξ” focal length. The residuals fan outward from the principal point, growing with radius. Different error, different signature.
  • Trade Ξ” range against Ξ” focal length β€” you can cancel most of one with the other. That is the focal/distance ambiguity, and it is why calibrating from a single flat target is fragile.
  • Press Scramble, then Re-calibrate. Nine parameters, ~78 observations, and the error falls back to numerical zero in a few dozen steps.
Takeaway Reprojection error is measured in pixels, in the image, where the measurements actually live β€” never in world units. A sub-pixel RMS on a bad configuration can still hide a badly wrong camera, because several parameters trade off against each other. Low error is necessary, not sufficient.
[ 06 ]

A plane collapses depth into one 3Γ—3

live

For points on a plane, depth is redundant β€” it is determined by the other two coordinates. So the map between two images of that plane needs no 3D at all: a single H = K'(R + t nα΅€/d)K⁻¹ takes every pixel of the plane in view A to its pixel in view B. The green dashed grid in view B is view A's pixels pushed through H. It lands exactly on the real one.

view A
view B

Rig

camera separation
plane tilt

Both cameras orbit the target at a fixed radius. Separation is the angle between them measured at the target, not the distance between them.

Overlays

Turn off the warped grid to see the true projection alone, or the cube to judge the plane by itself.

Hup to scale

Nine numbers, defined only up to scale β€” so eight degrees of freedom, which is why four point correspondences are enough to solve for one.

off-plane missβ€”
baselineβ€”
true projection in B H Β· (view A pixels) where H fails, off the plane
  • The green and amber grids stay locked together at every separation and every tilt. That is the homography being exact, not approximate.
  • The red cube is the same H applied to points that are not on the plane. It smears badly, and the miss grows with baseline β€” that residual is precisely the parallax a structure-from-motion pipeline feeds on.
  • Drop separation toward 5Β°. The red cube converges onto the real one: with almost no baseline, everything looks planar. This is why panorama stitching works handheld and fails when you walk.
Takeaway Two images are related by a homography in exactly two situations: the scene is planar, or the camera only rotated. Both kill parallax. If you can stitch two photos with a homography and get no ghosting, you have learned something real about the scene or the motion β€” not just about your stitcher.
[ 07 ]

One pixel in A is a line in B

live

Move your cursor over either image. A single pixel fixes a ray in space but not a point on it β€” so its match in the other view can be anywhere along that ray's image, which is a straight line: l' = F x. Every such line passes through the epipole (green cross), the image of the other camera's centre. This is what turns matching from a 2D search into a 1D one.

view A hover me
view B hover me

Stereo rig

separation
elevation
f of camera B

Uncheck to make the two cameras parallel. The epipoles shoot off to infinity and every epipolar line becomes horizontal β€” that is a rectified pair, and it is the whole reason stereo matchers rectify first.

Fup to scale

Also defined up to scale, and constrained to rank 2, leaving seven degrees of freedom.

baselineβ€”
det Fβ€”
epipolar line epipole det F = 0 always β€” F has rank 2
  • Hover a cube corner in A and find the same corner in B. It sits on the red line, always. That is the epipolar constraint doing its job.
  • Sweep your cursor around A and watch every line in B pivot about one point. That pencil of lines is the epipole. At moderate separations it sits outside the frame β€” push separation past ~130Β° and it walks into view as a green cross.
  • Uncheck converge. All lines go horizontal and the epipole leaves the frame. Notice det F stays pinned at zero throughout β€” rank 2 is structural, not incidental.
Takeaway F knows nothing about where things are β€” only about how the two cameras are arranged. It is 7 degrees of freedom (9 entries, minus scale, minus det F = 0) and can be estimated from point matches alone, with no calibration whatsoever. That is why it is the first thing a structure-from-motion pipeline computes.
[ 08 ]

Why depth is always the noisy direction

live

Two rays that nearly agree on direction intersect at a badly determined point. Each red dot is one triangulation from observations jittered by Οƒ pixels of noise; the ellipse is the 2Οƒ contour of the resulting cloud. It is almost never a circle β€” it is a sliver, and the sliver points along the line of sight.

top-down Β· Monte Carlo over pixel noise

Geometry

baseline b
depth Z

Baseline is the distance between the two camera centres; depth is how far the point sits in front of them. Their ratio is what governs precision.

Measurement

pixel noise Οƒ

How much noise sits on each observed pixel. A good feature detector is repeatable to a few tenths of a pixel, so Οƒ below 1 is realistic.

First-order estimate

2ΟƒZ β‰ˆ 2√2Β·ZΒ²Οƒ / (fxΒ·b)β€”

The textbook form is Οƒ_Z β‰ˆ ZΒ²Οƒ_d/(fΒ·b) with Οƒ_d the noise on the disparity. Both views are measured independently here, so Οƒ_d = Οƒβˆš2 β€” and the panel draws a 2Οƒ contour, hence the extra factor of two.

spread along rayβ€”
spread across rayβ€”
anisotropyβ€”
baselineβ€”
  • Collapse baseline toward 0.15 m. The ellipse stretches enormously β€” the two rays become nearly parallel and their intersection stops being localised at all.
  • Double depth. The spread grows roughly four-fold, not two-fold: depth error goes as ZΒ². This is why a stereo rig that is excellent at 3 m is useless at 30 m.
  • Compare the measured spread to the first-order estimate in the rail. They track closely until the baseline gets small, where the linearisation stops being honest.
Takeaway Depth precision scales as ZΒ²Οƒ / (fΒ·b) β€” quadratic in range, and inversely proportional to baseline and focal length. You cannot fix a short baseline with a better feature detector: halving Οƒ buys a factor of two, while doubling b buys the same factor and costs nothing but geometry. Arrange the cameras well before you tune the matcher.

β€” end of bench β€”

What is actually running here

There is no server, no build step and no precomputed data. camera-geom.js holds the geometry β€” projection, distortion, homographies, the fundamental matrix, DLT triangulation, and a small Jacobi eigensolver so the null-space and covariance work needs no linear algebra dependency. Every panel above calls into that one module, which is why the matrices and the pictures can never disagree: they are literally the same numbers.

The conventions are fixed throughout, and stated here because half of all camera-geometry bugs are convention bugs: the world is right-handed and Y-up; the camera frame is x-right, y-down, z-forward; [R | t] is world-to-camera; and a camera with zero yaw, pitch and roll looks along world βˆ’Z. Distortion is applied to normalised coordinates, between [R | t] and K.

For the underlying theory, Foundations of Computer Vision (Torralba, Isola and Freeman) is the reference I would point at first β€” chapter 5 builds the pinhole model from similar triangles, which is where all of this starts.