1use crate::features::{FeatureMatch, TwoViewGeometry, TwoViewGeometryConfig};
38use crate::ids::*;
39use crate::store::FeatureStore;
40use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
44pub struct Correspondence {
45 pub image_id: ImageId,
47 pub point2d_idx: Point2DIdx,
49}
50
51impl Correspondence {
52 pub fn new(image_id: ImageId, point2d_idx: Point2DIdx) -> Self {
54 Self { image_id, point2d_idx }
55 }
56}
57
58#[derive(Clone, Debug, Default)]
59struct GraphImage {
60 num_observations: usize,
62 num_correspondences: usize,
64 corrs: Vec<Vec<Correspondence>>,
65}
66
67#[derive(Clone, Debug)]
68struct GraphPair {
69 num_correspondences: usize,
70 geometry: TwoViewGeometry,
72}
73
74#[derive(Clone, Debug)]
76pub struct MatchGraphOptions {
77 pub min_num_matches: usize,
79 pub ignore_watermarks: bool,
81 pub image_names: HashSet<String>,
83 pub keep_all_images: bool,
85}
86
87impl Default for MatchGraphOptions {
88 fn default() -> Self {
89 Self { min_num_matches: 15, ignore_watermarks: false, image_names: HashSet::new(), keep_all_images: false }
90 }
91}
92
93#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
95pub struct AddPairStats {
96 pub num_added: usize,
98 pub num_out_of_range: usize,
100 pub num_duplicates: usize,
102 pub self_pair: bool,
104}
105
106#[derive(Clone, Debug, Default)]
108pub struct MatchGraph {
109 images: HashMap<ImageId, GraphImage>,
110 pairs: HashMap<PairId, GraphPair>,
111 store_cursor: usize,
113}
114
115impl MatchGraph {
116 pub fn new() -> Self {
118 Self::default()
119 }
120
121 pub fn add_image(&mut self, image_id: ImageId, num_points2d: usize) {
123 self.images.entry(image_id).or_insert_with(|| GraphImage { corrs: vec![Vec::new(); num_points2d], ..Default::default() });
124 }
125 pub fn exists_image(&self, image_id: ImageId) -> bool {
127 self.images.contains_key(&image_id)
128 }
129 pub fn num_images(&self) -> usize {
131 self.images.len()
132 }
133 pub fn pair_count(&self) -> usize {
135 self.pairs.len()
136 }
137 pub fn image_ids(&self) -> Vec<ImageId> {
139 let mut v: Vec<_> = self.images.keys().copied().collect();
140 v.sort();
141 v
142 }
143 pub fn num_points2d(&self, image_id: ImageId) -> usize {
145 self.images.get(&image_id).map_or(0, |i| i.corrs.len())
146 }
147 pub fn observation_count_of(&self, image_id: ImageId) -> usize {
149 self.images.get(&image_id).map_or(0, |i| i.num_observations)
150 }
151 pub fn match_count_of(&self, image_id: ImageId) -> usize {
153 self.images.get(&image_id).map_or(0, |i| i.num_correspondences)
154 }
155 pub fn match_count_between(&self, id1: ImageId, id2: ImageId) -> usize {
157 pair_id_of(id1, id2).ok().and_then(|p| self.pairs.get(&p)).map_or(0, |p| p.num_correspondences)
158 }
159 pub fn exists_image_pair(&self, id1: ImageId, id2: ImageId) -> bool {
161 pair_id_of(id1, id2).is_ok_and(|p| self.pairs.contains_key(&p))
162 }
163 pub fn image_pairs(&self) -> Vec<(PairId, usize)> {
165 let mut v: Vec<_> = self.pairs.iter().map(|(k, p)| (*k, p.num_correspondences)).collect();
166 v.sort();
167 v
168 }
169
170 pub fn insert_two_view(&mut self, id1: ImageId, id2: ImageId, tvg: &TwoViewGeometry) -> AddPairStats {
173 let mut st = AddPairStats::default();
174 if id1 == id2 {
175 st.self_pair = true;
176 return st;
177 }
178 let Ok(pid) = pair_id_of(id1, id2) else { return st };
179 if !self.images.contains_key(&id1) || !self.images.contains_key(&id2) || self.pairs.contains_key(&pid) {
180 return st;
181 }
182 let n1 = self.images[&id1].corrs.len();
183 let n2 = self.images[&id2].corrs.len();
184 let mut count = 0usize;
185 for m in &tvg.inlier_matches {
186 if m.idx1 as usize >= n1 || m.idx2 as usize >= n2 {
187 st.num_out_of_range += 1;
188 continue;
189 }
190 let c2 = Correspondence::new(id2, m.idx2);
191 {
192 let im1 = self.images.get_mut(&id1).expect("존재");
193 let list = &mut im1.corrs[m.idx1 as usize];
194 if list.contains(&c2) {
195 st.num_duplicates += 1;
196 continue;
197 }
198 if list.is_empty() {
199 im1.num_observations += 1;
200 }
201 list.push(c2);
202 }
203 {
204 let im2 = self.images.get_mut(&id2).expect("존재");
205 let list = &mut im2.corrs[m.idx2 as usize];
206 if list.is_empty() {
207 im2.num_observations += 1;
208 }
209 list.push(Correspondence::new(id1, m.idx1));
210 }
211 count += 1;
212 }
213 self.images.get_mut(&id1).expect("존재").num_correspondences += count;
214 self.images.get_mut(&id2).expect("존재").num_correspondences += count;
215 let mut geometry = TwoViewGeometry { inlier_matches: Vec::new(), ..tvg.clone() };
216 if swap_image_pair(id1, id2) {
217 geometry.invert();
218 }
219 self.pairs.insert(pid, GraphPair { num_correspondences: count, geometry });
220 st.num_added = count;
221 st
222 }
223
224 fn usable(tvg: &TwoViewGeometry, opts: &MatchGraphOptions) -> bool {
225 tvg.inlier_matches.len() >= opts.min_num_matches && !(opts.ignore_watermarks && tvg.config == TwoViewGeometryConfig::Watermark)
226 }
227
228 pub fn from_store(store: &FeatureStore, opts: &MatchGraphOptions) -> Self {
230 let mut g = Self::new();
231 g.update_from_store(store, opts);
232 g
233 }
234
235 pub fn update_from_store(&mut self, store: &FeatureStore, opts: &MatchGraphOptions) -> usize {
238 let (new_pairs, cursor) = store.two_view_geometries_since(self.store_cursor);
239 self.store_cursor = cursor;
240 let name_ok =
241 |id: ImageId| -> bool { opts.image_names.is_empty() || store.image(id).is_some_and(|im| opts.image_names.contains(&im.name)) };
242 if opts.keep_all_images {
243 for im in store.images() {
244 if name_ok(im.image_id) {
245 self.add_image(im.image_id, store.num_keypoints(im.image_id));
246 }
247 }
248 }
249 let mut by_pid: BTreeMap<PairId, _> = BTreeMap::new();
251 for (pid, t) in new_pairs {
252 by_pid.insert(pid, t);
253 }
254 let mut added = 0;
255 for (pid, t) in by_pid {
256 if self.pairs.contains_key(&pid) || !Self::usable(&t, opts) {
257 continue;
258 }
259 let (a, b) = images_of_pair(pid);
260 if !name_ok(a) || !name_ok(b) || a == b {
261 continue;
262 }
263 for id in [a, b] {
264 if !self.images.contains_key(&id) {
265 self.add_image(id, store.num_keypoints(id));
266 }
267 }
268 self.insert_two_view(a, b, &t);
269 added += 1;
270 }
271 added
272 }
273
274 pub fn find_correspondences(&self, image_id: ImageId, point2d_idx: Point2DIdx) -> &[Correspondence] {
276 self.images.get(&image_id).and_then(|i| i.corrs.get(point2d_idx as usize)).map_or(&[], |v| v.as_slice())
277 }
278 pub fn has_correspondences(&self, image_id: ImageId, point2d_idx: Point2DIdx) -> bool {
280 !self.find_correspondences(image_id, point2d_idx).is_empty()
281 }
282 pub fn in_two_view_track(&self, image_id: ImageId, point2d_idx: Point2DIdx) -> bool {
284 let c = self.find_correspondences(image_id, point2d_idx);
285 c.len() == 1 && self.find_correspondences(c[0].image_id, c[0].point2d_idx).len() == 1
286 }
287 pub fn matches_between(&self, id1: ImageId, id2: ImageId) -> Vec<FeatureMatch> {
289 let mut out = Vec::new();
290 if let Some(im) = self.images.get(&id1) {
291 for (k, list) in im.corrs.iter().enumerate() {
292 for c in list {
293 if c.image_id == id2 {
294 out.push(FeatureMatch::new(k as u32, c.point2d_idx));
295 }
296 }
297 }
298 }
299 out
300 }
301 pub fn transitive_matches(&self, image_id: ImageId, point2d_idx: Point2DIdx, depth: usize) -> Vec<Correspondence> {
303 let start = Correspondence::new(image_id, point2d_idx);
304 let mut seen: HashSet<Correspondence> = HashSet::from([start]);
305 let mut out = Vec::new();
306 let mut q = VecDeque::from([(start, 0usize)]);
307 while let Some((c, d)) = q.pop_front() {
308 if d >= depth {
309 continue;
310 }
311 for n in self.find_correspondences(c.image_id, c.point2d_idx) {
312 if seen.insert(*n) {
313 out.push(*n);
314 q.push_back((*n, d + 1));
315 }
316 }
317 }
318 out
319 }
320 pub fn two_view_geometry(&self, id1: ImageId, id2: ImageId) -> Option<TwoViewGeometry> {
322 let pid = pair_id_of(id1, id2).ok()?;
323 let g = &self.pairs.get(&pid)?.geometry;
324 Some(if swap_image_pair(id1, id2) { g.inverted() } else { g.clone() })
325 }
326 pub fn replace_two_view(&mut self, id1: ImageId, id2: ImageId, tvg: &TwoViewGeometry) -> bool {
328 let Ok(pid) = pair_id_of(id1, id2) else { return false };
329 let Some(p) = self.pairs.get_mut(&pid) else { return false };
330 let mut g = TwoViewGeometry { inlier_matches: Vec::new(), ..tvg.clone() };
331 if swap_image_pair(id1, id2) {
332 g.invert();
333 }
334 p.geometry = g;
335 true
336 }
337
338 pub fn union_find_tracks<F>(&self, pair_filter: F) -> Vec<Vec<Correspondence>>
341 where
342 F: Fn(ImageId, ImageId) -> bool,
343 {
344 let mut index: HashMap<Correspondence, usize> = HashMap::new();
345 let mut nodes: Vec<Correspondence> = Vec::new();
346 let mut parent: Vec<usize> = Vec::new();
347 fn find(p: &mut [usize], mut x: usize) -> usize {
348 while p[x] != x {
349 p[x] = p[p[x]];
350 x = p[x];
351 }
352 x
353 }
354 let mut get = |c: Correspondence, nodes: &mut Vec<Correspondence>, parent: &mut Vec<usize>| -> usize {
355 *index.entry(c).or_insert_with(|| {
356 nodes.push(c);
357 parent.push(parent.len());
358 parent.len() - 1
359 })
360 };
361 let mut pairs: Vec<PairId> = self.pairs.keys().copied().collect();
362 pairs.sort();
363 for pid in pairs {
364 let (a, b) = images_of_pair(pid);
365 if !pair_filter(a, b) {
366 continue;
367 }
368 for m in self.matches_between(a, b) {
369 let x = get(Correspondence::new(a, m.idx1), &mut nodes, &mut parent);
370 let y = get(Correspondence::new(b, m.idx2), &mut nodes, &mut parent);
371 let (rx, ry) = (find(&mut parent, x), find(&mut parent, y));
372 if rx != ry {
373 if nodes[rx] < nodes[ry] {
375 parent[ry] = rx;
376 } else {
377 parent[rx] = ry;
378 }
379 }
380 }
381 }
382 let mut comps: BTreeMap<usize, Vec<Correspondence>> = BTreeMap::new();
383 for (i, node) in nodes.iter().enumerate() {
384 let r = find(&mut parent, i);
385 comps.entry(r).or_default().push(*node);
386 }
387 let mut out: Vec<Vec<Correspondence>> = comps
388 .into_values()
389 .filter(|v| v.len() > 1)
390 .map(|mut v| {
391 v.sort();
392 v
393 })
394 .collect();
395 out.sort();
396 out
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403 use crate::camera::{Camera, CameraModelKind};
404 use crate::features::Keypoint;
405 use crate::geometry::{Quat, Rigid3, Vec3};
406
407 fn tvg(m: &[(u32, u32)]) -> TwoViewGeometry {
408 TwoViewGeometry {
409 config: TwoViewGeometryConfig::Calibrated,
410 inlier_matches: m.iter().map(|&(a, b)| FeatureMatch::new(a, b)).collect(),
411 ..Default::default()
412 }
413 }
414
415 #[test]
416 fn add_pair_rules() {
417 let mut g = MatchGraph::new();
418 g.add_image(1, 10);
419 g.add_image(2, 10);
420 g.add_image(3, 10);
421 let st = g.insert_two_view(1, 2, &tvg(&[(0, 0), (1, 1), (1, 1), (20, 2), (2, 30), (3, 3)]));
422 assert_eq!(st.num_added, 3);
423 assert_eq!(st.num_duplicates, 1);
424 assert_eq!(st.num_out_of_range, 2);
425 assert!(g.insert_two_view(3, 3, &tvg(&[(0, 0)])).self_pair);
426 assert_eq!(g.match_count_between(2, 1), 3);
427 assert_eq!(g.observation_count_of(1), 3);
428 assert_eq!(g.match_count_of(2), 3);
429 g.insert_two_view(3, 2, &tvg(&[(5, 0), (6, 1)]));
431 assert_eq!(g.observation_count_of(2), 3);
432 assert_eq!(g.match_count_of(2), 5);
433 assert_eq!(g.matches_between(2, 3), vec![FeatureMatch::new(0, 5), FeatureMatch::new(1, 6)]);
434 assert!(g.in_two_view_track(1, 3));
435 assert!(!g.in_two_view_track(1, 0)); let t = g.transitive_matches(1, 0, 2);
437 assert_eq!(t, vec![Correspondence::new(2, 0), Correspondence::new(3, 5)]);
438 assert_eq!(g.transitive_matches(1, 0, 1).len(), 1);
439 }
440
441 #[test]
442 fn geometry_direction() {
443 let mut g = MatchGraph::new();
444 g.add_image(1, 1);
445 g.add_image(2, 1);
446 let pose = Rigid3::new(Quat::from_axis_angle(&Vec3::new(1.0, 2.0, 3.0), 0.7), Vec3::new(0.3, -0.2, 1.0));
447 let t = TwoViewGeometry { cam1_to_cam2: Some(pose), ..tvg(&[(0, 0)]) };
448 g.insert_two_view(2, 1, &t);
449 let back = g.two_view_geometry(2, 1).unwrap().cam1_to_cam2.unwrap();
450 assert!(back.rotation.angular_distance(&pose.rotation) < 1e-12);
451 let inv = g.two_view_geometry(1, 2).unwrap().cam1_to_cam2.unwrap();
452 let id = inv * pose;
453 assert!(id.rotation.angular_distance(&Quat::IDENTITY) < 1e-12);
454 assert!(id.translation.norm() < 1e-12);
455 }
456
457 #[test]
458 fn incremental_from_store_and_tracks() {
459 let s = FeatureStore::new();
460 let c = s.add_camera(Camera::from_focal(CameraModelKind::OpenCv, 100.0, 100, 100)).unwrap();
461 let ids: Vec<_> = (0..4).map(|k| s.add_image(&format!("{k}.jpg"), c).unwrap()).collect();
462 for &i in &ids {
463 s.set_keypoints(i, vec![Keypoint::new(0.5, 0.5); 20]);
464 }
465 let full: Vec<(u32, u32)> = (0..15).map(|k| (k, k)).collect();
466 s.put_two_view(ids[0], ids[1], &tvg(&full)).unwrap();
467 s.put_two_view(ids[1], ids[2], &tvg(&full)).unwrap();
468 s.put_two_view(ids[2], ids[3], &tvg(&full[..14])).unwrap(); let opts = MatchGraphOptions::default();
470 let mut g = MatchGraph::from_store(&s, &opts);
471 assert_eq!(g.num_images(), 3);
472 assert_eq!(g.pair_count(), 2);
473 s.put_two_view(ids[3], ids[0], &tvg(&full)).unwrap();
475 assert_eq!(g.update_from_store(&s, &opts), 1);
476 assert_eq!(g.update_from_store(&s, &opts), 0);
477 assert_eq!(g.num_images(), 4);
478 let tracks = g.union_find_tracks(|_, _| true);
479 assert_eq!(tracks.len(), 15);
480 assert!(tracks.iter().all(|t| t.len() == 4));
481 let tracks = g.union_find_tracks(|a, b| !(a == ids[1] && b == ids[2]));
482 assert!(tracks.iter().all(|t| t.len() == 3));
483 }
484}