Skip to content

feat(cameracalibrate): --check for degraded camera calibration#3118

Open
AaryanAgrawal wants to merge 2 commits into
dimensionalOS:mainfrom
AaryanAgrawal:feat/cameracalibrate-check
Open

feat(cameracalibrate): --check for degraded camera calibration#3118
AaryanAgrawal wants to merge 2 commits into
dimensionalOS:mainfrom
AaryanAgrawal:feat/cameracalibrate-check

Conversation

@AaryanAgrawal

@AaryanAgrawal AaryanAgrawal commented Jul 21, 2026

Copy link
Copy Markdown

feat(cameracalibrate): a --check flag for degraded camera calibration

Contribution path

  • Linked: DIM-1309, DIM-1308

Problem

  • Marker poses came back bad, which points at the camera intrinsics.
  • No way to grade a deployed CameraInfo on a live camera — cameracalibrate could write a new calib but never score the shipped one.
  • Reprojection error alone can't catch it: a mirror-flipped board pose still reprojects under ~1.2 px.

Solution

  • cameracalibrate --check scores a deployed CameraInfo on the boards it sees: reprojection RMS + intrinsics drift vs a fresh calib from the same frames → OK/DEGRADED.
  • On DEGRADED, interactive y/N writes the fresh calib from those same frames — no second capture.
  • ChArUco support; fisheye fix (--distortion-model fisheye → true equidistant via cv2.fisheye.calibrate); frame-diversity gate; --frames-out to save accepted frames.
  • QoL only: doesn't touch the pipeline, off unless you run --check.

How to Test

  • Hardware (Go2 "astro"): --check over lcm:/color_image vs 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

  • Synthetic known-truth charuco + fisheye round-trip recovers K/D (~1e-5 px).
  • plumb_bob default unchanged.
  • 66 tests pass; ruff + mypy clean.

Checklist

  • Scoped to one problem.
  • Ran uv run pytest for the files changed.
  • Reviewed every line.
  • Core touch justified above.
  • Hardware status stated.
  • Read and approved the CLA.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds live camera-calibration checks and expands the calibration workflow. The main changes are:

  • Grades deployed calibration using reprojection error and intrinsics drift.
  • Adds ChArUco targets and equidistant fisheye calibration.
  • Adds guided capture, frame diversity checks, and reusable frame output.
  • Offers interactive recalibration when a deployed calibration is degraded.

Confidence Score: 5/5

The latest updates appear safe to merge.

  • No distinct blocking issue remains eligible for a new follow-up comment.
  • The latest push does not introduce a semantic regression in the reviewed paths.

Important Files Changed

Filename Overview
dimos/robot/cli/dimos.py Adds command options and dispatch for calibration checks, ChArUco boards, guided capture, and saved frames.
dimos/utils/cli/cameracalibrate/cameracalibrate.py Implements calibration grading, ChArUco and fisheye support, capture guidance, frame persistence, and replacement calibration.

Reviews (2): Last reviewed commit: "style(cameracalibrate): ruff format" | Re-trigger Greptile

Comment on lines +2599 to +2607
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."
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Comment on lines +2416 to +2417
board_type = BoardType(board)
fresh_model = "plumb_bob" if board_type is BoardType.charuco else result["distortion_model"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Comment on lines +272 to +277
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)):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Old Captures Remain In Output

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.

Comment on lines +974 to +980
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)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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']}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is very long now. It would be better to split it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants