Multi-View Fusion and Surface Reconstruction
=============================================
This page explains the final stage of the AquaMVS pipeline: fusing depth
maps from multiple cameras into a unified point cloud, filtering geometric
inconsistencies, and reconstructing surface meshes.
Multi-View Depth Fusion
------------------------
Each camera produces an independent depth map (see :doc:`dense_stereo`).
These depth maps overlap spatially but may contain inconsistencies due to
occlusions, noise, or matching errors. Fusion combines multi-view information
to produce a single, cleaned point cloud.
**Problem Statement**
Given :math:`N` cameras with depth maps :math:`\{D_i(u, v)\}_{i=1}^N`
and confidence maps :math:`\{C_i(u, v)\}_{i=1}^N`, produce a fused point
cloud :math:`\mathcal{P} = \{\mathbf{p}_j\}_{j=1}^M` where each point
is geometrically consistent across multiple views.
**Geometric Consistency Filtering**
For each pixel in reference camera :math:`R` with depth :math:`d_R(u, v)`:
1. **Back-project to 3D**: Compute the 3D point using the refractive ray
model (see :doc:`refractive_geometry`):
.. math::
\mathbf{p} = \mathbf{O}_R + d_R(u, v) \, \mathbf{d}_R(u, v)
where :math:`\mathbf{O}_R` is the ray origin and :math:`\mathbf{d}_R`
is the ray direction for pixel :math:`(u, v)`.
2. **Project into source cameras**: For each source camera :math:`S_j`,
project :math:`\mathbf{p}` to get pixel :math:`(u_j, v_j)`.
3. **Compare depths**: Retrieve the depth :math:`d_j(u_j, v_j)` from the
source depth map. Back-project the source pixel to 3D point :math:`\mathbf{p}_j`.
4. **Compute 3D distance**:
.. math::
\Delta_j = \|\mathbf{p} - \mathbf{p}_j\|
5. **Consistency check**: Mark as consistent if:
.. math::
\Delta_j < \tau_{\text{dist}}
where :math:`\tau_{\text{dist}}` is a distance threshold (e.g., 0.01 m = 1 cm).
6. **Count consistent views**: If the point is consistent with at least
:math:`N_{\min}` source cameras (e.g., :math:`N_{\min} = 2`), retain it.
Otherwise, discard as an outlier.
**Consistency Score**
For visualization and analysis, each point is tagged with a consistency
score:
.. math::
\text{score}(\mathbf{p}) = \frac{\text{\# consistent views}}{\text{total \# source cameras}}
High scores indicate strong multi-view agreement.
**Fusion Pipeline Diagram**
.. mermaid::
flowchart LR
DM1["Depth Map 1
(Camera A)"] --> GC["Geometric
Consistency
Filter"]
DM2["Depth Map 2
(Camera B)"] --> GC
DMN["Depth Map N
(Camera C)"] --> GC
GC --> FPC["Fused
Point Cloud"]
FPC --> OR["Outlier
Removal"]
OR --> SR["Surface
Reconstruction"]
style GC fill:#ffb74d,stroke:#f57c00,stroke-width:2px
style FPC fill:#81c784,stroke:#388e3c,stroke-width:2px
Point Cloud Generation
-----------------------
After geometric consistency filtering, valid depth pixels are converted to
3D points with color.
**Back-Projection**
For each valid pixel :math:`(u, v)` in reference camera :math:`R` with
depth :math:`d(u, v)`:
.. math::
\mathbf{p} = \mathbf{O}_R(u, v) + d(u, v) \, \mathbf{d}_R(u, v)
where :math:`(\mathbf{O}_R, \mathbf{d}_R)` is the ray from the refractive
projection model.
**Color Assignment**
Point color is taken from the reference image at pixel :math:`(u, v)`:
.. math::
\mathbf{c} = I_R(u, v)
For better color fidelity, colors can be averaged across consistent views
(not currently implemented in AquaMVS).
**Statistical Outlier Removal**
Even after consistency filtering, some outliers may remain. Statistical
outlier removal cleans the point cloud:
1. For each point :math:`\mathbf{p}_i`, find its :math:`k` nearest neighbors.
2. Compute mean distance to neighbors: :math:`\bar{d}_i`.
3. Compute global statistics across all points:
.. math::
\mu = \text{mean}(\{\bar{d}_i\}), \quad
\sigma = \text{std}(\{\bar{d}_i\})
4. Remove outliers where:
.. math::
\bar{d}_i > \mu + \lambda \sigma
(e.g., :math:`k = 20`, :math:`\lambda = 2.0`).
This filters isolated points far from the main surface.
**Merging Overlapping Regions**
When multiple cameras view the same region, their point clouds overlap.
AquaMVS currently retains all points (no explicit deduplication). For very
dense clouds, voxel downsampling can reduce redundancy:
.. math::
\text{voxel\_size} = 0.001 \text{ m (1 mm)}
Surface Reconstruction
----------------------
Point clouds are useful but often a continuous surface mesh is desired for
visualization, physics simulation, or further processing. AquaMVS supports
three surface reconstruction methods.
Poisson Surface Reconstruction
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Overview**
Poisson reconstruction solves for a smooth, watertight surface that best
fits an oriented point cloud (points + normals). It's robust and produces
high-quality meshes but may hallucinate geometry in regions without point
coverage.
**Algorithm**
Given point cloud :math:`\mathcal{P} = \{\mathbf{p}_i, \mathbf{n}_i\}_{i=1}^N`
(points and normals):
1. **Indicator Function**: Compute a volumetric indicator function
:math:`\chi(\mathbf{x})` that is 1 inside the surface and 0 outside.
The gradient of :math:`\chi` aligns with the normals:
.. math::
\nabla \chi(\mathbf{x}) \approx \mathbf{n}_i \text{ near } \mathbf{p}_i
2. **Poisson Equation**: Solve the Poisson equation:
.. math::
\Delta \chi = \nabla \cdot \mathbf{V}
where :math:`\mathbf{V}` is a vector field constructed from point normals.
3. **Isosurface Extraction**: Extract the :math:`\chi = 0.5` isosurface
using marching cubes to get the triangle mesh.
**Parameters**
* **Depth**: Octree depth controlling mesh resolution (default: 9).
Higher depth → finer mesh but slower computation.
* **Density Filtering**: Poisson fills gaps, so low-density regions are
trimmed using a density percentile threshold (e.g., 1st percentile).
**Pros**
* Smooth, watertight mesh
* Handles noise well
* Good for general-purpose reconstruction
**Cons**
* May fill holes with hallucinated geometry
* Requires normal estimation (done automatically)
**Use Case**
Best for smooth surfaces (e.g., sand beds, rock surfaces) where gaps
should be interpolated.
Height-Field Reconstruction
~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Overview**
Height-field reconstruction projects the point cloud onto a regular XY
grid and interpolates Z values. Assumes the surface is roughly planar and
single-valued in Z (no overhangs).
**Algorithm**
1. **Grid Definition**: Create a regular 2D grid in the XY plane:
.. math::
x_i \in [x_{\min}, x_{\max}], \quad
y_j \in [y_{\min}, y_{\max}]
with resolution :math:`\Delta` (e.g., 5 mm).
2. **Interpolation**: For each grid cell :math:`(x_i, y_j)`, interpolate
Z value from nearby points using linear interpolation:
.. math::
z_{ij} = \text{interp}\left(\{(x_k, y_k) \to z_k\}_{k=1}^N, (x_i, y_j)\right)
(implemented via ``scipy.interpolate.griddata``).
3. **Triangulation**: Connect neighboring grid cells with triangles to
form a mesh. Each :math:`2 \times 2` grid cell produces 2 triangles.
4. **Color Interpolation**: Interpolate point colors onto grid vertices
similarly.
**Parameters**
* **Grid Resolution**: Grid spacing in meters (default: 0.005 m = 5 mm).
**Pros**
* Fast
* Preserves fine detail
* Simple and predictable
**Cons**
* Cannot represent overhangs or vertical surfaces
* Assumes planar geometry
* Gaps in coverage → holes in mesh
**Use Case**
Best for approximately planar surfaces viewed from above (e.g., water
surface, sand bed in overhead view).
Ball Pivoting Algorithm (BPA)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Overview**
BPA "rolls" a virtual ball over the point cloud, creating triangles where
the ball touches three points. Preserves detail and requires no normal
orientation but is sensitive to sampling density.
**Algorithm**
1. **Seed Triangle**: Find an initial triangle where a ball of radius
:math:`r` rests on three points without containing other points.
2. **Pivot**: For each triangle edge, pivot the ball around the edge until
it touches a third point, forming a new triangle.
3. **Grow**: Repeat pivoting until no new triangles can be formed.
**Parameters**
* **Radii**: List of ball radii to try (e.g., ``[0.005, 0.01, 0.02]``).
Multiple radii handle varying point density.
**Pros**
* Preserves fine geometric detail
* No normal estimation required
* Non-watertight (doesn't fill gaps)
**Cons**
* Sensitive to point density variations
* May produce disconnected patches
* Requires parameter tuning (ball radii)
**Use Case**
Best for detailed surfaces with uniform point sampling where you want to
avoid gap-filling (e.g., high-resolution scans of textured objects).
Mesh Simplification
-------------------
High-resolution meshes can have millions of faces, which is impractical for
visualization or downstream processing. Mesh simplification reduces face count
while preserving shape.
**Quadric Error Decimation**
AquaMVS uses quadric error metrics to iteratively collapse edges:
1. For each vertex, compute a quadric (a 4×4 matrix) representing the
error of collapsing it to nearby positions.
2. Iteratively collapse the edge with minimum error.
3. Stop when target face count is reached.
**Parameters**
* **Target Faces**: Desired number of triangles (e.g., 100,000).
**Example**
A 2M-face Poisson mesh can be simplified to 100k faces with minimal
visual difference.
Mesh Export Formats
-------------------
AquaMVS supports multiple mesh export formats:
* **PLY** (Polygon File Format): Simple, widely supported. Stores vertices,
faces, and colors. Binary or ASCII.
* **OBJ** (Wavefront Object): Human-readable ASCII format. Stores geometry
and can reference material files for textures.
* **STL** (Stereolithography): Used for 3D printing. Stores only triangle
geometry (no color). Requires normals (auto-computed).
* **GLTF** (GL Transmission Format): Modern web-friendly format. Supports
animation, materials, and embedded textures.
Connection to Code
------------------
The fusion and surface reconstruction algorithms are implemented in:
* :py:func:`aquamvs.fusion.filter_depth_map`: Geometric consistency filtering.
* :py:func:`aquamvs.fusion.fuse_point_clouds`: Multi-view point cloud merging.
* :py:func:`aquamvs.surface.reconstruct_poisson`: Poisson surface reconstruction.
* :py:func:`aquamvs.surface.reconstruct_heightfield`: Height-field reconstruction.
* :py:func:`aquamvs.surface.reconstruct_bpa`: Ball pivoting algorithm.
For API details, see :doc:`/api/reconstruction`.
Summary
-------
The fusion stage transforms per-camera depth maps into a unified 3D surface:
1. **Geometric Consistency Filtering**: Cross-view validation to remove outliers.
2. **Point Cloud Generation**: Back-projection with color assignment.
3. **Outlier Removal**: Statistical filtering to clean the point cloud.
4. **Surface Reconstruction**: Choose from Poisson (smooth), height-field
(planar), or BPA (detailed).
5. **Mesh Simplification**: Reduce polygon count for practical use.
6. **Export**: Save as PLY, OBJ, STL, or GLTF.
This completes the AquaMVS reconstruction pipeline: from camera pixels, through
refractive ray tracing (:doc:`refractive_geometry`), dense stereo matching
(:doc:`dense_stereo`), to final 3D surface meshes.