1use crate::core::types::{Point, Scalar};
2use crate::error::{IrisError, Result};
3use crate::features::KeyPoint;
4use crate::image::Image;
5use burn::tensor::{Tensor, TensorData, backend::Backend};
6
7#[derive(Clone, Copy, Debug, PartialEq)]
9pub struct DMatch {
10 pub query_idx: usize,
11 pub train_idx: usize,
12 pub img_idx: usize,
13 pub distance: f32,
14}
15
16pub struct BFMatcher;
17
18impl BFMatcher {
19 pub fn match_descriptors<B: Backend>(
21 &self,
22 query: &Tensor<B, 2>,
23 train: &Tensor<B, 2>,
24 ) -> Result<Vec<DMatch>> {
25 let q_dims = query.dims();
26 let t_dims = train.dims();
27 let n1 = q_dims[0];
28 let _n2 = t_dims[0];
29
30 let q_unsqueezed = query.clone().unsqueeze_dim::<3>(1);
32 let t_unsqueezed = train.clone().unsqueeze_dim::<3>(0);
33
34 let diff = q_unsqueezed.sub(t_unsqueezed);
35 let squared_diff = diff.powf_scalar(2.0);
36 let dists = squared_diff.sum_dim(2).squeeze::<2>().sqrt(); let min_dists = dists.clone().min_dim(1).squeeze::<1>(); let argmins = dists.argmin(1).squeeze::<1>(); let min_dists_data = min_dists.into_data();
42 let argmins_data = argmins.into_data();
43
44 let min_dists_vec: Vec<f32> = min_dists_data.iter::<f32>().collect();
45 let argmins_vec: Vec<i32> = argmins_data.iter::<i32>().collect();
46
47 let mut matches = Vec::new();
48 for i in 0..n1 {
49 matches.push(DMatch {
50 query_idx: i,
51 train_idx: argmins_vec[i] as usize,
52 img_idx: 0,
53 distance: min_dists_vec[i],
54 });
55 }
56 Ok(matches)
57 }
58}
59
60struct KdNode {
66 idx: usize,
68 axis: usize,
70 threshold: f32,
72 left: Option<usize>,
74 right: Option<usize>,
76}
77
78pub struct FlannMatcher {
85 k: usize,
87 trees: usize,
89 checks: usize,
92}
93
94impl Default for FlannMatcher {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100impl FlannMatcher {
101 #[must_use]
104 pub fn new() -> Self {
105 Self {
106 k: 2,
107 trees: 5,
108 checks: 32,
109 }
110 }
111
112 #[must_use]
114 pub fn with_k(mut self, k: usize) -> Self {
115 self.k = k;
116 self
117 }
118
119 #[must_use]
121 pub fn with_trees(mut self, trees: usize) -> Self {
122 self.trees = trees;
123 self
124 }
125
126 #[must_use]
128 pub fn with_checks(mut self, checks: usize) -> Self {
129 self.checks = checks;
130 self
131 }
132
133 fn build_kd_tree(
137 items: &mut [(usize, Vec<f32>)],
138 depth: usize,
139 nodes: &mut Vec<KdNode>,
140 ) -> Option<usize> {
141 if items.is_empty() {
142 return None;
143 }
144 if items.len() == 1 {
145 let idx = items[0].0;
146 let axis = depth % items[0].1.len();
147 let threshold = items[0].1[axis];
148 let node_idx = nodes.len();
149 nodes.push(KdNode {
150 idx,
151 axis,
152 threshold,
153 left: None,
154 right: None,
155 });
156 return Some(node_idx);
157 }
158
159 let dim = items[0].1.len();
160 let axis = depth % dim;
161
162 let mid = items.len() / 2;
164 items.select_nth_unstable_by(mid, |a, b| {
166 a.1[axis]
167 .partial_cmp(&b.1[axis])
168 .unwrap_or(std::cmp::Ordering::Equal)
169 });
170
171 let threshold = items[mid].1[axis];
172 let split_idx = mid;
173
174 let node = KdNode {
175 idx: items[split_idx].0,
176 axis,
177 threshold,
178 left: None,
179 right: None,
180 };
181 let node_pos = nodes.len();
182 nodes.push(node);
183
184 let (left_items, right_items) = items.split_at_mut(split_idx);
185 let (right_items, _) = right_items.split_at_mut(1); let left_child = Self::build_kd_tree(left_items, depth + 1, nodes);
188 let right_child = Self::build_kd_tree(right_items, depth + 1, nodes);
189
190 nodes[node_pos].left = left_child;
191 nodes[node_pos].right = right_child;
192
193 Some(node_pos)
194 }
195
196 fn search_nn(
199 nodes: &[KdNode],
200 train: &[Vec<f32>],
201 query: &[f32],
202 root: Option<usize>,
203 checks_remaining: &mut usize,
204 ) -> (usize, f32) {
205 let mut best_idx = 0usize;
206 let mut best_dist = f32::MAX;
207 Self::search_nn_recursive(
208 nodes,
209 train,
210 query,
211 root,
212 checks_remaining,
213 &mut best_idx,
214 &mut best_dist,
215 );
216 (best_idx, best_dist)
217 }
218
219 fn search_nn_recursive(
220 nodes: &[KdNode],
221 train: &[Vec<f32>],
222 query: &[f32],
223 node_idx: Option<usize>,
224 checks: &mut usize,
225 best_idx: &mut usize,
226 best_dist: &mut f32,
227 ) {
228 let idx = match node_idx {
229 Some(i) => i,
230 None => return,
231 };
232 if *checks == 0 {
233 return;
234 }
235
236 let node = &nodes[idx];
237
238 let dist = euclidean_dist_sq(query, &train[node.idx]);
240 if dist < *best_dist {
241 *best_dist = dist;
242 *best_idx = node.idx;
243 }
244 *checks = checks.saturating_sub(1);
245
246 let diff = query[node.axis] - node.threshold;
247 let (near, far) = if diff <= 0.0 {
248 (node.left, node.right)
249 } else {
250 (node.right, node.left)
251 };
252
253 Self::search_nn_recursive(nodes, train, query, near, checks, best_idx, best_dist);
254
255 if diff * diff < *best_dist {
258 Self::search_nn_recursive(nodes, train, query, far, checks, best_idx, best_dist);
259 }
260 }
261
262 pub fn find_matches<B: Backend>(
271 &self,
272 desc1: &Tensor<B, 2>,
273 desc2: &Tensor<B, 2>,
274 ) -> Result<Vec<DMatch>> {
275 let q_dims = desc1.dims();
276 let t_dims = desc2.dims();
277 let n1 = q_dims[0];
278 let n2 = t_dims[0];
279 let dim = q_dims[1];
280
281 if dim != t_dims[1] {
282 return Err(IrisError::DimensionMismatch {
283 expected: vec![n1, dim],
284 actual: vec![n2, t_dims[1]],
285 });
286 }
287
288 let q_data = desc1.clone().into_data();
289 let t_data = desc2.clone().into_data();
290 let q_flat: Vec<f32> = q_data.iter::<f32>().collect();
291 let t_flat: Vec<f32> = t_data.iter::<f32>().collect();
292
293 let query_vecs: Vec<Vec<f32>> = (0..n1)
295 .map(|i| q_flat[i * dim..(i + 1) * dim].to_vec())
296 .collect();
297 let train_vecs: Vec<Vec<f32>> = (0..n2)
298 .map(|i| t_flat[i * dim..(i + 1) * dim].to_vec())
299 .collect();
300
301 let mut forest: Vec<Vec<KdNode>> = Vec::with_capacity(self.trees);
304 let mut forest_roots: Vec<Option<usize>> = Vec::with_capacity(self.trees);
305
306 for t in 0..self.trees {
307 let mut indices: Vec<usize> = (0..n2).collect();
309 let mut seed = (t as u32).wrapping_mul(1103515245).wrapping_add(12345);
310 for i in (1..n2).rev() {
311 seed = seed.wrapping_mul(1103515245).wrapping_add(12345);
312 let j = (seed as usize) % (i + 1);
313 indices.swap(i, j);
314 }
315
316 let mut items: Vec<(usize, Vec<f32>)> = indices
317 .into_iter()
318 .map(|i| (i, train_vecs[i].clone()))
319 .collect();
320 let mut nodes = Vec::new();
321 let root = Self::build_kd_tree(&mut items, 0, &mut nodes);
322 forest.push(nodes);
323 forest_roots.push(root);
324 }
325
326 let mut matches = Vec::with_capacity(n1);
328
329 for qi in 0..n1 {
330 let mut dist_acc = vec![0.0f32; n2];
332 let mut count = vec![0u32; n2];
333
334 for t in 0..self.trees {
335 let mut checks = self.checks;
336 let (best_train_idx, best_dist) = Self::search_nn(
337 &forest[t],
338 &train_vecs,
339 &query_vecs[qi],
340 forest_roots[t],
341 &mut checks,
342 );
343 dist_acc[best_train_idx] += best_dist;
344 count[best_train_idx] += 1;
345 }
346
347 let mut best_idx = 0usize;
350 let mut best_dist = f32::MAX;
351 for ti in 0..n2 {
352 if count[ti] > 0 {
353 let avg = dist_acc[ti] / count[ti] as f32;
354 if avg < best_dist {
355 best_dist = avg;
356 best_idx = ti;
357 }
358 }
359 }
360
361 if best_dist.is_infinite() || best_dist >= f32::MAX {
363 for ti in 0..n2 {
364 let d = euclidean_dist_sq(&query_vecs[qi], &train_vecs[ti]);
365 if d < best_dist {
366 best_dist = d;
367 best_idx = ti;
368 }
369 }
370 }
371
372 matches.push(DMatch {
373 query_idx: qi,
374 train_idx: best_idx,
375 img_idx: 0,
376 distance: best_dist.sqrt(),
377 });
378 }
379
380 Ok(matches)
381 }
382}
383
384fn euclidean_dist_sq(a: &[f32], b: &[f32]) -> f32 {
386 a.iter()
387 .zip(b.iter())
388 .map(|(x, y)| {
389 let d = x - y;
390 d * d
391 })
392 .sum()
393}
394
395pub struct MatchDrawer;
397
398impl MatchDrawer {
399 pub fn draw_matches<B: Backend>(
401 img1: &Image<B>,
402 kps1: &[KeyPoint],
403 img2: &Image<B>,
404 kps2: &[KeyPoint],
405 matches: &[DMatch],
406 ) -> Result<Image<B>> {
407 let h1 = img1.height();
408 let w1 = img1.width();
409 let h2 = img2.height();
410 let w2 = img2.width();
411
412 let out_h = h1.max(h2);
413 let out_w = w1 + w2;
414 let c = img1.channels();
415
416 let device = img1.tensor.device();
417
418 let data1 = img1.tensor.clone().into_data();
421 let data2 = img2.tensor.clone().into_data();
422 let flat1: Vec<f32> = data1.iter::<f32>().collect();
423 let flat2: Vec<f32> = data2.iter::<f32>().collect();
424 let mut out_flat = vec![0.0f32; c * out_h * out_w];
425
426 for ch in 0..c {
427 for y in 0..h1 {
428 for x in 0..w1 {
429 out_flat[ch * out_h * out_w + y * out_w + x] = flat1[ch * h1 * w1 + y * w1 + x];
430 }
431 }
432 for y in 0..h2 {
433 for x in 0..w2 {
434 out_flat[ch * out_h * out_w + y * out_w + (x + w1)] =
435 flat2[ch * h2 * w2 + y * w2 + x];
436 }
437 }
438 }
439
440 let assembled_tensor =
441 Tensor::<B, 3>::from_data(TensorData::new(out_flat, [c, out_h, out_w]), &device);
442 let mut assembled = Image::new(assembled_tensor);
443
444 for m in matches {
446 let kp1 = &kps1[m.query_idx];
447 let kp2 = &kps2[m.train_idx];
448
449 let p1 = Point::new(kp1.pt.x as usize, kp1.pt.y as usize);
450 let p2 = Point::new(kp2.pt.x as usize + w1, kp2.pt.y as usize);
451
452 assembled = assembled.draw_line(p1, p2, Scalar::new(0.0, 1.0, 0.0, 0.0))?;
453 }
454
455 Ok(assembled)
456 }
457}
458
459#[cfg(test)]
460mod tests {
461 use super::*;
462 use crate::test_helpers::{TestBackend, test_device};
463
464 #[test]
465 fn test_descriptor_matching() {
466 let device = test_device();
467 let query = Tensor::<TestBackend, 2>::from_data(
468 TensorData::new(vec![1.0f32, 0.0, 0.0, 1.0], [2, 2]),
469 &device,
470 );
471 let train = Tensor::<TestBackend, 2>::from_data(
472 TensorData::new(vec![1.0f32, 0.0, 0.0, 1.0], [2, 2]),
473 &device,
474 );
475
476 let matcher = BFMatcher;
477 let matches = matcher.match_descriptors(&query, &train).unwrap();
478 assert_eq!(matches.len(), 2);
479 assert_eq!(matches[0].query_idx, 0);
480 assert_eq!(matches[0].train_idx, 0);
481 assert_eq!(matches[1].query_idx, 1);
482 assert_eq!(matches[1].train_idx, 1);
483
484 let flat_data = vec![0.5f32; 3 * 8 * 8];
485 let t1 = Tensor::<TestBackend, 3>::from_data(
486 TensorData::new(flat_data.clone(), [3, 8, 8]),
487 &device,
488 );
489 let t2 =
490 Tensor::<TestBackend, 3>::from_data(TensorData::new(flat_data, [3, 8, 8]), &device);
491 let img1 = Image::new(t1);
492 let img2 = Image::new(t2);
493
494 let kps1 = vec![KeyPoint::new(1.0, 1.0, 2.0), KeyPoint::new(2.0, 2.0, 2.0)];
495 let kps2 = vec![KeyPoint::new(1.0, 1.0, 2.0), KeyPoint::new(2.0, 2.0, 2.0)];
496
497 let drawn = MatchDrawer::draw_matches(&img1, &kps1, &img2, &kps2, &matches).unwrap();
498 assert_eq!(drawn.shape(), [3, 8, 16]);
499 }
500
501 #[test]
502 fn test_flann_matcher_known_pairs() {
503 let device = test_device();
504 let query = Tensor::<TestBackend, 2>::from_data(
508 TensorData::new(vec![1.01, 0.01, 0.0, 0.0, 0.01, 0.99, 0.0, 0.0], [2, 4]),
509 &device,
510 );
511 let train = Tensor::<TestBackend, 2>::from_data(
512 TensorData::new(
513 vec![
514 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.05, 0.05, 0.0, 0.0,
517 ],
518 [3, 4],
519 ),
520 &device,
521 );
522
523 let matcher = FlannMatcher::new();
524 let matches = matcher.find_matches(&query, &train).unwrap();
525 assert_eq!(matches.len(), 2);
526
527 assert_eq!(matches[0].query_idx, 0);
529 assert_eq!(matches[0].train_idx, 0); assert_eq!(matches[1].query_idx, 1);
531 assert_eq!(matches[1].train_idx, 1); }
533}