1use crate::error::{Result, VisionError};
8use image::Rgba;
9use scirs2_core::ndarray::{Array1, Array2};
10use scirs2_core::simd_ops::SimdUnifiedOps;
11
12#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum BorderMode {
15 Constant(Rgba<u8>),
17 Reflect,
19 Replicate,
21 Wrap,
23 Transparent,
25}
26
27impl Default for BorderMode {
28 fn default() -> Self {
29 Self::Constant(Rgba([0, 0, 0, 255]))
30 }
31}
32
33#[derive(Debug, Clone)]
35pub struct PerspectiveTransform {
36 pub matrix: Array2<f64>,
38}
39
40#[derive(Debug, Clone)]
42pub struct RansacParams {
43 pub max_iterations: usize,
45 pub threshold: f64,
47 pub min_inliers: usize,
49 pub confidence: f64,
51 pub seed: Option<u64>,
53}
54
55impl Default for RansacParams {
56 fn default() -> Self {
57 Self {
58 max_iterations: 1000,
59 threshold: 2.0,
60 min_inliers: 10,
61 confidence: 0.99,
62 seed: None,
63 }
64 }
65}
66
67#[derive(Debug, Clone)]
69pub struct RansacResult {
70 pub transform: PerspectiveTransform,
72 pub inliers: Vec<usize>,
74 pub iterations: usize,
76 pub inlier_ratio: f64,
78}
79
80impl PerspectiveTransform {
81 pub fn new(data: [f64; 9]) -> Self {
83 let matrix = Array2::from_shape_vec((3, 3), data.to_vec()).expect("Operation failed");
84 Self { matrix }
85 }
86
87 pub fn identity() -> Self {
89 Self::new([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0])
90 }
91
92 pub fn from_points(srcpoints: &[(f64, f64)], dst_points: &[(f64, f64)]) -> Result<Self> {
94 if srcpoints.len() != dst_points.len() {
95 return Err(VisionError::InvalidParameter(
96 "Source and destination point sets must have the same length".to_string(),
97 ));
98 }
99
100 if srcpoints.len() < 4 {
101 return Err(VisionError::InvalidParameter(
102 "At least 4 point correspondences are required for perspective transformation"
103 .to_string(),
104 ));
105 }
106
107 let n = srcpoints.len();
108
109 let mut a = Array2::<f64>::zeros((2 * n, 9));
112
113 for (i, (&(x, y), &(xp, yp))) in srcpoints.iter().zip(dst_points.iter()).enumerate() {
114 a[[2 * i, 0]] = -x;
117 a[[2 * i, 1]] = -y;
118 a[[2 * i, 2]] = -1.0;
119 a[[2 * i, 3]] = 0.0;
120 a[[2 * i, 4]] = 0.0;
121 a[[2 * i, 5]] = 0.0;
122 a[[2 * i, 6]] = x * xp;
123 a[[2 * i, 7]] = y * xp;
124 a[[2 * i, 8]] = xp;
125
126 a[[2 * i + 1, 0]] = 0.0;
129 a[[2 * i + 1, 1]] = 0.0;
130 a[[2 * i + 1, 2]] = 0.0;
131 a[[2 * i + 1, 3]] = -x;
132 a[[2 * i + 1, 4]] = -y;
133 a[[2 * i + 1, 5]] = -1.0;
134 a[[2 * i + 1, 6]] = x * yp;
135 a[[2 * i + 1, 7]] = y * yp;
136 a[[2 * i + 1, 8]] = yp;
137 }
138
139 let ata = a.t().dot(&a);
141
142 let h = Self::find_smallest_eigenvector(&ata)?;
145
146 let matrix = Array2::from_shape_vec((3, 3), h.to_vec()).expect("Operation failed");
147 Ok(Self { matrix })
148 }
149
150 fn find_smallest_eigenvector(matrix: &Array2<f64>) -> Result<Array1<f64>> {
152 let n = matrix.nrows();
153 let mut v = Array1::<f64>::ones(n);
154
155 let norm = v.iter().map(|x| x * x).sum::<f64>().sqrt();
157 v.mapv_inplace(|x| x / norm);
158
159 for _ in 0..100 {
161 let mut v_new = Array1::<f64>::zeros(n);
164 let shift = 1e-6;
165
166 for iter in 0..50 {
168 let mut converged = true;
169 for i in 0..n {
170 let mut sum = 0.0;
171 for j in 0..n {
172 if i != j {
173 sum += matrix[[i, j]] * v_new[j];
174 }
175 }
176 let diagonal = matrix[[i, i]] + shift;
177 let new_val = if diagonal.abs() > 1e-10 {
178 (v[i] - sum) / diagonal
179 } else {
180 v[i]
181 };
182
183 if (new_val - v_new[i]).abs() > 1e-8 {
184 converged = false;
185 }
186 v_new[i] = new_val;
187 }
188 if converged && iter > 5 {
189 break;
190 }
191 }
192
193 let norm = v_new.iter().map(|x| x * x).sum::<f64>().sqrt();
195 if norm > 1e-10 {
196 v_new.mapv_inplace(|x| x / norm);
197 }
198
199 let diff: f64 = v.iter().zip(v_new.iter()).map(|(a, b)| (a - b).abs()).sum();
201 if diff < 1e-8 {
202 break;
203 }
204
205 v = v_new;
206 }
207
208 Ok(v)
209 }
210
211 pub fn from_quad(
213 x: f64,
214 y: f64,
215 width: f64,
216 height: f64,
217 dst_points: &[(f64, f64)],
218 ) -> Result<Self> {
219 if dst_points.len() != 4 {
220 return Err(VisionError::InvalidParameter(
221 "Exactly 4 destination points are required for quadrilateral mapping".to_string(),
222 ));
223 }
224
225 let srcquad = [
227 (x, y),
228 (x + width, y),
229 (x + width, y + height),
230 (x, y + height),
231 ];
232
233 Self::from_points(&srcquad, dst_points)
234 }
235
236 pub fn inverse(&self) -> Result<Self> {
242 let det = self.compute_determinant();
244 if det.abs() < 1e-10 {
245 return Err(VisionError::OperationError(
246 "Matrix is singular, cannot compute inverse".to_string(),
247 ));
248 }
249
250 let mut inv = Array2::zeros((3, 3));
252
253 inv[[0, 0]] =
255 self.matrix[[1, 1]] * self.matrix[[2, 2]] - self.matrix[[1, 2]] * self.matrix[[2, 1]];
256 inv[[0, 1]] =
257 self.matrix[[0, 2]] * self.matrix[[2, 1]] - self.matrix[[0, 1]] * self.matrix[[2, 2]];
258 inv[[0, 2]] =
259 self.matrix[[0, 1]] * self.matrix[[1, 2]] - self.matrix[[0, 2]] * self.matrix[[1, 1]];
260 inv[[1, 0]] =
261 self.matrix[[1, 2]] * self.matrix[[2, 0]] - self.matrix[[1, 0]] * self.matrix[[2, 2]];
262 inv[[1, 1]] =
263 self.matrix[[0, 0]] * self.matrix[[2, 2]] - self.matrix[[0, 2]] * self.matrix[[2, 0]];
264 inv[[1, 2]] =
265 self.matrix[[0, 2]] * self.matrix[[1, 0]] - self.matrix[[0, 0]] * self.matrix[[1, 2]];
266 inv[[2, 0]] =
267 self.matrix[[1, 0]] * self.matrix[[2, 1]] - self.matrix[[1, 1]] * self.matrix[[2, 0]];
268 inv[[2, 1]] =
269 self.matrix[[0, 1]] * self.matrix[[2, 0]] - self.matrix[[0, 0]] * self.matrix[[2, 1]];
270 inv[[2, 2]] =
271 self.matrix[[0, 0]] * self.matrix[[1, 1]] - self.matrix[[0, 1]] * self.matrix[[1, 0]];
272
273 inv.mapv_inplace(|v| v / det);
275
276 Ok(Self { matrix: inv })
277 }
278
279 pub fn transform_point(&self, point: (f64, f64)) -> (f64, f64) {
289 let (x, y) = point;
290 let h = &self.matrix;
291
292 let w = h[[2, 0]] * x + h[[2, 1]] * y + h[[2, 2]];
293 let w_inv = if w.abs() > 1e-10 { 1.0 / w } else { 1.0 };
294
295 let x_prime = (h[[0, 0]] * x + h[[0, 1]] * y + h[[0, 2]]) * w_inv;
296 let y_prime = (h[[1, 0]] * x + h[[1, 1]] * y + h[[1, 2]]) * w_inv;
297
298 (x_prime, y_prime)
299 }
300
301 pub fn transform_points_simd(&self, points: &[(f64, f64)]) -> Vec<(f64, f64)> {
316 if points.is_empty() {
317 return Vec::new();
318 }
319
320 let n = points.len();
321 let mut result = Vec::with_capacity(n);
322
323 let x_coords: Vec<f64> = points.iter().map(|p| p.0).collect();
325 let y_coords: Vec<f64> = points.iter().map(|p| p.1).collect();
326
327 let x_arr = Array1::from_vec(x_coords);
328 let y_arr = Array1::from_vec(y_coords);
329
330 let h = &self.matrix;
332 let h00 = h[[0, 0]];
333 let h01 = h[[0, 1]];
334 let h02 = h[[0, 2]];
335 let h10 = h[[1, 0]];
336 let h11 = h[[1, 1]];
337 let h12 = h[[1, 2]];
338 let h20 = h[[2, 0]];
339 let h21 = h[[2, 1]];
340 let h22 = h[[2, 2]];
341
342 let mut x_prime_h = Array1::zeros(n);
345 let mut y_prime_h = Array1::zeros(n);
346 let mut w_h = Array1::zeros(n);
347
348 for i in 0..n {
349 x_prime_h[i] = h00 * x_arr[i] + h01 * y_arr[i] + h02;
350 y_prime_h[i] = h10 * x_arr[i] + h11 * y_arr[i] + h12;
351 w_h[i] = h20 * x_arr[i] + h21 * y_arr[i] + h22;
352 }
353
354 for i in 0..n {
356 let w = w_h[i];
357 let w_inv = if w.abs() > 1e-10 { 1.0 / w } else { 1.0 };
358
359 let x_prime = x_prime_h[i] * w_inv;
360 let y_prime = y_prime_h[i] * w_inv;
361
362 result.push((x_prime, y_prime));
363 }
364
365 result
366 }
367
368 pub fn reprojection_errors(
379 &self,
380 srcpoints: &[(f64, f64)],
381 dst_points: &[(f64, f64)],
382 ) -> Vec<f64> {
383 srcpoints
384 .iter()
385 .zip(dst_points.iter())
386 .map(|(&src, &dst)| {
387 let projected = self.transform_point(src);
388 (projected.0 - dst.0).powi(2) + (projected.1 - dst.1).powi(2)
389 })
390 .collect()
391 }
392
393 pub fn compute_determinant(&self) -> f64 {
395 let m = &self.matrix;
396 m[[0, 0]] * (m[[1, 1]] * m[[2, 2]] - m[[1, 2]] * m[[2, 1]])
397 - m[[0, 1]] * (m[[1, 0]] * m[[2, 2]] - m[[1, 2]] * m[[2, 0]])
398 + m[[0, 2]] * (m[[1, 0]] * m[[2, 1]] - m[[1, 1]] * m[[2, 0]])
399 }
400}