feat(cameracalibrate): --check for degraded camera calibration#3118
feat(cameracalibrate): --check for degraded camera calibration#3118AaryanAgrawal wants to merge 2 commits into
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR adds live camera-calibration checks and expands the calibration workflow. The main changes are:
Confidence Score: 5/5The latest updates appear safe to merge.
Important Files Changed
Reviews (2): Last reviewed commit: "style(cameracalibrate): ruff format" | Re-trigger Greptile |
| if ( | ||
| camera_info.width | ||
| and camera_info.height | ||
| and (int(camera_info.width), int(camera_info.height)) != image_size_wh | ||
| ): | ||
| result["resolution_warning"] = ( | ||
| f"deployed CameraInfo is {camera_info.width}x{camera_info.height} but frames are " | ||
| f"{w0}x{h0}; reprojection RMS is not meaningful across resolutions." | ||
| ) |
There was a problem hiding this comment.
Resolution Mismatch Still Gets Graded
When the frame size differs from the deployed CameraInfo, this path only records a warning and still calculates a verdict with unscaled pixel intrinsics. For example, checking 640×480 frames against the default 1280×720 Go2 calibration can return a misleading OK from invalid reprojection and drift measurements.
| board_type = BoardType(board) | ||
| fresh_model = "plumb_bob" if board_type is BoardType.charuco else result["distortion_model"] |
There was a problem hiding this comment.
Fisheye Replacement Changes Lens Model
A ChArUco check of an equidistant camera always writes the accepted replacement as plumb_bob, because its drift solve also uses the pinhole ChArUco calibrator. On the default Go2 fisheye path, accepting the prompt therefore installs a calibration for the wrong projection model and downstream undistortion uses incompatible K/D semantics.
| frames_out.mkdir(parents=True, exist_ok=True) | ||
| pad = max(3, len(str(max(len(frames) - 1, 0)))) | ||
| paths: list[Path] = [] | ||
| for i, frame in enumerate(frames): | ||
| p = frames_out / f"frame_{i:0{pad}d}.png" | ||
| if not cv2.imwrite(str(p), np.asarray(frame)): |
There was a problem hiding this comment.
Writing to an existing --frames-out directory overwrites matching names but leaves higher-numbered files from earlier runs. If a 20-frame capture is followed by a 10-frame capture, a later --source folder run loads the 10 new and 10 stale images together, which can mix cameras or board geometries into one calibration.
| cand = np.asarray(candidate_corners, dtype=np.float64).reshape(-1, 2) | ||
| nearest_mean_disp_px = float("inf") | ||
| for accepted in accepted_corners_list: | ||
| acc = np.asarray(accepted, dtype=np.float64).reshape(-1, 2) | ||
| if acc.shape != cand.shape: | ||
| continue # different corner count -> not comparable, cannot prove a duplicate | ||
| mean_disp_px = float(np.mean(np.linalg.norm(cand - acc, axis=1))) |
There was a problem hiding this comment.
ChArUco Novelty Ignores Corner IDs
Partial ChArUco detections can contain different corner IDs even when their arrays have the same length, but this code subtracts them by position; detections with different lengths are treated as novel without comparison. A stationary board whose visible markers change can therefore pass the default diversity gate repeatedly and produce a poorly constrained calibration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| @@ -1003,6 +2958,8 @@ def calibrate( | |||
| f"Detected pattern: {tuple(result.get('pattern_size', (cols, rows)))} " | |||
| f"({result.get('pattern_label', 'requested inner corners')})" | |||
| ) | |||
| if "frames_dir" in result: | |||
| typer.echo(f"Saved {result.get('n_frames_saved', 0)} frame(s) to {result['frames_dir']}") | |||
There was a problem hiding this comment.
This file is very long now. It would be better to split it.
feat(cameracalibrate): a
--checkflag for degraded camera calibrationContribution path
Problem
CameraInfoon a live camera —cameracalibratecould write a new calib but never score the shipped one.Solution
cameracalibrate --checkscores a deployedCameraInfoon the boards it sees: reprojection RMS + intrinsics drift vs a fresh calib from the same frames →OK/DEGRADED.DEGRADED, interactivey/Nwrites the fresh calib from those same frames — no second capture.--distortion-model fisheye→ true equidistant viacv2.fisheye.calibrate); frame-diversity gate;--frames-outto save accepted frames.--check.How to Test
--checkoverlcm:/color_imagevs a ChArUco board → verdict OK, RMS 0.29 px median / 0.68 p90, drift 3.9% (n=20).uv run pytest dimos/utils/cli/cameracalibrate/Evidence
Checklist
uv run pytestfor the files changed.