Skip to content

Add sequential matcher with bundled vocab tree to colmap.py - #70

Merged
kobejean merged 11 commits into
mainfrom
colmap-sequential-matcher
Jul 5, 2026
Merged

Add sequential matcher with bundled vocab tree to colmap.py#70
kobejean merged 11 commits into
mainfrom
colmap-sequential-matcher

Conversation

@kobejean

@kobejean kobejean commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a --use_sequential matching mode to script/colmap/colmap.py, tuned for sequential iPhone captures where consecutive frames sometimes lack direct overlap. It runs colmap sequential_matcher — a sliding window over frames (in filename/capture order) plus vocab-tree loop detection for spatially-near-but-sequentially-distant frames — giving a well-connected match graph without a full O(N²) exhaustive pass.

Changes

  • --use_sequential flag (+ --sequential_overlap, default 10). Uses --SequentialMatching.quadratic_overlap so the window reaches frames at exponentially increasing distances.
  • Thorough loop closure when a vocab tree is present: loop_detection_period 1 (query every image), loop_detection_num_images 60, loop_detection_num_nearest_neighbors 5 (soft visual-word assignment for higher retrieval recall).
  • Tight geometric verification (max_ratio 0.8, min_num_inliers 15) so the extra loop-closure candidate edges that survive are reliable, not spurious.
  • Bundled default vocab tree committed at script/colmap/vocab_tree.bin (15 MB, Flickr100K / 32K words). New resolve_vocab_tree_path(): a per-session <source_dir>/vocab_tree.bin overrides, else the bundled default is used. The existing --use_vocab_tree path now shares this resolver.

Usage

uv run python script/colmap/colmap.py input/<session> --use_sequential --use_glomap

Works out of the box with loop detection — no per-session vocab tree setup needed.

🤖 Generated with Claude Code

Trinh and others added 11 commits July 4, 2026 15:38
Add --use_sequential flag to run colmap sequential_matcher, which does
sliding-window matching (--sequential_overlap, default 10) plus vocab-tree
loop detection for spatially-near-but-sequentially-distant frames. Suits
sequential captures where consecutive images sometimes lack direct overlap.

Commit a default Flickr100K/32K-word vocab_tree.bin next to the script and
resolve it via resolve_vocab_tree_path(): a per-session
<source_dir>/vocab_tree.bin still overrides, else fall back to the bundled
default. The existing --use_vocab_tree path now uses the same resolver.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Query every image for loop detection (period 1), retrieve 60 candidates with
soft visual-word assignment (num_nearest_neighbors 5) for a well-connected
graph. Add tight geometric verification (max_ratio 0.8, min_num_inliers 15) so
the extra candidate edges that survive are reliable rather than spurious.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Indexing every extracted feature (up to max_num_features, 16384) into the vocab
tree dominated matching runtime (~50s/image). Set loop_detection_max_num_features
to 4096 so the index uses only the top-scale features (which dominate retrieval),
cutting indexing ~4-8x. Full pairwise SIFT matching still uses all features, so
map density is unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Querying every image (period 1) against 60 candidates made the loop-detection
matching pass take ~12h on a 765-image dataset. Consecutive frames retrieve
nearly identical loop candidates, so period 1 is mostly redundant -- a revisit
spans many frames and is still caught at period 5. Set period 5 and num_images
40 for ~5x faster matching (~2.5h) with negligible recall loss.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The script already writes ARKit position pose priors to the pose_priors table,
but neither colmap mapper nor glomap mapper consumes them, so they were dormant
during reconstruction -- a likely cause of disconnected components when visual
matching between clusters is too sparse.

Add run_colmap_pose_prior_mapping() and a --use_pose_prior flag that routes
reconstruction through colmap pose_prior_mapper, which registers cameras using
their ARKit positions (robust loss on prior position, DB covariance). Takes
precedence over --use_glomap; mirrors the intrinsics-fixed BA settings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ntion

Vision-only SfM (incremental/GLOMAP) cannot cohere on wide-baseline, low-parallax
capture (park foliage): despite rich matches (median 331 inliers/pair), two-view
relative poses are inconsistent, so GLOMAP connects only 192/673 images and the
incremental mapper dies at init. Add --use_arkit_poses: seed a COLMAP model with
the trusted ARKit poses for every frame and run point_triangulator, giving a fully
connected 673-image / ~52k-point map (poses never depend on the visual view graph).

Fix the ARKit<->COLMAP coordinate convention, which was geometrically wrong: it
applied a world-axis column flip instead of the standard camera-axis flip on an
inverted matrix (ARKit camera-to-world -> COLMAP world-to-camera:
R_w2c = diag(1,-1,-1) @ R_c2w^T, t = -R_w2c @ C). Correct convention triangulates
~34k+ points vs ~4.8k for the old one. Fix flows through:
- colmap_pose.py: extract_rotation_translation_from_extrinsics + ColmapPose
- arkit_integration.py / database_operations.py: derive camera center as -R^T t
- colmap_database.cpp: colmapPoseToMatrix + drop the landmark/camera y/z flips,
  keeping the C++ refiner consistent with the corrected Python convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ARKit VIO drift splits multi-view observations into separate 2-view points that
fail the >=3-sightings cull. A 12px merge/complete/filter threshold (was 6px)
rejoins them into longer tracks, roughly doubling final usable landmarks after
refinement (15.7k -> 31k) at essentially the same reprojection error
(0.39 -> 0.42px). The refiner's BA + outlier removal prunes any bad merges.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both were empirically validated as non-viable for this data (see
docs/RECONSTRUCTION_CLEANUP_PLAN.md):
- pose_prior_mapper stalls at incremental init even with correct, tight priors
  (init is pure vision); the low-parallax view graph can't bootstrap.
- Injecting ARKit odometry into two_view_geometries has no effect: COLMAP/GLOMAP
  re-estimate relative poses from feature matches and filter edges with <30
  inliers, so pose-only edges are discarded. Odometry belongs in the g2o BA
  (ColmapRefiner already adds it), not the SfM view graph.

Removed: --use_pose_prior + run_colmap_pose_prior_mapping; insert_arkit_odometry
+ insert_two_view_geometries_from_arkit and its helpers (image_ids_to_pair_id,
array_to_blob, compute_relative_pose_from_arkit); the pose_priors write in
create_colmap_database. Restored the full pipeline (setup + extract + match) that
was commented out for the resume workflow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- docs/RECONSTRUCTION_CLEANUP_PLAN.md: findings, keep/remove decisions, future
  --use_hybrid design, and merge action items.
- gitignore .claude/ (Claude Code metadata) and debug.txt (g2o Hessian dump).
- Untrack committed __pycache__/*.pyc (already covered by *.pyc).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Point RECONSTRUCTION.md at --use_sequential --use_arkit_poses as the path for
large / low-parallax capture, explain when vision-only SfM fragments, drop the
removed ARKit-odometry pipeline step, and note alignment is skipped for the
ARKit-pose path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kobejean
kobejean merged commit bf13828 into main Jul 5, 2026
2 checks passed
@kobejean
kobejean deleted the colmap-sequential-matcher branch July 5, 2026 01:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant