The deltas tell the solver what the IMU says happened. The solver also needs to know how much to believe it: that's the 9×9 covariance \(\Sigma_{ij}\) of the deltas. Its inverse is the weight on the IMU residual, and in glass-lio it's what gets set against the LiDAR. This lesson is how that covariance is built, one sample at a time. It's the second half of the course's uncertainty-propagation goal, and the answer to an interview question you should expect: how do you weigh the IMU against another sensor?
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.
- The old velocity, times \(\Delta t\)
- \(\tfrac{1}{2}\) on both \(\Delta t^{2}\) terms
- \(\mathbf{R}(t)\) in front of the bias-corrected specific force
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.
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:
- Position row: the error picks up the old velocity error times \(\Delta t\) (the \(\mathbf{I}\,\Delta t\)), and accelerometer noise enters with \(\tfrac{1}{2}\Delta\tilde{\mathbf{R}}_{ik}\,\Delta t^{2}\), exactly where the specific force does in (31).
- Velocity row: accelerometer noise enters with \(\Delta\tilde{\mathbf{R}}_{ik}\,\Delta t\), again mirroring (31).
- The first column: an orientation error tilts the specific force slightly the wrong way. To first order that's \(-\Delta\tilde{\mathbf{R}}_{ik}\,\mathbf{a}_k^{\wedge}\,\delta\boldsymbol{\phi}\), fed into velocity and position.
- Rotation row: the old error, re-expressed in the new frame, plus fresh gyro noise.
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.
For example, let \(\Sigma=\operatorname{diag}(4,1)\), let \(A\) rotate by 90°, and add \(Q=0.1I\). Then \(A\Sigma A^\top+Q=\operatorname{diag}(1.1,4.1)\). The first variance fell despite fresh noise. 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.
Say it at a whiteboard
"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 · Practice
From memory, no scrolling. The last question comes from lesson 4.
Why can \(\Sigma_{ij}\) be computed by linear propagation at all?
To first order, Forster's eqs. (42)–(43) write the preintegrated noise as a linear combination of the raw IMU noise, so its covariance follows by linear propagation. The datasheet gives \(\Sigma_{\eta}\), the raw noise, not \(\Sigma_{ij}\).
What is \(\Sigma_{ii}\), the covariance at the very start of the interval?
An empty interval has measured nothing, so its delta carries no uncertainty yet. Forster starts from
\(\Sigma_{ii} = \mathbf{0}_{9\times 9}\), and glass-core's cov_ starts at zero.
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?
A small rotation error \(\delta\boldsymbol{\phi}\) points the measured specific force slightly the wrong way. To first order that's \(-\Delta\tilde{\mathbf{R}}_{ik}\,\mathbf{a}_k^{\wedge}\,\delta\boldsymbol{\phi}\), integrated over \(\Delta t\). Bias uncertainty isn't part of this 9×9 covariance at all.
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?
In this specified scalar model, \(1/(\sigma_a^2(2T))\) is half the original weight. The full coupled 9×9 propagation does not imply that every direction loses information monotonically.
From lesson 4. In eq. (44), which delta's bias correction is not a plain addition?
Rotations don't add: \(\Delta\tilde{\mathbf{R}}_{ij}\,\mathrm{Exp}(\mathbf{J}^{R}_{g}\,\delta\mathbf{b}^{g})\), applied on the right. Velocity and position live in \(\mathbb{R}^3\) and just add.
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\).
- \(\mathbf{A}\Sigma\mathbf{A}^{\top}\): sandwiched, not \(\mathbf{A}\Sigma\)
- Plus \(\mathbf{B}\Sigma_{\eta}\mathbf{B}^{\top}\), fresh noise every sample
- Starts from zero
- \(\sigma^{2}/\Delta t\): divided by the step, not multiplied
- Its inverse is the weight on the IMU residual
Missed something? Try this box again after a night's sleep, before rereading anything.
Apply it: complete the practical worksheet exercise before moving on. Derive an answer and test it on the supplied inputs.