use crate::error::{Result, VisionError};
use image::Rgba;
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::simd_ops::SimdUnifiedOps;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BorderMode {
Constant(Rgba<u8>),
Reflect,
Replicate,
Wrap,
Transparent,
}
impl Default for BorderMode {
fn default() -> Self {
Self::Constant(Rgba([0, 0, 0, 255]))
}
}
#[derive(Debug, Clone)]
pub struct PerspectiveTransform {
pub matrix: Array2<f64>,
}
#[derive(Debug, Clone)]
pub struct RansacParams {
pub max_iterations: usize,
pub threshold: f64,
pub min_inliers: usize,
pub confidence: f64,
pub seed: Option<u64>,
}
impl Default for RansacParams {
fn default() -> Self {
Self {
max_iterations: 1000,
threshold: 2.0,
min_inliers: 10,
confidence: 0.99,
seed: None,
}
}
}
#[derive(Debug, Clone)]
pub struct RansacResult {
pub transform: PerspectiveTransform,
pub inliers: Vec<usize>,
pub iterations: usize,
pub inlier_ratio: f64,
}
impl PerspectiveTransform {
pub fn new(data: [f64; 9]) -> Self {
let matrix = Array2::from_shape_vec((3, 3), data.to_vec()).expect("Operation failed");
Self { matrix }
}
pub fn identity() -> Self {
Self::new([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0])
}
pub fn from_points(srcpoints: &[(f64, f64)], dst_points: &[(f64, f64)]) -> Result<Self> {
if srcpoints.len() != dst_points.len() {
return Err(VisionError::InvalidParameter(
"Source and destination point sets must have the same length".to_string(),
));
}
if srcpoints.len() < 4 {
return Err(VisionError::InvalidParameter(
"At least 4 point correspondences are required for perspective transformation"
.to_string(),
));
}
let n = srcpoints.len();
let mut a = Array2::<f64>::zeros((2 * n, 9));
for (i, (&(x, y), &(xp, yp))) in srcpoints.iter().zip(dst_points.iter()).enumerate() {
a[[2 * i, 0]] = -x;
a[[2 * i, 1]] = -y;
a[[2 * i, 2]] = -1.0;
a[[2 * i, 3]] = 0.0;
a[[2 * i, 4]] = 0.0;
a[[2 * i, 5]] = 0.0;
a[[2 * i, 6]] = x * xp;
a[[2 * i, 7]] = y * xp;
a[[2 * i, 8]] = xp;
a[[2 * i + 1, 0]] = 0.0;
a[[2 * i + 1, 1]] = 0.0;
a[[2 * i + 1, 2]] = 0.0;
a[[2 * i + 1, 3]] = -x;
a[[2 * i + 1, 4]] = -y;
a[[2 * i + 1, 5]] = -1.0;
a[[2 * i + 1, 6]] = x * yp;
a[[2 * i + 1, 7]] = y * yp;
a[[2 * i + 1, 8]] = yp;
}
let ata = a.t().dot(&a);
let h = Self::find_smallest_eigenvector(&ata)?;
let matrix = Array2::from_shape_vec((3, 3), h.to_vec()).expect("Operation failed");
Ok(Self { matrix })
}
fn find_smallest_eigenvector(matrix: &Array2<f64>) -> Result<Array1<f64>> {
let n = matrix.nrows();
let mut v = Array1::<f64>::ones(n);
let norm = v.iter().map(|x| x * x).sum::<f64>().sqrt();
v.mapv_inplace(|x| x / norm);
for _ in 0..100 {
let mut v_new = Array1::<f64>::zeros(n);
let shift = 1e-6;
for iter in 0..50 {
let mut converged = true;
for i in 0..n {
let mut sum = 0.0;
for j in 0..n {
if i != j {
sum += matrix[[i, j]] * v_new[j];
}
}
let diagonal = matrix[[i, i]] + shift;
let new_val = if diagonal.abs() > 1e-10 {
(v[i] - sum) / diagonal
} else {
v[i]
};
if (new_val - v_new[i]).abs() > 1e-8 {
converged = false;
}
v_new[i] = new_val;
}
if converged && iter > 5 {
break;
}
}
let norm = v_new.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm > 1e-10 {
v_new.mapv_inplace(|x| x / norm);
}
let diff: f64 = v.iter().zip(v_new.iter()).map(|(a, b)| (a - b).abs()).sum();
if diff < 1e-8 {
break;
}
v = v_new;
}
Ok(v)
}
pub fn from_quad(
x: f64,
y: f64,
width: f64,
height: f64,
dst_points: &[(f64, f64)],
) -> Result<Self> {
if dst_points.len() != 4 {
return Err(VisionError::InvalidParameter(
"Exactly 4 destination points are required for quadrilateral mapping".to_string(),
));
}
let srcquad = [
(x, y),
(x + width, y),
(x + width, y + height),
(x, y + height),
];
Self::from_points(&srcquad, dst_points)
}
pub fn inverse(&self) -> Result<Self> {
let det = self.compute_determinant();
if det.abs() < 1e-10 {
return Err(VisionError::OperationError(
"Matrix is singular, cannot compute inverse".to_string(),
));
}
let mut inv = Array2::zeros((3, 3));
inv[[0, 0]] =
self.matrix[[1, 1]] * self.matrix[[2, 2]] - self.matrix[[1, 2]] * self.matrix[[2, 1]];
inv[[0, 1]] =
self.matrix[[0, 2]] * self.matrix[[2, 1]] - self.matrix[[0, 1]] * self.matrix[[2, 2]];
inv[[0, 2]] =
self.matrix[[0, 1]] * self.matrix[[1, 2]] - self.matrix[[0, 2]] * self.matrix[[1, 1]];
inv[[1, 0]] =
self.matrix[[1, 2]] * self.matrix[[2, 0]] - self.matrix[[1, 0]] * self.matrix[[2, 2]];
inv[[1, 1]] =
self.matrix[[0, 0]] * self.matrix[[2, 2]] - self.matrix[[0, 2]] * self.matrix[[2, 0]];
inv[[1, 2]] =
self.matrix[[0, 2]] * self.matrix[[1, 0]] - self.matrix[[0, 0]] * self.matrix[[1, 2]];
inv[[2, 0]] =
self.matrix[[1, 0]] * self.matrix[[2, 1]] - self.matrix[[1, 1]] * self.matrix[[2, 0]];
inv[[2, 1]] =
self.matrix[[0, 1]] * self.matrix[[2, 0]] - self.matrix[[0, 0]] * self.matrix[[2, 1]];
inv[[2, 2]] =
self.matrix[[0, 0]] * self.matrix[[1, 1]] - self.matrix[[0, 1]] * self.matrix[[1, 0]];
inv.mapv_inplace(|v| v / det);
Ok(Self { matrix: inv })
}
pub fn transform_point(&self, point: (f64, f64)) -> (f64, f64) {
let (x, y) = point;
let h = &self.matrix;
let w = h[[2, 0]] * x + h[[2, 1]] * y + h[[2, 2]];
let w_inv = if w.abs() > 1e-10 { 1.0 / w } else { 1.0 };
let x_prime = (h[[0, 0]] * x + h[[0, 1]] * y + h[[0, 2]]) * w_inv;
let y_prime = (h[[1, 0]] * x + h[[1, 1]] * y + h[[1, 2]]) * w_inv;
(x_prime, y_prime)
}
pub fn transform_points_simd(&self, points: &[(f64, f64)]) -> Vec<(f64, f64)> {
if points.is_empty() {
return Vec::new();
}
let n = points.len();
let mut result = Vec::with_capacity(n);
let x_coords: Vec<f64> = points.iter().map(|p| p.0).collect();
let y_coords: Vec<f64> = points.iter().map(|p| p.1).collect();
let x_arr = Array1::from_vec(x_coords);
let y_arr = Array1::from_vec(y_coords);
let h = &self.matrix;
let h00 = h[[0, 0]];
let h01 = h[[0, 1]];
let h02 = h[[0, 2]];
let h10 = h[[1, 0]];
let h11 = h[[1, 1]];
let h12 = h[[1, 2]];
let h20 = h[[2, 0]];
let h21 = h[[2, 1]];
let h22 = h[[2, 2]];
let mut x_prime_h = Array1::zeros(n);
let mut y_prime_h = Array1::zeros(n);
let mut w_h = Array1::zeros(n);
for i in 0..n {
x_prime_h[i] = h00 * x_arr[i] + h01 * y_arr[i] + h02;
y_prime_h[i] = h10 * x_arr[i] + h11 * y_arr[i] + h12;
w_h[i] = h20 * x_arr[i] + h21 * y_arr[i] + h22;
}
for i in 0..n {
let w = w_h[i];
let w_inv = if w.abs() > 1e-10 { 1.0 / w } else { 1.0 };
let x_prime = x_prime_h[i] * w_inv;
let y_prime = y_prime_h[i] * w_inv;
result.push((x_prime, y_prime));
}
result
}
pub fn reprojection_errors(
&self,
srcpoints: &[(f64, f64)],
dst_points: &[(f64, f64)],
) -> Vec<f64> {
srcpoints
.iter()
.zip(dst_points.iter())
.map(|(&src, &dst)| {
let projected = self.transform_point(src);
(projected.0 - dst.0).powi(2) + (projected.1 - dst.1).powi(2)
})
.collect()
}
pub fn compute_determinant(&self) -> f64 {
let m = &self.matrix;
m[[0, 0]] * (m[[1, 1]] * m[[2, 2]] - m[[1, 2]] * m[[2, 1]])
- m[[0, 1]] * (m[[1, 0]] * m[[2, 2]] - m[[1, 2]] * m[[2, 0]])
+ m[[0, 2]] * (m[[1, 0]] * m[[2, 1]] - m[[1, 1]] * m[[2, 0]])
}
}