Skip to content

IMU model

CamCalib solves IMU intrinsics — scale and misalignment for gyroscope and accelerometer, optional g-sensitivity, and biases — rather than assuming a datasheet. Symbols follow Notation & glossary.

ωm=Dwω+Tga+bg+ng\boldsymbol{\omega}_m = \mathbf{D}_w\, \boldsymbol{\omega} + \mathbf{T}_g\, \mathbf{a} + \mathbf{b}_g + \mathbf{n}_g am=Daa+ba+na\mathbf{a}_m = \mathbf{D}_a\, \mathbf{a} + \mathbf{b}_a + \mathbf{n}_a

Dw / Da carry scale and misalignment, Tg the g-sensitivity — how linear acceleration leaks into the gyro reading — and b_g / b_a the biases.

cam_imu.yaml
solve:
imu_intrinsics_model: kalibr # kalibr | rpng | calyx

The nine entries of each D are not all identifiable — the frame must be pinned to something, and what it is pinned to is the convention:

ModelPins the frame toNotes
kalibraccelerometer triadMatches Kalibr output
rpnggyroscope triadRPNG lineage
calyxGyro correction as a full 3×3, decomposed post-hoc into rotation × upper-triangular

The matrices themselves are not numerically comparable across conventions. Before concluding that two calibrations disagree, check they used the same one.

Many IMU drivers publish gyro and accelerometer on one timestamp even though the two channels are sampled at different rates and instants. t_ga is that internal offset: the combined stamp is taken as the gyro clock, and the accel stored at t is modelled as physically sampled at t − t_ga.

cam_imu.yaml
solve:
estimate_t_ga: true
sigma_t_ga: 0.001

It is estimated inside the ACI factor — one extra Jacobian column in the preintegration (∂a/∂t_ga = ȧ from per-step finite differences, coupled into the gyro row through Tg) plus an optional sixth factor key with a staged prior. With the flag off, the five-key factor is untouched.

In simulation, injected offsets of −2, +1, and +5 ms recover with |z| ≤ 1.3 at σ = 0.024 ms, with no td / Dw / Da cross-contamination.

On real data, the T265’s Bosch BMI055 measures t_ga ≈ −6.2 ms, stable to about 0.2 ms across bags — roughly 16σ. That number is a direct measurement of the driver’s accel-interpolation artefact.

t_ga is reported in the result JSON, the calibration YAML, and the text report; apply-calib corrects it by re-interpolating the accel channel at t + t_ga, and roundtrip checks that it collapses toward zero.

The other answer to an untrustworthy accelerometer — skip it:

cam_imu.yaml
input:
imus:
- {} # base IMU
- {gyro_only: true} # aux

Solves extrinsic rotation, td, Dw, and gyro bias; lever arm, Da, and Tg are pinned. Not permitted on the base IMU, which needs the accelerometer for gravity and metric scale. The calibration self-documents with a gyro_only: true flag per IMU.

On the T265 this makes the td collapse in a correct→recalibrate round-trip (+8.6 → −9.0 ms becomes +0.9 → +0.4 ms) and halves the residual lever drift.

Both address the same defect. gyro_only avoids the bad accelerometer; estimate_t_ga measures the offset causing it. Prefer estimate_t_ga on the base IMU, where you have no choice but to use the accel — and where the measured value is itself worth having. Prefer gyro_only for an auxiliary whose accelerometer adds nothing you need.

Scale and misalignment need excitation on every axis. G-sensitivity needs simultaneous rotation and strong linear acceleration, which most hand-held calibration motions do not produce — when Tg returns near its initial value with a wide covariance, it was not observed. See Degeneracy.