projective_grid/square/
rectify.rs1use crate::float_helpers::lit;
8use crate::homography::{estimate_homography, Homography};
9use crate::Float;
10use crate::GridCoords;
11use nalgebra::Point2;
12use std::collections::HashMap;
13
14#[non_exhaustive]
16#[derive(thiserror::Error, Debug)]
17pub enum GridRectifyError {
18 #[error("not enough grid corners with positions (need >= 4, got {got})")]
20 NotEnoughPoints {
21 got: usize,
23 },
24 #[error("homography estimation failed")]
26 HomographyFailed,
27 #[error("homography not invertible")]
29 NonInvertible,
30}
31
32#[derive(Clone, Debug)]
34pub struct SquareGridHomography<F: Float = f32> {
35 pub h_img_from_rect: Homography<F>,
37 pub h_rect_from_img: Homography<F>,
39 pub min_i: i32,
41 pub min_j: i32,
43 pub max_i: i32,
45 pub max_j: i32,
47 pub px_per_cell: F,
49 pub rect_width: usize,
51 pub rect_height: usize,
53}
54
55impl<F: Float> SquareGridHomography<F> {
56 pub fn from_corners(
62 corners: &HashMap<GridCoords, Point2<F>>,
63 px_per_cell: F,
64 margin_cells: F,
65 ) -> Result<Self, GridRectifyError> {
66 if corners.len() < 4 {
67 return Err(GridRectifyError::NotEnoughPoints { got: corners.len() });
68 }
69
70 let (mut min_i, mut min_j) = (i32::MAX, i32::MAX);
71 let (mut max_i, mut max_j) = (i32::MIN, i32::MIN);
72 for g in corners.keys() {
73 min_i = min_i.min(g.i);
74 min_j = min_j.min(g.j);
75 max_i = max_i.max(g.i);
76 max_j = max_j.max(g.j);
77 }
78
79 let mi = nalgebra::try_convert::<F, f64>((lit::<F>(min_i as f64) - margin_cells).floor())
80 .unwrap_or(min_i as f64) as i32;
81 let mj = nalgebra::try_convert::<F, f64>((lit::<F>(min_j as f64) - margin_cells).floor())
82 .unwrap_or(min_j as f64) as i32;
83 let ma = nalgebra::try_convert::<F, f64>((lit::<F>(max_i as f64) + margin_cells).ceil())
84 .unwrap_or(max_i as f64) as i32;
85 let mb = nalgebra::try_convert::<F, f64>((lit::<F>(max_j as f64) + margin_cells).ceil())
86 .unwrap_or(max_j as f64) as i32;
87
88 let w = lit::<F>((ma - mi) as f64) * px_per_cell;
89 let h = lit::<F>((mb - mj) as f64) * px_per_cell;
90 let rect_width =
91 nalgebra::try_convert::<F, f64>(w.round().max(F::one())).unwrap_or(1.0) as usize;
92 let rect_height =
93 nalgebra::try_convert::<F, f64>(h.round().max(F::one())).unwrap_or(1.0) as usize;
94
95 let mut rect_pts = Vec::with_capacity(corners.len());
96 let mut img_pts = Vec::with_capacity(corners.len());
97 for (g, &pos) in corners {
98 let x = lit::<F>((g.i - mi) as f64) * px_per_cell;
99 let y = lit::<F>((g.j - mj) as f64) * px_per_cell;
100 rect_pts.push(Point2::new(x, y));
101 img_pts.push(pos);
102 }
103
104 let h_img_from_rect =
105 estimate_homography(&rect_pts, &img_pts).ok_or(GridRectifyError::HomographyFailed)?;
106
107 let h_rect_from_img = h_img_from_rect
108 .inverse()
109 .ok_or(GridRectifyError::NonInvertible)?;
110
111 Ok(Self {
112 h_img_from_rect,
113 h_rect_from_img,
114 min_i: mi,
115 min_j: mj,
116 max_i: ma,
117 max_j: mb,
118 px_per_cell,
119 rect_width,
120 rect_height,
121 })
122 }
123
124 pub fn rect_to_img(&self, p_rect: Point2<F>) -> Point2<F> {
126 self.h_img_from_rect.apply(p_rect)
127 }
128
129 pub fn img_to_rect(&self, p_img: Point2<F>) -> Point2<F> {
131 self.h_rect_from_img.apply(p_img)
132 }
133}