Photogrammetry is the art of reconstructing digital 3D models using photos.
To reconstruct a 3D model of an object, photos are captured from different angles.

Contents

Input

Photos

Tips for capturing photos:

Video

A set of images can be extracted from a video too. Note that a set of photos
has (minor) advantages over a set of extracted images from a video:

ffmpeg can extract images from a video with a specified framerate (FPS):

ffmpeg -i "$VIDEO" -r "$FPS" -qmin 1 -qscale:v 1 img_%04d.jpg

Preparation

Photos stored in JPG files usually contain meta information in EXIF tags.
exiftran losslessly rotates photos according to the EXIF tag Orientation:

exiftran -ai *.jpg

rembg removes the background in images.
To replace the background with black:

rembg p -bgc 0 0 0 255 "$INPUT_DIR" "$OUTPUT_DIR"

COLMAP

COLMAP reconstructs a point cloud using images captured from the same object
from different angles.

Put all photos into a folder called images inside a named project folder:

PROJECT="my_photogrammetry_experiment"
mkdir -p "$PROJECT/images"
cp *.jpg "$PROJECT/images"

Providing image masks is optional. If the input images contain background,
it is highly recommended to create an image mask for each image.
Image masks tell COLMAP where to look for keypoints during feature extraction.
For an image images/0123.jpg, the corresponding mask has to be named masks/0123.jpg.png.
The size of the mask (width and height) has to match the size of the image.
No features will be extracted in regions, where the mask is black (pixel value 0).
Set the regions of interest to white (without transparency / alpha channel).

Execute the reconstruction:

colmap automatic_reconstructor \
  --workspace_path "$PROJECT" \
  --image_path "$PROJECT/images" \
  --mask_path "$PROJECT/masks"

For a dense point cloud reconstruction, COLMAP requires CUDA.
Without CUDA, only a sparse point cloud is generated.

The reconstructed point cloud is written to:

$PROJECT/dense/0/fused.ply

The reconstructed mesh is written to:

$PROJECT/dense/0/meshed-poisson.ply

COLMAP reconstructs a mesh with vertex colors.
It does not create textures for the mesh.

OpenMVS

OpenMVS reads a sparse point cloud and the corresponding images to generate
a textured mesh. A command line interface (CLI) controls OpenMVS.

The command line tools of OpenMVS need to be found in one of the directories
in the PATH variable. Set the environment variable PATH and
change to the project directory:

export PATH="/usr/bin/OpenMVS:$PATH"
cd "$PROJECT"

Read the sparse point cloud and the corresponding images from COLMAP:

colmap image_undistorter \
  --image_path images \
  --input_path sparse/0 \
  --output_path dense \
  --output_type COLMAP

InterfaceCOLMAP -i dense -o scene.mvs --image-folder dense/images

Run the OpenMVS pipeline:

DensifyPointCloud scene.mvs
ReconstructMesh scene_dense.mvs -p scene_dense.ply
RefineMesh scene_dense.mvs -m scene_dense_mesh.ply
TextureMesh scene_dense.mvs -m scene_dense_refine.ply --empty-color 16777215 # = 0xFFFFFF

The reconstructed textured mesh is written to:

$PROJECT/scene_dense_texture.ply
$PROJECT/scene_dense_texture0.png

A shell script that executes the whole OpenMVS pipeline can be found
in the files section.

MeshLab

MeshLab is a graphical user interface to visualize point clouds and meshes and
to apply various operations to them. These operations are organized in filters.

MeshLab can import point clouds and meshes stored in
the Stanford Polygon File Format (*.ply):

File > Import Mesh...

Because there is no undo function, it is recommended to duplicate the mesh
before applying a filter. On the right side of the window, right-click on
the mesh name. Choose Duplicate Current Layer to copy the mesh.
Filters are applied on the selected layer / mesh.

Filters

There are various filters available in MeshLab. Only a few are listed here.

Using the point cloud, a mesh can be computed using the following filter:

Remeshing, Simplification and Reconstruction > Surface Reconstruction: Screened Poisson
Reconstruction Depth: 12

There is a filter to remove small unconnected objects:

Cleaning and Repairing > Remove Isolated pieces (wrt Face num.)
Enter minimum conn. comp size: 1000

In COLMAP, the y-axis is pointing downwards. To rotate the mesh into
a coordinate system where the z-axis is pointing upwards:

Normals, Curvatures and Orientationn > Transform: Rotate
Rotation Angle: -90

To move the mesh:

Normals, Curvatures and Orientation > Transform: Translate, Center, set Origin
Z Axis: 1

To cut the mesh with a plane:

Quality Measure and Computations > Compute Planar Section
Plane perpendicular to: Z Axis
Create also section surface: On
Create also split surfaces: On

The newly created section surface and the split surface need to be merged
into one mesh. Set only these two surfaces/meshes to be visible.
Visibility is set by clicking on the eye next to the mesh name on the right side.
After visibility has been set correctly, right-click on the mesh name and
select Flatten Visible Layers to merge the visible meshes into one mesh.

Remaining holes in the surface should be closed:

Remeshing, Simplification and Reconstruction > Close Holes
Max size to be closed

Most applications need a 'watertight' mesh. A 'watertight' mesh is a mesh
that forms a closed surface. To check the quality of the mesh:

Quality Measure and Computations > Compute Geometric Measures

The output is shown in the log view in the lower right corner of the window.

There is also a filter for mesh simplification.
Before simplification, the mesh should be smoothed.

Remeshing, Simplification and Reconstruction > Simplification: Quadric Edge Collapse Decimation
Target number of faces

PyMeshLab

All filters available in MeshLab can be used in Python scripts too. Writing a
PyMeshLab script is very useful to apply the same filters on different meshes.

import pymeshlab

ms = pymeshlab.MeshSet()
ms.load_new_mesh('input.ply')

# Cleaning and Repairing > Remove Isolated pieces (wrt Face num.)
ms.meshing_remove_connected_component_by_face_number(mincomponentsize=1000)

# Normals, Curvatures and Orientationn > Transform: Rotate
ms.compute_matrix_from_rotation(angle=-90.0)

# Smoothing, Fairing and Deformation > Laplacian Smooth
ms.apply_coord_laplacian_smoothing(stepsmoothnum=5)

# Texture > Transfer: Texture to Vertex Color (1 or 2 meshes)
ms.transfer_texture_to_color_per_vertex()

ms.save_current_mesh('output.ply')

In case a functionality is missing in MeshLab, write a function in Python.
See in the files section for an example.

Blender

Blender can import meshes stored in Stanford PLY files:

File > Import > Stanford PLY (.ply)

To manually smooth the mesh at selected positions, open the Sculpting view.
In the Sculpting view, the tool Smooth is found in the toolbar at the bottom.

Other Photogrammetry Software

Meshroom

Using Meshroom 2025.1.0, the quality of the created mesh was not as detailed
as a mesh reconstructed with COLMAP 3.12.6.

Rembg
https://github.com/danielgatis/rembg
COLMAP
https://colmap.github.io/
OpenMVS
https://cdcseacave.github.io/
MeshLab
https://www.meshlab.net/
https://pymeshlab.readthedocs.io/
https://www.youtube.com/user/MrPMeshLabTutorials
Blender
https://www.blender.org/
Meshroom
https://alicevision.org/#meshroom
Comparison of photogrammetry software
https://www.reddit.com/r/photogrammetry/comments/nlkxfd/colmap_openmvs_is_my_favourite_free_combination/