176 lines
6.3 KiB
Rust
176 lines
6.3 KiB
Rust
//! Incremental Structure-from-Motion (SfM).
|
|
//!
|
|
//! This module mirrors the high-level incremental mapping pipeline of
|
|
//! [PyCOLMAP](https://colmap.github.io/pycolmap/) (`pycolmap.incremental_mapping`
|
|
//! and the underlying `IncrementalMapper` / `IncrementalPipeline` controllers).
|
|
//!
|
|
//! It exposes the **option structs** and **driver function signatures** that the
|
|
//! C++ implementation uses, so that calling code can be written and type-checked
|
|
//! against the stable public surface. The heavy numerical core — robust two-view
|
|
//! initialization, absolute-pose registration, triangulation and bundle
|
|
//! adjustment scheduling — is not ported in this pure-Rust crate yet and the
|
|
//! driver functions therefore return [`crate::Error::Unimplemented`].
|
|
|
|
use std::path::Path;
|
|
|
|
use crate::scene::Reconstruction;
|
|
|
|
/// Options that control a single run of the incremental mapper.
|
|
///
|
|
/// These mirror a subset of COLMAP's `IncrementalMapperOptions`. Only the most
|
|
/// load-bearing knobs are surfaced here; the defaults match COLMAP's defaults.
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
pub struct IncrementalMapperOptions {
|
|
/// Minimum number of two-view inliers required to initialize the model
|
|
/// from the seed image pair.
|
|
pub init_min_num_inliers: usize,
|
|
/// Minimum number of inliers required to accept an absolute-pose
|
|
/// (image registration) estimate.
|
|
pub abs_pose_min_num_inliers: usize,
|
|
/// Minimum number of feature matches an image pair must have to be used
|
|
/// at all during mapping.
|
|
pub min_num_matches: usize,
|
|
/// Whether to reconstruct multiple disjoint models from the remaining,
|
|
/// not-yet-registered images.
|
|
pub multiple_models: bool,
|
|
/// Upper bound on the number of distinct models to reconstruct when
|
|
/// [`multiple_models`](Self::multiple_models) is enabled.
|
|
pub max_num_models: usize,
|
|
}
|
|
|
|
impl Default for IncrementalMapperOptions {
|
|
fn default() -> Self {
|
|
Self {
|
|
init_min_num_inliers: 100,
|
|
abs_pose_min_num_inliers: 30,
|
|
min_num_matches: 15,
|
|
multiple_models: true,
|
|
max_num_models: 50,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Options that control the full incremental SfM pipeline.
|
|
///
|
|
/// Mirrors a subset of COLMAP's `IncrementalPipelineOptions`, which wraps an
|
|
/// [`IncrementalMapperOptions`] together with pipeline-level scheduling knobs
|
|
/// such as how often global bundle adjustment is triggered.
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
pub struct IncrementalPipelineOptions {
|
|
/// Minimum number of feature matches an image pair must have to be used.
|
|
pub min_num_matches: usize,
|
|
/// The growth ratio of registered images that triggers a new round of
|
|
/// global bundle adjustment (e.g. `1.1` => re-run when the model grows
|
|
/// by 10%).
|
|
pub ba_global_images_ratio: f64,
|
|
/// Options forwarded to the underlying incremental mapper.
|
|
pub mapper: IncrementalMapperOptions,
|
|
}
|
|
|
|
impl Default for IncrementalPipelineOptions {
|
|
fn default() -> Self {
|
|
Self {
|
|
min_num_matches: 15,
|
|
ba_global_images_ratio: 1.1,
|
|
mapper: IncrementalMapperOptions::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Run the full incremental Structure-from-Motion pipeline.
|
|
///
|
|
/// Reads features and matches from the COLMAP feature database at
|
|
/// `database_path`, registers images one by one, triangulates points and
|
|
/// bundle-adjusts, writing the resulting sparse model(s) under `output_path`.
|
|
/// Mirrors `pycolmap.incremental_mapping`.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `database_path` — path to the SQLite feature database.
|
|
/// * `image_path` — root directory containing the source images.
|
|
/// * `output_path` — directory under which reconstructed model(s) are written.
|
|
/// * `options` — pipeline configuration.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns [`crate::Error::Unimplemented`]; the numerical SfM core is not ported
|
|
/// in this pure-Rust crate.
|
|
pub fn incremental_mapping(
|
|
database_path: &Path,
|
|
image_path: &Path,
|
|
output_path: &Path,
|
|
options: &IncrementalPipelineOptions,
|
|
) -> crate::Result<Vec<Reconstruction>> {
|
|
let _ = (database_path, image_path, output_path, options);
|
|
Err(crate::Error::Unimplemented("incremental SfM mapping"))
|
|
}
|
|
|
|
/// Triangulate additional 3D points into an existing reconstruction.
|
|
///
|
|
/// Given an already-registered set of images (poses fixed), this completes and
|
|
/// merges feature tracks read from the database into new and existing 3D points.
|
|
/// Mirrors `pycolmap.triangulate_points`.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns [`crate::Error::Unimplemented`]; triangulation is not ported in this
|
|
/// pure-Rust crate.
|
|
pub fn triangulate_points(
|
|
rec: &mut Reconstruction,
|
|
database_path: &Path,
|
|
) -> crate::Result<()> {
|
|
let _ = (rec, database_path);
|
|
Err(crate::Error::Unimplemented("point triangulation"))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::path::Path;
|
|
|
|
#[test]
|
|
fn mapper_options_defaults() {
|
|
let o = IncrementalMapperOptions::default();
|
|
assert_eq!(o.init_min_num_inliers, 100);
|
|
assert_eq!(o.abs_pose_min_num_inliers, 30);
|
|
assert_eq!(o.min_num_matches, 15);
|
|
assert!(o.multiple_models);
|
|
assert_eq!(o.max_num_models, 50);
|
|
}
|
|
|
|
#[test]
|
|
fn pipeline_options_defaults() {
|
|
let o = IncrementalPipelineOptions::default();
|
|
assert_eq!(o.min_num_matches, 15);
|
|
assert!((o.ba_global_images_ratio - 1.1).abs() < 1e-12);
|
|
assert_eq!(o.mapper, IncrementalMapperOptions::default());
|
|
}
|
|
|
|
#[test]
|
|
fn incremental_mapping_is_unimplemented() {
|
|
let err = incremental_mapping(
|
|
Path::new("db.db"),
|
|
Path::new("images"),
|
|
Path::new("out"),
|
|
&IncrementalPipelineOptions::default(),
|
|
)
|
|
.unwrap_err();
|
|
assert!(matches!(err, crate::Error::Unimplemented(_)));
|
|
}
|
|
|
|
#[test]
|
|
fn triangulate_points_is_unimplemented() {
|
|
let mut rec = Reconstruction {
|
|
rigs: Default::default(),
|
|
cameras: Default::default(),
|
|
frames: Default::default(),
|
|
images: Default::default(),
|
|
points3d: Default::default(),
|
|
};
|
|
let err = triangulate_points(&mut rec, Path::new("db.db")).unwrap_err();
|
|
assert!(matches!(err, crate::Error::Unimplemented(_)));
|
|
}
|
|
}
|