use crate::pose::Pose3d;
use kornia_algebra::{Mat3F64, Vec2F64, Vec3F64};
use kornia_image::ImageSize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProjectionReject {
BelowMinDepth,
InvalidProjection,
OutOfImage,
}
#[derive(Debug, Clone)]
pub struct PinholeCamera {
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 fn project_point(
camera: &PinholeCamera,
pose: &Pose3d,
point_world: &Vec3F64,
) -> Option<(f64, f64, f64)> {
let p_cam = pose.transform_point(point_world);
if p_cam.z <= 1e-8 {
return None;
}
let u = camera.fx * p_cam.x / p_cam.z + camera.cx;
let v = camera.fy * p_cam.y / p_cam.z + camera.cy;
Some((u, v, p_cam.z))
}
impl PinholeCamera {
pub fn undistort(&self, col: f64, row: f64) -> Vec2F64 {
let xd = (col - self.cx) / self.fx;
let yd = (row - self.cy) / self.fy;
let mut x = xd;
let mut y = yd;
for _ in 0..5 {
let r2 = x * x + y * y;
let rad = 1.0 + self.k1 * r2 + self.k2 * r2 * 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 = (xd - dx) / rad;
y = (yd - dy) / rad;
}
Vec2F64::new(self.fx * x + self.cx, self.fy * y + self.cy)
}
pub fn project_to_pixel(&self, p_cam: &Vec3F64, min_depth: f64) -> Option<Vec2F64> {
if p_cam.z <= min_depth {
return None;
}
let u = self.fx * p_cam.x / p_cam.z + self.cx;
let v = self.fy * p_cam.y / p_cam.z + self.cy;
Some(Vec2F64::new(u, v))
}
pub fn project_to_image(
&self,
p_cam: &Vec3F64,
min_depth: f64,
image_size: ImageSize,
) -> Result<Vec2F64, ProjectionReject> {
let pixel = self
.project_to_pixel(p_cam, min_depth)
.ok_or(ProjectionReject::BelowMinDepth)?;
if pixel.x < 0.0
|| pixel.y < 0.0
|| pixel.x >= image_size.width as f64
|| pixel.y >= image_size.height as f64
{
return Err(ProjectionReject::OutOfImage);
}
Ok(pixel)
}
pub fn reprojection_error_sq_cam(
&self,
p_cam: &Vec3F64,
u_meas: f64,
v_meas: f64,
) -> Option<f64> {
let projected = self.project_to_pixel(p_cam, 1e-8)?;
let du = projected.x - u_meas;
let dv = projected.y - v_meas;
Some(du * du + dv * dv)
}
pub fn reprojection_error_sq_world(
&self,
pose_world_to_cam: &Pose3d,
p_world: &Vec3F64,
u_meas: f64,
v_meas: f64,
) -> Option<f64> {
let p_cam = pose_world_to_cam.transform_point(p_world);
self.reprojection_error_sq_cam(&p_cam, u_meas, v_meas)
}
pub fn intrinsic_matrix(&self) -> Mat3F64 {
Mat3F64::from_cols(
Vec3F64::new(self.fx, 0.0, 0.0),
Vec3F64::new(0.0, self.fy, 0.0),
Vec3F64::new(self.cx, self.cy, 1.0),
)
}
pub fn intrinsics(&self) -> (f64, f64, f64, f64) {
(self.fx, self.fy, self.cx, self.cy)
}
pub fn undistort_matched_pairs(
&self,
kps1: &[[f32; 2]],
kps2: &[[f32; 2]],
matches: &[(usize, usize)],
) -> (Vec<Vec2F64>, Vec<Vec2F64>) {
let mut pts1 = Vec::with_capacity(matches.len());
let mut pts2 = Vec::with_capacity(matches.len());
for &(i1, i2) in matches {
if i1 >= kps1.len() || i2 >= kps2.len() {
continue;
}
let p1 = kps1[i1];
let p2 = kps2[i2];
pts1.push(self.undistort(p1[0] as f64, p1[1] as f64));
pts2.push(self.undistort(p2[0] as f64, p2[1] as f64));
}
(pts1, pts2)
}
}
#[cfg(test)]
mod tests {
use super::*;
use kornia_algebra::Mat3F64;
fn camera() -> PinholeCamera {
PinholeCamera {
fx: 200.0,
fy: 200.0,
cx: 320.0,
cy: 240.0,
k1: 0.0,
k2: 0.0,
p1: 0.0,
p2: 0.0,
}
}
#[test]
fn test_project_to_pixel() {
let cam = camera();
let p = Vec3F64::new(0.0, 0.0, 5.0);
let uv = cam.project_to_pixel(&p, 1e-8).unwrap();
assert!((uv.x - 320.0).abs() < 1e-12);
assert!((uv.y - 240.0).abs() < 1e-12);
}
#[test]
fn test_project_to_image() {
let cam = camera();
let p = Vec3F64::new(0.0, 0.0, 5.0);
let uv = cam
.project_to_image(
&p,
1e-8,
ImageSize {
width: 640,
height: 480,
},
)
.unwrap();
assert!((uv.x - 320.0).abs() < 1e-12);
assert!((uv.y - 240.0).abs() < 1e-12);
}
#[test]
fn test_project_to_image_accepts_kornia_image_size() {
let cam = camera();
let p = Vec3F64::new(0.0, 0.0, 5.0);
let uv = cam
.project_to_image(
&p,
1e-8,
ImageSize {
width: 640,
height: 480,
},
)
.unwrap();
assert!((uv.x - 320.0).abs() < 1e-12);
assert!((uv.y - 240.0).abs() < 1e-12);
}
#[test]
fn test_project_to_image_out_of_image() {
let cam = camera();
let p = Vec3F64::new(10.0, 0.0, 1.0);
let err = cam
.project_to_image(
&p,
1e-8,
ImageSize {
width: 640,
height: 480,
},
)
.unwrap_err();
assert_eq!(err, ProjectionReject::OutOfImage);
}
#[test]
fn test_project_to_image_below_min_depth() {
let cam = camera();
let p = Vec3F64::new(0.0, 0.0, 0.5);
let err = cam
.project_to_image(
&p,
1.0,
ImageSize {
width: 640,
height: 480,
},
)
.unwrap_err();
assert_eq!(err, ProjectionReject::BelowMinDepth);
}
#[test]
fn test_reprojection_error_sq_cam_zero() {
let cam = camera();
let p = Vec3F64::new(0.0, 0.0, 5.0);
let err = cam.reprojection_error_sq_cam(&p, 320.0, 240.0).unwrap();
assert!(err < 1e-12);
}
#[test]
fn test_reprojection_error_sq_world_zero_identity() {
let cam = camera();
let pose = Pose3d::new(Mat3F64::IDENTITY, Vec3F64::ZERO);
let p_world = Vec3F64::new(0.0, 0.0, 5.0);
let err = cam
.reprojection_error_sq_world(&pose, &p_world, 320.0, 240.0)
.unwrap();
assert!(err < 1e-12);
}
}