Note
Go to the end to download the full example code.
Stereo Calibration + DIC¶
This example detects a calibration-dot target in synchronised stereo images and uses the detected correspondences to calibrate the two cameras.
The required calibration images are not distributed with the package to keep things lightweight. You can download them here.
Make sure to download/clone the repository and unzip the calibration images. For this example we’ll assume you’ve cloned the images to your current working directory.
Currently, the DIC module in pyvale only supports the below type of calibration targets (If there’s a specific type of calibration target you would like adding then please get in touch and we’ll see what we can do).
Calibration has two stages: the dot detection, then a bundle adjustment to find the camera parameters that minimise the reprojection error across every image pair. you might need to ammend the below path depending on where you have saved the calibration images. The below path assumes you have saved the calibration images in a folder called “calibration-data” in your current working directory.
cam0 = "./calibration-data/cam0_*.bmp"
cam1 = "./calibration-data/cam1_*.bmp"
Dot detection needs the image pairs, the target dimensions and spacing, and
the three deliberately missing circles. Coordinates use the target grid with
(0, 0) at the bottom-left dot; the missing dots can be specified in any order.
dots0, dots1, grid, filenames0, filenames1 = calib.detect_dots(
cam0=cam0,
cam1=cam1,
grid_height=9,
grid_width=12,
missing_dots=[(9, 6), (2, 2), (2, 6)], # order of white dots does not matter
min_dot_fraction=0.5, # minimum fraction of matching dots.
grid_spacing=1.25, # in mm.
)
dots0 and dots1 are lists with one (N, 2) array per accepted image
pair. Each row is a dot position in pixel coordinates. grid is the matching list of
(N, 3) target coordinates in millimetres; it is the common object-point
reference frame used by both cameras. The filename lists contain the accepted
filenames from the dot detection.
For a high volume of calibration images that have large dimensions. It might be worth saving the detected points so the detection doesn’t need to be rerun everything you need to reperform a stereo calibration.
# make the output directory
output_dir = Path.cwd() / "pyvale-output" / "dic_ex08"
output_dir.mkdir(parents=True, exist_ok=True)
# save the detected points to plain text files
for points0, points1, filename0, filename1 in zip(dots0, dots1, filenames0, filenames1):
np.savetxt(output_dir / f"dots_{Path(filename0).stem}.txt", points0)
np.savetxt(output_dir / f"dots_{Path(filename1).stem}.txt", points1)
The initial estimates are computed with Zhang’s calibration method. More details
of the approach, which we use as an initial estimate, can be found in the
paper titled “A Flexible New Technique for Camera Calibration”. The parameters from
this method are then further refined with stereo bundle adjustment. You can toggle
distortion parameters in the calibration using the optimize_distortion= function
argument. The argument img_dims is [width, height] in
pixels and must match the input images.
calib_params, reproj_err0, reproj_err1 = calib.calibrate_stereo(
dots_cam0=dots0,
dots_cam1=dots1,
grid=grid,
optimize_distortion=False, # all distortion parameters set to 0.
img_dims=[2464, 2056],
error_formulation="RMSE"
)
calib.savetxt(calib=calib_params, path=output_dir / "stereo_calibration.txt", delimiter=",")
calib_params contains the intrinsics of each camera (focal lengths, skew,
principal point and distortion), plus the translation in millimetres and
rotation in degrees from camera 0 to camera 1. reproj_err0 and
reproj_err1 contain one root-mean-square reprojection error, in pixels,
for each accepted image pair. Large values identify target poses that should
be checked for poor dot detection or unsuitable images.
print("")
print("Cam 0 Intrinsic Parameters:")
print(calib_params.cam0)
print("")
print("Cam 1 Intrinsic Parameters:")
print(calib_params.cam1)
print("")
print("Translation from cam 0 to cam 1 (mm):")
print(calib_params.translation)
print("")
print("Rotation from cam 0 to cam 1 (degrees):")
print(calib_params.rotation)
print("")
print("Reprojection Errors for cam 0 (pixels):")
print(reproj_err0)
print("")
print("Reprojection Errors for cam 1 (pixels):")
print(reproj_err1)