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 max(σ₀, σ₁) ≤ topological.axis_align_tol_rad. 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.
3topological_gridoriented features + cluster centres → labelled componentsThe topological grid finder through expert::square::assemble_oriented2_components; this builder seam stops after generic component merge and preserves the axis-slot frame for chessboard parity/recovery.
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.

Why the prefilter reads σ (Stage 1)

The axis half of the prefilter is derived, not configured: it reuses advanced.topological.axis_align_tol_rad, the tolerance the grid builder’s cell test applies to those same axes. The argument is dimensional rather than empirical — an axis estimate whose own 1σ uncertainty exceeds the alignment window cannot answer the question the cell test asks of it, so letting it vote adds noise to the classification, not information. Because the gate is the tolerance it protects, the two cannot drift apart, and there is no separate constant to tune.

This replaces the pre-0.11.0 fit-residual ratio (fit_rms ≤ max_fit_rms_ratio · contrast), which is not expressible on chess-corners 1.0 — contrast and fit_rms no longer exist. Note that the builder’s own max_axis_sigma_rad is a looser backstop applied inside grid construction and is unrelated to admission.

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).

Multi-component dispatch

ChessboardDetector::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

ChessboardParams splits into a stable core (min_labeled_corners, max_components, min_corner_strength) plus an opt-in, non-semver advanced (ChessboardAdvancedTuning) 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 ChessboardParams::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.