imu-calib 0.3.0

Kalibr-compatible IMU intrinsic calibration and correction library for Rust.
Documentation

imu-calib

Kalibr-compatible IMU intrinsic calibration for Rust: estimate the parameters, then apply them online.

Implements the model from Rehder et al., "Extending Kalibr: Calibrating the Extrinsics of Multiple IMUs and of Individual Axes", ICRA 2016, and reads and writes the YAML files Kalibr uses.

Workflow

Two independent parameter groups, from two different recordings.

        long stationary recording                 static poses + rotations
                    │                                        │
                    ▼                                        ▼
          allan::AllanEstimator              estimate::calibrate_recording
                    │                                        │
       noise densities, random walks         M_a, M_g, C_gyro_i, b_a, b_g
                    │                                        │
                    └───────────────► ImuIntrinsics ◄───────────┘
                                           │
                          ┌──────────────────┴──────────────────┐
                          ▼                                 ▼
         save_kalibr_input_yaml()                     ImuCorrector
           feeds the Kalibr run                 corrects a live stream

Noise parameters

Record at least 3 hours with the IMU undisturbed, away from vibration.

use imu_calib::allan::{AllanConfig, AllanEstimator};

let mut estimator = AllanEstimator::new(AllanConfig::default());

// for every sample of the recording:
estimator.push(timestamp_seconds, raw_accel, raw_gyro);

let result = estimator.finish()?;
let noise = result.noise()?;

println!("accel noise density: {}", noise.accel_noise_density);
println!("gyro random walk:    {}", noise.gyro_random_walk);

std::fs::write("allan.csv", result.to_csv_string())?;

White noise is read off the −1/2 slope at τ = 1 s, bias random walk off the +1/2 slope at τ = 3 s. noise() returns an error if the recording is too short to show either region.

Intrinsics

Needs no camera and no target. Hold the IMU still in at least nine well-spread orientations, rotating about a different axis between each one. Start at rest: the first seconds set the noise floor that tells a hold from a move.

GuidedCalibration prompts through it live:

use imu_calib::estimate::{GuidedCalibration, GuidedConfig, Status};

let mut session = GuidedCalibration::new(GuidedConfig::default());

// for every incoming sample:
match session.push(t, raw_accel, raw_gyro) {
    Status::Initialising { collected, total } => { /* hold still */ }
    Status::WaitingForMotion { next_pose, total } => { /* pick it up and turn it */ }
    Status::WaitingForStillness { .. }           => { /* put it down */ }
    Status::Collecting { collected, total, .. }  => { /* keep holding */ }
    Status::PoseCaptured { pose, total }         => { /* one down */ }
    Status::Done => {
        let calibration = session.finish()?;
        println!("{}", calibration.report.summary());
        calibration.intrinsics.save_kalibr_yaml("imu.yaml")?;
    }
}

For a finished recording, estimate::calibrate_recording finds the static stretches itself. Both return a CalibrationReport; check its residuals.

With only the six axis-aligned faces, set accel_parametrization: AccelParametrization::ScaleBias.

Apply

use imu_calib::ImuIntrinsics;

let intrinsics = ImuIntrinsics::from_kalibr_yaml("imu.yaml")?;
let corrector = intrinsics.corrector()?;   // build once

// per sample:
let (accel, gyro) = corrector.correct(raw_accel, raw_gyro);

correct_msg and correct_msg_in_place take a ROS-shaped [ImuMsg].

ImuCorrector::gyro_bias_from_static re-estimates the gyro bias from a few seconds of stationary data, which is worth doing at startup — bias moves with temperature and between power cycles, scale and misalignment do not.

Kalibr interop

from_kalibr_yaml reads Kalibr's imu-<bagname>.yaml directly, including the imu0: / imu1: nesting and every key of all three --imu-models:

let intrinsics = ImuIntrinsics::from_kalibr_yaml("imu-dynamic.yaml")?;
let second     = ImuIntrinsics::from_kalibr_yaml_named("imu-dynamic.yaml", "imu1")?;
let all        = ImuIntrinsics::all_from_kalibr_yaml_str(&yaml)?;

Files written by this crate use Kalibr's layout, so the two are interchangeable.

To feed a Kalibr run, write the flat input file after the Allan step:

intrinsics.save_kalibr_input_yaml("imu0.yaml")?;
rosrun kalibr kalibr_calibrate_imu_camera --target april_6x6_80x80cm.yaml --imu imu0.yaml --imu-models scale-misalignment --cam camchain.yaml --bag dynamic.bag

For low-cost MEMS parts the Kalibr wiki suggests inflating the measured noise densities by 10× or more before using them in an estimator.

Command line

binnacle runs both estimators over an MCAP recording: record a bag, get a Kalibr YAML.

License