Skip to main content

patch_tracker/
image_utilities.rs

1use crate::corners_fast9::Corner;
2use image::{GrayImage, Luma};
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::*;
9
10pub type ImageLumaf32 = image::ImageBuffer<Luma<f32>, Vec<f32>>;
11pub type Vec3f = na::SVector<f32, 3>;
12
13pub fn image_gray_to_f32(img: &GrayImage) -> ImageLumaf32 {
14    let buf = img.par_iter().map(|&p| p as f32).collect();
15    ImageLumaf32::from_vec(img.width(), img.height(), buf).expect("failed to f32")
16}
17
18// Computes 3-channel gradient (dx, dy, dx*dx + dy*dy) and intensity map from a greyscale image.
19pub fn compute_gradients(img: &GrayImage) -> (Vec<f32>, Vec<Vec3f>) {
20    let w = img.width();
21    let h = img.height();
22    let size = (w * h) as usize;
23
24    let mut intensity = vec![0.0f32; size];
25    let mut gradients = vec![Vec3f::zeros(); size];
26
27    // Copy intensity and convert to f32
28    // Simple copy for now, optimization: combine with loops below if needed, but rayon helps.
29    img.pixels().enumerate().for_each(|(i, p)| {
30        // Note: GrayImage iterator is row-major
31        intensity[i] = p[0] as f32;
32    });
33
34    // Compute gradients (central difference, handling borders)
35    // Parallel iter over rows for efficiency
36    // Safety: we are careful with indices.
37    // Optimization: could be better with unsafe unchecked access or SIMD, keeping it safe for now.
38
39    let w = w as usize;
40    let h = h as usize;
41
42    // We can't easily parallelize mutable writes to the same Vec without unsafe or splitting slices.
43    // For simplicity in this `safe` version, we iterate normally or use chunks.
44    // Let's use simple iter for now, optimize later.
45
46    for y in 1..h - 1 {
47        for x in 1..w - 1 {
48            let idx = y * w + x;
49            let g_idx = y + x * h;
50
51            // Central difference
52            // dx = (I(x+1) - I(x-1)) * 0.5
53            // dy = (I(y+1) - I(y-1)) * 0.5
54
55            let val_right = intensity[idx + 1];
56            let val_left = intensity[idx - 1];
57            let val_down = intensity[idx + w];
58            let val_up = intensity[idx - w];
59
60            let dx = 0.5 * (val_right - val_left);
61            let dy = 0.5 * (val_down - val_up);
62
63            // Third channel is often used for weight or gradient magnitude squared in DSO,
64            // but for now we put just 1.0 or placeholder.
65            // In original DSO: vec3(dx, dy, sqrt(dx*dx + dy*dy) ?) -> actually correlation.
66            // Let's store (dx, dy, 1.0) for now.
67            gradients[g_idx] = Vec3f::new(dx, dy, dx * dx + dy * dy);
68        }
69    }
70
71    (intensity, gradients)
72}
73pub fn compute_gradients_f32(img: &ImageLumaf32) -> Vec<Vec3f> {
74    let w = img.width();
75    let h = img.height();
76    let size = (w * h) as usize;
77
78    let mut intensity = vec![0.0f32; size];
79    let mut gradients = vec![Vec3f::zeros(); size];
80
81    // Copy intensity and convert to f32
82    // Simple copy for now, optimization: combine with loops below if needed, but rayon helps.
83    img.pixels().enumerate().for_each(|(i, p)| {
84        // Note: GrayImage iterator is row-major
85        intensity[i] = p[0];
86    });
87
88    // Compute gradients (central difference, handling borders)
89    // Parallel iter over rows for efficiency
90    // Safety: we are careful with indices.
91    // Optimization: could be better with unsafe unchecked access or SIMD, keeping it safe for now.
92
93    let w = w as usize;
94    let h = h as usize;
95
96    // We can't easily parallelize mutable writes to the same Vec without unsafe or splitting slices.
97    // For simplicity in this `safe` version, we iterate normally or use chunks.
98    // Let's use simple iter for now, optimize later.
99
100    for y in 1..h - 1 {
101        for x in 1..w - 1 {
102            let idx = y * w + x;
103            let g_idx = y + x * h;
104
105            // Central difference
106            // dx = (I(x+1) - I(x-1)) * 0.5
107            // dy = (I(y+1) - I(y-1)) * 0.5
108
109            let val_right = intensity[idx + 1];
110            let val_left = intensity[idx - 1];
111            let val_down = intensity[idx + w];
112            let val_up = intensity[idx - w];
113
114            let dx = 0.5 * (val_right - val_left);
115            let dy = 0.5 * (val_down - val_up);
116
117            // Third channel is often used for weight or gradient magnitude squared in DSO,
118            // but for now we put just 1.0 or placeholder.
119            // In original DSO: vec3(dx, dy, sqrt(dx*dx + dy*dy) ?) -> actually correlation.
120            // Let's store (dx, dy, 1.0) for now.
121            gradients[g_idx] = Vec3f::new(dx, dy, dx * dx + dy * dy);
122        }
123    }
124
125    gradients
126}
127
128pub trait HalfSize {
129    fn half_size(&self) -> Self;
130}
131
132impl HalfSize for GrayImage {
133    fn half_size(&self) -> Self {
134        let (w, h) = (self.width() as usize, self.height() as usize);
135        let new_w = w / 2;
136        let new_h = h / 2;
137        let src_ptr = self.as_raw();
138        // Pre-allocate the buffer to avoid reallocs
139        let mut dst = Vec::with_capacity(new_w * new_h);
140
141        unsafe {
142            let s_ptr = src_ptr.as_ptr();
143            let d_ptr: *mut u8 = dst.as_mut_ptr();
144
145            for y in 0..new_h {
146                // Pre-calculate row starts to avoid multiplications in the inner loop
147                let row0 = s_ptr.add((y * 2) * w);
148                let row1 = s_ptr.add((y * 2 + 1) * w);
149                let dst_row = d_ptr.add(y * new_w);
150
151                for x in 0..new_w {
152                    let x2 = x * 2;
153                    // Read 4 pixels (2x2 block)
154                    let p00 = *row0.add(x2) as u32;
155                    let p01 = *row0.add(x2 + 1) as u32;
156                    let p10 = *row1.add(x2) as u32;
157                    let p11 = *row1.add(x2 + 1) as u32;
158
159                    // Average: (sum + 2) / 4. Adding 2 handles rounding correctly.
160                    let avg = (p00 + p01 + p10 + p11 + 2) >> 2;
161
162                    *dst_row.add(x) = avg as u8;
163                }
164            }
165            dst.set_len(new_w * new_h);
166        }
167
168        // Wrap back into GrayImage if needed, or just black_box the Vec
169        GrayImage::from_raw(new_w as u32, new_h as u32, dst).unwrap()
170    }
171}
172
173impl HalfSize for ImageLumaf32 {
174    fn half_size(&self) -> Self {
175        let (w, h) = (self.width() as usize, self.height() as usize);
176        let new_w = w / 2;
177        let new_h = h / 2;
178        let src_ptr = self.as_raw();
179        let mut dst = Vec::with_capacity(new_w * new_h);
180
181        unsafe {
182            let s_ptr = src_ptr.as_ptr();
183            let d_ptr: *mut f32 = dst.as_mut_ptr();
184
185            for y in 0..new_h {
186                let row0 = s_ptr.add(y * 2 * w);
187                let row1 = row0.add(w);
188                let dst_row = d_ptr.add(y * new_w);
189
190                for x in 0..new_w {
191                    let x2 = x * 2;
192                    let avg =
193                        (*row0.add(x2) + *row0.add(x2 + 1) + *row1.add(x2) + *row1.add(x2 + 1))
194                            * 0.25;
195
196                    *dst_row.add(x) = avg;
197                }
198            }
199            dst.set_len(new_w * new_h);
200        }
201        ImageLumaf32::from_raw(new_w as u32, new_h as u32, dst).unwrap()
202    }
203}
204
205pub fn to_u8_image(image: &ImageLumaf32) -> GrayImage {
206    let u8_data = image
207        .clone()
208        .into_vec()
209        .iter()
210        .map(|&x| {
211            if x < 0.0 {
212                0u8
213            } else if x > 255.0 {
214                255u8
215            } else {
216                x as u8
217            }
218        })
219        .collect::<Vec<u8>>();
220    GrayImage::from_raw(image.width(), image.height(), u8_data).unwrap()
221}
222
223pub fn image_grad(grayscale_image: &GrayImage, x: f32, y: f32) -> na::SVector<f32, 3> {
224    // inbound
225    let ix = x.floor() as u32;
226    let iy = y.floor() as u32;
227
228    let dx = x - ix as f32;
229    let dy = y - iy as f32;
230
231    let ddx = 1.0 - dx;
232    let ddy = 1.0 - dy;
233
234    let px0y0 = grayscale_image.get_pixel(ix, iy).0[0] as f32;
235    let px1y0 = grayscale_image.get_pixel(ix + 1, iy).0[0] as f32;
236    let px0y1 = grayscale_image.get_pixel(ix, iy + 1).0[0] as f32;
237    let px1y1 = grayscale_image.get_pixel(ix + 1, iy + 1).0[0] as f32;
238
239    let res0 = ddx * ddy * px0y0 + ddx * dy * px0y1 + dx * ddy * px1y0 + dx * dy * px1y1;
240
241    let pxm1y0 = grayscale_image.get_pixel(ix - 1, iy).0[0] as f32;
242    let pxm1y1 = grayscale_image.get_pixel(ix - 1, iy + 1).0[0] as f32;
243
244    let res_mx = ddx * ddy * pxm1y0 + ddx * dy * pxm1y1 + dx * ddy * px0y0 + dx * dy * px0y1;
245
246    let px2y0 = grayscale_image.get_pixel(ix + 2, iy).0[0] as f32;
247    let px2y1 = grayscale_image.get_pixel(ix + 2, iy + 1).0[0] as f32;
248
249    let res_px = ddx * ddy * px1y0 + ddx * dy * px1y1 + dx * ddy * px2y0 + dx * dy * px2y1;
250
251    let res1 = 0.5 * (res_px - res_mx);
252
253    let px0ym1 = grayscale_image.get_pixel(ix, iy - 1).0[0] as f32;
254    let px1ym1 = grayscale_image.get_pixel(ix + 1, iy - 1).0[0] as f32;
255
256    let res_my = ddx * ddy * px0ym1 + ddx * dy * px0y0 + dx * ddy * px1ym1 + dx * dy * px1y0;
257
258    let px0y2 = grayscale_image.get_pixel(ix, iy + 2).0[0] as f32;
259    let px1y2 = grayscale_image.get_pixel(ix + 1, iy + 2).0[0] as f32;
260
261    let res_py = ddx * ddy * px0y1 + ddx * dy * px0y2 + dx * ddy * px1y1 + dx * dy * px1y2;
262
263    let res2 = 0.5 * (res_py - res_my);
264
265    na::SVector::<f32, 3>::new(res0, res1, res2)
266}
267
268pub fn point_in_bound(keypoint: &Corner, height: u32, width: u32, radius: u32) -> bool {
269    keypoint.x >= radius
270        && keypoint.x <= width - radius
271        && keypoint.y >= radius
272        && keypoint.y <= height - radius
273}
274
275pub fn inbound(image: &GrayImage, x: f32, y: f32, radius: u32) -> bool {
276    let x = x.round() as u32;
277    let y = y.round() as u32;
278
279    x >= radius && y >= radius && x < image.width() - radius && y < image.height() - radius
280}
281
282pub fn se2_exp_matrix(a: &na::SVector<f32, 3>) -> na::SMatrix<f32, 3, 3> {
283    let theta = a[2];
284    let mut so2 = na::Rotation2::new(theta);
285    let sin_theta_by_theta;
286    let one_minus_cos_theta_by_theta;
287
288    if theta.abs() < f32::EPSILON {
289        let theta_sq = theta * theta;
290        sin_theta_by_theta = 1.0f32 - 1.0 / 6.0 * theta_sq;
291        one_minus_cos_theta_by_theta = 0.5f32 * theta - 1. / 24. * theta * theta_sq;
292    } else {
293        let cos = so2.matrix_mut_unchecked().m22;
294        let sin = so2.matrix_mut_unchecked().m21;
295        sin_theta_by_theta = sin / theta;
296        one_minus_cos_theta_by_theta = (1. - cos) / theta;
297    }
298    let mut se2_mat = na::SMatrix::<f32, 3, 3>::identity();
299    se2_mat.m11 = so2.matrix_mut_unchecked().m11;
300    se2_mat.m12 = so2.matrix_mut_unchecked().m12;
301    se2_mat.m21 = so2.matrix_mut_unchecked().m21;
302    se2_mat.m22 = so2.matrix_mut_unchecked().m22;
303    se2_mat.m13 = sin_theta_by_theta * a[0] - one_minus_cos_theta_by_theta * a[1];
304    se2_mat.m23 = one_minus_cos_theta_by_theta * a[0] + sin_theta_by_theta * a[1];
305    se2_mat
306}
307
308pub fn detect_key_points(
309    image: &GrayImage,
310    detect_image: &GrayImage,
311    detect_scale: u32,
312    grid_size: u32,
313    current_corners: &Vec<Corner>,
314    num_points_in_cell: u32,
315) -> Vec<Corner> {
316    const EDGE_THRESHOLD: u32 = 19;
317    const MIN_THRESHOLD: u8 = 10;
318    let h = image.height();
319    let w = image.width();
320
321    let x_start = (w % grid_size) / 2;
322    let y_start = (h % grid_size) / 2;
323
324    let grid_cols = (w / grid_size) as usize;
325    let grid_rows = (h / grid_size) as usize;
326
327    // Track how many points each cell already has
328    let mut grid_count = vec![0u32; grid_rows * grid_cols];
329
330    for corner in current_corners {
331        if corner.x >= x_start && corner.y >= y_start {
332            let gx = ((corner.x - x_start) / grid_size) as usize;
333            let gy = ((corner.y - y_start) / grid_size) as usize;
334            if gx < grid_cols && gy < grid_rows {
335                grid_count[gy * grid_cols + gx] += 1;
336            }
337        }
338    }
339
340    // Run FAST9 on the detection image (possibly lower resolution) once
341    let mut all_fast_corners =
342        crate::corners_fast9::simd_corners_fast9(detect_image, MIN_THRESHOLD);
343
344    // Sort descending by score to prioritize the strongest corners
345    all_fast_corners.sort_unstable_by(|a, b| {
346        b.score
347            .partial_cmp(&a.score)
348            .unwrap_or(std::cmp::Ordering::Equal)
349    });
350
351    // Assign best corners to empty grid cells
352    let mut result = Vec::new();
353
354    for mut corner in all_fast_corners {
355        // Scale coordinates back to full resolution
356        corner.x *= detect_scale;
357        corner.y *= detect_scale;
358
359        if !point_in_bound(&corner, h, w, EDGE_THRESHOLD) {
360            continue;
361        }
362        if corner.x < x_start || corner.y < y_start {
363            continue;
364        }
365
366        let gx = ((corner.x - x_start) / grid_size) as usize;
367        let gy = ((corner.y - y_start) / grid_size) as usize;
368
369        if gx >= grid_cols || gy >= grid_rows {
370            continue;
371        }
372
373        let cell_idx = gy * grid_cols + gx;
374        if grid_count[cell_idx] >= num_points_in_cell {
375            continue;
376        }
377
378        grid_count[cell_idx] += 1;
379        result.push(corner);
380    }
381
382    result
383}