1use crate::{charuco, chessboard, core, marker};
2use chess_corners::{find_chess_corners_image, ChessConfig, CornerDescriptor};
3use nalgebra::Point2;
4
5#[cfg(feature = "tracing")]
6use tracing::instrument;
7
8#[derive(thiserror::Error, Debug)]
10pub enum DetectError {
11 #[error("invalid grayscale image buffer length (expected {expected} bytes, got {got})")]
12 InvalidGrayBuffer { expected: usize, got: usize },
13
14 #[error("invalid grayscale image dimensions (width={width}, height={height})")]
15 InvalidGrayDimensions { width: u32, height: u32 },
16
17 #[error(transparent)]
18 CharucoBoard(#[from] charuco::CharucoBoardError),
19
20 #[error(transparent)]
21 CharucoDetect(#[from] charuco::CharucoDetectError),
22}
23
24pub fn default_chess_config() -> ChessConfig {
29 let mut cfg = ChessConfig::single_scale();
30 cfg.params.threshold_rel = 0.2;
31 cfg.params.nms_radius = 2;
32 cfg
33}
34
35pub fn gray_view(img: &::image::GrayImage) -> core::GrayImageView<'_> {
37 core::GrayImageView {
38 width: img.width() as usize,
39 height: img.height() as usize,
40 data: img.as_raw(),
41 }
42}
43
44#[cfg_attr(
46 feature = "tracing",
47 instrument(level = "info", skip(img, cfg), fields(width = img.width(), height = img.height()))
48)]
49pub fn detect_chess_corners_raw(
50 img: &::image::GrayImage,
51 cfg: &ChessConfig,
52) -> Vec<CornerDescriptor> {
53 find_chess_corners_image(img, cfg)
54}
55
56pub fn detect_corners(img: &::image::GrayImage, cfg: &ChessConfig) -> Vec<core::Corner> {
58 detect_chess_corners_raw(img, cfg)
59 .iter()
60 .map(adapt_chess_corner)
61 .collect()
62}
63
64pub fn detect_corners_default(img: &::image::GrayImage) -> Vec<core::Corner> {
66 let cfg = default_chess_config();
67 detect_corners(img, &cfg)
68}
69
70#[cfg_attr(
72 feature = "tracing",
73 instrument(
74 level = "info",
75 skip(img, chess_cfg, params),
76 fields(width = img.width(), height = img.height())
77 )
78)]
79pub fn detect_chessboard(
80 img: &::image::GrayImage,
81 chess_cfg: &ChessConfig,
82 params: chessboard::ChessboardParams,
83) -> Option<chessboard::ChessboardDetectionResult> {
84 let corners = detect_corners(img, chess_cfg);
85 let detector = chessboard::ChessboardDetector::new(params);
86 detector.detect_from_corners(&corners)
87}
88
89#[cfg_attr(
91 feature = "tracing",
92 instrument(
93 level = "info",
94 skip(img, chess_cfg, params),
95 fields(
96 width = img.width(),
97 height = img.height(),
98 board_rows = params.charuco.rows,
99 board_cols = params.charuco.cols
100 )
101 )
102)]
103pub fn detect_charuco(
104 img: &::image::GrayImage,
105 chess_cfg: &ChessConfig,
106 params: charuco::CharucoDetectorParams,
107) -> Result<charuco::CharucoDetectionResult, DetectError> {
108 let corners = detect_corners(img, chess_cfg);
109 let detector = charuco::CharucoDetector::new(params)?;
110 Ok(detector.detect(&gray_view(img), &corners)?)
111}
112
113pub fn detect_charuco_default(
115 img: &::image::GrayImage,
116 params: charuco::CharucoDetectorParams,
117) -> Result<charuco::CharucoDetectionResult, DetectError> {
118 let chess_cfg = default_chess_config();
119 detect_charuco(img, &chess_cfg, params)
120}
121
122#[cfg_attr(
124 feature = "tracing",
125 instrument(
126 level = "info",
127 skip(img, chess_cfg, params),
128 fields(width = img.width(), height = img.height())
129 )
130)]
131pub fn detect_marker_board(
132 img: &::image::GrayImage,
133 chess_cfg: &ChessConfig,
134 params: marker::MarkerBoardParams,
135) -> Option<marker::MarkerBoardDetectionResult> {
136 let corners = detect_corners(img, chess_cfg);
137 let detector = marker::MarkerBoardDetector::new(params);
138 detector.detect_from_image_and_corners(&gray_view(img), &corners)
139}
140
141pub fn detect_marker_board_default(
143 img: &::image::GrayImage,
144 params: marker::MarkerBoardParams,
145) -> Option<marker::MarkerBoardDetectionResult> {
146 let chess_cfg = default_chess_config();
147 detect_marker_board(img, &chess_cfg, params)
148}
149
150pub fn gray_image_from_slice(
152 width: u32,
153 height: u32,
154 pixels: &[u8],
155) -> Result<::image::GrayImage, DetectError> {
156 let w = usize::try_from(width).ok();
157 let h = usize::try_from(height).ok();
158 let Some((w, h)) = w.zip(h) else {
159 return Err(DetectError::InvalidGrayDimensions { width, height });
160 };
161 let Some(expected) = w.checked_mul(h) else {
162 return Err(DetectError::InvalidGrayDimensions { width, height });
163 };
164 if pixels.len() != expected {
165 return Err(DetectError::InvalidGrayBuffer {
166 expected,
167 got: pixels.len(),
168 });
169 }
170 ::image::GrayImage::from_raw(width, height, pixels.to_vec())
171 .ok_or(DetectError::InvalidGrayDimensions { width, height })
172}
173
174pub fn detect_chessboard_from_gray_u8(
175 width: u32,
176 height: u32,
177 pixels: &[u8],
178 chess_cfg: &ChessConfig,
179 params: chessboard::ChessboardParams,
180) -> Result<Option<chessboard::ChessboardDetectionResult>, DetectError> {
181 let img = gray_image_from_slice(width, height, pixels)?;
182 Ok(detect_chessboard(&img, chess_cfg, params))
183}
184
185pub fn detect_charuco_from_gray_u8(
186 width: u32,
187 height: u32,
188 pixels: &[u8],
189 chess_cfg: &ChessConfig,
190 params: charuco::CharucoDetectorParams,
191) -> Result<charuco::CharucoDetectionResult, DetectError> {
192 let img = gray_image_from_slice(width, height, pixels)?;
193 detect_charuco(&img, chess_cfg, params)
194}
195
196pub fn detect_marker_board_from_gray_u8(
197 width: u32,
198 height: u32,
199 pixels: &[u8],
200 chess_cfg: &ChessConfig,
201 params: marker::MarkerBoardParams,
202) -> Result<Option<marker::MarkerBoardDetectionResult>, DetectError> {
203 let img = gray_image_from_slice(width, height, pixels)?;
204 Ok(detect_marker_board(&img, chess_cfg, params))
205}
206
207fn adapt_chess_corner(c: &CornerDescriptor) -> core::Corner {
208 core::Corner {
209 position: Point2::new(c.x, c.y),
210 orientation: c.orientation,
211 orientation_cluster: None,
212 strength: c.response,
213 }
214}