#!/usr/bin/env python3
import argparse
import glob
import os
import time
from libCalibGeom import (
Calibration,
Camera,
CameraModelType,
ImageU8,
ImageDetection,
ImageSize,
ParameterState,
PatternSize,
Point3,
Pose,
OptimizationSettings,
find_checkerboard,
subpix_saddle_point_polynomium,
validate_license_with_certificate,
)
def build_target(pattern_rows: int, pattern_cols: int, spacing: float):
points = []
for i in range(pattern_rows):
for j in range(pattern_cols):
points.append(Point3(j * spacing, i * spacing, 0.0))
return points
def collect_detections(image_paths, pattern: PatternSize, kernel_half_width: int):
detections = []
image_size = None
for path in image_paths:
img = ImageU8()
if not img.load_from_disk(path):
continue
image_size = ImageSize(img.get_width(), img.get_height())
ok, points = find_checkerboard(img, pattern)
if not ok or len(points) != pattern.rows * pattern.columns:
continue
points = subpix_saddle_point_polynomium(img, kernel_half_width, points)
if len(points) != pattern.rows * pattern.columns:
continue
detections.append(ImageDetection(points, 0, len(detections), 0))
return detections, image_size
def main():
parser = argparse.ArgumentParser(description="Single camera calibration using libCalibGeom Python bindings.")
parser.add_argument("--images", required=True, help="Directory with calibration images.")
parser.add_argument("--pattern-rows", type=int, default=6, help="Number of inner corners in rows.")
parser.add_argument("--pattern-cols", type=int, default=7, help="Number of inner corners in columns.")
parser.add_argument("--spacing", type=float, default=0.050, help="Feature spacing in meters.")
parser.add_argument("--kernel-half-width", type=int, default=5, help="Subpixel kernel half width.")
parser.add_argument("--license-certificate", default=None, help="Compact libCalibGeom license certificate.")
args = parser.parse_args()
if args.license_certificate:
if not validate_license_with_certificate(args.license_certificate):
print("License validation failed (certificate).")
return 1
else:
if not validate_license_with_certificate():
print("License validation failed. Set LIBCALIBGEOM_LICENSE_CERTIFICATE or pass --license-certificate.")
return 1
image_paths = sorted(glob.glob(os.path.join(args.images, "*.png")))
if not image_paths:
print(f"No images found in {args.images}")
return 1
pattern = PatternSize()
pattern.rows = args.pattern_rows
pattern.columns = args.pattern_cols
print(f"Loading {len(image_paths)} images from {args.images}")
start = time.time()
detections, image_size = collect_detections(image_paths, pattern, args.kernel_half_width)
if not detections or image_size is None:
print("No valid detections found.")
return 1
print(f"Detections: {len(detections)}")
calib = Calibration()
calib.add_target(build_target(pattern.rows, pattern.columns, args.spacing))
calib.poses = [Pose() for _ in detections]
camera = Camera(CameraModelType.OpenCV, image_size)
for name in ("f", "ar", "cx", "cy", "k1", "k2", "k3", "p1", "p2"):
camera.model.param(name).state = ParameterState.FREE
calib.add_camera(camera)
print("Initializing...")
calib.initialize(detections)
print("Optimizing...")
result = calib.optimize(detections, OptimizationSettings())
elapsed = time.time() - start
print(f"Reprojection RMS: {result.rpe_rms}")
print(calib.cameras[0].to_string())
print(f"Elapsed: {elapsed:.2f}s")
return 0
if __name__ == "__main__":
raise SystemExit(main())