use super::generic_model::*;
use image::{DynamicImage, ImageBuffer};
use nalgebra as na;
use rayon::prelude::*;
pub fn init_undistort_map(
camera_model: &dyn CameraModel<f64>,
projection_mat: &na::Matrix3<f64>,
new_w_h: (u32, u32),
rotation: Option<na::Rotation3<f64>>,
) -> (na::DMatrix<f32>, na::DMatrix<f32>) {
if projection_mat.shape() != (3, 3) {
panic!("projection matrix has the wrong shape");
}
let fx = projection_mat[(0, 0)];
let fy = projection_mat[(1, 1)];
let cx = projection_mat[(0, 2)];
let cy = projection_mat[(1, 2)];
let rmat_inv = rotation
.unwrap_or(na::Rotation3::identity())
.inverse()
.matrix()
.to_owned();
let p3ds: Vec<na::Vector3<f64>> = (0..new_w_h.1)
.into_par_iter()
.flat_map(|y| {
(0..new_w_h.0)
.map(|x| {
rmat_inv * na::Vector3::new((x as f64 - cx) / fx, (y as f64 - cy) / fy, 1.0)
})
.collect::<Vec<na::Vector3<f64>>>()
})
.collect();
let p2ds = camera_model.project(&p3ds);
let (xvec, yvec): (Vec<f32>, Vec<f32>) = p2ds
.iter()
.map(|xy| {
if let Some(xy) = xy {
(xy[0] as f32, xy[1] as f32)
} else {
(f32::NAN, f32::NAN)
}
})
.unzip();
let xmap = na::DMatrix::from_vec(new_w_h.1 as usize, new_w_h.0 as usize, xvec);
let ymap = na::DMatrix::from_vec(new_w_h.1 as usize, new_w_h.0 as usize, yvec);
(xmap, ymap)
}
#[inline]
fn interpolate_bilinear_weight(x: f32, y: f32) -> (u16, u16) {
if !(0.0..=65535.0).contains(&x) {
panic!("x not in [0-65535]");
}
if !(0.0..=65535.0).contains(&y) {
panic!("y not in [0-65535]");
}
const UPPER: f32 = 256.0;
let x_weight = (UPPER * (x.ceil() - x)) as u16;
let y_weight = (UPPER * (y.ceil() - y)) as u16;
(x_weight, y_weight)
}
pub fn compute_for_fast_remap(
xmap: &na::DMatrix<f32>,
ymap: &na::DMatrix<f32>,
src_width: usize,
) -> Vec<(usize, u16, u16)> {
let xy_pos_weight_vec: Vec<_> = xmap
.iter()
.zip(ymap.iter())
.map(|(&x, &y)| {
let (xw, yw) = interpolate_bilinear_weight(x, y);
let x0 = x.floor() as usize;
let y0 = y.floor() as usize;
let offset = y0 * src_width + x0;
(offset, xw, yw)
})
.collect();
xy_pos_weight_vec
}
fn reinterpret_vec(input: Vec<[u8; 3]>) -> Vec<u8> {
let len = input.len() * 3;
let capacity = input.capacity() * 3;
let ptr = input.as_ptr() as *mut u8;
std::mem::forget(input); unsafe { Vec::from_raw_parts(ptr, len, capacity) }
}
pub fn fast_remap(
img: &DynamicImage,
new_w_h: (u32, u32),
xy_pos_weight_vec: &[(usize, u16, u16)],
) -> DynamicImage {
match img {
DynamicImage::ImageLuma8(image_buffer) => {
DynamicImage::ImageLuma8(fast_remap_gray_u8(image_buffer, new_w_h, xy_pos_weight_vec))
}
DynamicImage::ImageRgb8(image_buffer) => {
let src = image_buffer.as_raw();
let src_stride = image_buffer.width() as usize * 3;
let val: Vec<[u8; 3]> = xy_pos_weight_vec
.par_iter()
.map(|&(offset, xw0, yw0)| {
let off3 = offset * 3;
let xw1 = 256 - xw0 as u32;
let yw1 = 256 - yw0 as u32;
let xw0 = xw0 as u32;
let yw0 = yw0 as u32;
let w00 = xw0 * yw0;
let w10 = xw1 * yw0;
let w01 = xw0 * yw1;
let w11 = xw1 * yw1;
let mut v = [0, 0, 0];
unsafe {
let p00 = *src.get_unchecked(off3) as u32;
let p10 = *src.get_unchecked(off3 + 3) as u32;
let p01 = *src.get_unchecked(off3 + src_stride) as u32;
let p11 = *src.get_unchecked(off3 + src_stride + 3) as u32;
v[0] = ((p00 * w00 + p10 * w10 + p01 * w01 + p11 * w11) >> 16) as u8;
let p00 = *src.get_unchecked(off3 + 1) as u32;
let p10 = *src.get_unchecked(off3 + 4) as u32;
let p01 = *src.get_unchecked(off3 + src_stride + 1) as u32;
let p11 = *src.get_unchecked(off3 + src_stride + 4) as u32;
v[1] = ((p00 * w00 + p10 * w10 + p01 * w01 + p11 * w11) >> 16) as u8;
let p00 = *src.get_unchecked(off3 + 2) as u32;
let p10 = *src.get_unchecked(off3 + 5) as u32;
let p01 = *src.get_unchecked(off3 + src_stride + 2) as u32;
let p11 = *src.get_unchecked(off3 + src_stride + 5) as u32;
v[2] = ((p00 * w00 + p10 * w10 + p01 * w01 + p11 * w11) >> 16) as u8;
}
v
})
.collect();
let img = ImageBuffer::from_vec(new_w_h.0, new_w_h.1, reinterpret_vec(val)).unwrap();
DynamicImage::ImageRgb8(img)
}
DynamicImage::ImageLuma16(image_buffer) => DynamicImage::ImageLuma16(fast_remap_gray_u16(
image_buffer,
new_w_h,
xy_pos_weight_vec,
)),
_ => panic!("Only mono8, mono16, and rgb8 support fast remap."),
}
}
macro_rules! fast_remap_gray {
($name:ident, $ty:ty) => {
pub fn $name(
image_buffer: &ImageBuffer<image::Luma<$ty>, Vec<$ty>>,
new_w_h: (u32, u32),
xy_pos_weight_vec: &[(usize, u16, u16)],
) -> ImageBuffer<image::Luma<$ty>, Vec<$ty>> {
let src = image_buffer.as_raw();
let src_stride = image_buffer.width() as usize;
let val: Vec<$ty> = xy_pos_weight_vec
.par_iter()
.map(|&(offset, xw0, yw0)| unsafe {
let p00 = *src.get_unchecked(offset) as u32;
let p10 = *src.get_unchecked(offset + 1) as u32;
let p01 = *src.get_unchecked(offset + src_stride) as u32;
let p11 = *src.get_unchecked(offset + src_stride + 1) as u32;
let xw0 = xw0 as u32;
let yw0 = yw0 as u32;
let xw1 = 256 - xw0;
let yw1 = 256 - yw0;
((p00 * xw0 * yw0 + p10 * xw1 * yw0 + p01 * xw0 * yw1 + p11 * xw1 * yw1) >> 16)
as $ty
})
.collect();
ImageBuffer::from_vec(new_w_h.0, new_w_h.1, val).unwrap()
}
};
}
fast_remap_gray!(fast_remap_gray_u8, u8);
fast_remap_gray!(fast_remap_gray_u16, u16);
pub fn estimate_new_camera_matrix_for_undistort(
camera_model: &dyn CameraModel<f64>,
balance: f64,
new_image_w_h: Option<(u32, u32)>,
) -> na::Matrix3<f64> {
if !(0.0..=1.0).contains(&balance) {
panic!("balance should be [0.0-1.0], got {}", balance);
}
let params = camera_model.params();
let cx = params[2];
let cy = params[3];
let w = camera_model.width();
let h = camera_model.height();
let p2ds = vec![
na::Vector2::new(cx, 0.0),
na::Vector2::new(w - 1.0, cy),
na::Vector2::new(cx, h - 1.0),
na::Vector2::new(0.0, cy),
];
let undist_pts = camera_model.unproject(&p2ds);
let undist_pts = undist_pts.iter().map(|p| {
let p = p.unwrap();
na::Vector2::new(p.x / p.z, p.y / p.z)
});
let mut min_x = f64::MAX;
let mut min_y = f64::MAX;
let mut max_x = f64::MIN;
let mut max_y = f64::MIN;
for p in undist_pts {
min_x = min_x.min(p.x);
min_y = min_y.min(p.y);
max_x = max_x.max(p.x);
max_y = max_y.max(p.y);
}
min_x = min_x.abs();
min_y = min_y.abs();
let (new_w, new_h) = if let Some((new_w, new_h)) = new_image_w_h {
(new_w, new_h)
} else {
(camera_model.width() as u32, camera_model.height() as u32)
};
let new_cx = new_w as f64 * min_x / (min_x + max_x);
let new_cy = new_h as f64 * min_y / (min_y + max_y);
let fx = new_w as f64 / (min_x + max_x);
let fy = new_h as f64 / (min_y + max_y);
let fmin = fx.min(fy);
let fmax = fx.max(fy);
let new_f = balance * fmin + (1.0 - balance) * fmax;
let mut out = na::Matrix3::identity();
out[(0, 0)] = new_f;
out[(1, 1)] = new_f;
out[(0, 2)] = new_cx;
out[(1, 2)] = new_cy;
out
}
pub fn stereo_rectify(
camera_model0: &GenericModel<f64>,
camera_model1: &GenericModel<f64>,
rvec: &na::Vector3<f64>,
tvec: &na::Vector3<f64>,
new_image_w_h: Option<(u32, u32)>,
) -> (na::Rotation3<f64>, na::Rotation3<f64>, na::Matrix3<f64>) {
let r_half_inv = -rvec / 2.0;
let rotated_t = na::Rotation3::from_scaled_axis(r_half_inv) * tvec;
let idx = if rotated_t.x.abs() > rotated_t.y.abs() {
0
} else {
1
};
let axis_to_rectify = rotated_t[idx];
let translation_norm = rotated_t.norm();
let mut unit_vector = na::Vector3::zeros();
if axis_to_rectify > 0.0 {
unit_vector[idx] = 1.0;
} else {
unit_vector[idx] = -1.0;
}
let mut axis_for_rotation = rotated_t.cross(&unit_vector);
let axis_norm = axis_for_rotation.norm();
let mut rotation_for_compensating_translation = na::Rotation3::identity();
if axis_norm > 0.0 {
axis_for_rotation /= axis_norm;
axis_for_rotation *= (axis_to_rectify.abs() / translation_norm).acos();
rotation_for_compensating_translation = na::Rotation3::from_scaled_axis(axis_for_rotation);
}
let rotation_for_cam0 =
rotation_for_compensating_translation * na::Rotation3::from_scaled_axis(-r_half_inv);
let rotation_for_cam1 =
rotation_for_compensating_translation * na::Rotation3::from_scaled_axis(r_half_inv);
let new_cam_mat0 = camera_model0.estimate_new_camera_matrix_for_undistort(0.0, new_image_w_h);
let new_cam_mat1 = camera_model1.estimate_new_camera_matrix_for_undistort(0.0, new_image_w_h);
let mut avg_new_mat = na::Matrix3::identity();
let avg_f = (new_cam_mat0[(0, 0)] + new_cam_mat1[(0, 0)]) / 2.0;
avg_new_mat[(0, 0)] = avg_f;
avg_new_mat[(1, 1)] = avg_f;
let avg_cx = (new_cam_mat0[(0, 2)] + new_cam_mat1[(0, 2)]) / 2.0;
avg_new_mat[(0, 2)] = avg_cx;
let avg_cy = (new_cam_mat0[(1, 2)] + new_cam_mat1[(1, 2)]) / 2.0;
avg_new_mat[(1, 2)] = avg_cy;
(rotation_for_cam0, rotation_for_cam1, avg_new_mat)
}