slamkit_rs/feature/
matcher.rs

1use opencv::{
2    core::{Mat, Vector},
3    features2d::BFMatcher,
4    prelude::*,
5};
6
7/// Feature matcher using Brute Force
8/// For each descriptor in the first set, this matcher finds the closest descriptor in the second set by trying each one. This descriptor matcher supports masking permissible matches of descriptor sets.
9pub struct FeatureMatcher {
10    matcher: opencv::core::Ptr<BFMatcher>,
11}
12
13impl FeatureMatcher {
14    /// Create a new feature matcher using Brute Force
15    pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
16        let matcher = BFMatcher::create(
17            opencv::core::NORM_HAMMING,
18            false, // don't cross check
19        )?;
20        Ok(Self { matcher })
21    }
22
23    /// Match descriptors between two frames
24    pub fn match_descriptors(
25        &mut self,
26        desc1: &Mat,
27        desc2: &Mat,
28    ) -> Result<Vector<opencv::core::DMatch>, Box<dyn std::error::Error>> {
29        if desc1.empty() || desc2.empty() {
30            return Ok(Vector::new());
31        }
32
33        let mut matches = Vector::new();
34        self.matcher
35            .train_match(desc1, desc2, &mut matches, &Mat::default())?;
36        Ok(matches)
37    }
38
39    /// Filter matches by distance (keep best matches)
40    pub fn filter_good_matches(
41        &self,
42        matches: &Vector<opencv::core::DMatch>,
43        ratio: f32,
44    ) -> Vector<opencv::core::DMatch> {
45        if matches.is_empty() {
46            return Vector::new();
47        }
48
49        // Find min distance
50        let mut min_dist = f32::MAX;
51        for m in matches.iter() {
52            if m.distance < min_dist {
53                min_dist = m.distance;
54            }
55        }
56        // Keep matches with distance < max(2*min_dist, 30.0)
57        // TODO: Implement a better threshold calculation
58        let threshold = (ratio * min_dist).max(30.0);
59        let mut good = Vector::new();
60        for m in matches.iter() {
61            if m.distance < threshold {
62                good.push(m);
63            }
64        }
65        good
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_matcher_creation() {
75        let matcher = FeatureMatcher::new();
76        assert!(matcher.is_ok());
77    }
78
79    #[test]
80    fn test_empty_match() {
81        let mut matcher = FeatureMatcher::new().unwrap();
82        let empty = Mat::default();
83        let result = matcher.match_descriptors(&empty, &empty);
84        assert!(result.is_ok());
85        assert_eq!(result.unwrap().len(), 0);
86    }
87}