Chessboard object points
Define 3D coordinates of inner corners in board units (e.g. mm). For a board with cols × rows inner corners, Z = 0 on the plane.
import numpy as np
cols, rows = 9, 6
square_size = 25.0 # mm
objp = np.zeros((cols * rows, 3), np.float32)
objp[:, :2] = np.mgrid[0:cols, 0:rows].T.reshape(-1, 2)
objp *= square_size
Find and refine corners
import cv2
img = cv2.imread("calib_01.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
pattern = (cols, rows)
found, corners = cv2.findChessboardCorners(gray, pattern, None)
if found:
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
cv2.drawChessboardCorners(img, pattern, corners2, found)
Adaptive flags for hard images
flags = cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_NORMALIZE_IMAGE
found, corners = cv2.findChessboardCornersSB(gray, pattern, flags) # often sharper
findChessboardCornersSB is available in newer OpenCV; fall back to findChessboardCorners if missing.
Run calibrateCamera
Collect objpoints (repeated objp per image) and imgpoints (detected corners). Image size is (width, height).
objpoints = [] # 3d in world
imgpoints = [] # 2d in image
# loop over images: append objp and corners2 when found
# ...
h, w = gray.shape[:2]
rms, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, (w, h), None, None,
flags=cv2.CALIB_FIX_K3) # optional: fix higher radial terms for wide FOV
print("RMS reprojection error (px):", rms)
print("K:\n", mtx)
print("dist:", dist.ravel())
Undistort
One-off
dst = cv2.undistort(img, mtx, dist)
Remap (faster for video)
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
mapx, mapy = cv2.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w, h), cv2.CV_32FC1)
undist = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
x, y, w2, h2 = roi
undist_cropped = undist[y:y+h2, x:x+w2]
projectPoints and error check
imgpts, _ = cv2.projectPoints(objp, rvecs[0], tvecs[0], mtx, dist)
err = cv2.norm(imgpoints[0], imgpts.reshape(-1, 2), cv2.NORM_L2) / len(imgpts)
Average per-corner error for one image; aggregate over the dataset to judge calibration quality.
Save and load
np.savez("calib.npz", mtx=mtx, dist=dist, rms=rms)
D = np.load("calib.npz")
mtx, dist = D["mtx"], D["dist"]
Write YAML with OpenCV
fs = cv2.FileStorage("calib.yml", cv2.FILE_STORAGE_WRITE)
fs.write("camera_matrix", mtx)
fs.write("dist_coeffs", dist)
fs.release()
Practical tips
- Cover the full frame and multiple angles; avoid all images from the same pose.
- Use sharp prints; matte board reduces glare.
- For strong wide-angle / fisheye, consider
cv2.fisheyeroutines instead of the pinhole + Brown model.
Takeaways
Kanddistdescribe the camera;rvec, tvecper image place the board in camera coordinates.- Lower RMS → better fit; outliers usually mean bad corner detection.
- Use
remapwhen undistorting video streams.