///|
/// Checks if two quads represent the same marker by comparing corner distances.
pub fn are_quads_duplicate(
q1 : Quad,
q2 : Quad,
min_corner_dist : Double,
) -> Bool {
if q1.corners.length() != 4 || q2.corners.length() != 4 {
return false
}
let mut sum_dist = 0.0
for i in 0..<4 {
sum_dist += q1.corners[i].distance(q2.corners[i])
}
sum_dist / 4.0 < min_corner_dist
}
///|
/// Comprehensive Marker Detection Pipeline parameterized with DetectorParameters.
pub fn detect_markers_with_params(
img : Image,
dict : MarkerDictionary,
params : DetectorParameters,
camera_matrix? : CameraIntrinsics?,
marker_length? : Double = 0.05,
) -> Array[MarkerDetection] {
let w = img.width
let h = img.height
if w < 10 || h < 10 {
return []
}
let total_area = (w * h).to_double()
let min_perim = (w + h).to_double() * params.min_marker_perimeter_rate
let max_perim = (w + h).to_double() * params.max_marker_perimeter_rate
let min_area = total_area * 0.0002
let all_quad_candidates : Array[Quad] = []
// Multi-scale adaptive threshold loop
let mut win_size = params.adaptive_thresh_win_size_min
while win_size <= params.adaptive_thresh_win_size_max {
let binarized = threshold_adaptive(
img,
win_size,
params.adaptive_thresh_constant,
)
let contours = find_contours(binarized, 15)
for c in contours {
let perim = c.perimeter()
let area = c.area()
if perim >= min_perim && perim <= max_perim && area >= min_area {
let epsilon = perim * params.polygonal_approx_accuracy_rate
let approx = rdp_simplify(c.points, epsilon)
if approx.length() == 4 || approx.length() == 5 {
let four_pts = if approx.length() == 5 {
[approx[0], approx[1], approx[2], approx[3]]
} else {
approx
}
let quad = order_quad_corners(four_pts)
if is_quad_convex(quad) {
// Check distance to border
let bbox = quad.bounding_rect()
let min_b = params.min_distance_to_border.to_double()
if bbox.x >= min_b &&
bbox.y >= min_b &&
bbox.x + bbox.width <= w.to_double() - min_b &&
bbox.y + bbox.height <= h.to_double() - min_b {
all_quad_candidates.push(quad)
}
}
}
}
}
win_size += params.adaptive_thresh_win_size_step
}
// Remove duplicate quad candidates
let unique_quads : Array[Quad] = []
for q in all_quad_candidates {
let mut is_dup = false
let min_dist = q.perimeter() * params.min_corner_distance_rate
for u in unique_quads {
if are_quads_duplicate(q, u, min_dist) {
is_dup = true
break
}
}
if !is_dup {
unique_quads.push(q)
}
}
// Decode each unique quad candidate
let detections : Array[MarkerDetection] = []
let detected_ids : Array[Int] = []
for q in unique_quads {
// Optional corner refinement
let refined_quad = if params.corner_refinement_method == 1 {
refine_quad_corners(
img,
q,
window_radius=params.corner_refinement_win_size,
)
} else {
q
}
match decode_quad_marker(img, refined_quad, dict) {
Some(det) => {
// Check if ID already detected with lower Hamming distance
let mut replace_idx = -1
for i in 0.. estimate_marker_pose(det.quad, cam, marker_length)
_ => None
}
let final_det = {
id: det.id,
quad: det.quad,
pose,
rotation_index: det.rotation_index,
hamming_distance: det.hamming_distance,
}
if replace_idx >= 0 {
detections[replace_idx] = final_det
} else if replace_idx == -1 {
detections.push(final_det)
detected_ids.push(det.id)
}
}
None => ()
}
}
detections
}