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
Orientationdeg
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.
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
Compare
- 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
Tangential
Presets
Overlays
- 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
- 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
Overlays
Hup to scale
- 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
- 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
Measurement
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.