const UNDISTORT_ITERS: usize = 20;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Camera {
pub fx: f64,
pub fy: f64,
pub cx: f64,
pub cy: f64,
pub k1: f64,
pub k2: f64,
pub p1: f64,
pub p2: f64,
pub k3: f64,
}
impl Camera {
pub fn new(fx: f64, fy: f64, cx: f64, cy: f64) -> Self {
Self {
fx,
fy,
cx,
cy,
k1: 0.0,
k2: 0.0,
p1: 0.0,
p2: 0.0,
k3: 0.0,
}
}
#[allow(clippy::too_many_arguments)]
pub fn with_distortion(
fx: f64,
fy: f64,
cx: f64,
cy: f64,
k1: f64,
k2: f64,
p1: f64,
p2: f64,
k3: f64,
) -> Self {
Self {
fx,
fy,
cx,
cy,
k1,
k2,
p1,
p2,
k3,
}
}
pub fn undistort_point(&self, u: f64, v: f64) -> (f64, f64) {
let (x0, y0) = ((u - self.cx) / self.fx, (v - self.cy) / self.fy);
let (mut x, mut y) = (x0, y0);
for _ in 0..UNDISTORT_ITERS {
let r2 = x * x + y * y;
let radial = 1.0 + ((self.k3 * r2 + self.k2) * r2 + self.k1) * r2;
let dx = 2.0 * self.p1 * x * y + self.p2 * (r2 + 2.0 * x * x);
let dy = self.p1 * (r2 + 2.0 * y * y) + 2.0 * self.p2 * x * y;
x = (x0 - dx) / radial;
y = (y0 - dy) / radial;
}
(x * self.fx + self.cx, y * self.fy + self.cy)
}
pub fn distort_point(&self, x: f64, y: f64) -> (f64, f64) {
let r2 = x * x + y * y;
let radial = 1.0 + ((self.k3 * r2 + self.k2) * r2 + self.k1) * r2;
let dx = 2.0 * self.p1 * x * y + self.p2 * (r2 + 2.0 * x * x);
let dy = self.p1 * (r2 + 2.0 * y * y) + 2.0 * self.p2 * x * y;
(
(x * radial + dx) * self.fx + self.cx,
(y * radial + dy) * self.fy + self.cy,
)
}
}
#[cfg(test)]
mod tests {
use super::Camera;
#[test]
fn undistort_without_distortion_is_identity() {
let camera = Camera::new(256.9, 256.9, 131.6, 188.6);
for &(u, v) in &[(0.0, 0.0), (100.0, 50.0), (345.0, 259.0)] {
let (nu, nv) = camera.undistort_point(u, v);
assert!((nu - u).abs() < 1e-9 && (nv - v).abs() < 1e-9);
}
}
#[test]
fn undistort_inverts_the_forward_distortion() {
let camera = Camera::with_distortion(
256.919, 256.862, 131.651, 188.576, -0.366475, 0.130235, 0.000262, 0.000191, 0.0,
);
for &(xn, yn) in &[(0.0, 0.0), (0.2, -0.3), (-0.4, 0.1), (0.5, 0.5)] {
let (u, v) = camera.distort_point(xn, yn);
let (xu, yu) = camera.undistort_point(u, v);
let (ideal_u, ideal_v) = (xn * camera.fx + camera.cx, yn * camera.fy + camera.cy);
assert!(
(xu - ideal_u).abs() < 1e-3 && (yu - ideal_v).abs() < 1e-3,
"round-trip drift at ({xn}, {yn}): got ({xu}, {yu}) vs ({ideal_u}, {ideal_v})"
);
}
}
}