After a sparse reconstruction (camera poses + sparse points), OpenSfM can compute a dense representation of the scene and derive 2D map products from it:
- a dense point cloud (
fused.ply, optionallyfused.las/fused.laz), - a triangle mesh (
mesh.ply, Surface Nets), - a Digital Surface Model raster (
dsm.tif) and an orthophoto (ortho.tif), - streaming octree tiles for the web/point-cloud viewer (
point_cloud/).
The math behind depthmap backprojection, plane-induced homographies and undistortion is in dense_matching.md. This page is the practical how-to.
- A sparse reconstruction (
reconstruction.json), i.e. the core pipeline up toreconstruct. - An undistorted reconstruction: run
undistortfirst. The dense stages operate exclusively onundistorted/(perspective images, no radial distortion; spherical panoramas are split into several perspective views — see dense_matching.md). - A GPU with OpenCL: depthmap estimation and fusion run on the GPU.
All dense artefacts live under undistorted/depthmaps/.
Dense reconstruction is split into four commands that hand off through disk, so each can run in its own process (bounded memory, clean teardown) — or even on a different machine sharing the dataset directory. Each later stage fails fast if an earlier stage's artefacts are missing.
| # | Command | Reads | Writes |
|---|---|---|---|
| 1 | dense_clustering |
undistorted reconstruction + tracks | clusters, super-points, neighbours, depth ranges, cluster bounding boxes |
| 2 | compute_depthmaps |
stage-1 artefacts + undistorted images | per-shot *.raw.npz → *.clean.npz depthmaps |
| 3 | fuse_depthmaps |
cleaned depthmaps + stage-1 artefacts | per-cluster fused_batch_*.ply (+ mesh_batch_*.ply, DSM/ortho tiles) |
| 4 | dense_merging |
per-cluster batches | fused.ply, mesh.ply, dsm.tif, ortho.tif, fused.las/.laz, point_cloud/ |
bin/opensfm undistort path/to/dataset
bin/opensfm dense_clustering path/to/dataset
bin/opensfm compute_depthmaps path/to/dataset
bin/opensfm fuse_depthmaps path/to/dataset
bin/opensfm dense_merging path/to/dataset # add --georeferenced for geo-located LAS/LAZ + DSM/orthoGroups covisible shots into small clusters and prepares the per-shot context the GPU stages need:
- Clusters — shots that see common surface are grouped (bounded by
depthmap_cluster_max_size) so fusion can process the scene in disjoint, memory-bounded chunks. - Super-points — sparse track points fused per cluster, used to seed depth ranges and priors.
- Neighbours — for each shot, the best matching views for stereo (gated by baseline angle,
depthmap_neighbor_min_angle/_max_angle) are stored asneighbors_best.json/neighbors_all.json. - Depth ranges — a per-shot
[min, max]depth used to bound the PatchMatch search.
Outputs: clusters.json, clusters_points.json, neighbors_best.json, neighbors_all.json, depth_ranges.json, cluster_bboxes.json.
Estimates a depth + normal map for every shot, then cleans it.
- Estimation — a PatchMatch-based multi-view stereo (ACMMP-style: adaptive checkerboard propagation with multi-hypothesis sampling) running on the GPU via OpenCL. It is multi-scale (
depthmap_hierarchy_levels), uses a bilateral-NCC matching cost with a Census-transform fallback in low-texture regions (depthmap_use_census), and can add a geometric-consistency term across neighbouring views (depthmap_geom_consistency_weight). - Cleaning — a consistency + visibility pass keeps only depths confirmed by enough neighbours (
depthmap_min_consistent_views) and carves away depths that are occluded by other views (depthmap_carving_threshold, two-pass by default).
Outputs (per shot, lz4-compressed NumPy): <image>.raw.npz then <image>.clean.npz. The raw map is deleted once its clean counterpart exists (depthmap_delete_raw_after_clean). Set depthmap_save_debug_ply to also dump per-shot/per-cluster PLYs for inspection.
See the Depth Estimation config for all knobs.
Fuses the cleaned depthmaps into a surface using a sparse-voxel-octree (SVO) TSDF integrator, one cluster at a time over disjoint territories (so two clusters never fuse the same surface twice).
- Voxel size is automatic, derived from the data's median per-pixel ground footprint (
depth/focal);depthmap_fusion_svo_voxel_levelselectsfine/half/quarterof that resolution. - Mesh — when
depthmap_fusion_mesh_enabledis set (opt-in, off by default), a Surface Nets (dual-contouring) triangle mesh is extracted from the same TSDF zero-set. Unlike Poisson it never balloons facades, but it leaves holes where the volume is empty. - 2D tiles — when
dsm_enabled(default), each cluster also renders its slice of the DSM and orthophoto.
Outputs per cluster: fused_batch_*.ply (xyz + normal + rgb), mesh_batch_*.ply, dsm_ortho_batch_*.npz. See the Fusion config.
Merges the per-cluster batches into the final products and exports them:
fused.ply— the merged dense point cloud (always topocentric).mesh.ply— the merged Surface Nets mesh, whendepthmap_fusion_mesh_enabledis set (always topocentric).dsm.tif/ortho.tif— the merged DSM and orthophoto GeoTIFFs (per-cluster tiles are feather-blended,dsm_merge_feather).fused.las/fused.laz— the dense cloud as LAS/LAZ, whendense_pointcloud_export_las/_lazare enabled.point_cloud/— Potree-style octree tiles for streaming in a web/point-cloud viewer (see Octree Tiling).
Add --georeferenced to write the LAS/LAZ and DSM/ortho in the output coordinate system instead of the topocentric frame. The point cloud PLY, mesh and octree always stay topocentric. See Georeferencing & GIS outputs for how the output CRS is chosen and a full GIS walkthrough.
All paths are relative to undistorted/depthmaps/.
| File | Format | Produced by | Georeferenced? |
|---|---|---|---|
<image>.clean.npz |
lz4 NPZ (depth, normal, score) | compute_depthmaps |
— |
fused.ply |
binary PLY (xyz + normal + rgb) | dense_merging |
no (topocentric) |
mesh.ply |
binary PLY mesh | dense_merging |
no (topocentric) |
fused.las, fused.laz |
LAS / LAZ 1.4 | dense_merging |
with --georeferenced |
dsm.tif |
single-band Float32 GeoTIFF | dense_merging |
with --georeferenced (else topocentric grid) |
ortho.tif |
RGB + alpha GeoTIFF | dense_merging |
with --georeferenced (else topocentric grid) |
point_cloud/ |
Potree octree tiles | dense_merging |
no (topocentric) |
Legacy note: older docs/tools refer to
merged.ply; the current pipeline writesfused.ply.export_geocoords --densestill reads the legacymerged.ply— for georeferenced dense products usedense_merging --georeferenced.
The DSM (dsm.tif) is a top-down height raster; the orthophoto (ortho.tif) is a top-down, perspective-corrected color image on the same grid. Both are rendered from the fused surface during stage 3 and finalized in stage 4. The ground sample distance is derived automatically from the fused voxel size.
DSM/ortho quality is shaped by a dedicated config group (DSM and Orthophoto): shape-aware hole filling (compact gaps flat-filled, elongated ones edge-aware diffused), a coherence-enhancing shock filter that sharpens edges, robust multi-view color baking with detail injection for the ortho, and feather blending across cluster tiles. Set dsm_enabled: false to skip 2D maps entirely.
- Run stages separately. The four-command split keeps peak memory bounded; you can stop and resume between stages, and re-run a single stage after changing its config.
- Disk reclamation. Intermediate artefacts are removed by default once consumed (
depthmap_delete_raw_after_clean,depthmap_delete_fusion_batches,depthmap_fusion_mesh_delete_batches). Set the relevant flag tofalseto keep them for debugging. See Dense disk reclamation and export. - Image size.
depthmap_max_image_sizecaps the processed resolution;undistorted_image_max_sizecaps the undistorted images.
- Point cloud / mesh: open
fused.plyormesh.plyin MeshLab or any PLY viewer. - DSM / ortho: open the GeoTIFFs in QGIS or any GIS tool.
- Web viewer: serve the
point_cloud/octree (Potree-style tiles). - Rerun:
export_rerunfor an interactive 3D session.
- Pipeline commands — command reference.
- Configuration reference — Depth Estimation, Fusion, DSM and Orthophoto, Octree Tiling.
- Georeferencing & GIS outputs — output CRS, GCPs, and a large-scale aerial walkthrough.
- Dense matching notes — the underlying math.

