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 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 self.tracked_points_map = track_points::<LEVELS>(
27 &self.previous_image_pyramid,
28 ¤t_image_pyramid,
29 &self.tracked_points_map,
30 );
31 info!("tracked old points {}", self.tracked_points_map.len());
32 }
33 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 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 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 if !self.previous_image_pyramid0.is_empty() {
77 info!("old points {}", self.tracked_points_map_cam0.len());
78 self.tracked_points_map_cam0 = track_points::<LEVELS>(
80 &self.previous_image_pyramid0,
81 ¤t_image_pyramid0,
82 &self.tracked_points_map_cam0,
83 );
84 self.tracked_points_map_cam1 = track_points::<LEVELS>(
85 &self.previous_image_pyramid1,
86 ¤t_image_pyramid1,
87 &self.tracked_points_map_cam1,
88 );
89 info!("tracked old points {}", self.tracked_points_map_cam0.len());
90 }
91 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 ¤t_image_pyramid0,
106 ¤t_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 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 image_utilities::detect_key_points(
176 grayscale_image,
177 GRID_SIZE,
178 ¤t_corners,
179 num_points_in_cell,
180 )
181 }
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 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 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 }
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 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 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 if let Some(res) = dp.residual(grayscale_image, &transformed_pat) {
284 let inc = -dp.h_se2_inv_j_se2_t * res;
285
286 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}