A camera maps three-dimensional geometry onto a flat sensor through a lens, and that projection can bend straight lines or shift measured positions. OpenCV estimates the intrinsic camera matrix and lens-distortion coefficients from repeated views of a known flat pattern.
Chessboard dimensions are passed to cv.findChessboardCorners() as inner corners in columns, rows order. A board printed with 8 columns by 7 rows of squares therefore uses a 7×6 pattern, while the measured square width sets the real-world unit used by later pose calculations.
Use at least ten sharp, same-resolution frames from the exact camera, lens, focus, and zoom configuration that will use the calibration. Move and tilt the board across the frame between captures; repeated near-identical views can produce a weak estimate even when the RMS reprojection error is low.
Related: How to install OpenCV on Ubuntu
Steps to calibrate a camera with OpenCV:
- Place at least ten same-resolution chessboard photos in calibration.
$ ls calibration left01.jpg left03.jpg left05.jpg left07.jpg left09.jpg left12.jpg left14.jpg left02.jpg left04.jpg left06.jpg left08.jpg left11.jpg left13.jpg
A usable frame shows the whole chessboard with a light border around it. Varied positions, distances, and angles provide stronger calibration constraints.
- Create the calibration inputs and 7×6 chessboard model with a 25.0-unit square size in calibrate_camera.py.
- calibrate_camera.py
from pathlib import Path import cv2 as cv import numpy as np IMAGE_DIR = Path("calibration") OUTPUT_PATH = Path("calibration.npz") PATTERN_SIZE = (7, 6) SQUARE_SIZE = 25.0 MIN_FRAMES = 10 image_paths = sorted( path for path in IMAGE_DIR.iterdir() if path.suffix.lower() in {".jpg", ".jpeg", ".png", ".tif", ".tiff"} ) if not image_paths: raise SystemExit(f"no calibration images found in {IMAGE_DIR}") columns, rows = PATTERN_SIZE object_template = np.zeros((rows * columns, 3), np.float32) object_template[:, :2] = np.mgrid[0:columns, 0:rows].T.reshape(-1, 2) object_template *= SQUARE_SIZE
PATTERN_SIZE counts inner corners across and down. SQUARE_SIZE carries the measured square width in millimeters, inches, or the unit required by later pose calculations.
- Append the chessboard-corner collection section to calibrate_camera.py.
criteria = ( cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001, ) object_points = [] image_points = [] image_size = None for path in image_paths: image = cv.imread(str(path)) if image is None: print(f"skipped unreadable image: {path.name}") continue gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY) current_size = gray.shape[::-1] if image_size is None: image_size = current_size elif current_size != image_size: raise SystemExit( f"image size changed at {path.name}: {current_size} != {image_size}" ) found, corners = cv.findChessboardCorners( gray, PATTERN_SIZE, cv.CALIB_CB_ADAPTIVE_THRESH + cv.CALIB_CB_NORMALIZE_IMAGE, ) if not found: print(f"skipped no corners: {path.name}") continue refined = cv.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) object_points.append(object_template.copy()) image_points.append(refined) print(f"accepted: {path.name}") if len(object_points) < MIN_FRAMES: raise SystemExit( f"found {len(object_points)} usable frames, need at least {MIN_FRAMES}" )
Frames without all 7×6 inner corners are skipped. A different image size stops the run because one calibration matrix cannot be applied unchanged across mixed resolutions.
- Append the result-writing calibration section to calibrate_camera.py.
rms, camera_matrix, dist_coeffs, rvecs, tvecs = cv.calibrateCamera( object_points, image_points, image_size, None, None, ) np.savez( OUTPUT_PATH, camera_matrix=camera_matrix, dist_coeffs=dist_coeffs, image_size=np.array(image_size), pattern_size=np.array(PATTERN_SIZE), square_size=np.array([SQUARE_SIZE]), rms=np.array([rms]), ) np.set_printoptions(precision=4, suppress=True) print(f"accepted frames: {len(object_points)} of {len(image_paths)}") print(f"image size: {image_size[0]}x{image_size[1]}") print("camera matrix:") print(camera_matrix) print("distortion coefficients:") print(dist_coeffs.ravel()) print(f"opencv rms: {rms:.4f} px") print(f"wrote: {OUTPUT_PATH}")
cv.calibrateCamera() returns RMS reprojection error in pixels. Low values indicate that the fitted model projects detected corners close to their observed positions. Frame coverage and sharp corner detections still determine whether the result is reusable.
- Run calibrate_camera.py from the directory that contains calibration.
$ python3 calibrate_camera.py accepted: left01.jpg accepted: left02.jpg ##### snipped ##### accepted: left14.jpg accepted frames: 10 of 13 image size: 640x480 camera matrix: [[533.8228 0. 341.1336] [ 0. 533.874 231.9884] [ 0. 0. 1. ]] distortion coefficients: [-0.2933 0.1189 0.0013 -0.0002 0.0157] opencv rms: 0.1577 px wrote: calibration.npz
Too few accepted images or a high RMS error indicates that blurred, cropped, or similarly positioned frames need replacement. The reported image size belongs with the calibration profile.
- Inspect the saved calibration arrays with a fail-capable NumPy check.
$ python3 - <<'PY' import numpy as np calibration = np.load("calibration.npz") camera_matrix = calibration["camera_matrix"] dist_coeffs = calibration["dist_coeffs"] rms = float(calibration["rms"][0]) if camera_matrix.shape != (3, 3): raise SystemExit(f"unexpected camera matrix shape: {camera_matrix.shape}") if dist_coeffs.size < 4: raise SystemExit(f"too few distortion coefficients: {dist_coeffs.size}") if not np.isfinite(camera_matrix).all() or not np.isfinite(dist_coeffs).all(): raise SystemExit("calibration contains non-finite values") print(f"camera matrix: {camera_matrix.shape}") print(f"distortion coefficients: {dist_coeffs.size}") print(f"opencv rms: {rms:.4f} px") print("calibration archive: valid") PY camera matrix: (3, 3) distortion coefficients: 5 opencv rms: 0.1577 px calibration archive: validThe calibration.npz archive applies only to frames from the same camera, lens state, and resolution.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.