use crate::interpolation::{interpolate_pixel, InterpolationMode};
use crate::{
image::{Image, ImageSize},
interpolation::meshgrid,
};
use anyhow::Result;
use ndarray::stack;
pub type PerspectiveMatrix = [f32; 9];
#[rustfmt::skip]
fn determinant3x3(m: &PerspectiveMatrix) -> f32 {
m[0] * (m[4] * m[8] - m[5] * m[7]) -
m[1] * (m[3] * m[8] - m[5] * m[6]) +
m[2] * (m[3] * m[7] - m[4] * m[6])
}
#[rustfmt::skip]
fn adjugate3x3(m: &PerspectiveMatrix) -> PerspectiveMatrix {
[
m[4] * m[8] - m[5] * m[7], m[2] * m[7] - m[1] * m[8], m[1] * m[5] - m[2] * m[4], m[5] * m[6] - m[3] * m[8], m[0] * m[8] - m[2] * m[6], m[2] * m[3] - m[0] * m[5], m[3] * m[7] - m[4] * m[6], m[1] * m[6] - m[0] * m[7], m[0] * m[4] - m[1] * m[3], ]
}
fn inverse_perspective_matrix(m: PerspectiveMatrix) -> Result<PerspectiveMatrix> {
let det = determinant3x3(&m);
if det == 0.0 {
return Err(anyhow::anyhow!("Matrix is singular and cannot be inverted"));
}
let adj = adjugate3x3(&m);
let inv_det = 1.0 / det;
let mut inv_m = [0.0; 9];
for i in 0..9 {
inv_m[i] = adj[i] * inv_det;
}
Ok(inv_m)
}
fn transform_point(x: f32, y: f32, m: PerspectiveMatrix) -> (f32, f32) {
let w = m[6] * x + m[7] * y + m[8];
let x = (m[0] * x + m[1] * y + m[2]) / w;
let y = (m[3] * x + m[4] * y + m[5]) / w;
(x, y)
}
pub fn warp_perspective<const CHANNELS: usize>(
src: &Image<f32, CHANNELS>,
m: PerspectiveMatrix,
new_size: ImageSize,
interpolation: InterpolationMode,
) -> Result<Image<f32, CHANNELS>> {
let inv_m = inverse_perspective_matrix(m)?;
let mut dst = Image::from_size_val(new_size, 0.0)?;
let x = ndarray::Array::range(0.0, new_size.width as f32, 1.0).insert_axis(ndarray::Axis(0));
let y = ndarray::Array::range(0.0, new_size.height as f32, 1.0).insert_axis(ndarray::Axis(0));
let (xx, yy) = meshgrid(&x, &y);
let xy = stack![ndarray::Axis(2), xx, yy];
ndarray::Zip::from(xy.rows())
.and(dst.data.rows_mut())
.par_for_each(|uv, mut out| {
assert_eq!(uv.len(), 2);
let (u, v) = (uv[0], uv[1]);
let (u_src, v_src) = transform_point(u, v, inv_m);
let pixels = (0..src.num_channels())
.map(|c| interpolate_pixel(&src.data, u_src, v_src, c, interpolation));
for (c, pixel) in pixels.enumerate() {
out[c] = pixel;
}
});
Ok(dst)
}
#[cfg(test)]
mod tests {
use anyhow::Result;
#[test]
fn inverse_perspective_matrix() -> Result<()> {
let m = [1.0, 0.0, -1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0];
let expected = [1.0, 0.0, 1.0, 0.0, 1.0, -1.0, 0.0, 0.0, 1.0];
let inv_m = super::inverse_perspective_matrix(m)?;
assert_eq!(inv_m, expected);
Ok(())
}
#[test]
fn transform_point() {
let m = [1.0, 0.0, -1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0];
let (x, y) = super::transform_point(1.0, 1.0, m);
let (x_expected, y_expected) = (0.0, 2.0);
assert_eq!(x, x_expected);
assert_eq!(y, y_expected);
}
#[test]
fn warp_perspective_identity() -> Result<()> {
use crate::image::{Image, ImageSize};
let image: Image<f32, 3> = Image::from_size_val(
ImageSize {
width: 4,
height: 5,
},
0.0f32,
)?;
let m = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
let image_transformed = super::warp_perspective(
&image,
m,
ImageSize {
width: 2,
height: 3,
},
super::InterpolationMode::Bilinear,
)?;
assert_eq!(image_transformed.num_channels(), 3);
assert_eq!(image_transformed.size().width, 2);
assert_eq!(image_transformed.size().height, 3);
Ok(())
}
#[test]
fn warp_perspective_hflip() -> Result<()> {
use crate::image::{Image, ImageSize};
let image = Image::<_, 1>::new(
ImageSize {
width: 2,
height: 3,
},
vec![0.0f32, 1.0, 2.0, 3.0, 4.0, 5.0],
)?;
let image_expected = vec![1.0, 0.0, 3.0, 2.0, 5.0, 4.0];
let m = [-1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
let image_transformed = super::warp_perspective(
&image,
m,
ImageSize {
width: 2,
height: 3,
},
super::InterpolationMode::Bilinear,
)?;
assert_eq!(image_transformed.num_channels(), 1);
assert_eq!(image_transformed.size().width, 2);
assert_eq!(image_transformed.size().height, 3);
assert_eq!(image_transformed.data.as_slice().expect(""), image_expected);
Ok(())
}
#[test]
fn test_warp_perspective_resize() -> Result<()> {
use crate::image::{Image, ImageSize};
let image = Image::<_, 1>::new(
ImageSize {
width: 4,
height: 4,
},
vec![
0.0f32, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0,
15.0,
],
)?;
let m = [0.3333, 0.0, 0.0, 0.0, 0.3333, 0.0, 0.0, 0.0, 1.0];
let image_expected = vec![0.0, 3.0, 12.0, 15.0];
let image_transformed = super::warp_perspective(
&image,
m,
ImageSize {
width: 2,
height: 2,
},
super::InterpolationMode::Bilinear,
)?;
let image_resized = crate::resize::resize_native(
&image,
ImageSize {
width: 2,
height: 2,
},
super::InterpolationMode::Bilinear,
)?;
assert_eq!(image_transformed.num_channels(), 1);
assert_eq!(image_transformed.size().width, 2);
assert_eq!(image_transformed.size().height, 2);
assert_eq!(image_transformed.data.as_slice().expect(""), image_expected);
assert_eq!(
image_transformed.data.as_slice().expect(""),
image_resized.data.as_slice().expect("")
);
Ok(())
}
}