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

UAV Systems Engineering · Module 3

M3-T6: Error-State Kalman Filter

Keep the big nonlinear state outside the filter and estimate only its small error: that keeps rotations on the manifold and the linearisation honest.

  • Lesson 6 of 16
  • Sensor Fusion and State Estimation
  • Established engineering
  • About 5 minutes to read

Autopilots such as PX4’s EKF2 are error-state filters. The reason is rotation: a three-parameter error stays small and well-behaved even when the attitude itself is arbitrary. This lesson builds a complete attitude ESKF in thirty lines.

Learning objectives

After this lesson, you should be able to:

  • Explain the nominal, error and true states and the injection and reset steps.
  • Write the attitude error-state propagation and gravity measurement update.
  • Run an ESKF that estimates attitude and gyro bias, and check it against truth.
  • State what remains unobservable (yaw from gravity alone).

Conceptual explanation

An error-state (indirect) filter splits the state into a nominal state x^\hat{x} propagated with the full nonlinear equations and an error state δx\delta x, small, Gaussian and estimated by a linear filter. Measurements update δx\delta x; the estimate is then injected into the nominal state and δx\delta x is reset to zero (with a small covariance correction).

Error-state filter cycleThe nominal state is integrated nonlinearly from IMU data. The error state covariance is propagated with the linearised error dynamics. A measurement produces an error-state estimate, which is injected into the nominal state; the error is reset to zero.Nominal stateintegrate IMUError covarianceF P Fᵀ + QMeasureupdate δxInjectx ← x ⊞ δxResetδx = 0rotations: the error is a 3-vector, no constraint to maintain
The error-state loop.
Animation of an error-state Kalman filter for a heading angle with an unknown gyro bias. The nominal heading is integrated from the biased gyro and drifts; every half second a heading measurement arrives, the filter estimates the small error state of heading and bias, injects the correction into the nominal state and resets the error state to zero. Charts show the heading error shrinking with each injection and the bias estimate converging to the true bias.
ESKF: the nominal state is integrated with the large-signal model; the filter estimates only the small error δx\delta x (heading error, bias error), which is injected into the nominal state after each update and then reset to zero. The error stays small, so linearisation is accurate. Heading and gyro bias, measurement noise 1∘1^{\circ}, true bias 2°/s. (Looping, about 6 seconds; drawn and computed for this course with illustrative values.)

Engineering intuition

The nominal rotation can be anything; the error between estimate and truth is small if the filter works, so its first-order (small-angle) model is accurate where it matters, around zero. A quaternion or rotation matrix in the state would need a constraint (unit norm) that a linear Kalman update does not respect; a three-component error rotation vector has none. Zero is also where the error-state dynamics are best conditioned, and where they cost least to linearise.

Mathematical formulation

Attitude RR (body to world) with true R=R^ Exp(δθ)R=\hat{R}\,\mathrm{Exp} (\delta \theta ) (error in the body frame), gyro bias error δb\delta b and measured rate ωm\omega _{m}:

Error-state propagation
δθ˙=−[ωm−b^]× δθ−δb−ng\dot{\delta\boldsymbol\theta} = -[\boldsymbol\omega_m - \hat{\mathbf b}]_{\times}\,\delta\boldsymbol\theta - \delta\mathbf b - \mathbf n_g
δb˙=nb\dot{\delta\mathbf b} = \mathbf n_b
Discrete transition matrix
Fd=[Exp ⁣(−(ωm−b^)Δt)−I Δt0I]\mathbf F_d = \begin{bmatrix}\mathrm{Exp}\!\left(-(\boldsymbol\omega_m - \hat{\mathbf b})\Delta t\right) & -\mathbf I\,\Delta t\\ \mathbf 0 & \mathbf I\end{bmatrix}
Covariance propagation
P←Fd P Fd⊤+Qd\mathbf P \leftarrow \mathbf F_d\,\mathbf P\,\mathbf F_d^{\top} + \mathbf Q_d

A gravity-direction measurement (accelerometer with negligible linear acceleration), z=RT z^z=R^{T}\,\hat{z} + noise, predicted as h^=R^T z^\hat{h}=\hat{R}^{T}\,\hat{z}. To first order the innovation is z−h^≈[h^]z-\hat{h}\approx [\hat{h}]ₓ δθ\delta \theta , so HH = [ [h^\hat{h}]ₓ 0 ]. Standard Kalman update gives δx=K(z−h^)\delta x=K(z-\hat{h}); then:

Injection and reset
R^←R^ Exp(δθ^),b^←b^+δb^,δx←0\hat{\mathbf R} \leftarrow \hat{\mathbf R}\,\mathrm{Exp}(\delta\hat{\boldsymbol\theta}), \qquad \hat{\mathbf b} \leftarrow \hat{\mathbf b} + \delta\hat{\mathbf b}, \qquad \delta\mathbf x \leftarrow \mathbf 0
Covariance reset (reset Jacobian G)
P←G P G⊤,G=I−12[δθ^]×\mathbf P \leftarrow \mathbf G\,\mathbf P\,\mathbf G^{\top}, \qquad \mathbf G = \mathbf I - \tfrac{1}{2}[\delta\hat{\boldsymbol\theta}]_{\times}

Gravity measures only roll and pitch: the rotation about the vertical (yaw) is unobservable from it, so yaw drifts at the unestimated part of the gyro bias until a magnetometer, vision or GNSS heading supplies it (lessons M3-T12 and M2-T11). The same structure extends to position, velocity and accelerometer bias for the full navigation filter (Solà 2017).

δθ\delta \theta
attitude error as a rotation vector (small).
δb\delta b
gyro bias error.
[·]ₓ
skew-symmetric (cross-product) matrix.
Exp
exponential map from rotation vector to rotation matrix.

Architecture and data flow

PX4’s EKF2 follows this pattern for full navigation: it propagates a nominal state with delayed-horizon IMU data and estimates error states with GNSS, barometer, magnetometer and external-vision updates, with innovation gates (lesson M6-T5). The accelerometer correction of roll and pitch comes via the velocity and position measurements rather than a gravity vector, since the vehicle accelerates.

Real-world UAV applications

  • Attitude estimation on every autopilot.
  • The core of visual-inertial and LiDAR-inertial filters (OpenVINS, FAST-LIO2).
  • Estimating gyro bias in flight.
  • Any estimator with rotations in its state.

Implementation example

This runs as shown: an attitude ESKF with gravity updates tracks a rotating body with a gyro bias, keeping tilt error below half a degree and estimating the bias, whereas pure gyro integration drifts by tens of degrees.

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 run(use_update, seed=0, n=6000, dt=0.01):
    rng = np.random.default_rng(seed); g, sg, sa = 9.81, 0.002, 0.05
    bg_true = np.array([0.02, -0.01, 0.015]); z_up = np.array([0, 0, 1.0])
    Rt = np.eye(3); Rh = expm(np.array([0.2, -0.15, 0.0])); bh = np.zeros(3)         # initial attitude error: 0.25 rad = 14 degrees
    P = np.diag([0.3**2] * 3 + [0.05**2] * 3); Q = np.diag([sg**2 * dt] * 3 + [(1e-5) ** 2 * dt] * 3); Rm = np.eye(3) * (sa / g) ** 2
    tilt = []
    for k in range(n):
        w = np.array([0.3 * np.sin(0.01 * k), 0.2 * np.cos(0.013 * k), 0.1]); Rt = Rt @ expm(w * dt)
        gm = w + bg_true + sg / np.sqrt(dt) * rng.standard_normal(3)
        Rh = Rh @ expm((gm - bh) * dt)                                                # nominal propagation (nonlinear)
        F = np.eye(6); F[:3, :3] = expm(-(gm - bh) * dt); F[:3, 3:] = -np.eye(3) * dt; P = F @ P @ F.T + Q   # error-state covariance propagation
        if use_update and k % 5 == 0:
            z = Rt.T @ z_up + (sa / g) * rng.standard_normal(3); h = Rh.T @ z_up       # gravity direction measured vs predicted
            H = np.zeros((3, 6)); H[:, :3] = hat(h)
            K = P @ H.T @ np.linalg.inv(H @ P @ H.T + Rm); dx = K @ (z - h)
            Rh = Rh @ expm(dx[:3]); bh = bh + dx[3:]                                  # inject, then reset the error state
            IKH = np.eye(6) - K @ H; P = IKH @ P @ IKH.T + K @ Rm @ K.T
        tilt.append(np.degrees(np.arccos(np.clip((Rt.T @ z_up) @ (Rh.T @ z_up), -1, 1))))
    return np.array(tilt), bh, bg_true

tilt_f, bh, bg = run(True); tilt_g, _, _ = run(False)
print(f"tilt error: filter max after 10 s {tilt_f[1000:].max():.2f} deg, gyro-only at the end {tilt_g[-1]:.1f} deg; bias estimate {np.round(bh, 4)} vs {bg}")
assert tilt_f[1000:].max() < 0.5 and tilt_g[-1] > 10
assert np.all(np.abs(bh - bg) < 0.005)
Attitude ESKF: gravity corrects tilt and gyro bias; gyro integration alone drifts.

Common engineering mistakes

Linear accelerometer-gravity update while accelerating

The gravity assumption fails in manoeuvres; gate it or use velocity aiding.

Forgetting the reset Jacobian

Small but real covariance error for large corrections.

Mixing error conventions (body versus world)

The sign and frame of δθ\delta \theta must match the FF and HH matrices.

Estimating yaw from gravity

Not observable: it needs another heading source.

Non-symmetric P

Use the Joseph form.

Failure modes

Failure modes, symptoms and causes
Failure modeHow it shows upLikely cause
Tilt error during aggressive manoeuvresAttitude wanders when acceleratingGravity update trusted despite linear acceleration
Yaw driftHeading slowly rotatesNo heading source
Bias not convergingPersistent driftInsufficient rotation excitation or QQ too small
Divergence after a large initial errorFilter fails to convergeInitial error beyond the linearisation region

Debugging approach

  1. Compare against truth on synthetic data as above.
  2. Plot bias estimates and covariance.
  3. Check innovations of the gravity update against their expected variance.
  4. Test with injected linear accelerations.

Practical exercises

  1. Add position and velocity states with accelerometer bias and GNSS updates.
  2. Show numerically that yaw error grows while tilt stays bounded.
  3. Remove the reset Jacobian and compare covariance consistency.
  4. Replace the rotation matrix by a quaternion nominal state.

Interview questions

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

Question 1

Why use an error-state filter for attitude?

Model answer

The error is a small three-vector that needs no constraint and is well approximated by a linear model, while the nominal rotation is propagated nonlinearly and kept on the manifold.

Question 2

What are injection and reset?

Model answer

After a measurement the estimated error is added to the nominal state (injection) and the error state is set to zero with a covariance correction (reset).

Question 3

What does the gravity measurement observe?

Model answer

Roll and pitch only; yaw about the vertical direction is unobservable from gravity.

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-T7: IMU noise and bias modelling

Read an Allan deviation plot and set noise densities and random walks.