UAV Systems Engineering · Module 4
M4-T11: Loop closure
Recognise that you have been here before, prove it geometrically, and only then let it change the map.
Loop closure is what turns odometry into SLAM: it supplies the one measurement that does not drift. It also supplies the one error that destroys a map: a false positive. This lesson separates the two stages that guard against that, recognition and verification.
Learning objectives
After this lesson, you should be able to:
- Describe place recognition with bag-of-words (visual) and global scan descriptors (LiDAR).
- Make a descriptor invariant to heading and compare descriptors by a shifted distance.
- Explain geometric verification and why it is mandatory.
- Measure precision and recall of a recogniser and choose a threshold.
Conceptual explanation
Loop closure has two stages. Recognition proposes candidate revisits quickly from a database of past places: visual bag-of-words (DBoW2, DBoW3, NetVLAD descriptors) or LiDAR global descriptors (Scan Context, which encodes the polar structure of a scan). Verification computes the relative pose between the current scan or frame and the candidate (RANSAC on feature matches, or ICP) and checks that it is consistent and that enough points agree; only then is a pose-graph edge added (lesson M4-T12).

Engineering intuition
Many buildings look alike: identical corridors, repeated pillars, uniform fields. Appearance-based recognition cannot tell them apart and will propose confident wrong candidates (perceptual aliasing). Verification catches most by demanding that the geometry actually align with many inliers; the remaining risk is handled by robust back-ends. The cost of a false positive (a folded map) far exceeds the cost of a missed loop (some residual drift), so thresholds are set for high precision, accepting lower recall.
Mathematical formulation
A ring–sector descriptor: divide the scan into range rings and azimuth sectors; the entry is a summary (for example the maximum height, or a point count) in each bin. A rotation of the sensor about the vertical axis shifts the columns cyclically, so the distance is minimised over column shifts:
Precision = TP/(TP + FP) and recall = TP/(TP + FN) over candidate pairs; choose the threshold to reach the required precision (for example 100% on the validation set), then report the recall achieved. Verification inlier ratio and the residual after alignment are the standard accept tests; a loop with a large relative-pose residual or a heading that disagrees with the descriptor’s yaw estimate is rejected.
- ,
- number of rings and sectors.
- k*
- best column shift (heading difference).
Architecture and data flow
Keyframes → descriptor → database (excluding recent frames, to avoid trivial matches) → candidates → verification → pose-graph edge with covariance → optimisation → map correction published as the map-to-odom transform. In a UAV stack, run recognition on a separate thread and never block odometry. A loop correction must not jump the controller’s frame (lesson M4-T1).
Real-world UAV applications
- Drift removal in LiDAR and visual SLAM (ORB-SLAM3, LIO-SAM).
- Multi-session localisation against a prior map.
- Map merging between vehicles.
- Relocalisation after tracking loss.
Implementation example
This runs as shown: 2-D range scans taken from random poses in a room with obstacles are encoded by a ring–sector descriptor; a revisit with a different heading is recognised (and the heading recovered) while a different place with similar structure is rejected at the chosen threshold.
import numpy as np
rng = np.random.default_rng(4)
R_RINGS, S_SECT, MAXR = 10, 60, 20.0
def make_world(seed):
r = np.random.default_rng(seed); return [(r.uniform(-12, 12), r.uniform(-12, 12), r.uniform(0.6, 2.0)) for _ in range(40)] # circular obstacles
def scan_points(world, pose): # points hit by 360 beams, in the sensor frame
pts = []
for a in np.linspace(0, 2 * np.pi, 720, endpoint=False):
d = np.array([np.cos(a + pose[2]), np.sin(a + pose[2])]); best = MAXR
for (cx, cy, rad) in world:
f = np.array([cx, cy]) - pose[:2]; t = f @ d
if t > 0 and (f @ f - t * t) < rad * rad: best = min(best, t - np.sqrt(rad * rad - (f @ f - t * t)))
if best < MAXR: pts.append(best * np.array([np.cos(a), np.sin(a)]))
return np.array(pts) + 0.03 * rng.standard_normal((len(pts), 2))
def descriptor(P): # rings x sectors count of points
r = np.hypot(P[:, 0], P[:, 1]); a = (np.arctan2(P[:, 1], P[:, 0]) % (2 * np.pi))
D, _, _ = np.histogram2d(np.minimum((r / MAXR * R_RINGS).astype(int), R_RINGS - 1), (a / (2 * np.pi) * S_SECT).astype(int) % S_SECT, bins=[R_RINGS, S_SECT], range=[[0, R_RINGS], [0, S_SECT]])
return (D > 0).astype(float)
def distance(A, B):
best = (9.0, 0)
for k in range(S_SECT):
Bk = np.roll(B, k, axis=1); num = (A * Bk).sum(0); den = np.linalg.norm(A, axis=0) * np.linalg.norm(Bk, axis=0)
cos = np.where(den > 0, num / np.maximum(den, 1e-9), 0.0); d = np.mean(1 - cos)
best = min(best, (d, k))
return best
world = make_world(1)
A = descriptor(scan_points(world, np.array([2.0, 1.0, 0.0]))) # place A
A_rev = descriptor(scan_points(world, np.array([2.1, 0.9, 1.3]))) # the same place revisited, rotated by 1.3 rad (about 74 degrees)
others = [descriptor(scan_points(world, np.array([x, y, 0.0]))) for x, y in ((-8, -6), (7, -9), (-5, 8), (9, 8), (-10, 0))]
d_same, k = distance(A, A_rev); d_other = min(distance(A, O)[0] for O in others)
print(f"revisit distance {d_same:.3f} (yaw shift {k} sectors = {k * 360 / S_SECT:.0f} deg), best different-place distance {d_other:.3f}")
yaw = (k * 360 / S_SECT) % 360; true_yaw = np.degrees(1.3)
assert d_same < 0.6 * d_other # the revisit is much closer than any other place
assert min(abs(yaw - true_yaw), abs(360 - yaw - true_yaw)) < 20 # and the recovered heading difference is right (up to the shift direction convention)Common engineering mistakes
Skipping geometric verification
Appearance alone is fooled by repeated structure.
Thresholds for recall instead of precision
One false loop costs more than many missed loops.
Matching against very recent frames
Trivial matches are not loops: exclude a time window.
Ignoring heading in LiDAR descriptors
Use yaw-invariant matching or a rotation-aware search.
Blocking odometry on recognition
Run it in a separate thread.
Failure modes
| Failure mode | How it shows up | Likely cause |
|---|---|---|
| False positive | Map folds or doubles | Aliasing, loose thresholds, no verification |
| Missed loops | Drift not corrected | Threshold too strict or viewpoint change too large |
| Slow database search | Latency grows | Linear search over many keyframes |
| Wrong relative pose | Local distortion after correction | Verification accepted a poor alignment |
Debugging approach
- Evaluate precision and recall on a sequence with known revisits.
- Inspect accepted loops visually (overlay).
- Plot descriptor distance for true and false pairs.
- Check the verification residual distribution.
Practical exercises
- Compute precision and recall of the example for a range of thresholds.
- Add a second building with the same layout and see the false-positive rate.
- Add geometric verification by ICP on the point sets.
- Explain why the time-exclusion window is needed.
Interview questions
Write an answer in your own words, then reveal the model answer. Nothing is sent anywhere.
Question 1
Why are loop closures verified geometrically?
Model answer
Recognition from appearance or global descriptors can match different but similar places; checking that the geometry aligns with many inliers rejects most false loops, which would otherwise distort the map.
Question 2
How is a LiDAR descriptor made yaw-invariant?
Model answer
By encoding the scan in polar bins and comparing descriptors under all cyclic column shifts, taking the best; the shift is the heading difference.
Question 3
Precision or recall: which matters more for loop closure?
Model answer
Precision: a false positive can ruin the map while a missed loop only leaves drift.
References and further reading
Primary papers, official documentation and standards. Each claim above that is specific to a tool or version points here.
- G. Kim, A. Kim, “Scan Context: egocentric spatial descriptor for place recognition within 3D point cloud map”, IROS 2018
- D. Gálvez-López, J. Tardós, “Bags of binary words for fast place recognition in image sequences”, IEEE T-RO, 2012
- R. Arandjelović et al., “NetVLAD: CNN architecture for weakly supervised place recognition”, IEEE TPAMI, 2018