cumulus3d-dense 0.8.0

Undistortion and PatchMatch-style multi-view stereo (depth maps, filtering, fusion) for cumulus3d
Documentation

English | 한국어

cumulus3d-dense

Undistortion and multi-view densification: estimates, filters, and fuses depth maps from a registered sparse model and its images to produce a dense point cloud (PLY).

Pipeline stage: the last stage, after SfM (cumulus3d-sfm) and GPS alignment (cumulus3d-align). Sparse model → undistortion (PINHOLE) → densification scene → neighbor views and depth ranges → multi-scale PatchMatch depth maps → filtering and post-processing → fusion → dense point cloud.

PatchMatch execution itself (red–black propagation at one scale, view selection, refinement, readout) is handled by a backend implementing PatchMatchBackend; this crate handles everything around it (scene, neighbor selection, depth ranges, multi-scale progression, upsampling, detail-restoration decisions, filter thresholds, post-processing, fusion, statistics).

Main entry points

Function/type Role Input → output
undistort Undistort all registered images (image loading is a caller closure) &Reconstruction, &UndistortOptions, &UndistortCache, Fn(&Image) -> Option<Arc<ImageBuffer>> → Result<UndistortResult>
undistort_from_dir Read from an image folder and undistort &Reconstruction, image folder, options, cache → Result<UndistortResult>
write_undistorted_workspace Write the undistorted result to a workspace folder (images/, sparse/, stereo/) &UndistortResult, output folder, options → Result<()>
DenseScene::from_reconstruction Build a scene from an in-memory undistorted model and images &Reconstruction, &BTreeMap<ImageId, Arc<ImageBuffer>>, &SceneOptions → Result<DenseScene>
DenseScene::from_workspace_dir Build a scene from an undistorted workspace folder folder, &SceneOptions → Result<DenseScene>
DensifyOptions::with_profile Default settings for a profile (Fast/Quality) MvsProfile → DensifyOptions
densify Full densification (depth maps → filtering → fusion) &DenseScene, &DensifyOptions, &dyn PatchMatchBackend, Option<&DepthMapCache> → Result<DenseOutput>
densify::densify_with densify + optional score fusion the above + Option<&ScoreFusionOptions> → Result<DenseOutput>
compute_depth_maps Compute depth maps only (no fusion) &DenseScene, &DensifyOptions, backend, cache → Result<DepthMapSet>
fuse_depth_maps Fuse computed depth maps &DenseScene, &DepthMapSet, &DensifyOptions, thread count → FusionOutput
densify::fuse_output Re-run only fusion from stored depth maps &DenseScene, &DepthMapSet, &DensifyOptions, Option<&ScoreFusionOptions> → DenseOutput
fuse Fuse an array of depth maps (low level) &DenseScene, &[Option<FusionInput>], overlap lists, &FusionParams, thread count → FusionOutput
DenseOutput::write_ply Write the point cloud as PLY (x y z nx ny nz red green blue) path → Result<()>
cloud_stats Point cloud quality statistics (spacing, GSD, outlier and duplicate rates) scene, depth maps, point cloud, visibility, sample count, tolerance → CloudStats
PatchMatchBackend Backend boundary trait (GPU implementation is cumulus3d-cuda::CudaPatchMatch) &KernelInput → Box<dyn PatchMatchSession>

Public items

"Root" marks items re-exported at the crate root as cumulus3d_dense::name.

undistort — undistortion

Item Kind Role
UndistortOptions (root) struct Blank-pixel ratio, scale range, max_image_size, region of interest, number of source images, JPEG quality. pipeline(): max_image_size = 960
undistorted_camera (root) fn Compute the distortion-free PINHOLE camera (keeps the camera id)
CameraUndistortion (root) struct Undistortion result for one camera (source, pinhole, resampling map). new, undistort_image
UndistortCache (root) struct Cache of camera-parameter hash → CameraUndistortion (shared across zones). new, get, len, is_empty
undistort_reconstruction (root) fn Transform the sparse model: cameras to PINHOLE, 2D observations to new coordinates
UndistortResult (root) struct reconstruction, images (image id → undistorted image), failed
undistort (root) fn Undistort all registered images (image loader closure)
undistort_from_dir (root) fn Read from an image folder and undistort
write_undistorted_workspace (root) fn Write the undistorted result to a dense reconstruction workspace folder

image — image buffers

Item Kind Role
ImageBuffer (root) struct 8-bit 1/3-channel image. new, load, save, get, rgb, to_gray, resize_area
GrayImage (root) struct f32 grayscale image. new, at, at_clamped, bilinear_clamped, resize_area, resize_cubic, rescale
Integral struct Integral image (f64). new, box_mean, resample (area-average resampling)
bilinear_clamped fn Bilinear interpolation with integer-center convention (edge-clamped)

scene — densification scene

Item Kind Role
SceneOptions (root) struct max_image_size (upper bound on the long side, downscale only)
DenseView (root) struct One view (image_id, name, size, k, r, t, gray, color). center, to_cam, to_world, dir_to_world, dir_to_cam, project_cam, ray, geometry_hash
ScenePoint (root) struct Sparse point (xyz, indices of observing views)
DenseScene (root) struct views, points. from_reconstruction, from_workspace_dir
gray_of fn RGB → 8-bit gray

params — settings

Item Kind Role
DensifyOptions (root) struct Full densification settings. with_profile, schedule, fingerprint (hash for cache keys)
MvsProfile (root) enum Fast (default) / Quality. parse, name
LevelSchedule (root) struct Per-scale iteration schedule (photometric iterations, detail-restoration initialization, geometric rounds and iterations)
PmParams (root) struct PatchMatch constants (window, bilateral σ, voting τ0/α/τ1/β/n1/n2, priors, geometric λ/δ, refinement perturbation). sigma_s, window_offsets
FilterParams (root) struct Depth map filter tolerances (min_ncc, minimum triangulation angle, minimum consistent views, reprojection tolerance, median filter)
FusionMode (root) enum Traversal (expansion median) / Consistency (consistent average, default). parse
FusionResidual (root) enum Handling of leftover pixels: None / Release / SecondPass. parse, name
ResidualParams (root) struct Second-pass fusion tolerances
FusionParams (root) struct Fusion tolerances (mode, minimum consistent views, inverse-variance weighting, depth/normal/reprojection tolerances, etc.)
NeighborParams (root) struct Number of neighbor views (≤ 32), minimum triangulation angle, directional diversity

kernel — backend boundary

Item Kind Role
PatchMatchBackend (root) trait Backend: name, begin(&KernelInput) -> Box<dyn PatchMatchSession>
PatchMatchSession (root) trait Backend state for the duration of one call: run, evaluate, filter, upsample/median_filter (default implementation = host computation)
KernelInput (root) struct Full kernel input (views, number of scales, PmParams, FilterParams, seed)
KernelView (root) struct One view (random key, per-scale images, source views, relative geometry, depth range)
LevelImage (root) struct Grayscale image and K at one scale
PairGeometry (root) struct Reference → source relative geometry (r, t, center)
ViewState (root) struct Pixel state (depth, normal, cost, coarse-scale hypothesis). new
DepthSnapshot (root) struct Depth snapshot read by geometric runs
RunParams (root) struct Parameters for one run (scale, geometric or not, initialization, iteration count, run number, prior usage)
rng_state, rng_next fn Per-pixel random number definition identical to the kernel

densify — progression

Item Kind Role
densify (root) fn Full densification: depth maps → filtering → fusion
densify_with fn densify + optional score fusion
compute_depth_maps (root) fn Compute final depth maps for all views
fuse_depth_maps (root) fn Fuse depth maps (opts.fusion.mode)
fuse_depth_maps_with fn Fuse depth maps + optional score fusion
fuse_output fn Re-run only fusion from stored depth maps and build a DenseOutput
pair_geometry (root) fn Reference → source relative geometry
DenseOutput (root) struct cloud, visibility, timings, cache_hits, depth_views, depth_maps, residual. write_ply, num_residual, write_residual_ply
DepthMapSet (root) struct Per-view depth maps, source views, baselines, cache hits, number of scales, timings
DepthMapResult (root) struct One depth map (pre-/post-filter depth, normal, cost, whether cached)
DenseTimings (root) struct Per-stage timings. depth, total

fusion, fusion_score — fusion

Item Kind Role
fuse (root) fn Consistency or traversal fusion depending on FusionParams::mode
fuse_consistency fn Consistency fusion (average of the reference pixel and consistent points in overlapping views; deterministic)
fuse_traversal fn Neighbor-of-neighbor expansion + per-component median fusion
FusionInput (root) struct Depth, normal, cost, and baseline of one view. plain
FusionOutput (root) struct Point cloud, visibility, second-pass fusion flags, timings. num_residual, residual_cloud
fusion_score::ScoreFusionOptions struct Score fusion options. from_fusion
fusion_score::fuse_scored fn Score fusion (consistency contribution − free-space violation penalty)

neighbors — neighbor views and depth ranges

Item Kind Role
PairStats struct Shared point count and triangulation angle per view pair. new, select, select_diverse, select_all
depth_ranges fn Per-view depth range (sparse point depth 1%/99% × 0.75/1.25)
depth_range_of fn Range of a single depth list

upsample — pyramid and upsampling

Item Kind Role
num_levels fn Compute the number of scales
build_pyramid fn Grayscale pyramid (0 = coarsest) and per-scale K
joint_bilateral_upsample fn Joint bilateral upsampling
median_plane_filter fn 5×5 median plane filter
downsample_depth fn Downsample a depth map by 2×

postproc — post-processing

Item Kind Role
PostParams (root) struct Speckle removal and hole filling settings
remove_speckles fn Remove small speckles (number of pixels removed)
fill_holes fn Edge-aware hole filling (number of pixels filled)

stats — quality statistics

Item Kind Role
cloud_stats (root) fn Median point spacing, GSD, outlier ratio, duplicate rate, local plane deviation
CloudStats (root) struct The statistics above

cache — depth map cache

Item Kind Role
DepthMapCache (root) struct (image geometry hash, settings hash) → depth map; thread-safe, capacity-limited. new, with_capacity, get, insert, len, is_empty
CachedDepth (root) struct One cached view (pre-/post-filter depth, normal, cost)

math — numerical tools

Item Kind Role
hash_bytes, mix64 fn Deterministic hash (FNV-1a), splitmix64 mixing
erf fn Error function
emission_norm, emission, visibility_probability fn Emission density of a cost and visibility probability
triangulation_prior, incident_prior, resolution_prior fn View selection priors
apply_h fn Transfer a point with a 3×3 homography
quantile_sorted, median_in_place fn Quantile, median
plane_transfer fn Depth of a neighbor plane transferred to another ray

synthetic — synthetic scenes for testing

Item Kind Role
make_scene fn Textured floor + box scene, grid of cameras, ground-truth depth and normals, sparse points
SynthConfig struct Image size, focal length, camera grid, boxes, seed, number of sparse points
SynthScene struct scene, ground-truth depth/normal, config. surface_distance
SynthBox struct Axis-aligned box
texture fn Deterministic texture function

Examples

Fuse the ground-truth depth maps of a synthetic scene on the CPU (no backend needed; same as the doc-test in src/lib.rs):

use cumulus3d_dense::neighbors::PairStats;
use cumulus3d_dense::synthetic::{make_scene, SynthConfig};
use cumulus3d_dense::{fuse, FusionInput, FusionParams};

let cfg = SynthConfig { width: 96, height: 72, focal: 75.0, num_points: 500, ..SynthConfig::default() };
let s = make_scene(&cfg);
let stats = PairStats::new(&s.scene);
let overlap: Vec<Vec<usize>> = (0..s.scene.views.len()).map(|v| stats.select(v, 50, 0.0)).collect();
let inputs: Vec<Option<FusionInput>> =
    s.depth.iter().zip(&s.normal).map(|(d, n)| Some(FusionInput::plain(d, n))).collect();
let out = fuse(&s.scene, &inputs, &overlap, &FusionParams::default(), 1);
assert!(out.cloud.len() > 0);

Real densification (requires a GPU backend and real data; the doc-test is no_run):

use cumulus3d_dense::{densify, undistort_from_dir, DenseScene, DensifyOptions, PatchMatchBackend, SceneOptions, UndistortCache, UndistortOptions};

fn run(backend: &dyn PatchMatchBackend) -> cumulus3d_core::Result<()> {
    let rec = cumulus3d_core::interop::read_model("sparse/0")?;
    let und = undistort_from_dir(&rec, "images", &UndistortOptions::pipeline(), &UndistortCache::new())?;
    let scene = DenseScene::from_reconstruction(&und.reconstruction, &und.images, &SceneOptions::default())?;
    let out = densify(&scene, &DensifyOptions::default(), backend, None)?;
    out.write_ply("dense/fused.ply")?;
    Ok(())
}

Pass cumulus3d_cuda::CudaPatchMatch as backend (see the cumulus3d-cuda crate).

Feature flags and hardware

  • No feature flags. Pure Rust; builds on any machine.
  • Densification (densify, compute_depth_maps) requires a PatchMatchBackend implementation. This crate has no CPU backend; the only current implementation is CudaPatchMatch in cumulus3d-cuda (NVIDIA GPU, CUDA 12.x driver).
  • What works on the CPU without a backend: undistortion, scene construction, neighbor selection and depth ranges, upsampling and median filtering, post-processing, fusion (fuse, fuse_depth_maps, fuse_output), statistics, synthetic scenes. Parallelism via rayon.

Processing flow

  1. Scene: undistorted model (pinhole) + images → views (K, pose, gray/color). Pixel coordinates inside the scene are integer = pixel center (principal point −0.5).
  2. Neighbor views: from sparse point tracks, compute the shared point count and the 75th-percentile triangulation angle for each view pair, and pick up to 10 views with an angle of at least 1°, ordered by shared count. Optionally, per-bin attenuation over baseline azimuth spreads the chosen directions evenly (NeighborParams::diversity_decay < 1).
  3. Depth range: the 1%/99% values of observed sparse point depths times 0.75/1.25. Used only for random initialization.
  4. Multi-scale (downscale factor 0.5, up to 3 levels): the coarsest scale runs a photometric pass from random initialization; higher scales do joint bilateral upsampling → detail restoration (pixels where upsampled-hypothesis cost − photometric-pass cost > 0.1 are replaced with the photometric result) → 2 geometric-consistency runs (double-buffered: every reference view reads the previous round's depth snapshot, so the result is independent of execution order).
  5. Readout and filtering: after a 5×5 median plane filter, keep only pixels for which at least 2 source views (or the number of source views, if fewer) satisfy all of: triangulation angle ≥ 3°, incidence cos > 0, emission visibility probability ≥ E (0.9), forward–backward reprojection ≤ 1 px.
  6. Post-processing: remove small, high-cost components among relative-depth-connected regions; optionally fill holes, stopping at intensity edges.
  7. Fusion: the default is consistency fusion — project the reference pixel's point into overlapping views; if at least 5 views agree in depth (1% relative), reprojection (2 px), and normal (10°), output the inverse-variance-weighted average of the reference and the consistent points (σ_d = d²·σ_px/(f·b), σ_px = 0.25 + cost). Traversal median fusion (FusionMode::Traversal) and score fusion (ScoreFusionOptions) are also available.
  8. Output: PLY (x y z nx ny nz red green blue, 27 bytes/point).

Profiles

Fast (default) Quality
Window radius 5, step 2 (36 samples) radius 5, step 1 (121 samples)
Coarsest scale photometric 6 + geometric 2×2 photometric 7 + geometric 2×6
Higher scales detail restoration 3 (starting from upsampled hypothesis) + geometric 2×2 detail restoration 6 (random start) + geometric 2×6

Determinism

Per-pixel random numbers come from a counter-based sequence determined by (seed, image name hash, scale, run, iteration, half-step, pixel), so they are independent of thread scheduling. Geometric runs use a double-buffered snapshot, so they are independent of view processing order. Consistency fusion and score fusion are also deterministic (traversal fusion is not when run in parallel).

Tests

  • cargo test -p cumulus3d-dense: numerical reference values, neighbor selection, relative geometry, upsampling and median filtering, fusion of synthetic-scene ground-truth depth maps, second-pass fusion, post-processing, undistortion.
  • GPU accuracy: tests/patchmatch_gpu.rs in cumulus3d-cuda (compared against synthetic-scene ground truth).

References

  • M. Bleyer, C. Rhemann, C. Rother. PatchMatch Stereo – Stereo Matching with Slanted Support Windows. BMVC 2011.
  • S. Galliani, K. Lasinger, K. Schindler. Massively Parallel Multiview Stereopsis by Surface Normal Diffusion. ICCV 2015.
  • J. L. Schönberger, E. Zheng, J.-M. Frahm, M. Pollefeys. Pixelwise View Selection for Unstructured Multi-View Stereo. ECCV 2016.
  • Q. Xu, W. Tao. Multi-Scale Geometric Consistency Guided Multi-View Stereo. CVPR 2019.
  • J. Kopf, M. F. Cohen, D. Lischinski, M. Uyttendaele. Joint Bilateral Upsampling. SIGGRAPH 2007.

License

Apache-2.0 (LICENSE, NOTICE).