patch_tracker/
tracker.rs

1use image::{imageops, GrayImage};
2use imageproc::corners::Corner;
3use nalgebra as na;
4use rayon::prelude::*;
5use std::collections::HashMap;
6use std::ops::AddAssign;
7
8use crate::{image_utilities, patch};
9
10use log::info;
11
12#[derive(Default)]
13pub struct PatchTracker<const N: u32> {
14    last_keypoint_id: usize,
15    tracked_points_map: HashMap<usize, na::Affine2<f32>>,
16    previous_image_pyramid: Vec<GrayImage>,
17}
18impl<const LEVELS: u32> PatchTracker<LEVELS> {
19    pub fn process_frame(&mut self, greyscale_image: &GrayImage) {
20        // build current image pyramid
21        let current_image_pyramid: Vec<GrayImage> = build_image_pyramid(greyscale_image, LEVELS);
22
23        if !self.previous_image_pyramid.is_empty() {
24            info!("old points {}", self.tracked_points_map.len());
25            // track prev points
26            self.tracked_points_map = track_points::<LEVELS>(
27                &self.previous_image_pyramid,
28                &current_image_pyramid,
29                &self.tracked_points_map,
30            );
31            info!("tracked old points {}", self.tracked_points_map.len());
32        }
33        // add new points
34        let new_points = add_points(&self.tracked_points_map, greyscale_image);
35        for point in &new_points {
36            let mut v = na::Affine2::<f32>::identity();
37
38            v.matrix_mut_unchecked().m13 = point.x as f32;
39            v.matrix_mut_unchecked().m23 = point.y as f32;
40            self.tracked_points_map.insert(self.last_keypoint_id, v);
41            self.last_keypoint_id += 1;
42        }
43
44        // update saved image pyramid
45        self.previous_image_pyramid = current_image_pyramid;
46    }
47    pub fn get_track_points(&self) -> HashMap<usize, (f32, f32)> {
48        self.tracked_points_map
49            .iter()
50            .map(|(k, v)| (*k, (v.matrix().m13, v.matrix().m23)))
51            .collect()
52    }
53    pub fn remove_id(&mut self, ids: &[usize]) {
54        for id in ids {
55            self.tracked_points_map.remove(id);
56        }
57    }
58}
59
60#[derive(Default)]
61pub struct StereoPatchTracker<const N: u32> {
62    last_keypoint_id: usize,
63    tracked_points_map_cam0: HashMap<usize, na::Affine2<f32>>,
64    previous_image_pyramid0: Vec<GrayImage>,
65    tracked_points_map_cam1: HashMap<usize, na::Affine2<f32>>,
66    previous_image_pyramid1: Vec<GrayImage>,
67}
68
69impl<const LEVELS: u32> StereoPatchTracker<LEVELS> {
70    pub fn process_frame(&mut self, greyscale_image0: &GrayImage, greyscale_image1: &GrayImage) {
71        // build current image pyramid
72        let current_image_pyramid0: Vec<GrayImage> = build_image_pyramid(greyscale_image0, LEVELS);
73        let current_image_pyramid1: Vec<GrayImage> = build_image_pyramid(greyscale_image1, LEVELS);
74
75        // not initialized
76        if !self.previous_image_pyramid0.is_empty() {
77            info!("old points {}", self.tracked_points_map_cam0.len());
78            // track prev points
79            self.tracked_points_map_cam0 = track_points::<LEVELS>(
80                &self.previous_image_pyramid0,
81                &current_image_pyramid0,
82                &self.tracked_points_map_cam0,
83            );
84            self.tracked_points_map_cam1 = track_points::<LEVELS>(
85                &self.previous_image_pyramid1,
86                &current_image_pyramid1,
87                &self.tracked_points_map_cam1,
88            );
89            info!("tracked old points {}", self.tracked_points_map_cam0.len());
90        }
91        // add new points
92        let new_points0 = add_points(&self.tracked_points_map_cam0, greyscale_image0);
93        let tmp_tracked_points0: HashMap<usize, _> = new_points0
94            .iter()
95            .enumerate()
96            .map(|(i, point)| {
97                let mut v = na::Affine2::<f32>::identity();
98                v.matrix_mut_unchecked().m13 = point.x as f32;
99                v.matrix_mut_unchecked().m23 = point.y as f32;
100                (i, v)
101            })
102            .collect();
103
104        let tmp_tracked_points1 = track_points::<LEVELS>(
105            &current_image_pyramid0,
106            &current_image_pyramid1,
107            &tmp_tracked_points0,
108        );
109
110        for (key0, pt0) in tmp_tracked_points0 {
111            if let Some(pt1) = tmp_tracked_points1.get(&key0) {
112                self.tracked_points_map_cam0
113                    .insert(self.last_keypoint_id, pt0);
114                self.tracked_points_map_cam1
115                    .insert(self.last_keypoint_id, *pt1);
116                self.last_keypoint_id += 1;
117            }
118        }
119
120        // update saved image pyramid
121        self.previous_image_pyramid0 = current_image_pyramid0;
122        self.previous_image_pyramid1 = current_image_pyramid1;
123    }
124    pub fn get_track_points(&self) -> [HashMap<usize, (f32, f32)>; 2] {
125        let tracked_pts0 = self
126            .tracked_points_map_cam0
127            .iter()
128            .map(|(k, v)| (*k, (v.matrix().m13, v.matrix().m23)))
129            .collect();
130        let tracked_pts1 = self
131            .tracked_points_map_cam1
132            .iter()
133            .map(|(k, v)| (*k, (v.matrix().m13, v.matrix().m23)))
134            .collect();
135        [tracked_pts0, tracked_pts1]
136    }
137    pub fn remove_id(&mut self, ids: &[usize]) {
138        for id in ids {
139            self.tracked_points_map_cam0.remove(id);
140            self.tracked_points_map_cam1.remove(id);
141        }
142    }
143}
144
145fn build_image_pyramid(greyscale_image: &GrayImage, levels: u32) -> Vec<GrayImage> {
146    const FILTER_TYPE: imageops::FilterType = imageops::FilterType::Triangle;
147    let (w0, h0) = greyscale_image.dimensions();
148    (0..levels)
149        .into_par_iter()
150        .map(|i| {
151            let scale_down: u32 = 1 << i;
152            let (new_w, new_h) = (w0 / scale_down, h0 / scale_down);
153            imageops::resize(greyscale_image, new_w, new_h, FILTER_TYPE)
154        })
155        .collect()
156}
157
158fn add_points(
159    tracked_points_map: &HashMap<usize, na::Affine2<f32>>,
160    grayscale_image: &GrayImage,
161) -> Vec<Corner> {
162    const GRID_SIZE: u32 = 50;
163    let num_points_in_cell = 1;
164    let current_corners: Vec<Corner> = tracked_points_map
165        .values()
166        .map(|v| {
167            Corner::new(
168                v.matrix().m13.round() as u32,
169                v.matrix().m23.round() as u32,
170                0.0,
171            )
172        })
173        .collect();
174    // let curr_img_luma8 = DynamicImage::ImageLuma16(grayscale_image.clone()).into_luma8();
175    image_utilities::detect_key_points(
176        grayscale_image,
177        GRID_SIZE,
178        &current_corners,
179        num_points_in_cell,
180    )
181    // let mut prev_points =
182    // Eigen::aligned_vector<Eigen::Vector2d> pts0;
183
184    // for (const auto &kv : observations.at(0)) {
185    //   pts0.emplace_back(kv.second.translation().template cast<double>());
186    // }
187}
188fn track_points<const LEVELS: u32>(
189    image_pyramid0: &[GrayImage],
190    image_pyramid1: &[GrayImage],
191    transform_maps0: &HashMap<usize, na::Affine2<f32>>,
192) -> HashMap<usize, na::Affine2<f32>> {
193    let transform_maps1: HashMap<usize, na::Affine2<f32>> = transform_maps0
194        .par_iter()
195        .filter_map(|(k, v)| {
196            if let Some(new_v) = track_one_point::<LEVELS>(image_pyramid0, image_pyramid1, v) {
197                // return Some((k.clone(), new_v));
198                if let Some(old_v) =
199                    track_one_point::<LEVELS>(image_pyramid1, image_pyramid0, &new_v)
200                {
201                    if (v.matrix() - old_v.matrix())
202                        .fixed_view::<2, 1>(0, 2)
203                        .norm_squared()
204                        < 0.4
205                    {
206                        return Some((*k, new_v));
207                    }
208                }
209            }
210            None
211        })
212        .collect();
213
214    transform_maps1
215}
216fn track_one_point<const LEVELS: u32>(
217    image_pyramid0: &[GrayImage],
218    image_pyramid1: &[GrayImage],
219    transform0: &na::Affine2<f32>,
220) -> Option<na::Affine2<f32>> {
221    let mut patch_valid = true;
222    let mut transform1 = na::Affine2::<f32>::identity();
223    transform1.matrix_mut_unchecked().m13 = transform0.matrix().m13;
224    transform1.matrix_mut_unchecked().m23 = transform0.matrix().m23;
225
226    for i in (0..LEVELS).rev() {
227        let scale_down = 1 << i;
228
229        transform1.matrix_mut_unchecked().m13 /= scale_down as f32;
230        transform1.matrix_mut_unchecked().m23 /= scale_down as f32;
231
232        let pattern = patch::Pattern52::new(
233            &image_pyramid0[i as usize],
234            transform0.matrix().m13 / scale_down as f32,
235            transform0.matrix().m23 / scale_down as f32,
236        );
237        patch_valid &= pattern.valid;
238        if patch_valid {
239            // Perform tracking on current level
240            patch_valid &=
241                track_point_at_level(&image_pyramid1[i as usize], &pattern, &mut transform1);
242            if !patch_valid {
243                return None;
244            }
245        } else {
246            return None;
247        }
248
249        transform1.matrix_mut_unchecked().m13 *= scale_down as f32;
250        transform1.matrix_mut_unchecked().m23 *= scale_down as f32;
251        // transform1.matrix_mut_unchecked().m33 = 1.0;
252    }
253    let new_r_mat = transform0.matrix() * transform1.matrix();
254    transform1.matrix_mut_unchecked().m11 = new_r_mat.m11;
255    transform1.matrix_mut_unchecked().m12 = new_r_mat.m12;
256    transform1.matrix_mut_unchecked().m21 = new_r_mat.m21;
257    transform1.matrix_mut_unchecked().m22 = new_r_mat.m22;
258    Some(transform1)
259}
260
261pub fn track_point_at_level(
262    grayscale_image: &GrayImage,
263    dp: &patch::Pattern52,
264    transform: &mut na::Affine2<f32>,
265) -> bool {
266    // let mut patch_valid: bool = false;
267    let optical_flow_max_iterations = 5;
268    let patten = na::SMatrix::<f32, 52, 2>::from_fn(|i, j| {
269        patch::Pattern52::PATTERN_RAW[i][j] / dp.pattern_scale_down
270    })
271    .transpose();
272    // transform.
273    // println!("before {}", transform.matrix());
274    for _iteration in 0..optical_flow_max_iterations {
275        let mut transformed_pat = transform.matrix().fixed_view::<2, 2>(0, 0) * patten;
276        for i in 0..52 {
277            transformed_pat
278                .column_mut(i)
279                .add_assign(transform.matrix().fixed_view::<2, 1>(0, 2));
280        }
281        // println!("{}", smatrix.transpose());
282        // let mut res = na::SVector::<f32, PATTERN52_SIZE>::zeros();
283        if let Some(res) = dp.residual(grayscale_image, &transformed_pat) {
284            let inc = -dp.h_se2_inv_j_se2_t * res;
285
286            // avoid NaN in increment (leads to SE2::exp crashing)
287            if !inc.iter().all(|x| x.is_finite()) {
288                return false;
289            }
290            if inc.norm() > 1e6 {
291                return false;
292            }
293            let new_trans = transform.matrix() * image_utilities::se2_exp_matrix(&inc);
294            *transform = na::Affine2::<f32>::from_matrix_unchecked(new_trans);
295            let filter_margin = 2;
296            if !image_utilities::inbound(
297                grayscale_image,
298                transform.matrix_mut_unchecked().m13,
299                transform.matrix_mut_unchecked().m23,
300                filter_margin,
301            ) {
302                return false;
303            }
304        }
305    }
306
307    true
308}