MyRoboPath
computer vision15 min readUpdated 2026-03-08Intermediate

Camera Calibration & Distortion Correction with OpenCV: Pinhole Model to 3D Rays

Step-by-step camera calibration using chessboard grids: compute intrinsic camera matrix K, radial/tangential distortion coefficients (k1, k2, p1, p2), and unproject 2D pixels to 3D spatial rays.

Dr. Elena Rostova
Dr. Elena Rostova
Principal Computer Vision Scientist

Key Engineering Takeaways

  • The Intrinsic Matrix K maps 3D camera coordinates to 2D image pixels via focal lengths (fx, fy) and optical center (cx, cy).
  • Wide-angle lenses exhibit barrel distortion; calibrating with OpenCV undistorts images for accurate geometric SLAM and vision guidance.
  • Always capture at least 15-20 chessboard images at varying angles and depths filling the entire camera field of view.
Prerequisites
  • Matrix algebra
  • Python OpenCV
Required Hardware / Tools
  • USB Webcam / Raspberry Pi Camera
  • Printed 8x6 Checkerboard Calibration Target

The Pinhole Camera Model & Intrinsic Matrix K

A camera projects a 3D point $\mathbf{P}_c = [X_c, Y_c, Z_c]^T$ in the camera coordinate frame onto a 2D pixel coordinate $\mathbf{p} = [u, v]^T$: $$\begin{bmatrix} u \\ v \\ 1 \end{bmatrix} \sim \mathbf{K} \begin{bmatrix} X_c / Z_c \\ Y_c / Z_c \\ 1 \end{bmatrix} = \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} X_c / Z_c \\ Y_c / Z_c \\ 1 \end{bmatrix}$$

Automated OpenCV Chessboard Calibration Script

Here is the complete Python calibration script:
calibrate_camera.py
python
import cv2
import numpy as np
import glob

# Checkerboard dimensions (inner corners)
CHECKERBOARD = (9, 6)
SQUARE_SIZE_MM = 25.0 # 25mm per square

# 3D world coordinates of checkerboard corners
objp = np.zeros((CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2) * SQUARE_SIZE_MM

objpoints = [] # 3D real world points
imgpoints = [] # 2D pixel points

images = glob.glob('calibration_images/*.jpg')
for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, None)
    if ret:
        objpoints.append(objp)
        # Refine corner sub-pixel precision
        criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
        corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
        imgpoints.append(corners2)

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
    objpoints, imgpoints, gray.shape[::-1], None, None
)

print("=== CAMERA CALIBRATION RESULTS ===")
print(f"Reprojection Error (RMS): {ret:.4f} pixels")
print("Intrinsic Matrix K:
", mtx)
print("Distortion Coefficients [k1, k2, p1, p2, k3]:
", dist.ravel())
Tags:#Computer Vision#OpenCV#Camera Calibration#Pinhole Model#Distortion#Python