calib_targets_core/rectify.rs
1use crate::{GrayImage, Homography};
2use nalgebra::Point2;
3
4/// A rectified (fronto-parallel) view of a detected board, plus the
5/// mapping back to the original image.
6///
7/// `#[non_exhaustive]`: construct with [`RectifiedView::new`].
8#[non_exhaustive]
9pub struct RectifiedView {
10 /// The rectified grayscale image.
11 pub rect: GrayImage,
12 /// Side length, in rectified pixels, of one board square.
13 pub px_per_square: f32,
14 /// Number of board squares spanned horizontally by `rect`.
15 pub cells_x: usize,
16 /// Number of board squares spanned vertically by `rect`.
17 pub cells_y: usize,
18 /// Maps rectified coordinates back into the original image.
19 pub rect_to_img: RectToImgMapper,
20}
21
22impl RectifiedView {
23 /// Create a rectified view from its image, cell geometry, and the
24 /// rectified-to-image mapping.
25 pub fn new(
26 rect: GrayImage,
27 px_per_square: f32,
28 cells_x: usize,
29 cells_y: usize,
30 rect_to_img: RectToImgMapper,
31 ) -> Self {
32 Self {
33 rect,
34 px_per_square,
35 cells_x,
36 cells_y,
37 rect_to_img,
38 }
39 }
40}
41
42/// Mapping from rectified-image coordinates back to original-image
43/// coordinates.
44#[non_exhaustive]
45pub enum RectToImgMapper {
46 /// A single global homography — appropriate when lens distortion is
47 /// negligible.
48 Global {
49 /// Homography mapping rectified coordinates to image coordinates.
50 h_img_from_rect: Homography,
51 },
52 /// A per-cell homography mesh — tolerates lens distortion that a
53 /// single global fit cannot.
54 Mesh {
55 /// Number of cells horizontally.
56 cells_x: usize,
57 /// Number of cells vertically.
58 cells_y: usize,
59 /// Side length of one cell in rectified pixels.
60 px_per_square: f32,
61 /// One homography per cell (row-major); `None` for cells whose
62 /// fit failed.
63 cell_h: Vec<Option<Homography>>,
64 },
65}
66
67impl RectToImgMapper {
68 /// Map a rectified-image point back to original-image coordinates.
69 ///
70 /// Returns `None` for the mesh mapper when `p_rect` falls outside the
71 /// cell grid or lands in a cell with no valid homography.
72 pub fn map(&self, p_rect: Point2<f32>) -> Option<Point2<f32>> {
73 match self {
74 RectToImgMapper::Global { h_img_from_rect } => Some(h_img_from_rect.apply(p_rect)),
75 RectToImgMapper::Mesh {
76 cells_x,
77 cells_y,
78 px_per_square,
79 cell_h,
80 } => {
81 let s = *px_per_square;
82 let ci = (p_rect.x / s).floor() as i32;
83 let cj = (p_rect.y / s).floor() as i32;
84 if ci < 0 || cj < 0 || ci >= *cells_x as i32 || cj >= *cells_y as i32 {
85 return None;
86 }
87 let idx = cj as usize * (*cells_x) + ci as usize;
88 let h = cell_h[idx].as_ref()?;
89 let local = Point2::new(p_rect.x - ci as f32 * s, p_rect.y - cj as f32 * s);
90 Some(h.apply(local))
91 }
92 }
93 }
94}