#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RadialDistortion {
pub k1: f64,
pub k2: f64,
pub k3: f64,
#[serde(default)]
pub p1: f64,
#[serde(default)]
pub p2: f64,
}
impl RadialDistortion {
pub fn new(k1: f64, k2: f64, k3: f64) -> Self {
Self {
k1,
k2,
k3,
p1: 0.0,
p2: 0.0,
}
}
pub fn with_tangential(k1: f64, k2: f64, k3: f64, p1: f64, p2: f64) -> Self {
Self {
k1,
k2,
k3,
p1,
p2,
}
}
pub fn distort(&self, x: f64, y: f64) -> (f64, f64) {
let r2 = x * x + y * y;
let r4 = r2 * r2;
let r6 = r2 * r4;
let radial = 1.0 + self.k1 * r2 + self.k2 * r4 + self.k3 * r6;
let dx_t = 2.0 * self.p1 * x * y + self.p2 * (r2 + 2.0 * x * x);
let dy_t = self.p1 * (r2 + 2.0 * y * y) + 2.0 * self.p2 * x * y;
(x * radial + dx_t, y * radial + dy_t)
}
pub fn undistort(&self, x_d: f64, y_d: f64) -> (f64, f64) {
let mut x = x_d;
let mut y = y_d;
for _ in 0..20 {
let r2 = x * x + y * y;
let r4 = r2 * r2;
let r6 = r2 * r4;
let radial = 1.0 + self.k1 * r2 + self.k2 * r4 + self.k3 * r6;
let radial_prime = self.k1 + 2.0 * self.k2 * r2 + 3.0 * self.k3 * r4;
let dx_t = 2.0 * self.p1 * x * y + self.p2 * (r2 + 2.0 * x * x);
let dy_t = self.p1 * (r2 + 2.0 * y * y) + 2.0 * self.p2 * x * y;
let fx = x * radial + dx_t;
let fy = y * radial + dy_t;
let rx = fx - x_d;
let ry = fy - y_d;
if rx * rx + ry * ry < 1e-20 {
break;
}
let j11 = radial + 2.0 * x * x * radial_prime + 2.0 * self.p1 * y + 6.0 * self.p2 * x;
let j12 = 2.0 * x * y * radial_prime + 2.0 * self.p1 * x + 2.0 * self.p2 * y;
let j21 = 2.0 * x * y * radial_prime + 2.0 * self.p1 * x + 2.0 * self.p2 * y;
let j22 = radial + 2.0 * y * y * radial_prime + 6.0 * self.p1 * y + 2.0 * self.p2 * x;
let det = j11 * j22 - j12 * j21;
if det.abs() < 1e-15 {
break;
}
let inv_det = 1.0 / det;
let dx_step = inv_det * (j22 * rx - j12 * ry);
let dy_step = inv_det * (-j21 * rx + j11 * ry);
x -= dx_step;
y -= dy_step;
if dx_step.abs() + dy_step.abs() < 1e-12 {
break;
}
}
(x, y)
}
pub fn is_zero(&self) -> bool {
self.k1 == 0.0
&& self.k2 == 0.0
&& self.k3 == 0.0
&& self.p1 == 0.0
&& self.p2 == 0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_roundtrip_radial_only() {
let d = RadialDistortion::new(-7e-9, 2e-15, 0.0);
for &(x, y) in &[
(100.0, 200.0),
(500.0, 300.0),
(0.0, 1000.0),
(1024.0, 512.0),
] {
let (xd, yd) = d.distort(x, y);
let (xu, yu) = d.undistort(xd, yd);
assert!(
(xu - x).abs() < 1e-6 && (yu - y).abs() < 1e-6,
"Roundtrip failed for ({}, {}): got ({}, {})",
x,
y,
xu,
yu
);
}
}
#[test]
fn test_roundtrip_full_brown_conrady() {
let d = RadialDistortion::with_tangential(-7e-9, 2e-15, 0.0, 5e-7, -3e-7);
for &(x, y) in &[
(100.0, 200.0),
(500.0, 300.0),
(0.0, 1000.0),
(1024.0, 512.0),
(-800.0, -700.0),
] {
let (xd, yd) = d.distort(x, y);
let (xu, yu) = d.undistort(xd, yd);
assert!(
(xu - x).abs() < 1e-6 && (yu - y).abs() < 1e-6,
"Roundtrip failed for ({}, {}): got ({}, {})",
x,
y,
xu,
yu
);
}
}
#[test]
fn test_zero_distortion() {
let d = RadialDistortion::new(0.0, 0.0, 0.0);
let (xu, yu) = d.undistort(100.0, 200.0);
assert!((xu - 100.0).abs() < 1e-12);
assert!((yu - 200.0).abs() < 1e-12);
}
#[test]
fn test_origin() {
let d = RadialDistortion::new(-1e-6, 1e-12, 0.0);
let (xu, yu) = d.undistort(0.0, 0.0);
assert!(xu.abs() < 1e-12);
assert!(yu.abs() < 1e-12);
}
}