Part VI: Motion, Location, and Inertial Intelligence
Chapter 23: Inertial Sensors and Motion Signals

IMU calibration (bias, scale, misalignment)

"They told me the device measures acceleration. What it actually measures is acceleration, plus a lie it tells consistently, times a number that is almost one, rotated by an angle nobody soldered on purpose."

A Recalibrated AI Agent

The Big Picture

An uncalibrated inertial measurement unit (IMU) does not report the physical quantity you asked for. It reports a distorted, offset, cross-coupled version of it. A gyroscope that reads \(0.6\,^\circ/\text{s}\) while sitting perfectly still will, if you integrate it for orientation as Chapter 24 does, drift more than \(2000^\circ\) over a single hour. The good news is that most of the error is systematic and repeatable, which means it is correctable. Calibration is the step that turns raw counts into physically meaningful acceleration and angular rate, and it is the single highest-leverage preprocessing action you can take before any downstream orientation, dead-reckoning, or activity model sees the data. This section builds the deterministic error model, shows how to estimate its parameters from simple static and rotation experiments, and explains when the effort pays off.

This section assumes you know what accelerometers and gyroscopes measure (Section 23.1), the body and world coordinate frames they live in (Section 23.2), and the basic idea of a linear measurement model with additive noise from Chapter 2. The random part of the error, characterized by Allan variance and noise density, is a companion topic; here we target the deterministic part that a fixed correction can remove.

The deterministic error model

For each triaxial sensor (accelerometer or gyroscope), the standard affine model relates the true physical vector \(\mathbf{s}\) to the raw measured vector \(\mathbf{y}\) through three effects, in order of magnitude for a typical consumer microelectromechanical systems (MEMS) part: bias, scale-factor error, and axis misalignment. Written compactly,

$$\mathbf{y} = M\,\mathbf{s} + \mathbf{b} + \boldsymbol{\varepsilon}, \qquad M = \begin{bmatrix} s_x & m_{xy} & m_{xz} \\ m_{yx} & s_y & m_{yz} \\ m_{zx} & m_{zy} & s_z \end{bmatrix}.$$

Here \(\mathbf{b}\) is the additive bias (also called offset or, for gyroscopes, zero-rate output), the diagonal of \(M\) holds the scale factors \(s_x, s_y, s_z\) that should each be exactly 1 but are typically \(1 \pm 0.01\) to \(1 \pm 0.05\), the off-diagonal entries of \(M\) capture misalignment (the sensitive axes are not perfectly orthogonal, and the sensor triad is not perfectly aligned to the case), and \(\boldsymbol{\varepsilon}\) is the zero-mean random noise we are not trying to remove here. Calibration means estimating \(M\) and \(\mathbf{b}\), then recovering the corrected estimate at run time by inverting the affine map:

$$\hat{\mathbf{s}} = M^{-1}(\mathbf{y} - \mathbf{b}).$$

Why each term matters. Bias dominates gyroscope drift: it integrates directly into a linearly growing angle error, so a constant \(0.6\,^\circ/\text{s}\) bias is catastrophic for dead reckoning. Scale error matters most under large signals: a 3 percent accelerometer scale error is invisible at rest but corrupts a \(4\,\text{g}\) impact by more than \(1\,\text{m/s}^2\). Misalignment leaks motion between axes, so a pure yaw rotation shows up partly as false pitch and roll, which is exactly the kind of error a naive step counter or gesture model cannot recover from.

Key Insight

Bias and misalignment errors are constant with respect to the signal, while scale errors are proportional to the signal. This is why a device can pass a flat-on-the-table sanity check (small signals, scale error hidden) and still be badly calibrated for high-dynamic motion. Always validate calibration across the amplitude range you actually operate in, not just at rest.

Accelerometer calibration: the sphere trick

The accelerometer has a free, always-available reference: gravity. When the device is held still in any orientation, the true specific-force vector has magnitude \(g \approx 9.81\,\text{m/s}^2\). Geometrically, the set of true static readings across all orientations traces out a sphere of radius \(g\) centered at the origin. An uncalibrated sensor instead traces an ellipsoid that is offset (bias), stretched unevenly (scale), and tilted (misalignment). Calibration is therefore an ellipsoid-fitting problem: collect static samples in a variety of orientations, fit the ellipsoid, and find the affine transform that maps it back to the unit sphere scaled by \(g\).

Concretely, we solve for \(M\) and \(\mathbf{b}\) that minimize the constraint residual over \(N\) static poses:

$$\min_{M,\,\mathbf{b}} \; \sum_{i=1}^{N} \Big( \lVert M^{-1}(\mathbf{y}_i - \mathbf{b}) \rVert - g \Big)^2.$$

Six well-separated orientations (roughly each face of the device pointing up and down) are the classic minimum, but a dozen varied poses fit the misalignment terms far more robustly. Crucially this needs no turntable and no lab equipment, which is why it is the calibration a phone runs silently in your pocket.

import numpy as np

def calibrate_accel(static_samples, g=9.81):
    """Fit bias + symmetric scale/misalignment so |M^-1 (y - b)| = g.
    static_samples: (N, 3) accel readings, device held still in varied poses."""
    Y = np.asarray(static_samples, float)
    # Ellipsoid fit: y^T A y + y^T k + d = 0, solved as linear least squares.
    x, y, z = Y[:, 0], Y[:, 1], Y[:, 2]
    D = np.column_stack([x*x, y*y, z*z, 2*x*y, 2*x*z, 2*y*z, 2*x, 2*y, 2*z,
                         np.ones_like(x)])
    _, _, Vt = np.linalg.svd(D, full_matrices=False)
    p = Vt[-1]                                  # null-space = ellipsoid params
    A = np.array([[p[0], p[3], p[4]],
                  [p[3], p[1], p[5]],
                  [p[4], p[5], p[2]]])
    k, d = p[6:9], p[9]
    b = -np.linalg.solve(A, k)                  # ellipsoid center = bias
    # Whitening transform maps the ellipsoid back onto a sphere of radius g.
    scale = A / (b @ A @ b - d)
    L = np.linalg.cholesky(scale) * g
    return L.T, b                               # returns (M_inv-like, bias)

M_corr, bias = calibrate_accel(collected_static_poses)
corrected = (raw_accel - bias) @ M_corr        # apply at run time
Ellipsoid-fit accelerometer calibration from static poses only. The calibrate_accel routine recovers bias as the ellipsoid center and the combined scale/misalignment as its whitening transform, then the last line applies the correction to a live stream. No turntable, motor, or reference instrument is required.

The code above illustrates the core estimation, but production systems rarely reimplement the ellipsoid solver. The next callout shows why.

Library Shortcut

Hand-rolling the SVD ellipsoid fit, pose selection, and outlier rejection is roughly 60 to 80 lines to get right, plus a numerically stable inverse. Libraries such as imucal (its FerrarisCalibration and TurntableCalibration classes) or scikit-kinematics reduce a full six-position accelerometer-plus-gyro calibration to about 5 lines: load the labeled recording, call compute(), and serialize the resulting calibration object to JSON for the fleet. They handle the pose bookkeeping, temperature fields, and the affine inversion so you correct with a single cal.calibrate(data) call.

Gyroscope calibration and the awkwardness of angular rate

Gyroscopes have no free reference analogous to gravity, because there is no constant, always-present angular-rate field to lean on. This splits gyro calibration into two tractable halves. The bias is easy: hold the device perfectly still and average each axis over several seconds; the mean is the zero-rate offset \(\mathbf{b}_{\text{gyro}}\). Because this bias drifts slowly with temperature and even between power cycles, good systems re-estimate it opportunistically whenever they detect the device is stationary (a technique you will meet again as the zero-velocity update in Chapter 24).

The scale and misalignment are harder because they need a known rotation. The lab answer is a rate table or turntable that commands a precise angular velocity. The field answer is to rotate the device by a known total angle (for example a clean 90 or 360 degrees against a mechanical stop) and require that the integrated gyro reading matches that angle; the ratio of measured to true integrated angle gives the scale factor per axis. Misalignment terms follow from rotating about one axis and measuring the spurious integrated rate on the others.

Practical Example: the drifting warehouse forklift

A robotics team deployed autonomous forklifts using a low-cost MEMS IMU for short-horizon dead reckoning between lidar fixes. Straight aisles tracked fine, but every tight 90-degree turn accumulated a heading error, and after a shift the vehicles were consistently biased clockwise. The culprit was not noise: it was a \(-2.1\) percent gyroscope z-axis scale error, so every commanded quarter-turn was under-integrated by nearly two degrees. A one-time turntable calibration of the scale matrix, stored per unit and applied with \(M^{-1}\), removed the systematic heading drift and cut the reliance on lidar re-localization by a third. The random noise never changed; only the repeatable lie did, and that was the whole problem.

Temperature, per-unit variation, and when to bother

The affine parameters are not truly constant. MEMS bias and scale drift measurably with temperature, so aerospace-grade and automotive parts ship with a factory temperature model, \(\mathbf{b}(T) = \mathbf{b}_0 + \mathbf{c}_1 T + \mathbf{c}_2 T^2\), and the best consumer pipelines log die temperature alongside every sample and fit a low-order correction. Calibration parameters are also per unit: two chips from the same reel differ enough that a single fleet-wide constant leaves visible residual bias. This is a data-engineering concern as much as a signal one; treat the calibration object as versioned metadata attached to a specific serial number, and beware of training a model on device A's corrected data and testing on device B's, which is a subtle leakage trap of the kind Chapter 5 warns about.

When is it worth it? If you only classify coarse activities (walking versus sitting) from a single well-placed sensor, downstream models often absorb a modest miscalibration and the return is small. If you integrate the signal over time (orientation, velocity, position), calibrate aggressively, because integration converts a small constant bias into an unbounded error. The magnetometer, mentioned in Section 23.1, has its own calibration story dominated by hard-iron and soft-iron distortion; it uses the same ellipsoid-fitting machinery as the accelerometer but references the local geomagnetic field instead of gravity.

Exercise

You record a gyroscope perfectly still for 10 seconds and measure a mean of \(0.45\,^\circ/\text{s}\) on the z-axis. (a) If you integrate this raw signal to estimate heading, how large is the accumulated error after 5 minutes? (b) After removing the estimated bias, the residual random walk has an Allan-deviation noise density of \(0.01\,^\circ/\sqrt{\text{s}}\); estimate the standard deviation of the heading error after the same 5 minutes and compare the two magnitudes. Comment on which error the calibration eliminated and which it did not.

Self-Check

What's Next

In Section 23.5, we put the newly trustworthy accelerometer signal to work: detecting individual footsteps and characterizing gait. Calibration is what lets a step detector rely on the true vertical acceleration peak rather than an axis-leaked artifact, so the correction you built here is the quiet foundation under everything that follows.