use crate::{
Bearing, CameraPoint, FeatureMatch, Pose, RelativeCameraPose, TriangulatorProject,
TriangulatorRelative, UnscaledRelativeCameraPose,
};
use derive_more::{AsMut, AsRef, Deref, DerefMut, From, Into};
use nalgebra::{Matrix3, Rotation3, Vector3, SVD};
use num_traits::Float;
use sample_consensus::Model;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, AsMut, AsRef, Deref, DerefMut, From, Into)]
pub struct EssentialMatrix(pub Matrix3<f64>);
impl EssentialMatrix {
pub fn recondition(self, epsilon: f64, max_iterations: usize) -> Option<Self> {
let old_svd = self.try_svd(true, true, epsilon, max_iterations)?;
let mut sources = [0, 1, 2];
sources.sort_unstable_by_key(|&ix| float_ord::FloatOrd(-old_svd.singular_values[ix]));
let mut svd = old_svd;
for (dest, &source) in sources.iter().enumerate() {
svd.singular_values[dest] = old_svd.singular_values[source];
svd.u
.as_mut()
.unwrap()
.column_mut(dest)
.copy_from(&old_svd.u.as_ref().unwrap().column(source));
svd.v_t
.as_mut()
.unwrap()
.row_mut(dest)
.copy_from(&old_svd.v_t.as_ref().unwrap().row(source));
}
svd.singular_values[2] = 0.0;
let new_singular = (svd.singular_values[0] + svd.singular_values[1]) / 2.0;
svd.singular_values[0] = new_singular;
svd.singular_values[1] = new_singular;
let mat = svd.recompose().unwrap();
Some(Self(mat))
}
pub fn possible_rotations_unscaled_translation(
&self,
epsilon: f64,
max_iterations: usize,
) -> Option<(Rotation3<f64>, Rotation3<f64>, Vector3<f64>)> {
let Self(essential) = *self;
let essential = essential;
let w = Matrix3::new(0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0);
let wt = w.transpose();
let svd = SVD::try_new(essential, true, true, epsilon, max_iterations);
let u_v_t = svd.map(|svd| {
if let SVD {
u: Some(u),
v_t: Some(v_t),
singular_values,
} = svd
{
let mut sources: [usize; 3] = [0, 1, 2];
sources.sort_unstable_by_key(|&ix| float_ord::FloatOrd(-singular_values[ix]));
let mut sorted_u = Matrix3::zeros();
let mut sorted_v_t = Matrix3::zeros();
for (&ix, mut column) in sources.iter().zip(sorted_u.column_iter_mut()) {
column.copy_from(&u.column(ix));
}
for (&ix, mut row) in sources.iter().zip(sorted_v_t.row_iter_mut()) {
row.copy_from(&v_t.row(ix));
}
(sorted_u, sorted_v_t)
} else {
panic!("Didn't get U and V matrix in SVD");
}
});
let u_v_t = u_v_t.map(|(mut u, mut v_t)| {
if u.determinant() < 0.0 {
for n in u.column_mut(2).iter_mut() {
*n *= -1.0;
}
}
if v_t.determinant() < 0.0 {
for n in v_t.row_mut(2).iter_mut() {
*n *= -1.0;
}
}
(u, v_t)
});
u_v_t.map(|(u, v_t)| {
(
Rotation3::from_matrix_unchecked(u * w * v_t),
Rotation3::from_matrix_unchecked(u * wt * v_t),
u.column(2).into_owned(),
)
})
}
pub fn possible_rotations(
&self,
epsilon: f64,
max_iterations: usize,
) -> Option<[Rotation3<f64>; 2]> {
self.possible_rotations_unscaled_translation(epsilon, max_iterations)
.map(|(rot_a, rot_b, _)| [rot_a, rot_b])
}
pub fn possible_unscaled_poses(
&self,
epsilon: f64,
max_iterations: usize,
) -> Option<[UnscaledRelativeCameraPose; 4]> {
self.possible_rotations_unscaled_translation(epsilon, max_iterations)
.map(|(rot_a, rot_b, t)| {
[
UnscaledRelativeCameraPose(RelativeCameraPose::from_parts(rot_a, t)),
UnscaledRelativeCameraPose(RelativeCameraPose::from_parts(rot_b, t)),
UnscaledRelativeCameraPose(RelativeCameraPose::from_parts(rot_a, -t)),
UnscaledRelativeCameraPose(RelativeCameraPose::from_parts(rot_b, -t)),
]
})
}
pub fn possible_unscaled_poses_bearing(
&self,
epsilon: f64,
max_iterations: usize,
) -> Option<[UnscaledRelativeCameraPose; 2]> {
self.possible_rotations_unscaled_translation(epsilon, max_iterations)
.map(|(rot_a, rot_b, t)| {
[
UnscaledRelativeCameraPose(RelativeCameraPose::from_parts(rot_a, t)),
UnscaledRelativeCameraPose(RelativeCameraPose::from_parts(rot_b, t)),
]
})
}
pub fn pose_solver(&self) -> PoseSolver<'_> {
PoseSolver {
essential: self,
epsilon: 1e-9,
max_iterations: 100,
consensus_ratio: 0.5,
}
}
}
impl<P> Model<FeatureMatch<P>> for EssentialMatrix
where
P: Bearing,
{
fn residual(&self, data: &FeatureMatch<P>) -> f32 {
let Self(mat) = *self;
let FeatureMatch(a, b) = data;
let normalized = |p: &P| {
let p = p.bearing_unnormalized();
p / p.z
};
Float::abs((normalized(b).transpose() * mat * normalized(a))[0] as f32)
}
}
#[derive(Copy, Clone, Debug)]
pub struct PoseSolver<'a> {
essential: &'a EssentialMatrix,
epsilon: f64,
max_iterations: usize,
consensus_ratio: f64,
}
impl<'a> PoseSolver<'a> {
pub fn epsilon(self, epsilon: f64) -> Self {
Self { epsilon, ..self }
}
pub fn max_iterations(self, max_iterations: usize) -> Self {
Self {
max_iterations,
..self
}
}
pub fn consensus_ratio(self, consensus_ratio: f64) -> Self {
Self {
consensus_ratio,
..self
}
}
pub fn solve_unscaled<P>(
&self,
triangulator: impl TriangulatorRelative,
correspondences: impl Iterator<Item = FeatureMatch<P>>,
) -> Option<UnscaledRelativeCameraPose>
where
P: Bearing + Copy,
{
self.essential
.possible_unscaled_poses(self.epsilon, self.max_iterations)
.and_then(|poses| {
let (ts, total) = correspondences.fold(
([0usize; 4], 0usize),
|(mut ts, total), FeatureMatch(a, b)| {
let trans_and_agree = |pose| {
triangulator
.triangulate_relative(pose, a, b)
.map(|p| {
let tp = pose.transform(p);
p.coords.normalize().dot(&a.bearing()) > 0.0
&& tp.coords.normalize().dot(&b.bearing()) > 0.0
})
.unwrap_or(false)
};
for (tn, &UnscaledRelativeCameraPose(pose)) in ts.iter_mut().zip(&poses) {
if trans_and_agree(pose) {
*tn += 1;
}
}
(ts, total + 1)
},
);
if total == 0 {
return None;
}
let (ix, best) = ts
.iter()
.copied()
.enumerate()
.max_by_key(|&(_, t)| t)
.unwrap();
if (best as f64) < self.consensus_ratio * total as f64 && best != 0 {
return None;
}
Some(poses[ix])
})
}
#[cfg(feature = "alloc")]
pub fn solve_unscaled_inliers<P>(
&self,
triangulator: impl TriangulatorRelative,
correspondences: impl Iterator<Item = FeatureMatch<P>>,
) -> Option<(UnscaledRelativeCameraPose, alloc::vec::Vec<usize>)>
where
P: Bearing + Copy,
{
self.essential
.possible_unscaled_poses(self.epsilon, self.max_iterations)
.and_then(|poses| {
let (mut ts, total) = correspondences.enumerate().fold(
(
[
(0usize, alloc::vec::Vec::new()),
(0usize, alloc::vec::Vec::new()),
(0usize, alloc::vec::Vec::new()),
(0usize, alloc::vec::Vec::new()),
],
0usize,
),
|(mut ts, total), (ix, FeatureMatch(a, b))| {
let trans_and_agree = |pose| {
triangulator
.triangulate_relative(pose, a, b)
.map(|p| {
let tp = pose.transform(p);
p.coords.normalize().dot(&a.bearing()) > 0.0
&& tp.coords.normalize().dot(&b.bearing()) > 0.0
})
.unwrap_or(false)
};
for ((tn, ti), &UnscaledRelativeCameraPose(pose)) in
ts.iter_mut().zip(&poses)
{
if trans_and_agree(pose) {
*tn += 1;
ti.push(ix);
}
}
(ts, total + 1)
},
);
if total == 0 {
return None;
}
let (ix, best) = ts
.iter()
.map(|&(tn, _)| tn)
.enumerate()
.max_by_key(|&(_, t)| t)
.unwrap();
if (best as f64) < self.consensus_ratio * total as f64 && best != 0 {
return None;
}
let inliers = core::mem::take(&mut ts[ix].1);
Some((poses[ix], inliers))
})
}
pub fn solve<P>(
&self,
triangulator: impl TriangulatorProject,
correspondences: impl Iterator<Item = (CameraPoint, P)> + Clone,
) -> Option<RelativeCameraPose>
where
P: Bearing + Copy,
{
self.essential
.possible_unscaled_poses_bearing(self.epsilon, self.max_iterations)
.and_then(|poses| {
let (ts, total) = correspondences.fold(
([(0usize, 0.0); 2], 0usize),
|(mut ts, total), (a, b)| {
let trans_and_agree = |pose: UnscaledRelativeCameraPose| {
let untranslated = pose.rotation * a.0;
let t_scale = triangulator.triangulate_project(
CameraPoint(untranslated),
b,
pose.translation.vector,
)?;
if (untranslated.coords + t_scale * pose.translation.vector)
.dot(&b.bearing())
> 0.0
{
Some(t_scale)
} else {
None
}
};
for (tn, &pose) in ts.iter_mut().zip(&poses) {
if let Some(scale) = trans_and_agree(pose) {
tn.0 += 1;
tn.1 += scale;
}
}
(ts, total + 1)
},
);
if total == 0 {
return None;
}
let (ix, (best_num, scale_acc)) = ts
.iter()
.copied()
.enumerate()
.max_by_key(|&(_, (t, _))| t)
.unwrap();
if (best_num as f64) < self.consensus_ratio * total as f64 && best_num != 0 {
return None;
}
let scale = scale_acc / best_num as f64;
Some(RelativeCameraPose::from_parts(
poses[ix].rotation,
scale * poses[ix].translation.vector,
))
})
}
#[cfg(feature = "alloc")]
pub fn solve_inliers<P>(
&self,
triangulator: impl TriangulatorProject,
correspondences: impl Iterator<Item = (CameraPoint, P)> + Clone,
) -> Option<(RelativeCameraPose, alloc::vec::Vec<usize>)>
where
P: Bearing + Copy,
{
self.essential
.possible_unscaled_poses_bearing(self.epsilon, self.max_iterations)
.and_then(|poses| {
let (mut ts, total) = correspondences.enumerate().fold(
(
[
(0usize, 0.0, alloc::vec::Vec::new()),
(0usize, 0.0, alloc::vec::Vec::new()),
],
0usize,
),
|(mut ts, total), (ix, (a, b))| {
let trans_and_agree = |pose: UnscaledRelativeCameraPose| {
let untranslated = pose.rotation * a.0;
let t_scale = triangulator.triangulate_project(
CameraPoint(untranslated),
b,
pose.translation.vector,
)?;
if (untranslated.coords + t_scale * pose.translation.vector)
.dot(&b.bearing())
> 0.0
{
Some(t_scale)
} else {
None
}
};
for (tn, &pose) in ts.iter_mut().zip(&poses) {
if let Some(scale) = trans_and_agree(pose) {
tn.0 += 1;
tn.1 += scale;
tn.2.push(ix);
}
}
(ts, total + 1)
},
);
if total == 0 {
return None;
}
let ix = (0..ts.len()).max_by_key(|&ix| ts[ix].0).unwrap();
let (best_num, scale_acc, best_inliers) = core::mem::take(&mut ts[ix]);
if (best_num as f64) < self.consensus_ratio * total as f64 && best_num != 0 {
return None;
}
let scale = scale_acc / best_num as f64;
Some((
RelativeCameraPose::from_parts(
poses[ix].rotation,
scale * poses[ix].translation.vector,
),
best_inliers,
))
})
}
}