1#![no_std]
22#![warn(missing_docs)]
23
24use arrayvec::ArrayVec;
25use num_traits::Float;
26
27use cv_core::nalgebra::{Matrix3, Rotation3, Vector3};
36use cv_core::sample_consensus::Estimator;
37use cv_core::{Bearing, FeatureWorldMatch, Pose, Projective, WorldToCamera};
38
39type Mat3 = Matrix3<f64>;
40type Vec3 = Vector3<f64>;
41
42#[derive(Copy, Clone, Debug, PartialEq)]
55#[non_exhaustive]
56pub struct LambdaTwist {
57 pub gauss_newton_iterations: usize,
60 pub rotation_convergence_iterations: usize,
62 pub rotation_convergence_epsilon: f64,
64}
65
66impl LambdaTwist {
67 pub fn new() -> Self {
69 Self::default()
70 }
71
72 pub fn gauss_newton_iterations(self, gauss_newton_iterations: usize) -> Self {
74 Self {
75 gauss_newton_iterations,
76 ..self
77 }
78 }
79
80 pub fn rotation_convergence_iterations(self, rotation_convergence_iterations: usize) -> Self {
82 Self {
83 rotation_convergence_iterations,
84 ..self
85 }
86 }
87
88 pub fn rotation_convergence_epsilon(self, rotation_convergence_epsilon: f64) -> Self {
90 Self {
91 rotation_convergence_epsilon,
92 ..self
93 }
94 }
95
96 fn compute_poses_nordberg<P: Bearing>(
106 &self,
107 samples: [FeatureWorldMatch<P>; 3],
108 ) -> ArrayVec<[WorldToCamera; 4]> {
109 let to_wp = |&FeatureWorldMatch(_, point)| point.point();
111 let wps = [
112 if let Some(p) = to_wp(&samples[0]) {
113 p
114 } else {
115 return ArrayVec::new();
116 },
117 if let Some(p) = to_wp(&samples[1]) {
118 p
119 } else {
120 return ArrayVec::new();
121 },
122 if let Some(p) = to_wp(&samples[2]) {
123 p
124 } else {
125 return ArrayVec::new();
126 },
127 ];
128 let to_bearing = |FeatureWorldMatch(point, _): &FeatureWorldMatch<P>| point.bearing();
129 let bearings = [
130 to_bearing(&samples[0]),
131 to_bearing(&samples[1]),
132 to_bearing(&samples[2]),
133 ];
134
135 let d12 = wps[0] - wps[1];
137 let d13 = wps[0] - wps[2];
138 let d23 = wps[1] - wps[2];
139 let d12xd13 = d12.cross(&d13);
140
141 let a12 = d12.norm_squared();
143 let a13 = d13.norm_squared();
144 let a23 = d23.norm_squared();
145
146 let c12 = bearings[0].dot(&bearings[1]);
148 let c23 = bearings[1].dot(&bearings[2]);
149 let c31 = bearings[2].dot(&bearings[0]);
150 let blob = c12 * c23 * c31 - 1.0;
151
152 let s12_sqr = 1.0 - c12 * c12;
154 let s23_sqr = 1.0 - c23 * c23;
155 let s31_sqr = 1.0 - c31 * c31;
156
157 let b12 = -2.0 * c12;
159 let b13 = -2.0 * c31;
160 let b23 = -2.0 * c23;
161
162 let p3 = a13 * (a23 * s31_sqr - a13 * s23_sqr);
165 let p2 = 2.0 * blob * a23 * a13
166 + a13 * (2.0 * a12 + a13) * s23_sqr
167 + a23 * (a23 - a12) * s31_sqr;
168 let p1 = a23 * (a13 - a23) * s12_sqr
169 - a12 * a12 * s23_sqr
170 - 2.0 * a12 * (blob * a23 + a13 * s23_sqr);
171 let p0 = a12 * (a12 * s23_sqr - a23 * s12_sqr);
172
173 let g = cube_root(p2 / p3, p1 / p3, p0 / p3);
175
176 let d0_00 = a23 * (1.0 - g);
178 let d0_01 = -(a23 * c12);
179 let d0_02 = a23 * c31 * g;
180 let d0_11 = a23 - a12 + a13 * g;
181 let d0_12 = -c23 * (a13 * g - a12);
182 let d0_22 = g * (a13 - a23) - a12;
183 #[rustfmt::skip]
184 let d0_mat = Mat3::new(
185 d0_00, d0_01, d0_02,
186 d0_01, d0_11, d0_12,
187 d0_02, d0_12, d0_22,
188 );
189
190 let (eig_vectors, eig_values) = eigen_decomposition_singular(d0_mat);
192
193 let mut lambdas: ArrayVec<[Vec3; 4]> = ArrayVec::new();
196
197 let eigen_ratio = (0.0_f64.max(-eig_values[1] / eig_values[0])).sqrt();
199
200 let quadratic_coefficients = |ratio: f64| {
203 let w2 = 1.0 / (ratio * eig_vectors.m12 - eig_vectors.m11);
204 let w0 = w2 * (eig_vectors.m21 - ratio * eig_vectors.m22);
205 let w1 = w2 * (eig_vectors.m31 - ratio * eig_vectors.m32);
206
207 let a = 1.0 / ((a13 - a12) * w1 * w1 - a12 * b13 * w1 - a12);
208 let b = a * (a13 * b12 * w1 - a12 * b13 * w0 - 2.0 * w0 * w1 * (a12 - a13));
209 let c = a * ((a13 - a12) * w0 * w0 + a13 * b12 * w0 + a13);
210 (w0, w1, b, c)
211 };
212
213 let possible_depths = |tau: f64, w0: f64, w1: f64| {
216 let d = a23 / (tau * (b23 + tau) + 1.0);
217 if d > 0.0 {
218 let l2 = d.sqrt();
219 let l3 = tau * l2;
220 let l1 = w0 * l2 + w1 * l3;
221 (true, l1, l2, l3)
222 } else {
223 (false, 0.0, 0.0, 0.0)
224 }
225 };
226
227 let mut push_solution = |tau: f64, w0: f64, w1: f64| {
229 if tau > 0.0 {
230 let (valid, l1, l2, l3) = possible_depths(tau, w0, w1);
231 if valid && l1 >= 0.0 {
232 lambdas.push(Vec3::new(l1, l2, l3));
233 }
234 }
235 };
236
237 let mut push_solutions_to_lambdas = |ratio: f64| {
240 let (w0, w1, b, c) = quadratic_coefficients(ratio);
241 if b * b - 4.0 * c >= 0.0 {
242 let (_, tau1, tau2) = root2real(b, c);
243 push_solution(tau1, w0, w1);
244 push_solution(tau2, w0, w1);
245 }
246 };
247
248 push_solutions_to_lambdas(eigen_ratio);
249 push_solutions_to_lambdas(-eigen_ratio);
250
251 #[rustfmt::skip]
256 let x_mat = Mat3::new(
257 d12[0], d13[0], d12xd13[0],
258 d12[1], d13[1], d12xd13[1],
259 d12[2], d13[2], d12xd13[2],
260 );
261 let x_mat = if let Some(x_mat) = x_mat.try_inverse() {
262 x_mat
263 } else {
264 return ArrayVec::new();
265 };
266
267 lambdas
268 .iter()
269 .map(|&lambda| {
270 let lambda_refined = gauss_newton_refine_lambda(
272 lambda,
273 self.gauss_newton_iterations,
274 a12,
275 a13,
276 a23,
277 b12,
278 b13,
279 b23,
280 );
281
282 let ry1 = lambda_refined[0] * *bearings[0];
283 let ry2 = lambda_refined[1] * *bearings[1];
284 let ry3 = lambda_refined[2] * *bearings[2];
285
286 let yd1 = ry1 - ry2;
287 let yd2 = ry1 - ry3;
288 let yd1xd2 = yd1.cross(&yd2);
289
290 #[rustfmt::skip]
291 let y_mat = Mat3::new(
292 yd1[0], yd2[0], yd1xd2[0],
293 yd1[1], yd2[1], yd1xd2[1],
294 yd1[2], yd2[2], yd1xd2[2],
295 );
296
297 let rot = y_mat * x_mat;
298 (rot, ry1 - rot * wps[0].coords)
299 })
300 .map(|(rot, trans)| {
301 WorldToCamera::from_parts(
302 trans,
303 Rotation3::from_matrix_eps(
304 &rot,
305 self.rotation_convergence_epsilon,
306 self.rotation_convergence_iterations,
307 Rotation3::identity(),
308 ),
309 )
310 })
311 .collect()
312 }
313}
314
315impl Default for LambdaTwist {
316 fn default() -> Self {
317 Self {
318 gauss_newton_iterations: 5,
319 rotation_convergence_iterations: 100,
320 rotation_convergence_epsilon: 1e-12,
321 }
322 }
323}
324
325impl<P> Estimator<FeatureWorldMatch<P>> for LambdaTwist
326where
327 P: Bearing,
328{
329 type Model = WorldToCamera;
330 type ModelIter = ArrayVec<[WorldToCamera; 4]>;
331 const MIN_SAMPLES: usize = 3;
332 fn estimate<I>(&self, mut data: I) -> Self::ModelIter
333 where
334 I: Iterator<Item = FeatureWorldMatch<P>> + Clone,
335 {
336 self.compute_poses_nordberg([
337 data.next()
338 .expect("must provide 3 samples at minimum to LambdaTwist"),
339 data.next()
340 .expect("must provide 3 samples at minimum to LambdaTwist"),
341 data.next()
342 .expect("must provide 3 samples at minimum to LambdaTwist"),
343 ])
344 }
345}
346
347#[allow(clippy::similar_names)]
358#[allow(clippy::too_many_arguments)]
359fn gauss_newton_refine_lambda(
360 lambda: Vec3,
361 iterations: usize,
362 a12: f64,
363 a13: f64,
364 a23: f64,
365 b12: f64,
366 b13: f64,
367 b23: f64,
368) -> Vec3 {
369 let compute_residual = |l: &Vec3| {
370 let l1 = l.x;
371 let l2 = l.y;
372 let l3 = l.z;
373 let r1 = l1 * l1 + l2 * l2 + b12 * l1 * l2 - a12;
374 let r2 = l1 * l1 + l3 * l3 + b13 * l1 * l3 - a13;
375 let r3 = l2 * l2 + l3 * l3 + b23 * l2 * l3 - a23;
376 (l1, l2, l3, Vec3::new(r1, r2, r3))
377 };
378 let (mut l1, mut l2, mut l3, mut res) = compute_residual(&lambda);
379 for _ in 0..iterations {
380 if l1_norm(res) < 1e-10 {
381 break;
382 }
383
384 let dr1dl1 = 2.0 * l1 + b12 * l2;
385 let dr1dl2 = 2.0 * l2 + b12 * l1;
386 let dr2dl1 = 2.0 * l1 + b13 * l3;
387 let dr2dl3 = 2.0 * l3 + b13 * l1;
388 let dr3dl2 = 2.0 * l2 + b23 * l3;
389 let dr3dl3 = 2.0 * l3 + b23 * l2;
390 let det = 1.0 / (-dr1dl1 * dr2dl3 * dr3dl2 - dr1dl2 * dr2dl1 * dr3dl3);
391
392 #[rustfmt::skip]
393 let jacobian = Mat3::new(
394 -dr2dl3 * dr3dl2, -dr1dl2 * dr3dl3, dr1dl2 * dr2dl3,
395 -dr2dl1 * dr3dl3, dr1dl1 * dr3dl3, -dr1dl1 * dr2dl3,
396 dr2dl1 * dr3dl2, -dr1dl1 * dr3dl2, -dr1dl2 * dr2dl1,
397 );
398 let lambda_new = Vec3::new(l1, l2, l3) - det * (jacobian * res);
399 let (l1_new, l2_new, l3_new, res_new) = compute_residual(&lambda_new);
400 if l1_norm(res_new) > l1_norm(res) {
401 break;
402 } else {
403 l1 = l1_new;
404 l2 = l2_new;
405 l3 = l3_new;
406 res = res_new;
407 }
408 }
409 Vec3::new(l1, l2, l3)
410}
411
412#[inline]
415fn l1_norm(v: Vec3) -> f64 {
416 v.x.abs() + v.y.abs() + v.z.abs()
417}
418
419fn root2real(b: f64, c: f64) -> (bool, f64, f64) {
422 let discriminant = b * b - 4.0 * c;
423 if discriminant < 0.0 {
424 let root = 0.5 * b;
425 (false, root, root)
426 } else if b < 0.0 {
427 let y = Float::sqrt(discriminant);
428 (true, 0.5 * (-b + y), 0.5 * (-b - y))
429 } else {
430 let y = Float::sqrt(discriminant);
431 (true, 2.0 * c / (-b + y), 2.0 * c / (-b - y))
432 }
433}
434
435#[allow(clippy::many_single_char_names)]
456fn cube_root(b: f64, c: f64, d: f64) -> f64 {
457 let mut r0;
459 if b * b >= 3.0 * c {
461 let v = (b * b - 3.0 * c).sqrt();
464 let t1 = (-b - v) / 3.0;
465
466 let mut k = ((t1 + b) * t1 + c) * t1 + d;
468
469 if k > 0.0 {
470 r0 = t1 - (-k / (3.0 * t1 + b)).sqrt();
472 } else {
473 let t2 = (-b + v) / 3.0;
474 k = ((t2 + b) * t2 + c) * t2 + d;
475 r0 = t2 + (-k / (3.0 * t2 + b)).sqrt();
477 }
478 } else {
479 r0 = -b / 3.0;
481 if ((3.0 * r0 + 2.0 * b) * r0 + c).abs() < 1e-4 {
482 r0 += 1.0;
483 }
484 }
485
486 for _ in 0..7 {
490 let fx = ((r0 + b) * r0 + c) * r0 + d;
491 let fpx = (3.0 * r0 + 2.0 * b) * r0 + c;
492 r0 -= fx / fpx;
493 }
494 for _ in 0..43 {
495 let fx = ((r0 + b) * r0 + c) * r0 + d;
496 if fx.abs() > 1e-13 {
497 let fpx = (3.0 * r0 + 2.0 * b) * r0 + c;
498 r0 -= fx / fpx;
499 } else {
500 break;
501 }
502 }
503 r0
504}
505
506fn eigen_decomposition_singular(x: Mat3) -> (Mat3, Vec3) {
509 let mut eigenvalues = Vec3::zeros();
510 #[rustfmt::skip]
511 let mut v3 = Vec3::new(
512 x[1] * x[5] - x[2] * x[4],
513 x[2] * x[3] - x[5] * x[0],
514 x[4] * x[0] - x[1] * x[3],
515 );
516 v3.normalize_mut();
517
518 let x12_sqr = x.m12 * x.m12;
519 let b = -x.m11 - x.m22 - x.m33;
520 let c = -x12_sqr - x.m13 * x.m13 - x.m23 * x.m23 + x.m11 * (x.m22 + x.m33) + x.m22 * x.m33;
521 let (_, mut e1, mut e2) = root2real(b, c);
522 if e1.abs() < e2.abs() {
523 core::mem::swap(&mut e1, &mut e2);
524 }
525 eigenvalues[0] = e1;
526 eigenvalues[1] = e2;
527
528 let mx0011 = -x.m11 * x.m22;
529 let prec_0 = x.m12 * x.m23 - x.m13 * x.m22;
530 let prec_1 = x.m12 * x.m13 - x.m11 * x.m23;
531
532 let compute_eigen_vector = |e: f64| {
533 let tmp = 1.0 / (e * (x.m11 + x.m22) + mx0011 - e * e + x12_sqr);
534 let mut a1 = -(e * x.m13 + prec_0) * tmp;
535 let mut a2 = -(e * x.m23 + prec_1) * tmp;
536 let rnorm = 1.0 / (a1 * a1 + a2 * a2 + 1.0).sqrt();
537 a1 *= rnorm;
538 a2 *= rnorm;
539 Vec3::new(a1, a2, rnorm)
540 };
541 let v1 = compute_eigen_vector(e1);
542 let v2 = compute_eigen_vector(e2);
543
544 #[rustfmt::skip]
545 let eigenvectors = Mat3::new(
546 v1[0], v2[0], v3[0],
547 v1[1], v2[1], v3[1],
548 v1[2], v2[2], v3[2],
549 );
550
551 (eigenvectors, eigenvalues)
552}