Lesson 5 · Inertial state estimation · 30–45 minutes first study · 15 minutes revision

The noise preintegration carries

One covariance, propagated a sample at a time, and why it decides who wins.

Preintegrated deltas describe the IMU prediction; their 9×9 covariance describes its uncertainty. Propagate that covariance one sample at a time, then use it to weight the IMU residual against other measurements.

0 · Warm-up, from memory

The position line of eq. (31), from lesson 2. The noise update in this lesson has exactly its shape.

From memory: write the position line of eq. (31), with noise dropped.

1 · Why a covariance at all

Least squares weights each residual by its inverse covariance. Forster calls modelling it "of paramount importance… the inverse noise covariance is used to weight the terms in the optimization."Forster et al., On-Manifold Preintegration…, §VI-B. glass-lio does exactly that. The IMU factor's weight is the inverse of the preintegration covariance, while each LiDAR point is whitened by lidar_sigma:tight_registration.cpp, lines 118–129. glass-lio also adds the previous state's own uncertainty, \(\mathbf{J}_i\mathbf{P}_i\mathbf{J}_i^{\top}\), which marginalizes an independent Gaussian perturbation of x_i from this one linearized factor. The LiDAR side is at lines 64–70.

const Eigen::Matrix<double, 9, 9> sigma_eff =
  pre.covariance() + Ji * xi_cov * Ji.transpose();
Eigen::Matrix<double, 9, 9> imu_information =
  sigma_eff.inverse() * params.imu_prior_weight;

This matrix helps set the balance with LiDAR. The covariance inflation is exact for that local linear Gaussian model with independent noise; it is not the marginalization of every historical factor or a proof of consistency of the whole estimator.

2 · The noise is linear, so propagate it linearly

Forster writes each delta as "the (to-be-estimated) state 'plus' a random noise", stacked as \(\boldsymbol{\eta}^{\Delta} = [\delta\boldsymbol{\phi};\ \delta\mathbf{v};\ \delta\mathbf{p}]\), and shows that, to first order, this is "a linear function of the IMU measurement noise". That makes the covariance "a simple linear propagation". Better still, it can be done iteratively: "as a new IMU measurement arrive[s] we only update \(\Sigma_{ij}\), rather than recomputing it from scratch."Forster §VI-A (eq. 38) and §VI-B (eqs. 39–43). The iterative form is Appendix IX-A.

3 · One sample at a time

With \(\mathbf{a}_k \doteq \tilde{\mathbf{a}}_k - \mathbf{b}^{a}\) and the raw IMU noise \(\boldsymbol{\eta}^{d}_k = [\boldsymbol{\eta}^{gd}_k;\ \boldsymbol{\eta}^{ad}_k]\), each sample updates the noise, and so the covariance, like this:Forster Appendix IX-A, eqs. (59)–(63), written there from \(j-1\) to \(j\); here from \(k\) to \(k+1\). \(\Delta\tilde{\mathbf{R}}_{k,k+1}\) is this step's rotation, and \(\Delta\tilde{\mathbf{R}}_{ik}\) the rotation accumulated before it.

$$ \begin{aligned} \boldsymbol{\eta}^{\Delta}_{i,k+1} &= \mathbf{A}_k\,\boldsymbol{\eta}^{\Delta}_{ik} + \mathbf{B}_k\,\boldsymbol{\eta}^{d}_k\\ \Sigma_{i,k+1} &= \mathbf{A}_k\,\Sigma_{ik}\,\mathbf{A}_k^{\top} + \mathbf{B}_k\,\Sigma_{\eta}\,\mathbf{B}_k^{\top}, \qquad \Sigma_{ii} = \mathbf{0} \end{aligned} \tag{63} $$
$$ \mathbf{A}_k = \begin{bmatrix} \Delta\tilde{\mathbf{R}}_{k,k+1}^{\top} & \mathbf{0} & \mathbf{0}\\ -\Delta\tilde{\mathbf{R}}_{ik}\,\mathbf{a}_k^{\wedge}\,\Delta t & \mathbf{I} & \mathbf{0}\\ -\tfrac{1}{2}\Delta\tilde{\mathbf{R}}_{ik}\,\mathbf{a}_k^{\wedge}\,\Delta t^{2} & \mathbf{I}\,\Delta t & \mathbf{I} \end{bmatrix} \qquad \mathbf{B}_k = \begin{bmatrix} \mathbf{J}_r^{k}\,\Delta t & \mathbf{0}\\ \mathbf{0} & \Delta\tilde{\mathbf{R}}_{ik}\,\Delta t\\ \mathbf{0} & \tfrac{1}{2}\Delta\tilde{\mathbf{R}}_{ik}\,\Delta t^{2} \end{bmatrix} $$

It starts at zero because an empty interval has measured nothing yet. The raw noise covariance \(\Sigma_{\eta}\) comes from the datasheet densities, turned into discrete-time noise exactly as in lesson 2: \(\sigma^{2}/\Delta t\).

Here \(\mathbf{J}_r^k=\mathbf{J}_r((\tilde{\boldsymbol{\omega}}_k-\mathbf{b}^g)\Delta t)\) is the right Jacobian of SO(3): it converts an additive error in a rotation vector into a small right-multiplied rotation. It tends to \(\mathbf{I}\) near zero.

The covariance recursion assumes the new measurement noise is independent of the accumulated error. The densities use the convention \(Q_d=\operatorname{diag}(\sigma_g^2\mathbf I,\sigma_a^2\mathbf I)/\Delta t\). This 9×9 matrix conditions on the chosen integration biases; it does not include their uncertainty or random walk.

4 · Read it like eq. (31)

Don't memorise A and B as matrices. Read them row by row, and they're the warm-up's equation, linearised:

glass-core builds exactly these blocks, term for term:preintegration.cpp, lines 40–68. It runs before the state update in the same loop: "it linearises about the CURRENT state, so it needs dR before we advance it." That's the update-order trap from lesson 2, again.

A.block<3, 3>(0, 0) = dRk.matrix().transpose();
A.block<3, 3>(3, 0) = -dR_mat * a_hat * dt;
A.block<3, 3>(6, 0) = -0.5 * dR_mat * a_hat * dt * dt;
A.block<3, 3>(6, 3) = Eigen::Matrix3d::Identity() * dt;

B.block<3, 3>(0, 0) = Jr * dt;
B.block<3, 3>(3, 3) = dR_mat * dt;
B.block<3, 3>(6, 3) = 0.5 * dR_mat * dt * dt;

cov_ = A * cov_ * A.transpose() + B * Q * B.transpose();

5 · Accumulating noise is not a monotonicity theorem

Each sample adds a positive semidefinite term \(\mathbf B\Sigma_\eta\mathbf B^\top\) to the propagated covariance \(\mathbf A\Sigma\mathbf A^\top\). This does not imply \(\Sigma_{k+1}-\Sigma_k\succeq0\): propagation rotates and couples errors, so individual directional variances need not increase at every step.

A rotation, for instance, can move variance from one axis onto another, so the variance along one axis can fall even though fresh noise was added. Longer inertial dead reckoning generally accumulates error, but that practical trend is not an ordering of every covariance matrix or its inverse.

In the simpler scalar model \(v_{k+1}=v_k+\eta_k\Delta t\), with independent \(\operatorname{Var}(\eta_k)=\sigma_a^2/\Delta t\), the variance is exactly \(\sigma_a^2T\). Doubling \(T\) doubles that variance and halves its information. State the model before drawing this conclusion.

Noise densities are physical parameters to measure or obtain from a suitable sensor specification. Validate their units, bandwidth assumptions, and the resulting residual statistics. Datasheet range noise alone is not the full point-to-plane residual uncertainty: plane fitting, deskew errors, and correlated returns also matter. The historical glass-lio settings are calibration choices for those experiments, not universal constants.

Explain the idea

"Preintegration's noise is a linear function of the IMU's white noise, so I propagate its 9×9 covariance over \([\delta\boldsymbol{\phi}, \delta\mathbf{v}, \delta\mathbf{p}]\) one sample at a time: \(\Sigma \leftarrow \mathbf{A}\Sigma\mathbf{A}^{\top} + \mathbf{B}\Sigma_{\eta}\mathbf{B}^{\top}\), starting from zero, with the datasheet densities turned into discrete covariances \(\sigma^{2}/\Delta t\). A and B are eq. (31) linearised, plus one coupling: an orientation error tilts the specific force. The inverse of that covariance weights the IMU residual. Dead reckoning generally accumulates uncertainty, but propagation does not guarantee monotonic growth in every direction."

6 · What one scan's covariance looks like

Run the recursion of section 3 over one glass-lio scan and read the standard deviations off the diagonal of \(\Sigma_{ij}\). Over one scan all three blocks are small: the gyro alone knows the scan's rotation extremely well, and the velocity and position uncertainties are tiny compared with what a LiDAR can resolve. For a non-rotating sensor they follow simple formulas you could derive by hand: \(\sigma_g\sqrt{T}\) for rotation, \(\sigma_a\sqrt{T}\) for velocity, and \(\sigma_a T^{3/2}/\sqrt3\) for position.

The one coupling the recursion adds, orientation error tilting the specific force, is present but weak over a single scan. Run the same recursion for much longer and that coupling becomes the dominant term, which is lesson 2's \(t^3\) drift seen from the covariance side.

A test the recursion must pass: the sampling rate must not matter

Run the same interval at twice the IMU rate. Each step's discrete noise \(\sigma^2/\Delta t\) doubles, and there are twice as many steps, each weighted by a smaller \(\Delta t\) or \(\Delta t^2\) in \(\mathbf{B}_k\). The final covariance comes out essentially the same.

That is not a coincidence; it is what a noise density means. The physical uncertainty after a given time cannot depend on how often you happened to sample it. This makes an excellent unit test, and it catches the single most common mistake in writing this code: multiplying by \(\Delta t\) instead of dividing. With \(\sigma^2\Delta t\), a higher rate would shrink the variance, and a faster IMU would appear to make the estimator more certain for free.

7 · “Who wins”: the reasoning behind a real calibration fix

The covariance's inverse is the IMU's information. Each LiDAR point-to-plane correspondence, whitened by lidar_sigma, carries \(1/\sigma^2\) along its plane's normal. In the tight solve these two kinds of information are simply added, so their ratio decides which sensor wins a disagreement, direction by direction. Because information goes as \(1/\sigma^2\), shrinking lidar_sigma by some factor multiplies the LiDAR's say in every argument by the square of that factor.

This is not hypothetical. With lidar_sigma set well above the Livox's real point-to-plane noise, glass-lio's tight coupling let the IMU win arguments it should have lost. The point-to-plane residual has no velocity columns, so position is the only channel that disciplines velocity; with position under-trusted, the IMU over-integrated velocity to implausible speeds and the trajectory drifted far from the truth. Setting lidar_sigma to the sensor's actual noise brought it level with the loosely coupled baseline, with sane velocities.docs/implementation/testing.md, §12.5, “The last drift — an under-trusted LiDAR”.

The lesson in one line

In a fused solve, a noise parameter is not a tuning knob for one sensor; it is a vote on which sensor is right. Set it to the physical truth, measured, and let the covariances argue. When the output looks wrong, check the votes before touching the model.

8 · What this covariance does not contain

Section 3 already warned that \(\Sigma_{ij}\) is conditioned on the integration biases. It is worth listing everything it leaves out, because each omission is handled somewhere else, and forgetting where is how covariances become over-confident.

Try it: a platform vibrates so its accelerometer noise is correlated across neighbouring samples. Should you raise or lower σa?

Raise it. Correlated noise does not average down as fast as white noise: a run of correlated samples carries far less information than the same number of independent ones. Treating them as independent would claim too small a standard deviation for integrated quantities. Inflating the density is the standard, honest fix when you cannot model the correlation.

9 · Practice

From memory, no scrolling. The last question comes from lesson 4.

Why can \(\Sigma_{ij}\) be computed by linear propagation at all?

What is \(\Sigma_{ii}\), the covariance at the very start of the interval?

In the velocity row of \(\mathbf{A}_k\), where does the \(-\Delta\tilde{\mathbf{R}}_{ik}\,\mathbf{a}_k^{\wedge}\,\Delta t\) block come from?

For scalar velocity driven only by independent white accelerometer noise, \(\operatorname{Var}(v)=\sigma_a^2T\). Double \(T\): what happens to its inverse-variance weight?

From lesson 4. In eq. (44), which delta's bias correction is not a plain addition?

You rerun the same interval with the IMU at twice the rate. With the discrete noise set correctly, the final \(\Sigma_{ij}\)…

Dividing lidar_sigma by a factor \(k\) multiplies the LiDAR's information in the tight solve by…

Motor vibration makes a platform's accelerometer noise correlated across samples. The honest fix, if you cannot model the correlation, is to…

From memory: write the covariance recursion, with its starting value and the discrete IMU noise covariance. Then say in one sentence what its inverse is used for. Stretch: write \(\mathbf{B}_k\).