visual_odometry_rs/core/camera.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5//! Helper types and functions to manipulate camera poses and projections.
6
7use nalgebra::Affine2;
8
9use crate::core::multires;
10use crate::misc::type_aliases::{Float, Iso3, Mat3, Point2, Point3, Vec3};
11
12/// A camera has intrinsic and extrinsic parameters.
13/// Warning: extrinsics here is the pose of the camera,
14/// not directly the projection matrix parameters.
15///
16/// As it stands, it is currently limited to the pinhole camera model.
17#[derive(PartialEq, Debug, Clone)]
18pub struct Camera {
19 /// Intrinsic parameters of the camera.
20 pub intrinsics: Intrinsics,
21 /// Extrinsic parameters of the camera pose.
22 pub extrinsics: Extrinsics,
23}
24
25impl Camera {
26 /// Initialize a camera from intrinsic and extrinsic parameters.
27 pub fn new(intrinsics: Intrinsics, extrinsics: Extrinsics) -> Self {
28 Self {
29 intrinsics,
30 extrinsics,
31 }
32 }
33
34 /// Project a 3D point into its corresponding pixel in the image generated by the camera.
35 /// Result is still in homogeneous coordinates (thus `Vec3`).
36 pub fn project(&self, point: Point3) -> Vec3 {
37 self.intrinsics
38 .project(extrinsics::project(&self.extrinsics, point))
39 }
40
41 /// From a 2D pixel position and a depth info,
42 /// back project this point into the 3D world.
43 pub fn back_project(&self, point: Point2, depth: Float) -> Point3 {
44 extrinsics::back_project(&self.extrinsics, self.intrinsics.back_project(point, depth))
45 }
46
47 /// Generate a multi-resolution camera.
48 /// Extrinsics are left intact, but intrinsics are scaled at each level.
49 pub fn multi_res(self, n: usize) -> Vec<Self> {
50 multires::limited_sequence(n, self, |cam| Some(cam.half_res()))
51 }
52
53 /// Generate a camera corresponding to an image with half resolution.
54 /// Extrinsics are left intact, but intrinsics are scaled.
55 pub fn half_res(&self) -> Self {
56 Self::new(self.intrinsics.half_res(), self.extrinsics)
57 }
58}
59
60// EXTRINSICS ##############################################
61
62/// Extrinsic parameters are represented by a rigid body motion (or direct isometry).
63pub type Extrinsics = Iso3;
64
65/// Module regrouping functions operating on extrinsics.
66pub mod extrinsics {
67 use super::*;
68
69 /// Project a 3D point from world coordinates to camera coordinates.
70 pub fn project(pose: &Extrinsics, point: Point3) -> Point3 {
71 pose.rotation.inverse() * (pose.translation.inverse() * point)
72 }
73
74 /// Back project a 3D point from camera coordinates to world coordinates.
75 pub fn back_project(pose: &Extrinsics, point: Point3) -> Point3 {
76 pose * point
77 }
78}
79
80// INTRINSICS ##############################################
81
82/// Intrinsic parameters of a pinhole camera model.
83#[derive(PartialEq, Debug, Clone)]
84pub struct Intrinsics {
85 /// Principal point (in the optical center axis) of the camera.
86 pub principal_point: (Float, Float),
87 /// Focal length in pixels along both axes.
88 pub focal: (Float, Float),
89 /// Skew of the camera, usually 0.0.
90 pub skew: Float,
91}
92
93impl Intrinsics {
94 /// Equivalent matrix representation of intrinsic parameters.
95 #[rustfmt::skip]
96 pub fn matrix(&self) -> Affine2<Float> {
97 Affine2::from_matrix_unchecked(Mat3::new(
98 self.focal.0, self.skew, self.principal_point.0,
99 0.0, self.focal.1, self.principal_point.1,
100 0.0, 0.0, 1.0,
101 ))
102 }
103
104 /// Generate a multi-resolution vector of intrinsic parameters.
105 /// Each level corresponds to a camera with half resolution.
106 pub fn multi_res(self, n: usize) -> Vec<Self> {
107 multires::limited_sequence(n, self, |intrinsics| Some(intrinsics.half_res()))
108 }
109
110 /// Compute intrinsic parameters of a camera with half resolution.
111 ///
112 /// Since the (0,0) coordinates correspond the center of the first pixel,
113 /// and not its top left corner, a shift of 0.5 is performed
114 /// for the principal point before and after the resolution scaling.
115 pub fn half_res(&self) -> Self {
116 let (cx, cy) = self.principal_point;
117 let (fx, fy) = self.focal;
118 Self {
119 principal_point: ((cx + 0.5) / 2.0 - 0.5, (cy + 0.5) / 2.0 - 0.5),
120 focal: (0.5 * fx, 0.5 * fy),
121 skew: self.skew,
122 }
123 }
124
125 /// Project a 3D point in camera coordinates into a 2D homogeneous image point.
126 pub fn project(&self, point: Point3) -> Vec3 {
127 Vec3::new(
128 self.focal.0 * point[0] + self.skew * point[1] + self.principal_point.0 * point[2],
129 self.focal.1 * point[1] + self.principal_point.1 * point[2],
130 point[2],
131 )
132 }
133
134 /// Back project a pixel with depth info into a 3D point in camera coordinates.
135 pub fn back_project(&self, point: Point2, depth: Float) -> Point3 {
136 let z = depth;
137 let y = (point[1] - self.principal_point.1) * z / self.focal.1;
138 let x = ((point[0] - self.principal_point.0) * z - self.skew * y) / self.focal.0;
139 Point3::new(x, y, z)
140 }
141}