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
live Β· client-side
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.
Position Cm
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 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
| pt | X | Y | Z | u | v | depth |
|---|
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
Kmove β it translates the image, nothing else. - Drag perspective β ortho to 1. The cube's far face stops being smaller than its near face.
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.
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.
Lens
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.
- 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.
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.
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.
Camera pose
Point P
Drag the amber point in the panel to move it. Both readouts below describe that same location, in two different frames.
Compare
These are the two numbers people confuse. t is where the world origin lands in camera coordinates; C is where the camera actually is.
- Set Cx = 2.5, Cz = 3.5, yaw = 0. Note that
treads roughly (β2.5, 0, 3.5) whileCreads (2.5, 0, 3.5) β same numbers, different signs and different meaning. - Now spin yaw.
Cdoes not move at all;tswings wildly. The camera stayed put β only the frame it reports coordinates in rotated.
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.
Radial
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
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.
- 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.
Break the calibration, then watch it heal
liveThe 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.
Intrinsic error
Extrinsic error
Pose errors. These shift the whole image rather than reshaping it, which is exactly why they can hide behind an intrinsic error.
- 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.
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.
Rig
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.
- 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
Happlied 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.
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.
Stereo rig
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.
- 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 Fstays pinned at zero throughout β rank 2 is structural, not incidental.
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.
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.
Geometry
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
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
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.
- 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.
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.