VINS Calibration
Targetless camera-IMU calibration from natural motion with voxl-open-vins-server and voxl-portal.
Table of contents
- Overview
- Run from the Command Line
- Motion
- Outputs and Exit Codes
- voxl-portal VINS Calibration Page
- Commit the Results
- Calibrator Profile
- Offline Replay with voxl-vins-lab
VINS calibration requires SDK 1.8.0 or newer (voxl-open-vins-server 0.7.8, voxl-portal 0.9.16). The voxl-open-vins-server 0.6.x releases in SDK 1.6 do not include it.
Overview
VINS calibration estimates the camera-IMU calibration from natural motion, with no calibration target. It uses the same camera drivers and frame gating as VIO. A session estimates these blocks:
- camera-IMU extrinsics (
R_ItoC,p_IinC) - the camera-IMU time offset (
td) - IMU intrinsics (
Dw,Da,R_AtoI, and the gyro g-sensitivityTg) - optionally, camera intrinsics
Each block either commits a new value or abstains and keeps its seed. A block that abstains is normal: it means the motion in that session did not make the block observable enough to trust.
Use it when the camera-IMU extrinsics or time offset are uncertain, for example after a camera is remounted or replaced, or when VIO drifts on a vehicle whose camera intrinsics are already good. Camera intrinsics still come from Camera Calibration.
By default every configured camera that has intrinsics joins one joint solve, and the first global-shutter camera anchors it.
Run from the Command Line
The calibrate mode of voxl-open-vins-server needs exclusive use of the cameras and IMU, so stop the VIO service first:
systemctl stop voxl-open-vins-server
voxl-open-vins-server --calibrate --calib-flight
--calibrate requires rs_convention: center in the estimator configuration. Otherwise it exits with ERROR: --calibrate requires rs_convention: center.
During the session the server requests the performance CPU governor. A board that is already in performance mode is not changed.
| Option | Description |
|---|---|
--calibrate, -k | Run a calibration session instead of VIO |
--calib-flight | Use the flight profile |
--calib-cam <name> | Calibrate only this camera. By default every camera with intrinsics joins, and the first global-shutter camera anchors the solve |
--calib-out <yaml> | Result file (default /data/ov_calib/ov_calib_result.yaml) |
--calib-config <yaml> | Calibrator profile (default /etc/modalai/ov_calib.yaml) |
--calib-max-sec <s> | Cap on the collection time |
--calib-budget <s> | Solve time budget |
--calib-min-eig <x> | Excitation threshold that ends collection early (default 5.0, 0 uses the full budget) |
--calib-no-perf-mode | Do not request the performance governor |
--no-calib-hotstart | Do not apply a committed snapshot at startup |
Motion
The solve can only commit what the motion makes observable.
- Good: figure-8s with speed changes, yaw sweeps at varying rates while translating, and vertical bobs.
- Avoid: hover, constant-velocity straight lines, and pure yaw in place.
- The session prompts for the direction that is currently weakest.
A hover-only session that refuses to commit extrinsics is behaving correctly.
Outputs and Exit Codes
Results are written to /data/ov_calib/:
| File | Contents |
|---|---|
ov_calib_result.yaml | Committed and seed values for each block, written atomically. This is the snapshot used for hot start |
ov_calib_result.yaml.report.json | Session report (committed blocks, gates, verification) |
ov_calib_result.yaml.session.bin | Session record for offline replay, written when record_session is true in the profile |
The calibrate mode exits with:
| Code | Meaning |
|---|---|
| 0 | Session completed and the result was committed |
| 2 | Session aborted or the result was refused |
| 255 | Setup failed before the session started, for example the rs_convention check |
voxl-portal VINS Calibration Page
voxl-portal 0.9.12 and newer (0.9.16 in SDK 1.8.0) has Debug > VINS Calibration. The page runs the standalone voxl-zcalib calibrator, which can run with VIO stopped or alongside VIO. When VIO is running, voxl-zcalib shares its pipes, does not stop VIO, does not change the CPU governor, and writes no configuration. Only one session runs at a time.
The page has three tabs: Calibrate, Results, and Configure.
Calibrate
- Cameras: pick from the declared cameras, or choose Any pipe and give the pipe, an intrinsics file, a label, the shutter type (Global, or Rolling with a readout time), and whether rescaling is allowed.
- Session: choose the source.
- Live: calibrate from the cameras and IMU now.
- Session record: replay a
.session.binfrom/data/ov_calibor from a voxl-vins-lab tracker-only run. A typed path overrides the list. - voxl-logger capture: see the warning below.
- Profile: Flight, or Bench (full 70-clone solve that takes minutes). Collection cap in seconds, where 0 uses the profile budget.
In SDK 1.8.0 the voxl-logger capture source fails, because voxl-zcalib 0.7.8 does not accept
--log. Convert the log first withvoxl-vins-lab -l <dir> --tracker-only, then pick<dir>/vins-lab/run_*/tracker_session.binunder Session record.
The live panel shows the stages (Settle, Bootstrap, Collect, Solve, Verify, Commit), tiles for windows, tracked rate and features per camera, and drops, and the calibrator log.
- Solve now ends collection and starts the solve.
- Extend +30 s adds 30 seconds of recording time. The solve budget does not change.
- Abort finalizes the session through the control pipe and then stops the calibrator.
Solve now and Extend +30 s are only enabled during live collection. They are disabled for recorded sources.
From a shell, a live voxl-zcalib session can also be solved early through its control pipe:
echo solve > /run/mpa/vins_calib/control
Results
The Results tab lists completed sessions. For each one it shows the committed blocks, the time offset, prior and posterior values, coupling, held-out verification, solve stages, bootstrap, the committed values, and the gates.
Write to extrinsics.conf runs voxl-calib-to-extrinsics. It shows a preview first, maps each camera to its entry in /etc/modalai/extrinsics.conf, and keeps a .bak copy. It is refused while voxl-open-vins-server is running.
Configure
The Configure tab edits three files:
/etc/modalai/voxl-open-vins-server.confestimator_config.yamlin the configuredyaml_folder. A file without the%YAML:1.0header, or with a:inside a trailing comment, is rejected/etc/modalai/ov_calib.yaml
Saves are refused while voxl-open-vins-server is running. Each save keeps a .bak copy and replaces the file atomically.
Commit the Results
A committed result can be used two ways: hot start, which overlays it at boot without editing the estimator files, or a permanent write into the configuration files.
Write the configuration files
voxl-calib-commit-chains writes the committed blocks into kalibr_imu_chain.yaml and kalibr_imucam_chain.yaml, keeping .bak copies:
systemctl stop voxl-open-vins-server
voxl-calib-commit-chains --dry-run
voxl-calib-commit-chains -e
systemctl start voxl-open-vins-server
| Option | Description |
|---|---|
--result | Result yaml to read (default /data/ov_calib/ov_calib_result.yaml) |
--conf | voxl-open-vins-server conf (default /etc/modalai/voxl-open-vins-server.conf) |
--extrinsics-conf | extrinsics.conf to update with -e (default /etc/modalai/extrinsics.conf) |
--chain-dir | Override the conf’s yaml_folder |
--cam IDX=CHILD | Map a camera to its extrinsics.conf entry (repeatable) |
--dry-run | Print the changes without writing |
--force | Commit despite a resolution mismatch |
-e, --write-extrinsics | Also write extrinsics.conf |
--keep-hotstart | Leave the hot-start keys on |
--keep-sync-config | Leave sync_config unchanged |
By default it turns off calibrate_first, en_calib_hotstart, and calib_hotstart_on_reset, because the values now live in the chain files. It also sets sync_config to false (adding the key if absent) so the chain files are not regenerated over the result. The IMU chain is only written when the result commits the full Dw, Da, R_AtoI set.
To update only /etc/modalai/extrinsics.conf, use voxl-calib-to-extrinsics:
voxl-calib-to-extrinsics
voxl-calib-to-extrinsics --in-place
Without --in-place it writes <conf>.calibrated (by default /etc/modalai/extrinsics.conf.calibrated). --in-place replaces the file and keeps a .bak. Only committed extrinsic blocks are changed, and the old and new values are printed. Other options are --result, --conf, --out, --cam IDX=CHILD, --imu-frame, and --imu-child (default imu_apps).
Hot start
These keys in /etc/modalai/voxl-open-vins-server.conf all default to false:
| Key | Description |
|---|---|
en_calib_hotstart | At boot, overlay the committed values from /data/ov_calib/ov_calib_result.yaml onto the estimator configuration. Seed values and filter noise settings are never applied |
calibrate_first | A boot with no committed snapshot runs a calibration session first, then starts VIO. Requires en_calib_hotstart. Retries are bounded to 3 attempts, counted in /data/ov_calib/.calibrate_first_attempts |
calib_hotstart_on_reset | A hard reset normally reverts to the on-disk configuration. When true, the snapshot is applied again |
calib_restore_governor | Restore the automatic CPU governor after a calibration session |
voxl-vins-autoconf --enable-zcal sets en_calib_hotstart. --disable-zcal clears en_calib_hotstart, calibrate_first, and calib_hotstart_on_reset. Neither changes calib_restore_governor:
voxl-vins-autoconf --enable-zcal
voxl-vins-autoconf --disable-zcal
To recalibrate with calibrate_first, delete /data/ov_calib/ov_calib_result.yaml and reboot.
Calibrator Profile
The profile is /etc/modalai/ov_calib.yaml. The package default is installed at /usr/share/modalai/voxl-open-vins/ov_calib.yaml and copied to /etc/modalai/ only when no copy exists there, so deleting the /etc/modalai/ copy resets it. The shipped profile uses the flight profile with a 30 s collection budget, a 120 s solve budget, collect_min_eig 5.0, and record_session true. The seed_* keys choose which blocks start from the current configuration; intrinsics are seeded, extrinsics and time offset are not.
The file is validated strictly. Keep comments on their own line: a : inside a trailing comment silently makes that key keep its default.
Offline Replay with voxl-vins-lab
voxl-vins-lab replays recorded data through the same code:
voxl-vins-lab -l <log_dir>
voxl-vins-lab -l <log_dir> --tracker-only
voxl-vins-lab --session <session.bin>
voxl-vins-lab -l <log_dir> --calib /data/ov_calib/ov_calib_result.yaml
-l <log_dir>runs the full system on a voxl-logger capture.--tracker-onlywrites<log_dir>/vins-lab/run_*/tracker_session.bin, which voxl-zcalib and the portal can replay.--session <bin>runs the filter only on a session record.--calib <yaml>replays the log under a committed calibration, for an A/B comparison against the original run.
Every run writes a calibration.json. voxl-vins-autoconf --log <dir> prints the replay command for a given log. For repeatable accuracy comparisons across many logs, see voxl-benchmark-vio.