import pickle
import os
from pathlib import Path
# Check if we have calibration data
if "objpoints" not in locals() or "imgpoints" not in locals():
print("Error: No calibration data found. Please run the previous cell first.")
elif len(objpoints) == 0:
print("Error: No successful corner detections found. Cannot proceed with calibration.")
else:
print(f"Starting calibration with {len(objpoints)} successful detections...")
# Load test image
test_image_path = os.path.join(calib_dir, "test_image.jpg")
if not os.path.exists(test_image_path):
# Use first calibration image as fallback
test_image_path = images[0]
print(f"Using {os.path.basename(test_image_path)} as test image")
img = cv2.imread(test_image_path)
if img is None:
print("Error: Could not load test image")
else:
img_size = (img.shape[1], img.shape[0]) # (width, height)
print(f"Image size: {img_size}")
# Perform camera calibration
print("Performing camera calibration...")
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, img_size, None, None)
print(f"Calibration successful! RMS error: {ret:.4f}")
# Print calibration results
print("\n=== Camera Calibration Results ===")
print("Camera Matrix (K):")
print(mtx)
print(f"\nFocal lengths: fx={mtx[0, 0]:.2f}, fy={mtx[1, 1]:.2f}")
print(f"Principal point: cx={mtx[0, 2]:.2f}, cy={mtx[1, 2]:.2f}")
print(f"Aspect ratio: {mtx[0, 0] / mtx[1, 1]:.4f}")
print(f"\nDistortion Coefficients:")
print(f"k1={dist[0, 0]:.6f}, k2={dist[0, 1]:.6f}, p1={dist[0, 2]:.6f}")
print(f"p2={dist[0, 3]:.6f}, k3={dist[0, 4]:.6f}")
# Test undistortion on the image
print(f"\nApplying undistortion to test image...")
dst = cv2.undistort(img, mtx, dist, None, mtx)
# Save undistorted image
output_path = os.path.join(calib_dir, "test_undist.jpg")
cv2.imwrite(output_path, dst)
print(f"Saved undistorted image to: {output_path}")
# Save calibration results
calib_data = {
"mtx": mtx,
"dist": dist,
"rvecs": rvecs,
"tvecs": tvecs,
"rms_error": ret,
"image_size": img_size,
"num_images": len(objpoints),
}
pickle_path = os.path.join(calib_dir, "wide_dist_pickle.p")
with open(pickle_path, "wb") as f:
pickle.dump(calib_data, f)
print(f"Saved calibration data to: {pickle_path}")
# Visualize undistortion results
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 8))
# Convert images from BGR to RGB for matplotlib
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
dst_rgb = cv2.cvtColor(dst, cv2.COLOR_BGR2RGB)
ax1.imshow(img_rgb)
ax1.set_title("Original Image", fontsize=16)
ax1.axis("off")
ax2.imshow(dst_rgb)
ax2.set_title("Undistorted Image", fontsize=16)
ax2.axis("off")
plt.suptitle(f"Camera Calibration Results (RMS Error: {ret:.4f})", fontsize=18)
plt.tight_layout()
plt.show()
# Calculate and display reprojection error statistics
total_error = 0
total_points = 0
max_error = 0
for i in range(len(objpoints)):
# Project 3D points back to image plane
projected_points, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
# Calculate error
error = cv2.norm(imgpoints[i], projected_points, cv2.NORM_L2) / len(projected_points)
total_error += error
total_points += len(projected_points)
max_error = max(max_error, error)
mean_error = total_error / len(objpoints)
print(f"\n=== Reprojection Error Analysis ===")
print(f"Mean reprojection error: {mean_error:.4f} pixels")
print(f"Maximum reprojection error: {max_error:.4f} pixels")
print(f"RMS reprojection error: {ret:.4f} pixels")
if ret < 1.0:
print("✓ Excellent calibration quality (RMS < 1.0)")
elif ret < 2.0:
print("✓ Good calibration quality (RMS < 2.0)")
else:
print("⚠ Consider recalibrating with more/better images (RMS >= 2.0)")
print(f"\nCalibration complete! You can now use 'mtx' and 'dist' to undistort images.")