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