1use crate::types::{ARLabelInfo, ARMarkerInfo2, ARdouble};
41use log::debug;
42
43pub const AR_AREA_MAX: i32 = 100000;
44pub const AR_AREA_MIN: i32 = 70;
45pub const AR_SQUARE_FIT_THRESH: f64 = 0.05;
46pub const AR_CHAIN_MAX: usize = 10000;
47pub const AR_SQUARE_MAX: usize = 30;
48
49#[derive(Debug, PartialEq, Clone, Copy)]
50pub enum ImageProcMode {
51 FrameImage = 0,
52 FieldImage = 1,
53}
54
55pub fn ar_detect_marker(
58 ar_handle: &mut crate::types::ARHandle,
59 frame: &crate::types::AR2VideoBufferT,
60) -> Result<(), &'static str> {
61 ar_handle.marker_num = 0;
62
63 let luma_buff = match &frame.buff_luma {
64 Some(b) => b.as_slice(),
65 None => return Err("AR2VideoBufferT requires buff_luma to be available"),
66 };
67
68 let color_buff = match &frame.buff {
69 Some(b) => b.as_slice(),
70 None => return Err("AR2VideoBufferT requires buff to be available"),
71 };
72
73 let thresh = ar_handle.ar_labeling_thresh as u8;
74 let label_mode = if ar_handle.ar_labeling_mode == 0 {
75 crate::labeling::LabelingMode::BlackRegion
76 } else {
77 crate::labeling::LabelingMode::WhiteRegion
78 };
79
80 let labeling_proc_mode = if ar_handle.ar_image_proc_mode == 0 {
81 crate::labeling::ImageProcMode::FrameImage
82 } else {
83 crate::labeling::ImageProcMode::FieldImage
84 };
85
86 crate::labeling::ar_labeling(
87 luma_buff,
88 ar_handle.xsize,
89 ar_handle.ysize,
90 label_mode,
91 thresh,
92 labeling_proc_mode,
93 &mut ar_handle.label_info,
94 ar_handle.ar_debug != 0
95 )?;
96
97 if ar_handle.ar_debug != 0 {
98 debug!("ar_labeling found {} labels.", ar_handle.label_info.label_num);
99 }
100
101 let image_proc_mode = if ar_handle.ar_image_proc_mode == 0 {
102 ImageProcMode::FrameImage
103 } else {
104 ImageProcMode::FieldImage
105 };
106
107 ar_detect_marker2(
108 ar_handle.xsize,
109 ar_handle.ysize,
110 &mut ar_handle.label_info,
111 image_proc_mode,
112 AR_AREA_MAX,
113 AR_AREA_MIN,
114 AR_SQUARE_FIT_THRESH,
115 &mut *ar_handle.marker_info2,
116 &mut ar_handle.marker2_num,
117 )?;
118
119 if ar_handle.ar_debug != 0 {
120 debug!("ar_detect_marker2 found {} square candidates.", ar_handle.marker2_num);
121 }
122
123 if ar_handle.ar_param_lt.is_null() {
124 return Err("ARParamLT is null in ARHandle");
125 }
126
127 let image_proc_mode2 = if ar_handle.ar_image_proc_mode == 0 {
128 ImageProcMode::FrameImage
129 } else {
130 ImageProcMode::FieldImage
131 };
132
133 let param_ltf = unsafe { &(*ar_handle.ar_param_lt).param_ltf };
134
135 let patt_handle_opt = if !ar_handle.patt_handle.is_null() {
136 Some(unsafe { &*ar_handle.patt_handle })
137 } else {
138 None
139 };
140
141 ar_get_marker_info(
142 color_buff,
143 ar_handle.xsize,
144 ar_handle.ysize,
145 ar_handle.ar_pixel_format,
146 &ar_handle.marker_info2[..],
147 ar_handle.marker2_num,
148 image_proc_mode2,
149 ar_handle.ar_pattern_detection_mode,
150 param_ltf,
151 ar_handle.patt_ratio,
152 patt_handle_opt,
153 &mut *ar_handle.marker_info,
154 &mut ar_handle.marker_num,
155 ar_handle.matrix_code_type,
156 )?;
157
158 if ar_handle.ar_debug != 0 {
159 debug!("ar_get_marker_info produced {} final markers.", ar_handle.marker_num);
160 }
161
162 Ok(())
163}
164
165pub fn ar_detect_marker2(
167 xsize: i32,
168 ysize: i32,
169 label_info: &mut ARLabelInfo,
170 image_proc_mode: ImageProcMode,
171 area_max: i32,
172 area_min: i32,
173 square_fit_thresh: ARdouble,
174 marker_info2: &mut [ARMarkerInfo2],
175 marker2_num: &mut i32,
176) -> Result<(), &'static str> {
177 let mut xsize_local = xsize;
178 let mut ysize_local = ysize;
179 let mut area_min_local = area_min;
180 let mut area_max_local = area_max;
181
182 if matches!(image_proc_mode, ImageProcMode::FieldImage) {
183 area_min_local /= 4;
184 area_max_local /= 4;
185 xsize_local /= 2;
186 ysize_local /= 2;
187 }
188
189 *marker2_num = 0;
190
191 let label_num = label_info.label_num as usize;
192 for i in 0..label_num {
193 if label_info.area[i] < area_min_local || label_info.area[i] > area_max_local {
194 debug!("Label {} skipped due to Area ({}) not in [{}, {}]", i, label_info.area[i], area_min_local, area_max_local);
195 continue;
196 }
197 if label_info.clip[i][0] <= 1 || label_info.clip[i][1] >= xsize_local - 2 {
198 debug!("Label {} skipped due to X-Clip bounds", i);
199 continue;
200 }
201 if label_info.clip[i][2] <= 1 || label_info.clip[i][3] >= ysize_local - 2 {
202 debug!("Label {} skipped due to Y-Clip bounds", i);
203 continue;
204 }
205
206 let mut current_marker = ARMarkerInfo2::default();
207
208 let ret = ar_get_contour(
209 &label_info.label_image,
210 xsize_local,
211 ysize_local,
212 &label_info.work,
213 (i + 1) as i32,
214 &label_info.clip[i],
215 &mut current_marker,
216 );
217
218 if ret.is_err() {
219 debug!("ar_get_contour failed for label {}: {:?}", i, ret.unwrap_err());
220 continue;
221 }
222
223 let ret = check_square(label_info.area[i], &mut current_marker, square_fit_thresh);
224 if ret.is_err() {
225 debug!("check_square failed for label {}: {:?}", i, ret.unwrap_err());
226 continue;
227 }
228
229 current_marker.area = label_info.area[i];
230 current_marker.pos[0] = label_info.pos[i][0];
231 current_marker.pos[1] = label_info.pos[i][1];
232
233 marker_info2[*marker2_num as usize] = current_marker;
234 *marker2_num += 1;
235 if *marker2_num as usize == marker_info2.len() {
236 break;
237 }
238 }
239
240 let num_markers = *marker2_num as usize;
242 for i in 0..num_markers {
243 for j in i + 1..num_markers {
244 if marker_info2[i].area == 0 || marker_info2[j].area == 0 {
245 continue;
246 }
247 let d = (marker_info2[i].pos[0] - marker_info2[j].pos[0]).powi(2)
248 + (marker_info2[i].pos[1] - marker_info2[j].pos[1]).powi(2);
249
250 if marker_info2[i].area > marker_info2[j].area {
251 if d < (marker_info2[i].area as ARdouble) / 4.0 {
252 marker_info2[j].area = 0;
253 }
254 } else {
255 if d < (marker_info2[j].area as ARdouble) / 4.0 {
256 marker_info2[i].area = 0;
257 }
258 }
259 }
260 }
261
262 let mut valid_count = 0;
264 for i in 0..num_markers {
265 if marker_info2[i].area > 0 {
266 if i != valid_count {
267 marker_info2[valid_count] = marker_info2[i].clone();
268 }
269 valid_count += 1;
270 }
271 }
272 *marker2_num = valid_count as i32;
273
274 if matches!(image_proc_mode, ImageProcMode::FieldImage) {
275 for i in 0..(*marker2_num as usize) {
276 let pm = &mut marker_info2[i];
277 pm.area *= 4;
278 pm.pos[0] *= 2.0;
279 pm.pos[1] *= 2.0;
280 for j in 0..pm.coord_num as usize {
281 pm.x_coord[j] *= 2;
282 pm.y_coord[j] *= 2;
283 }
284 }
285 }
286
287 Ok(())
288}
289
290fn ar_get_contour(
291 limage: &[crate::types::ARLabelingLabelType],
292 xsize: i32,
293 _ysize: i32,
294 label_ref: &[i32],
295 label: i32,
296 clip: &[i32; 4],
297 marker_info2: &mut ARMarkerInfo2,
298) -> Result<(), &'static str> {
299 let xdir = [0, 1, 1, 1, 0, -1, -1, -1];
300 let ydir = [-1, -1, 0, 1, 1, 1, 0, -1];
301
302 let mut sx = -1;
303 let sy = clip[2];
304
305 let mut p_idx = (sy * xsize + clip[0]) as usize;
306 for i in clip[0]..=clip[1] {
307 if p_idx < limage.len() {
308 let val = limage[p_idx];
309 if val > 0 && label_ref[(val - 1) as usize] == label {
310 sx = i;
311 break;
312 }
313 }
314 p_idx += 1;
315 }
316
317 if sx == -1 {
318 let mut row_dump = String::new();
319 let mut p_idx_d = (sy * xsize + clip[0]) as usize;
320 for _ in clip[0]..=clip[1] {
321 if p_idx_d < limage.len() {
322 let v = limage[p_idx_d];
323 if v > 0 {
324 row_dump.push_str(&format!("{}({}),", v, label_ref[(v - 1) as usize]));
325 }
326 }
327 p_idx_d += 1;
328 }
329 debug!("ar_get_contour failed. label={}. clip={:?}. Found on row: {}", label, clip, row_dump);
330 return Err("Contour start point not found");
331 }
332
333 marker_info2.coord_num = 1;
334 marker_info2.x_coord[0] = sx;
335 marker_info2.y_coord[0] = sy;
336 let mut dir = 5;
337
338 loop {
339 let last_idx = (marker_info2.coord_num - 1) as usize;
340 let p_idx = (marker_info2.y_coord[last_idx] * xsize + marker_info2.x_coord[last_idx]) as usize;
341
342 dir = (dir + 5) % 8;
343 let mut found = false;
344 for _ in 0..8 {
345 let next_idx = (p_idx as isize + ydir[dir] as isize * xsize as isize + xdir[dir] as isize) as usize;
346 if next_idx < limage.len() && limage[next_idx] > 0 {
347 found = true;
348 break;
349 }
350 dir = (dir + 1) % 8;
351 }
352
353 if !found {
354 return Err("Contour broken");
355 }
356
357 let curr_idx = marker_info2.coord_num as usize;
358 marker_info2.x_coord[curr_idx] = marker_info2.x_coord[last_idx] + xdir[dir];
359 marker_info2.y_coord[curr_idx] = marker_info2.y_coord[last_idx] + ydir[dir];
360
361 if marker_info2.x_coord[curr_idx] == sx && marker_info2.y_coord[curr_idx] == sy {
362 break;
363 }
364
365 marker_info2.coord_num += 1;
366 if marker_info2.coord_num as usize >= AR_CHAIN_MAX - 1 {
367 return Err("Contour too long");
368 }
369 }
370
371 let mut dmax = 0;
372 let mut v1 = 0;
373
374 for i in 1..marker_info2.coord_num as usize {
375 let d = (marker_info2.x_coord[i] - sx).pow(2) + (marker_info2.y_coord[i] - sy).pow(2);
376 if d > dmax {
377 dmax = d;
378 v1 = i;
379 }
380 }
381
382 let mut wx = vec![0; v1];
383 let mut wy = vec![0; v1];
384
385 for i in 0..v1 {
386 wx[i] = marker_info2.x_coord[i];
387 wy[i] = marker_info2.y_coord[i];
388 }
389
390 let coord_num = marker_info2.coord_num as usize;
391 for i in v1..coord_num {
392 marker_info2.x_coord[i - v1] = marker_info2.x_coord[i];
393 marker_info2.y_coord[i - v1] = marker_info2.y_coord[i];
394 }
395
396 let offset = coord_num - v1;
397 for i in 0..v1 {
398 marker_info2.x_coord[offset + i] = wx[i];
399 marker_info2.y_coord[offset + i] = wy[i];
400 }
401
402 let end_idx = marker_info2.coord_num as usize;
403 marker_info2.x_coord[end_idx] = marker_info2.x_coord[0];
404 marker_info2.y_coord[end_idx] = marker_info2.y_coord[0];
405 marker_info2.coord_num += 1;
406
407 Ok(())
408}
409
410fn check_square(area: i32, marker_info2: &mut ARMarkerInfo2, factor: ARdouble) -> Result<(), &'static str> {
411 let mut dmax = 0;
412 let mut v1 = 0;
413 let sx = marker_info2.x_coord[0];
414 let sy = marker_info2.y_coord[0];
415 let coord_num = marker_info2.coord_num as usize;
416
417 for i in 1..(coord_num - 1) {
418 let d = (marker_info2.x_coord[i] - sx).pow(2) + (marker_info2.y_coord[i] - sy).pow(2);
419 if d > dmax {
420 dmax = d;
421 v1 = i;
422 }
423 }
424
425 let thresh = ((area as f64) / 0.75) * 0.01 * factor;
426 let mut vertex = [0; 10];
427 vertex[0] = 0;
428 let mut wv1 = [0; 10];
429 let mut wvnum1 = 0;
430 let mut wv2 = [0; 10];
431 let mut wvnum2 = 0;
432
433 if get_vertex(&marker_info2.x_coord, &marker_info2.y_coord, 0, v1, thresh, &mut wv1, &mut wvnum1).is_err() {
434 return Err("Square check failed");
435 }
436 if get_vertex(&marker_info2.x_coord, &marker_info2.y_coord, v1, coord_num - 1, thresh, &mut wv2, &mut wvnum2).is_err() {
437 return Err("Square check failed");
438 }
439
440 if wvnum1 == 1 && wvnum2 == 1 {
441 vertex[1] = wv1[0];
442 vertex[2] = v1;
443 vertex[3] = wv2[0];
444 } else if wvnum1 > 1 && wvnum2 == 0 {
445 let v2 = v1 / 2;
446 wvnum1 = 0;
447 wvnum2 = 0;
448 if get_vertex(&marker_info2.x_coord, &marker_info2.y_coord, 0, v2, thresh, &mut wv1, &mut wvnum1).is_err() {
449 return Err("Square check failed");
450 }
451 if get_vertex(&marker_info2.x_coord, &marker_info2.y_coord, v2, v1, thresh, &mut wv2, &mut wvnum2).is_err() {
452 return Err("Square check failed");
453 }
454 if wvnum1 == 1 && wvnum2 == 1 {
455 vertex[1] = wv1[0];
456 vertex[2] = wv2[0];
457 vertex[3] = v1;
458 } else {
459 return Err("Not a square");
460 }
461 } else if wvnum1 == 0 && wvnum2 > 1 {
462 let v2 = (v1 + coord_num - 1) / 2;
463 wvnum1 = 0;
464 wvnum2 = 0;
465 if get_vertex(&marker_info2.x_coord, &marker_info2.y_coord, v1, v2, thresh, &mut wv1, &mut wvnum1).is_err() {
466 return Err("Square check failed");
467 }
468 if get_vertex(&marker_info2.x_coord, &marker_info2.y_coord, v2, coord_num - 1, thresh, &mut wv2, &mut wvnum2).is_err() {
469 return Err("Square check failed");
470 }
471 if wvnum1 == 1 && wvnum2 == 1 {
472 vertex[1] = v1;
473 vertex[2] = wv1[0];
474 vertex[3] = wv2[0];
475 } else {
476 return Err("Not a square");
477 }
478 } else {
479 return Err("Not a square");
480 }
481
482 marker_info2.vertex[0] = vertex[0] as i32;
483 marker_info2.vertex[1] = vertex[1] as i32;
484 marker_info2.vertex[2] = vertex[2] as i32;
485 marker_info2.vertex[3] = vertex[3] as i32;
486 marker_info2.vertex[4] = (coord_num - 1) as i32;
487
488 Ok(())
489}
490
491fn get_vertex(
492 x_coord: &[i32],
493 y_coord: &[i32],
494 st: usize,
495 ed: usize,
496 thresh: ARdouble,
497 vertex: &mut [usize],
498 vnum: &mut usize,
499) -> Result<(), &'static str> {
500 let a = (y_coord[ed] - y_coord[st]) as f64;
501 let b = (x_coord[st] - x_coord[ed]) as f64;
502 let c = (x_coord[ed] * y_coord[st] - y_coord[ed] * x_coord[st]) as f64;
503
504 let mut dmax = 0.0;
505 let mut v1 = st + 1;
506
507 for i in (st + 1)..ed {
508 let d = a * (x_coord[i] as f64) + b * (y_coord[i] as f64) + c;
509 if d * d > dmax {
510 dmax = d * d;
511 v1 = i;
512 }
513 }
514
515 if dmax / (a * a + b * b) > thresh {
516 if get_vertex(x_coord, y_coord, st, v1, thresh, vertex, vnum).is_err() {
517 return Err("Vertex expansion failed");
518 }
519
520 if *vnum > 5 {
521 return Err("Too many vertices");
522 }
523 vertex[*vnum] = v1;
524 *vnum += 1;
525
526 if get_vertex(x_coord, y_coord, v1, ed, thresh, vertex, vnum).is_err() {
527 return Err("Vertex expansion failed");
528 }
529 }
530
531 Ok(())
532}
533
534use crate::math::{ARMat, ARVec};
535use crate::types::{ARParamLTf, ARMarkerInfo};
536
537pub fn ar_get_line(
539 x_coord: &[i32],
540 y_coord: &[i32],
541 _coord_num: usize,
542 vertex: &[i32],
543 param_ltf: &ARParamLTf,
544 line: &mut [[ARdouble; 3]; 4],
545 v: &mut [[ARdouble; 2]; 4],
546) -> Result<(), &'static str> {
547 for i in 0..4 {
548 let w1 = ((vertex[i + 1] - vertex[i] + 1) as f64) * 0.05 + 0.5;
549 let st = (vertex[i] as f64 + w1) as usize;
550 let ed = (vertex[i + 1] as f64 - w1) as usize;
551 let n = ed - st + 1;
552
553 let mut input = ARMat::new(n as i32, 2);
554 for j in 0..n {
555 let (ix, iy) = param_ltf.observ2ideal(x_coord[st + j] as f32, y_coord[st + j] as f32)?;
556 input.m[j * 2 + 0] = ix as f64;
557 input.m[j * 2 + 1] = iy as f64;
558 }
559
560 let mut evec = ARMat::new(2, 2);
561 let mut ev = ARVec::new(2);
562 let mut mean = ARVec::new(2);
563
564 input.pca(&mut evec, &mut ev, &mut mean)?;
565
566 line[i][0] = evec.m[1];
567 line[i][1] = -evec.m[0];
568 line[i][2] = -(line[i][0] * mean.v[0] + line[i][1] * mean.v[1]);
569 }
570
571 for i in 0..4 {
572 let w1 = line[(i + 3) % 4][0] * line[i][1] - line[i][0] * line[(i + 3) % 4][1];
573 if w1.abs() < 0.0001 {
574 return Err("Lines are near parallel");
575 }
576 v[i][0] = (line[(i + 3) % 4][1] * line[i][2] - line[i][1] * line[(i + 3) % 4][2]) / w1;
577 v[i][1] = (line[i][0] * line[(i + 3) % 4][2] - line[(i + 3) % 4][0] * line[i][2]) / w1;
578 }
579
580 Ok(())
581}
582
583pub fn ar_get_marker_info(
585 image: &[u8],
586 xsize: i32,
587 ysize: i32,
588 pixel_format: crate::types::ARPixelFormat,
589 marker_info2: &[ARMarkerInfo2],
590 marker2_num: i32,
591 image_proc_mode: ImageProcMode,
592 patt_detect_mode: i32,
593 param_ltf: &ARParamLTf,
594 patt_ratio: ARdouble,
595 patt_handle_opt: Option<&crate::types::ARPattHandle>,
596 marker_info: &mut [ARMarkerInfo],
597 marker_num: &mut i32,
598 _matrix_code_type: crate::types::ARMatrixCodeType,
599) -> Result<(), &'static str> {
600 let mut j = 0;
601
602 for i in 0..marker2_num as usize {
603 marker_info[j].area = marker_info2[i].area;
604
605 if let Ok((ix, iy)) = param_ltf.observ2ideal(marker_info2[i].pos[0] as f32, marker_info2[i].pos[1] as f32) {
606 marker_info[j].pos[0] = ix as f64;
607 marker_info[j].pos[1] = iy as f64;
608 } else {
609 continue;
610 }
611
612 if ar_get_line(
613 &marker_info2[i].x_coord,
614 &marker_info2[i].y_coord,
615 marker_info2[i].coord_num as usize,
616 &marker_info2[i].vertex,
617 param_ltf,
618 &mut marker_info[j].line,
619 &mut marker_info[j].vertex,
620 ).is_err() {
621 continue;
622 }
623
624 if let Some(patt_handle) = patt_handle_opt {
625 if patt_handle.patt_num > 0 {
626 let patt_size = patt_handle.patt_size;
627 let ext_patt_len = if patt_detect_mode == crate::pattern::AR_TEMPLATE_MATCHING_COLOR {
628 (patt_size * patt_size * 3) as usize
629 } else {
630 (patt_size * patt_size) as usize
631 };
632 let mut ext_patt = vec![0u8; ext_patt_len];
633
634 let res = crate::pattern::ar_patt_get_image(
635 image_proc_mode as i32,
636 patt_detect_mode,
637 patt_size,
638 patt_size * 2, image,
640 xsize,
641 ysize,
642 pixel_format,
643 &marker_info[j].vertex,
644 patt_ratio,
645 &mut ext_patt,
646 );
647
648 if res.is_ok() {
649 let mut p_code = -1;
650 let mut p_dir = 0;
651 let mut p_cf = -1.0;
652 let match_res = crate::pattern::pattern_match(
653 patt_handle,
654 patt_detect_mode,
655 &ext_patt,
656 patt_size,
657 &mut p_code,
658 &mut p_dir,
659 &mut p_cf,
660 );
661
662 if match_res.is_ok() && p_code >= 0 {
663 marker_info[j].id = p_code;
664 marker_info[j].dir = p_dir;
665 marker_info[j].cf = p_cf;
666 } else {
667 marker_info[j].id = -1;
668 marker_info[j].dir = 0;
669 marker_info[j].cf = p_cf;
670 }
671 } else {
672 marker_info[j].id = -1;
673 marker_info[j].dir = 0;
674 marker_info[j].cf = -1.0;
675 }
676 } else {
677 marker_info[j].id = -1;
678 marker_info[j].dir = 0;
679 marker_info[j].cf = 0.0;
680 }
681 } else {
682 marker_info[j].id = -1;
683 marker_info[j].dir = 0;
684 marker_info[j].cf = 0.0;
685 }
686
687 j += 1;
688 }
689 *marker_num = j as i32;
690
691 Ok(())
692}
693
694#[cfg(test)]
695mod tests {
696 use super::*;
697
698 #[test]
699 fn test_ar_detect_marker2_empty() {
700 let mut label_info = ARLabelInfo::default();
701 let mut marker_info2 = vec![ARMarkerInfo2::default(); AR_SQUARE_MAX];
702 let mut marker2_num = 0;
703
704 let res = ar_detect_marker2(
705 640,
706 480,
707 &mut label_info,
708 ImageProcMode::FrameImage,
709 AR_AREA_MAX,
710 AR_AREA_MIN,
711 AR_SQUARE_FIT_THRESH,
712 &mut marker_info2,
713 &mut marker2_num,
714 );
715
716 assert!(res.is_ok());
717 assert_eq!(marker2_num, 0);
718 }
719}