Skip to content

Post-BA Retriangulation (Structure-Refinement) - #1111

Merged
kathirgounder merged 8 commits into
borglab:masterfrom
kathirgounder:retri-densify-pr
Apr 28, 2026
Merged

Post-BA Retriangulation (Structure-Refinement)#1111
kathirgounder merged 8 commits into
borglab:masterfrom
kathirgounder:retri-densify-pr

Conversation

@kathirgounder

@kathirgounder kathirgounder commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds an opt-in post-BA retriangulation step. Default off — existing pipelines unchanged.

When use_multi_view_retriangulation=True, after BA converges:

  1. Re-triangulates the union-find 2D tracks against the post-BA cameras (uses the existing Point3dInitializer). Recovers tracks the upstream pipeline dropped earlier when cameras weren't yet converged.
  2. Runs a final BA on the augmented track set.

How to enable

In your bundle_adjustment_module yaml:

bundle_adjustment_module:
  _target_: gtsfm.bundle.bundle_adjustment.BundleAdjustmentOptimizer
  reproj_error_thresholds: [10, 5, 3]
  use_multi_view_retriangulation: true
  # Optional tuning (sensible defaults; override if needed):
  # mv_retri_min_track_length: 3
  # mv_retri_reproj_error_thresh: 10.0

The MultiViewOptimizer threads its existing tracks2d_graph through to BA automatically — no caller changes needed.

Results on IMC scenes

Scene num_matched, min_score Without retri With retri
British Museum (100, 0.20) AUC@5 = 65.8 AUC@5 = 69.5 (+3.7)
Brussels (100, 0.15) AUC@5 = 79.5
Pantheon (15, 0.45) AUC@5 = 79.9

Wall-time: one extra BA pass.

What's in the diff

  • 1 new function: multi_view_retriangulate_from_2d_tracks in bundle_adjustment.py
  • 3 new opt-in flags on BundleAdjustmentOptimizer.__init__: use_multi_view_retriangulation, mv_retri_min_track_length, mv_retri_reproj_error_thresh
  • tracks_2d kwarg threaded through create_computation_graph and _run_ba_and_evaluate
  • One-line wire-in in multi_view_optimizer.py: tracks_2d=tracks2d_graph passed to ba_optimizer.create_computation_graph

@kathirgounder
kathirgounder force-pushed the retri-densify-pr branch 3 times, most recently from e854f95 to b1a34cb Compare April 27, 2026 23:50
@kathirgounder kathirgounder changed the title Add 3 utility functions for post-BA structure refinement Post-BA Retriangulation (Structure-Refinement) Apr 27, 2026
Adds an opt-in retriangulation + final-BA pass that runs after the existing BA
loop in `BundleAdjustmentOptimizer`. Recovers tracks dropped between union-find
(`CppDsfTracksEstimator`) and the BA filter passes — these are 3D points that
*could* be reconstructed once cameras are well-converged, but failed to triangulate
cleanly at the intermediate camera estimates from translation averaging.

**Default behavior is byte-identical to master.** All new flags default off;
`tracks_2d` defaults None. Existing callers see zero change.

## What it does

When `use_multi_view_retriangulation=True`, after the existing BA loop converges:

1. **Re-triangulate** the union-find 2D track set against the post-BA cameras
   using multi-view RANSAC (via existing `Point3dInitializer`). Samples camera
   pairs, triangulates with DLT, scores candidate inlier sets by reprojection
   error across all track measurements.
2. **Run final BA** on the augmented track set. Cameras get a second pass of
   refinement now that the geometry has more constraints.

That's it. Two ops. No filter, no loop.

## How to enable (config example)

Add `use_multi_view_retriangulation: true` to your `bundle_adjustment_module`
yaml config:

```yaml
bundle_adjustment_module:
  _target_: gtsfm.bundle.bundle_adjustment.BundleAdjustmentOptimizer
  reproj_error_thresholds: [10, 5, 3]   # existing config — unchanged
  use_multi_view_retriangulation: true  # ← new flag, opt-in
  # Optional tuning (sensible defaults; only override if you know what you want):
  # mv_retri_min_track_length: 3
  # mv_retri_reproj_error_thresh: 10.0
```

The `MultiViewOptimizer` automatically threads its existing `tracks2d_graph`
through to BA — no caller changes needed.

## Validated results (IMC phototourism scenes, sift_gp_single_pt config)

| Scene          | num_matched, min_score | Without retri | With retri | Δ      |
|----------------|------------------------|---------------|------------|--------|
| British Museum | (100, 0.20)            | AUC@5 = 65.8  | AUC@5 = 69.5 | +3.7 |
| Brussels       | (100, 0.15)            | —             | AUC@5 = 79.5 | (vs GLOMAP-full 81.2) |
| Pantheon       | (15,  0.45)            | —             | AUC@5 = 79.9 | strong |

Wall-time: one extra BA pass (~10-15s on BM-scale).

## API summary

New public function:
  `multi_view_retriangulate_from_2d_tracks(gtsfm_data, tracks_2d, ...) -> GtsfmData`

New `BundleAdjustmentOptimizer.__init__` flags (all default off / sensible):
  `use_multi_view_retriangulation: bool = False`
  `mv_retri_min_track_length: int = 3`
  `mv_retri_reproj_error_thresh: float = 10.0`

Plumbing:
  - `tracks_2d: Optional[List[SfmTrack2d]]` kwarg added to `_run_ba_and_evaluate`
    and `create_computation_graph`
  - `MultiViewOptimizer` passes its existing `tracks2d_graph` to
    `ba_optimizer.create_computation_graph` (single-line change)

## How to review

1. Read `multi_view_retriangulate_from_2d_tracks` (~80 lines, single loop, well-commented).
2. Read the `if self._use_multi_view_retriangulation:` block in `_run_ba_and_evaluate` (~40 lines, the integration point).
3. Verify new `__init__` flags default off and `tracks_2d` defaults None (backwards compat).
4. Verify the `MultiViewOptimizer` change (one line: `tracks_2d=tracks2d_graph,` added to `ba_optimizer.create_computation_graph`).

Total diff: 165 lines added, 1 deleted, across 2 files.

## What this PR does NOT add (saved for follow-ups)

- **2-view densification** (visualization-only post-BA point-cloud densification).
  Adds ~24% point cloud density on Sacre Coeur with no AUC impact. Will follow
  as a separate PR if needed for visualization use cases.
- **Full GLOMAP `IterativeRetriangulateAndRefine` port** (DeleteAllPoints +
  per-image `TriangulateImage` + 5-round refinement loop). Implemented and
  validated correct on synthetic tests in the `gp-glomap-parity` experimental
  branch but ~15x slower than C++/Ceres in Python/GTSAM, so uneconomical to ship.
  Recoverable from git history if anyone pursues a Ceres-backed reimplementation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
kathirgounder and others added 4 commits April 27, 2026 19:59
After merging master's BundleAdjustmentOptions / new __init__ params, both
the pre-merge default (1e-8) and master's new default (0.0) ended up in the
__init__ signature. The duplicate kwarg made the second definition (0.0)
silently override the first — kept that one and removed the stale 1e-8 line.

Also moved the retri opt-in flags to the bottom of the __init__ signature
so the master-side BA params group cleanly first.

Verified:
  - factor_weight_outlier_threshold: 0.0 (matches master default)
  - use_multi_view_retriangulation: False (default off — backwards compat)
  - opt-in works: BundleAdjustmentOptimizer(use_multi_view_retriangulation=True)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The function uses Point3dInitializer.triangulate, which samples camera PAIRS
as RANSAC hypotheses (2-view DLT), scores inliers across the whole track,
then computes the final 3D point via multi-view DLT on inliers. Calling it
"multi-view RANSAC" oversold what the sampling step actually does.

Also dropped the 'GLOMAP-style' framing (this is gtsfm code, no need to
reference an experimental branch's lineage).
Comment thread gtsfm/bundle/bundle_adjustment.py Outdated
Akshay's review feedback (PR borglab#1111):
- max_num_hypotheses was hardcoded at 100 — now configurable via
  mv_retri_max_num_hypotheses on BundleAdjustmentOptimizer.__init__.
  Default kept at 100; users can lower for long tracks where the cap binds.

- Re: mv_retri_reproj_error_thresh = 10.0 'seems way too high':
  Kept default at 10.0. This threshold serves both the RANSAC inlier check
  AND the final-track-acceptance gate (Point3dInitializer.triangulate
  rejects the entire track if any inlier measurement exceeds the threshold).
  10.0 was validated to give the +3.7 AUC@5 lift on BM, and matches the
  data_association_module's existing convention. GLOMAP's analogous
  TriangulatorOptions actually uses 15.0px for complete/merge max reproj
  error during build phase, so 10.0 is on the tighter side of the regime.
  Made the threshold tunable via mv_retri_reproj_error_thresh — users can
  lower for stricter track acceptance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kathirgounder
kathirgounder marked this pull request as ready for review April 28, 2026 02:23
Removed the loop for BA steps and replaced it with a single call to run_ba, simplifying the bundle adjustment process.
@kathirgounder
kathirgounder merged commit b61e7e3 into borglab:master Apr 28, 2026
18 checks passed
kathirgounder added a commit to kathirgounder/gtsfm that referenced this pull request Apr 28, 2026
Mirrors the retri stage merged in borglab#1111 (BundleAdjustmentOptimizer) at the
cluster-BA layer so VGGT-frontend pipelines also recover tracks dropped
earlier. After the initial cluster BA + filter, optionally re-triangulates
the union-find 2D tracks against the post-BA cameras (multi_view_retriangulate_from_2d_tracks)
and runs a second cluster BA on the augmented set.

Default off (use_multi_view_retriangulation: false) → no behavior change.

## Diff

- `_run_cluster_ba` (shared by ClusterVGGT and ClusterVGGTWithFrontend) gains
  `tracks_2d` and `use_multi_view_retriangulation` kwargs. When the flag is on
  and tracks_2d is supplied, runs retri + a second BA + post-BA reproj filter.
- `ClusterVGGTWithFrontend.__init__` adds `use_multi_view_retriangulation: bool = False`
  and passes the union-find tracks_2d_graph through to `_run_cluster_ba`.
- 3 yamls (vggt_sift_frontend_megaloc, _phototourism, vggt_unified_frontend_megaloc)
  expose the flag with default false.

## How to enable

```yaml
cluster_optimizer:
  optimizer:
    _target_: gtsfm.cluster_optimizer.VggtWithFrontend
    ...
    use_multi_view_retriangulation: true
```

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants