Update COLMAP integration for 4.1.0: add GLOMAP mapper and matching type options
#1
by cllim118 - opened
- .gitignore +5 -1
- colmap_mapper.py +0 -85
- colmap_mapper.sh +43 -0
- colmap_matcher.py +0 -111
- colmap_matcher.sh +155 -0
- colmap_reconstruction.sh +69 -0
- colmap_to_vslamlab.py +71 -58
- colmap_utilities.py +0 -114
- create_colmap_mask_dir.py +0 -61
- get_calibration.py +22 -34
- vslamlab_colmap.py +0 -111
.gitignore
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
LightGlue/
|
| 2 |
-
__pycache__/
|
|
|
|
| 1 |
+
vocab_tree_flickr100K_words1M.bin
|
| 2 |
+
vocab_tree_flickr100K_words256K.bin
|
| 3 |
+
vocab_tree_flickr100K_words32K.bin
|
| 4 |
+
|
| 5 |
LightGlue/
|
| 6 |
+
__pycache__/
|
colmap_mapper.py
DELETED
|
@@ -1,85 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Module: VSLAM-LAB - Baselines - colmap - colmap_mapper.py
|
| 3 |
-
- Author: Alejandro Fontan Villacampa
|
| 4 |
-
- Assisted by: Claude (Fable 5.1)
|
| 5 |
-
- Version: 2.0
|
| 6 |
-
- Created: 2024-07-12
|
| 7 |
-
- Updated: 2026-10-03
|
| 8 |
-
- License: GPLv3 License
|
| 9 |
-
|
| 10 |
-
Mapping stage of the colmap pipeline (Python port of colmap_mapper.sh): runs COLMAP's incremental
|
| 11 |
-
mapper or GLOMAP's global mapper on the database, picks the sub-model with the most registered
|
| 12 |
-
images and exports it as TXT into the colmap folder. Called by vslamlab_colmap.py.
|
| 13 |
-
"""
|
| 14 |
-
|
| 15 |
-
import re
|
| 16 |
-
from pathlib import Path
|
| 17 |
-
|
| 18 |
-
from colmap_utilities import colmap_camera, run
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
def registered_images(model_dir: Path) -> int:
|
| 22 |
-
out = run(["colmap", "model_analyzer", "--path", str(model_dir)], capture=True)
|
| 23 |
-
match = re.search(r"Registered images: (\d+)", out)
|
| 24 |
-
return int(match.group(1)) if match else 0
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
def select_best_model(exp_folder_colmap: Path) -> tuple[Path, int]:
|
| 28 |
-
"""COLMAP may split the scene into several sub-models (<exp_folder_colmap>/0, /1, ...), and the
|
| 29 |
-
largest one is not necessarily /0. Returns the sub-model with the most registered images and
|
| 30 |
-
records the choice in <exp_folder_colmap>/best_model for the rest of the pipeline (gui, ...)."""
|
| 31 |
-
best_model, best_num_images = None, -1
|
| 32 |
-
for model_dir in sorted(p for p in exp_folder_colmap.iterdir() if p.is_dir() and p.name.isdigit()):
|
| 33 |
-
if not (model_dir / "images.bin").is_file():
|
| 34 |
-
continue
|
| 35 |
-
num_images = registered_images(model_dir)
|
| 36 |
-
print(f" sub-model {model_dir.name}: {num_images} registered images")
|
| 37 |
-
if num_images > best_num_images:
|
| 38 |
-
best_model, best_num_images = model_dir, num_images
|
| 39 |
-
if best_model is None or best_num_images == 0: # glomap writes an empty /0 when its pose graph is empty
|
| 40 |
-
raise SystemExit(f" no reconstruction produced (no sub-model with registered images in {exp_folder_colmap})")
|
| 41 |
-
(exp_folder_colmap / "best_model").write_text(f"{best_model}\n")
|
| 42 |
-
return best_model, best_num_images
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
def run_mapper(exp_folder_colmap: Path, rgb_path: Path, calibration_yaml: Path, camera_name: str,
|
| 46 |
-
mapper_type: str, optimize_intrinsics: int) -> Path:
|
| 47 |
-
"""Reconstruct from <exp_folder_colmap>/colmap_database.db; returns the best sub-model folder."""
|
| 48 |
-
print("Executing colmap_mapper ...")
|
| 49 |
-
|
| 50 |
-
calibration_model, _, _ = colmap_camera(calibration_yaml, camera_name)
|
| 51 |
-
print(f" camera model : {calibration_model}")
|
| 52 |
-
|
| 53 |
-
# optimize_intrinsics (0/1, default 1): whether bundle adjustment refines the camera intrinsics.
|
| 54 |
-
# With 1, focal length and distortion (extra params) are refined and the principal point stays
|
| 55 |
-
# fixed, matching COLMAP's own defaults; with 0 the intrinsics from the calibration yaml are kept
|
| 56 |
-
# as given. An 'unknown' calibration model has no intrinsics to keep (the matcher started COLMAP
|
| 57 |
-
# from a guess), so it always refines regardless of the flag.
|
| 58 |
-
if calibration_model == "unknown" and optimize_intrinsics != 1:
|
| 59 |
-
print(f" WARNING: optimize_intrinsics={optimize_intrinsics} ignored: camera model is 'unknown' (no intrinsics to keep fixed), refining intrinsics")
|
| 60 |
-
optimize_intrinsics = 1
|
| 61 |
-
refine = "1" if optimize_intrinsics == 1 else "0"
|
| 62 |
-
ba_refine_focal_length, ba_refine_principal_point, ba_refine_extra_params = refine, "0", refine
|
| 63 |
-
print(f" optimize_intrinsics: {optimize_intrinsics} (ba_refine_focal_length={ba_refine_focal_length}, "
|
| 64 |
-
f"ba_refine_principal_point={ba_refine_principal_point}, ba_refine_extra_params={ba_refine_extra_params})")
|
| 65 |
-
|
| 66 |
-
database = exp_folder_colmap / "colmap_database.db"
|
| 67 |
-
if mapper_type == "glomap":
|
| 68 |
-
print(" global mapper (GLOMAP) ...")
|
| 69 |
-
command, prefix = "global_mapper", "GlobalMapper"
|
| 70 |
-
else:
|
| 71 |
-
print(" colmap mapper (COLMAP) ...")
|
| 72 |
-
command, prefix = "mapper", "Mapper"
|
| 73 |
-
run(["colmap", command,
|
| 74 |
-
"--database_path", str(database),
|
| 75 |
-
"--image_path", str(rgb_path),
|
| 76 |
-
"--output_path", str(exp_folder_colmap),
|
| 77 |
-
f"--{prefix}.ba_refine_focal_length", ba_refine_focal_length,
|
| 78 |
-
f"--{prefix}.ba_refine_principal_point", ba_refine_principal_point,
|
| 79 |
-
f"--{prefix}.ba_refine_extra_params", ba_refine_extra_params])
|
| 80 |
-
|
| 81 |
-
best_model, best_num_images = select_best_model(exp_folder_colmap)
|
| 82 |
-
|
| 83 |
-
print(f" colmap model_converter (sub-model {best_model.name}, {best_num_images} images) ...")
|
| 84 |
-
run(["colmap", "model_converter", "--input_path", str(best_model), "--output_path", str(exp_folder_colmap), "--output_type", "TXT"])
|
| 85 |
-
return best_model
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
colmap_mapper.sh
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
echo "Executing colmap_mapper.sh ..."
|
| 3 |
+
|
| 4 |
+
sequence_path="$1"
|
| 5 |
+
exp_folder="$2"
|
| 6 |
+
exp_id="$3"
|
| 7 |
+
settings_yaml="$4"
|
| 8 |
+
calibration_yaml="$5"
|
| 9 |
+
rgb_csv="$6"
|
| 10 |
+
camera_name="$7"
|
| 11 |
+
|
| 12 |
+
exp_folder_colmap="${exp_folder}/colmap_${exp_id}"
|
| 13 |
+
rgb_dir="${camera_name}"
|
| 14 |
+
rgb_path="${sequence_path}/${rgb_dir}"
|
| 15 |
+
|
| 16 |
+
read -r calibration_model more_ <<< $(python3 Baselines/colmap/get_calibration.py "$calibration_yaml" "$camera_name")
|
| 17 |
+
echo " camera model : $calibration_model"
|
| 18 |
+
ba_refine_focal_length="0"
|
| 19 |
+
ba_refine_principal_point="0"
|
| 20 |
+
ba_refine_extra_params="0"
|
| 21 |
+
if [ "${calibration_model}" == "unknown" ]
|
| 22 |
+
then
|
| 23 |
+
ba_refine_focal_length="1"
|
| 24 |
+
ba_refine_principal_point="1"
|
| 25 |
+
ba_refine_extra_params="1"
|
| 26 |
+
fi
|
| 27 |
+
|
| 28 |
+
echo " colmap mapper ..."
|
| 29 |
+
database="${exp_folder_colmap}/colmap_database.db"
|
| 30 |
+
|
| 31 |
+
colmap mapper \
|
| 32 |
+
--database_path ${database} \
|
| 33 |
+
--image_path ${rgb_path} \
|
| 34 |
+
--output_path ${exp_folder_colmap} \
|
| 35 |
+
--Mapper.ba_refine_focal_length ${ba_refine_focal_length} \
|
| 36 |
+
--Mapper.ba_refine_principal_point ${ba_refine_principal_point} \
|
| 37 |
+
--Mapper.ba_refine_extra_params ${ba_refine_extra_params}
|
| 38 |
+
|
| 39 |
+
echo " colmap model_converter ..."
|
| 40 |
+
colmap model_converter \
|
| 41 |
+
--input_path ${exp_folder_colmap}/0 --output_path ${exp_folder_colmap} --output_type TXT
|
| 42 |
+
|
| 43 |
+
|
colmap_matcher.py
DELETED
|
@@ -1,111 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Module: VSLAM-LAB - Baselines - colmap - colmap_matcher.py
|
| 3 |
-
- Author: Alejandro Fontan Villacampa
|
| 4 |
-
- Assisted by: Claude (Fable 5.1)
|
| 5 |
-
- Version: 2.0
|
| 6 |
-
- Created: 2024-07-12
|
| 7 |
-
- Updated: 2026-10-03
|
| 8 |
-
- License: GPLv3 License
|
| 9 |
-
|
| 10 |
-
Feature extraction and matching stage of the colmap pipeline (Python port of colmap_matcher.sh):
|
| 11 |
-
creates the COLMAP database, extracts features for the frames listed in the experiment's rgb csv
|
| 12 |
-
(optionally masked) and matches them exhaustively or sequentially. Called by vslamlab_colmap.py.
|
| 13 |
-
"""
|
| 14 |
-
|
| 15 |
-
from pathlib import Path
|
| 16 |
-
|
| 17 |
-
from colmap_utilities import colmap_camera, detect_gpus, run
|
| 18 |
-
from create_colmap_image_list import create_colmap_image_list
|
| 19 |
-
from create_colmap_mask_dir import create_colmap_mask_dir
|
| 20 |
-
|
| 21 |
-
# matching_type -> (FeatureExtraction.type, FeatureMatching.type)
|
| 22 |
-
MATCHING_TYPES: dict[str, tuple[str, str]] = {
|
| 23 |
-
'sift_bruteforce': ('SIFT', 'SIFT_BRUTEFORCE'),
|
| 24 |
-
'sift_lightglue': ('SIFT', 'SIFT_LIGHTGLUE'),
|
| 25 |
-
'aliked_bruteforce': ('ALIKED_N16ROT', 'ALIKED_BRUTEFORCE'),
|
| 26 |
-
'aliked_lightglue': ('ALIKED_N16ROT', 'ALIKED_LIGHTGLUE'),
|
| 27 |
-
}
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
def run_matcher(sequence_path: Path, exp_folder_colmap: Path, rgb_path: Path, rgb_csv: Path, calibration_yaml: Path,
|
| 31 |
-
camera_name: str, matcher_type: str, matching_type: str, use_gpu: int, use_mask: int) -> Path:
|
| 32 |
-
"""Build <exp_folder_colmap>/colmap_database.db with features and matches; returns its path."""
|
| 33 |
-
print("\nExecuting colmap_matcher ...")
|
| 34 |
-
|
| 35 |
-
if matching_type not in MATCHING_TYPES:
|
| 36 |
-
raise SystemExit(f"Unknown matching_type: {matching_type}")
|
| 37 |
-
feature_extraction_type, feature_matching_type = MATCHING_TYPES[matching_type]
|
| 38 |
-
|
| 39 |
-
gpu_index_list, num_threads = detect_gpus(use_gpu)
|
| 40 |
-
|
| 41 |
-
calibration_model, colmap_camera_model, camera_params = colmap_camera(calibration_yaml, camera_name)
|
| 42 |
-
|
| 43 |
-
colmap_image_list = exp_folder_colmap / "colmap_image_list.txt"
|
| 44 |
-
create_colmap_image_list(str(rgb_csv), str(colmap_image_list), camera_name)
|
| 45 |
-
|
| 46 |
-
database = exp_folder_colmap / "colmap_database.db"
|
| 47 |
-
database.unlink(missing_ok=True)
|
| 48 |
-
run(["colmap", "database_creator", "--database_path", str(database)])
|
| 49 |
-
|
| 50 |
-
print(f" colmap feature_extractor ({feature_extraction_type}) ...")
|
| 51 |
-
print(f" gpu_index: {gpu_index_list}")
|
| 52 |
-
print(f" camera model : {calibration_model} (colmap: {colmap_camera_model})")
|
| 53 |
-
camera_params_args: list[str] = []
|
| 54 |
-
if camera_params:
|
| 55 |
-
print(f" camera params: {camera_params}")
|
| 56 |
-
camera_params_args = ["--ImageReader.camera_params", camera_params]
|
| 57 |
-
|
| 58 |
-
# Masks (use_mask=1): the rgb csv's path_mask_<i> column (1 = usable pixel, 0 = masked out - no
|
| 59 |
-
# features are extracted where the mask is 0). One shared mask (refrax) goes through
|
| 60 |
-
# --ImageReader.camera_mask_path, per-frame masks (mask2former, datasets shipping masks) through
|
| 61 |
-
# a directory of <image name>.png symlinks and --ImageReader.mask_path.
|
| 62 |
-
mask_args: list[str] = []
|
| 63 |
-
if use_mask == 1:
|
| 64 |
-
mask_spec = create_colmap_mask_dir(str(rgb_csv), camera_name, str(sequence_path), str(exp_folder_colmap / "colmap_masks"))
|
| 65 |
-
if mask_spec.startswith("camera_mask:"):
|
| 66 |
-
mask_args = ["--ImageReader.camera_mask_path", mask_spec[len("camera_mask:"):]]
|
| 67 |
-
print(f" mask: shared camera mask {mask_spec[len('camera_mask:'):]}")
|
| 68 |
-
elif mask_spec.startswith("mask_dir:"):
|
| 69 |
-
mask_args = ["--ImageReader.mask_path", mask_spec[len("mask_dir:"):]]
|
| 70 |
-
print(f" mask: per-frame masks in {mask_spec[len('mask_dir:'):]}")
|
| 71 |
-
else:
|
| 72 |
-
print(f" mask: none available for {camera_name} in {rgb_csv}")
|
| 73 |
-
else:
|
| 74 |
-
print(f" mask: disabled (use_mask={use_mask})")
|
| 75 |
-
|
| 76 |
-
run(["colmap", "feature_extractor",
|
| 77 |
-
"--database_path", str(database),
|
| 78 |
-
"--image_path", str(rgb_path),
|
| 79 |
-
"--image_list_path", str(colmap_image_list),
|
| 80 |
-
"--ImageReader.camera_model", colmap_camera_model,
|
| 81 |
-
"--ImageReader.single_camera", "1",
|
| 82 |
-
"--ImageReader.single_camera_per_folder", "1",
|
| 83 |
-
"--FeatureExtraction.type", feature_extraction_type,
|
| 84 |
-
"--FeatureExtraction.use_gpu", str(use_gpu),
|
| 85 |
-
"--FeatureExtraction.gpu_index", gpu_index_list,
|
| 86 |
-
"--FeatureExtraction.num_threads", str(num_threads),
|
| 87 |
-
*camera_params_args, *mask_args])
|
| 88 |
-
|
| 89 |
-
matching_args = ["--database_path", str(database),
|
| 90 |
-
"--FeatureMatching.type", feature_matching_type,
|
| 91 |
-
"--FeatureMatching.use_gpu", str(use_gpu),
|
| 92 |
-
"--FeatureMatching.gpu_index", gpu_index_list,
|
| 93 |
-
"--FeatureMatching.num_threads", str(num_threads)]
|
| 94 |
-
|
| 95 |
-
if matcher_type == "exhaustive":
|
| 96 |
-
print(f" colmap exhaustive_matcher ({feature_matching_type}) ...")
|
| 97 |
-
print(f" gpu_index: {gpu_index_list}")
|
| 98 |
-
run(["colmap", "exhaustive_matcher", *matching_args])
|
| 99 |
-
elif matcher_type == "sequential":
|
| 100 |
-
# Loop detection uses COLMAP's default vocabulary tree for the feature type (a FAISS index
|
| 101 |
-
# per SIFT / ALIKED / LoMa, auto-downloaded once into ~/.cache/colmap). The legacy
|
| 102 |
-
# flickr100K FLANN trees are rejected by COLMAP >= 3.12 ("Failed to read faiss index").
|
| 103 |
-
print(f" colmap sequential_matcher ({feature_matching_type}) ...")
|
| 104 |
-
print(f" Vocabulary Tree: COLMAP default for {feature_extraction_type} (cached in ~/.cache/colmap)")
|
| 105 |
-
print(f" gpu_index: {gpu_index_list}")
|
| 106 |
-
run(["colmap", "sequential_matcher", *matching_args,
|
| 107 |
-
"--SequentialMatching.loop_detection", "1"])
|
| 108 |
-
else:
|
| 109 |
-
raise SystemExit(f"Unknown matcher_type: {matcher_type}")
|
| 110 |
-
|
| 111 |
-
return database
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
colmap_matcher.sh
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
echo ""
|
| 3 |
+
echo "Executing colmap_matcher.sh ..."
|
| 4 |
+
|
| 5 |
+
sequence_path="$1"
|
| 6 |
+
exp_folder="$2"
|
| 7 |
+
exp_id="$3"
|
| 8 |
+
settings_yaml="$4"
|
| 9 |
+
calibration_yaml="$5"
|
| 10 |
+
rgb_csv="$6"
|
| 11 |
+
matcher_type="$7"
|
| 12 |
+
use_gpu="$8"
|
| 13 |
+
camera_name="$9"
|
| 14 |
+
|
| 15 |
+
exp_folder_colmap="${exp_folder}/colmap_${exp_id}"
|
| 16 |
+
rgb_dir=$(awk -F, 'NR==2 { split($2,a,"/"); print a[1]; exit }' "$rgb_csv")
|
| 17 |
+
rgb_path="${sequence_path}/${rgb_dir}"
|
| 18 |
+
|
| 19 |
+
# Get calibration model
|
| 20 |
+
read -r calibration_model more_ <<< $(python3 Baselines/colmap/get_calibration.py "$calibration_yaml" "$camera_name")
|
| 21 |
+
|
| 22 |
+
# Create colmap image list
|
| 23 |
+
colmap_image_list="${exp_folder_colmap}/colmap_image_list.txt"
|
| 24 |
+
python3 Baselines/colmap/create_colmap_image_list.py "$rgb_csv" "$colmap_image_list" "$camera_name"
|
| 25 |
+
|
| 26 |
+
# Create Colmap Database
|
| 27 |
+
database="${exp_folder_colmap}/colmap_database.db"
|
| 28 |
+
rm -rf ${database}
|
| 29 |
+
colmap database_creator --database_path ${database}
|
| 30 |
+
|
| 31 |
+
# Feature extractor
|
| 32 |
+
echo " colmap feature_extractor ..."
|
| 33 |
+
|
| 34 |
+
if [ "${calibration_model}" == "unknown" ]
|
| 35 |
+
then
|
| 36 |
+
echo " camera model : $calibration_model"
|
| 37 |
+
colmap feature_extractor \
|
| 38 |
+
--database_path ${database} \
|
| 39 |
+
--image_path ${rgb_path} \
|
| 40 |
+
--image_list_path ${colmap_image_list} \
|
| 41 |
+
--ImageReader.camera_model SIMPLE_PINHOLE \
|
| 42 |
+
--ImageReader.single_camera 1 \
|
| 43 |
+
--ImageReader.single_camera_per_folder 1 \
|
| 44 |
+
--FeatureExtraction.use_gpu ${use_gpu}
|
| 45 |
+
fi
|
| 46 |
+
|
| 47 |
+
if [ "${calibration_model}" == "pinhole" ]
|
| 48 |
+
then
|
| 49 |
+
read -r calibration_model fx fy cx cy <<< $(python3 Baselines/colmap/get_calibration.py "$calibration_yaml" "$camera_name")
|
| 50 |
+
echo " camera model : $calibration_model"
|
| 51 |
+
echo " fx: $fx , fy: $fy , cx: $cx , cy: $cy"
|
| 52 |
+
colmap feature_extractor \
|
| 53 |
+
--database_path ${database} \
|
| 54 |
+
--image_path ${rgb_path} \
|
| 55 |
+
--image_list_path ${colmap_image_list} \
|
| 56 |
+
--ImageReader.camera_model PINHOLE \
|
| 57 |
+
--ImageReader.single_camera 1 \
|
| 58 |
+
--ImageReader.single_camera_per_folder 1 \
|
| 59 |
+
--FeatureExtraction.use_gpu ${use_gpu} \
|
| 60 |
+
--ImageReader.camera_params "${fx},${fy},${cx},${cy}"
|
| 61 |
+
fi
|
| 62 |
+
|
| 63 |
+
if [ "${calibration_model}" == "radtan4" ]
|
| 64 |
+
then
|
| 65 |
+
read -r calibration_model fx fy cx cy k1 k2 p1 p2 <<< $(python3 Baselines/colmap/get_calibration.py "$calibration_yaml" "$camera_name")
|
| 66 |
+
echo " camera model : $calibration_model"
|
| 67 |
+
echo " fx: $fx , fy: $fy , cx: $cx , cy: $cy"
|
| 68 |
+
echo " k1: $k1 , k2: $k2 , p1: $p1 , p2: $p2"
|
| 69 |
+
colmap feature_extractor \
|
| 70 |
+
--database_path ${database} \
|
| 71 |
+
--image_path ${rgb_path} \
|
| 72 |
+
--image_list_path ${colmap_image_list} \
|
| 73 |
+
--ImageReader.camera_model "OPENCV" \
|
| 74 |
+
--ImageReader.single_camera 1 \
|
| 75 |
+
--ImageReader.single_camera_per_folder 0 \
|
| 76 |
+
--FeatureExtraction.use_gpu ${use_gpu} \
|
| 77 |
+
--ImageReader.camera_params "${fx},${fy},${cx},${cy},${k1},${k2},${p1},${p2}"
|
| 78 |
+
fi
|
| 79 |
+
|
| 80 |
+
if [ "${calibration_model}" == "radtan5" ]
|
| 81 |
+
then
|
| 82 |
+
read -r calibration_model fx fy cx cy k1 k2 p1 p2 k3 <<< $(python3 Baselines/colmap/get_calibration.py "$calibration_yaml" "$camera_name")
|
| 83 |
+
echo " camera model : $calibration_model"
|
| 84 |
+
echo " fx: $fx , fy: $fy , cx: $cx , cy: $cy"
|
| 85 |
+
echo " k1: $k1 , k2: $k2 , p1: $p1 , p2: $p2, k3: $k3"
|
| 86 |
+
colmap feature_extractor \
|
| 87 |
+
--database_path ${database} \
|
| 88 |
+
--image_path ${rgb_path} \
|
| 89 |
+
--image_list_path ${colmap_image_list} \
|
| 90 |
+
--ImageReader.camera_model "FULL_OPENCV" \
|
| 91 |
+
--ImageReader.single_camera 1 \
|
| 92 |
+
--ImageReader.single_camera_per_folder 1 \
|
| 93 |
+
--FeatureExtraction.use_gpu ${use_gpu} \
|
| 94 |
+
--ImageReader.camera_params "${fx},${fy},${cx},${cy},${k1},${k2},${p1},${p2},${k3},0,0,0"
|
| 95 |
+
fi
|
| 96 |
+
|
| 97 |
+
if [ "${calibration_model}" == "equid4" ]
|
| 98 |
+
then
|
| 99 |
+
read -r calibration_model fx fy cx cy k1 k2 k3 k4 <<< $(python3 Baselines/colmap/get_calibration.py "$calibration_yaml" "$camera_name")
|
| 100 |
+
echo " camera model : $calibration_model"
|
| 101 |
+
echo " fx: $fx , fy: $fy , cx: $cx , cy: $cy"
|
| 102 |
+
echo " k1: $k1 , k2: $k2 , k3: $k3 , k4: $k4"
|
| 103 |
+
colmap feature_extractor \
|
| 104 |
+
--database_path ${database} \
|
| 105 |
+
--image_path ${rgb_path} \
|
| 106 |
+
--image_list_path ${colmap_image_list} \
|
| 107 |
+
--ImageReader.camera_model "OPENCV_FISHEYE"\
|
| 108 |
+
--ImageReader.single_camera 1 \
|
| 109 |
+
--ImageReader.single_camera_per_folder 1 \
|
| 110 |
+
--FeatureExtraction.use_gpu ${use_gpu} \
|
| 111 |
+
--ImageReader.camera_params "${fx},${fy},${cx},${cy},${k1},${k2},${k3},${k4}"
|
| 112 |
+
fi
|
| 113 |
+
|
| 114 |
+
# Exhaustive Feature Matcher
|
| 115 |
+
if [ "${matcher_type}" == "exhaustive" ]
|
| 116 |
+
then
|
| 117 |
+
echo " colmap exhaustive_matcher ..."
|
| 118 |
+
colmap exhaustive_matcher \
|
| 119 |
+
--database_path ${database} \
|
| 120 |
+
--FeatureMatching.use_gpu ${use_gpu}
|
| 121 |
+
fi
|
| 122 |
+
|
| 123 |
+
# Sequential Feature Matcher
|
| 124 |
+
if [ "${matcher_type}" == "sequential" ]
|
| 125 |
+
then
|
| 126 |
+
num_rgb=$(( $(wc -l < "$rgb_csv") - 1 ))
|
| 127 |
+
|
| 128 |
+
# Pick vocabulary tree based on the number of images
|
| 129 |
+
vocabulary_tree="Baselines/colmap/vocab_tree_faiss_flickr100K_words32K.bin"
|
| 130 |
+
if [ "$num_rgb" -gt 1000 ]; then
|
| 131 |
+
vocabulary_tree="Baselines/colmap/vocab_tree_faiss_flickr100K_words256K.bin"
|
| 132 |
+
fi
|
| 133 |
+
if [ "$num_rgb" -gt 10000 ]; then
|
| 134 |
+
vocabulary_tree="Baselines/colmap/vocab_tree_faiss_flickr100K_words1M.bin"
|
| 135 |
+
fi
|
| 136 |
+
|
| 137 |
+
echo " colmap sequential_matcher ..."
|
| 138 |
+
echo " Vocabulary Tree: $vocabulary_tree"
|
| 139 |
+
colmap sequential_matcher \
|
| 140 |
+
--database_path "${database}" \
|
| 141 |
+
--SequentialMatching.loop_detection 1 \
|
| 142 |
+
--SequentialMatching.vocab_tree_path ${vocabulary_tree} \
|
| 143 |
+
--FeatureMatching.use_gpu "${use_gpu}"
|
| 144 |
+
fi
|
| 145 |
+
|
| 146 |
+
# LightGlue Feature Matcher
|
| 147 |
+
if [ "${matcher_type}" == "custom" ]
|
| 148 |
+
then
|
| 149 |
+
colmap exhaustive_matcher \
|
| 150 |
+
--database_path ${database} \
|
| 151 |
+
--FeatureMatching.use_gpu ${use_gpu}
|
| 152 |
+
|
| 153 |
+
pixi run -e lightglue python3 Baselines/colmap/feature_matcher.py --database ${database} --rgb_path ${rgb_path} --rgb_csv ${rgb_csv}
|
| 154 |
+
fi
|
| 155 |
+
|
colmap_reconstruction.sh
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# Default values
|
| 4 |
+
matcher_type="exhaustive"
|
| 5 |
+
use_gpu="1"
|
| 6 |
+
verbose="0"
|
| 7 |
+
settings_yaml=""
|
| 8 |
+
sequence_path=""
|
| 9 |
+
exp_folder=""
|
| 10 |
+
exp_id=""
|
| 11 |
+
calibration_yaml=""
|
| 12 |
+
rgb_csv=""
|
| 13 |
+
camera_name="rgb_0"
|
| 14 |
+
|
| 15 |
+
# Function to split key-value pairs and assign them to variables
|
| 16 |
+
split_and_assign() {
|
| 17 |
+
local input=$1
|
| 18 |
+
local key=$(echo $input | cut -d':' -f1)
|
| 19 |
+
local value=$(echo $input | cut -d':' -f2-)
|
| 20 |
+
eval $key=$value
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
# Read Inputs
|
| 24 |
+
for ((i=1; i<=$#; i++)); do
|
| 25 |
+
split_and_assign "${!i}"
|
| 26 |
+
done
|
| 27 |
+
|
| 28 |
+
exp_id=$(printf "%05d" ${exp_id})
|
| 29 |
+
|
| 30 |
+
echo -e "\n================= Experiment Configuration ================="
|
| 31 |
+
echo " Sequence Path : $sequence_path"
|
| 32 |
+
echo " Experiment Folder : $exp_folder"
|
| 33 |
+
echo " Experiment ID : $exp_id"
|
| 34 |
+
echo " Verbose : $verbose"
|
| 35 |
+
echo " Matcher Type : $matcher_type"
|
| 36 |
+
echo " Use GPU : $use_gpu"
|
| 37 |
+
echo " Settings YAML : $settings_yaml"
|
| 38 |
+
echo " Calibration YAML : $calibration_yaml"
|
| 39 |
+
echo " RGB CSV : $rgb_csv"
|
| 40 |
+
echo " Camera Name : $camera_name"
|
| 41 |
+
echo "============================================================"
|
| 42 |
+
|
| 43 |
+
# Create folder to save colmap files
|
| 44 |
+
exp_folder_colmap="${exp_folder}/colmap_${exp_id}"
|
| 45 |
+
rm -rf "$exp_folder_colmap"
|
| 46 |
+
mkdir "$exp_folder_colmap"
|
| 47 |
+
|
| 48 |
+
# Run COLMAP scripts for matching and mapping
|
| 49 |
+
export QT_QPA_PLATFORM_PLUGIN_PATH="$CONDA_PREFIX/plugins/platforms"
|
| 50 |
+
colmap_args="$sequence_path $exp_folder $exp_id $settings_yaml $calibration_yaml $rgb_csv"
|
| 51 |
+
./Baselines/colmap/colmap_matcher.sh $colmap_args $matcher_type $use_gpu $camera_name
|
| 52 |
+
./Baselines/colmap/colmap_mapper.sh $colmap_args $camera_name
|
| 53 |
+
|
| 54 |
+
# Convert COLMAP outputs to a format suitable for VSLAM-LAB
|
| 55 |
+
python Baselines/colmap/colmap_to_vslamlab.py $sequence_path $exp_folder $exp_id $verbose $rgb_csv $camera_name
|
| 56 |
+
|
| 57 |
+
# Visualization with colmap gui
|
| 58 |
+
if [ "$verbose" -eq 1 ]; then
|
| 59 |
+
exp_folder_colmap="${exp_folder}/colmap_${exp_id}"
|
| 60 |
+
rgb_dir=$(awk -F, 'NR==2 { split($2,a,"/"); print a[1]; exit }' "$rgb_csv")
|
| 61 |
+
rgb_path="${sequence_path}/${rgb_dir}"
|
| 62 |
+
database="${exp_folder_colmap}/colmap_database.db"
|
| 63 |
+
colmap gui --import_path "${exp_folder_colmap}/0" --database_path ${database} --image_path ${rgb_path}
|
| 64 |
+
fi
|
| 65 |
+
|
| 66 |
+
# # Remove colmap data
|
| 67 |
+
# rm -rf ${exp_folder_colmap}
|
| 68 |
+
|
| 69 |
+
|
colmap_to_vslamlab.py
CHANGED
|
@@ -1,89 +1,102 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Module: VSLAM-LAB - Baselines - colmap - colmap_to_vslamlab.py
|
| 3 |
-
- Author: Alejandro Fontan Villacampa
|
| 4 |
-
- Assisted by: Claude (Fable 5.1)
|
| 5 |
-
- Version: 1.1
|
| 6 |
-
- Created: 2024-07-12
|
| 7 |
-
- Updated: 2026-10-03
|
| 8 |
-
- License: GPLv3 License
|
| 9 |
-
|
| 10 |
-
Writes <exp_folder>/<exp_id>_KeyFrameTrajectory.csv (VSLAM-LAB's TUM-style csv, camera-to-world
|
| 11 |
-
poses) from the TXT model <exp_folder>/colmap_<exp_id>/images.txt, taking each image's timestamp
|
| 12 |
-
from the experiment's rgb csv (COLMAP image ids are 1-based row indices of that csv, as the image
|
| 13 |
-
list was written in csv order). Importable (vslamlab_colmap.py) and runnable with the positional
|
| 14 |
-
arguments the old shell pipeline used.
|
| 15 |
-
"""
|
| 16 |
-
|
| 17 |
-
import os
|
| 18 |
-
import sys
|
| 19 |
-
from pathlib import Path
|
| 20 |
-
|
| 21 |
import numpy as np
|
| 22 |
-
import pandas as pd
|
| 23 |
from scipy.spatial.transform import Rotation as R
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
|
| 26 |
-
def get_colmap_keyframes(images_file: str | Path, number_of_header_lines: int = 4) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
| 27 |
-
"""(image ids, t_wc, q_wc xyzw) sorted by image id, quaternion signs made continuous."""
|
| 28 |
print(f"get_colmap_keyframes: {images_file}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
| 32 |
for _ in range(number_of_header_lines):
|
| 33 |
file.readline()
|
|
|
|
| 34 |
while True:
|
| 35 |
line1 = file.readline()
|
| 36 |
if not line1:
|
| 37 |
break
|
| 38 |
elements = line1.split()
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
-
file.readline()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
|
|
|
| 52 |
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
|
|
|
| 57 |
|
|
|
|
| 58 |
|
| 59 |
-
def write_trajectory_tum_format(file_name
|
| 60 |
print(f"writeTrajectoryTUMformat: {file_name}")
|
|
|
|
| 61 |
data = np.hstack((image_ts.reshape(-1, 1), t_wc, q_wc_xyzw))
|
| 62 |
data = data[data[:, 0].argsort()]
|
|
|
|
| 63 |
with open(file_name, 'w', newline='') as file:
|
| 64 |
file.write('ts (ns),tx (m),ty (m),tz (m),qx,qy,qz,qw\n')
|
| 65 |
for row in data:
|
| 66 |
file.write(','.join(f'{x:.15f}' for x in row) + '\n')
|
| 67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
|
|
|
|
|
|
| 73 |
|
| 74 |
-
|
| 75 |
-
images_file = Path(exp_folder) / f'colmap_{exp_id}' / 'images.txt'
|
| 76 |
-
image_id, t_wc, q_wc_xyzw = get_colmap_keyframes(images_file)
|
| 77 |
|
| 78 |
-
|
| 79 |
-
|
| 80 |
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
|
|
|
| 84 |
|
|
|
|
| 85 |
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
_, exp_folder_, exp_id_, _, rgb_csv_, camera_name_ = sys.argv[1:7]
|
| 89 |
-
colmap_to_vslamlab(exp_folder_, exp_id_, rgb_csv_, camera_name_)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import numpy as np
|
|
|
|
| 2 |
from scipy.spatial.transform import Rotation as R
|
| 3 |
+
import sys
|
| 4 |
+
import os
|
| 5 |
+
import pandas as pd
|
| 6 |
|
| 7 |
+
def get_colmap_keyframes(images_file, number_of_header_lines, verbose=False):
|
|
|
|
|
|
|
| 8 |
print(f"get_colmap_keyframes: {images_file}")
|
| 9 |
+
|
| 10 |
+
image_id = []
|
| 11 |
+
q_wc_xyzw = []
|
| 12 |
+
t_wc = []
|
| 13 |
|
| 14 |
+
with open(f"{images_file}", 'r') as file:
|
| 15 |
+
# Skip the header lines
|
| 16 |
for _ in range(number_of_header_lines):
|
| 17 |
file.readline()
|
| 18 |
+
|
| 19 |
while True:
|
| 20 |
line1 = file.readline()
|
| 21 |
if not line1:
|
| 22 |
break
|
| 23 |
elements = line1.split()
|
| 24 |
+
|
| 25 |
+
IMAGE_ID = int(elements[0])
|
| 26 |
+
image_id.append(IMAGE_ID)
|
| 27 |
+
|
| 28 |
+
QW = float(elements[1])
|
| 29 |
+
QX = float(elements[2])
|
| 30 |
+
QY = float(elements[3])
|
| 31 |
+
QZ = float(elements[4])
|
| 32 |
+
|
| 33 |
+
TX = float(elements[5])
|
| 34 |
+
TY = float(elements[6])
|
| 35 |
+
TZ = float(elements[7])
|
| 36 |
+
|
| 37 |
+
t_cw_i = np.array([TX, TY, TZ])
|
| 38 |
+
q_wc_i = R.from_quat([QX, QY, QZ, QW]).inv()
|
| 39 |
+
R_wc_i = q_wc_i.as_matrix()
|
| 40 |
+
|
| 41 |
+
q_wc_xyzw.append([q_wc_i.as_quat()[0], q_wc_i.as_quat()[1], q_wc_i.as_quat()[2], q_wc_i.as_quat()[3]])
|
| 42 |
+
t_wc.append(-R_wc_i @ t_cw_i)
|
| 43 |
|
| 44 |
+
file.readline()
|
| 45 |
+
|
| 46 |
+
image_id = np.array(image_id)
|
| 47 |
+
q_wc_xyzw = np.array(q_wc_xyzw)
|
| 48 |
+
t_wc = np.array(t_wc)
|
| 49 |
|
| 50 |
+
sorted_indices = image_id.argsort()
|
| 51 |
+
image_id = image_id[sorted_indices]
|
| 52 |
+
q_wc_xyzw = q_wc_xyzw[sorted_indices]
|
| 53 |
+
t_wc = t_wc[sorted_indices]
|
| 54 |
|
| 55 |
+
q_wc_xyzw_corrected = q_wc_xyzw.copy()
|
| 56 |
+
for i in range(1, len(q_wc_xyzw_corrected)):
|
| 57 |
+
dot_product = np.dot(q_wc_xyzw_corrected[i - 1], q_wc_xyzw_corrected[i])
|
| 58 |
+
if dot_product < 0:
|
| 59 |
+
q_wc_xyzw_corrected[i] = -q_wc_xyzw_corrected[i]
|
| 60 |
|
| 61 |
+
return image_id, t_wc, q_wc_xyzw_corrected
|
| 62 |
|
| 63 |
+
def write_trajectory_tum_format(file_name, image_ts, t_wc, q_wc_xyzw):
|
| 64 |
print(f"writeTrajectoryTUMformat: {file_name}")
|
| 65 |
+
|
| 66 |
data = np.hstack((image_ts.reshape(-1, 1), t_wc, q_wc_xyzw))
|
| 67 |
data = data[data[:, 0].argsort()]
|
| 68 |
+
|
| 69 |
with open(file_name, 'w', newline='') as file:
|
| 70 |
file.write('ts (ns),tx (m),ty (m),tz (m),qx,qy,qz,qw\n')
|
| 71 |
for row in data:
|
| 72 |
file.write(','.join(f'{x:.15f}' for x in row) + '\n')
|
| 73 |
|
| 74 |
+
def get_timestamps(files_path, rgb_file, camera_name):
|
| 75 |
+
print(f"getTimestamps: {os.path.join(files_path, rgb_file)}")
|
| 76 |
+
df = pd.read_csv(rgb_file)
|
| 77 |
+
ts = df[f'ts_{camera_name} (ns)'].to_list()
|
| 78 |
+
return ts
|
| 79 |
+
|
| 80 |
+
if __name__ == "__main__":
|
| 81 |
|
| 82 |
+
sequence_path = sys.argv[1]
|
| 83 |
+
exp_folder = sys.argv[2]
|
| 84 |
+
exp_id = sys.argv[3]
|
| 85 |
+
verbose = bool(int(sys.argv[4]))
|
| 86 |
+
rgb_file = sys.argv[5]
|
| 87 |
+
camera_name = sys.argv[6]
|
| 88 |
|
| 89 |
+
images_file = os.path.join(exp_folder, f'colmap_{exp_id}', 'images.txt')
|
|
|
|
|
|
|
| 90 |
|
| 91 |
+
number_of_header_lines = 4
|
| 92 |
+
image_id, t_wc, q_wc_xyzw = get_colmap_keyframes(images_file, number_of_header_lines, verbose)
|
| 93 |
|
| 94 |
+
image_ts = np.array(get_timestamps(sequence_path, rgb_file, camera_name))
|
| 95 |
+
timestamps = []
|
| 96 |
+
for id in image_id:
|
| 97 |
+
timestamps.append(float(image_ts[id-1]))
|
| 98 |
|
| 99 |
+
timestamps = np.array(timestamps)
|
| 100 |
|
| 101 |
+
keyFrameTrajectory_txt = os.path.join(exp_folder, exp_id + '_KeyFrameTrajectory' + '.csv')
|
| 102 |
+
write_trajectory_tum_format(keyFrameTrajectory_txt, timestamps, t_wc, q_wc_xyzw)
|
|
|
|
|
|
colmap_utilities.py
DELETED
|
@@ -1,114 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Module: VSLAM-LAB - Baselines - colmap - colmap_utilities.py
|
| 3 |
-
- Author: Alejandro Fontan Villacampa
|
| 4 |
-
- Assisted by: Claude (Fable 5.1)
|
| 5 |
-
- Version: 1.0
|
| 6 |
-
- Created: 2026-10-03
|
| 7 |
-
- Updated: 2026-10-03
|
| 8 |
-
- License: GPLv3 License
|
| 9 |
-
|
| 10 |
-
Helpers shared by the colmap pipeline stages (vslamlab_colmap.py, colmap_matcher.py,
|
| 11 |
-
colmap_mapper.py): running colmap commands, GPU / thread detection, and mapping a VSLAM-LAB
|
| 12 |
-
calibration onto a COLMAP camera model.
|
| 13 |
-
"""
|
| 14 |
-
|
| 15 |
-
import os
|
| 16 |
-
import subprocess
|
| 17 |
-
import sys
|
| 18 |
-
from pathlib import Path
|
| 19 |
-
|
| 20 |
-
import pandas as pd
|
| 21 |
-
|
| 22 |
-
from get_calibration import get_camera_intrinsics
|
| 23 |
-
|
| 24 |
-
COLMAP_DIR = Path(__file__).resolve().parent
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
def run(cmd: list[str], capture: bool = False) -> str:
|
| 28 |
-
"""Run a command, exiting with its return code if it fails. Returns the merged output when capture=True."""
|
| 29 |
-
if capture:
|
| 30 |
-
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
| 31 |
-
else:
|
| 32 |
-
result = subprocess.run(cmd)
|
| 33 |
-
if result.returncode != 0:
|
| 34 |
-
print(f" ERROR: '{' '.join(cmd[:2])}' failed with return code {result.returncode}")
|
| 35 |
-
if capture:
|
| 36 |
-
print(result.stdout)
|
| 37 |
-
sys.exit(result.returncode)
|
| 38 |
-
return result.stdout if capture else ""
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
def shell_output(cmd: list[str]) -> str:
|
| 42 |
-
"""stdout+stderr of a command, '' if it cannot run (e.g. no nvidia-smi)."""
|
| 43 |
-
try:
|
| 44 |
-
return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True).stdout
|
| 45 |
-
except FileNotFoundError as e:
|
| 46 |
-
return str(e)
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
def indent(text: str, prefix: str) -> str:
|
| 50 |
-
return "\n".join(prefix + line for line in text.rstrip("\n").split("\n"))
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
def detect_gpus(use_gpu: int) -> tuple[str, int]:
|
| 54 |
-
"""GPU index list for COLMAP's gpu_index options and the CPU thread count.
|
| 55 |
-
|
| 56 |
-
Extraction / matching run as a single job across all usable GPUs, since COLMAP spawns one
|
| 57 |
-
worker per listed GPU index and splits the workload internally. When CUDA_VISIBLE_DEVICES is
|
| 58 |
-
set (a scheduler restricted this job to a device set, often MIG slice UUIDs on HPC that
|
| 59 |
-
nvidia-smi does not enumerate per job), CUDA remaps the listed devices to ordinals 0..N-1 inside
|
| 60 |
-
the process and COLMAP's gpu_index is just cudaSetDevice(ordinal), so the ordinals are used
|
| 61 |
-
rather than the real indices. The thread count comes from the scheduling affinity (what `nproc`
|
| 62 |
-
reports) rather than COLMAP's num_threads=-1 auto-detection, which on cgroup-limited nodes often
|
| 63 |
-
sees the whole node's cores.
|
| 64 |
-
"""
|
| 65 |
-
print(" detecting GPUs ...")
|
| 66 |
-
print(" nvidia-smi -L:")
|
| 67 |
-
print(indent(shell_output(["nvidia-smi", "-L"]), " " * 12))
|
| 68 |
-
print(" nvidia-smi --query-gpu=index,name,uuid --format=csv:")
|
| 69 |
-
print(indent(shell_output(["nvidia-smi", "--query-gpu=index,name,uuid", "--format=csv"]), " " * 12))
|
| 70 |
-
cuda_visible_devices = os.environ.get("CUDA_VISIBLE_DEVICES", "")
|
| 71 |
-
print(f" CUDA_VISIBLE_DEVICES: {cuda_visible_devices}")
|
| 72 |
-
|
| 73 |
-
if cuda_visible_devices:
|
| 74 |
-
num_gpus = len([d for d in cuda_visible_devices.split(",") if d.strip()])
|
| 75 |
-
gpu_ids = list(range(num_gpus))
|
| 76 |
-
else:
|
| 77 |
-
out = shell_output(["nvidia-smi", "--query-gpu=index", "--format=csv,noheader"])
|
| 78 |
-
gpu_ids = [int(tok) for tok in out.split() if tok.isdigit()]
|
| 79 |
-
if len(gpu_ids) < 1 or use_gpu == 0:
|
| 80 |
-
gpu_ids = [0]
|
| 81 |
-
gpu_index_list = ",".join(str(i) for i in gpu_ids)
|
| 82 |
-
print(f" use_gpu: {use_gpu}")
|
| 83 |
-
print(f" detected gpu_ids: {' '.join(str(i) for i in gpu_ids)}")
|
| 84 |
-
print(f" gpu_index_list passed to colmap: {gpu_index_list}")
|
| 85 |
-
|
| 86 |
-
num_threads = len(os.sched_getaffinity(0)) if hasattr(os, "sched_getaffinity") else (os.cpu_count() or 1)
|
| 87 |
-
print(f" num_threads: {num_threads}")
|
| 88 |
-
return gpu_index_list, num_threads
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
def rgb_path_from_csv(sequence_path: Path, rgb_csv: Path, camera_name: str) -> Path:
|
| 92 |
-
"""Frame folder of camera_name as the csv names it: the run pipeline may point path_rgb_0 at a
|
| 93 |
-
generated folder (refrax_0 for 'refraction: refrax') rather than rgb_0."""
|
| 94 |
-
df = pd.read_csv(rgb_csv, nrows=1)
|
| 95 |
-
rgb_dir = str(df[f"path_{camera_name}"].iloc[0]).split("/")[0]
|
| 96 |
-
return sequence_path / rgb_dir
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
def colmap_camera(calibration_yaml: Path, camera_name: str) -> tuple[str, str, str]:
|
| 100 |
-
"""(vslamlab calibration model, COLMAP camera model, comma-separated COLMAP params or '')."""
|
| 101 |
-
calibration_model, params = get_camera_intrinsics(calibration_yaml, camera_name)
|
| 102 |
-
params_csv = ",".join(str(p) for p in params)
|
| 103 |
-
if calibration_model == "unknown":
|
| 104 |
-
return calibration_model, "OPENCV", ""
|
| 105 |
-
if calibration_model == "pinhole":
|
| 106 |
-
return calibration_model, "PINHOLE", params_csv
|
| 107 |
-
if calibration_model == "radtan4":
|
| 108 |
-
return calibration_model, "OPENCV", params_csv
|
| 109 |
-
if calibration_model == "radtan5":
|
| 110 |
-
return calibration_model, "FULL_OPENCV", params_csv + ",0,0,0"
|
| 111 |
-
if calibration_model == "equid4":
|
| 112 |
-
return calibration_model, "OPENCV_FISHEYE", params_csv
|
| 113 |
-
print(f"Unknown calibration_model: {calibration_model}")
|
| 114 |
-
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
create_colmap_mask_dir.py
DELETED
|
@@ -1,61 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Module: VSLAM-LAB - Baselines - colmap - create_colmap_mask_dir.py
|
| 3 |
-
- Author: Alejandro Fontan Villacampa
|
| 4 |
-
- Assisted by: Claude (Fable 5)
|
| 5 |
-
- Version: 1.0
|
| 6 |
-
- Created: 2026-08-28
|
| 7 |
-
- Updated: 2026-08-28
|
| 8 |
-
- License: GPLv3 License
|
| 9 |
-
|
| 10 |
-
Turns the path_mask_<i> column of an rgb_exp.csv (written by the run pipeline for
|
| 11 |
-
'segmentation: mask2former', 'refraction: refrax', or datasets that ship masks; 1 = usable pixel,
|
| 12 |
-
0 = masked out, which is COLMAP's own convention: no features where the mask is 0) into what
|
| 13 |
-
colmap feature_extractor accepts, and prints one line the calling shell script evals:
|
| 14 |
-
camera_mask:<png> every frame shares one mask (refrax's mask.png) -> --ImageReader.camera_mask_path
|
| 15 |
-
mask_dir:<dir> per-frame masks -> --ImageReader.mask_path: <dir>/<image name>.png symlinks,
|
| 16 |
-
one per frame (COLMAP looks masks up by image name + '.png')
|
| 17 |
-
none the csv has no mask column for this camera (or a mask file is missing)
|
| 18 |
-
"""
|
| 19 |
-
|
| 20 |
-
import argparse
|
| 21 |
-
import os
|
| 22 |
-
from pathlib import Path
|
| 23 |
-
|
| 24 |
-
import pandas as pd
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
def create_colmap_mask_dir(rgb_csv: str, camera_name: str, sequence_path: str, mask_dir: str) -> str:
|
| 28 |
-
df = pd.read_csv(rgb_csv)
|
| 29 |
-
cam_idx = camera_name.rsplit("_", 1)[-1]
|
| 30 |
-
mask_col, path_col = f"path_mask_{cam_idx}", f"path_{camera_name}"
|
| 31 |
-
if mask_col not in df.columns:
|
| 32 |
-
print(f" no '{mask_col}' column in {os.path.basename(rgb_csv)}; extracting features without masks")
|
| 33 |
-
return "none"
|
| 34 |
-
|
| 35 |
-
masks = [Path(sequence_path) / p for p in df[mask_col]]
|
| 36 |
-
missing = [m for m in masks if not m.exists()]
|
| 37 |
-
if missing:
|
| 38 |
-
print(f" {len(missing)}/{len(masks)} mask files missing (e.g. {missing[0]}); extracting features without masks")
|
| 39 |
-
return "none"
|
| 40 |
-
|
| 41 |
-
if len(set(masks)) == 1:
|
| 42 |
-
return f"camera_mask:{masks[0].resolve()}"
|
| 43 |
-
|
| 44 |
-
mask_dir = Path(mask_dir)
|
| 45 |
-
mask_dir.mkdir(parents=True, exist_ok=True)
|
| 46 |
-
for image, mask in zip(df[path_col], masks):
|
| 47 |
-
link = mask_dir / f"{os.path.basename(image)}.png"
|
| 48 |
-
if link.is_symlink() or link.exists():
|
| 49 |
-
link.unlink()
|
| 50 |
-
os.symlink(mask.resolve(), link)
|
| 51 |
-
return f"mask_dir:{mask_dir.resolve()}"
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
if __name__ == "__main__":
|
| 55 |
-
parser = argparse.ArgumentParser()
|
| 56 |
-
parser.add_argument("rgb_csv", help="Path to the experiment's rgb csv")
|
| 57 |
-
parser.add_argument("camera_name", help="camera_name (e.g. rgb_0)")
|
| 58 |
-
parser.add_argument("sequence_path", help="Sequence folder the csv paths are relative to")
|
| 59 |
-
parser.add_argument("mask_dir", help="Where to build the per-frame mask directory if needed")
|
| 60 |
-
args = parser.parse_args()
|
| 61 |
-
print(create_colmap_mask_dir(args.rgb_csv, args.camera_name, args.sequence_path, args.mask_dir))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
get_calibration.py
CHANGED
|
@@ -1,45 +1,33 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Module: VSLAM-LAB - Baselines - colmap - get_calibration.py
|
| 3 |
-
- Author: Alejandro Fontan Villacampa
|
| 4 |
-
- Assisted by: Claude (Fable 5.1)
|
| 5 |
-
- Version: 1.1
|
| 6 |
-
- Created: 2024-07-12
|
| 7 |
-
- Updated: 2026-10-03
|
| 8 |
-
- License: GPLv3 License
|
| 9 |
-
|
| 10 |
-
Reads one camera of a VSLAM-LAB calibration yaml as (model, [fx, fy, cx, cy, *distortion]).
|
| 11 |
-
The model is the distortion_type when the camera has distortion fields, else its cam_model
|
| 12 |
-
(so 'pinhole' and 'unknown' come through unchanged). Importable (colmap_utilities.colmap_camera)
|
| 13 |
-
and runnable: `python get_calibration.py <calibration_yaml> <camera_name>` prints the same
|
| 14 |
-
fields space-separated on one line.
|
| 15 |
-
"""
|
| 16 |
-
|
| 17 |
-
import argparse
|
| 18 |
-
from pathlib import Path
|
| 19 |
-
|
| 20 |
import yaml
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
-
|
| 23 |
-
def get_camera_intrinsics(calibration_yaml: str | Path, cam_name: str) -> tuple[str, list[float]]:
|
| 24 |
with open(calibration_yaml, 'r') as file:
|
| 25 |
data = yaml.safe_load(file)
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
has_dist = ('distortion_type' in cam) and ('distortion_coefficients' in cam)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
if has_dist:
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
| 38 |
if __name__ == "__main__":
|
| 39 |
parser = argparse.ArgumentParser()
|
| 40 |
parser.add_argument("calibration_yaml", help="Path to the calibration YAML")
|
| 41 |
parser.add_argument("camera_name", help="camera_name")
|
| 42 |
args = parser.parse_args()
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
print(model, *params)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import yaml
|
| 2 |
+
import sys
|
| 3 |
+
import argparse
|
| 4 |
+
import numpy as np
|
| 5 |
|
| 6 |
+
def get_camera_intrinsics(calibration_yaml, cam_name):
|
|
|
|
| 7 |
with open(calibration_yaml, 'r') as file:
|
| 8 |
data = yaml.safe_load(file)
|
| 9 |
+
cameras = data.get('cameras', [])
|
| 10 |
+
for cam_ in cameras:
|
| 11 |
+
if cam_['cam_name'] == cam_name:
|
| 12 |
+
cam = cam_;
|
| 13 |
+
break;
|
| 14 |
+
|
| 15 |
has_dist = ('distortion_type' in cam) and ('distortion_coefficients' in cam)
|
| 16 |
+
K = np.array([[cam['focal_length'][0], 0, cam['principal_point'][0]],
|
| 17 |
+
[0, cam['focal_length'][1], cam['principal_point'][1]],
|
| 18 |
+
[0, 0, 1]], dtype=np.float32)
|
| 19 |
+
|
| 20 |
if has_dist:
|
| 21 |
+
dist= " ".join(map(str, cam['distortion_coefficients']))
|
| 22 |
+
print(f"{cam['distortion_type']} {K[0,0]} {K[1,1]} {K[0,2]} {K[1,2]} {dist}")
|
| 23 |
+
else:
|
| 24 |
+
print(f"{cam['cam_model']} {K[0,0]} {K[1,1]} {K[0,2]} {K[1,2]}")
|
| 25 |
+
|
| 26 |
+
|
| 27 |
if __name__ == "__main__":
|
| 28 |
parser = argparse.ArgumentParser()
|
| 29 |
parser.add_argument("calibration_yaml", help="Path to the calibration YAML")
|
| 30 |
parser.add_argument("camera_name", help="camera_name")
|
| 31 |
args = parser.parse_args()
|
| 32 |
+
|
| 33 |
+
get_camera_intrinsics(args.calibration_yaml, args.camera_name)
|
|
|
vslamlab_colmap.py
DELETED
|
@@ -1,111 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Module: VSLAM-LAB - Baselines - colmap - vslamlab_colmap.py
|
| 3 |
-
- Author: Alejandro Fontan Villacampa
|
| 4 |
-
- Assisted by: Claude (Fable 5.1)
|
| 5 |
-
- Version: 2.0
|
| 6 |
-
- Created: 2024-07-12
|
| 7 |
-
- Updated: 2026-10-03
|
| 8 |
-
- License: GPLv3 License
|
| 9 |
-
|
| 10 |
-
VSLAM-LAB entry point for the colmap baseline (Python port of colmap_reconstruction.sh), run by
|
| 11 |
-
the pixi task execute-mono with the arguments BaselineVSLAMLAB.build_execute_command passes for
|
| 12 |
-
command_style 'python' (--key value). Stages, each in its own module:
|
| 13 |
-
colmap_matcher.run_matcher database, feature extraction (masks optional), exhaustive / sequential matching
|
| 14 |
-
colmap_mapper.run_mapper COLMAP incremental or GLOMAP global mapping, best sub-model, TXT export
|
| 15 |
-
colmap_to_vslamlab <exp_folder>/<exp_id>_KeyFrameTrajectory.csv from images.txt
|
| 16 |
-
With verbose=1 the best sub-model is opened in the colmap gui afterwards.
|
| 17 |
-
"""
|
| 18 |
-
|
| 19 |
-
import argparse
|
| 20 |
-
import os
|
| 21 |
-
import shutil
|
| 22 |
-
import sys
|
| 23 |
-
from pathlib import Path
|
| 24 |
-
|
| 25 |
-
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
| 26 |
-
|
| 27 |
-
from colmap_matcher import run_matcher # noqa: E402
|
| 28 |
-
from colmap_mapper import run_mapper # noqa: E402
|
| 29 |
-
from colmap_to_vslamlab import colmap_to_vslamlab # noqa: E402
|
| 30 |
-
from colmap_utilities import rgb_path_from_csv, run # noqa: E402
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
def parse_args() -> argparse.Namespace:
|
| 34 |
-
parser = argparse.ArgumentParser(description="VSLAM-LAB colmap baseline")
|
| 35 |
-
# Fixed per-run arguments (BaselineVSLAMLAB.build_execute_command)
|
| 36 |
-
parser.add_argument("--sequence_path", type=Path, required=True)
|
| 37 |
-
parser.add_argument("--calibration_yaml", type=Path, required=True)
|
| 38 |
-
parser.add_argument("--rgb_csv", type=Path, required=True)
|
| 39 |
-
parser.add_argument("--exp_folder", type=Path, required=True)
|
| 40 |
-
parser.add_argument("--exp_it", type=int, required=True)
|
| 41 |
-
parser.add_argument("--settings_yaml", type=Path, default=None)
|
| 42 |
-
# Baseline parameters (baseline_colmap.py default_parameters, overridable per experiment)
|
| 43 |
-
parser.add_argument("--verbose", type=int, default=0)
|
| 44 |
-
parser.add_argument("--mode", type=str, default="mono")
|
| 45 |
-
parser.add_argument("--matcher_type", type=str, default="exhaustive", choices=["exhaustive", "sequential"])
|
| 46 |
-
parser.add_argument("--matching_type", type=str, default="sift_bruteforce")
|
| 47 |
-
parser.add_argument("--mapper_type", type=str, default="colmap", choices=["colmap", "glomap"])
|
| 48 |
-
parser.add_argument("--rgb_max", type=int, default=None, help="consumed by the run pipeline; accepted here because it is forwarded")
|
| 49 |
-
parser.add_argument("--use_mask", type=int, default=0)
|
| 50 |
-
parser.add_argument("--optimize_intrinsics", type=int, default=1)
|
| 51 |
-
parser.add_argument("--dense", type=int, default=0)
|
| 52 |
-
parser.add_argument("--dense_max_image_size", type=int, default=1600)
|
| 53 |
-
parser.add_argument("--mesher", type=str, default="none")
|
| 54 |
-
# Not exposed as baseline parameters
|
| 55 |
-
parser.add_argument("--use_gpu", type=int, default=1)
|
| 56 |
-
parser.add_argument("--camera_name", type=str, default="rgb_0")
|
| 57 |
-
return parser.parse_args()
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
def main() -> None:
|
| 61 |
-
args = parse_args()
|
| 62 |
-
exp_id = f"{args.exp_it:05d}"
|
| 63 |
-
|
| 64 |
-
print("\n================= Experiment Configuration =================")
|
| 65 |
-
print(f" Sequence Path : {args.sequence_path}")
|
| 66 |
-
print(f" Experiment Folder : {args.exp_folder}")
|
| 67 |
-
print(f" Experiment ID : {exp_id}")
|
| 68 |
-
print(f" Verbose : {args.verbose}")
|
| 69 |
-
print(f" Matcher Type : {args.matcher_type}")
|
| 70 |
-
print(f" Matching Type : {args.matching_type}")
|
| 71 |
-
print(f" Mapper Type : {args.mapper_type}")
|
| 72 |
-
print(f" Use GPU : {args.use_gpu}")
|
| 73 |
-
print(f" Use Mask : {args.use_mask}")
|
| 74 |
-
print(f" Optimize Intrins. : {args.optimize_intrinsics}")
|
| 75 |
-
print(f" Dense : {args.dense}")
|
| 76 |
-
print(f" Dense Max Img Size: {args.dense_max_image_size}")
|
| 77 |
-
print(f" Mesher : {args.mesher}")
|
| 78 |
-
print(f" Settings YAML : {args.settings_yaml}")
|
| 79 |
-
print(f" Calibration YAML : {args.calibration_yaml}")
|
| 80 |
-
print(f" RGB CSV : {args.rgb_csv}")
|
| 81 |
-
print(f" Camera Name : {args.camera_name}")
|
| 82 |
-
print("============================================================")
|
| 83 |
-
|
| 84 |
-
# Folder for the colmap files of this run
|
| 85 |
-
exp_folder_colmap = args.exp_folder / f"colmap_{exp_id}"
|
| 86 |
-
shutil.rmtree(exp_folder_colmap, ignore_errors=True)
|
| 87 |
-
exp_folder_colmap.mkdir(parents=True)
|
| 88 |
-
|
| 89 |
-
conda_prefix = os.environ.get("CONDA_PREFIX")
|
| 90 |
-
if conda_prefix:
|
| 91 |
-
os.environ["QT_QPA_PLATFORM_PLUGIN_PATH"] = f"{conda_prefix}/plugins/platforms"
|
| 92 |
-
|
| 93 |
-
rgb_path = rgb_path_from_csv(args.sequence_path, args.rgb_csv, args.camera_name)
|
| 94 |
-
|
| 95 |
-
database = run_matcher(args.sequence_path, exp_folder_colmap, rgb_path, args.rgb_csv, args.calibration_yaml, args.camera_name,
|
| 96 |
-
args.matcher_type, args.matching_type, args.use_gpu, args.use_mask)
|
| 97 |
-
best_model = run_mapper(exp_folder_colmap, rgb_path, args.calibration_yaml, args.camera_name, args.mapper_type, args.optimize_intrinsics)
|
| 98 |
-
|
| 99 |
-
# Convert COLMAP outputs to a format suitable for VSLAM-LAB
|
| 100 |
-
colmap_to_vslamlab(args.exp_folder, exp_id, args.rgb_csv, args.camera_name)
|
| 101 |
-
|
| 102 |
-
if args.dense == 1:
|
| 103 |
-
print(f"\n WARNING: dense={args.dense} (mesher={args.mesher}) requested, but the dense stage is not wired yet; skipping")
|
| 104 |
-
|
| 105 |
-
# Visualization with colmap gui
|
| 106 |
-
if args.verbose == 1:
|
| 107 |
-
run(["colmap", "gui", "--import_path", str(best_model), "--database_path", str(database), "--image_path", str(rgb_path)])
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
if __name__ == "__main__":
|
| 111 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|