Skip to the content
FixTheRobot Explore Robotics / UAV / Module 3 / M3-T8

UAV Systems Engineering · Module 3

M3-T8: IMU preintegration

Integrate the IMU once between keyframes into a relative-motion measurement, so the optimiser can change its mind without re-integrating hundreds of samples.

  • Lesson 8 of 16
  • Sensor Fusion and State Estimation
  • Research-level
  • About 6 minutes to read

Every tightly coupled VIO and LIO system faces the same problem: the IMU runs at hundreds of hertz, the optimisation at tens, and the states keep changing as the solver iterates. Preintegration (Lupton and Sukkarieh; Forster et al.) is the answer, and its core is a few lines of code you can check against direct integration.

Learning objectives

After this lesson, you should be able to:

  • Define the preintegrated measurements ΔR\Delta R, Δv\Delta v and Δp\Delta p and why they do not depend on the world pose.
  • Reconstruct the state at keyframe jj from the state at ii and the preintegrated terms.
  • Explain first-order bias correction and why re-integration is avoided.
  • Verify preintegration numerically against direct propagation.

Conceptual explanation

Directly propagating the IMU from keyframe ii to jj requires the full state (Ri, vi, pi)(R_{i},\,v_{i},\,p_{i}) and every sample; if the optimiser later changes RiR_{i} or viv_{i}, the integration must be redone. Preintegration rewrites the integral in the body frame of keyframe ii so that it depends only on the IMU samples and the biases, not on the world state at ii. The estimator then treats the three increments as one measurement linking the two keyframes.

IMU preintegration between keyframesHigh-rate IMU samples between two keyframes are integrated once into a single relative motion measurement expressed in the body frame of the first keyframe. The estimator uses that one measurement instead of hundreds of samples, and does not have to re-integrate when the poses or the velocity estimate change. keyframe ikeyframe j IMU samples (100–1000 Hz) → one relative-motion factor [ΔR_ij, Δv_ij, Δp_ij] + covarianceindependent of the world pose and velocity at i
Hundreds of IMU samples become one relative-motion factor with its covariance.
Animation of IMU preintegration in the plane. Between two camera keyframes a stream of gyro and accelerometer samples is integrated into one relative motion. The gyro bias estimate is then changed; the exact answer needs re-integration of all the samples, while the preintegrated result is corrected with a first-order Jacobian. For small bias changes the two relative paths coincide, and the error of the first-order correction grows quadratically with the size of the bias change.
Preintegration turns the samples between two frames into one relative-motion constraint (Δp, Δv, ΔR)(\Delta p,\,\Delta v,\,\Delta R). When the bias estimate changes, the constraint is corrected linearly with stored Jacobians instead of re-integrating: accurate for small changes, error ∝ (Δb)2(\Delta b)^{2}. Planar example with a gyro bias change from 0 to 0.1 rad/s over 1 s of motion; computed by exact re-integration against the first-order update. (Looping, about 6 seconds; drawn and computed for this course with illustrative values.)

Engineering intuition

Gravity and the unknown initial velocity are what tie a naive integration to the world state. Preintegration removes them from the integral: it integrates only the body-frame measured rotation and specific force relative to the start, and adds gravity and initial velocity analytically afterwards. The increments are therefore reusable. The remaining dependency is the bias, which changes slowly: instead of re-integrating, a first-order (Jacobian) correction updates the increments for a small bias change.

Mathematical formulation

With samples (ω~k, a~k)(\tilde{\omega }_{k},\,\tilde{a}_{k}) at interval Δt\Delta t, biases (bg, ba)(b_{g},\,b_{a}) and RikR_{ik} the rotation accumulated since keyframe ii:

Preintegrated measurements (noise-free)
ΔRij=∏kExp ⁣((ω~k−bg) Δt)\Delta\mathbf R_{ij} = \prod_k \mathrm{Exp}\!\left((\tilde{\boldsymbol\omega}_k - \mathbf b_g)\,\Delta t\right)
Δvij=∑kΔRik(a~k−ba) Δt\Delta\mathbf v_{ij} = \sum_k \Delta\mathbf R_{ik}(\tilde{\mathbf a}_k - \mathbf b_a)\,\Delta t
Δpij=∑k[Δvik Δt+12ΔRik(a~k−ba) Δt2]\Delta\mathbf p_{ij} = \sum_k\left[\Delta\mathbf v_{ik}\,\Delta t + \tfrac{1}{2}\Delta\mathbf R_{ik}(\tilde{\mathbf a}_k - \mathbf b_a)\,\Delta t^{2}\right]

The state at keyframe jj follows from the state at ii, the time T=tj−tiT=t_{j}-t_{i} and gravity gg (world frame):

Reconstruction of the state at j
Rj=Ri ΔRij\mathbf R_j = \mathbf R_i\,\Delta\mathbf R_{ij}
vj=vi+g T+Ri Δvij\mathbf v_j = \mathbf v_i + \mathbf g\,T + \mathbf R_i\,\Delta\mathbf v_{ij}
pj=pi+vi T+12g T2+Ri Δpij\mathbf p_j = \mathbf p_i + \mathbf v_i\,T + \tfrac{1}{2}\mathbf g\,T^{2} + \mathbf R_i\,\Delta\mathbf p_{ij}

This is exactly what direct integration produces (for the same discretisation), which is the test below. A noise model propagates the IMU noise densities through the recursion into a 9×9 covariance of (δϕ, δv, δp)(\delta \phi ,\,\delta v,\,\delta p), which weights the factor in the optimisation. For small bias changes δb\delta b, the increments are updated by first-order Jacobians (∂ΔR/∂bg, ∂Δv/∂bg, ∂Δv/∂ba, …)(\partial \Delta R/\partial b_{g},\,\partial \Delta v/\partial b_{g},\,\partial \Delta v/\partial b_{a},\,\ldots ) accumulated during preintegration, avoiding re-integration:

First-order bias correction (rotation)
ΔR(bg+δbg)≈ΔR(bg) Exp ⁣(∂ΔR∂bg δbg)\Delta\mathbf R(\mathbf b_g + \delta\mathbf b_g) \approx \Delta\mathbf R(\mathbf b_g)\,\mathrm{Exp}\!\left(\frac{\partial\Delta\mathbf R}{\partial\mathbf b_g}\,\delta\mathbf b_g\right)

Forster et al. (2017) give the manifold-consistent derivation, including the covariance recursion and the Jacobians; use that paper when implementing a real system.

Architecture and data flow

IMU samples between two camera or LiDAR timestamps → preintegrator (accumulates ΔR\Delta R, Δv\Delta v, Δp\Delta p, Jacobians and covariance) → factor between keyframes ii and jj (and the bias random-walk factor) → sliding-window optimiser or smoother (VINS-Fusion, OpenVINS-style filters use related ideas). IMU samples must be interpolated exactly at the keyframe timestamps, which is why the camera-IMU time offset (lesson M7-T8) is part of the estimation: a time error shifts where the integration begins and ends.

Real-world UAV applications

  • VINS-Fusion and similar sliding-window estimators.
  • Factor-graph smoothing in GTSAM-based VIO.
  • LiDAR-inertial systems that need the motion between scans.
  • Fast re-linearisation in optimisation: the Jacobians avoid re-integration at every iteration.

Implementation example

This runs as shown: it propagates a state for one second directly, then reconstructs the same final state from the preintegrated increments, with a rotating body and non-zero biases. It also shows the first-order bias correction reproducing re-integration to about 1e-7.

import numpy as np
hat = lambda w: np.array([[0, -w[2], w[1]], [w[2], 0, -w[0]], [-w[1], w[0], 0]])
def expm(w):
    th = np.linalg.norm(w)
    if th < 1e-12: return np.eye(3) + hat(w)
    K = hat(w / th); return np.eye(3) + np.sin(th) * K + (1 - np.cos(th)) * K @ K
def logm(R):
    th = np.arccos(np.clip((np.trace(R) - 1) / 2, -1, 1))
    return np.zeros(3) if th < 1e-9 else th / (2 * np.sin(th)) * np.array([R[2, 1] - R[1, 2], R[0, 2] - R[2, 0], R[1, 0] - R[0, 1]])

g = np.array([0, 0, -9.81]); dt, n = 0.005, 200; T = n * dt
gyro = np.array([[0.5 * np.sin(0.02 * k) + 0.1, 0.3 * np.cos(0.03 * k), 0.4] for k in range(n)])          # measured rate (rad/s)
acc = np.array([[1 + np.sin(0.05 * k), 0.5 * np.cos(0.04 * k), 9.81 + 0.3 * np.sin(0.07 * k)] for k in range(n)])   # measured specific force
bg, ba = np.array([0.01, -0.02, 0.015]), np.array([0.05, 0.02, -0.03])
R0, v0, p0 = expm(np.array([0.2, -0.1, 0.5])), np.array([1.0, 0.5, 0.0]), np.array([0.0, 0.0, 1.0])

# direct propagation in the world frame
R, v, p = R0.copy(), v0.copy(), p0.copy()
for k in range(n):
    a = R @ (acc[k] - ba) + g
    p = p + v * dt + 0.5 * a * dt * dt; v = v + a * dt; R = R @ expm((gyro[k] - bg) * dt)

def preintegrate(bg_, ba_):                                      # body-frame increments: independent of R0, v0, p0 and gravity
    dR, dv, dp = np.eye(3), np.zeros(3), np.zeros(3)
    for k in range(n):
        a = acc[k] - ba_
        dp = dp + dv * dt + 0.5 * dR @ a * dt * dt; dv = dv + dR @ a * dt; dR = dR @ expm((gyro[k] - bg_) * dt)
    return dR, dv, dp
dR, dv, dp = preintegrate(bg, ba)
R_j, v_j, p_j = R0 @ dR, v0 + g * T + R0 @ dv, p0 + v0 * T + 0.5 * g * T * T + R0 @ dp
print("max reconstruction error  R: %.1e  v: %.1e  p: %.1e" % (np.abs(R_j - R).max(), np.abs(v_j - v).max(), np.abs(p_j - p).max()))
assert np.abs(R_j - R).max() < 1e-12 and np.abs(v_j - v).max() < 1e-10 and np.abs(p_j - p).max() < 1e-10

# first-order bias correction of the rotation increment
dbg = np.array([0.002, 0.001, -0.001]); dR0 = dR; dR1 = preintegrate(bg + dbg, ba)[0]
J = np.column_stack([logm(dR0.T @ preintegrate(bg + e, ba)[0]) / 1e-6 for e in 1e-6 * np.eye(3)])   # numerical d(log dR)/d(bg)
err_corrected = np.abs(dR0 @ expm(J @ dbg) - dR1).max(); err_none = np.abs(dR0 - dR1).max()
print(f"bias change: error without correction {err_none:.1e}, with first-order Jacobian {err_corrected:.1e}")
assert err_corrected < err_none / 1000
Preintegration reproduces direct propagation exactly and supports bias correction without re-integration.

Common engineering mistakes

Integrating in the world frame

Then the result depends on the current estimate of RiR_{i} and must be redone at every optimiser iteration.

Forgetting gravity and the initial velocity in the reconstruction

They are added back analytically; omitting them gives a vehicle that floats.

Re-integrating on every bias update

Use the first-order Jacobian correction for small changes and re-integrate only when the bias moves a lot.

Interpolating IMU data at wrong timestamps

Keyframe times must align with the IMU stream, including the camera-IMU offset.

Inconsistent covariance

Wrong noise densities (lesson M3-T7) mis-weight the factor and the filter becomes overconfident or sluggish.

Failure modes

Failure modes, symptoms and causes
Failure modeHow it shows upLikely cause
Drift with a stationary vehicleSlow motion estimate at restBias not estimated or noise parameters wrong
Scale error in visual-inertial odometryTrajectory size wrongIMU excitation too low to observe scale, or gravity direction not initialised
Divergence after fast rotationEstimate explodes after aggressive yawGyro saturation or time offset; first-order bias correction invalid for large changes
Wrong gravity magnitudeConstant climb or sink in the estimateGravity constant or direction wrong in the initialisation

Debugging approach

  1. Test the preintegrator against direct integration on synthetic data, as above.
  2. Check the covariance by Monte Carlo: simulate many noisy samples and compare the sample covariance of the increments.
  3. Plot estimated biases over the flight; they should be smooth and within the sensor’s specification.
  4. Vary the camera-IMU time offset and watch the residuals.

Practical exercises

  1. Show that ΔR\Delta R, Δv\Delta v and Δp\Delta p are unchanged if R0R_{0}, v0v_{0} and p0p_{0} change (modify the code).
  2. Measure the error of the first-order correction as a function of the size of δbg\delta b_{g}.
  3. Add white noise to the samples and estimate the covariance of Δp\Delta p by Monte Carlo.
  4. Compute the number of IMU samples saved per keyframe for a 200 Hz IMU and a 20 Hz camera.

Interview questions

Write an answer in your own words, then reveal the model answer. Nothing is sent anywhere.

Question 1

Why is preintegration useful in a sliding-window estimator?

Model answer

The relative-motion increments do not depend on the world pose and velocity at the start, so the optimiser can change those states without re-integrating hundreds of IMU samples; bias changes are handled with first-order Jacobians.

Question 2

What has to be added back when reconstructing the state from the increments?

Model answer

The initial velocity and gravity over the interval, and the initial rotation to express the body-frame increments in the world frame.

Question 3

Which IMU error terms does preintegration carry?

Model answer

Bias (corrected to first order via Jacobians) and white noise (propagated into a covariance), plus the bias random walk as a separate factor between keyframes.

References and further reading

Primary papers, official documentation and standards. Each claim above that is specific to a tool or version points here.

What’s next

Mark this lesson

Saved in this browser and synced to your account when you are signed in.

Next lesson

M3-T9: Camera-IMU fusion

Describe loosely and tightly coupled camera-IMU fusion and the information each throws away or keeps.