Code Decoding
After the outer ellipse is fitted, the detector samples the code band — the annular region between the inner and outer rings — to read the marker’s 16-sector binary code and match it against the active embedded codebook profile. The shipped default is the 893-codeword base profile; extended is explicit opt-in.
Code Band Sampling
The code band sits at a radius that is a configurable fraction of the outer ellipse size. The code_band_ratio parameter (default derived from marker geometry, typically ~0.74) defines where the sampling circle lies relative to the outer ellipse.
For each of the 16 angular sectors, the decoder:
- Computes the sector center angle:
θ_k = k × 2π/16fork = 0..15 - Samples pixel intensities at multiple points within the sector (oversampled in both angular and radial directions)
- Aggregates samples to produce a single intensity value per sector
This multi-sample approach provides robustness against noise, blur, and slight geometric inaccuracies in the ellipse fit.
Binarization
The 16 sector intensities are converted to binary using an iterative 2-means threshold:
- Initialize threshold at the mean of all sector intensities
- Split sectors into two groups (above/below threshold)
- Recompute threshold as the mean of the group means
- Repeat until convergence
This local thresholding adapts to the actual contrast of each marker, handling varying illumination across the image.
Cyclic Codebook Matching
The 16-bit binary word is matched against the selected embedded codebook profile with cyclic rotation search:
- For each of the 16 possible rotational offsets, compute the Hamming distance between the observed word and each codebook entry
- Also check the inverted (bitwise NOT) word at each rotation, handling both dark-on-light and light-on-dark contrast
- Select the best match: the (codeword, rotation, polarity) triple with minimum Hamming distance
The best match is accepted based on:
- Hamming distance (
best_dist): number of bit disagreements with the closest codeword - Margin (
margin): gap between the best and second-best Hamming distances - Decode confidence:
clamp(1 - dist/6) × clamp(margin / active_profile_min_cyclic_dist), a heuristic combining closeness and uniqueness. For the shippedbaseprofile, the minimum cyclic Hamming distance is2; for the opt-inextendedprofile it is1.
DecodeMetrics
The decoding stage produces a DecodeMetrics struct:
| Field | Type | Meaning |
|---|---|---|
observed_word | u16 | The raw 16-bit word before matching |
best_id | usize | Matched codebook entry ID |
best_rotation | u8 | Rotation offset (0–15 sectors) |
best_dist | u8 | Hamming distance to best match |
margin | u8 | Gap to second-best match |
decode_confidence | f32 | Combined confidence score in [0, 1] |
A best_dist of 0 means a perfect match. In the shipped base profile, minimum cyclic Hamming distance is 2, so a distance of 1 is still unambiguous. The opt-in extended profile weakens that minimum distance to 1 in exchange for more available IDs.
Source: marker/decode.rs, marker/codec.rs, marker/codebook.rs