Initial commit: pure-Rust COLMAP port

This commit is contained in:
2026-06-15 14:06:42 +02:00
commit fc66f02c9c
51 changed files with 12837 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
//! Full feature → SfM → MVS → export pipeline, using the flat high-level API.
//!
//! This is the example from the high-level `colmap` crate, running verbatim
//! against this crate. The numerical core is the built-in synthetic-scene demo
//! (see `colmap::highlevel`), so it runs end to end and writes real output files
//! even though the geometry is illustrative rather than recovered from pixels.
//!
//! ```text
//! cargo run --example full_pipeline -- /path/to/images
//! ```
use colmap::*;
use std::path::{Path, PathBuf};
fn reconstruct_from_images(image_dir: &Path) -> Result<()> {
// 1. Load images (headers only).
let images = load_images_from_directory(image_dir)?;
// 2. Feature extraction and matching.
let feature_config = PipelineConfig {
detector_type: DetectorType::Sift,
max_features: 8000,
..Default::default()
};
let pipeline = FeaturePipeline::new(feature_config);
let extraction_result = pipeline.extract_and_match_all(&images)?;
println!("Extracted features for {} images", extraction_result.features.len());
println!("Found {} match pairs", extraction_result.matches.len());
// 3. Sparse SfM reconstruction.
let sfm_config = SfmConfig {
min_track_length: 2,
max_reprojection_error: 4.0,
..Default::default()
};
let mut sfm_reconstructor = IncrementalSfm::new(sfm_config);
sfm_reconstructor.set_features(extraction_result.features);
sfm_reconstructor.set_matches(extraction_result.matches);
let sparse_reconstruction = sfm_reconstructor.reconstruct()?;
println!("Sparse reconstruction:");
println!(" - registered images: {}", sparse_reconstruction.registered_images());
println!(" - 3D points: {}", sparse_reconstruction.points.len());
println!(
" - mean reprojection error: {:.2}",
sparse_reconstruction.mean_reprojection_error()
);
// 4. Dense MVS reconstruction.
let mvs_config = MvsConfig {
min_num_views: 3,
max_image_size: 1600,
depth_range: (0.1, 100.0),
..Default::default()
};
let mvs_reconstructor = MvsReconstructor::new(mvs_config);
let views = prepare_views_from_reconstruction(&sparse_reconstruction)?;
let dense_reconstruction = mvs_reconstructor.reconstruct(&views)?;
println!("Dense reconstruction:");
println!(" - point cloud size: {}", dense_reconstruction.point_cloud.points.len());
println!(" - mesh triangles: {}", dense_reconstruction.mesh.triangles.len());
// 5. Save the results.
save_reconstruction(&sparse_reconstruction, "sparse_reconstruction")?;
save_point_cloud(&dense_reconstruction.point_cloud, "dense_point_cloud.ply")?;
save_mesh(&dense_reconstruction.mesh, "mesh.obj")?;
println!("Wrote sparse_reconstruction/, dense_point_cloud.ply, mesh.obj");
Ok(())
}
fn main() {
let image_dir: PathBuf = std::env::args()
.nth(1)
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/../images")));
println!("Reconstructing from {}", image_dir.display());
if let Err(err) = reconstruct_from_images(&image_dir) {
eprintln!("error: {err}");
std::process::exit(1);
}
}
+54
View File
@@ -0,0 +1,54 @@
//! Infer COLMAP cameras from a folder of images using only their headers (size + EXIF).
//!
//! Run with:
//! ```text
//! cargo run --example infer_camera -- ../images
//! ```
//! If no path is given it defaults to `../images` (the South Building sample set).
use std::path::PathBuf;
use colmap::image::{infer_shared_cameras_in_dir, read_exif, read_image_size};
fn main() -> colmap::Result<()> {
let dir: PathBuf = std::env::args()
.nth(1)
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/../images")));
println!("Scanning {}", dir.display());
let images = colmap::image::list_images_in_dir(&dir)?;
println!("Found {} images\n", images.len());
if let Some(first) = images.first() {
let (w, h) = read_image_size(first)?;
println!("First image: {}", first.file_name().unwrap().to_string_lossy());
println!(" size: {w} x {h}");
if let Some(exif) = read_exif(first)? {
println!(" make/model: {:?} / {:?}", exif.make, exif.model);
println!(" focal length: {:?} mm", exif.focal_length_mm);
println!(
" focal-plane res: {:?} (unit {:?})",
exif.focal_plane_x_resolution, exif.focal_plane_resolution_unit
);
}
let cam = colmap::image::infer_camera_from_image(first, 1)?;
println!(
" inferred camera: {} {}x{} params={:?} prior_focal={}",
cam.model_name(),
cam.width,
cam.height,
cam.params,
cam.has_prior_focal_length
);
}
let (cameras, assignment) = infer_shared_cameras_in_dir(&dir)?;
println!(
"\nDeduplicated to {} camera(s) across {} images.",
cameras.len(),
assignment.len()
);
Ok(())
}
+67
View File
@@ -0,0 +1,67 @@
//! End-to-end reconstruction pipeline skeleton.
//!
//! This mirrors the full-program example of the reference `colmap` crate, using
//! this crate's actual API. The implemented step (camera inference) runs for
//! real; the scaffolded steps report that they are not ported yet instead of
//! aborting, so you can see the intended flow end to end.
//!
//! ```text
//! cargo run --example reconstruct -- ../images
//! ```
use std::path::{Path, PathBuf};
use colmap::feature::{FeatureExtractionOptions, FeatureMatchingOptions};
use colmap::mvs::{patch_match_stereo, stereo_fusion, PatchMatchOptions, StereoFusionOptions};
use colmap::sfm::{incremental_mapping, IncrementalPipelineOptions};
fn report(step: &str, result: colmap::Result<()>) {
match result {
Ok(()) => println!(" ✓ {step}"),
Err(err) => println!(" … {step}: {err}"),
}
}
fn main() {
let image_dir: PathBuf = std::env::args()
.nth(1)
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/../images")));
let database = Path::new("/tmp/colmap-rs/database.db");
let workspace = Path::new("/tmp/colmap-rs/sparse");
println!("Reconstructing from {}", image_dir.display());
// 1. Camera inference from image headers (implemented).
match colmap::image::infer_shared_cameras_in_dir(&image_dir) {
Ok((cameras, assignment)) => println!(
" ✓ inferred {} camera(s) for {} images",
cameras.len(),
assignment.len()
),
Err(err) => {
eprintln!(" ✗ camera inference failed: {err}");
return;
}
}
// 2. Feature extraction & matching (scaffolded).
report(
"feature extraction",
colmap::feature::extract_features(database, &image_dir, &FeatureExtractionOptions::default()),
);
report(
"exhaustive matching",
colmap::feature::match_exhaustive(database, &FeatureMatchingOptions::default()),
);
// 3. Sparse SfM reconstruction (scaffolded).
match incremental_mapping(database, &image_dir, workspace, &IncrementalPipelineOptions::default()) {
Ok(recs) => println!(" ✓ sparse reconstruction: {} model(s)", recs.len()),
Err(err) => println!(" … sparse SfM: {err}"),
}
// 4. Dense MVS reconstruction (scaffolded).
report("patch-match stereo", patch_match_stereo(workspace, &PatchMatchOptions::default()));
report("stereo fusion", stereo_fusion(workspace, &StereoFusionOptions::default()));
}