IR Camera Calibration
The goal of calibration is to estimate the intrinsic and distortion parameters of the IR camera so that each pixel can be mapped to a metric position on the measured surface.
Camera model
A 3D world point \(X_w\) projects to image pixel \((u, v)\) via:
Intrinsic matrix K
| Symbol | Meaning |
|---|---|
| \(f_x\), \(f_y\) | Focal lengths in pixels |
| \(c_x\), \(c_y\) | Principal point (optical axis in image coordinates) |
Non-square pixels
IR sensors commonly have \(f_x \neq f_y\) because the pixel pitch differs between axes.
Distortion coefficients
| Symbol | Type | Effect |
|---|---|---|
| \(k_1\), \(k_2\), \(k_3\) | Radial | Barrel / pincushion distortion |
| \(p_1\), \(p_2\) | Tangential | Lens tilt / decentering |
Spatial resolution
Once K is known, the physical size of one pixel at working distance \(Z\) is:
This is used to convert the undistorted image from pixels to millimetres before exporting to VTU.
Running calibration
The script expects an HDF5 file with a thermogram dataset of shape (N, H, W) — \(N\) frames of a thermal checkerboard target.
Checkerboard parameters (edit in scripts/calibrate.py):
| Parameter | Default | Meaning |
|---|---|---|
pattern_size |
(7, 4) |
Inner corners (cols × rows) |
square_size |
5.0 mm |
Physical side length of one square |
Output .npz fields
| Key | Shape | Description |
|---|---|---|
K |
(3, 3) |
Camera matrix |
dist |
(5, 1) |
Distortion coefficients |
img_size |
(2,) |
Image size [W, H] |
rms |
scalar | RMS reprojection error (px) |
Undistortion
import cv2, numpy as np
calib = np.load("data/ir_calib.npz")
K = calib["K"]
dist = calib["dist"].flatten()
h, w = img.shape
newK, roi = cv2.getOptimalNewCameraMatrix(K, dist, (w, h), alpha=0.0)
undistorted = cv2.undistort(img, K, dist, None, newK)
alpha=0.0 crops to only valid pixels (no black border).
alpha=1.0 keeps all pixels (black border where no source data exists).