Skip to main content
Contents4 / 8 ▾
04
Phase Four · Waking Up Lost, Efficiently

Global Localization & Relocalization

Phase 3 built the map. This phase answers the question a deployed robot faces every single morning: given that map and no idea where I am, find me — and find me again after every tracking failure, kidnap, and elevator ride.

4.1The Kidnapped Robot Problem

Localization against a known map comes in three regimes of increasing cruelty. Tracking: the pose is approximately known and each update is a small correction — Phase 2’s Gaussian filters handle this natively. Global localization: the robot wakes with no pose prior; the belief must start spread over the entire map, and in any building with repeated structure it will be genuinely multimodal — "I am in one of these four identical aisles" is the honest answer for a while. Kidnapped robot: hardest of all, the robot is confidently wrong — it was carried, the wheels slipped on a dolly, the elevator opened on a different floor — and it must first discover that its tight, self-assured belief is fiction before it can rebuild one. A unimodal Gaussian is structurally incapable of the last two: it has exactly one bump and no mechanism to say "or, entirely elsewhere".

RegimePriorBelief shapeRight tool
Trackinggood pose estimateunimodal, tightEKF / scan matching (Phase 2)
Global localizationnonespread, multimodalparticle filter (4.2), place recognition (4.4)
Kidnapped robota confident liewrong mode must die firstparticle filter + recovery injection, place recognition restart
The kidnapped-robot test is not academic paranoia — it is Tuesday. Tracking loss, wheel slip on a trolley, a reboot in a new dock, an elevator: every deployed robot relocalizes routinely, and a stack is only as reliable as its worst recovery. Design for the third row and the first two come free.

4.2Monte Carlo Localization

MCL represents the belief as N weighted samples — particles, each a full pose hypothesis — and runs the Bayes filter on the swarm. Multimodality costs nothing: four plausible aisles simply means four clusters of particles. The loop is Phase 1’s heartbeat with a population:

predict:   move every particle through the motion model + sampled noise
weight:    wᵢ ∝ p(z | xᵢ, map)          how well does the scan fit HERE?
resample:  draw N new particles ∝ wᵢ    (low-variance/systematic sampler)
           … but only when N_eff = 1 / Σ wᵢ² drops below ~N/2

global init: particles uniform over free space
recovery (Augmented MCL): track fast/slow averages of the mean weight —
  w_fast ← w_fast + α_fast(w̄ − w_fast)        α_fast ≈ 0.1
  w_slow ← w_slow + α_slow(w̄ − w_slow)        α_slow ≈ 0.01
  inject uniform random particles with probability max(0, 1 − w_fast/w_slow)
  → when observations suddenly stop fitting (kidnap!), w_fast dives below
    w_slow and the filter automatically re-seeds hypotheses everywhere

The measurement likelihood p(z | x, map) is where lidar MCL earns its keep, and two models dominate. The beam model raycasts the map from the hypothesis pose and compares each predicted range to the measured one — physically faithful (it models max-range misses and unexpected obstacles explicitly) but expensive and brittle: a hypothesis one cell off a doorway predicts a wildly different ray. The likelihood field model precomputes a distance transform of the map and scores each scan endpoint by its distance to the nearest obstacle — smoother, faster, forgiving of small pose errors, blind to the free-space information along the ray. Production 2D stacks almost universally run likelihood fields, with the smoothing that makes the posterior well-behaved being exactly why.

MCL CONVERGING IN A CORRIDOR WITH THREE DOORS t₀ — no prior: particles uniform over the corridor t₁ — robot sees a door: three surviving hypotheses (honest multimodality) t₂ — drove 3 m, saw a second door at the wrong spacing for two modes: unique fix
Fig 4.1: belief density (rust) along a corridor with three identical doors (gold). One door observation cannot disambiguate — and must not: the honest posterior has three modes. Motion plus a second observation kills the impostors. Setup A reproduces this exact behavior, kidnap included.
Resampling’s dark side is particle deprivation: every resample deletes diversity, and in a symmetric building each resample slightly favors whichever mode the last scan flattered. Twenty eager resamples later the filter is certain — about aisle 3, while the robot sits in aisle 6, and no surviving particle can ever say otherwise. Defenses: resample only on the N_eff test, keep the motion noise honest (it is the only force re-spreading collapsed clusters), and keep the Augmented-MCL injection on in production. If localization in a repetitive building converges suspiciously fast, distrust it until the robot has passed something unique.

Setup A: MCL in a corridor, kidnap included — one evening, zero dependencies. Goal: a complete particle filter in ~90 lines of plain C++: uniform initialization, door-sensor weighting, N_eff-gated systematic resampling, Augmented-MCL recovery — then a kidnap at step 90 to prove the recovery works. This is Fig 4.1 as a program.

Save as mcl1d.cc, compile with g++ -O2 -std=c++17 mcl1d.cc -o mcl1d:

// mcl1d.cc — Monte Carlo localization on a 20 m circular corridor.
#include <algorithm>
#include <cmath>
#include <iostream>
#include <random>
#include <vector>

const double L = 20.0;                          // corridor length (wraps)
bool at_door(double x) {                        // doors at 3, 9, 15 (1 m wide)
  for (double d : {3.0, 9.0, 15.0})
    if (std::abs(std::fmod(x - d + L, L)) < 0.5) return true;
  return false;
}

int main() {
  std::mt19937 rng(3);
  std::uniform_real_distribution<double> U(0.0, 1.0);
  auto gauss = [&](double s) {
    return std::normal_distribution<double>(0.0, s)(rng); };

  const int N = 1000;
  std::vector<double> p(N), w(N, 1.0 / N);
  for (auto& x : p) x = U(rng) * L;             // global init: uniform

  double robot = 6.2;                            // true pose (unknown to filter)
  double w_slow = 0, w_fast = 0;                 // augmented-MCL averages

  for (int step = 1; step <= 160; ++step) {
    if (step == 90) robot = std::fmod(robot + 7.0, L);   // KIDNAP!

    robot = std::fmod(robot + 0.25 + gauss(0.02), L);    // truth moves
    const bool z = (U(rng) < 0.9) == at_door(robot);     // noisy door sensor

    double w_sum = 0.0;
    for (int i = 0; i < N; ++i) {                        // predict + weight
      p[i] = std::fmod(p[i] + 0.25 + gauss(0.05) + L, L);
      w[i] *= (z == at_door(p[i])) ? 0.9 : 0.1;
      w_sum += w[i];
    }
    double w_avg = w_sum / N, neff = 0.0;
    for (auto& wi : w) { wi /= w_sum; neff += wi * wi; }
    neff = 1.0 / neff;

    w_fast += 0.10 * (w_avg - w_fast);                   // recovery bookkeeping
    w_slow += 0.01 * (w_avg - w_slow);
    const double p_inject = std::max(0.0, 1.0 - w_fast / std::max(w_slow, 1e-12));

    if (neff < N / 2.0) {                                // systematic resample
      std::vector<double> np(N);
      double c = w[0], r = U(rng) / N;
      for (int i = 0, j = 0; i < N; ++i) {
        const double u = r + double(i) / N;
        while (u > c) c += w[++j];
        np[i] = (U(rng) < p_inject) ? U(rng) * L : p[j]; // inject when lost
      }
      p = np;  std::fill(w.begin(), w.end(), 1.0 / N);
    }

    if (step % 10 == 0) {                                // ASCII belief map
      std::vector<int> hist(40, 0);
      for (double x : p) hist[int(x / L * 40)]++;
      std::cout << "step " << step << (step == 90 ? " [KIDNAPPED]" : "")
                << "  inject=" << p_inject << "\n  ";
      for (int h : hist) std::cout << " .:-=+*#@"[std::min(8, h / 12)];
      std::cout << "\n  robot at column " << int(robot / L * 40) << "\n";
    }
  }
}

Expected, reading the ASCII histograms: at step 10 the belief is a fuzz across the corridor; once the robot has passed a door it collapses to three clusters (the honest three-mode posterior of Fig 4.1); by step 40–60 the door spacing has killed two impostors and one tight cluster tracks the robot’s column. At step 90 the kidnap teleports the robot 7 m: the cluster keeps tracking the old pose, observations stop fitting, inject climbs off zero within a few steps, random particles re-seed the corridor, and by step 130–150 a new cluster has locked onto the true column. That inject number rising exactly when the world betrays the filter is Augmented MCL earning its two lines of code.

Sanity checks: (1) set p_inject to 0 permanently — after the kidnap the filter never recovers, tracking a ghost forever: deprivation in its purest form. (2) Resample every step regardless of N_eff — convergence gets brittler and occasionally snaps to a wrong door early. (3) Make the sensor near-perfect (0.99) — faster convergence but harder kidnap recovery, because wrong particles die before injection can build a rival cluster: better sensors are not uniformly better for recovery.

4.3Scan Matching Against a Known Map

Particles get you a coarse, possibly multimodal fix; finishing the job — and staying localized cheaply afterwards — is registration against the map, which is Phase 2’s machinery pointed at a prior map instead of the previous scan. Two regimes matter. With a decent initial guess (a converged particle cluster, or last frame’s pose), ICP/NDT refine to centimetres in a few iterations. With no guess, gradient methods fall into the nearest local minimum, and the honest tool is exhaustive: correlative scan matching scores every pose in a search window against the map’s likelihood field, made real-time by branch and bound — evaluate coarse map resolutions first, and prune whole branches whose upper bound already loses to the best fine-resolution candidate. This is precisely Cartographer’s loop-closure searcher from Phase 3, reused here as a global localizer: deterministic, no learning, and guaranteed to find the window’s best pose.

correlative search:  score(x, y, θ) = Σᵢ likelihood_field( map, endpointᵢ(x,y,θ) )
                     over a window, e.g. ±5 m × ±5 m × 360°

branch & bound: coarse-to-fine map pyramid; a coarse cell's score upper-bounds
all its children → prune losing branches wholesale. Exhaustive answer,
logarithmic-ish cost. (Hess et al., Phase 3 reading list.)

4.4Place Recognition: Localization as Retrieval

Correlative search saturates at building scale, and cameras don’t raycast. At scale, global localization becomes a retrieval problem: during mapping, compress every place into a compact descriptor and index it; at wake-up (or at every frame, hunting loop closures for Phase 3), compress the current view the same way and search the index. The pipeline is always the same four boxes, and the last one is not optional:

THE PLACE RECOGNITION PIPELINE query image / scan descriptor BoW / VLAD / polar grid index search inverted file / kd / ANN top-k candidates plausible, unproven geometric verification PnP-RANSAC / ICP fitness pose (or reject)
Fig 4.2: retrieval proposes, geometry disposes. The descriptor index answers "what does this look like" in milliseconds over millions of places; only geometric verification (PnP for images, ICP fitness for scans) is allowed to say "and therefore I am here". Skipping the last box is how Phase 3’s corridor-folding disasters begin.
Descriptor
A compact vector (or small matrix) summarizing a whole image or scan, built to be near its twins and far from everything else under some cheap distance.
Perceptual aliasing
Different places that produce near-identical descriptors — office corridors, warehouse aisles, tree lines. The central failure mode of this entire phase.
Inverted index
Word → list of images containing it. Makes BoW search cost proportional to shared vocabulary, not database size.
Geometric verification
Proving a candidate with geometry: enough RANSAC-consistent feature matches (PnP) or a high ICP fitness. The gate between "similar" and "same".

4.5Visual Place Recognition: DBoW3, NetVLAD, CosPlace

The classical engine is bag of visual words. Offline, cluster millions of local feature descriptors (ORB) into a vocabulary tree — words are cluster centers, the tree makes lookup logarithmic. Online, quantize each image’s features into words and represent the image as a sparse TF-IDF-weighted histogram; two images are compared by histogram similarity, and the inverted index finds sharers without scanning the database:

TF-IDF weight of word i in image:   tf(i) · log( N_images / N_images_containing_i )
   → words that are common everywhere (floor texture) count for nearly nothing;
     rare words (that one exit sign) dominate the vote

score(v₁, v₂) = 1 − ½‖ v₁/‖v₁‖ − v₂/‖v₂‖ ‖₁      then: candidates → verify

DBoW3 (successor of DBoW2, which sits inside ORB-SLAM3) is the standalone C++ library for exactly this: binary-descriptor vocabularies, inverted index, direct index for later feature matching — CPU-only, milliseconds per query, and still the default relocalizer of the classical stack. Its weakness is inherited from its features: ORB words describe texture, and texture changes with light, weather, and season.

The learned generation attacks that. NetVLAD (CVPR 2016) bolts a differentiable VLAD-pooling layer onto a CNN and trains end-to-end on street-view imagery with a weakly-supervised triplet loss ("same place under different conditions must be closer than different places"), producing a single global vector per image whose nearest neighbors survive day/night and seasonal change that kills BoW. CosPlace (CVPR 2022) got the same robustness cheaper by reframing training as classification over geographic cells on a city-scale dataset — no triplet mining — yielding compact descriptors (as small as 512-D) that set retrieval benchmarks with a fraction of the compute. The practical stack in 2026: CosPlace-style global descriptors for candidates, local-feature verification (ORB, or Phase 8’s SuperPoint+LightGlue) for the geometry gate.

DBoW3NetVLADCosPlace
Typehandcrafted BoW over ORBCNN + learned VLAD poolingCNN trained as geo-cell classification
Descriptorsparse word histogramdense, 4096-D (typ.)dense, 512-D (typ.)
ComputeCPU, msGPU preferredGPU for extraction, tiny index
Survives lighting/seasonpoorlywellwell, at lower cost
Natural homeORB-SLAM-style relocalization, loop proposalcity-scale geo-localizationlarge-scale, resource-conscious retrieval

4.6LiDAR Place Recognition: Scan Context++, OverlapNet, LiDAR-Iris

Lidar descriptors get one enormous gift: geometry doesn’t care about lighting. Their enemy is viewpoint — revisit a place facing the other way and a naive descriptor sees nothing familiar — so every serious design builds in rotation invariance explicitly.

Scan Context++ is the classical reference. Flatten the scan into a polar bird’s-eye grid — rings (range bins) × sectors (azimuth bins), each cell keeping the max point height — giving a small 2D matrix that reads like a fingerprint of the surrounding structure. A yaw change of the robot is a pure column shift of that matrix, so matching scores the minimum distance over all shifts — and the best shift itself is a free yaw estimate, exactly the initial guess registration wants. Candidate search stays sub-linear via a ring key (a rotation-invariant per-ring summary) in a kd-tree; the ++ version adds lateral-shift robustness for revisits in adjacent lanes. Setup B builds the 2D miniature of this.

scan context:  M[r][s] = max height of points in ring r, sector s
distance(A, B) = min over column shifts k of  Σ_cols cosine_dist(A_col, B_col(+k))
best k · (360°/N_sectors)  =  relative yaw  →  free initial guess for ICP

OverlapNet is the learned counterpoint: render scans as range images, feed pairs through a siamese network that predicts both their overlap fraction (how much they see the same structure — a direct loop-closure confidence) and their relative yaw. LiDAR-Iris borrows iris recognition from biometrics: expand the bird’s-eye view into a binary "iris" image whose Fourier-aligned Hamming distance is fast and rotation-invariant. The selection logic mirrors vision: Scan Context++ as the CPU-cheap default with a free yaw, OverlapNet where training data exists and confidence calibration matters, LiDAR-Iris where binary compactness and speed dominate.

SCAN CONTEXT: A SCAN BECOMES A MATRIX, YAW BECOMES A SHIFT rings × sectors around the robot encode max height per cell columns: sectors · rows: rings match over shifts → best = yaw estimate
Fig 4.3: Scan Context flattens the scan into a rings-by-sectors matrix; turning in place permutes columns without changing content. Matching over circular shifts buys rotation invariance and hands registration its initial yaw for free.

4.7Relocalization Inside Real Systems

Assembled, the production relocalization pipeline is a funnel of cheap-to-expensive filters, and it is worth knowing how the reference systems walk it. ORB-SLAM3, on tracking loss: query DBoW2 over keyframes → for each candidate, PnP-RANSAC on ORB matches → guided re-matching and pose refinement → resume tracking; the same funnel with stricter thresholds proposes loop closures, and Atlas decides "known map, merge" versus "new map, explore". Lidar stacks: Scan Context++ (or correlative search at modest scale) proposes pose + yaw → point-to-plane ICP against the map refines → fitness gate (inlier fraction, residual RMS) accepts or rejects → the accepted pose seeds MCL or the Phase 2 filter for continuous tracking. Note the division of labor: descriptors handle the combinatorial explosion, geometry handles the truth, and the probabilistic filter handles time. No single stage is trusted alone.

Retrieval proposes, geometry disposes, the filter endures. Every reliable relocalization system in this book is those three verbs in that order — and every infamous localization failure is a system that let stage one speak directly to the map.

Setup B: a miniature Scan Context — one evening, zero dependencies. Goal: implement the sector-encoding + shift-matching idea in 2D, retrieve a revisited place from a 200-pose database despite a 137° heading change, recover the yaw from the best shift, and then watch perceptual aliasing produce a near-tie between two identical rooms.

Save as minisc.cc, compile with g++ -O2 -std=c++17 minisc.cc -o minisc:

// minisc.cc — a 2D miniature of Scan Context: polar signature + shift match.
#include <algorithm>
#include <array>
#include <cmath>
#include <iostream>
#include <random>
#include <vector>

// world: 20×10 outer walls, two IDENTICAL rooms + one distinctive pillar room
bool occupied(double x, double y) {
  auto box = [&](double x0, double y0, double x1, double y1) {
    return x >= x0 && x <= x1 && y >= y0 && y <= y1; };
  if (x < 0 || x > 20 || y < 0 || y > 10) return true;         // outer walls
  if (box(6.0, 0.0, 6.2, 7.0) || box(13.0, 3.0, 13.2, 10.0)) return true;
  if (box(16.5, 1.5, 17.5, 2.5)) return true;                   // the pillar
  return false;
}

using Sig = std::array<double, 72>;                             // 5° sectors
Sig signature(double x, double y, double yaw) {                 // range per sector
  Sig s{};
  for (int k = 0; k < 72; ++k) {
    const double a = yaw + k * (2 * M_PI / 72);
    double r = 0.0;
    while (r < 12.0 && !occupied(x + r * std::cos(a), y + r * std::sin(a)))
      r += 0.02;
    s[k] = r;
  }
  return s;
}
double dist_shift(const Sig& a, const Sig& b, int& best_k) {    // min over shifts
  double best = 1e18;
  for (int k = 0; k < 72; ++k) {
    double d = 0.0;
    for (int i = 0; i < 72; ++i) d += std::abs(a[i] - b[(i + k) % 72]);
    if (d < best) { best = d; best_k = k; }
  }
  return best / 72.0;
}

int main() {
  std::mt19937 rng(5);
  std::uniform_real_distribution<double> ux(0.5, 19.5), uy(0.5, 9.5);

  std::vector<std::array<double, 2>> poses;                     // database
  std::vector<Sig> db;
  while (poses.size() < 200) {
    const double x = ux(rng), y = uy(rng);
    if (occupied(x, y)) continue;
    poses.push_back({x, y});
    db.push_back(signature(x, y, 0.0));                         // canonical yaw
  }

  // query: revisit database place #17, heading rotated by 137°
  const double qyaw = 137.0 * M_PI / 180.0;
  const Sig q = signature(poses[17][0], poses[17][1], qyaw);

  int best = -1, k_at_best = 0;
  double d1 = 1e18, d2 = 1e18;
  for (int i = 0; i < 200; ++i) {
    int k;  const double d = dist_shift(q, db[i], k);
    if (d < d1) { d2 = d1; d1 = d; best = i; k_at_best = k; }
    else d2 = std::min(d2, d);
  }
  std::cout << "retrieved place " << best << " (truth: 17), "
            << "yaw from shift: " << k_at_best * 5 << "° (truth: 137°)\n"
            << "best distance " << d1 << "  runner-up " << d2
            << "  margin " << d2 / d1 << "×\n";
}

Expected: place 17 retrieved, yaw recovered to 135° (the nearest 5° bin to 137°), with a comfortable margin over the runner-up. Then the aliasing experiment: change the query to a pose in the middle of one of the two identical rooms (say near (3, 8) and its twin near (10, 1.5) — mirror-ish layouts around the interior walls) and watch the top two candidates come back with margins near 1.0×. The descriptor is not wrong — those places really do look alike from a lidar’s point of view. It is the downstream gate’s job (ICP fitness at both candidates, or just driving three metres and re-querying, as in Setup A) to refuse the coin flip.

Sanity checks: (1) add range noise (±5 cm) to the query signature — retrieval barely notices; coarse structure carries the match, which is why these descriptors compress so well. (2) Reduce sectors from 72 to 12 — yaw resolution and margins degrade together; descriptor size is a real design dial. (3) Query from a pose 0.5 m off any database pose — still retrieved, margin thinner: place recognition is deliberately coarse, and that coarseness is what the refinement stage (4.3) exists to absorb.

4.8Reading List

Robust Monte Carlo Localization for Mobile RobotsThrun, Fox, Burgard & Dellaert · Artificial Intelligence 2001MCL from its authors: the particle machinery of 4.2, mixture proposals, and the kidnapped-robot experiments that defined the benchmark. Probabilistic Robotics — chapters 7 & 8Thrun, Burgard & Fox · MIT Press 2005Beam vs likelihood-field measurement models, Augmented MCL’s w_fast/w_slow, and KLD-sampling, all with pseudocode. Bags of Binary Words for Fast Place Recognition in Image SequencesGálvez-López & Tardós · IEEE TRO 2012The DBoW paper: binary vocabularies, inverted + direct indices — the relocalizer ORB-SLAM3 still ships. NetVLAD: CNN Architecture for Weakly Supervised Place RecognitionArandjelović, Gronat, Torii, Pajdla & Sivic · CVPR 2016The learned-descriptor watershed: differentiable VLAD pooling and training from street-view time machines. Rethinking Visual Geo-localization for Large-Scale Applications (CosPlace)Berton, Masone & Caputo · CVPR 2022Classification-style training on city-scale data: NetVLAD-class robustness at a fraction of the descriptor size and training cost. Scan Context++: Structural Place Recognition Robust to Rotation and Lateral VariationsKim, Choi & Kim · IEEE TRO 2022The polar-matrix descriptor of Fig 4.3 and Setup B, matured: ring keys, lateral robustness, and the free yaw estimate. OverlapNet: Loop Closing for LiDAR-based SLAMChen, Läbe, Milioto, Röhling, Vysotska, Haag, Behley & Stachniss · RSS 2020A siamese network predicting scan overlap and yaw — learned loop-closure confidence you can threshold honestly. LiDAR Iris for Loop-Closure DetectionWang, Wang, Chen & Xie · IROS 2020Biometrics borrowed for robots: binary iris images and Hamming distance — the compact, fast corner of the design space. Visual Place Recognition: A SurveyLowry, Sünderhauf, Newman, Leonard, Cox, Corke & Milford · IEEE TRO 2016The map of the whole territory: appearance change, viewpoint change, and why perceptual aliasing is the boss enemy.

4.9Interview Questions

Why does global localization demand a particle filter when tracking is happy with an EKF?

Tracking’s belief is unimodal and tight, exactly what a Gaussian represents. Global localization’s belief starts uniform over the map and, in any building with repeated structure, passes through genuinely multimodal stages — "one of these four aisles". A Gaussian has one mode by construction; forced to cover four hypotheses it either straddles them (its mean in a wall between aisles) or collapses onto one prematurely. Particles represent arbitrary distributions: multimodality is just multiple clusters, global init is just uniform sampling, and at 3-DoF pose dimensionality the particle count stays affordable.

Explain particle deprivation and how Augmented MCL fixes it.

Resampling duplicates heavy particles and kills light ones; every pass deletes diversity that only motion noise slowly restores. In symmetric environments the filter can converge onto a wrong mode with zero survivors near the truth — after which no amount of good data helps, because the truth has no particle to reweight. Augmented MCL tracks a fast and a slow exponential average of mean particle weight; when observations suddenly stop fitting the belief (a kidnap), w_fast dives below w_slow, and the filter injects uniform random particles with probability 1 − w_fast/w_slow. The injected seeds give the true pose a foothold, and normal weighting does the rest. The elegant part: injection self-throttles to zero when tracking is healthy.

Beam model vs likelihood field for lidar MCL — trade-offs?

The beam model raycasts the map from each particle and compares predicted to measured ranges per beam: physically faithful, models unexpected obstacles and max-range readings explicitly, but expensive (raycast × beams × particles) and brittle — the likelihood is spiky, so a particle a few centimetres wrong at a doorway scores as badly as one metres wrong. The likelihood field precomputes a distance transform and scores each scan endpoint by distance to the nearest obstacle: no raycasting at query time, smooth well-behaved likelihood that forgives small pose errors, but it ignores free-space evidence along the ray and can be fooled by scans through glass or clutter. Production 2D MCL default: likelihood field, with the beam model kept for when free-space reasoning matters.

What is perceptual aliasing, and what actually mitigates it?

Different places producing near-identical observations — identical corridors, aisles, cubicle farms — so any descriptor honestly reports high similarity for the wrong place. Mitigations stack: geometric verification rejects candidates whose fine structure disagrees (aliased places are rarely identical to RANSAC); sequence matching requires several consecutive queries to agree with the map’s spatial ordering, which single impostors rarely sustain; temporal filtering (MCL) demands consistency with motion — two aliased rooms 40 m apart cannot both be reachable; and margin gating simply refuses to decide when the top two scores are close, which Setup B shows is the honest reading of a near-tie. What does not work is tuning the descriptor harder: aliasing is a property of the world.

How does bag-of-words search stay fast over a million-image database?

Two structures. The vocabulary tree quantizes each feature by descending k branches per level — cost logarithmic in vocabulary size, independent of database size. The inverted index maps each word to the list of images containing it, so scoring touches only images sharing words with the query — cost proportional to overlap, not to N; TF-IDF weighting simultaneously mutes ubiquitous words so the shared-word lists that matter stay short. Add-ons: a direct index accelerates the later feature-matching stage, and binary descriptors (ORB) make per-feature quantization a few XORs. The net effect is millisecond queries at scales where linear scan would take seconds.

How does Scan Context achieve rotation invariance, and what does it hand downstream for free?

The descriptor is a polar matrix — rings (range) × sectors (azimuth), max height per cell — anchored to the robot’s current heading. Rotating in place permutes the columns circularly without changing their content, so matching computes the distance at all N_sector column shifts and keeps the minimum: the descriptor is compared in every orientation at matrix-shift cost. The arg-min shift times the sector width is the relative yaw between query and candidate — which downstream registration (ICP) receives as its initial guess, converting a would-be local-minimum trap into a two-iteration refinement. Candidate search stays sub-linear via rotation-invariant ring keys in a kd-tree.

Learned place recognition (NetVLAD/CosPlace) vs classical (DBoW): when does each win?

Learned global descriptors win when appearance changes between mapping and query — day/night, weather, seasons — because they were trained to be invariant to exactly that; they also compress a whole image into one dense vector, making city-scale ANN search trivial. They cost GPU inference, and they inherit their training distribution: a model trained on street scenes can underperform in a warehouse. DBoW wins on CPU-only platforms, indoors where lighting is controlled, and when the same ORB features are already being extracted for SLAM anyway — the marginal cost is near zero, and it integrates natively with feature-level verification. Production systems increasingly run learned retrieval with classical geometric verification — the robustness of one, the honesty of the other.

Design the full recovery pipeline for a warehouse robot that just woke up somewhere unknown.

Stage 0, admit ignorance: no tracking, no control decisions that assume a pose. Stage 1, propose: query the place-recognition index (Scan Context++ for lidar) with the current scan for top-k candidates plus yaw estimates. Stage 2, verify: run point-to-plane ICP against the map at each candidate; gate on fitness (inlier fraction, residual RMS); require the best candidate to beat the runner-up by a margin — near-ties in an aisle warehouse mean "drive and re-query", not "guess". Stage 3, seed time: initialize MCL with particles at the accepted pose (plus a dusting of uniform particles for humility) and require consistency over several metres of motion before declaring localized. Stage 4, resume: hand the pose to the Phase 2 filter, re-enable the planner, log the event. The design rule throughout: every stage can say "not sure yet", and only agreement across all of them ends the recovery.