Skip to main content

projective_grid/
grid_rectify.rs

1//! Single global homography from grid corners.
2//!
3//! Computes a global projective mapping between a rectified coordinate system
4//! (uniform grid spacing) and the original image. Suitable when lens distortion
5//! is negligible. For distorted images, prefer [`GridHomographyMesh`](crate::GridHomographyMesh).
6
7use 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/// A global homography mapping between rectified grid space and image space.
24#[derive(Clone, Debug)]
25pub struct GridHomography {
26    /// Maps rectified coordinates to image coordinates.
27    pub h_img_from_rect: Homography,
28    /// Maps image coordinates to rectified coordinates.
29    pub h_rect_from_img: Homography,
30    /// Grid bounding box (with margin) in corner index space.
31    pub min_i: i32,
32    pub min_j: i32,
33    pub max_i: i32,
34    pub max_j: i32,
35    /// Rectified pixels per grid cell.
36    pub px_per_cell: f32,
37    /// Rectified image dimensions.
38    pub rect_width: usize,
39    pub rect_height: usize,
40}
41
42impl GridHomography {
43    /// Compute a global homography from grid corners to a rectified coordinate system.
44    ///
45    /// - `corners`: map from grid index to image position.
46    /// - `px_per_cell`: rectified pixels per grid cell.
47    /// - `margin_cells`: extra margin around the grid bounding box (in cell units).
48    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    /// Map a point from rectified space to image space.
104    pub fn rect_to_img(&self, p_rect: Point2<f32>) -> Point2<f32> {
105        self.h_img_from_rect.apply(p_rect)
106    }
107
108    /// Map a point from image space to rectified space.
109    pub fn img_to_rect(&self, p_img: Point2<f32>) -> Point2<f32> {
110        self.h_rect_from_img.apply(p_img)
111    }
112}