Skip to main content

patch_tracker/
tracker.rs

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