slamkit_rs/mapping/
map.rs

1use nalgebra as na;
2use std::collections::HashMap;
3
4use super::triangulation::MapPoint;
5use crate::odometry::CameraIntrinsics;
6
7/// A global map containing all 3D points and their observations
8pub struct Map {
9    /// All map points indexed by ID
10    points: HashMap<usize, MapPoint>,
11    /// Next available point ID
12    next_id: usize,
13    /// Camera intrinsics for reprojection
14    intrinsics: CameraIntrinsics,
15    /// Minimum observations before a point is considered stable
16    min_observations: usize,
17}
18
19impl Map {
20    /// Create a new empty map
21    pub fn new(intrinsics: CameraIntrinsics) -> Self {
22        Self {
23            points: HashMap::new(),
24            next_id: 0,
25            intrinsics,
26            min_observations: 2,
27        }
28    }
29
30    /// Add new points to the map
31    pub fn add_points(&mut self, mut points: Vec<MapPoint>) {
32        for point in points.iter_mut() {
33            point.id = self.next_id;
34            self.points.insert(self.next_id, point.clone());
35            self.next_id += 1;
36        }
37    }
38
39    /// Get all points in the map
40    pub fn points(&self) -> Vec<&MapPoint> {
41        self.points.values().collect()
42    }
43
44    /// Get number of points
45    pub fn size(&self) -> usize {
46        self.points.len()
47    }
48
49    /// Project a 3D point to 2D image coordinates
50    fn project_point(
51        &self,
52        point: &na::Point3<f64>,
53        r: &na::Matrix3<f64>,
54        t: &na::Vector3<f64>,
55    ) -> Option<na::Point2<f64>> {
56        // Transform to camera frame
57        let p_cam = r * point + t;
58
59        // Check if point is in front of camera
60        if p_cam.z <= 0.0 {
61            return None;
62        }
63
64        // Project to image
65        let x = self.intrinsics.fx * (p_cam.x / p_cam.z) + self.intrinsics.cx;
66        let y = self.intrinsics.fy * (p_cam.y / p_cam.z) + self.intrinsics.cy;
67
68        Some(na::Point2::new(x, y))
69    }
70
71    /// Find map points visible in current frame and match with features
72    pub fn find_matches(
73        &mut self,
74        _keypoints: &opencv::core::Vector<opencv::core::KeyPoint>,
75        descriptors: &opencv::core::Mat,
76        pose: &(na::Matrix3<f64>, na::Vector3<f64>),
77        matcher: &mut crate::FeatureMatcher,
78    ) -> Result<Vec<(usize, usize)>, Box<dyn std::error::Error>> {
79        let (r, t) = pose;
80        let mut matches = Vec::new();
81
82        // Build descriptor matrix for visible map points
83        let mut visible_points = Vec::new();
84        let mut visible_descriptors = Vec::new();
85
86        for (id, point) in &self.points {
87            // Project point to image
88            if let Some(proj) = self.project_point(&point.position, r, t) {
89                // Check if projection is within image bounds (rough check)
90                if proj.x >= 0.0 && proj.x < 4000.0 && proj.y >= 0.0 && proj.y < 3000.0 {
91                    if let Some(ref desc) = point.descriptor {
92                        visible_points.push(*id);
93                        visible_descriptors.push(desc.clone());
94                    }
95                }
96            }
97        }
98
99        if visible_descriptors.is_empty() {
100            return Ok(matches);
101        }
102
103        // Create Mat from visible descriptors
104        let map_desc = descriptors_to_mat(&visible_descriptors)?;
105
106        // Match current features against visible map points
107        let raw_matches = matcher.match_descriptors(&map_desc, descriptors)?;
108        let good_matches = matcher.filter_good_matches(&raw_matches, 2.0);
109
110        // Convert to (map_point_id, keypoint_idx)
111        for m in good_matches.iter() {
112            let map_id = visible_points[m.query_idx as usize];
113            let kp_idx = m.train_idx as usize;
114            matches.push((map_id, kp_idx));
115        }
116
117        Ok(matches)
118    }
119
120    /// Update observations for matched points
121    pub fn update_observations(&mut self, matches: &[(usize, usize)]) {
122        for (map_id, _kp_idx) in matches {
123            if let Some(point) = self.points.get_mut(map_id) {
124                point.add_observation();
125            }
126        }
127    }
128
129    /// Prune bad points (few observations or high reprojection error)
130    pub fn prune_outliers(&mut self) -> usize {
131        let before = self.points.len();
132
133        self.points
134            .retain(|_id, point| point.observations >= self.min_observations);
135
136        before - self.points.len()
137    }
138
139    /// Get points with minimum observations (stable points)
140    pub fn stable_points(&self) -> Vec<&MapPoint> {
141        self.points
142            .values()
143            .filter(|p| p.observations >= self.min_observations)
144            .collect()
145    }
146
147    /// Clear all points
148    pub fn clear(&mut self) {
149        self.points.clear();
150        self.next_id = 0;
151    }
152}
153
154/// Helper to convert Vec<Vec<u8>> to OpenCV Mat
155fn descriptors_to_mat(
156    descriptors: &[Vec<u8>],
157) -> Result<opencv::core::Mat, Box<dyn std::error::Error>> {
158    use opencv::core::{CV_8U, Mat};
159    use opencv::prelude::*;
160
161    if descriptors.is_empty() {
162        return Ok(Mat::default());
163    }
164
165    let rows = descriptors.len() as i32;
166    let cols = descriptors[0].len() as i32;
167    let mut mat =
168        Mat::new_rows_cols_with_default(rows, cols, CV_8U, opencv::core::Scalar::all(0.0))?;
169
170    for (i, desc) in descriptors.iter().enumerate() {
171        for (j, &val) in desc.iter().enumerate() {
172            *mat.at_2d_mut::<u8>(i as i32, j as i32)? = val;
173        }
174    }
175
176    Ok(mat)
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    #[test]
184    fn test_map_creation() {
185        let cam = CameraIntrinsics::kitti();
186        let map = Map::new(cam);
187        assert_eq!(map.size(), 0);
188    }
189
190    #[test]
191    fn test_add_points() {
192        let cam = CameraIntrinsics::kitti();
193        let mut map = Map::new(cam);
194
195        let points = vec![
196            MapPoint::new(na::Point3::new(1.0, 2.0, 10.0), 0),
197            MapPoint::new(na::Point3::new(2.0, 3.0, 10.0), 1),
198        ];
199
200        map.add_points(points);
201        assert_eq!(map.size(), 2);
202    }
203
204    #[test]
205    fn test_projection() {
206        let cam = CameraIntrinsics::kitti();
207        let map = Map::new(cam);
208
209        let point = na::Point3::new(0.0, 0.0, 10.0);
210        let r = na::Matrix3::identity();
211        let t = na::Vector3::zeros();
212
213        let proj = map.project_point(&point, &r, &t);
214        assert!(proj.is_some());
215
216        let p = proj.unwrap();
217        assert!((p.x - cam.cx).abs() < 1e-6);
218        assert!((p.y - cam.cy).abs() < 1e-6);
219    }
220
221    #[test]
222    fn test_prune_outliers() {
223        let cam = CameraIntrinsics::kitti();
224        let mut map = Map::new(cam);
225
226        let mut points = vec![
227            MapPoint::new(na::Point3::new(1.0, 2.0, 10.0), 0),
228            MapPoint::new(na::Point3::new(2.0, 3.0, 10.0), 1),
229        ];
230        points[0].observations = 5;
231        points[1].observations = 1;
232
233        map.add_points(points);
234        let removed = map.prune_outliers();
235
236        assert_eq!(removed, 1);
237        assert_eq!(map.size(), 1);
238    }
239}