Skip to the content
FixTheRobot Explore Robotics / UAV / Module 7 / M7-T16

UAV Systems Engineering · Module 7

M7-T16: Camera-IMU-LiDAR extrinsic calibration

Extrinsics are the unglamorous numbers that make fusion work. Estimate them with rich motion, and verify them against something you did not fit.

  • Lesson 16 of 19
  • Embedded Systems, Timing and System Integration
  • Established engineering
  • About 6 minutes to read

A camera and an IMU on one vehicle see the same motion through different frames and clocks. The extrinsic calibration is the fixed rotation and translation between their frames, and the time offset between their clocks. Errors here produce drift and instability in VIO that no filter tuning will remove.

Learning objectives

After this lesson, you should be able to:

  • Define the extrinsic parameters and the time offset and state what excitation observes each.
  • Estimate rotation by aligning angular-rate vectors and the time offset by correlating rate magnitudes.
  • Explain why translation is weakly observable and what motion improves it.
  • Design an independent validation for the calibrated result.

Conceptual explanation

Established The formulations follow standard practice (angular-rate alignment, Kalibr-style continuous-time calibration, LiDAR-IMU calibration methods). Achievable accuracy depends on the sensors, the excitation and the target; none is claimed here.

The parameters:

  • Rotation RICR_{\text{IC}} between the camera and IMU frames: observable from any rotation, since the angular velocity of one rigid body expressed in two frames differs by exactly RICR_{\text{IC}}.
  • Translation pICp_{\text{IC}} (the lever arm): observable only when the body accelerates, because the rotation-induced acceleration at the camera, ω×(ω×p)+α×p\omega \times (\omega \times p)+\alpha \times p, is what depends on it. Small lever arms (a few centimetres) need strong rotational acceleration to be resolved.
  • Time offset tdt_{d}: observable from the time shift that best aligns the two sensors’ motion signals, again requiring rotation (a static or slow trajectory has no signal to align).
  • LiDAR-IMU: similar structure, but the “camera” measurement is a registration of point clouds; the same excitation requirements apply, and the scan-time distortion of spinning LiDARs must be modelled.
Extrinsic and time-offset calibrationExcite all rotation axes; estimate the time offset first (so that signals line up), then the rotation, then the translation (which needs acceleration and is the least observable); validate on a separate motion with an independent check.Excitation motionall axes, variedTime offsetcorrelate ratesRotationalign angular ratesTranslationneeds accelerationValidateindependent testEach stage depends on the one before; validate on data not used for fitting.
Calibrate in order; validate on independent data.
Animation of rotation estimation between two sensor frames. Twelve corresponding directions are measured in the LiDAR frame and, with noise, in the camera frame. The cross-covariance matrix of the two point sets is decomposed by singular value decomposition, which gives the best-fit rotation. The LiDAR points are rotated gradually onto the camera points and the residual error and the angle error against the true rotation fall to the noise floor.
The Kabsch method: R=V diag⁡(1, 1, det⁡(V U⊤))U⊤R=V\,\operatorname{diag} (1,\,1,\,\det (V\,U^{\top}))U^{\top} for H=ΣH=\Sigma aᵢ bbᵢ⊤=U S V⊤^{\top}=U\,S\,V^{\top}. It minimises ∑∥b\sum \Vert bᵢ − RR aᵢ∥2\Vert ^{2} and is exact up to noise, but only if the data excite all axes; a single-axis motion leaves the rotation about it unobservable. Illustrative true rotation 25∘25^{\circ} about (1, 2, 3)/14(1,\,2,\,3)/\sqrt{14} and 1 cm noise on 1 m directions. (Looping, about 5 seconds; drawn and computed for this course with illustrative values.)

Engineering intuition

If you spin the vehicle, both the gyro and the camera (through visual odometry) report the same angular velocity vector, but in different coordinates. The rotation that maps one set of vectors onto the other is the extrinsic rotation: a 3D point-alignment problem, solved exactly by the SVD (Kabsch/Arun method). The time offset is the shift that makes the two angular-rate signals overlap.

The translation is a harder and more fragile measurement: it depends on accelerations that the IMU measures with noise, and it correlates with the accelerometer bias and gravity direction. Expect a few millimetres to a centimetre of uncertainty even with good excitation, and do not trust a translation estimated from a smooth, slow trajectory.

Mathematical formulation

The angular velocities in the two frames are related by ωI(t)=RIC ωC(t−td)\omega _{I}(t)=R_{\text{IC}}\,\omega _{C}(t-t_{d}), up to noise (the sign convention of tdt_{d} must be fixed and documented). For time-aligned samples, the rotation minimising ∑∥ωI, k−R ωC, k∥2\sum \Vert \omega _{I,\,k}-R\,\omega _{C,\,k}\Vert ^{2} is found by SVD of the cross-covariance:

Rotation by SVD (Arun, Huang, Blostein 1987; Kabsch)
H=∑kωC,k ωI,k⊤=UΣV⊤\mathbf{H} = \sum_k \boldsymbol{\omega}_{C,k}\,\boldsymbol{\omega}_{I,k}^{\top} = \mathbf{U}\boldsymbol{\Sigma}\mathbf{V}^{\top}
Best-fit rotation
R=V diag⁡ ⁣(1, 1, det⁡(VU⊤))U⊤\mathbf{R} = \mathbf{V}\,\operatorname{diag}\!\left(1,\,1,\,\det(\mathbf{V}\mathbf{U}^{\top})\right)\mathbf{U}^{\top}

The time offset maximises the correlation of the rate magnitudes ∥ω∥\Vert \omega \Vert (which does not depend on the unknown rotation): t^d=arg max⁡τ ∑∥ωI(t)∥∥ωC(t−τ)∥\hat{t}_{d}=\operatorname{arg\,max} _{\tau }\,\sum \Vert \omega _{I}(t)\Vert \Vert \omega _{C}(t-\tau )\Vert after resampling to a common grid; refine to a fraction of the sample period by interpolating the correlation peak. A necessary condition for observability of the rotation is that the angular velocity vectors span three dimensions: the matrix HH must have rank 3, in practice a well-conditioned smallest singular value. Translation enters through the specific-force difference:

Lever-arm acceleration (the signal that makes translation observable)
aC=R(aI+[ω]×[ω]×p+[α]×p)\mathbf{a}_C = \mathbf{R}\left(\mathbf{a}_I + [\boldsymbol{\omega}]_{\times}[\boldsymbol{\omega}]_{\times}\mathbf{p} + [\boldsymbol{\alpha}]_{\times}\mathbf{p}\right)
Centripetal term
[ω]×[ω]×p=ω(ω⋅p)−∥ω∥2 p[\boldsymbol{\omega}]_{\times}[\boldsymbol{\omega}]_{\times}\mathbf{p} = \boldsymbol{\omega}(\boldsymbol{\omega}\cdot\mathbf{p}) - \lVert\boldsymbol{\omega}\rVert^{2}\,\mathbf{p}
RICR_{\text{IC}}, pICp_{\text{IC}}
rotation and translation between camera and IMU frames.
tdt_{d}
time offset between the sensors.
HH, UU, Σ\Sigma , VV
cross-covariance and its SVD.

Architecture and data flow

Recorded rosbag of the excitation motion (IMU at hundreds of Hz, camera at 20–30 Hz with its stamps) → calibration tool (Kalibr for camera-IMU with a target, or a LiDAR-IMU tool) → extrinsics file (RR, pp, ttd, covariance) → the VIO or LiDAR-inertial pipeline’s configuration. The same file is used by the TF publisher. The tool’s covariance output and the validation residuals are stored with it.

Real-world UAV applications

  • Setting up VIO before the first flight.
  • Checking a camera mount after a repair.
  • Calibrating LiDAR-IMU for LiDAR-inertial odometry.
  • Diagnosing why a VIO that works on one vehicle fails on another of the same type.

Implementation example

This runs as shown. Synthetic angular-rate data for a rotating vehicle: an IMU at 200 Hz and a visual-odometry angular rate at 30 Hz, with a 37 ms time offset and a fixed rotation. The code estimates the offset by correlating rate magnitudes, then the rotation by the SVD, and validates on a second, independent motion.

import numpy as np
rng = np.random.default_rng(14)
def rotvec(w):
    th = np.linalg.norm(w); K = np.array([[0, -w[2], w[1]], [w[2], 0, -w[0]], [-w[1], w[0], 0]]) / (th if th > 0 else 1)
    return np.eye(3) + np.sin(th) * K + (1 - np.cos(th)) * K @ K
R_true = rotvec(np.array([0.3, -0.5, 1.9])); t_d_true = 0.037               # camera-to-IMU rotation, and the delay of the camera stamps

def omega(t, seed):                                                         # smooth rich angular velocity: sums of sinusoids with different frequencies per axis
    r = np.random.default_rng(seed); f = r.uniform(0.3, 1.5, (3, 3)); p = r.uniform(0, 6.28, (3, 3)); a = r.uniform(0.4, 1.2, (3, 3))
    return sum(a[:, i:i + 1] * np.sin(2 * np.pi * f[:, i:i + 1] * t + p[:, i:i + 1]) for i in range(3)).T
def make(seed, T=30.0):
    t_i = np.arange(0, T, 1 / 200.0); w_i = omega(t_i, seed) + 0.01 * rng.standard_normal((t_i.size, 3))        # IMU gyro
    t_c = np.arange(1.0, T - 1.0, 1 / 30.0)
    w_c = (omega(t_c - t_d_true, seed) @ R_true) + 0.02 * rng.standard_normal((t_c.size, 3))                    # VO angular rate in the camera frame (camera stamps lag by t_d)
    return t_i, w_i, t_c, w_c
def estimate_td(t_i, w_i, t_c, w_c, span=0.1, step=0.0005):
    n_i = np.linalg.norm(w_i, axis=1); n_c = np.linalg.norm(w_c, axis=1); best = None
    for tau in np.arange(-span, span, step):                                # shift the camera stamps by tau and compare magnitudes on the camera grid
        m = np.interp(t_c - tau, t_i, n_i); c = np.corrcoef(m, n_c)[0, 1]
        if best is None or c > best[0]: best = (c, tau)
    return best[1]
def estimate_R(t_i, w_i, t_c, w_c, td):
    wi = np.stack([np.interp(t_c - td, t_i, w_i[:, a]) for a in range(3)], axis=1)
    H = w_c.T @ wi; U, S, Vt = np.linalg.svd(H); D = np.diag([1, 1, np.linalg.det(Vt.T @ U.T)])
    return Vt.T @ D @ U.T, S
d1 = make(1); td = estimate_td(*d1); R, S = estimate_R(*d1, td)
ang = lambda A, B: np.degrees(np.arccos(np.clip((np.trace(A.T @ B) - 1) / 2, -1, 1)))
print(f"time offset estimate {td * 1e3:.1f} ms (true {t_d_true * 1e3:.1f}); rotation error {ang(R, R_true):.3f} deg; singular values {np.round(S, 1)}")
d2 = make(2)                                                                 # independent validation motion, not used for fitting
wi2 = np.stack([np.interp(d2[2] - td, d2[0], d2[1][:, a]) for a in range(3)], axis=1)
res = np.sqrt(np.mean(np.linalg.norm(wi2 - d2[3] @ R.T, axis=1) ** 2)); res0 = np.sqrt(np.mean(np.linalg.norm(wi2 - d2[3], axis=1) ** 2))
print(f"validation residual of angular rates: calibrated {res:.3f} rad/s, uncalibrated {res0:.3f} rad/s")
assert abs(td - t_d_true) < 0.003 and ang(R, R_true) < 0.5 and res < 0.05 and res0 > 5 * res and S[2] > 0.1 * S[0]
Time offset by magnitude correlation, rotation by SVD alignment of angular rates, and validation on an independent motion.

Common engineering mistakes

Calibrating with a slow or planar motion

Rotation about one axis leaves the others unobservable; excite all three axes.

Estimating translation from gentle motion

It needs rotational acceleration; expect a large uncertainty otherwise.

Fitting and validating on the same data

Validate on a separate motion or against an external reference (a motion-capture trajectory).

Ignoring the time offset

A few milliseconds of offset looks like a rotation error during fast turns.

Sign convention confusion

Define extrinsics as TICT_{\text{IC}} or TCIT_{\text{CI}} and the offset sign in the file; apply consistently.

Failure modes

Failure modes, symptoms and causes
Failure modeHow it shows upLikely cause
VIO scale driftBias in position estimatesWrong translation or time offset
Divergence in fast rotationsTracking loss at high angular ratesTime offset or rotation error
Calibration differs between runsParameters do not repeatInsufficient excitation; poorly observable parameters
Good fit but poor flightResiduals are small, flight badOver-fitting to a motion not representative of flight

Debugging approach

  1. Plot the angular-rate signals before and after alignment.
  2. Check the singular values of the cross-covariance: a small third value means poor excitation.
  3. Repeat the calibration on separate recordings and compare the spread.
  4. Test the result in VIO with an external reference trajectory.

Practical exercises

  1. Reduce the motion to rotation about one axis in the example and report the singular values and the rotation error.
  2. Compute the acceleration signal at a 5 cm lever arm for ω=3 rad/s\omega =3\,\mathrm{rad}/\mathrm{s} and α=20 rad/s2\alpha =20\,\mathrm{rad}/\mathrm{s}^{2}; compare with accelerometer noise of 0.05 m/s20.05\,\mathrm{m}/\mathrm{s}^{2}.
  3. Add a gyro bias of 0.02 rad/s and see how it affects the rotation estimate.
  4. Design the validation procedure for the extrinsics of your vehicle.

Interview questions

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

Question 1

What excitation is needed to calibrate camera-IMU extrinsics?

Model answer

Rotation about all three axes for the rotation and time offset, and rotational acceleration (plus translation) for the lever arm; slow or planar motion leaves parameters unobservable.

Question 2

How can you estimate the time offset without knowing the rotation?

Model answer

Correlate the magnitudes of the angular rates of the two sensors, which are independent of the rotation between the frames, and find the shift that maximises the correlation.

Question 3

Why is validation on separate data necessary?

Model answer

A calibration can fit its own motion well while being wrong for others (over-fitting or low observability); independent data or an external reference reveals this.

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

M7-T17: Interface Control Documents

Write an Interface Control Document with message, rate, frame, units, timing and failure behaviour per interface.