Skip to main content

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}