use itertools::izip;
use nalgebra::DMatrix;
use crate::core::{
camera::Intrinsics,
candidates::coarse_to_fine as candidates,
gradient,
inverse_depth::{self, InverseDepth},
multires,
track::lm_optimizer::{self, LMOptimizerState},
};
use crate::math::optimizer::State as _;
use crate::misc::helper;
use crate::misc::type_aliases::{Float, Iso3, Mat6, Point2, Vec6};
pub type Levels<T> = Vec<T>;
pub struct Tracker {
config: Config,
state: State,
}
pub struct Config {
pub nb_levels: usize,
pub candidates_diff_threshold: u16,
pub depth_scale: Float,
pub intrinsics: Intrinsics,
pub idepth_variance: Float,
}
struct State {
keyframe_multires_data: MultiresData,
keyframe_depth_timestamp: f64,
keyframe_img_timestamp: f64,
keyframe_pose: Iso3,
current_frame_depth_timestamp: f64,
current_frame_img_timestamp: f64,
current_frame_pose: Iso3,
}
#[allow(clippy::type_complexity)]
struct MultiresData {
intrinsics_multires: Levels<Intrinsics>,
img_multires: Levels<DMatrix<u8>>,
usable_candidates_multires: Levels<(Vec<(usize, usize)>, Vec<Float>)>,
jacobians_multires: Levels<Vec<Vec6>>,
hessians_multires: Levels<Vec<Mat6>>,
}
impl Config {
pub fn init(
self,
keyframe_depth_timestamp: f64,
depth_map: &DMatrix<u16>,
keyframe_img_timestamp: f64,
img: DMatrix<u8>,
) -> Tracker {
let intrinsics_multires = self.intrinsics.clone().multi_res(self.nb_levels);
let img_multires = multires::mean_pyramid(self.nb_levels, img);
let keyframe_multires_data =
precompute_multires_data(&self, depth_map, intrinsics_multires, img_multires);
Tracker {
state: State {
keyframe_multires_data,
keyframe_depth_timestamp,
keyframe_img_timestamp,
keyframe_pose: Iso3::identity(),
current_frame_depth_timestamp: keyframe_depth_timestamp,
current_frame_img_timestamp: keyframe_img_timestamp,
current_frame_pose: Iso3::identity(),
},
config: self,
}
}
}
#[allow(clippy::used_underscore_binding)]
fn precompute_multires_data(
config: &Config,
depth_map: &DMatrix<u16>,
intrinsics_multires: Levels<Intrinsics>,
img_multires: Levels<DMatrix<u8>>,
) -> MultiresData {
let mut gradients_multires = multires::gradients_xy(&img_multires);
gradients_multires.insert(0, gradient::centered(&img_multires[0]));
let gradients_squared_norm_multires: Vec<_> = gradients_multires
.iter()
.map(|(gx, gy)| gradient::squared_norm(gx, gy))
.collect();
let candidates_points = candidates::select(
config.candidates_diff_threshold,
&gradients_squared_norm_multires,
)
.pop()
.unwrap();
let from_depth = |z| inverse_depth::from_depth(config.depth_scale, z, config.idepth_variance);
let idepth_candidates = helper::zip_mask_map(
depth_map,
&candidates_points,
InverseDepth::Unknown,
from_depth,
);
let fuse = |a, b, c, d| inverse_depth::fuse(a, b, c, d, inverse_depth::strategy_dso_mean);
let idepth_multires = multires::limited_sequence(config.nb_levels, idepth_candidates, |m| {
multires::halve(m, fuse)
});
let usable_candidates_multires: Levels<_> = idepth_multires.iter().map(extract_z).collect();
let jacobians_multires: Levels<Vec<Vec6>> = izip!(
&intrinsics_multires,
&usable_candidates_multires,
&gradients_multires,
)
.map(|(intrinsics, (coord, _z), (gx, gy))| warp_jacobians(intrinsics, coord, _z, gx, gy))
.collect();
let hessians_multires: Levels<_> = jacobians_multires.iter().map(hessians_vec).collect();
MultiresData {
intrinsics_multires,
img_multires,
usable_candidates_multires,
jacobians_multires,
hessians_multires,
}
}
impl Tracker {
#[allow(clippy::used_underscore_binding)]
#[allow(clippy::cast_precision_loss)]
pub fn track(
&mut self,
depth_time: f64,
depth_map: &DMatrix<u16>,
img_time: f64,
img: DMatrix<u8>,
) {
let mut lm_model = self.state.current_frame_pose.inverse() * self.state.keyframe_pose;
let img_multires = multires::mean_pyramid(self.config.nb_levels, img);
let keyframe_data = &self.state.keyframe_multires_data;
let mut optimization_went_well = true;
for lvl in (0..self.config.nb_levels).rev() {
let obs = lm_optimizer::Obs {
intrinsics: &keyframe_data.intrinsics_multires[lvl],
template: &keyframe_data.img_multires[lvl],
image: &img_multires[lvl],
coordinates: &keyframe_data.usable_candidates_multires[lvl].0,
_z_candidates: &keyframe_data.usable_candidates_multires[lvl].1,
jacobians: &keyframe_data.jacobians_multires[lvl],
hessians: &keyframe_data.hessians_multires[lvl],
};
match LMOptimizerState::iterative_solve(&obs, lm_model) {
Ok((lm_state, _)) => {
lm_model = lm_state.eval_data.model;
}
Err(err) => {
eprintln!("{}", err);
optimization_went_well = false;
break;
}
}
}
self.state.current_frame_depth_timestamp = depth_time;
self.state.current_frame_img_timestamp = img_time;
if optimization_went_well {
self.state.current_frame_pose = self.state.keyframe_pose * lm_model.inverse();
}
let (coordinates, _z_candidates) = keyframe_data.usable_candidates_multires.last().unwrap();
let intrinsics = keyframe_data.intrinsics_multires.last().unwrap();
let optical_flow_sum: Float = _z_candidates
.iter()
.zip(coordinates.iter())
.map(|(&_z, &(x, y))| {
let (u, v) = warp(&lm_model, x as Float, y as Float, _z, intrinsics);
(x as Float - u).abs() + (y as Float - v).abs()
})
.sum();
let optical_flow = optical_flow_sum / _z_candidates.len() as Float;
eprintln!("Optical_flow: {}", optical_flow);
let change_keyframe = optical_flow >= 1.0;
if change_keyframe {
let delta_time = depth_time - self.state.keyframe_depth_timestamp;
eprintln!("Changing keyframe after: {} seconds", delta_time);
self.state.keyframe_multires_data = precompute_multires_data(
&self.config,
depth_map,
keyframe_data.intrinsics_multires.clone(),
img_multires,
);
self.state.keyframe_depth_timestamp = depth_time;
self.state.keyframe_img_timestamp = img_time;
self.state.keyframe_pose = self.state.current_frame_pose;
}
}
pub fn current_frame(&self) -> (f64, Iso3) {
(
self.state.current_frame_depth_timestamp,
self.state.current_frame_pose,
)
}
}
#[allow(clippy::used_underscore_binding)]
fn extract_z(idepth_mat: &DMatrix<InverseDepth>) -> (Vec<(usize, usize)>, Vec<Float>) {
let mut u = 0;
let mut v = 0;
let mut coordinates = Vec::new();
let mut _z_vec = Vec::new();
let (nb_rows, _) = idepth_mat.shape();
for idepth in idepth_mat.iter() {
if let InverseDepth::WithVariance(_z, _) = *idepth {
coordinates.push((u, v));
_z_vec.push(_z);
}
v += 1;
if v >= nb_rows {
u += 1;
v = 0;
}
}
(coordinates, _z_vec)
}
#[allow(clippy::used_underscore_binding)]
#[allow(clippy::cast_precision_loss)]
fn warp_jacobians(
intrinsics: &Intrinsics,
coordinates: &[(usize, usize)],
_z_candidates: &[Float],
grad_x: &DMatrix<i16>,
grad_y: &DMatrix<i16>,
) -> Vec<Vec6> {
let (cu, cv) = intrinsics.principal_point;
let (fu, fv) = intrinsics.focal;
let s = intrinsics.skew;
coordinates
.iter()
.zip(_z_candidates.iter())
.map(|(&(u, v), &_z)| {
let gu = Float::from(grad_x[(v, u)]);
let gv = Float::from(grad_y[(v, u)]);
warp_jacobian_at(gu, gv, u as Float, v as Float, _z, cu, cv, fu, fv, s)
})
.collect()
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::many_single_char_names)]
#[allow(clippy::used_underscore_binding)]
#[allow(clippy::similar_names)]
fn warp_jacobian_at(
gu: Float,
gv: Float,
u: Float,
v: Float,
_z: Float,
cu: Float,
cv: Float,
fu: Float,
fv: Float,
s: Float,
) -> Vec6 {
let a = u - cu;
let b = v - cv;
let c = a * fv - s * b;
let _fv = 1.0 / fv;
let _fuv = 1.0 / (fu * fv);
Vec6::new(
gu * _z * fu, _z * (gu * s + gv * fv), -_z * (gu * a + gv * b), gu * (-a * b * _fv - s) + gv * (-b * b * _fv - fv), gu * (a * c * _fuv + fu) + gv * (b * c * _fuv), gu * (-fu * fu * b + s * c) * _fuv + gv * (c / fu), )
}
#[allow(clippy::ptr_arg)] fn hessians_vec(jacobians: &Vec<Vec6>) -> Vec<Mat6> {
jacobians.iter().map(|j| j * j.transpose()).collect()
}
#[allow(clippy::used_underscore_binding)]
fn warp(model: &Iso3, x: Float, y: Float, _z: Float, intrinsics: &Intrinsics) -> (Float, Float) {
let x1 = intrinsics.back_project(Point2::new(x, y), 1.0 / _z);
let x2 = model * x1;
let uvz2 = intrinsics.project(x2);
(uvz2.x / uvz2.z, uvz2.y / uvz2.z)
}