Pipeline Overview
Every detector in the workspace shares the same high-level workflow:
take a grayscale image (or a pre-detected corner cloud), produce a
TargetDetection with labelled (i, j) grid coordinates, logical
marker IDs (where applicable), and rectification-ready pixel
positions.
Shared stages
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ Image │ -> │ ChESS │ -> │ Target- │ -> │ Labelled │
│ (u8 gray) │ │ corners │ │ specific │ │ grid out │
└───────────┘ │ (front- │ │ detector │ │ │
│ end) │ │ │ │ │
└───────────┘ └───────────┘ └───────────┘
- Input image —
image::GrayImageor aGrayImageView. The facade helpers incalib_targets::detectaccept either. - Corner front-end — ChESS X-junction
detector via the
chess-cornerscrate. Produces aVec<Corner>— per-corner position, two axis-angle estimates, strength, and fit residuals. The workspace’s default config iscalib_targets::detect::default_chess_config(). - Target-specific detector — see the dedicated chapters:
- Chessboard — invariant-first detector precision-by-construction on our private regression dataset (high detection rate, zero wrong labels).
- ChArUco — chessboard detector + ArUco marker decoding + alignment.
- PuzzleBoard — chessboard detector + edge-dot decoder.
- Marker board — ChESS checker corners + 3-circle marker anchoring.
- Output — every detector produces a
TargetDetectionwrapping aVec<LabeledCorner>. Higher-level detectors (ChArUco, PuzzleBoard) wrap that in their own result struct with extra metadata (marker decodes, alignment, per-corner IDs).
Chessboard detector internals
The chessboard detector itself runs eight internal stages. The invariant-first framing means every stage emits a more-constrained subset of the previous stage’s output, with no backtracking that would compromise precision:
| Stage | Input | Output | Reference |
|---|---|---|---|
| 1. Pre-filter | raw Corner array | CornerStage::Strong corners (strength + fit-quality pass) | cluster::build_histogram |
| 2. Global grid directions | axes histograms | two centers (Θ₀, Θ₁) via plateau peaks + double-angle 2-means | projective_grid::circular_stats |
| 3. Per-corner label | each Strong corner’s axes vs (Θ₀, Θ₁) | CornerStage::Clustered { label } with Canonical/Swapped parity | cluster::assign_corner |
| 4. Cell size | Clustered cross-cluster NN distances | cell_size: f32 estimate | derived inside Stage 5; global scalar kept only as a sanity prior |
| 5. Seed | clustered corners + cluster centers | 2×2 quad + cell_size = mean of seed edges | seed::find_seed |
| 6. Grow | seed + candidate pool | labelled (i, j) → idx map via BFS + prediction averaging | projective_grid::square::grow |
| 7. Validate | labelled map | blacklist via line collinearity + local-H residuals | projective_grid::square::validate |
| 8. Recall boosters | labelled map + remaining clustered corners | additional admits via gap-fill, line extrapolation, component merge | boosters::apply_boosters |
Stages 5-7 run inside a blacklist loop — each iteration the validator
may reject outliers; the pipeline re-seeds on the remaining set.
Capped by DetectorParams::max_validation_iters (default 3).
See the Chessboard Detector chapter for the full invariant stack and failure-mode analysis.
Which crate does what
The chessboard detector algorithm is split across two crates:
projective-gridowns the pattern-agnostic machinery — BFS growth, KD-tree candidate search, circular- histogram peak picking (plateau-aware), double-angle 2-means, line / local-H validation. No calibration-specific dependencies; useful standalone.calib-targets-chessboardsupplies the chessboard-specific pieces that plug into the generic trait surface: ChESS-axis-based clustering,ClusterLabelparity, per-axis-slot edge validation, boosters. Orchestrates the end-to-end pipeline.
Output types are standardised in calib-targets-core as
TargetDetection with LabeledCorner values. Higher-level crates
enrich that output with additional metadata (marker detections,
rectified views, per-corner IDs).