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