Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chessboard pipeline

Composes the full grid stack: ChESS cornersaxis clusteringtopological gridrecovery & validation. Source of truth: crates/calib-targets-chessboard/docs/PIPELINE.md. Crate reference: The Chessboard Detector.

The chessboard detector takes a cloud of ChESS X-junction corners and produces an integer-labelled grid (i, j) → image position. It is the shared spine of every other target pipeline, and it is precision-anchored: every stage that can attach a label runs an axis / parity / edge invariant, and the mandatory final geometry check drops anything that slipped through. Wrong (i, j) labels are unrecoverable for downstream calibration; missing corners are acceptable — that asymmetry is the whole contract.

The six stages

The orchestrator is pipeline::detect_all_topological. The canonical stage map (mirror of the crate docs/PIPELINE.md):

#StageIn → OutWhat it does
1prefilterChESS corners → usable-flagged cornersKeep a corner iff strength ≥ min_corner_strength and fit_rms ≤ max_fit_rms_ratio · contrast. Weak corners are kept as positions with no-information axes (so indices stay stable) but cannot vote.
2cluster_axesstrong corners’ axes → {Θ₀ ≤ Θ₁} + per-corner slot labelThe generic axis clustering (histogram + plateau peak picking + double-angle 2-means), then the DiskFit slot-coherence repair (below).
3topological_gridoriented features + cluster centres → labelled componentsThe topological grid finder (detect_grid_all); its own post-build validation / residual / recovery are disabled — the chessboard owns those downstream.
4recover_componentsmerged components → boosted, re-merged gridPer-component cell-size estimate, then the recovery boosters (interior gap fill + line extrapolation with a per-axis directional edge scale), optional weak-cluster rescue, then merge_components_local. Every addition re-runs the axis / parity / edge-slot-swap invariants.
5final_geometry_checklabelled set → drop list + refuse flagMandatory, can only DROP. The shared drop_set precision pass: line collinearity + local-H residual + the topological wrong-label checks (skipped-corner edges, duplicate-pixel labels, frontier line-spacing smoothness) + the largest-component filter. Refuses if survivors < min_labeled_corners.
6outputsurviving set → ChessboardDetectionBuild a LabelledGrid and call normalize() (rebase min → (0, 0); canonicalise +u ≈ +x, +v ≈ +y; stable (v, u) sort). The lattice Coord{u,v} is the canonical grid-coordinate type, so it is copied straight onto each output corner.

Note on the output shape. ChessboardDetection is Coord{u,v}-based; what moved is where normalization lives — the rebase + canonicalise + sort algorithm is now owned by projective_grid::LabelledGrid::normalize, with the output stage merely calling it.

Key invariants

These hold across every stage that can attach a label, and are what make a miss recoverable but a false positive impossible:

  • Two grid directions. Clustering recovers {Θ₀, Θ₁} (≈ 90° apart) as the only global axis prior. All axis means use the undirected (cos 2θ, sin 2θ) accumulation — there is no Corner::orientation, only Corner.axes: [AxisEstimate; 2].
  • Parity / edge-slot-swap. A corner’s four cardinal neighbours sit at the opposite axis-slot parity by construction. Every attachment checks that the candidate edge crosses a slot-swap boundary, so a diagonal or skipped-corner attachment is rejected structurally, not by a magnitude threshold.
  • Geometry check can only subtract. Stage 5 never adds or relabels; a corner that survives every stage has been proven to sit at a real intersection.
  • Non-negative labels. Output rebases the labelled bbox minimum to (0, 0).

DiskFit slot-coherence repair (Stage 2)

The ChESS detector’s DiskFit mode can uniformly pick the wrong antipodal dark sector, reversing a corner’s (axes[0], axes[1]) ordering and breaking the parity invariant globally. A live recall safety-net (slot_coherence) detects this with a gross-imbalance gate, BFS-2-colours the clustered corners at cell spacing, and swaps the two AxisEstimate slots of whichever corners disagree. A bipartite-quality gate aborts the pass unless the 2-colouring is essentially perfect, so it can only add recall, never a wrong label. Under RingFit the split is already ~50/50 and the pass is a no-op.

Multi-component dispatch

Detector::detect_all is the multi-board entry point: it returns several ChessboardDetections (up to max_components) when one image contains physically distinct grids. Within a single image, the topological facade already merges connected components, so a single physical board split into disjoint sub-grids (e.g. ChArUco rows separated by markers) is reunited in label space by the Stage-4 merge. The precision contract holds per emitted component. The workspace explicitly does not support multiple separate physical boards in one frame.

Failure modes

Identify the stage from the serializable topological trace (pipeline::trace_topological, layered over the production path) and the final-check GeometryCheckTrace drop counters, then consult:

SymptomLikely stageKnob to tryNotes
No detection, no grid directionsStage 2 (clustering)min_peak_weight_fraction, peak_min_separation_degThe two grid axes never separated — common on very-bad-light frames.
No cell size / no seedStage 3 (topological)detect_chessboard_best with sweep_default()No quad assembled. Builder tolerances are internal.
Very few cornersStage 4 (recover)attach_search_rel, attach_axis_tol_deg, step_tol, edge_axis_tol_degGrid grew but couldn’t extend — common on heavily distorted views.
Many dropped cornersStage 5 (geometry check)geometry_check_local_h_tol_relInvariants found outliers; check the drop reasons.
Wrong (i, j) labelsneverFile a bug. The precision contract has been violated; do not tune around it.

Tuning

DetectorParams splits into a stable core (graph_build_algorithm [single-variant], min_labeled_corners, max_components, min_corner_strength) plus an opt-in, non-semver advanced (AdvancedTuning) block of per-stage knobs. Leave advanced unset unless a specific input fails and you have evidence for the change. For challenging images use detect_chessboard_best with DetectorParams::sweep_default() (three configs varying only recall-affecting tolerances; all preserve the precision invariants). The full knob table is in Tuning the Detector and the chessboard crate chapter.

Cross-references

  • The Chessboard Detector — the full invariant stack, the topological-trace diagnostics surface, and a quickstart.
  • crates/calib-targets-chessboard/docs/PIPELINE.md — the canonical stage map this page mirrors.
  • The downstream pipelines that build on this spine: PuzzleBoard, ChArUco, Marker board.