Skip to content

Dead reckoning

Estimating your current position from a previously known position, using how you have moved since then.

\[ \text{new position} = \text{old position} + \text{estimated movement} \]

Dead reckoning with an IMU means estimating where you are now by starting from a known state and continuously integrating the IMU measurements.

Gyroscope ──► Orientation
Accelerometer ────┤
          Remove gravity
             Acceleration
                  │ integrate
               Velocity
                  │ integrate
               Position

IMU

  • Gyroscope → angular velocity [rad/s]
  • Accelerometer → specific force [m/s²]
  • Sometimes a magnetometer → magnetic heading

The important complication is that the accelerometer measurements are in the IMU/body coordinate frame, while navigation usually needs acceleration in a fixed world/navigation frame.

Orientation estimate

Using gyro to update orientation

\(\theta_{k+1} = \theta_k + \omega_k\Delta t\)

Quaternion

Transform acceleration

  • Rotate acceleration from body frame to world frame
  • Remove gravity
  • Integrated acceleration to velocity
  • Integrated velocity to position
  • \(a_{world} = R_{body\rightarrow world}a_{imu}\)
1
2
3
4
5
6
7
8
9
IMU measurement
      │ rotate
 World frame
      │ remove gravity ≈ 9.81 m/s²
 Linear acceleration ≈ 0

Acceleration to velocity

\(v_{k+1}=v_k+a_k\Delta t\)

Velocity to position

\(p_{k+1}=p_k+v_k\Delta t+\frac12a_k\Delta t^2\)

\(\boxed{\text{IMU} \rightarrow \text{orientation} \rightarrow \text{linear acceleration} \rightarrow \text{velocity} \rightarrow \text{position}}\)


Drift noise and integrator

The most influence imu error on dead reckoning

Priority Error Sensor What happens Main compensation
🔴 1 Bias Accel + Gyro Constant/slow offset gets integrated Calibration + EKF bias estimation
🔴 2 White noise Accel + Gyro Random measurement variation accumulates Filtering + sensor fusion
🔴 3 Bias drift / random walk Accel + Gyro Bias slowly changes over time EKF + external measurements
🟠 4 Temperature drift Accel + Gyro Bias changes as IMU temperature changes Temperature calibration/compensation

Accelerometer bias

The sensor might report:

\[ a_{measured} = a_{true} + b_a \]

For example, when true acceleration is zero:

\[ a_{true} = 0 \]

but the IMU reports:

\[ a_{measured} = 0.02\;m/s^2 \]

Dead reckoning integrates it twice:

\[ \boxed{0.02 \xrightarrow{\int} \text{velocity error} \xrightarrow{\int} \text{position error}} \]

Position error grows roughly with:

\[ e_p \propto t^2 \]

So this is very important.

Gyro bias

The gyro might say:

\[ \omega_z = 0.1^\circ/s \]

even though the robot is not rotating.

Because you integrate the gyro:

\[ \omega \xrightarrow{\int} q \]

your quaternion slowly rotates even while the robot is stationary.

This produces an even more dangerous chain:

gyro bias
wrong quaternion
wrong body → world rotation
gravity points slightly wrong
gravity looks like acceleration
wrong velocity
wrong position

For a first simulator, simplify the entire IMU error model to:

\[ \boxed{\text{measurement} = \text{truth} + \text{bias} + \text{white noise}} \]

For the accelerometer:

\[ a_m = a_{true} + b_a + n_a \]

and the gyro:

\[ \omega_m = \omega_{true} + b_g + n_g \]

Once you understand what those four terms—truth, bias, white noise, and integration—do to dead reckoning, add slowly changing bias. That is enough to understand most of the fundamental IMU navigation problem.

Can bias be calibrated?

Bias can be calibrated, but it can also change while the IMU is operating.

  • Constant bias: Keep the IMU stationary, measure its average output, and subtract that offset.
  • Temperature-dependent bias: Calibrate at several temperatures or use the IMU temperature measurement for compensation.
  • Bias drift: Time, temperature, vibration, and sensor aging can slowly change the bias, so initial calibration cannot remove it completely.
  • In-run estimation: An EKF can continuously estimate bias using references such as GNSS, wheel odometry, cameras, or known stationary periods.

A simple changing-bias model is:

\[ b_{k+1} = b_k + w_b \]

where \(w_b\) is a small random change. The measurement model becomes:

\[ z_k = x_k + b_k + n_k \]

In practice, calibrate the initial bias and then estimate how it changes during operation. Without an external reference or a known stationary period, bias and real motion can be difficult or impossible to distinguish.


Demo

install c4dynamics

pip install c4dynamics

This demo uses C4Dynamics to model the robot as a rigid body and simulate noisy, biased IMU measurements. The dead-reckoning calculations remain explicit so the resulting position and heading drift can be compared with the simulated ground truth.

Scenario

The robot accelerates from rest for two seconds. It then travels at a constant speed while turning left at \(18^\circ/s\). The simulated IMU adds white noise and constant accelerometer and gyroscope biases to the true motion.

The dead-reckoning estimate knows only its initial state. It integrates the noisy gyroscope measurement to estimate yaw, rotates the measured acceleration into the world frame, and integrates acceleration twice to estimate position.

hello_dead.py
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np
import c4dynamics as c4d


np.random.seed(7)  # Reproducible sensor noise

dt = 0.01
time = np.arange(0.0, 10.0, dt)

robot = c4d.rigidbody()
imu = c4d.sensors.imu(
    acc_std=0.02,                         # accelerometer noise [m/s²]
    gyro_std=0.005,                       # gyroscope noise [rad/s]
    acc_bias=[0.01, -0.008, 0.0],         # acceleration bias creates position drift
    gyro_bias=[0.0, 0.0, np.deg2rad(0.2)],  # yaw-rate bias creates yaw drift
    dt=dt,
)

# Dead reckoning starts at the known initial state.
dead_x = dead_y = dead_vx = dead_vy = dead_yaw = 0.0
true_x, true_y, true_yaw = [], [], []
estimated_x, estimated_y, estimated_yaw = [], [], []

for t in time:
    # Accelerate, then follow a constant-speed turn.
    speed = min(t, 2.0)
    robot.r = 0.0 if t < 2.0 else np.deg2rad(18.0)
    robot.psi += robot.r * dt
    robot.vx = speed * np.cos(robot.psi)
    robot.vy = -speed * np.sin(robot.psi)  # C4Dynamics navigation axes
    robot.x += robot.vx * dt
    robot.y += robot.vy * dt

    ax, ay, _az, _p, _q, measured_r = imu.measure(robot, t=t)

    dead_yaw += measured_r * dt
    c, s = np.cos(dead_yaw), np.sin(dead_yaw)
    world_ax = c * ax - s * ay
    world_ay = -s * ax - c * ay
    dead_vx += world_ax * dt
    dead_vy += world_ay * dt
    dead_x += dead_vx * dt
    dead_y += dead_vy * dt

    true_x.append(robot.x)
    true_y.append(robot.y)
    true_yaw.append(robot.psi)
    estimated_x.append(dead_x)
    estimated_y.append(dead_y)
    estimated_yaw.append(dead_yaw)

fig, (trajectory, yaw_plot) = plt.subplots(1, 2, figsize=(12, 5))
trajectory.plot(true_x, true_y, label="Ground truth", linewidth=2)
trajectory.plot(estimated_x, estimated_y, "--", label="Dead reckoning")

# Add heading arrows to both paths.
step = len(time) // 12
for x, y, yaw, color in (
    (true_x, true_y, true_yaw, "C0"),
    (estimated_x, estimated_y, estimated_yaw, "C1"),
):
    trajectory.quiver(
        x[::step], y[::step], np.cos(yaw[::step]), -np.sin(yaw[::step]),
        color=color, angles="xy", scale_units="xy", scale=2.5, width=0.006,
    )

trajectory.set(xlabel="x [m]", ylabel="y [m]", title="Trajectory and yaw")
trajectory.axis("equal")
trajectory.grid()
trajectory.legend()

yaw_plot.plot(time, np.rad2deg(true_yaw), label="Ground truth")
yaw_plot.plot(time, np.rad2deg(estimated_yaw), "--", label="Dead reckoning")
yaw_plot.set(xlabel="time [s]", ylabel="yaw [deg]", title="Yaw drift")
yaw_plot.grid()
yaw_plot.legend()

fig.tight_layout()
fig.savefig(Path(__file__).parent.parent / "images/dead_reckoning.png", dpi=150)
plt.show()

Result

The estimates begin close to the ground truth, then separate as noise and bias are integrated. The heading arrows show how gyro bias changes the estimated orientation; that orientation error also rotates acceleration into the wrong world direction and increases the position error.

Ground truth and dead-reckoning trajectory with yaw drift