Vision Systems & Engineering

COLMAP

An open-source structure-from-motion and multi-view stereo pipeline that recovers camera poses, intrinsics, sparse points and dense geometry from image collections, with a GUI, a command-line interface and Python bindings.

intermediate

COLMAP is an open-source program that reconstructs a scene from photographs: it estimates the pose and intrinsics of every camera, a sparse cloud of 3D points, and optionally dense depth maps, point clouds and meshes. It implements an incremental structure-from-motion (SfM) pipeline, a global SfM pipeline, and a PatchMatch-based multi-view stereo (MVS) pipeline, behind a graphical interface, a command-line interface and Python bindings (pycolmap). It was originally written by Johannes Schönberger, with funding from his PhD advisors Jan-Michael Frahm and Marc Pollefeys, and released with two 2016 papers, one on SfM (Schönberger and Frahm, CVPR) and one on MVS (Schönberger, Zheng, Pollefeys and Frahm, ECCV). As of October 2026 it is developed on GitHub by a core team of maintainers that includes Schönberger (README), under the new (3-clause) BSD licence. Every version-specific statement below was checked against COLMAP 4.2.1 and pycolmap 4.2.1, the current release as of October 2026.

The methods COLMAP implements belong to the structure from motion, multi-view stereo and bundle adjustment articles. This article covers the program: what each stage does as implemented, how the data is stored, and how the tool is driven and used.

Purpose

COLMAP takes a folder of overlapping images of a static scene, ordered (video frames) or unordered (photos from many people), with or without known calibration. Besides being end products, its models serve as inputs to other work: camera poses for novel-view synthesis, pseudo ground truth for benchmarks, and maps against which new images are localized (the last in the FAQ). The documentation’s tutorial says the defaults should usually work without changes, trading robustness and quality against speed, and offers presets for different kinds of data.

Core Capabilities

Feature extraction. By default COLMAP detects SIFT features, on the GPU or the CPU, and stores them in its database. Since 4.0 it can also extract ALIKED and, since 4.2, LoMa features, both learned extractors run through ONNX Runtime; builds must enable ONNX support for them, and one database must not mix feature types (Feature Extraction and Matching docs). Images that share a physical camera and zoom can share one set of intrinsics; focal lengths are read from EXIF tags when present. Without EXIF, the initial focal length is 1.2 times the larger image side (the default of ImageReader.default_focal_length_factor), which the example below shows being corrected during mapping.

Matching modes. Feature matching runs over a set of candidate image pairs chosen by one of several modes (Tutorial):

  • Exhaustive: every pair, recommended up to several hundred images. Its cost grows quadratically with the number of images.
  • Sequential: consecutive frames of a video, with loop detection that matches every NN-th frame against its most similar images in a vocabulary tree.
  • Vocabulary tree: each image against its visual nearest neighbours by image retrieval with spatial re-ranking (Schönberger et al.’s 2016 vote-and-verify method, as the docs cite it), recommended for thousands of images.
  • Spatial: each image against its nearest neighbours by position, for example EXIF GPS.
  • Transitive: if A matches B and B matches C, A is matched against C.
  • Custom: user-specified pairs, or imported raw or verified matches.

SIFT descriptors are matched by brute force with a ratio test; since 4.0, LightGlue models can match SIFT or ALIKED features instead. Matching skips pairs already in the database, so modes can be combined and new images added later.

Geometric verification. Each matched pair is verified with RANSAC by fitting a fundamental matrix, a homography and, for calibrated cameras, an essential matrix. As the 2016 paper describes it, the inlier counts classify the pair as a general, planar or panoramic (pure-rotation) configuration. Pairs linked only by watermarks, timestamps or frames at the image border are detected by fitting a similarity transformation there, and are rejected. Reconstruction is seeded only from non-panoramic pairs, and points are never triangulated from panoramic ones. Verified pairs form the scene graph that the mappers read.

Incremental mapping

The mapper command implements the incremental pipeline of Schönberger and Frahm (2016), whose steps the structure from motion article explains. In outline, as implemented:

  1. Initialization from a pair with many verified inliers and a wide triangulation angle (defaults in pycolmap 4.2.1: at least 100 inliers, at least 16°).
  2. Next-best-view selection. Each candidate image is scored by how many already triangulated points it sees and how evenly they cover it. The paper counts occupied cells in a pyramid of image grids with Kl=2lK_l = 2^l cells per side at level ll, weighted by Kl2K_l^2, so that spread-out points score higher than clustered ones.
  3. Registration by perspective-n-point inside RANSAC, refining focal length and distortion for uncalibrated cameras. Since 4.0, images without three-view overlap can fall back to structure-less registration from relative poses (Changelog).
  4. Robust triangulation. Tracks are formed by chaining two-view matches, and each track is triangulated by RANSAC over pairs of its observations. The procedure recurses on the remaining observations, so one wrong match that merged two tracks yields two points instead of one corrupted point.
  5. Bundle adjustment with Ceres Solver: local adjustment of the most connected images after each registration, and global adjustment after the model has grown by a set percentage. Global adjustment is iterated with re-triangulation and outlier filtering until the numbers of filtered observations and newly triangulated points diminish, which the paper reports significantly improves completeness.

The pipeline repeats steps 2–5 until no further image can be registered. When images remain unregistered, COLMAP attempts to reconstruct further models from them (Tutorial), so a disconnected collection yields several models (sparse/0, sparse/1, …). For uncalibrated images, the principal point is kept fixed at the image centre, because the paper considers its estimation ill-posed; cameras whose focal length or distortion become implausible are filtered. Since 4.1, Caspar, a GPU bundle adjustment backend, can replace Ceres; the CLI documentation calls it experimental.

Global and hierarchical mapping

Since 4.0 (March 2026), COLMAP includes GLOMAP, the global SfM system of Pan et al. (2024), as global_mapper. GLOMAP had been released as a separate repository, and the changelog says it is now maintained in COLMAP’s. It estimates all rotations by rotation averaging and then positions cameras and points jointly by global positioning, followed by global bundle adjustment. The global mapper needs good focal length priors. Without reliable intrinsics, the documentation recommends running view_graph_calibrator first; that command rewrites intrinsics and two-view geometries in the database in place, so the docs advise working on a copy. The FAQ calls the incremental mapper the most robust and best-tested pipeline, describes the global one as possibly faster on large datasets with good matching graphs but less robust to outliers, and describes hierarchical_mapper, which reconstructs overlapping sub-models in parallel and merges them, as usually less robust than both. pose_prior_mapper is the incremental mapper with GPS position priors.

Dense reconstruction

Dense reconstruction runs on an undistorted copy of the images (Tutorial):

  1. image_undistorter removes lens distortion from the images and cameras and writes a workspace.
  2. patch_match_stereo estimates a depth map and a normal map per image by the PatchMatch method of Schönberger et al. (2016), with pixelwise view selection and an optional geometric consistency term; the multi-view stereo article explains the method.
  3. stereo_fusion merges consistent depths and normals into an oriented point cloud, fused.ply.
  4. Meshing, optionally: poisson_mesher (Poisson surface reconstruction), delaunay_mesher (graph cut on a Delaunay triangulation with visibility voting, also applicable to sparse points), or, since 4.1, advancing_front_mesher. Since 4.0, mesh_simplifier decimates a mesh and mesh_texturer builds a texture atlas.

PatchMatch stereo needs a CUDA GPU, or since 4.2 an AMD GPU through HIP/ROCm in builds from source. The FAQ notes that without such a GPU all other functionality remains available, and the tutorial that the undistorter can instead export to external MVS software such as PMVS or CMP-MVS.

Data layout

The database. All per-image data lives in one SQLite file: tables rigs, cameras, frames, images, keypoints, descriptors, matches and two_view_geometries. Keypoints are float32 rows whose first two columns are pixel coordinates; descriptors are uint8 rows of 128 bytes for SIFT and float32 rows for the learned features. Only two_view_geometries, the verified matches, feeds reconstruction. The matches of images i1i_1 and i2i_2 are keyed by a single integer, the pair_id

p=2147483647⋅min⁡(i1,i2)+max⁡(i1,i2),p = 2147483647 \cdot \min(i_1, i_2) + \max(i_1, i_2),

which limits image identifiers to below 231−12^{31} - 1 (Database Format docs). Because the database is documented, other tools can write their own features or matches into it and use COLMAP only for mapping.

Pixel convention. COLMAP puts the origin at the top-left corner of the image, so the centre of the top-left pixel is (0.5,0.5)(0.5, 0.5). A principal point from OpenCV, whose integer coordinates are pixel centres, needs 0.5 added to cxc_x and cyc_y (FAQ).

The sparse model. A model is a folder of five files: rigs, cameras, frames, images and points3D, binary (.bin, the default since 3.1) or text (.txt), convertible with model_converter (Output Format docs). In the text form:

  • cameras.txt: one line per camera, CAMERA_ID MODEL WIDTH HEIGHT PARAMS[], the parameter list depending on the model.
  • images.txt: two lines per image. The first gives the pose as a world-to-camera rotation quaternion QW QX QY QZ (Hamilton convention) and translation TX TY TZ, then the camera and file name; the second lists the image’s keypoints as X Y POINT3D_ID, with −1-1 for keypoints not in the model. The camera centre is c=−R⊤t\mathbf{c} = -R^\top\mathbf{t}, and camera axes are xx right, yy down, zz forward.
  • points3D.txt: one line per point with its position, colour, mean reprojection error (updated only after global bundle adjustment) and track, the list of IMAGE_ID POINT2D_IDX that observe it.

Identifiers are not contiguous: points removed by filtering leave gaps. The camera pose article compares this convention with OpenGL’s and NeRF’s and gives the conversions.

Rigs and frames. Since 3.12 (June 2025), COLMAP models multi-camera rigs: a rig is a set of sensors with fixed relative poses, and a frame is one capture of a rig at one time, so the pose of an image is composed as sensor_from_world = sensor_from_rig * rig_from_world (Changelog). For a single camera every frame holds one image, and older models without rigs and frames files still load.

The dense workspace holds images/ (undistorted), sparse/ (the undistorted cameras), stereo/depth_maps/, stereo/normal_maps/ and consistency graphs, then fused.ply and the meshes. Depth and normal maps are a text header width&height&channels& followed by row-major float32 data.

Camera models

A camera model maps a point (X,Y,Z)(X, Y, Z) in camera coordinates to a pixel by perspective division, distortion and the intrinsic transform; COLMAP’s documentation gives SIMPLE_RADIAL, with parameters f,cx,cy,kf, c_x, c_y, k, as its worked example:

u=XZ,v=YZ,x=f u (1+k r2)+cx,y=f v (1+k r2)+cy,r2=u2+v2.u = \frac{X}{Z},\quad v = \frac{Y}{Z},\qquad x = f\,u\,(1 + k\,r^2) + c_x,\quad y = f\,v\,(1 + k\,r^2) + c_y,\qquad r^2 = u^2 + v^2 .

The 4.2.1 models fall into groups (Camera Models docs and, for the equirectangular model, the changelog and the camera models header; see pinhole camera model and lens distortion):

  • SIMPLE_PINHOLE, PINHOLE: one or two focal lengths, for undistorted images.
  • SIMPLE_RADIAL (the default), RADIAL: one or two radial terms, recommended when every image has its own unknown calibration, as with internet photos.
  • OPENCV, FULL_OPENCV: OpenCV’s distortion coefficients in OpenCV’s order, for known calibrations or many images sharing intrinsics.
  • Fisheye models (SIMPLE_RADIAL_FISHEYE, RADIAL_FISHEYE, OPENCV_FISHEYE, FOV, THIN_PRISM_FISHEYE, RAD_TAN_THIN_PRISM_FISHEYE), equidistant models without distortion (SIMPLE_FISHEYE, FISHEYE), division models, the Enhanced Unified Camera Model (EUCM), and since 4.1 an equirectangular model (EQUIRECTANGULAR) for 360° panoramas.

The documentation advises the simplest model that captures the distortion. Grossly wrong focal lengths or distortion suggest a model that is too complex, and many repeated bundle adjustments suggest one that is too simple.

Interfaces

All three interfaces call the same library.

  • GUI (colmap gui): project setup, each processing step as a dialog, a database manager for editing cameras and shared intrinsics, and a 3D viewer that shows the model growing during mapping. It needs a display with OpenGL 3.2. Since 4.2, a browser-based viewer also displays binary sparse models.
  • CLI (colmap <command>, unified into one executable in 3.4): one command per stage, with options given as arguments or in a .ini project file. automatic_reconstructor runs everything, with choices of quality, data type (individual images, video, internet photos), features, mapper and mesher. Since 4.2, long-running commands stop gracefully on a first SIGINT or SIGTERM, keeping usable partial results.
  • pycolmap: Python bindings that the documentation says expose most of COLMAP’s capabilities, from the full pipelines to single estimators (absolute and relative pose, essential and fundamental matrices, triangulation, similarity alignment) and reading and writing models. Wheels are on PyPI, with CUDA 12 wheels for Linux as pycolmap-cuda12. The bindings were developed in a separate repository and moved into COLMAP’s in 3.10 (July 2024); on PyPI, version 0.6.1 was followed by 3.10.0, and pycolmap has carried COLMAP’s version numbers since.

A typical stepwise run from the command line, condensed from the CLI documentation:

colmap feature_extractor   --database_path db.db --image_path images
colmap exhaustive_matcher  --database_path db.db
mkdir -p sparse dense
colmap mapper              --database_path db.db --image_path images --output_path sparse
colmap image_undistorter   --image_path images --input_path sparse/0 --output_path dense \
                           --output_type COLMAP
colmap patch_match_stereo  --workspace_path dense --workspace_format COLMAP \
                           --PatchMatchStereo.geom_consistency true
colmap stereo_fusion       --workspace_path dense --workspace_format COLMAP \
                           --input_type geometric --output_path dense/fused.ply

Minimal Example

The script renders eight grey-level images of a room corner with NumPy: a floor and two walls, each with its own band-limited random texture, seen by eight cameras on an arc. Their poses and the focal length (400 px) are known. It then runs COLMAP’s sparse pipeline through pycolmap 4.2.1, the CPU wheel for macOS, with NumPy 2.5.4 and SciPy 1.18.1 (the dense step needs a GPU). Every stage gets one thread and every randomized stage a fixed seed, and the camera model is the default SIMPLE_RADIAL, shared by all images, with no EXIF data. The script prints the database and model contents, then aligns each reconstruction to the ground truth with a similarity transformation estimated from camera centres and reports the remaining pose errors. Finally, it runs the global mapper on a copy of the same database. Two runs printed identical output; COLMAP’s log goes to standard error and is not shown.

import pathlib, shutil
import numpy as np
from PIL import Image
from scipy import ndimage
from scipy.spatial.transform import Rotation as Rot
import pycolmap

rng = np.random.default_rng(0)
W, H, F = 480, 360, 400.0                      # image size and true focal length (pixels)

# Scene: a floor (y = 1), a back wall (z = 8) and a side wall (x = -3), each with
# its own band-limited random texture; world axes follow COLMAP: x right, y down, z forward.
def make_texture():
    t = sum(ndimage.gaussian_filter(rng.random((512, 512)), s, mode="wrap") * s
            for s in (1.5, 4, 12))
    return 255 * (t - t.min()) / np.ptp(t)
planes = [(np.array([0, 1, 0.]), 1.0, [0, 2], make_texture()),    # (normal, offset, uv axes, texture)
          (np.array([0, 0, 1.]), 8.0, [0, 1], make_texture()),
          (np.array([1, 0, 0.]), -3.0, [2, 1], make_texture())]

def render(R, t):                              # ray-cast one image of the world-to-camera pose (R, t)
    u, v = np.meshgrid(np.arange(W) + 0.5, np.arange(H) + 0.5)    # COLMAP: pixel centres at +0.5
    rays = np.stack([(u - W / 2) / F, (v - H / 2) / F, np.ones_like(u)], -1) @ R  # world directions
    centre = -R.T @ t
    depth, img = np.full((H, W), np.inf), np.zeros((H, W))
    for n, d, axes, tex in planes:
        s = (d - centre @ n) / (rays @ n)                       # ray parameter at the plane
        p = centre + s[..., None] * rays
        hit = (s > 0) & (s < depth)
        depth[hit] = s[hit]
        uv = p[..., axes] * 40.0                                # 40 texels per scene unit
        img[hit] = ndimage.map_coordinates(tex, [uv[..., 1][hit], uv[..., 0][hit]],
                                           order=1, mode="wrap")
    return img.astype(np.uint8)

# Eight cameras on an arc, all looking at a point on the back wall.
target = np.array([-0.5, 0.0, 7.0])
gt = {}
work = pathlib.Path("colmap_demo")                # scratch workspace, removed at the end
shutil.rmtree(work, ignore_errors=True)
(work / "images").mkdir(parents=True)
for k in range(8):
    a = np.radians(-28 + 8 * k)
    C = np.array([2.5 * np.sin(a), -0.3 + 0.05 * k, 7.0 - 5.0 * np.cos(a)])
    z = (target - C) / np.linalg.norm(target - C)
    x = np.cross([0, 1, 0], z); x /= np.linalg.norm(x)
    R = np.stack([x, np.cross(z, x), z])                         # rows: camera axes in world
    t = -R @ C
    name = f"view{k}.png"
    Image.fromarray(render(R, t)).save(work / "images" / name)
    gt[name] = (R, C)

# COLMAP pipeline: SIFT extraction, exhaustive matching with verification, incremental mapping.
pycolmap.set_random_seed(0)
db = work / "database.db"
ext = pycolmap.FeatureExtractionOptions(num_threads=1)
pycolmap.extract_features(db, work / "images", camera_mode=pycolmap.CameraMode.SINGLE,
                          extraction_options=ext, device=pycolmap.Device.cpu)
match = pycolmap.FeatureMatchingOptions(num_threads=1)
verify = pycolmap.TwoViewGeometryOptions()
verify.ransac.random_seed = 0
pycolmap.match_exhaustive(db, matching_options=match, verification_options=verify,
                          device=pycolmap.Device.cpu)
opts = pycolmap.IncrementalPipelineOptions(num_threads=1, random_seed=0)
recs = pycolmap.incremental_mapping(db, work / "images", work / "sparse", opts)

database = pycolmap.Database.open(db)                          # the SQLite database
n_kp = [database.num_keypoints_for_image(i.image_id) for i in database.read_all_images()]
n_pairs = database.num_verified_image_pairs()
f0 = database.read_all_cameras()[0].params[0]                  # focal length before mapping
database.close()
print(f"keypoints per image: {min(n_kp)} to {max(n_kp)}; verified pairs: {n_pairs} of 28; "
      f"initial focal length {f0:.0f} px")
rec = recs[0]
print(f"models: {len(recs)}; registered images: {rec.num_reg_images()} of 8; "
      f"3D points: {rec.num_points3D()}")
print(f"mean track length: {rec.compute_mean_track_length():.2f}; "
      f"mean reprojection error: {rec.compute_mean_reprojection_error():.3f} px")
print("files in sparse/0:", sorted(p.name for p in (work / "sparse" / "0").iterdir()))
(work / "text").mkdir()
rec.write_text(work / "text")                                   # human-readable copy
def short(tok):                                                 # round long decimals for printing
    return f"{float(tok):.4g}" if "." in tok and tok[-4:] != ".png" else tok
for fname, n_tok in (("cameras.txt", 8), ("images.txt", 10), ("points3D.txt", 14)):
    line = next(l for l in open(work / "text" / fname) if not l.startswith("#"))
    toks = line.split()
    print(f"{fname}: {' '.join(map(short, toks[:n_tok]))}{' ...' * (len(toks) > n_tok)}")

# Compare with ground truth after a similarity alignment of the camera centres.
def pose_errors(label, rec):
    est = {img.name: img for img in rec.images.values() if img.has_pose}
    names = sorted(est)
    C_est = np.array([est[n].projection_center() for n in names])
    C_gt = np.array([gt[n][1] for n in names])
    sim = pycolmap.estimate_sim3d(C_est, C_gt)                  # gt_from_est
    pos_err = np.linalg.norm(np.array([sim * c for c in C_est]) - C_gt, axis=1)
    rot_err = [Rot.from_matrix(gt[n][0] @ sim.rotation.matrix()
                               @ est[n].cam_from_world().rotation.matrix().T).magnitude()
               for n in names]
    extent = np.linalg.norm(C_gt.max(0) - C_gt.min(0))           # bounding-box diagonal
    print(f"{label}: {len(names)} images, {rec.num_points3D()} points, "
          f"f = {rec.cameras[1].params[0]:.1f} px; max errors: position "
          f"{100 * pos_err.max() / extent:.3f}% of extent, rotation {np.degrees(max(rot_err)):.3f} deg")
pose_errors("incremental", rec)

# Global SfM (GLOMAP, integrated in COLMAP 4.0) on a copy of the same database.
shutil.copy(db, work / "global.db")
pycolmap.calibrate_view_graph(work / "global.db")             # modifies the database in place
gopts = pycolmap.GlobalPipelineOptions(num_threads=1, random_seed=0)
grecs = pycolmap.global_mapping(work / "global.db", work / "images", work / "global", gopts)
pose_errors("global     ", grecs[0])
shutil.rmtree(work)

Output:

keypoints per image: 1604 to 1867; verified pairs: 28 of 28; initial focal length 576 px
models: 1; registered images: 8 of 8; 3D points: 1735
mean track length: 6.32; mean reprojection error: 0.101 px
files in sparse/0: ['cameras.bin', 'frames.bin', 'images.bin', 'points3D.bin', 'rigs.bin']
cameras.txt: 1 SIMPLE_RADIAL 480 360 400.7 240 180 -0.003706
images.txt: 6 0.9995 0.005656 -0.03244 0.002467 -1.944 -0.3075 0.9965 1 view5.png
points3D.txt: 1 -5.844 -3.13 24.42 102 102 102 0.04764 2 3 6 4 5 5 ...
incremental: 8 images, 1735 points, f = 400.7 px; max errors: position 0.068% of extent, rotation 0.101 deg
global     : 8 images, 1734 points, f = 400.5 px; max errors: position 0.058% of extent, rotation 0.093 deg

The focal length starts at the EXIF-less default of 1.2 × 480 = 576 px and ends within 0.2% of the true 400 px. This is self-calibration through bundle adjustment; sharing one camera among the eight images gives it many observations of the same focal length, which in our reading is why it succeeds on so few images. The principal point stays at the image centre, as COLMAP fixes it for uncalibrated cameras; the small radial coefficient is fitted although the renderer had no distortion. The reconstruction is not in the world’s frame or units: the similarity alignment is what makes poses comparable, and before it the point coordinates (such as the first point at z≈24z \approx 24) are in an arbitrary scale. After alignment both pipelines recover every camera centre to within 0.07% of the extent of the camera positions (the diagonal of their bounding box), and every orientation to within about 0.1°. On so small and clean a collection the example shows only that they share the database and produce the same model format, not how they differ in robustness or speed. The printed text lines show the formats described above. The first line of images.txt belongs to view5.png, image 6, because identifiers are assigned at import and need not follow the file order. The point’s track starts with image 2, keypoint 3.

Ecosystem and Integrations

Neural rendering data. Neural radiance fields were trained on real scenes with camera poses, intrinsics and scene bounds estimated by COLMAP (Mildenhall et al., 2020, §5.3, arXiv v2); the novel-view synthesis article lists later datasets whose poses come from COLMAP. The official 3D Gaussian splatting code reads a COLMAP folder directly: images/ beside sparse/0/ with cameras.bin, images.bin and points3D.bin. Its rasterizer accepts only SIMPLE_PINHOLE or PINHOLE cameras, so its convert.py script runs COLMAP and its undistorter on the user’s images (README at commit 54c035f). The method also initializes its Gaussians from the sparse points, as the 3D Gaussian splatting article describes. COLMAP stores world-to-camera poses with yy pointing down, whereas NeRF code uses camera-to-world matrices with OpenGL axes; the camera pose article gives the conversion.

Benchmarks and datasets. COLMAP models serve as reference or pseudo ground truth: GLOMAP’s evaluation used COLMAP-built ground truth for the Image Matching Challenge 2023 and the provided COLMAP models of Mip-NeRF 360 (Pan et al., 2024). The ETH3D and Tanks and Temples articles describe how those benchmarks use COLMAP in building and scoring their data.

Interoperability. Models export to Bundler, VisualSfM’s NVM, PLY and VRML (model_converter; the docs note that the NVM export is not fully compatible with VisualSfM, which applies distortion to the measurements rather than the projection). The undistorter writes PMVS and CMP-MVS workspaces. Features and matches from other extractors and matchers can be imported through text files or by writing to the database directly, and model_aligner geo-registers a model to known camera positions. A Matlab reader and C++ library headers are also provided.

Alternatives

Comparisons here are given only as their sources report them, with dates and set-ups.

  • Incremental systems. In the 2016 paper, Schönberger and Frahm compared COLMAP with Bundler (open source; the system behind Photo Tourism), VisualSFM (closed source) and the global systems DISCO and Theia, on 17 internet photo collections, using RootSIFT features and vocabulary-tree matching throughout and excluding matching from the timings. On Rome they registered 20,918 of 74,394 images in the largest model, against 14,797 for VisualSFM and 13,455 for Bundler (Table 1). They reported Theia as the fastest, COLMAP slightly slower than VisualSFM and more than 50 times faster than Bundler, and the lowest camera position error on Quad (0.85 m against 0.89 m for VisualSFM, 1.01 m for Bundler and 1.16 m for DISCO).
  • Global systems. Pan et al. (2024) compared GLOMAP, still a separate program, with OpenMVG, Theia and the COLMAP of that time, giving all methods the same feature matches and excluding matching from the timings. On ETH3D’s DSLR scenes the averages were AUC at 1° of 80.8 for GLOMAP and 79.2 for COLMAP, at 16.5 s against 27.5 s per scene (Table 3). On the training scenes of the Image Matching Challenge 2023, GLOMAP scored about 4 points higher in AUC and ran about 8 times faster (Table 5). These numbers predate the integration of GLOMAP into COLMAP 4.0 and the changes the changelog lists for it.
  • Learned reconstruction. Feed-forward and differentiable methods, such as VGGSfM and DUSt3R, are surveyed in the structure from motion article.

Versions and Compatibility

The history below is taken from the changelog in the 4.2.1 source; dates are release dates.

Version Date Changes
1.0 4 April 2016 Initial release
2.0 8 September 2016 Dense reconstruction pipeline
3.0 22 May 2017 Automatic end-to-end reconstruction, transitive matching
3.1 15 June 2017 Binary model format becomes the default
3.4 29 January 2018 Single colmap <command> executable
3.5 22 August 2018 Licence changed from GPL to BSD; hierarchical mapper; Delaunay meshing
3.10 23 July 2024 pycolmap moved into the main repository
3.12.0 30 June 2025 Rigs and frames; vocabulary trees switched from FLANN to faiss
3.13.0 7 November 2025 random_seed option for deterministic reconstruction; rig bundle adjuster removed
4.0.0 14 March 2026 GLOMAP integrated as global_mapper; ALIKED and LightGlue; OpenImageIO replaces FreeImage
4.1.0 26 June 2026 Caspar GPU bundle adjustment; equirectangular camera model; advancing-front meshing
4.2.0 31 August 2026 LoMa features; HIP/ROCm dense stereo; browser viewer; graceful shutdown
4.2.1 29 September 2026 Bug fixes, including a regression in the global mapper’s bundle adjustment

Compatibility notes, as of 4.2.1:

  • Models and databases. Models without rigs and frames files, written before 3.12, still load, with trivial rigs and frames created automatically. The 3.12 changelog states that older code can also read the new models.
  • Breaking changes are listed per release. Examples: the deprecated rig bundle adjuster was removed in 3.13 in favour of rig_configurator and the standard bundle adjuster. FLANN-based vocabulary trees stopped working in 3.12. Ceres options gained a BundleAdjustmentCeres. prefix in 4.0. Since 4.2 the global mapper returns every connected component as its own model by default.
  • Determinism. The 3.13 changelog states that the new random_seed parameter makes reconstruction fully deterministic. The example above also fixes the RANSAC seed and uses one thread per stage, and its two runs agreed exactly.
  • Documentation versions. The documentation site, colmap.github.io, follows the development branch; on 30 October 2026 it showed 4.3.0.dev0. The doc/ folder at a release tag documents that release.
  • Licence. COLMAP itself is under the new BSD licence, copyright ETH Zurich and UNC Chapel Hill. Its third-party dependencies are licensed separately, and the licence text warns that building with them may affect the licence of the result. The README asks users of specific algorithms to also cite their original authors.

Limitations

  • Static, textured, overlapping scenes. The tutorial asks for good texture, similar illumination, high overlap with each object seen in at least three images, and camera translation between shots rather than pure rotation. Repetitive and symmetric structures can also produce wrong matches that pass verification and break a model, as the structure from motion article describes.
  • Incomplete models. Images that cannot be registered are left out, and poorly connected collections split into several models; model_merger can join models that share registered images.
  • Scale and frame. Monocular reconstructions have an arbitrary scale, position and orientation unless they are aligned to GPS, known camera positions or another reference, as the example’s alignment step shows.
  • Run time. Exhaustive matching grows quadratically with the number of images: the tutorial estimates minutes for tens of images, hours for hundreds and days or weeks for thousands. Repeated bundle adjustment is often the bottleneck of incremental mapping on large collections (FAQ); for those, the documentation points to vocabulary-tree or sequential matching, the Caspar backend, and the hierarchical and global mappers.
  • Dense reconstruction needs a supported GPU (CUDA, or HIP in source builds since 4.2). The tutorial also notes that the display may freeze and that the process may crash when GPU memory is exhausted.
  • Camera model choice is left to the user. Too simple a model leaves distortion unmodelled and too complex a one overfits; the docs warn that estimating OPENCV parameters will most likely fail when every image has its own intrinsics.
  • Research software. The tutorial warns that in rare cases COLMAP may exit ungracefully when its constraints are not met, and recommends running it from a terminal to see the error.

Related

  • Bundle Adjustment

    The joint nonlinear least-squares refinement of all camera parameters and 3D points in a reconstruction, made practical by the sparse structure of the problem and the Schur complement.

  • Camera Pose

    The position and orientation of a camera as a rigid transform in SE(3), the two directions it is stored in (world-to-camera and camera-to-world), the axis conventions of OpenCV, COLMAP, OpenGL and NeRF, and how pose errors are measured.

  • Feature Matching

    Finding corresponding keypoints between two or more images of the same scene, from descriptor nearest-neighbor search to learned matchers.

  • Photo Tourism: Exploring Photo Collections in 3D

    Snavely, Seitz and Szeliski's 2006 SIGGRAPH paper and 2008 IJCV journal paper that recovered camera poses and sparse 3D points from internet photos of landmarks and used them to browse the photos in 3D.

  • Neural Radiance Fields

    NeRF, a network that maps a 3D position and viewing direction to density and colour and is fitted to posed photographs through volume rendering; the original 2020 model, how it is trained and rendered, its limits, and the family that made it anti-aliased, unbounded, fast, robust to photo collections, pose-free and dynamic.

  • 3D Gaussian Splatting

    A radiance-field model that represents a scene as millions of anisotropic 3D Gaussians, rasterizes them by sorting and alpha blending, and fits them to posed photographs; the 2023 model, its training and limits, and the variants for anti-aliasing, surfaces, compression, motion, feed-forward prediction and SLAM.

References

  1. Schönberger, J. L. & Frahm, J.-M. (2016). Structure-from-Motion Revisited. IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 4104–4113.
  2. Schönberger, J. L., Zheng, E., Pollefeys, M. & Frahm, J.-M. (2016). Pixelwise View Selection for Unstructured Multi-View Stereo. European Conference on Computer Vision (ECCV), Lecture Notes in Computer Science, 501–518.
  3. Pan, L., Baráth, D., Pollefeys, M. & Schönberger, J. L. (2024). Global Structure-from-Motion Revisited. Computer Vision – ECCV 2024, Lecture Notes in Computer Science, 58–77. Springer.
  4. COLMAP 4.2.1 documentation source (2026): Tutorial, doc/tutorial.rst.
  5. COLMAP 4.2.1 documentation source (2026): Command-line Interface, doc/cli.rst.
  6. COLMAP 4.2.1 documentation source (2026): Database Format, doc/database.rst.
  7. COLMAP 4.2.1 documentation source (2026): Output Format, doc/format.rst.
  8. COLMAP 4.2.1 documentation source (2026): Camera Models, doc/cameras.rst.
  9. COLMAP 4.2.1 documentation source (2026): Feature Extraction and Matching, doc/features.rst.
  10. COLMAP 4.2.1 documentation source (2026): Frequently Asked Questions, doc/faq.rst.
  11. COLMAP 4.2.1 documentation source (2026): PyCOLMAP, doc/pycolmap/index.rst.
  12. COLMAP 4.2.1 source (2026): Changelog, CHANGELOG.rst.
  13. COLMAP 4.2.1 source (2026): README.md and licence (doc/license.rst).
  14. pycolmap on the Python Package Index: release history (accessed 30 October 2026).
  15. Mildenhall, B., Srinivasan, P. P., Tancik, M., Barron, J. T., Ramamoorthi, R. & Ng, R. (2020). NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis. arXiv:2003.08934 (v2, with appendices).
  16. Kerbl, B., Kopanas, G., Leimkühler, T. & Drettakis, G. graphdeco-inria/gaussian-splatting official code repository: README, section Processing your own Scenes (commit 54c035f, 30 October 2024).