cv_geom/
lib.rs

1//! This crate contains computational geometry algorithms for [Rust CV](https://github.com/rust-cv/).
2//!
3//! ## Triangulation
4//!
5//! In this problem we know the relative pose of cameras and the [`Bearing`] of the same feature
6//! observed in each camera frame. We want to find the point of intersection from all cameras.
7//!
8//! - `p` the point we are trying to triangulate
9//! - `a` the normalized keypoint on camera A
10//! - `b` the normalized keypoint on camera B
11//! - `O` the optical center of a camera
12//! - `@` the virtual image plane
13//!
14//! ```text
15//!                        @
16//!                        @
17//!               p--------b--------O
18//!              /         @
19//!             /          @
20//!            /           @
21//!           /            @
22//!   @@@@@@@a@@@@@
23//!         /
24//!        /
25//!       /
26//!      O
27//! ```
28
29#![no_std]
30
31use cv_core::nalgebra::{zero, Matrix3x4, Matrix4, RowVector4};
32use cv_core::{
33    Bearing, CameraPoint, CameraToCamera, Pose, TriangulatorObservances, TriangulatorRelative,
34    WorldPoint, WorldToCamera,
35};
36
37/// This solves triangulation problems by simply minimizing the squared reprojection error of all observances.
38///
39/// This is a quick triangulator to execute and is fairly robust.
40///
41/// ```
42/// use cv_core::nalgebra::{Vector3, Point3, Rotation3, Unit};
43/// use cv_core::{TriangulatorRelative, CameraToCamera, CameraPoint, Pose, Projective};
44/// use cv_geom::MinSquaresTriangulator;
45///
46/// let point = CameraPoint::from_point(Point3::new(0.3, 0.1, 2.0));
47/// let pose = CameraToCamera::from_parts(Vector3::new(0.1, 0.1, 0.1), Rotation3::new(Vector3::new(0.1, 0.1, 0.1)));
48/// let bearing_a = point.bearing();
49/// let bearing_b = pose.transform(point).bearing();
50/// let triangulated = MinSquaresTriangulator::new().triangulate_relative(pose, bearing_a, bearing_b).unwrap();
51/// let distance = (point.point().unwrap().coords - triangulated.point().unwrap().coords).norm();
52/// assert!(distance < 1e-6);
53/// ```
54#[derive(Copy, Clone, Debug)]
55pub struct MinSquaresTriangulator {
56    epsilon: f64,
57    max_iterations: usize,
58}
59
60impl MinSquaresTriangulator {
61    /// Creates a `MinSquaresTriangulator` with default values.
62    ///
63    /// Same as calling [`Default::default`].
64    pub fn new() -> Self {
65        Default::default()
66    }
67
68    /// Set the epsilon used in the symmetric eigen solver.
69    ///
70    /// Default is `1e-9`.
71    pub fn epsilon(self, epsilon: f64) -> Self {
72        Self { epsilon, ..self }
73    }
74
75    /// Set the maximum number of iterations for the symmetric eigen solver.
76    ///
77    /// Default is `100`.
78    pub fn max_iterations(self, max_iterations: usize) -> Self {
79        Self {
80            max_iterations,
81            ..self
82        }
83    }
84}
85
86impl Default for MinSquaresTriangulator {
87    fn default() -> Self {
88        Self {
89            epsilon: 1e-9,
90            max_iterations: 100,
91        }
92    }
93}
94
95impl TriangulatorObservances for MinSquaresTriangulator {
96    fn triangulate_observances<B: Bearing>(
97        &self,
98        pairs: impl IntoIterator<Item = (WorldToCamera, B)>,
99    ) -> Option<WorldPoint> {
100        let mut a: Matrix4<f64> = zero();
101
102        for (pose, bearing) in pairs {
103            // Get the normalized bearing.
104            let bearing = bearing.bearing().into_inner();
105            // Get the pose as a 3x4 matrix.
106            let rot = pose.0.rotation.matrix();
107            let trans = pose.0.translation.vector;
108            let pose = Matrix3x4::<f64>::from_columns(&[
109                rot.column(0),
110                rot.column(1),
111                rot.column(2),
112                trans.column(0),
113            ]);
114            // Set up the least squares problem.
115            let term = pose - bearing * bearing.transpose() * pose;
116            a += term.transpose() * term;
117        }
118
119        let se = a.try_symmetric_eigen(self.epsilon, self.max_iterations)?;
120
121        se.eigenvalues
122            .iter()
123            .enumerate()
124            .min_by_key(|&(_, &n)| float_ord::FloatOrd(n))
125            .map(|(ix, _)| se.eigenvectors.column(ix).into_owned())
126            .map(|v| if v.w.is_sign_negative() { -v } else { v })
127            .map(Into::into)
128    }
129}
130
131/// Based on algorithm 12 from "Multiple View Geometry in Computer Vision, Second Edition"
132#[derive(Copy, Clone, Debug)]
133pub struct RelativeDltTriangulator {
134    epsilon: f64,
135    max_iterations: usize,
136}
137
138impl RelativeDltTriangulator {
139    /// Creates a `RelativeDltTriangulator` with default values.
140    ///
141    /// Same as calling [`Default::default`].
142    pub fn new() -> Self {
143        Default::default()
144    }
145
146    /// Set the epsilon used in the SVD solver.
147    ///
148    /// Default is `1e-9`.
149    pub fn epsilon(self, epsilon: f64) -> Self {
150        Self { epsilon, ..self }
151    }
152
153    /// Set the maximum number of iterations for the SVD solver.
154    ///
155    /// Default is `100`.
156    pub fn max_iterations(self, max_iterations: usize) -> Self {
157        Self {
158            max_iterations,
159            ..self
160        }
161    }
162}
163
164impl Default for RelativeDltTriangulator {
165    fn default() -> Self {
166        Self {
167            epsilon: 1e-9,
168            max_iterations: 100,
169        }
170    }
171}
172
173impl TriangulatorRelative for RelativeDltTriangulator {
174    fn triangulate_relative<A: Bearing, B: Bearing>(
175        &self,
176        relative_pose: CameraToCamera,
177        a: A,
178        b: B,
179    ) -> Option<CameraPoint> {
180        let pose = relative_pose.homogeneous();
181        let a = a.bearing_unnormalized();
182        let b = b.bearing_unnormalized();
183        let mut design = Matrix4::zeros();
184        design
185            .row_mut(0)
186            .copy_from(&RowVector4::new(-a.z, 0.0, a.x, 0.0));
187        design
188            .row_mut(1)
189            .copy_from(&RowVector4::new(0.0, -a.z, a.y, 0.0));
190        design
191            .row_mut(2)
192            .copy_from(&(b.x * pose.row(2) - b.z * pose.row(0)));
193        design
194            .row_mut(3)
195            .copy_from(&(b.y * pose.row(2) - b.z * pose.row(1)));
196
197        let svd = design.try_svd(false, true, self.epsilon, self.max_iterations)?;
198
199        // Extract the null-space vector from V* corresponding to the smallest
200        // singular value and then normalize it back from heterogeneous coordinates.
201        svd.singular_values
202            .iter()
203            .enumerate()
204            .min_by_key(|&(_, &n)| float_ord::FloatOrd(n))
205            .map(|(ix, _)| svd.v_t.unwrap().row(ix).transpose().into_owned())
206            .map(|v| if v.w.is_sign_negative() { -v } else { v })
207            .map(Into::into)
208    }
209}