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