align3d/
camera.rs

1use nalgebra::Vector3;
2
3use super::transform::Transform;
4
5/// Camera intrinsic parameters.
6#[derive(Clone, Debug)]
7pub struct CameraIntrinsics {
8    /// Focal length and pixel scale in the X-axis.
9    pub fx: f64,
10    /// Focal length and pixel scale in the Y-axis.
11    pub fy: f64,
12    /// Camera X-center.
13    pub cx: f64,
14    /// Camera Y-center.
15    pub cy: f64,
16    /// Image width in pixels.
17    pub width: usize,
18    /// Image height in pixels.
19    pub height: usize,
20}
21
22impl CameraIntrinsics {
23    /// Create a new camera intrinsic parameters.
24    ///
25    /// # Arguments
26    ///
27    /// * fx: Focal length and pixel scale in the X-axis.
28    /// * fy: Focal length and pixel scale in the Y-axis.
29    /// * cx: Camera X-center.
30    /// * cy: Camera Y-center.
31    /// * width: Image width in pixels.
32    /// * height: Image height in pixels.
33    ///
34    /// # Returns
35    ///
36    /// * A new camera intrinsic parameters.
37    pub fn from_simple_intrinsic(
38        fx: f64,
39        fy: f64,
40        cx: f64,
41        cy: f64,
42        width: usize,
43        height: usize,
44    ) -> Self {
45        Self {
46            fx,
47            fy,
48            cx,
49            cy,
50            width,
51            height,
52        }
53    }
54
55    /// Project a 3D point into image space.
56    ///
57    /// # Arguments
58    ///
59    /// * point: The 3D point.
60    ///
61    /// # Returns
62    ///
63    /// * (x and y) coordinates.
64    pub fn project(&self, point: &Vector3<f32>) -> (f32, f32) {
65        let z = point[2];
66        (
67            point[0] * self.fx as f32 / z + self.cx as f32,
68            point[1] * self.fy as f32 / z + self.cy as f32,
69        )
70    }
71
72    /// Return the Jacobian of the projection.
73    /// The Jacobian is the matrix of partial derivatives of the projection function.
74    ///
75    /// # Arguments
76    ///
77    /// * point: The 3D point.
78    ///
79    /// # Returns
80    ///
81    /// * ((dx/dfx, dx/dfy), (dy/dfx, dy/dfy)
82    pub fn project_grad(&self, point: &Vector3<f32>) -> ((f32, f32), (f32, f32)) {
83        let z = point[2];
84        let zz = z * z;
85        (
86            (self.fx as f32 / z, -point[0] * self.fx as f32 / zz),
87            (self.fy as f32 / z, -point[1] * self.fy as f32 / zz),
88        )
89    }
90
91    /// Backproject a 2D point into 3D space.
92    ///
93    /// # Arguments
94    ///
95    /// * x: The x coordinate.
96    /// * y: The y coordinate.
97    /// * z: The depth.
98    ///
99    /// # Returns
100    ///
101    /// * The 3D point.
102    pub fn backproject(&self, x: f32, y: f32, z: f32) -> Vector3<f32> {
103        Vector3::new(
104            (x - self.cx as f32) * z / self.fx as f32,
105            (y - self.cy as f32) * z / self.fy as f32,
106            z,
107        )
108    }
109
110    /// Scale the camera parameters according to the given scale.
111    ///
112    /// # Arguments
113    ///
114    /// * scale: The scale factor.
115    ///
116    /// # Returns
117    ///
118    /// * A new camera with scaled parameters.
119    pub fn scale(&self, scale: f64) -> Self {
120        Self {
121            fx: self.fx * scale,
122            fy: self.fy * scale,
123            cx: self.cx * scale,
124            cy: self.cy * scale,
125            width: self.width,
126            height: self.height,
127        }
128    }
129
130    pub fn size(&mut self, width: usize, height: usize) {
131        self.width = width;
132        self.height = height;
133    }
134}
135/// A pinhole camera. It is defined by its intrinsic parameters and its pose in the world.
136#[derive(Clone, Debug)]
137pub struct PinholeCamera {
138    pub intrinsics: CameraIntrinsics,
139    pub camera_to_world: Transform,
140    world_to_camera: Transform,
141    width_f32: f32,
142    height_f32: f32,
143}
144
145impl PinholeCamera {
146    /// Create a new pinhole camera.
147    ///
148    /// # Arguments
149    ///
150    /// * intrinsics: The camera intrinsic parameters.
151    /// * camera_to_world: The camera pose in the world.
152    ///
153    /// # Returns
154    ///
155    /// * A new pinhole camera.
156    pub fn new(intrinsics: CameraIntrinsics, camera_to_world: Transform) -> Self {
157        let width_f32 = intrinsics.width as f32;
158        let height_f32 = intrinsics.height as f32;
159        Self {
160            intrinsics,
161            world_to_camera: camera_to_world.inverse(),
162            camera_to_world,
163            width_f32,
164            height_f32,
165        }
166    }
167
168    /// Project a 3D point into image space.
169    ///
170    /// # Arguments
171    ///
172    /// * point: The 3D point.
173    ///
174    /// # Returns
175    ///
176    /// * (x and y) coordinates.
177    pub fn project(&self, point: &Vector3<f32>) -> (f32, f32, f32) {
178        let point = self.world_to_camera.transform_vector(point);
179        let (u, v) = self.intrinsics.project(&point);
180        (u, v, point[2])
181    }
182
183    /// Returns the projected 3D point if it is visible in the image.
184    ///
185    /// # Arguments
186    ///
187    /// * point: The 3D point.
188    ///
189    /// # Returns
190    ///
191    /// * (x and y) coordinates if the point is visible, None otherwise.
192    pub fn project_to_image(&self, point: &Vector3<f32>) -> Option<(f32, f32, f32)> {
193        let (x, y, z) = self.project(point);
194        let (x, y) = (x.round(), y.round());
195
196        if x >= 0.0 && x < self.width_f32 && y >= 0.0 && y < self.height_f32 {
197            //Some((x, self.height_f32 - y, z))
198            Some((x, y, z))
199        } else {
200            None
201        }
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use crate::transform::Transform;
208
209    #[test]
210    pub fn test_project() {
211        let camera = super::PinholeCamera::new(
212            super::CameraIntrinsics::from_simple_intrinsic(50.0, 50.0, 0.0, 0.0, 100, 100),
213            Transform::eye(),
214        );
215
216        let point = nalgebra::Vector3::new(1.0, 1.0, 1.0);
217        let (x, y, _) = camera.project(&point);
218        assert_eq!(x, 50.0);
219        assert_eq!(y, 50.0);
220
221        let point = nalgebra::Vector3::new(1.0, 1.5, 1.0);
222        let (x, y, _) = camera.project(&point);
223        assert_eq!(x, 50.0);
224        assert_eq!(y, 75.0);
225    }
226
227    #[test]
228    pub fn test_project_to_image() {
229        let camera = super::PinholeCamera::new(
230            super::CameraIntrinsics::from_simple_intrinsic(50.0, 50.0, 0.0, 0.0, 100, 100),
231            Transform::eye(),
232        );
233
234        let point = nalgebra::Vector3::new(1.0, 1.0, 1.0);
235        let (x, y, _) = camera.project_to_image(&point).unwrap();
236        assert_eq!(x, 50.0);
237        assert_eq!(y, 50.0);
238
239        let point = nalgebra::Vector3::new(1.0, 1.5, 1.0);
240        let (x, y, _) = camera.project_to_image(&point).unwrap();
241        assert_eq!(x, 50.0);
242        assert_eq!(y, 75.0);
243    }
244}