"You trusted every measurement equally, so one lidar return off a mirror convinced me the wall was two meters closer. I did exactly what the squared cost told me to. Next time, let me stop believing the liars."
A Chastened AI Agent
The Big Picture
The least squares objective of Section 11.2 is optimal under one assumption: every residual is Gaussian noise. Real sensor streams break that assumption constantly. A camera matches the wrong feature, a lidar beam glances off glass, a loop closure ties two poses that were never the same place, a GNSS fix arrives as a multipath ghost. These are not large noise samples; they are outliers, generated by a different process entirely, and the squared cost has no defense against them. Because error grows with the square of the residual, a single measurement that is ten times too far contributes a hundred times the pull of a good one, and it drags the whole trajectory toward a wrong answer. This section replaces the parabola with cost functions that saturate: they charge a normal price for small residuals and a capped price for large ones, so a gross outlier can be loud but not decisive. This one change is what separates a smoother that works in a lab from one that survives a real building, and it is why robust kernels are switched on by default in every deployed SLAM and visual-inertial system.
This section builds directly on the nonlinear least squares machinery of Section 11.2 and the sensor noise models of Chapter 2. It assumes you understand residuals, Jacobians, and the Gauss-Newton loop. The failure modes it defends against, multipath and non-line-of-sight returns, are the same ones catalogued for positioning in Chapter 25.
Why one outlier wins
Recall the whitened residual \(e_i = \lVert h_i(X_i) - z_i \rVert_{\Sigma_i}\), a scalar in units of standard deviations. The least squares objective sums \(\rho(e_i) = \tfrac12 e_i^2\). What a solver actually feels is not the cost but its derivative, the influence function \(\psi(e) = \rho'(e)\), which is the gradient each factor pushes with. For the squared cost, \(\psi(e) = e\): influence grows without bound. A residual at 30 standard deviations pushes thirty times harder than one at the noise floor, so it commandeers the solution. Under a Gaussian model a 30-sigma event should never happen, but a mismatched feature produces them routinely. The cure is to choose a \(\rho\) whose influence bounds or even redescends to zero, so that beyond some point a residual stops pulling harder the more wrong it is. Estimators built this way are called M-estimators, and the function \(\rho\) is a robust kernel or loss.
Three kernels span the useful range. The Huber loss is quadratic within a threshold \(\delta\) and linear beyond it, so influence saturates at a constant: outliers keep a fixed, bounded pull. It is convex, so it never introduces new local minima, which makes it the safe default. The Cauchy (Lorentzian) loss, \(\rho(e) = \tfrac{c^2}{2}\log(1 + e^2/c^2)\), is redescending: its influence rises, peaks, then decays toward zero, so a wild outlier is nearly ignored. Geman-McClure and truncated (Tukey) losses redescend even more aggressively, driving influence to exactly zero past a cutoff, which is the closest a smooth kernel gets to deleting a measurement. Redescending kernels reject outliers harder but are non-convex, so they need a good initial guess or a continuation strategy to avoid settling into a bad basin.
Key Insight
A robust kernel is not a filter that discards bad data before optimization; it is a reweighting that happens inside the optimization. Any \(\rho\) with the right shape can be minimized by the ordinary Gauss-Newton loop you already have, using iteratively reweighted least squares (IRLS). At each iteration, give factor \(i\) a scalar weight \(w_i = \psi(e_i)/e_i\), solve the resulting weighted normal equations, and recompute the weights from the new residuals. Good residuals keep \(w_i \approx 1\); a Huber outlier gets \(w_i = \delta/|e_i| < 1\); a Cauchy or Geman-McClure outlier gets \(w_i \to 0\) and quietly removes itself. The solver structure of Section 11.2 does not change at all; only the diagonal weighting does. This is why adding robustness to a factor graph is a one-line change in practice.
Iteratively reweighted least squares in code
The reweighting is easiest to trust when you watch it act. Below, a 1D estimate of a constant offset is measured many times; most readings are clean but a handful are gross outliers. Plain least squares averages everything and is pulled off; Huber IRLS down-weights the liars and recovers the true value. The weights are the whole story, so we print them.
import numpy as np
rng = np.random.default_rng(1)
truth = 5.0
z = truth + rng.normal(0, 0.3, 40) # 40 clean readings
z[:4] = [18.0, -7.0, 22.0, 15.0] # 4 gross outliers
sigma = 0.3
delta = 1.345 # Huber threshold, in sigmas
def huber_weights(e): # e = whitened residuals
a = np.abs(e)
return np.where(a <= delta, 1.0, delta / a)
x = np.median(z) # robust-ish start
for k in range(8):
e = (x - z) / sigma # whitened residuals
w = huber_weights(e) # per-factor weights
x = np.sum(w * z) / np.sum(w) # weighted normal equation
print("least squares mean:", z.mean().round(3))
print("Huber IRLS estimate:", round(x, 3), " truth:", truth)
print("weights on the 4 outliers:", huber_weights((x - z[:4]) / sigma).round(3))
1.345 is the standard choice that keeps 95% efficiency under pure Gaussian noise.The mechanism generalizes without modification: in a real factor graph each \(w_i\) scales that factor's rows in the Jacobian and residual, and the sparse solve proceeds exactly as before. Note the deliberate median initialization. Robust kernels care where they start, because a redescending loss can lock onto whatever majority the initial guess happens to sit near.
Practical Example: a delivery drone under the glass atrium
A last-mile delivery drone flies into a shopping-mall atrium ringed with glass balconies. Its visual-inertial odometry, the kind assembled in Chapter 52, tracks corner features across frames. Reflections in the glass create phantom features that the matcher happily pairs with real ones, producing reprojection residuals of tens of pixels against a sub-pixel noise floor. With a plain squared cost, a dozen phantom matches per frame bend the estimated flight path toward the reflections, and the drone reports itself drifting sideways into a wall it is nowhere near. Switching every reprojection factor to a Cauchy kernel changes nothing for the honest features and collapses the phantoms' weights toward zero within two Gauss-Newton iterations. The trajectory snaps back to the true corridor. The engineers did not write a reflection detector; they told the optimizer to stop over-trusting any single measurement, and the geometry sorted the liars out on its own.
Gating, switchable constraints, and graduated non-convexity
Robust kernels are the workhorse, but three companions matter in practice. First, chi-squared gating: because a whitened residual squared is chi-squared distributed under the Gaussian model, you can pre-reject any factor whose \(e_i^2\) exceeds a confidence threshold (for one degree of freedom, \(e_i^2 > 5.99\) is the 95% cutoff). This is a hard, cheap outlier test used to screen data associations before they ever enter the solve. Second, switchable constraints and dynamic covariance scaling attach a latent on/off weight to each suspect factor, typically a loop closure, and let the optimizer itself decide whether to trust it, jointly estimating the weights alongside the poses. This is the standard defense for the false loop closures introduced in Section 11.4, where a single wrong closure can shatter an entire map.
Research Frontier: certifiable outlier rejection
Non-convex redescending kernels reject outliers best but risk converging to a wrong basin from a poor start. Graduated non-convexity (GNC), popularized for robotics by Yang, Antonante, Tzoumas, and Carlone in 2020, resolves the tension by solving a sequence of surrogate problems: it begins with an almost-convex approximation of the robust cost, finds its easy minimum, then gradually sharpens the surrogate back toward the true non-convex kernel, carrying the solution along. The result rejects outliers as hard as a truncated loss yet needs no accurate initialization, and it underpins the certifiable estimators (TEASER++ for point-cloud registration, and rotation-averaging back-ends) that can certify global optimality even with more than 90% outliers. GNC is now a first-class option in modern factor-graph solvers and is the direction robust estimation is heading for the perception stacks of Chapter 42.
Right Tool: robustness as a noise-model wrapper
You almost never hand-code IRLS. In GTSAM you wrap any factor's noise model in noiseModel.Robust.Create(noiseModel.mEstimator.Huber(k), base), and the library computes the influence weights and re-solves internally; Ceres does the same with a one-argument LossFunction (HuberLoss, CauchyLoss, TukeyLoss) passed to AddResidualBlock. That is a single wrapping line replacing the roughly twenty lines of weight computation, reweighted assembly, and convergence bookkeeping you would otherwise maintain per factor type, and the library keeps the sparse structure and analytic derivatives intact. Choosing the kernel and its threshold is your job; implementing it is not.
Choosing a kernel and its scale
Two knobs decide behavior: which kernel, and its threshold. Prefer Huber when outliers are moderate and you value a convex, initialization-free solve; prefer Cauchy or Geman-McClure when outliers are gross and you already have a decent estimate (from a filter, from odometry, or from GNC's continuation). The threshold is set in units of the noise sigma, which is exactly why the whitening of Section 11.2 matters: a mis-scaled \(\Sigma_i\) makes every residual look like an outlier or none of them do. If your covariances are honest, the kernel scale becomes a portable constant rather than a per-dataset tuning chore. The cost of robustness is small and worth naming: influence weights make the effective information matrix slightly smaller, so the uncertainty you read out in Section 11.6 must be computed from the reweighted system, not the nominal one, or you will report yourself more certain than you are.
Exercise
Start from the IRLS code above. (1) Swap the Huber weights for Cauchy weights \(w_i = 1/(1 + e_i^2/c^2)\) with \(c = 2.5\), and compare the final estimate and the outlier weights against Huber; explain why the redescending kernel pushes the outlier weights closer to zero. (2) Initialize x at one of the outlier values (say 18.0) instead of the median and re-run both kernels; show that Huber still recovers the truth while Cauchy can get stuck, and connect this to convexity. (3) Add a chi-squared gate that hard-rejects any reading with \(e_i^2 > 5.99\) before the loop, and discuss what it catches that the kernel does not, and what the kernel catches that it does not.
Self-Check
- Using the influence function \(\psi(e) = \rho'(e)\), explain precisely why a single 30-sigma residual dominates a squared-cost solve but not a Huber solve.
- What is the per-factor weight \(w_i\) in iteratively reweighted least squares, and why does adding a robust kernel leave the sparse Gauss-Newton solver structure completely unchanged?
- Give one situation where you would choose Huber over Cauchy, and one where you would choose Cauchy (or GNC) over Huber. What property of each kernel drives the choice?
What's Next
In Section 11.6, we turn from the point estimate to its uncertainty. The same information matrix \(J^\top J\) that the solver builds to take a step also encodes how confident the estimate is: its inverse is the covariance of the whole trajectory. We will read marginal uncertainties for individual poses straight out of the sparse factorization, see why robust reweighting shrinks that information, and connect the result back to the calibration discipline the book carries throughout.