ringgrid
Pure-Rust detector for dense coded ring calibration targets on a hex lattice. Detects markers with subpixel edge precision, decodes 16-sector binary IDs from a 893-codeword codebook, fits ellipses via Fitzgibbon's direct method with RANSAC, corrects projective center bias, and estimates a board-to-image homography. No OpenCV dependency.
Key Features
- Subpixel edge detection — gradient-based radial sampling produces edge points fed to a direct ellipse fit, yielding subpixel-accurate marker localization
- Projective center correction — recovers the true projected center from inner/outer conic pencil geometry, correcting the systematic bias of ellipse-fit centers
- Consistency-first ID correction — verifies decoded IDs against local hex-lattice structure, clears contradictory IDs, and recovers safe missing IDs before global filtering
- 893 unique IDs — 16-sector binary codebook with minimum cyclic Hamming distance of 5, enabling reliable identification under noise and partial occlusion
- Distortion-aware — supports external camera models (Brown-Conrady) via the
PixelMappertrait, or blind single-parameter self-undistort estimation - Pure Rust — no C/C++ dependencies, no OpenCV bindings
Pipeline Stages
Named stage order:
proposal -> local fit/decode -> dedup -> projective center -> id_correction -> optional global filter -> optional completion -> final homography refit.
Installation
[]
= "0.1"
Fast Start: Generate Target JSON + Printable SVG/PNG
Target generation scripts are in the repository root (tools/).
Key knobs:
| Flag | What it controls | Typical value |
|---|---|---|
--board_mm |
Physical board side length (mm) | 200 |
--pitch_mm |
Marker spacing (mm) | 8 |
--n_images |
Number of synthetic images (0 for print-only) |
0 |
--print_dpi |
PNG raster resolution | 300 or 600 |
--print_margin_mm |
Extra white border | 3-10 |
--print_basename |
Output file basename | target_print |
This generates:
tools/out/target_faststart/board_spec.jsontools/out/target_faststart/target_print.svgtools/out/target_faststart/target_print.png
Use the generated JSON in detection:
use ;
use Path;
let board = from_json_file.unwrap;
let detector = new;
Complete step-by-step target generation docs (all flags/config fields):
Simple Detection
use ;
use Path;
let board = from_json_file.unwrap;
let image = open.unwrap.to_luma8;
let detector = new;
let result = detector.detect;
for marker in &result.detected_markers
With a marker diameter hint for better scale tuning:
# use ;
# use Path;
# let board = from_json_file.unwrap;
let detector = with_marker_diameter_hint;
Adaptive Scale Detection
For scenes with large marker size variation, use adaptive multi-scale methods:
# use ;
# use Path;
# let board = from_json_file.unwrap;
# let detector = new;
# let image = open.unwrap.to_luma8;
let result = detector.detect_adaptive;
let result = detector.detect_adaptive_with_hint;
let result = detector.detect_multiscale;
Which method to choose:
| Situation | Recommended call | Why |
|---|---|---|
| Marker size unknown / mixed near-far scene | detect_adaptive |
Probe + auto tier selection |
| Approximate diameter is known | detect_adaptive_with_hint(..., Some(d)) |
Skip probe and use focused two-tier bracket around d |
| Exact tier policy required (reproducible benchmarks) | detect_multiscale(..., tiers) |
Full explicit control over tier set |
| Size range is tight and throughput matters | detect |
Single-pass and fastest |
Inspect adaptive tiers before detecting:
# use ;
# use Path;
# let board = from_json_file.unwrap;
# let detector = new;
# let image = open.unwrap.to_luma8;
let tiers = detector.adaptive_tiers;
let result = detector.detect_multiscale;
Adaptive scale guide:
Detection with Camera Model
When camera intrinsics and distortion coefficients are known, use detect_with_mapper
for distortion-aware detection via a two-pass pipeline:
use ;
use Path;
let board = from_json_file.unwrap;
let image = open.unwrap.to_luma8;
let = image.dimensions;
let camera = CameraModel ;
let detector = new;
let result = detector.detect_with_mapper;
for marker in &result.detected_markers
Self-Undistort (No Calibration Required)
When camera calibration is unavailable, ringgrid can estimate a single-parameter division-model distortion correction from the detected markers:
use ;
use Path;
let board = from_json_file.unwrap;
let image = open.unwrap.to_luma8;
let mut cfg = from_target;
cfg.self_undistort.enable = true;
let detector = with_config;
let result = detector.detect;
if let Some = &result.self_undistort
Custom PixelMapper
Implement the PixelMapper trait to plug in any distortion model:
use PixelMapper;
;
Then use it with detector.detect_with_mapper(&image, &mapper).
Coordinate Frames
DetectedMarker.center— always raw image pixel coordinatesDetectedMarker.center_mapped— working-frame (undistorted) coordinates when a mapper is activeDetectedMarker.board_xy_mm— board-space marker coordinates in millimeters for valid decoded IDsDetectionResult.center_frame/homography_frame— explicit frame metadata
Documentation
- User Guide — comprehensive mdbook covering marker design, detection pipeline, mathematical foundations, and configuration
- API Reference — rustdoc for all public types
License
Licensed under either of:
- Apache License, Version 2.0 (
LICENSE-APACHE) - MIT license (
LICENSE-MIT)
at your option.