Run alongside:
06_calibrate_synthetic— it calibrates a camera whose trueKandDwe know, so every error in this chapter is measured, not asserted.
You show the camera a rigid pattern whose geometry you know — usually a
checkerboard with squares of a measured size. For each view, the unknowns are
the board's pose K
and D. The optimiser minimises the total reprojection error: the distance
in pixels between each detected corner and where the current model says it
should be.
cv2.calibrateCamera returns the RMS of that residual.
This is the most important thing in this repository, so here is the measurement rather than the claim. Twenty views, 0.25 px of corner noise, identical in every respect except how the board was held:
| experiment | RMS | error in fx |
|---|---|---|
| board held flat, all at the same distance | 0.341 px | +171.7 px (17% wrong) |
| board tilted 35°, distance varied | 0.340 px | +1.3 px |
Same reprojection error. One result is excellent and one is useless.
The reason is a degeneracy. If every board is fronto-parallel at roughly the same depth, then "a slightly bigger board slightly further away" and "a slightly smaller board slightly closer" produce nearly identical images. Focal length and board distance trade off against each other almost perfectly, and the optimiser is free to pick the wrong combination and still fit every corner to a fraction of a pixel.
Notice which parameters survived: cx and cy came out fine. The degeneracy is
specifically between focal length and depth. Tilting the board is what breaks
it — a tilted board's perspective foreshortening depends on focal length in a
way that its size does not.
Reprojection error measures how well the model fits the data you supplied. It cannot tell you the data was uninformative.
- Tilt the board 30–45° in several directions. Not just left-right: tilt it about both axes, and rotate it in-plane too. This is the single highest-value habit.
- Vary the distance so the board fills roughly ⅓ to ¾ of the frame across the set.
- Push the board into all four corners of the image.
Dis measured at large radius; if no corner data exists,k1andk2are guesses. - 15–25 good views beat 60 sloppy ones. More bad views do not fix a degeneracy, they just make the optimiser more confident about the wrong answer.
- Fix
k3withcv2.CALIB_FIX_K3unless the lens is genuinely very wide (see below). - Measure the square size properly. A 1% error in the square size is a 1% error in every distance you ever compute. Print on rigid board, not paper on a clipboard; measure across many squares and divide.
- Keep the board flat. A warped print puts a systematic error into
D, which is usually where inflatedp1/p2values come from.
Before you trust a calibration, check that:
-
cx,cyare within a few percent of the image centre. A principal point 20% off-centre means the optimiser wandered. -
fx/fyis within about 1% of 1.0 (unless you knowingly resized non-uniformly). -
$F = f_x \cdot p_x$ matches the lens on the camera. -
p1,p2are of order 1e-4 to 1e-3, not 1e-2. - Recalibrating with a different set of images gives you the same numbers. This is the check that catches degeneracy, and almost nobody does it.
Should you let k3 float? One calibration cannot tell you — the answer is in
the spread across repeats. Twelve independent calibrations, 15 views each,
0.4 px noise, on a lens whose true k3 is exactly 0:
| RMS | fx std |
k1 std |
recovered k3 |
|
|---|---|---|---|---|
free k1, k2, k3 |
0.5433 | 7.81 | 0.0197 | mean +0.33, std 1.47 |
k3 fixed to 0 |
0.5435 | 7.94 | 0.0122 | — |
The reprojection error is identical to four decimals. But k3 scatters across a
range hundreds of times wider than the coefficient it is estimating — it is
essentially unconstrained by this data — and that noise does not stay contained.
It leaks into k1, whose spread grows by about 60%.
So the argument for CALIB_FIX_K3 is not "lower error". It is that an
unconstrained parameter buys you nothing and destabilises the ones you actually
use. Add k3 only when the lens is wide enough to need it, and then check that
it comes out repeatable across recalibrations.
The same reasoning applies to CALIB_ZERO_TANGENT_DIST on a lens you have
reason to believe is well centred, and to CALIB_FIX_ASPECT_RATIO when you know
the pixels are square.
Checkerboards are the default because corner detection is sub-pixel accurate and robust. Two alternatives worth knowing:
- ChArUco (
cv2.aruco) — a checkerboard with ArUco markers in the white squares, so the board does not need to be fully visible. Much easier to get corner coverage with, which directly addresses the checklist above. - Circle grids (
cv2.findCirclesGrid) — centroids are accurate but shift slightly under perspective, since the centroid of a projected circle is not the projection of its centre. Asymmetric grids resolve orientation ambiguity.
Past roughly 120° of field of view the plumb-bob model starts to struggle: the
radial polynomial needs increasingly large coefficients and eventually folds
(see chapter 3). At that point switch to cv2.fisheye,
which models
- Quiz:
uv run python/quiz/run_quiz.py --topic calibration
Next: Cheat sheet