import cv2 import numpy as np import glob import os import sys import argparse # # Create calibration .npz from checkerboard images # Usage: # python callibriate.py --camera cam0 --input-dir data/calibration/20260610_143022 [--output-dir ...] # # ── CLI-Parameter ────────────────────────────────────────────────────────────── parser = argparse.ArgumentParser(description='Camera calibration from checkerboard images') parser.add_argument('--camera', required=True, help='Camera ID prefix, e.g. cam0') parser.add_argument('--input-dir', default='.', help='Directory containing calibration images') parser.add_argument('--output-dir', default=None, help='Directory to save .npz (default: same as --input-dir)') args = parser.parse_args() camera = args.camera input_dir = os.path.abspath(args.input_dir) output_dir = os.path.abspath(args.output_dir) if args.output_dir else input_dir def log(msg): print(msg, flush=True) # flush=True → Node.js sieht die Zeile sofort # ── Parameters ──────────────────────────────────────────────────────────────── CHECKERBOARD = (10, 7) # inner corners square_size = 25.0 / 1000.0 # 25 mm -> meters # Prepare object points objp = np.zeros((CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32) objp[:, :2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2) objp *= square_size objpoints = [] # 3D points imgpoints = [] # 2D points # ── Load images ─────────────────────────────────────────────────────────────── pattern = os.path.join(input_dir, f"{camera}_*.jpg") images = sorted(glob.glob(pattern)) log(f"Camera: {camera}") log(f"Input-Dir: {input_dir}") log(f"Output-Dir: {output_dir}") log(f"Pattern: {pattern}") log(f"Found images: {len(images)}") img_size = None for fname in images: img = cv2.imread(fname) log(f"Processing {os.path.basename(fname)} ...") if img is None: log(f" Warning: could not read {fname}") continue gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if img_size is None: img_size = gray.shape[::-1] ret, corners = cv2.findChessboardCorners( gray, CHECKERBOARD, flags=cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_NORMALIZE_IMAGE + cv2.CALIB_CB_FAST_CHECK ) if ret: corners2 = cv2.cornerSubPix( gray, corners, (11, 11), (-1, -1), (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-6) ) objpoints.append(objp) imgpoints.append(corners2) log(f" ✅ Corners found") else: log(f" ❌ No corners found") log(f"\nTotal valid images: {len(objpoints)} / {len(images)}") # ── Sanity checks ───────────────────────────────────────────────────────────── if img_size is None: log("ERROR: No images were successfully loaded.") sys.exit(1) if len(objpoints) == 0: log("ERROR: No chessboard corners detected in any image. Calibration failed.") sys.exit(1) log("\n=== Sanity Checks Passed ===") # ── Calibration ─────────────────────────────────────────────────────────────── ret, K, D, rvecs, tvecs = cv2.calibrateCamera( objpoints, imgpoints, img_size, None, None ) log("\n=== Calibration Results ===") log(f"RMS reprojection error: {ret}") log(f"Camera matrix:\n{K}") log(f"Distortion coefficients:\n{D}") # ── Save calibration ────────────────────────────────────────────────────────── os.makedirs(output_dir, exist_ok=True) output_path = os.path.join(output_dir, f"{camera}_calibration.npz") np.savez(output_path, camera_matrix=K, dist_coeffs=D) log(f"\n✅ Calibration saved to {output_path}")