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 shipped baseline 893-codeword profile (with an opt-in extended profile available for larger ID spaces), 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
- Stable baseline IDs plus opt-in extension — shipped
baseprofile keeps 893 stable IDs at minimum cyclic Hamming distance 2; opt-inextendedgrows capacity to 2180 IDs with a weaker minimum distance of 1 without introducing new polarity ambiguity beyond the shipped baseline - 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.5"
Rust Target Generation
The library can generate canonical target JSON plus printable SVG/PNG directly:
use ;
use Path;
let board = with_name.unwrap;
board.write_json_file.unwrap;
board
.write_target_svg
.unwrap;
board
.write_target_png
.unwrap;
render_target_svg returns the SVG as a string, and render_target_png returns an in-memory grayscale image::GrayImage when you want to avoid file I/O. write_target_png embeds the requested DPI as PNG print metadata.
Equivalent Command-Line Workflows
The Rust API above is equivalent to these command-line paths when you want the same artifact set from the terminal instead of from application code.
Rust CLI:
Python script from a repository checkout:
All three paths generate:
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 (Rust API, Rust CLI, Python script, and helper tools):
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.