Troubleshooting¶
This page provides practical solutions to common calibration issues.
Reference Camera Choice¶
Problem: Stage 3/4 optimization converges but produces poor extrinsic estimates or high validation errors.
Why it happens: The reference camera (first camera in your config’s cameras list) defines the world coordinate origin. If the reference camera has poor intrinsic calibration, all other cameras inherit these errors during extrinsic initialization.
Solution:
Check Stage 1 RMS reprojection errors (printed during
aquacal calibrate)Set the camera with the lowest Stage 1 RMS as your reference camera
Edit your config: move that camera to the first position in the
cameraslistRe-run calibration
Example:
Stage 1 complete.
cam0: RMS = 0.42 pixels ← good
cam1: RMS = 1.89 pixels ← poor intrinsics
cam2: RMS = 0.38 pixels ← best, use as reference
In this case, set cam2 as the reference camera (first in config).
High RMS in Stage 1 (Intrinsic Calibration)¶
Problem: Stage 1 reports RMS reprojection error > 1.0 pixels for one or more cameras.
Possible causes:
Too few calibration frames
Poor spatial coverage (board always in center of frame)
Board measurements in config don’t match physical board
Low-quality video (motion blur, compression artifacts)
Solutions:
1. Lower frame_step for more data¶
Edit your config:
detection:
frame_step: 5 # Change to 2 or 1 for more frames
This uses more frames from your in-air videos, improving intrinsic estimates.
2. Verify board measurements¶
Measure your physical ChArUco board with calipers:
square_size: Distance between inner corners of adjacent squares (in meters)marker_size: Black marker side length (in meters)
Even 1mm error can degrade calibration.
3. Check video quality¶
Ensure board is fully visible and in focus
Move board to different positions and orientations (not just center)
Avoid motion blur (hold board steady or use faster shutter speed)
4. Use max_calibration_frames to speed up iterations¶
If Stage 1 is good but Stage 3/4 are slow, limit optimization frames:
optimization:
max_calibration_frames: 150 # Use subset for faster Stage 3/4
This speeds up iteration time while you debug other issues.
Bad Round-Trip Errors or Diverging Optimization¶
Problem: Stage 3 or 4 fails to converge, or validation errors are much higher than training errors.
Possible causes:
Disconnected pose graph (no overlapping camera views)
Too few shared observations between cameras
Initial water_z estimates are far from true value
Overfitted intrinsic model (rare, but see next section)
Solutions:
1. Check for pose graph connectivity¶
All cameras must observe the calibration board in at least some shared frames. If cameras have completely non-overlapping views, extrinsic initialization will fail.
Fix: Ensure underwater videos include frames where the board is visible to multiple cameras simultaneously.
2. Improve initial water_z estimates¶
Add initial_water_z (approximate camera-to-water Z-distances) to your config:
interface:
initial_water_z:
cam0: 0.20 # Measure with a ruler (meters)
cam1: 0.22
cam2: 0.18
Better initialization helps Stage 3 converge faster and more reliably.
3. Check water_z bounds¶
Ensure the final water_z value is within the optimization bounds [0.01, 2.0] meters. If your cameras are farther than 2m from the water surface, you’ll need to modify the bounds in the source code.
Camera Layout Looks Wrong Despite Low Errors¶
Problem: The recovered camera positions look physically implausible — most often the cameras appear out of plane (their heights/depths scatter by centimeters, or the whole rig looks tilted) by much more than your real mounting could account for — yet Stage 3/4 reprojection RMS is low and 3D validation error is small.
Why it happens: Low reprojection error only certifies that your parameters are self-consistent in the directions the data actually constrains. It does not certify that every camera parameter was independently measured. In a downward-looking refractive array, the out-of-plane arrangement of the cameras (their depth/height along the viewing axis) is only weakly observable, so large deviations there cost almost nothing in residual. The optimizer isn’t wrong; it is sliding freely along a “soft” direction the geometry barely pins down. Concretely, that direction is soft because:
Cameras measure bearings, not range. Each corner tightly fixes the direction to a point but only weakly its depth. Depth separation between cameras comes from cross-camera parallax plus the known board scale — and for a top-down array over a limited board-depth range, the parallax that would separate camera heights is thin.
Small height spread vs. working distance. If the cameras span, say, ~10 cm in height but sit ~1 m from the board, height differences barely move where corners land — poor signal-to-noise on exactly that coordinate.
Refraction supplies compensating degrees of freedom. Each camera’s
interface_distanceequalswater_z − C_z, so a camera’s height and its interface distance trade off almost exactly; the free per-frame board poses absorb the rest. A camera placed too high can produce nearly the same refracted rays as a correctly-placed one — at ~zero reprojection cost. (The axial camera-to-interface distance being weakly observable is a known property of flat-refractive calibration.)
Because these modes are so soft, ordinary sub-pixel detection noise and small unmodeled effects get amplified into centimeter-scale scatter in the recovered layout. Low error ≠ faithful metric camera layout.
When NOT to worry (the common case):
Stage 3/4 RMS is low and the holdout 3D validation error is small, and
you use the calibration for triangulation/reconstruction within the captured volume.
The soft-mode deformation is self-consistent, so it preserves ray geometry — including for held-out points — which is exactly why 3D validation stays accurate even when the drawn camera positions look off. For reconstruction, this calibration is fine.
When to actually worry:
You need the metric camera layout itself (e.g. reporting inter-camera distances, comparing against a CAD/survey of the rig, or feeding poses to another system). Then don’t trust the out-of-plane numbers until you’ve excited that mode (see below) — a low RMS alone won’t tell you they’re right.
The apparent misarrangement comes with elevated or bimodal errors — e.g. a few cameras at 10–15 px while the rest are ~1–2 px, or validation 3D error that is also high. That is not benign soft-mode wander; it is bias, usually from contaminated frames (board at/above the surface, ripples, mis-detections) that inject a coherent error along the same soft direction. See Contaminated Frames below.
The layout changes substantially run-to-run or when you change the frame subset. Large sensitivity to which frames you use is a direct symptom of an under-excited soft mode.
What to do (to make the layout trustworthy):
Excite the weak direction. Present the board across a wide depth range and with strong tilts, and get corners out toward the image edges (larger incidence angles carry more refractive-geometry information). Ensure frames with genuine cross-camera parallax.
Add what you know as a prior. Provide measured
initial_water_zper camera; if the mounting plane is known, that constrains heights.Remove bias first. Trim contaminated segments with
detection.start_frame/detection.stop_frameand keep automaticreject_outlier_frameson, so a biased soft-mode tilt isn’t mistaken for real geometry.
For the underlying theory, see the Refractive Geometry and Optimizer Pipeline guides.
Contaminated Frames (Board Near the Surface, Ripples)¶
Problem: A subset of cameras show much higher reprojection RMS than the rest (e.g. 10–15 px vs. 1–2 px), and/or the rig looks tilted — typically the cameras physically nearest where the board enters the water.
Why it happens: Frames where the ChArUco board is at or above the water surface, or where the surface is rippled, violate the flat-interface refraction model. Near the surface, refractive projection is extremely sensitive to geometry, so these frames produce large, spatially-coherent residuals that a robust loss only partially suppresses. Their leverage biases the extrinsics of nearby cameras — and, because the bias lies along the weakly-observed out-of-plane direction (see Camera Layout Looks Wrong), it can tilt the rig while barely raising overall RMS. Such segments are common at the start (board being lowered in) and end (board lifted out) of a capture.
Solutions:
1. Trim contaminated start/end segments¶
If the board is out of the water or the surface is settling at the start (or end) of the extrinsic capture, skip those frames. These apply uniformly across all cameras, preserving frame-index (video) synchronization, and affect only the extrinsic stage:
detection:
start_frame: 600 # Skip the first N frames (board entering / ripples settling)
stop_frame: 5400 # Stop at frame N, exclusive (board being lifted out)
2. Keep automatic outlier-frame rejection on¶
By default (reject_outlier_frames: true), after Stage 3 the pipeline scores each frame by its reprojection RMS against independently estimated per-frame board poses, drops catastrophic outliers, and re-optimizes once. This is a no-op on clean data. It reliably catches discrete bad frames anywhere in the capture; a guardrail suppresses rejection (and warns) if more than frame_rejection_max_fraction of frames would be dropped, so a broadly-contaminated dataset surfaces loudly instead of being silently gutted. Tune with:
optimization:
reject_outlier_frames: true
frame_rejection_k: 5.0 # Reject if RMS > k × median RMS ...
frame_rejection_floor_px: 5.0 # ... and above this absolute pixel floor
frame_rejection_max_fraction: 0.25 # Guardrail
Note: Automatic rejection catches discrete catastrophic frames well, but a contiguous segment of mildly contaminated frames (each only a few pixels off) may not exceed any safe threshold. For those, prefer trimming by
start_frame/stop_frame— position in the capture is the meaningful signal. The best fix is a clean capture: keep the board fully submerged and the surface calm while collecting extrinsic frames.
Check the frame_rejection block in diagnostics.json to see what was flagged and why.
Camera Models and Overfitting¶
Problem: Stage 1 RMS is very low (< 0.2 pixels), but Stage 3/4 validation errors are high or undistortion produces artifacts.
Possible cause: Overfitted distortion model. With few calibration frames, higher-order distortion coefficients can fit noise rather than true lens distortion, causing the model to “blow up” outside the calibrated image region.
Camera models in AquaCal:
Model |
Distortion Coeffs |
When to Use |
Auto-Simplification |
|---|---|---|---|
Standard (pinhole) |
5 params: k1, k2, p1, p2, k3 |
Most cameras, moderate distortion |
✅ Yes (automatic) |
Rational |
8 params: k1-k6, p1, p2 |
Wide-angle lenses, extreme barrel/pincushion distortion |
❌ No |
Fisheye |
4 params: k1-k4 (equidistant) |
Ultra-wide or fisheye lenses (> 120° FOV) |
❌ No |
Auto-simplification (standard model only): If the full 5-parameter model produces a bad undistortion roundtrip (monotonicity check fails), AquaCal automatically retries with simpler models:
Fix k3 to 0 (3-parameter model: k1, k2, p1, p2)
Fix k3 and k2 to 0 (1-parameter model: k1, p1, p2)
This prevents overfitting when calibration data is sparse.
Solutions:
If using rational or fisheye models:¶
These models do NOT auto-simplify. If you suspect overfitting:
Remove the camera from
rational_model_camerasorfisheye_camerasin your configLet it use the standard 5-parameter model (with auto-simplification)
Re-run calibration
Signs of overfitting:¶
Stage 1 RMS < 0.2 pixels but high round-trip error warnings in console
Distortion correction produces visible artifacts at image edges
Validation RMS >> training RMS in Stage 3/4
When to use each model:¶
Standard: Default for most lenses (up to moderate wide-angle)
Rational: Use only if standard model gives RMS > 1.0 pixels and you have 50+ calibration frames
Fisheye: Use only for true fisheye lenses (GoPro, ultra-wide security cameras)
For detailed theory, see the Camera Models section in the Optimizer Pipeline guide.
Allowed Camera Combinations¶
Problem: Config validation fails with ValueError about camera lists.
Rules:
fisheye_camerasmust be a subset ofauxiliary_camerasfisheye_camerasmust not overlap withrational_model_camerasauxiliary_camerasmust not overlap withcameras(primary cameras)
Example (valid):
cameras:
- cam0 # Reference camera
- cam1 # Primary camera
auxiliary_cameras:
- cam2 # Auxiliary (registered post-hoc)
- cam3 # Auxiliary (fisheye)
rational_model_cameras:
- cam1 # Wide-angle primary camera
fisheye_cameras:
- cam3 # Subset of auxiliary_cameras
Example (invalid):
cameras:
- cam0
auxiliary_cameras:
- cam1
fisheye_cameras:
- cam0 # ERROR: cam0 is in cameras, not auxiliary_cameras
rational_model_cameras:
- cam1 # ERROR: cam1 is in both rational and fisheye
fisheye_cameras:
- cam1
Memory and Performance Issues¶
Problem: Calibration consumes too much RAM or takes too long.
Solution: Limit the number of frames used in Stage 3/4 optimization:
optimization:
max_calibration_frames: 100 # Reduce from default (null = no limit)
Stage 1 (intrinsics) always uses all detected frames (controlled by
frame_step)Stage 2 (extrinsic init) uses all frames for pose graph construction
Stage 3/4 can be limited to a random subset for faster optimization
Reducing to 100-150 frames typically has minimal impact on calibration quality while significantly reducing memory usage and runtime.
No Detections Found¶
Problem: aquacal calibrate fails with “No ChArUco board detected in any frame”.
Possible causes:
Wrong board configuration (dictionary, dimensions)
Board out of focus or too small in frame
Poor lighting or motion blur
Solutions:
Verify board config matches your physical board:
Check
dictionary(e.g.,DICT_4X4_50vs.DICT_6X6_250)Verify
squares_xandsquares_ymatch board layoutConfirm
legacy_patternsetting (check if top-left cell has a marker)
Improve video quality:
Ensure board fills at least 30% of frame
Check focus (board should be sharp)
Increase lighting to avoid motion blur
Lower
min_cornersthreshold:detection: min_corners: 8 # Lower to 4 or 6 if board is partially visible
See Also¶
CLI Reference — Command-line usage and options
Optimizer Pipeline — Understanding the calibration stages
Glossary — Definitions of key terms