1const UNDISTORT_ITERS: usize = 20;
8
9#[derive(Clone, Copy, Debug, PartialEq)]
11pub struct Camera {
12 pub fx: f64,
13 pub fy: f64,
14 pub cx: f64,
15 pub cy: f64,
16 pub k1: f64,
17 pub k2: f64,
18 pub p1: f64,
19 pub p2: f64,
20 pub k3: f64,
21}
22
23impl Camera {
24 pub fn new(fx: f64, fy: f64, cx: f64, cy: f64) -> Self {
26 Self {
27 fx,
28 fy,
29 cx,
30 cy,
31 k1: 0.0,
32 k2: 0.0,
33 p1: 0.0,
34 p2: 0.0,
35 k3: 0.0,
36 }
37 }
38
39 #[allow(clippy::too_many_arguments)]
41 pub fn with_distortion(
42 fx: f64,
43 fy: f64,
44 cx: f64,
45 cy: f64,
46 k1: f64,
47 k2: f64,
48 p1: f64,
49 p2: f64,
50 k3: f64,
51 ) -> Self {
52 Self {
53 fx,
54 fy,
55 cx,
56 cy,
57 k1,
58 k2,
59 p1,
60 p2,
61 k3,
62 }
63 }
64
65 pub fn undistort_point(&self, u: f64, v: f64) -> (f64, f64) {
69 let (x0, y0) = ((u - self.cx) / self.fx, (v - self.cy) / self.fy);
70 let (mut x, mut y) = (x0, y0);
71 for _ in 0..UNDISTORT_ITERS {
72 let r2 = x * x + y * y;
73 let radial = 1.0 + ((self.k3 * r2 + self.k2) * r2 + self.k1) * r2;
74 let dx = 2.0 * self.p1 * x * y + self.p2 * (r2 + 2.0 * x * x);
75 let dy = self.p1 * (r2 + 2.0 * y * y) + 2.0 * self.p2 * x * y;
76 x = (x0 - dx) / radial;
77 y = (y0 - dy) / radial;
78 }
79 (x * self.fx + self.cx, y * self.fy + self.cy)
80 }
81
82 pub fn distort_point(&self, x: f64, y: f64) -> (f64, f64) {
85 let r2 = x * x + y * y;
86 let radial = 1.0 + ((self.k3 * r2 + self.k2) * r2 + self.k1) * r2;
87 let dx = 2.0 * self.p1 * x * y + self.p2 * (r2 + 2.0 * x * x);
88 let dy = self.p1 * (r2 + 2.0 * y * y) + 2.0 * self.p2 * x * y;
89 (
90 (x * radial + dx) * self.fx + self.cx,
91 (y * radial + dy) * self.fy + self.cy,
92 )
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::Camera;
99
100 #[test]
101 fn undistort_without_distortion_is_identity() {
102 let camera = Camera::new(256.9, 256.9, 131.6, 188.6);
103 for &(u, v) in &[(0.0, 0.0), (100.0, 50.0), (345.0, 259.0)] {
104 let (nu, nv) = camera.undistort_point(u, v);
105 assert!((nu - u).abs() < 1e-9 && (nv - v).abs() < 1e-9);
106 }
107 }
108
109 #[test]
110 fn undistort_inverts_the_forward_distortion() {
111 let camera = Camera::with_distortion(
113 256.919, 256.862, 131.651, 188.576, -0.366475, 0.130235, 0.000262, 0.000191, 0.0,
114 );
115 for &(xn, yn) in &[(0.0, 0.0), (0.2, -0.3), (-0.4, 0.1), (0.5, 0.5)] {
117 let (u, v) = camera.distort_point(xn, yn);
118 let (xu, yu) = camera.undistort_point(u, v);
119 let (ideal_u, ideal_v) = (xn * camera.fx + camera.cx, yn * camera.fy + camera.cy);
120 assert!(
121 (xu - ideal_u).abs() < 1e-3 && (yu - ideal_v).abs() < 1e-3,
122 "round-trip drift at ({xn}, {yn}): got ({xu}, {yu}) vs ({ideal_u}, {ideal_v})"
123 );
124 }
125 }
126}