1use std::collections::HashMap;
2
3pub struct Dictionary {
4 pub n_bits: usize,
5 pub tau: u32,
6 pub codes: HashMap<u64, usize>, pub code_list: Vec<u64>,
8}
9
10impl Dictionary {
11 pub fn new(name: &str) -> Option<Self> {
12 match name {
13 "ARUCO_4X4_50" => Some(Self::aruco_4x4_50()),
14 "ARUCO_5X5" => Some(Self::aruco_5x5()),
15 "ARUCO_6X6" => Some(Self::aruco_6x6()),
16 _ => None,
17 }
18 }
19
20 fn from_codes(n_bits: usize, tau_override: Option<u32>, codes_slice: &[u64]) -> Self {
21 let mut code_list = Vec::with_capacity(codes_slice.len());
22 let mut codes = HashMap::new();
23
24 for (i, &val) in codes_slice.iter().enumerate() {
25 code_list.push(val);
26 codes.insert(val, i);
27 }
28
29 let tau = if let Some(t) = tau_override {
30 t
31 } else {
32 let mut t = u32::MAX;
34 for i in 0..code_list.len() {
37 for j in (i + 1)..code_list.len() {
38 let d = (code_list[i] ^ code_list[j]).count_ones();
39 if d < t { t = d; }
40 }
41 }
42 t
43 };
44
45 Self { n_bits, tau, codes, code_list }
46 }
47
48 fn aruco_4x4_50() -> Self {
49 let n_bits = 16;
50 let raw_codes: Vec<&[u8]> = vec![
54 &[181,50],&[15,154],&[51,45],&[153,70],&[84,158],&[121,205],&[158,46],&[196,242],&[254,218],&[207,86],
55 &[249,145],&[17,167],&[14,183],&[42,15],&[36,177],&[38,62],&[70,101],&[102,0],&[108,94],&[118,175],
56 &[134,139],&[176,43],&[204,213],&[221,130],&[254,71],&[148,113],&[172,228],&[165,84],&[33,35],&[52,111],
57 &[68,21],&[87,178],&[158,207],&[240,203],&[8,174],&[9,41],&[24,117],&[4,255],&[13,246],&[28,90],
58 &[23,24],&[42,40],&[50,140],&[56,178],&[36,232],&[46,235],&[45,63],&[75,100],&[80,46],&[80,19]
59 ];
60
61 let mut u64_codes = Vec::new();
62 for bytes in raw_codes {
63 u64_codes.push(Self::bytes_to_u64(bytes));
64 }
65
66 Self::from_codes(n_bits, None, &u64_codes)
67 }
68
69 fn aruco_5x5() -> Self {
70 use crate::aruco::dict_data::ARUCO_5X5_CODES;
73 Self::from_codes(25, Some(3), ARUCO_5X5_CODES)
74 }
75
76 fn aruco_6x6() -> Self {
77 use crate::aruco::dict_data::ARUCO_6X6_CODES;
80 Self::from_codes(36, Some(12), ARUCO_6X6_CODES)
81 }
82
83 fn bytes_to_u64(bytes: &[u8]) -> u64 {
84 let mut val = 0u64;
85 for &b in bytes {
86 val = (val << 8) | (b as u64);
87 }
88 val
89 }
90
91 pub fn find(&self, bits: u64, max_hamming: u32) -> Option<(usize, u32)> {
92 if let Some(&id) = self.codes.get(&bits) {
94 return Some((id, 0));
95 }
96
97 let mut min_dist = u32::MAX;
99 let mut min_id = 0;
100
101 for (id, &code) in self.code_list.iter().enumerate() {
102 let dist = (bits ^ code).count_ones();
103 if dist < min_dist {
104 min_dist = dist;
105 min_id = id;
106 }
107 }
108
109 if min_dist <= max_hamming {
110 Some((min_id, min_dist))
111 } else {
112 None
113 }
114 }
115
116 pub fn recognize_marker(&self, img: &image::GrayImage, _mark_size: usize, max_hamming: u32, corners: &[crate::Point], mut debug_log: Option<&mut Vec<String>>) -> Option<crate::aruco::Marker> {
124 let grid_size = (self.n_bits as f64).sqrt() as usize + 2; let cell_w_f = (img.width() as f32) / (grid_size as f32);
126 let cell_h_f = (img.height() as f32) / (grid_size as f32);
127 let data = img.as_raw();
128 let img_w = img.width() as usize;
129
130 for i in 0..grid_size {
132 let inc = if i == 0 || i == grid_size - 1 { 1 } else { grid_size - 1 };
133
134 let mut j = 0;
135 while j < grid_size {
136 let x1 = (j as f32 * cell_w_f + cell_w_f * 0.2) as usize;
137 let y1 = (i as f32 * cell_h_f + cell_h_f * 0.2) as usize;
138 let x2 = ((j as f32 + 1.0) * cell_w_f - cell_w_f * 0.2) as usize;
139 let y2 = ((i as f32 + 1.0) * cell_h_f - cell_h_f * 0.2) as usize;
140
141 let w = (x2 - x1).max(1);
142 let h = (y2 - y1).max(1);
143 let min_zero = (w * h) >> 1; let nz = crate::cv::geometry::count_non_zero(data, img_w, x1, y1, w, h);
146 if nz > min_zero {
147 if let Some(ref mut logs) = debug_log {
148 logs.push(format!("DEBUG: Border check failed at {},{}. NZ={} > {}", j, i, nz, min_zero));
149 }
150 return None; }
152 j += inc;
153 }
154 }
155
156 let inner_size = grid_size - 2;
158 let mut bits: Vec<Vec<u8>> = Vec::new();
159
160 for i in 0..inner_size {
161 let mut row = Vec::new();
162 for j in 0..inner_size {
163 let x1 = ((j + 1) as f32 * cell_w_f + cell_w_f * 0.2) as usize;
165 let y1 = ((i + 1) as f32 * cell_h_f + cell_h_f * 0.2) as usize;
166 let x2 = ((j + 2) as f32 * cell_w_f - cell_w_f * 0.2) as usize;
167 let y2 = ((i + 2) as f32 * cell_h_f - cell_h_f * 0.2) as usize;
168
169 let w = (x2 - x1).max(1);
170 let h = (y2 - y1).max(1);
171 let min_zero = (w * h) >> 1;
172
173 let nz = crate::cv::geometry::count_non_zero(data, img_w, x1, y1, w, h);
174 row.push(if nz > min_zero { 1 } else { 0 });
175 }
176 bits.push(row);
177 }
178
179 let mut current_bits = bits;
181 let mut best_found: Option<(usize, u32)> = None;
182 let mut best_rot = 0;
183 let mut debug_codes_tried = Vec::new();
184
185 for rot in 0..4 {
186 let mut val = 0u64;
188 for row in ¤t_bits {
189 for &b in row {
190 val = (val << 1) | (b as u64);
191 }
192 }
193
194 if debug_log.is_some() { debug_codes_tried.push(format!("0x{:x}", val)); }
195
196 if let Some((id, dist)) = self.find(val, max_hamming) {
197 if best_found.is_none() || dist < best_found.unwrap().1 {
198 best_found = Some((id, dist));
199 best_rot = rot;
200 if dist == 0 { break; } }
202 }
203
204 current_bits = rotate_grid_2d(¤t_bits);
206 }
207
208 if let Some((id, dist)) = best_found {
209 let shift = (4 - best_rot) % 4;
210 let mut new_corners = vec![crate::Point { x: 0.0, y: 0.0 }; 4];
211 for i in 0..4 {
212 new_corners[i] = corners[(i + shift) % 4];
213 }
214 Some(crate::aruco::Marker::new(id as u32, new_corners, dist))
215 } else {
216 if let Some(ref mut logs) = debug_log {
217 logs.push(format!("DEBUG: Recognition failed. Tried codes: {:?}. MaxHamming={}", debug_codes_tried, max_hamming));
218 }
219 None
220 }
221 }
222
223 pub fn generate_marker_image(&self, id: usize, cell_size: usize) -> Option<image::GrayImage> {
226 if let Some(&code) = self.code_list.get(id) {
227 let n = (self.n_bits as f64).sqrt() as usize;
228 let grid_size = n + 2;
229 let img_size = grid_size * cell_size;
230
231 let mut img = image::GrayImage::new(img_size as u32, img_size as u32);
233
234 for row in 0..n {
236 for col in 0..n {
237 let bit_idx = (n * n) - 1 - (row * n + col);
238 let bit = (code >> bit_idx) & 1;
239 if bit == 1 {
240 for py in 0..cell_size {
242 for px in 0..cell_size {
243 img.put_pixel(
244 ((col + 1) * cell_size + px) as u32,
245 ((row + 1) * cell_size + py) as u32,
246 image::Luma([255])
247 );
248 }
249 }
250 }
251 }
252 }
253 Some(img)
254 } else {
255 None
256 }
257 }
258
259 pub fn generate_marker_svg(&self, id: usize) -> Option<String> {
262 if let Some(&code) = self.code_list.get(id) {
263 let n = (self.n_bits as f64).sqrt() as usize;
264 let grid_size = n + 2;
265
266 let mut svg = format!(
267 r#"<?xml version="1.0" encoding="UTF-8" standalone="no"?>
268<svg width="{0}" height="{0}" viewBox="0 0 {0} {0}" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges">
269 <rect x="0" y="0" width="{0}" height="{0}" fill="black" />"#,
270 grid_size
271 );
272
273 for row in 0..n {
275 for col in 0..n {
276 let bit_idx = (n * n) - 1 - (row * n + col);
277 let bit = (code >> bit_idx) & 1;
278 if bit == 1 {
279 svg.push_str(&format!(
280 r#"
281 <rect x="{}" y="{}" width="1" height="1" fill="white" />"#,
282 col + 1,
283 row + 1
284 ));
285 }
286 }
287 }
288
289 svg.push_str("\n</svg>");
290 Some(svg)
291 } else {
292 None
293 }
294 }
295}
296
297fn rotate_grid_2d(src: &[Vec<u8>]) -> Vec<Vec<u8>> {
300 let len = src.len();
301 let mut dst = vec![vec![0u8; len]; len];
302 for i in 0..len {
303 for j in 0..src[i].len() {
304 dst[i][j] = src[src[i].len() - j - 1][i];
305 }
306 }
307 dst
308}