cad-3d-reconstruction / code /run_colmap.py
acd23's picture
Upload code/run_colmap.py with huggingface_hub
f34db7f verified
Raw History Blame Contribute Delete
3.98 kB
"""
Run COLMAP SfM (Structure-from-Motion) on each part's images using pycolmap.
Produces sparse reconstruction (cameras, images, points3D) needed for 3DGS.
If COLMAP fails (likely for textureless CAD parts with few views), we report
the failure clearly so we can fall back to an alternative pose estimator.
"""
import os
import sys
import shutil
import pycolmap
import numpy as np
from PIL import Image
PARTS_DIR = "data/parts"
PARTS = sorted(os.listdir(PARTS_DIR))
def run_colmap_for_part(part):
input_dir = os.path.abspath(os.path.join(PARTS_DIR, part, "input"))
workspace = os.path.abspath(os.path.join(PARTS_DIR, part, "colmap_ws"))
db_path = os.path.join(workspace, "database.db")
image_dir = os.path.join(workspace, "images")
sparse_dir = os.path.join(workspace, "sparse", "0")
# fresh workspace
if os.path.exists(workspace):
shutil.rmtree(workspace)
os.makedirs(image_dir, exist_ok=True)
os.makedirs(os.path.join(workspace, "sparse"), exist_ok=True)
# copy images
images = sorted([f for f in os.listdir(input_dir) if f.endswith(".png")])
for img in images:
shutil.copy(os.path.join(input_dir, img), os.path.join(image_dir, img))
print(f"\n{'='*60}")
print(f"PART: {part} ({len(images)} images)")
print(f"{'='*60}")
# Step 1: feature extraction
print("[1/4] Feature extraction...")
try:
pycolmap.extract_features(database_path=db_path, image_path=image_dir)
except Exception as e:
print(f" FAILED: {e}")
return False, str(e)
# Step 2: matching
print("[2/4] Feature matching (exhaustive)...")
try:
pycolmap.match_exhaustive(database_path=db_path)
except Exception as e:
print(f" FAILED: {e}")
return False, str(e)
# Step 3: incremental SfM
print("[3/4] Incremental mapping (SfM)...")
try:
os.makedirs(sparse_dir, exist_ok=True)
reconstruction = pycolmap.incremental_mapping(
database_path=db_path,
image_path=image_dir,
output_path=os.path.join(workspace, "sparse"),
options=pycolmap.IncrementalPipelineOptions(
min_num_matches=5,
min_focal_length_ratio=0.1,
max_focal_length_ratio=10.0,
),
)
except Exception as e:
print(f" FAILED: {e}")
return False, str(e)
if reconstruction is None or len(reconstruction) == 0:
print(" FAILED: No reconstruction produced (0 models)")
return False, "No reconstruction produced"
# Check how many images were registered
rec = reconstruction[0]
n_reg = sum(1 for im in rec.images.values() if im.has_pose)
n_points = len(rec.points3D)
n_cameras = len(rec.cameras)
print(f"[4/4] Reconstruction: {n_reg}/{len(images)} images registered, {n_points} 3D points, {n_cameras} cameras")
if n_reg < 3:
print(f" WARNING: only {n_reg} images registered - insufficient for 3DGS")
return False, f"Only {n_reg} images registered"
# Export to text + PLY for inspection
rec.write(sparse_dir)
pycolmap.export_points3D(
output_path=os.path.join(workspace, "sparse", "points3D.ply"),
database_path=db_path,
image_path=image_dir,
output_type=pycolmap.OutputType.PLY,
)
print(f" Saved sparse model to {sparse_dir}")
return True, f"{n_reg}/{len(images)} registered, {n_points} points"
def main():
results = {}
for part in PARTS:
input_dir = os.path.join(PARTS_DIR, part, "input")
if not os.path.isdir(input_dir):
continue
ok, msg = run_colmap_for_part(part)
results[part] = (ok, msg)
print(f"\n{'='*60}")
print("COLMAP SUMMARY")
print(f"{'='*60}")
for part, (ok, msg) in results.items():
status = "OK" if ok else "FAILED"
print(f" {part}: {status} - {msg}")
return results
if __name__ == "__main__":
main()