Camera calibration and undistortion

OpenCV · Advanced · 15 min

Any measurement in real-world units, 3-D pose estimation or AR overlay needs a calibrated camera.

A camera is described by its intrinsic matrix K (focal lengths fx, fy in pixels and principal point cx, cy) and distortion coefficients (radial k1, k2, k3 and tangential p1, p2) that bend straight lines, especially with wide-angle lenses.

Calibration recipe:

  • Print a chessboard and photograph it 10 to 20 times from different angles and distances, covering the whole frame.
  • For each image, cv2.findChessboardCorners(gray, (cols, rows)) finds the inner corners; refine them with cv2.cornerSubPix. (cols, rows) counts inner corners, not squares.
  • Build matching 3-D object points: a grid with z = 0, scaled by the real square size.
  • ret, K, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, image_size, None, None).

ret is the RMS reprojection error in pixels; under about 0.5 is good. Larger values point to blurry shots, wrong pattern size or too little variety.

Then cv2.undistort(img, K, dist) straightens images; for video, precompute maps once with cv2.initUndistortRectifyMap and apply cv2.remap per frame. cv2.solvePnP(object_pts, image_pts, K, dist) recovers the pose of a known object, and cv2.projectPoints draws 3-D points onto the image.

The intrinsics are only valid for the resolution and zoom/focus you calibrated with. The ChArUco boards in the aruco module are more robust when the board is partly visible.

Example

import cv2
import numpy as np

pattern, square = (7, 5), 30.0                 # inner corners, mm per square
obj = np.zeros((7 * 5, 3), np.float32)
obj[:, :2] = np.mgrid[0:7, 0:5].T.reshape(-1, 2) * square

K_true = np.array([[800, 0, 320], [0, 800, 240], [0, 0, 1]], np.float64)
objpoints, imgpoints = [], []
for rx, ry in [(0.3, 0.1), (-0.2, 0.3), (0.1, -0.3), (0.4, 0.4), (-0.3, -0.2)]:
    rvec = np.array([rx, ry, 0.05]); tvec = np.array([-90.0, -60.0, 600.0])
    pts, _ = cv2.projectPoints(obj, rvec, tvec, K_true, None)   # simulated corners
    objpoints.append(obj); imgpoints.append(pts.astype(np.float32))

rms, K, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, (640, 480), None, None)
print(rms < 0.01)                              # True: perfect synthetic data
print(K.round())                               # close to fx=fy=800, cx=320, cy=240
print(dist.shape)                              # (1, 5) k1 k2 p1 p2 k3

Remember

  • Intrinsics K and distortion coefficients describe the camera
  • Calibrate from many varied chessboard views; the pattern size counts inner corners
  • The RMS reprojection error should be well under a pixel
  • Undistort with undistort, or precomputed maps plus remap for video

Watch out for

  • Calibrating at one resolution and using the matrix on frames of another resolution or zoom.

Check yourself

  1. calibrateCamera returns an RMS error of 4.2 pixels. What does that suggest?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html

Builds on

Unlocks

Nothing builds on this yet.