1use image::imageops;
2use image::GrayImage;
3use nalgebra as na;
4use std::ops::AddAssign;
5
6use crate::image_utilities;
7
8pub const PATTERN52_SIZE: usize = 52;
9pub struct Pattern52 {
10 pub valid: bool,
11 pub mean: f32,
12 pub pos: na::SVector<f32, 2>,
13 pub data: [f32; PATTERN52_SIZE], pub h_se2_inv_j_se2_t: na::SMatrix<f32, 3, PATTERN52_SIZE>,
15 pub pattern_scale_down: f32,
16}
17impl Pattern52 {
18 pub const PATTERN_RAW: [[f32; 2]; PATTERN52_SIZE] = [
19 [-3.0, 7.0],
20 [-1.0, 7.0],
21 [1.0, 7.0],
22 [3.0, 7.0],
23 [-5.0, 5.0],
24 [-3.0, 5.0],
25 [-1.0, 5.0],
26 [1.0, 5.0],
27 [3.0, 5.0],
28 [5.0, 5.0],
29 [-7.0, 3.0],
30 [-5.0, 3.0],
31 [-3.0, 3.0],
32 [-1.0, 3.0],
33 [1.0, 3.0],
34 [3.0, 3.0],
35 [5.0, 3.0],
36 [7.0, 3.0],
37 [-7.0, 1.0],
38 [-5.0, 1.0],
39 [-3.0, 1.0],
40 [-1.0, 1.0],
41 [1.0, 1.0],
42 [3.0, 1.0],
43 [5.0, 1.0],
44 [7.0, 1.0],
45 [-7.0, -1.0],
46 [-5.0, -1.0],
47 [-3.0, -1.0],
48 [-1.0, -1.0],
49 [1.0, -1.0],
50 [3.0, -1.0],
51 [5.0, -1.0],
52 [7.0, -1.0],
53 [-7.0, -3.0],
54 [-5.0, -3.0],
55 [-3.0, -3.0],
56 [-1.0, -3.0],
57 [1.0, -3.0],
58 [3.0, -3.0],
59 [5.0, -3.0],
60 [7.0, -3.0],
61 [-5.0, -5.0],
62 [-3.0, -5.0],
63 [-1.0, -5.0],
64 [1.0, -5.0],
65 [3.0, -5.0],
66 [5.0, -5.0],
67 [-3.0, -7.0],
68 [-1.0, -7.0],
69 [1.0, -7.0],
70 [3.0, -7.0],
71 ];
72
73 pub fn set_data_jac_se2(
75 &mut self,
76 greyscale_image: &GrayImage,
77 j_se2: &mut na::SMatrix<f32, PATTERN52_SIZE, 3>,
78 ) {
79 let mut num_valid_points = 0;
80 let mut sum: f32 = 0.0;
81 let mut grad_sum_se2 = na::SVector::<f32, 3>::zeros();
82
83 let mut jw_se2 = na::SMatrix::<f32, 2, 3>::identity();
84
85 for (i, pattern_pos) in Self::PATTERN_RAW.into_iter().enumerate() {
86 let p = self.pos
87 + na::SVector::<f32, 2>::new(
88 pattern_pos[0] / self.pattern_scale_down,
89 pattern_pos[1] / self.pattern_scale_down,
90 );
91 jw_se2[(0, 2)] = -pattern_pos[1] / self.pattern_scale_down;
92 jw_se2[(1, 2)] = pattern_pos[0] / self.pattern_scale_down;
93
94 if image_utilities::inbound(greyscale_image, p.x, p.y, 2) {
95 let val_grad = image_utilities::image_grad(greyscale_image, p.x, p.y);
96
97 self.data[i] = val_grad[0];
98 sum += val_grad[0];
99 let re = val_grad.fixed_rows::<2>(1).transpose() * jw_se2;
100 j_se2.set_row(i, &re);
101 grad_sum_se2.add_assign(j_se2.fixed_rows::<1>(i).transpose());
102 num_valid_points += 1;
103 } else {
104 self.data[i] = -1.0;
105 }
106 }
107
108 self.mean = sum / num_valid_points as f32;
109
110 let mean_inv = num_valid_points as f32 / sum;
111
112 for i in 0..Self::PATTERN_RAW.len() {
113 if self.data[i] >= 0.0 {
114 let rhs = grad_sum_se2.transpose() * self.data[i] / sum;
115 j_se2.fixed_rows_mut::<1>(i).add_assign(-rhs);
116 self.data[i] *= mean_inv;
117 } else {
118 j_se2.set_row(i, &na::SMatrix::<f32, 1, 3>::zeros());
119 }
120 }
121 *j_se2 *= mean_inv;
122 }
123 pub fn new(greyscale_image: &GrayImage, px: f32, py: f32) -> Pattern52 {
124 let mut j_se2 = na::SMatrix::<f32, PATTERN52_SIZE, 3>::zeros();
125 let mut p = Pattern52 {
126 valid: false,
127 mean: 1.0,
128 pos: na::SVector::<f32, 2>::new(px, py),
129 data: [0.0; PATTERN52_SIZE], h_se2_inv_j_se2_t: na::SMatrix::<f32, 3, 52>::zeros(),
131 pattern_scale_down: 2.0,
132 };
133 p.set_data_jac_se2(greyscale_image, &mut j_se2);
134 let h_se2 = j_se2.transpose() * j_se2;
135 let mut h_se2_inv = na::SMatrix::<f32, 3, 3>::identity();
136
137 if let Some(x) = h_se2.cholesky() {
138 x.solve_mut(&mut h_se2_inv);
139 p.h_se2_inv_j_se2_t = h_se2_inv * j_se2.transpose();
140
141 p.valid = p.mean > f32::EPSILON
148 && p.h_se2_inv_j_se2_t.iter().all(|x| x.is_finite())
149 && p.data.iter().all(|x| x.is_finite());
150 }
151
152 p
153 }
154 pub fn residual(
155 &self,
156 greyscale_image: &GrayImage,
157 transformed_pattern: &na::SMatrix<f32, 2, PATTERN52_SIZE>,
158 ) -> Option<na::SVector<f32, PATTERN52_SIZE>> {
159 let mut sum: f32 = 0.0;
160 let mut num_valid_points = 0;
161 let mut residual = na::SVector::<f32, PATTERN52_SIZE>::zeros();
162 for i in 0..PATTERN52_SIZE {
163 if image_utilities::inbound(
164 greyscale_image,
165 transformed_pattern[(0, i)],
166 transformed_pattern[(1, i)],
167 2,
168 ) {
169 let p = imageops::interpolate_bilinear(
170 greyscale_image,
171 transformed_pattern[(0, i)],
172 transformed_pattern[(1, i)],
173 );
174 residual[i] = p.unwrap().0[0] as f32;
175 sum += residual[i];
176 num_valid_points += 1;
177 } else {
178 residual[i] = -1.0;
179 }
180 }
181
182 if sum < f32::EPSILON {
184 return None;
185 }
186
187 let mut num_residuals = 0;
188
189 for i in 0..PATTERN52_SIZE {
190 if residual[i] >= 0.0 && self.data[i] >= 0.0 {
191 let val = residual[i];
192 residual[i] = num_valid_points as f32 * val / sum - self.data[i];
193 num_residuals += 1;
194 } else {
195 residual[i] = 0.0;
196 }
197 }
198 if num_residuals > PATTERN52_SIZE / 2 {
199 Some(residual)
200 } else {
201 None
202 }
203 }
204}