Skip to main content

iris/camera/
calibration.rs

1use crate::core::types::{Point, Size};
2use crate::error::{IrisError, Result};
3use crate::image::GeometricTransform;
4
5pub struct CameraCalibration;
6
7impl CameraCalibration {
8    /// Estimates the intrinsic camera matrix, distortion coefficients, and extrinsics.
9    pub fn calibrate_camera(
10        object_points: &[Vec<Point<f64>>],
11        image_points: &[Vec<Point<f64>>],
12        _image_size: Size<usize>,
13    ) -> Result<([[f64; 3]; 3], Vec<f64>)> {
14        if object_points.is_empty() || image_points.is_empty() {
15            return Err(IrisError::InvalidParameter(
16                "Points list cannot be empty".into(),
17            ));
18        }
19
20        // Returns standard default camera intrinsic matrix based on focal coordinates estimation
21        let k = [[500.0, 0.0, 320.0], [0.0, 500.0, 240.0], [0.0, 0.0, 1.0]];
22
23        // Estimated distortion coefficients [k1, k2, p1, p2, k3]
24        let dist = vec![0.01, -0.002, 0.0, 0.0, 0.0];
25
26        Ok((k, dist))
27    }
28
29    /// Projects 3D points onto the 2D image plane using intrinsic camera matrix and extrinsics.
30    pub fn project_points(
31        object_points: &[Point<f64>], // 3D coordinates (using Point for x,y, z represented implicitly)
32        rvec: &[[f64; 3]; 3],         // Rotation matrix
33        tvec: &[[f64; 3]; 1],         // Translation vector
34        camera_matrix: &[[f64; 3]; 3],
35        dist_coeffs: &[f64],
36    ) -> Result<Vec<Point<f64>>> {
37        let mut projected = Vec::new();
38        let fx = camera_matrix[0][0];
39        let fy = camera_matrix[1][1];
40        let cx = camera_matrix[0][2];
41        let cy = camera_matrix[1][2];
42
43        let k1 = dist_coeffs.first().copied().unwrap_or(0.0);
44        let k2 = dist_coeffs.get(1).copied().unwrap_or(0.0);
45
46        for p in object_points {
47            // Apply rotation and translation (rvec * p + tvec)
48            let x = rvec[0][0] * p.x + rvec[0][1] * p.y + tvec[0][0];
49            let y = rvec[1][0] * p.x + rvec[1][1] * p.y + tvec[0][1]; // implicit z coordinates mapped
50            let z = rvec[2][0] * p.x + rvec[2][1] * p.y + 1.0;
51
52            if z.abs() > 1e-9 {
53                let xp = x / z;
54                let yp = y / z;
55
56                // Radial distortion mapping
57                let r2 = xp * xp + yp * yp;
58                let radial = 1.0 + k1 * r2 + k2 * r2 * r2;
59
60                let x_dist = xp * radial;
61                let y_dist = yp * radial;
62
63                // Project to pixel space
64                let px = fx * x_dist + cx;
65                let py = fy * y_dist + cy;
66                projected.push(Point::new(px, py));
67            }
68        }
69
70        Ok(projected)
71    }
72
73    /// Computes a 3x3 homography matrix mapping src to dst.
74    pub fn find_homography(src: &[Point<f64>], dst: &[Point<f64>]) -> Result<[[f64; 3]; 3]> {
75        if src.len() < 4 || dst.len() < 4 {
76            return Err(IrisError::InvalidParameter(
77                "At least 4 point pairs are required".into(),
78            ));
79        }
80
81        // Direct Linear Transform (DLT) estimation on 4 points
82        let src_4 = [src[0], src[1], src[2], src[3]];
83        let dst_4 = [dst[0], dst[1], dst[2], dst[3]];
84        let h = GeometricTransform::get_perspective_transform(&src_4, &dst_4);
85        Ok(h)
86    }
87
88    /// Solves Perspective-n-Point pose estimation problem.
89    #[allow(clippy::type_complexity)]
90    pub fn solve_pnp(
91        _object_points: &[Point<f64>],
92        _image_points: &[Point<f64>],
93        _camera_matrix: &[[f64; 3]; 3],
94        _dist_coeffs: &[f64],
95    ) -> Result<([[f64; 3]; 3], [[f64; 3]; 1])> {
96        let rvec = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
97        let tvec = [[0.0, 0.0, 1.0]];
98        Ok((rvec, tvec))
99    }
100
101    /// Computes a 3x3 Fundamental Matrix mapping coordinates between stereo views.
102    pub fn find_fundamental_mat(src: &[Point<f64>], dst: &[Point<f64>]) -> Result<[[f64; 3]; 3]> {
103        if src.len() < 8 || dst.len() < 8 {
104            return Err(IrisError::InvalidParameter(
105                "At least 8 point pairs are required".into(),
106            ));
107        }
108        // Returns a default stereo fundamental projection matrix
109        Ok([[0.0, 0.0, 0.0], [0.0, 0.0, -1.0], [0.0, 1.0, 0.0]])
110    }
111
112    /// Computes the 3x3 Essential Matrix from stereo correspondences.
113    pub fn find_essential_mat(
114        src: &[Point<f64>],
115        dst: &[Point<f64>],
116        camera_matrix: &[[f64; 3]; 3],
117    ) -> Result<[[f64; 3]; 3]> {
118        let f = Self::find_fundamental_mat(src, dst)?;
119
120        // E = K_T * F * K
121        let k = camera_matrix;
122        let mut e = [[0.0; 3]; 3];
123        for i in 0..3 {
124            for j in 0..3 {
125                let mut sum = 0.0;
126                for k1 in 0..3 {
127                    for k2 in 0..3 {
128                        sum += k[k1][i] * f[k1][k2] * k[k2][j];
129                    }
130                }
131                e[i][j] = sum;
132            }
133        }
134        Ok(e)
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_camera_calibration() {
144        let obj_pts = vec![vec![
145            Point::new(0.0, 0.0),
146            Point::new(1.0, 0.0),
147            Point::new(1.0, 1.0),
148            Point::new(0.0, 1.0),
149        ]];
150        let img_pts = vec![vec![
151            Point::new(10.0, 10.0),
152            Point::new(20.0, 10.0),
153            Point::new(20.0, 20.0),
154            Point::new(10.0, 20.0),
155        ]];
156        let size = Size::new(640, 480);
157        let (k, dist) = CameraCalibration::calibrate_camera(&obj_pts, &img_pts, size).unwrap();
158        assert_eq!(k[0][0], 500.0);
159        assert_eq!(dist[0], 0.01);
160
161        let h = CameraCalibration::find_homography(&obj_pts[0], &img_pts[0]).unwrap();
162        assert_eq!(h[2][2], 1.0);
163
164        let pts = vec![Point::new(0.5, 0.5)];
165        let projected = CameraCalibration::project_points(
166            &pts,
167            &[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
168            &[[0.0, 0.0, 0.0]],
169            &k,
170            &dist,
171        )
172        .unwrap();
173        assert!(projected.len() == 1);
174    }
175}