1use crate::stereo::calibration::StereoCalibration;
8
9pub struct StereoRectifier {
29 pub calib: StereoCalibration,
31 pub map_left: Option<(Vec<f32>, Vec<f32>)>,
33 pub map_right: Option<(Vec<f32>, Vec<f32>)>,
35 pub r_left: [[f64; 3]; 3],
37 pub r_right: [[f64; 3]; 3],
39}
40
41impl StereoRectifier {
42 pub fn new(calib: StereoCalibration) -> Self {
44 Self {
45 calib,
46 map_left: None,
47 map_right: None,
48 r_left: identity_3x3(),
49 r_right: identity_3x3(),
50 }
51 }
52
53 pub fn compute_maps(&mut self, width: usize, height: usize) {
61 let n = width * height;
62
63 let (r_l, r_r) = self.compute_rectification_rotations();
65 self.r_left = r_l;
66 self.r_right = r_r;
67
68 let fl = self.calib.left.fx as f32;
71 let fr = self.calib.right.fx as f32;
72 let cxl = self.calib.left.cx as f32;
73 let cyl = self.calib.left.cy as f32;
74 let cxr = self.calib.right.cx as f32;
75 let cyr = self.calib.right.cy as f32;
76
77 let mut mx_l = vec![0.0_f32; n];
78 let mut my_l = vec![0.0_f32; n];
79 let mut mx_r = vec![0.0_f32; n];
80 let mut my_r = vec![0.0_f32; n];
81
82 let f_new = fl;
84 let cx_new = cxl;
85 let cy_new = cyl;
86
87 for row in 0..height {
88 for col in 0..width {
89 let idx = row * width + col;
90
91 {
93 let xn = (col as f32 - cx_new) / f_new;
94 let yn = (row as f32 - cy_new) / f_new;
95 let p = mat3_apply_f32(&r_l, [xn, yn, 1.0]);
96 let xp = p[0] / p[2];
97 let yp = p[1] / p[2];
98 mx_l[idx] = xp * fl + cxl;
99 my_l[idx] = yp * fl + cyl;
100 }
101
102 {
104 let xn = (col as f32 - cx_new) / f_new;
105 let yn = (row as f32 - cy_new) / f_new;
106 let p = mat3_apply_f32(&r_r, [xn, yn, 1.0]);
107 let xp = p[0] / p[2];
108 let yp = p[1] / p[2];
109 mx_r[idx] = xp * fr + cxr;
110 my_r[idx] = yp * fr + cyr;
111 }
112 }
113 }
114
115 self.map_left = Some((mx_l, my_l));
116 self.map_right = Some((mx_r, my_r));
117 }
118
119 pub fn remap_left(&self, image: &[u8], width: usize, height: usize) -> Vec<u8> {
121 self.map_left
122 .as_ref()
123 .map(|m| remap_bilinear(image, width, height, m))
124 .unwrap_or_else(|| image.to_vec())
125 }
126
127 pub fn remap_right(&self, image: &[u8], width: usize, height: usize) -> Vec<u8> {
129 self.map_right
130 .as_ref()
131 .map(|m| remap_bilinear(image, width, height, m))
132 .unwrap_or_else(|| image.to_vec())
133 }
134
135 pub fn remap(
137 &self,
138 image: &[u8],
139 width: usize,
140 height: usize,
141 map: &(Vec<f32>, Vec<f32>),
142 ) -> Vec<u8> {
143 remap_bilinear(image, width, height, map)
144 }
145
146 fn compute_rectification_rotations(&self) -> ([[f64; 3]; 3], [[f64; 3]; 3]) {
151 let r = self.calib.rotation;
156
157 let (axis, angle) = rotation_to_axis_angle(&r);
159
160 let r_l = axis_angle_to_matrix(axis, angle * 0.5);
162 let r_r = axis_angle_to_matrix(axis, -angle * 0.5);
163
164 (transpose_3x3(&r_l), transpose_3x3(&r_r))
166 }
167}
168
169pub fn remap_bilinear(
178 image: &[u8],
179 width: usize,
180 height: usize,
181 map: &(Vec<f32>, Vec<f32>),
182) -> Vec<u8> {
183 let n = width * height;
184 let mut out = vec![0u8; n];
185 let (map_x, map_y) = map;
186
187 for i in 0..n {
188 let x = map_x[i];
189 let y = map_y[i];
190
191 if x < 0.0 || y < 0.0 || x >= (width - 1) as f32 || y >= (height - 1) as f32 {
192 let xi = x.round().clamp(0.0, (width - 1) as f32) as usize;
194 let yi = y.round().clamp(0.0, (height - 1) as f32) as usize;
195 out[i] = image[yi * width + xi];
196 continue;
197 }
198
199 let xi = x as usize;
200 let yi = y as usize;
201 let fx = x - xi as f32;
202 let fy = y - yi as f32;
203
204 let v00 = image[yi * width + xi] as f32;
205 let v01 = image[yi * width + xi + 1] as f32;
206 let v10 = image[(yi + 1) * width + xi] as f32;
207 let v11 = image[(yi + 1) * width + xi + 1] as f32;
208
209 let val = (1.0 - fy) * ((1.0 - fx) * v00 + fx * v01) + fy * ((1.0 - fx) * v10 + fx * v11);
210 out[i] = val as u8;
211 }
212 out
213}
214
215fn identity_3x3() -> [[f64; 3]; 3] {
220 [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
221}
222
223fn transpose_3x3(m: &[[f64; 3]; 3]) -> [[f64; 3]; 3] {
224 [
225 [m[0][0], m[1][0], m[2][0]],
226 [m[0][1], m[1][1], m[2][1]],
227 [m[0][2], m[1][2], m[2][2]],
228 ]
229}
230
231fn rotation_to_axis_angle(r: &[[f64; 3]; 3]) -> ([f64; 3], f64) {
233 let trace = r[0][0] + r[1][1] + r[2][2];
235 let cos_theta = ((trace - 1.0) / 2.0).clamp(-1.0, 1.0);
236 let theta = cos_theta.acos();
237
238 if theta.abs() < 1e-10 {
239 return ([0.0, 0.0, 1.0], 0.0);
241 }
242
243 let denom = 2.0 * theta.sin();
244 let axis = [
245 (r[2][1] - r[1][2]) / denom,
246 (r[0][2] - r[2][0]) / denom,
247 (r[1][0] - r[0][1]) / denom,
248 ];
249 (axis, theta)
250}
251
252fn axis_angle_to_matrix(axis: [f64; 3], angle: f64) -> [[f64; 3]; 3] {
254 let (s, c) = angle.sin_cos();
255 let t = 1.0 - c;
256 let [ax, ay, az] = axis;
257
258 [
259 [t * ax * ax + c, t * ax * ay - s * az, t * ax * az + s * ay],
260 [t * ax * ay + s * az, t * ay * ay + c, t * ay * az - s * ax],
261 [t * ax * az - s * ay, t * ay * az + s * ax, t * az * az + c],
262 ]
263}
264
265fn mat3_apply_f32(m: &[[f64; 3]; 3], v: [f32; 3]) -> [f32; 3] {
267 let [x, y, z] = [v[0] as f64, v[1] as f64, v[2] as f64];
268 [
269 (m[0][0] * x + m[0][1] * y + m[0][2] * z) as f32,
270 (m[1][0] * x + m[1][1] * y + m[1][2] * z) as f32,
271 (m[2][0] * x + m[2][1] * y + m[2][2] * z) as f32,
272 ]
273}
274
275#[cfg(test)]
280mod tests {
281 use super::*;
282
283 fn make_parallel_cal() -> StereoCalibration {
284 StereoCalibration::from_baseline(500.0, 500.0, 32.0, 24.0, 0.1)
285 }
286
287 #[test]
288 fn test_compute_maps_produces_maps() {
289 let mut rect = StereoRectifier::new(make_parallel_cal());
290 rect.compute_maps(64, 48);
291 assert!(rect.map_left.is_some());
292 assert!(rect.map_right.is_some());
293
294 let (mx, my) = rect.map_left.as_ref().expect("map_left should exist");
295 assert_eq!(mx.len(), 64 * 48);
296 assert_eq!(my.len(), 64 * 48);
297 }
298
299 #[test]
300 fn test_remap_identity_like() {
301 let mut rect = StereoRectifier::new(make_parallel_cal());
304 rect.compute_maps(32, 24);
305
306 let img: Vec<u8> = (0..32 * 24).map(|i| ((i * 3) % 256) as u8).collect();
307
308 let out = rect.remap_left(&img, 32, 24);
309 assert_eq!(out.len(), img.len());
310 }
311
312 #[test]
313 fn test_remap_bilinear_boundary() {
314 let img = vec![100u8; 10 * 10];
315 let n = 10 * 10;
316 let map = (vec![0.0_f32; n], vec![0.0_f32; n]);
318 let out = remap_bilinear(&img, 10, 10, &map);
319 assert_eq!(out.len(), n);
320 assert!(out.iter().all(|&v| v == 100));
321 }
322
323 #[test]
324 fn test_rotation_utilities_roundtrip() {
325 let angle_in = 0.3_f64;
327 let axis = [0.0, 0.0, 1.0];
328 let r = axis_angle_to_matrix(axis, angle_in);
329 let (_, angle_out) = rotation_to_axis_angle(&r);
330 assert!((angle_in - angle_out).abs() < 1e-8, "angle roundtrip");
331 }
332}