Skip to content

S10 — Online Calibration

The camera and IMU are two rigidly-attached sensors. Fusing them requires knowing exactly how they’re mounted relative to each other (the extrinsic transform T_CI), how the lens maps rays to pixels (intrinsics + distortion), and how their clocks line up (the time offset t_d). S10 is about whether the filter treats those numbers as estimated state with uncertainty — or as perfectly known constants. The cortex filter does the latter, and this page is the measured case that doing so is a leading contributor to the filter being over-confident.

OpenVINS carries the calibration as state, alongside the pose and biases:

  • Extrinsics T_CI ∈ SE(3) — 6 parameters per camera.
  • Intrinsics + distortion — 8 parameters per camera (radtan).
  • Time offset t_d — 1 parameter per camera.

Their Jacobians enter the measurement Jacobian H_x of the MSCKF update, so the filter both corrects them online and, more importantly, carries their uncertainty. The principle is unconditional: even a filter that does not estimate calibration online must still account for its uncertainty — by inflating the measurement noise R, or carrying a fixed prior block — or it treats imperfect calibration as perfect and becomes over-confident.

This is the crux of #212. The cortex MSCKF has no calibration states and no calibration term in the noise budget — the EuRoC T_CI is taken as truth. Any real mounting error then has nowhere to go: the filter can’t estimate it away and doesn’t widen its covariance for it, so the resulting reprojection error is silently absorbed into the state while the covariance stays tight. That is the textbook recipe for over-confidence.

Signaturecalibration as state: T_CI (6/cam), intrinsics+distortion (8/cam), t_d (1/cam)
What it doesestimates calibration online (its Jacobians enter H_x); or, if fixed, accounts for its uncertainty in R
Post / invariantsif estimated: calibration states observable only under sufficient excitation (like accel bias) · if fixed: the assumed-perfect calibration’s true uncertainty must appear somewhere in the noise budget
cortexnone of the above — no calibration states, no calibration noise term; T_CI/t_d/intrinsics treated as perfectly known

The S10 probe measures the hypothesis two ways — an analytic budget and an end-to-end sweep on the real backend.

1. Analytic noise budget — what unmodeled calibration costs in pixels

Section titled “1. Analytic noise budget — what unmodeled calibration costs in pixels”

Given a realistic calibration uncertainty, compute the reprojection error it induces and the equivalent R-inflation. The filter assumes ~4.6 px of image noise; a single degree of extrinsic error already exceeds that:

Extrinsic σ (gentle motion)induced reprojectionequivalent R inflation
0.5°4.1 px1.8×
1.0°8.1 px4.1×
2.0°16 px13.2×

Calibration noise budget — induced pixels & R-inflation vs extrinsic error

A 1° extrinsic error (entirely realistic for a hand-measured rig) induces ~8 px — nearly double the assumed noise — for an R-inflation of ~. That number is not a coincidence: it’s the same R×4 the EuRoC troubleshooting found restores consistency.

2. End-to-end R-inflation sweep — the filter’s measurement-noise deficit

Section titled “2. End-to-end R-inflation sweep — the filter’s measurement-noise deficit”

On the synthetic world with perfect calibration and clean sensors, the backend is already over-confident — pose NEES ≫ dof — reproducing the EuRoC fault in isolation. Sweeping the measurement-noise variance R and watching NEES return toward dof = 6 confirms R is the lever:

R scalepose NEES (target 6)
×142.6
×424.8
×1610.7
×326.09

Pose NEES vs R-inflation — the sweep back to consistency

The synthetic backend lands at NEES ≈ 43 at R×1 — almost exactly the EuRoC MH_05 value (~43) — so this clean, ground-truthed setup is reproducing the real over-confidence, not a synthetic artifact.

The probe above measures the cost of treating calibration as perfectly known. The real-data companion, s10_inspect, exercises the other S10 path — the shipped online extrinsic estimator (estimate_extrinsics) that carries the camera↔IMU extrinsic as state and refines it. It runs the real estimator over a real EuRoC sequence seeded from a deliberately perturbed extrinsic, and tracks the in-state estimate converging toward the dataset’s true calibration. No SDK change — backend().state() exposes the CalibState block.

Terminal window
s10_inspect --dataset /path/to/V1_01_easy/mav0 --out build/s10 --perturb-rot-deg 2 --perturb-trans-mm 30
node docs-site/scripts/gen-calibration-figures.mjs build/s10 # → calib_convergence.svg

It writes calibration.json and renders the convergence figure: the extrinsic-rotation error (deg) and translation error (mm) over time, each inside the filter’s own ±3σ band, decaying toward the reference (0 error). Seeded a couple of degrees / centimetres off, a healthy filter pulls the estimate back to truth while the band shrinks — the in-state remedy for the unmodeled-calibration over-confidence the probe quantifies. The per-frame error/σ read is unit-tested, and calib_recovery proves the operator converges on physically-consistent data.

The covariance ellipsoid in the 3-D viewer is this stage made visible: on the sample run the reported position σ collapses to ~13 cm while the true error grows past 2 m. An ellipsoid the ground-truth marker escapes from is the missing calibration uncertainty.