Lens Distortion: Radial and Tangential
Real lenses bend straight lines, so every photo carries a small warp. Lens distortion names that warp with a few coefficients, and undistortion removes it before you measure anything.
Why Does This Exist?
The pinhole model pretends light travels in perfectly straight lines onto a flat sensor. Real glass is curved, so hallway edges bow outward in a wide-angle shot and inward at full zoom. Any measurement taken on the raw pixels inherits that bend: a straight wall reads as curved, and a projection matrix fitted to bent pixels absorbs the error into the wrong focal length.
The fix is a small parametric warp applied before the pinhole math. The pinhole half is intrinsic parameters; the procedure that estimates both halves is camera calibration. This page names the warp, works one corner through it, and says when to apply it.
Think of It Like This
A peephole in a hotel door
Look through a door peephole and the corridor stays recognizable but wrong: the center reads fine while the edges balloon outward. One number captures most of it, how strongly the rim stretches relative to the center.
That number is . The analogy stops at sensor tilt: a peephole is symmetric by construction, while a real lens can sit slightly crooked on its sensor, adding the tangential terms below.
How It Actually Works
Work in normalized coordinates , meaning pixel offsets from the principal point divided by focal length. Let be the squared radius. With radial coefficients and tangential coefficients , the distorted point is:
and symmetrically for . Negative pulls points toward the center (barrel distortion, typical of wide lenses); positive pushes them outward (pincushion, typical at telephoto). Undistortion inverts this map, usually by iteration, and only then does the pinhole projection apply. Stereo vision depends on this order: rectification assumes already-straight rays.
Worked numbers
Take the normalized corner with , , and no tangential terms. Then and :
so . The corner renders at percent of its ideal radius: textbook barrel, strongest exactly where wide photos look most bent.
Code
def distort(xn: float, yn: float, k1: float, k2: float) -> tuple[float, float]: r2 = xn * xn + yn * yn # squared radius from the optical center f = 1.0 + k1 * r2 + k2 * r2 * r2 return (round(f * xn, 3), round(f * yn, 3))
print(distort(0.5, 0.5, -0.3, 0.08)) # -> (0.435, 0.435)Watch Out For
Calibrating distortion with center-only boards
is nearly unobservable if the checkerboard never visits the frame rim, because the warp is tiny near the center. The fit then reports success while straight edges still bow at the corners. The fix is coverage, not more shots: tilt the board and push it into every corner of the frame so the optimizer actually sees the bend.
Measuring before undistorting
The pipeline order is undistort, then project. Measuring raw pixels against the pinhole model folds the lens bend into the focal length and principal point, and the resulting quietly fits one depth while failing the next. Warp the pixels straight first; only straight rays obey .
The Quick Version
- Radial distortion scales each point by around the optical center.
- Negative gives barrel distortion; positive gives pincushion.
- Tangential terms cover a lens sitting crooked on its sensor.
- Undistortion runs before projection, never after measurement.
- Distortion coefficients are only observable where the board visits, so cover the frame corners.