Skip to content

Commit 71a00f5

Browse files
committed
feat: add best sparse model flag
Add BestSparseModel utility to detect and cache the sparse model with the most registered images, allowing intrinsics refinement and transforms.json generation to reliably use the best reconstruction.
1 parent 50e0e3c commit 71a00f5

File tree

2 files changed

+73
-2
lines changed

2 files changed

+73
-2
lines changed

nerfstudio/process_data/colmap_converter_to_nerfstudio_dataset.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ class ColmapConverterToNerfstudioDataset(BaseConverterToNerfstudioDataset):
4444
refine_intrinsics: bool = True
4545
"""If True, do bundle adjustment to refine intrinsics.
4646
Only works with colmap sfm_tool"""
47+
use_best_sparse_model: bool = True
48+
"""If True, use the best sparse model to refine intrinsics and save the camera transformations.
49+
Only works with colmap sfm_tool"""
4750
feature_type: Literal[
4851
"any",
4952
"sift",
@@ -111,6 +114,8 @@ def default_colmap_path() -> Path:
111114

112115
@property
113116
def absolute_colmap_model_path(self) -> Path:
117+
if self.use_best_sparse_model:
118+
return colmap_utils.BestSparseModel.get_model_path(self.absolute_colmap_path)
114119
return self.output_dir / self.colmap_model_path
115120

116121
@property
@@ -218,6 +223,7 @@ def _run_colmap(self, mask_path: Optional[Path] = None):
218223
verbose=self.verbose,
219224
matching_method=self.matching_method,
220225
refine_intrinsics=self.refine_intrinsics,
226+
use_best_sparse_model=self.use_best_sparse_model,
221227
colmap_cmd=self.colmap_cmd,
222228
)
223229
elif sfm_tool == "hloc":

nerfstudio/process_data/colmap_utils.py

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ def run_colmap(
9898
verbose: bool = False,
9999
matching_method: Literal["vocab_tree", "exhaustive", "sequential"] = "vocab_tree",
100100
refine_intrinsics: bool = True,
101+
use_best_sparse_model: bool = True,
101102
colmap_cmd: str = "colmap",
102103
) -> None:
103104
"""Runs COLMAP on the images.
@@ -111,6 +112,7 @@ def run_colmap(
111112
verbose: If True, logs the output of the command.
112113
matching_method: Matching method to use.
113114
refine_intrinsics: If True, refine intrinsics.
115+
use_best_sparse_model: If True, refine intrinsics of the best sparse model.
114116
colmap_cmd: Path to the COLMAP executable.
115117
"""
116118

@@ -173,11 +175,13 @@ def run_colmap(
173175
CONSOLE.log("[bold green]:tada: Done COLMAP bundle adjustment.")
174176

175177
if refine_intrinsics:
178+
sparse_model = "0" if not use_best_sparse_model else BestSparseModel.get_model(colmap_dir)
179+
176180
with status(msg="[bold yellow]Refine intrinsics...", spinner="dqpb", verbose=verbose):
177181
bundle_adjuster_cmd = [
178182
f"{colmap_cmd} bundle_adjuster",
179-
f"--input_path {sparse_dir}/0",
180-
f"--output_path {sparse_dir}/0",
183+
f"--input_path {sparse_dir}/{sparse_model}",
184+
f"--output_path {sparse_dir}/{sparse_model}",
181185
"--BundleAdjustment.refine_principal_point 1",
182186
]
183187
run_command(" ".join(bundle_adjuster_cmd), verbose=verbose)
@@ -712,3 +716,64 @@ def create_ply_from_colmap(
712716
x, y, z = coord
713717
r, g, b = color
714718
f.write(f"{x:8f} {y:8f} {z:8f} {r} {g} {b}\n")
719+
720+
721+
class BestSparseModel:
722+
"""
723+
Utility class to find and cache the best COLMAP sparse model
724+
from a given directory. Provides both the model name and full path.
725+
726+
The best model is defined as the one with the largest number of registered images.
727+
"""
728+
729+
_cached_model = None # Cached name of the best sparse model
730+
731+
@staticmethod
732+
def _find_best_model(colmap_dir: Path) -> str:
733+
"""
734+
Find the best sparse model in the COLMAP directory.
735+
736+
Args:
737+
colmap_dir (Path): Path to the COLMAP project directory containing 'sparse/'.
738+
739+
Returns:
740+
str: Name of the best sparse model directory.
741+
742+
Raises:
743+
ValueError: If no valid sparse models are found.
744+
"""
745+
sparse_dir = colmap_dir / "sparse"
746+
models = [m for m in sorted(sparse_dir.glob("*")) if (m / "images.bin").exists()]
747+
if not models:
748+
raise ValueError(f"No valid COLMAP sparse models found in {sparse_dir}")
749+
750+
best_model = max(models, key=lambda m: len(read_images_binary(m / "images.bin")))
751+
return best_model.name
752+
753+
@staticmethod
754+
def get_model(colmap_dir: Path) -> str:
755+
"""
756+
Get the name of the best sparse model, using cache if available.
757+
758+
Args:
759+
colmap_dir (Path): Path to the COLMAP project directory containing 'sparse/'.
760+
761+
Returns:
762+
str: Name of the best sparse model.
763+
"""
764+
if not BestSparseModel._cached_model:
765+
BestSparseModel._cached_model = BestSparseModel._find_best_model(colmap_dir)
766+
return BestSparseModel._cached_model
767+
768+
@staticmethod
769+
def get_model_path(colmap_dir: Path) -> Path:
770+
"""
771+
Get the full path to the best sparse model, using cache if available.
772+
773+
Args:
774+
colmap_dir (Path): Path to the COLMAP project directory containing 'sparse/'.
775+
776+
Returns:
777+
Path: Full path to the best sparse model directory.
778+
"""
779+
return colmap_dir / "sparse" / BestSparseModel.get_model(colmap_dir)

0 commit comments

Comments
 (0)