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 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 self.tracked_points_map = track_points::<LEVELS>(
34 &self.previous_image_pyramid,
35 ¤t_image_pyramid,
36 &self.tracked_points_map,
37 );
38 info!("tracked old points {}", self.tracked_points_map.len());
39 }
40 let new_points = add_points(&self.tracked_points_map, ¤t_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 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 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 if !self.previous_image_pyramid0.is_empty() {
84 info!("old points {}", self.tracked_points_map_cam0.len());
85 self.tracked_points_map_cam0 = track_points::<LEVELS>(
87 &self.previous_image_pyramid0,
88 ¤t_image_pyramid0,
89 &self.tracked_points_map_cam0,
90 );
91 self.tracked_points_map_cam1 = track_points::<LEVELS>(
92 &self.previous_image_pyramid1,
93 ¤t_image_pyramid1,
94 &self.tracked_points_map_cam1,
95 );
96 info!("tracked old points {}", self.tracked_points_map_cam0.len());
97 }
98 let new_points0 = add_points(
100 &self.tracked_points_map_cam0,
101 ¤t_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 ¤t_image_pyramid0,
117 ¤t_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 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 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 ¤t_corners,
201 num_points_in_cell,
202 )
203 }
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 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 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 }
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 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 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 if let Some(res) = dp.residual(grayscale_image, &transformed_pat) {
304 let inc = -dp.h_se2_inv_j_se2_t * res;
305
306 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}