#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
pub struct RadialDistortion {
pub k1: f64,
pub k2: f64,
pub k3: f64,
}
impl RadialDistortion {
pub fn new(k1: f64, k2: f64, k3: f64) -> Self {
Self { k1, k2, k3 }
}
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 scale = 1.0 + self.k1 * r2 + self.k2 * r4 + self.k3 * r6;
(x * scale, y * scale)
}
pub fn undistort(&self, x_d: f64, y_d: f64) -> (f64, f64) {
let r_d = (x_d * x_d + y_d * y_d).sqrt();
if r_d < 1e-12 {
return (x_d, y_d);
}
let mut r = r_d; for _ in 0..20 {
let r2 = r * r;
let r4 = r2 * r2;
let r6 = r2 * r4;
let f = r * (1.0 + self.k1 * r2 + self.k2 * r4 + self.k3 * r6) - r_d;
let df = 1.0 + 3.0 * self.k1 * r2 + 5.0 * self.k2 * r4 + 7.0 * self.k3 * r6;
let delta = f / df;
r -= delta;
if delta.abs() < 1e-12 {
break;
}
}
let scale = r / r_d;
(x_d * scale, y_d * scale)
}
pub fn is_zero(&self) -> bool {
self.k1 == 0.0 && self.k2 == 0.0 && self.k3 == 0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_roundtrip_radial() {
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_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);
}
}