///|
/// Verifies if outer ring of sampled grid is predominantly a black border.
pub fn verify_black_border(
sampled_grid : Image,
total_cells : Int,
black_threshold : Int,
) -> Bool {
let cell_w = sampled_grid.width.to_double() / total_cells.to_double()
let cell_h = sampled_grid.height.to_double() / total_cells.to_double()
let mut total_border_pixels = 0
let mut black_border_pixels = 0
for row in 0..= 0.75
}
///|
/// Extracts binary grid payload bits from inner cells of sampled grid.
pub fn sample_payload_bits(
sampled_grid : Image,
dict : MarkerDictionary,
binary_threshold : Int,
) -> Int64 {
let data_size = dict.grid_size
let total_cells = data_size + 2
let cell_w = sampled_grid.width.to_double() / total_cells.to_double()
let cell_h = sampled_grid.height.to_double() / total_cells.to_double()
let mut payload = 0L
for r in 0..= binary_threshold { 1L } else { 0L }
let bit_idx = r * data_size + c
if bit == 1L {
payload = payload | (1L << bit_idx)
}
}
}
payload
}
///|
/// Rotates 4 corners of a quad according to 90-degree rotation index.
pub fn rotate_quad_corners(quad : Quad, rotation_index : Int) -> Quad {
if quad.corners.length() != 4 {
return quad
}
let rot = (4 - rotation_index % 4) % 4
let rotated = [
quad.corners[rot],
quad.corners[(rot + 1) % 4],
quad.corners[(rot + 2) % 4],
quad.corners[(rot + 3) % 4],
]
{ corners: rotated }
}
///|
/// Full decoding pipeline for a single candidate quad.
pub fn decode_quad_marker(
src_img : Image,
quad : Quad,
dict : MarkerDictionary,
) -> MarkerDetection? {
let total_cells = dict.grid_size + 2
let sample_res = total_cells * 8
match warp_perspective_grid(src_img, quad, sample_res) {
Some(sampled) => {
let otsu_lvl = threshold_otsu(sampled)
let level = if otsu_lvl < 10 || otsu_lvl > 245 { 128 } else { otsu_lvl }
let binarized = threshold_global(sampled, level)
if !verify_black_border(binarized, total_cells, 128) {
return None
}
let payload_bits = sample_payload_bits(binarized, dict, 128)
match dict.find_best_match(payload_bits) {
Some((id, rot_idx, hamming_dist)) => {
let aligned_quad = rotate_quad_corners(quad, rot_idx)
Some({
id,
quad: aligned_quad,
pose: None,
rotation_index: rot_idx,
hamming_distance: hamming_dist,
})
}
None => None
}
}
None => None
}
}
///|
/// Detects all fiducial markers in an image using dictionary.
pub fn detect_markers(
img : Image,
dict : MarkerDictionary,
) -> Array[MarkerDetection] {
let binarized = threshold_adaptive(img, 31, 7)
let contours = find_contours(binarized, 20)
let min_area = (img.width * img.height).to_double() * 0.0005
let min_perim = (img.width + img.height).to_double() * 0.05
let quads = extract_quad_candidates(contours, min_area, min_perim)
let detections = Array::new()
for i in 0.. detections.push(det)
None => ()
}
}
detections
}