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