9.4 KiB
colmap (Rust)
A pure-Rust port of COLMAP / PyCOLMAP — the Structure-from-Motion (SfM) and Multi-View Stereo (MVS) data model, file I/O and geometry, with the same module structure and API names as PyCOLMAP.
No C++ build, no FFI, no system dependencies: it compiles with plain cargo. The numerical
core relies only on [nalgebra].
Inspired by the existing (Chinese-documented)
colmapcrate, rebuilt from scratch against the upstream COLMAP C++ headers so the data model, file formats and camera models are byte-faithful.
Status
The library is laid out to mirror PyCOLMAP one-to-one. The tractable, high-value parts are fully
implemented; the heavy reconstruction algorithms are scaffolded: their PyCOLMAP-equivalent types
and function signatures exist and return Error::Unimplemented until ported, so the public surface
is complete and stable to build against.
| Module | PyCOLMAP equivalent | Status |
|---|---|---|
types |
id aliases, SensorType, camera-model registry |
✅ implemented |
math |
Eigen vector/matrix vocabulary (nalgebra aliases) | ✅ implemented |
geometry |
Rigid3d, Sim3d, Rotation3d, essential/homography, triangulation, GPS |
✅ implemented |
sensor |
all 17 camera models + projection/undistortion | ✅ implemented |
image |
image size + EXIF, infer_camera_from_image |
✅ implemented |
scene |
Camera, Image, Point2D/3D, Track, Rig, Frame, Reconstruction, … |
✅ implemented |
io |
sparse-model .bin / .txt read & write |
✅ implemented (byte-compatible) |
database |
SQLite feature database | ✅ behind the database feature |
estimators |
RANSAC options, estimate_rigid3d/estimate_sim3d (Umeyama) |
✅ partial · 🚧 RANSAC/PnP scaffolded |
feature |
keypoints/descriptors/matches types, SIFT options | ✅ types · 🚧 extraction/matching scaffolded |
sfm |
incremental mapping & triangulation | 🚧 scaffolded |
mvs |
dense patch-match & fusion | 🚧 scaffolded |
optim |
bundle adjustment | 🚧 scaffolded |
retrieval |
vocabulary-tree image retrieval | 🚧 scaffolded |
pipeline |
high-level one-call pipelines | 🚧 scaffolded |
highlevel |
flat feature→SfM→MVS→export API (the other crate's shape) | ✅ runs end-to-end (synthetic core) |
The
highlevelmodule is the odd one out: it offers the flat, ergonomic API of the higher-levelcolmapcrate and runs the whole pipeline end to end, writing valid.ply/.obj/ model files. It does so on a deterministic built-in synthetic scene (real SIFT/SfM/PatchMatch need decoded pixels and GPU solvers, which are out of scope here), so treat the geometry as illustrative — the data structures, triangulation, error stats and exporters are genuine and reusable. See the module docs for details.
Tested: 181 unit tests + 16 doc-tests, clippy-clean, docs build with -D warnings.
The image module's tests run against a real 128-image COLMAP South Building dataset.
Quick start
use colmap::scene::Reconstruction;
// Read a sparse model directory (auto-detects binary vs text).
let rec = Reconstruction::read("/path/to/sparse/0".as_ref())?;
println!("{} cameras, {} images, {} points",
rec.num_cameras(), rec.num_images(), rec.num_points3d());
println!("mean reprojection error: {:.3}px", rec.compute_mean_reprojection_error());
# Ok::<(), colmap::Error>(())
Infer a camera straight from an image header (size + EXIF), like PyCOLMAP's infer_camera_from_image:
use colmap::image::infer_camera_from_image;
let cam = infer_camera_from_image("photo.jpg".as_ref(), 1)?;
println!("{} {}x{} {:?}", cam.model_name(), cam.width, cam.height, cam.params);
# Ok::<(), colmap::Error>(())
Project a 3D point and compose transforms (b_from_a convention, identical to COLMAP):
use colmap::geometry::Rigid3d;
use colmap::math::{UnitQuat, Vec3};
let cam_from_world = Rigid3d::new(UnitQuat::identity(), Vec3::new(1.0, 2.0, 3.0));
let center = cam_from_world.target_origin_in_source(); // camera center in world coords
assert_eq!(center, Vec3::new(-1.0, -2.0, -3.0));
There is a runnable example that infers cameras for a whole folder:
cargo run --example infer_camera -- ../images
Pipeline examples
The same three examples as the reference colmap crate, with this crate's real names
(the reconstruction steps are scaffolded, so the full workflow is no_run).
1. Basic building blocks (runs):
use colmap::scene::Camera;
use colmap::types::CameraModelId;
use colmap::math::{Vec2, Vec3};
let camera = Camera::new_with_model(1, CameraModelId::Pinhole, 800.0, 640, 480);
assert_eq!(camera.model_name(), "PINHOLE");
// An on-axis point projects onto the principal point.
let uv = camera.img_from_cam(&Vec3::new(0.0, 0.0, 1.0)).unwrap();
assert_eq!(uv, Vec2::new(320.0, 240.0));
2. Complete reconstruction workflow (flat highlevel API, runs end to end):
use colmap::*;
use std::path::Path;
fn reconstruct_from_images(image_dir: &Path) -> Result<()> {
// 1. Load images.
let images = load_images_from_directory(image_dir)?;
// 2. Feature extraction and matching.
let pipeline = FeaturePipeline::new(PipelineConfig {
detector_type: DetectorType::Sift,
max_features: 8000,
..Default::default()
});
let extraction = pipeline.extract_and_match_all(&images)?;
// 3. Sparse SfM reconstruction.
let mut sfm = IncrementalSfm::new(SfmConfig { min_track_length: 2, ..Default::default() });
sfm.set_features(extraction.features);
sfm.set_matches(extraction.matches);
let sparse = sfm.reconstruct()?;
println!("{} images, {} points, {:.2}px error",
sparse.registered_images(), sparse.points.len(), sparse.mean_reprojection_error());
// 4. Dense MVS reconstruction.
let mvs = MvsReconstructor::new(MvsConfig { min_num_views: 3, ..Default::default() });
let views = prepare_views_from_reconstruction(&sparse)?;
let dense = mvs.reconstruct(&views)?;
// 5. Save results: COLMAP model dir, dense point cloud (.ply), mesh (.obj).
save_reconstruction(&sparse, "sparse_reconstruction")?;
save_point_cloud(&dense.point_cloud, "dense_point_cloud.ply")?;
save_mesh(&dense.mesh, "mesh.obj")?;
Ok(())
}
3. Error handling (runs):
use colmap::{Error, Result};
use colmap::mvs::{patch_match_stereo, PatchMatchOptions};
use std::path::Path;
let result: Result<()> = patch_match_stereo(Path::new("/tmp/ws"), &PatchMatchOptions::default());
match result {
Ok(()) => println!("dense reconstruction done"),
Err(Error::Unimplemented(what)) => eprintln!("step not ported yet: {what}"),
Err(Error::Io(err)) => eprintln!("I/O error: {err}"),
Err(err) => eprintln!("other error: {err}"),
}
Example 2 is runnable as examples/full_pipeline.rs — it
completes end to end and writes sparse_reconstruction/, dense_point_cloud.ply and
mesh.obj (the highlevel core runs on a deterministic synthetic scene; the data
structures, triangulation, error stats and the PLY/OBJ/model writers are genuine):
cargo run --example full_pipeline -- /path/to/images
The PyCOLMAP-style stage API (extract_features, incremental_mapping,
patch_match_stereo, …) is shown in examples/reconstruct.rs,
where the not-yet-ported stages report Error::Unimplemented:
cargo run --example reconstruct -- ../images
Camera models
All 17 COLMAP camera models are implemented with their exact parameter ordering, projection
(img_from_cam) and unprojection (cam_from_img, iterative where needed): SIMPLE_PINHOLE,
PINHOLE, SIMPLE_RADIAL, RADIAL, OPENCV, OPENCV_FISHEYE, FULL_OPENCV, FOV,
SIMPLE_RADIAL_FISHEYE, RADIAL_FISHEYE, THIN_PRISM_FISHEYE, RAD_TAN_THIN_PRISM_FISHEYE,
SIMPLE_DIVISION, DIVISION, SIMPLE_FISHEYE, FISHEYE, EUCM.
Cargo features
| Feature | Default | Description |
|---|---|---|
database |
off | SQLite feature database via bundled rusqlite (no system SQLite required). |
serde |
off | Serialize/Deserialize derives for the public data types. |
[dependencies]
colmap = { version = "0.1", features = ["database", "serde"] }
File-format compatibility
io reads and writes COLMAP's sparse model files byte-compatibly:
- Binary:
cameras.bin,images.bin,points3D.bin(little-endian, exact field widths). - Text:
cameras.txt,images.txt,points3D.txt.
Reconstruction::read/write auto-detect the format and round-trip is covered by tests.
Roadmap
The scaffolded modules are where contributions land next, roughly in order of leverage:
- SIFT feature extraction & matching (
feature). - RANSAC estimators: essential/fundamental/homography, PnP absolute pose (
estimators). - Incremental mapping (
sfm) and bundle adjustment (optim). - Dense MVS: patch-match stereo & fusion (
mvs).
License
BSD-3-Clause, matching upstream COLMAP.