calib_targets_charuco/detector/
pipeline.rs1use super::alignment_select::select_alignment;
2use super::corner_mapping::map_charuco_corners;
3use super::marker_sampling::{build_corner_map, build_marker_cells};
4use super::{CharucoDetectError, CharucoDetectionResult, CharucoDetectorParams};
5use crate::alignment::CharucoAlignment;
6use crate::board::{CharucoBoard, CharucoBoardError};
7use calib_targets_aruco::{scan_decode_markers_in_cells, MarkerDetection, Matcher};
8use calib_targets_chessboard::ChessboardDetector;
9use calib_targets_core::{Corner, GrayImageView};
10
11#[cfg(feature = "tracing")]
12use tracing::instrument;
13
14#[derive(Debug)]
16pub struct CharucoDetector {
17 board: CharucoBoard,
18 params: CharucoDetectorParams,
19 matcher: Matcher,
20}
21
22impl CharucoDetector {
23 pub fn new(mut params: CharucoDetectorParams) -> Result<Self, CharucoBoardError> {
25 let board_cfg = params.charuco;
26 if params.chessboard.expected_rows.is_none() {
27 params.chessboard.expected_rows = board_cfg.rows.checked_sub(1);
29 }
30 if params.chessboard.expected_cols.is_none() {
31 params.chessboard.expected_cols = board_cfg.cols.checked_sub(1);
32 }
33 if !params.scan.marker_size_rel.is_finite() || params.scan.marker_size_rel <= 0.0 {
34 params.scan.marker_size_rel = board_cfg.marker_size_rel;
35 }
36
37 let max_hamming = params
38 .max_hamming
39 .min(board_cfg.dictionary.max_correction_bits);
40 params.max_hamming = max_hamming;
41
42 let matcher = Matcher::new(board_cfg.dictionary, max_hamming);
43 let board = CharucoBoard::new(board_cfg)?;
44
45 Ok(Self {
46 board,
47 params,
48 matcher,
49 })
50 }
51
52 #[inline]
54 pub fn board(&self) -> &CharucoBoard {
55 &self.board
56 }
57
58 #[inline]
60 pub fn params(&self) -> &CharucoDetectorParams {
61 &self.params
62 }
63
64 #[cfg_attr(feature = "tracing", instrument(level = "info", skip(self, image, corners), fields(num_corners=corners.len())))]
69 pub fn detect(
70 &self,
71 image: &GrayImageView<'_>,
72 corners: &[Corner],
73 ) -> Result<CharucoDetectionResult, CharucoDetectError> {
74 let detector = ChessboardDetector::new(self.params.chessboard.clone())
75 .with_grid_search(self.params.graph.clone());
76 let chessboard = detector
77 .detect_from_corners(corners)
78 .ok_or(CharucoDetectError::ChessboardNotDetected)?;
79
80 let corner_map = build_corner_map(&chessboard.detection.corners, &chessboard.inliers);
81 let cells = build_marker_cells(&corner_map);
82
83 let scan_cfg = self.params.scan.clone();
84
85 let markers = scan_decode_markers_in_cells(
86 image,
87 &cells,
88 self.params.px_per_square,
89 &scan_cfg,
90 &self.matcher,
91 );
92
93 if markers.is_empty() {
94 return Err(CharucoDetectError::NoMarkers);
95 }
96
97 let (markers, alignment) = self
98 .select_and_refine_markers(markers)
99 .ok_or(CharucoDetectError::AlignmentFailed { inliers: 0usize })?;
100
101 if alignment.marker_inliers.len() < self.params.min_marker_inliers {
102 return Err(CharucoDetectError::AlignmentFailed {
103 inliers: alignment.marker_inliers.len(),
104 });
105 }
106
107 let detection = map_charuco_corners(&self.board, &chessboard.detection, &alignment);
108
109 Ok(CharucoDetectionResult {
110 detection,
111 markers,
112 alignment: alignment.alignment,
113 })
114 }
115
116 #[cfg_attr(feature = "tracing", instrument(level = "info", skip(self, markers),
117 fields(markers=markers.len())))]
118 fn select_and_refine_markers(
119 &self,
120 markers: Vec<MarkerDetection>,
121 ) -> Option<(Vec<MarkerDetection>, CharucoAlignment)> {
122 let (markers, alignment) = select_alignment(&self.board, markers)?;
124
125 Some((markers, alignment))
126 }
127}