Camera Calibration Guide
This comprehensive guide covers camera calibration procedures for the Advanced Image Sensor Interface, including single camera calibration, multi-camera synchronization, and advanced calibration techniques.
Overview
Camera calibration is essential for accurate image processing and computer vision applications. This guide covers:
Intrinsic Calibration: Camera internal parameters
Extrinsic Calibration: Camera pose and position
Multi-Camera Calibration: Stereo and multi-camera systems
Distortion Correction: Lens distortion compensation
Color Calibration: Color accuracy and consistency
Temporal Calibration: Frame timing and synchronization
Intrinsic Camera Calibration
Overview
Intrinsic calibration determines the internal camera parameters including focal length, principal point, and distortion coefficients.
Camera Model
The pinhole camera model with distortion is used:
x' = x(1 + k1*r² + k2*r⁴ + k3*r⁶) + 2*p1*x*y + p2*(r² + 2*x²)
y' = y(1 + k1*r² + k2*r⁴ + k3*r⁶) + p1*(r² + 2*y²) + 2*p2*x*y
Where:
(x, y)are normalized image coordinates(x', y')are distorted coordinatesk1, k2, k3are radial distortion coefficientsp1, p2are tangential distortion coefficientsr² = x² + y²
Calibration Implementation
import numpy as np
from advanced_image_sensor_interface.sensor_interface.enhanced_sensor import EnhancedSensorInterface
from advanced_image_sensor_interface.utils.calibration import CameraCalibrator
# Initialize calibration system
calibrator = CameraCalibrator()
sensor = EnhancedSensorInterface()
# Calibration configuration
calibration_config = {
"pattern_type": "checkerboard", # Calibration pattern
"pattern_size": (9, 6), # Pattern dimensions
"square_size": 25.0, # Square size in mm
"num_images": 20, # Number of calibration images
"image_size": (1920, 1080), # Image resolution
"flags": [
"CALIB_RATIONAL_MODEL", # Use rational distortion model
"CALIB_THIN_PRISM_MODEL", # Include thin prism distortion
"CALIB_TILTED_MODEL" # Include sensor tilt
]
}
# Capture calibration images
calibration_images = []
for i in range(calibration_config["num_images"]):
print(f"Capture calibration image {i+1}/{calibration_config['num_images']}")
input("Position calibration pattern and press Enter...")
image = sensor.capture_frame()
if image is not None:
calibration_images.append(image)
else:
print("Failed to capture image, retrying...")
i -= 1
# Perform calibration
calibration_result = calibrator.calibrate_camera(
images=calibration_images,
config=calibration_config
)
# Extract calibration parameters
camera_matrix = calibration_result.camera_matrix
distortion_coeffs = calibration_result.distortion_coefficients
rvecs = calibration_result.rotation_vectors
tvecs = calibration_result.translation_vectors
rms_error = calibration_result.rms_reprojection_error
print(f"Calibration completed with RMS error: {rms_error:.3f} pixels")
print(f"Camera matrix:\n{camera_matrix}")
print(f"Distortion coefficients: {distortion_coeffs}")
Calibration Quality Assessment
# Assess calibration quality
quality_metrics = calibrator.assess_calibration_quality(calibration_result)
print("Calibration Quality Assessment:")
print(f"RMS Reprojection Error: {quality_metrics.rms_error:.3f} pixels")
print(f"Mean Error: {quality_metrics.mean_error:.3f} pixels")
print(f"Max Error: {quality_metrics.max_error:.3f} pixels")
print(f"Standard Deviation: {quality_metrics.std_error:.3f} pixels")
# Quality thresholds
if quality_metrics.rms_error < 0.5:
print("✅ Excellent calibration quality")
elif quality_metrics.rms_error < 1.0:
print("✅ Good calibration quality")
elif quality_metrics.rms_error < 2.0:
print("⚠️ Acceptable calibration quality")
else:
print("❌ Poor calibration quality - recalibration recommended")
Distortion Correction
# Apply distortion correction to images
def undistort_image(image, camera_matrix, distortion_coeffs):
"""Remove lens distortion from image."""
h, w = image.shape[:2]
# Generate optimal camera matrix
new_camera_matrix, roi = cv2.getOptimalNewCameraMatrix(
camera_matrix, distortion_coeffs, (w, h), 1, (w, h)
)
# Undistort image
undistorted = cv2.undistort(
image, camera_matrix, distortion_coeffs, None, new_camera_matrix
)
# Crop to region of interest
x, y, w, h = roi
undistorted = undistorted[y:y+h, x:x+w]
return undistorted, new_camera_matrix
# Example usage
raw_image = sensor.capture_frame()
corrected_image, new_matrix = undistort_image(
raw_image, camera_matrix, distortion_coeffs
)
Multi-Camera Calibration
Stereo Camera Calibration
from advanced_image_sensor_interface.sensor_interface.multi_sensor_sync import MultiSensorSync
# Initialize stereo system
stereo_sync = MultiSensorSync()
stereo_calibrator = StereoCalibrator()
# Stereo calibration configuration
stereo_config = {
"pattern_type": "checkerboard",
"pattern_size": (9, 6),
"square_size": 25.0,
"num_image_pairs": 25,
"flags": [
"CALIB_RATIONAL_MODEL",
"CALIB_SAME_FOCAL_LENGTH", # Assume same focal length
"CALIB_ZERO_TANGENT_DIST" # Assume no tangential distortion
]
}
# Capture stereo image pairs
left_images = []
right_images = []
for i in range(stereo_config["num_image_pairs"]):
print(f"Capture stereo pair {i+1}/{stereo_config['num_image_pairs']}")
input("Position calibration pattern and press Enter...")
# Capture synchronized frames
frames = stereo_sync.capture_synchronized_frames()
if len(frames) >= 2:
left_images.append(frames[0][0]) # First camera frame
right_images.append(frames[1][0]) # Second camera frame
# Perform stereo calibration
stereo_result = stereo_calibrator.calibrate_stereo(
left_images=left_images,
right_images=right_images,
config=stereo_config
)
# Extract stereo parameters
R = stereo_result.rotation_matrix # Rotation between cameras
T = stereo_result.translation_vector # Translation between cameras
E = stereo_result.essential_matrix # Essential matrix
F = stereo_result.fundamental_matrix # Fundamental matrix
print(f"Stereo calibration RMS error: {stereo_result.rms_error:.3f} pixels")
print(f"Baseline distance: {np.linalg.norm(T):.2f} mm")
Stereo Rectification
# Compute rectification transforms
rectify_result = stereo_calibrator.compute_rectification(
stereo_result, image_size=(1920, 1080)
)
R1 = rectify_result.rectification_transform_left
R2 = rectify_result.rectification_transform_right
P1 = rectify_result.projection_matrix_left
P2 = rectify_result.projection_matrix_right
Q = rectify_result.disparity_to_depth_matrix
# Create rectification maps
map1_left, map2_left = cv2.initUndistortRectifyMap(
camera_matrix_left, distortion_left, R1, P1, image_size, cv2.CV_16SC2
)
map1_right, map2_right = cv2.initUndistortRectifyMap(
camera_matrix_right, distortion_right, R2, P2, image_size, cv2.CV_16SC2
)
# Rectify stereo images
def rectify_stereo_pair(left_image, right_image):
"""Rectify stereo image pair."""
left_rectified = cv2.remap(left_image, map1_left, map2_left, cv2.INTER_LINEAR)
right_rectified = cv2.remap(right_image, map1_right, map2_right, cv2.INTER_LINEAR)
return left_rectified, right_rectified
Multi-Camera Array Calibration
# Calibrate camera array (3+ cameras)
array_calibrator = MultiCameraCalibrator()
# Array configuration
array_config = {
"num_cameras": 4,
"reference_camera": 0, # Reference camera index
"pattern_type": "checkerboard",
"pattern_size": (9, 6),
"square_size": 25.0,
"num_image_sets": 30
}
# Capture synchronized image sets
image_sets = []
for i in range(array_config["num_image_sets"]):
print(f"Capture image set {i+1}/{array_config['num_image_sets']}")
input("Position calibration pattern and press Enter...")
frames = stereo_sync.capture_synchronized_frames()
if len(frames) == array_config["num_cameras"]:
image_set = [frame[0] for frame in frames.values()]
image_sets.append(image_set)
# Perform multi-camera calibration
array_result = array_calibrator.calibrate_camera_array(
image_sets=image_sets,
config=array_config
)
# Extract relative poses
relative_poses = array_result.relative_poses
for i, pose in enumerate(relative_poses):
if i == array_config["reference_camera"]:
continue
R, T = pose.rotation_matrix, pose.translation_vector
print(f"Camera {i} relative to reference:")
print(f" Rotation: {R}")
print(f" Translation: {T} mm")
Color Calibration
Color Checker Calibration
from advanced_image_sensor_interface.utils.color_calibration import ColorCalibrator
# Initialize color calibrator
color_calibrator = ColorCalibrator()
# Color calibration configuration
color_config = {
"color_checker_type": "X-Rite ColorChecker Classic",
"reference_illuminant": "D65", # Standard daylight
"white_balance_method": "gray_world",
"color_space": "sRGB"
}
# Capture color checker image
print("Position X-Rite ColorChecker under standard illumination")
input("Press Enter to capture...")
color_checker_image = sensor.capture_frame()
# Detect color checker patches
patches = color_calibrator.detect_color_checker(
color_checker_image, color_config["color_checker_type"]
)
if len(patches) == 24: # Standard ColorChecker has 24 patches
print("✅ Color checker detected successfully")
# Compute color correction matrix
color_correction_matrix = color_calibrator.compute_color_correction(
patches, color_config
)
print(f"Color correction matrix:\n{color_correction_matrix}")
# Apply color correction
def apply_color_correction(image, ccm):
"""Apply color correction matrix to image."""
# Reshape image for matrix multiplication
h, w, c = image.shape
image_flat = image.reshape(-1, c).astype(np.float32)
# Apply color correction
corrected_flat = np.dot(image_flat, ccm.T)
# Clip values and reshape
corrected_flat = np.clip(corrected_flat, 0, 255)
corrected_image = corrected_flat.reshape(h, w, c).astype(np.uint8)
return corrected_image
else:
print("❌ Color checker detection failed")
White Balance Calibration
# White balance calibration
wb_calibrator = WhiteBalanceCalibrator()
# Capture white reference
print("Position white reference target")
input("Press Enter to capture white reference...")
white_reference = sensor.capture_frame()
# Compute white balance gains
wb_gains = wb_calibrator.compute_white_balance_gains(
white_reference, method="gray_world"
)
print(f"White balance gains - R: {wb_gains[0]:.3f}, G: {wb_gains[1]:.3f}, B: {wb_gains[2]:.3f}")
# Apply white balance
def apply_white_balance(image, gains):
"""Apply white balance gains to image."""
balanced = image.astype(np.float32)
balanced[:, :, 0] *= gains[0] # Red channel
balanced[:, :, 1] *= gains[1] # Green channel
balanced[:, :, 2] *= gains[2] # Blue channel
return np.clip(balanced, 0, 255).astype(np.uint8)
Temporal Calibration
Frame Timing Calibration
from advanced_image_sensor_interface.utils.timing_calibration import TimingCalibrator
# Initialize timing calibrator
timing_calibrator = TimingCalibrator()
# Timing calibration using LED flash
timing_config = {
"flash_duration_ms": 1.0, # LED flash duration
"flash_frequency_hz": 10.0, # Flash frequency
"measurement_duration_s": 30.0, # Measurement duration
"expected_frame_rate": 30.0 # Expected camera frame rate
}
print("Setup LED flash synchronized with external trigger")
input("Press Enter to start timing calibration...")
# Measure frame timing
timing_result = timing_calibrator.measure_frame_timing(
sensor, timing_config
)
print("Frame Timing Results:")
print(f"Measured frame rate: {timing_result.actual_frame_rate:.3f} fps")
print(f"Frame rate error: {timing_result.frame_rate_error:.3f} fps")
print(f"Jitter (std dev): {timing_result.frame_jitter:.3f} ms")
print(f"Maximum latency: {timing_result.max_latency:.3f} ms")
# Timing quality assessment
if timing_result.frame_jitter < 1.0:
print("✅ Excellent timing stability")
elif timing_result.frame_jitter < 5.0:
print("✅ Good timing stability")
else:
print("⚠️ Poor timing stability - check synchronization")
Multi-Camera Synchronization Calibration
# Synchronization calibration for multiple cameras
sync_calibrator = SynchronizationCalibrator()
# Synchronization test configuration
sync_config = {
"num_cameras": 4,
"test_duration_s": 60.0,
"flash_frequency_hz": 5.0,
"sync_tolerance_ms": 1.0
}
print("Setup synchronized LED flash visible to all cameras")
input("Press Enter to start synchronization test...")
# Measure synchronization accuracy
sync_result = sync_calibrator.measure_synchronization(
stereo_sync, sync_config
)
print("Synchronization Results:")
for i, camera_sync in enumerate(sync_result.camera_synchronization):
print(f"Camera {i}:")
print(f" Mean offset: {camera_sync.mean_offset:.3f} ms")
print(f" Std deviation: {camera_sync.std_offset:.3f} ms")
print(f" Max offset: {camera_sync.max_offset:.3f} ms")
# Overall synchronization quality
overall_sync = sync_result.overall_synchronization
print(f"\nOverall synchronization accuracy: {overall_sync:.3f} ms")
if overall_sync < 1.0:
print("✅ Excellent synchronization")
elif overall_sync < 5.0:
print("✅ Good synchronization")
else:
print("⚠️ Poor synchronization - check hardware setup")
Advanced Calibration Techniques
Rolling Shutter Calibration
# Rolling shutter distortion calibration
rs_calibrator = RollingShutterCalibrator()
# Rolling shutter test setup
rs_config = {
"motion_type": "linear", # Linear motion pattern
"motion_speed_mps": 1.0, # Motion speed in m/s
"pattern_type": "vertical_lines", # Vertical line pattern
"line_spacing": 10, # Line spacing in pixels
"exposure_time_us": 10000 # Exposure time in microseconds
}
print("Setup moving vertical line pattern")
input("Press Enter to capture rolling shutter test...")
# Capture test image with motion
rs_test_image = sensor.capture_frame()
# Analyze rolling shutter distortion
rs_result = rs_calibrator.analyze_rolling_shutter(
rs_test_image, rs_config
)
print("Rolling Shutter Analysis:")
print(f"Readout time: {rs_result.readout_time_us:.1f} μs")
print(f"Line readout time: {rs_result.line_readout_time_us:.3f} μs")
print(f"Skew angle: {rs_result.skew_angle_deg:.3f}°")
# Rolling shutter correction
def correct_rolling_shutter(image, readout_time_us, motion_vector):
"""Correct rolling shutter distortion."""
h, w = image.shape[:2]
corrected = np.zeros_like(image)
for row in range(h):
# Calculate time offset for this row
time_offset = (row / h) * readout_time_us * 1e-6
# Calculate motion compensation
motion_offset = motion_vector * time_offset
# Apply motion compensation (simplified)
offset_x = int(motion_offset[0])
if 0 <= offset_x < w:
corrected[row, :w-offset_x] = image[row, offset_x:]
return corrected
Geometric Distortion Calibration
# Advanced geometric distortion calibration
geo_calibrator = GeometricCalibrator()
# Geometric calibration using grid pattern
geo_config = {
"grid_type": "dot_grid", # Dot grid pattern
"grid_size": (15, 11), # Grid dimensions
"dot_spacing_mm": 10.0, # Dot spacing in mm
"detection_method": "blob", # Blob detection
"subpixel_accuracy": True # Subpixel accuracy
}
print("Position dot grid calibration target")
input("Press Enter to capture geometric calibration image...")
geo_image = sensor.capture_frame()
# Detect grid points
grid_points = geo_calibrator.detect_grid_points(geo_image, geo_config)
if len(grid_points) >= geo_config["grid_size"][0] * geo_config["grid_size"][1] * 0.8:
print("✅ Grid detection successful")
# Compute geometric distortion model
distortion_model = geo_calibrator.compute_geometric_distortion(
grid_points, geo_config
)
print("Geometric Distortion Model:")
print(f"Barrel distortion: {distortion_model.barrel_distortion:.6f}")
print(f"Pincushion distortion: {distortion_model.pincushion_distortion:.6f}")
print(f"Asymmetric distortion: {distortion_model.asymmetric_distortion}")
else:
print("❌ Grid detection failed - check lighting and focus")
Calibration Validation
Cross-Validation
# Cross-validation of calibration results
validator = CalibrationValidator()
# Validation configuration
validation_config = {
"validation_method": "k_fold",
"k_folds": 5,
"metrics": ["reprojection_error", "3d_accuracy", "stereo_accuracy"],
"test_patterns": ["checkerboard", "circles", "asymmetric_circles"]
}
# Perform cross-validation
validation_result = validator.cross_validate_calibration(
calibration_images, calibration_config, validation_config
)
print("Cross-Validation Results:")
for metric, result in validation_result.metrics.items():
print(f"{metric}:")
print(f" Mean: {result.mean:.3f}")
print(f" Std: {result.std:.3f}")
print(f" Min: {result.min:.3f}")
print(f" Max: {result.max:.3f}")
Real-World Accuracy Test
# Real-world accuracy validation
accuracy_tester = AccuracyTester()
# Setup known 3D reference points
reference_points_3d = np.array([
[0, 0, 0], # Origin
[100, 0, 0], # 100mm along X
[0, 100, 0], # 100mm along Y
[0, 0, 100], # 100mm along Z
[100, 100, 0], # Corner point
])
print("Position reference objects at known 3D coordinates")
input("Press Enter to capture validation image...")
validation_image = sensor.capture_frame()
# Detect reference points in image
detected_points_2d = accuracy_tester.detect_reference_points(
validation_image, reference_points_3d
)
# Compute 3D accuracy
accuracy_result = accuracy_tester.compute_3d_accuracy(
detected_points_2d, reference_points_3d, camera_matrix, distortion_coeffs
)
print("3D Accuracy Results:")
print(f"Mean 3D error: {accuracy_result.mean_error_mm:.2f} mm")
print(f"Max 3D error: {accuracy_result.max_error_mm:.2f} mm")
print(f"RMS 3D error: {accuracy_result.rms_error_mm:.2f} mm")
# Accuracy assessment
if accuracy_result.rms_error_mm < 1.0:
print("✅ Excellent 3D accuracy")
elif accuracy_result.rms_error_mm < 5.0:
print("✅ Good 3D accuracy")
else:
print("⚠️ Poor 3D accuracy - recalibration recommended")
Calibration Storage and Management
Calibration Data Storage
import json
import numpy as np
from datetime import datetime
class CalibrationManager:
"""Manage calibration data storage and retrieval."""
def __init__(self, storage_path="calibration_data"):
self.storage_path = storage_path
os.makedirs(storage_path, exist_ok=True)
def save_calibration(self, calibration_result, camera_id, metadata=None):
"""Save calibration data to file."""
timestamp = datetime.now().isoformat()
calibration_data = {
"timestamp": timestamp,
"camera_id": camera_id,
"camera_matrix": calibration_result.camera_matrix.tolist(),
"distortion_coefficients": calibration_result.distortion_coefficients.tolist(),
"rms_error": float(calibration_result.rms_reprojection_error),
"image_size": calibration_result.image_size,
"metadata": metadata or {}
}
filename = f"{self.storage_path}/calibration_{camera_id}_{timestamp}.json"
with open(filename, 'w') as f:
json.dump(calibration_data, f, indent=2)
print(f"Calibration saved to {filename}")
return filename
def load_calibration(self, camera_id, timestamp=None):
"""Load calibration data from file."""
if timestamp is None:
# Load most recent calibration
pattern = f"{self.storage_path}/calibration_{camera_id}_*.json"
files = glob.glob(pattern)
if not files:
raise FileNotFoundError(f"No calibration found for camera {camera_id}")
filename = max(files) # Most recent file
else:
filename = f"{self.storage_path}/calibration_{camera_id}_{timestamp}.json"
with open(filename, 'r') as f:
calibration_data = json.load(f)
# Convert back to numpy arrays
camera_matrix = np.array(calibration_data["camera_matrix"])
distortion_coeffs = np.array(calibration_data["distortion_coefficients"])
return {
"camera_matrix": camera_matrix,
"distortion_coefficients": distortion_coeffs,
"rms_error": calibration_data["rms_error"],
"image_size": calibration_data["image_size"],
"timestamp": calibration_data["timestamp"],
"metadata": calibration_data["metadata"]
}
# Usage example
calibration_manager = CalibrationManager()
# Save calibration
calibration_manager.save_calibration(
calibration_result,
camera_id="main_camera",
metadata={
"lens_model": "50mm f/1.8",
"sensor_size": "APS-C",
"calibration_environment": "laboratory",
"operator": "calibration_technician"
}
)
# Load calibration
loaded_calibration = calibration_manager.load_calibration("main_camera")
camera_matrix = loaded_calibration["camera_matrix"]
distortion_coeffs = loaded_calibration["distortion_coefficients"]
Troubleshooting Common Issues
Poor Calibration Quality
Symptoms:
High RMS reprojection error (>2.0 pixels)
Inconsistent results between calibration runs
Poor undistortion results
Solutions:
Improve calibration images:
Use more images (30+ recommended)
Ensure good pattern coverage across image
Vary pattern distance and orientation
Check image sharpness and focus
Check calibration pattern:
Verify pattern dimensions
Ensure pattern is flat and rigid
Use high-contrast pattern
Check for pattern detection errors
Optimize camera settings:
Use manual focus
Disable auto-exposure during calibration
Use adequate lighting
Minimize motion blur
Synchronization Issues
Symptoms:
Large timing offsets between cameras
Inconsistent frame timing
Dropped frames during synchronized capture
Solutions:
Hardware synchronization:
Use external trigger signal
Check trigger signal quality
Verify cable connections
Use proper termination
Software optimization:
Increase buffer sizes
Use real-time scheduling
Minimize system load
Check USB/network bandwidth
Distortion Correction Artifacts
Symptoms:
Visible distortion after correction
Image quality degradation
Cropping issues
Solutions:
Calibration improvement:
Recalibrate with more images
Use appropriate distortion model
Check for rolling shutter effects
Validate calibration accuracy
Correction optimization:
Use optimal new camera matrix
Adjust alpha parameter
Use high-quality interpolation
Consider region of interest cropping
This comprehensive calibration guide ensures accurate and reliable camera calibration for all supported protocols and configurations in the Advanced Image Sensor Interface.