Structure from Motion
Imagine handing a computer a thousand unordered tourist photos of the Colosseum. It must build a 3D model from them. No camera positions are known. No calibration rig was used. The photos come from dozens of different phones and years. Structure from Motion (SfM) is the family of algorithms that solves exactly this problem. It recovers the 3D positions of scene points. It also recovers the pose and calibration of every camera. It does this using only 2D pixel correspondences across images. This problem sits at the heart of practical computer vision. It powers photogrammetry pipelines that turn drone footage into surveyed 3D maps. It underlies the mapping backbones of AR platforms and cultural-heritage digitization projects. It also drives the camera-pose initialization stage of every modern visual SLAM and neural-rendering system. COLMAP output, for instance, is the standard input to NeRF and Gaussian-splatting pipelines. Getting camera geometry right is the prerequisite for almost everything else in 3D vision. SfM is therefore the classical capstone that ties together Feature Descriptors and Matching, Epipolar Geometry, and Camera Calibration. The big picture is deceptively simple to state. It is deeply subtle to execute correctly. Match features across images, estimate relative camera geometry from those matches, triangulate 3D points, and refine everything jointly. Each of those four verbs hides a nontrivial algorithm. Each also hides a numerical minefield. These include outlier correspondences, geometric ambiguities, drift accumulation, and a nonconvex optimization landscape with tens of thousands of variables. Doing it robustly at scale is one of the genuine engineering triumphs of classical computer vision. It was later inherited almost unchanged as the backbone of modern differentiable 3D pipelines. This article gives the full incremental SfM pipeline end to end. It covers feature tracks, two-view initialization, incremental registration and triangulation. It also covers the bundle adjustment optimization that ties it all together. Included are pseudocode, a worked numeric trace, and an honest account of complexity and failure modes.
- SfM jointly recovers unknown 3D scene structure and camera poses from 2D correspondences across many uncalibrated images.
- The pipeline is: feature matching → two-view geometry → incremental registration → triangulation → global bundle adjustment.
- Camera poses and 3D points are only recoverable up to a similarity transform (scale, rotation, translation ambiguity) without extra metric information.
- Epipolar Geometry gives the two-view seed (essential/fundamental matrix); Stereo Vision and Depth supplies triangulation; bundle adjustment is nonlinear least squares over the reprojection error.
- Bundle adjustment is a sparse Levenberg–Marquardt problem; exploiting the block-sparse structure of the Jacobian is what makes it tractable for thousands of cameras and millions of points.
- Robustness (RANSAC at every geometric estimation step) matters as much as the optimizer, since a single bad match can silently corrupt the whole reconstruction.
Structure from Motion turns thousands of noisy, unordered 2D pixel matches into a single globally consistent 3D scene by minimizing total reprojection error over all cameras and points simultaneously.
The idea
Structure from Motion is the problem of estimating, from a set of 2D image correspondences across images, the 3D positions of scene points and the pose (and often intrinsics ) of each camera , such that projecting each point into each camera that observes it reproduces the measured 2D location as closely as possible. Formally, if is the pinhole projection function, we seek parameters minimizing the reprojection error
where is the set of observed (camera, point) pairs and is the measured pixel location of point in image . This joint nonlinear least-squares problem, solved over all cameras and points at once, is called bundle adjustment (BA) — named for the "bundle" of light rays from each 3D point through the camera centers that must be adjusted to intersect correctly. Two structural facts define the character of the problem. First, without external metric information (a known baseline, GPS, or a calibration object), the entire reconstruction is recoverable only up to a similarity transform — a global rotation, translation, and uniform scale — because scaling the whole scene and all camera translations together leaves every image projection unchanged. This is the scale ambiguity inherited directly from monocular Epipolar Geometry, where the essential matrix determines relative translation only up to scale. Second, the optimization landscape of is highly nonconvex in the camera rotations and 3D point positions, so bundle adjustment requires a good initialization; this is why SfM is built as an incremental pipeline that grows a partial, already-good reconstruction one camera at a time rather than trying to solve for everything from a cold start. SfM comes in two major flavors. Incremental SfM (the classical and still most common approach, exemplified by Bundler and COLMAP) starts from a single well-conditioned image pair, triangulates an initial point cloud, then repeatedly registers new cameras via Camera Calibration-style pose estimation (PnP) against the existing 3D points, triangulates new points, and periodically re-runs bundle adjustment. Global SfM instead estimates all relative rotations and translations simultaneously via rotation averaging and translation averaging before a single final bundle adjustment; it is faster and more resistant to drift but more fragile to outlier relative poses. This article focuses on the incremental pipeline, since it is the clearer pedagogical path and the historically dominant method.
How it works
The incremental SfM pipeline has five stages, executed in this order for each new image, with global refinement interleaved.
Stage 1 — Correspondence and tracks. Detect and describe features in every image (e.g., SIFT), match pairwise using nearest-neighbor plus ratio test, as covered in Feature Descriptors and Matching, then verify each pairwise match geometrically with RANSAC over the fundamental or essential matrix, discarding image pairs with too few inlier matches. Chains of pairwise matches across three or more images are linked into tracks: a track is a single 3D point's set of observations across all images that see it.
Stage 2 — Initialization. Pick the image pair with the largest number of well-distributed inlier matches and a sufficiently wide baseline (narrow-baseline pairs give poorly conditioned triangulation). Estimate the essential matrix (if intrinsics are known) or fundamental matrix, decompose it into a relative rotation and translation (using Epipolar Geometry), and triangulate the shared tracks to seed the initial 3D point cloud and set the world coordinate frame.
Stage 3 — Incremental registration. For each remaining image, find its 2D-3D correspondences against already-triangulated points (via feature matches to already-registered images), and solve the Perspective-n-Point (PnP) problem inside a RANSAC loop to get that camera's pose robustly. This is the same pose-from-known-points problem used in Camera Calibration, just applied per new image instead of to a calibration target.
Stage 4 — Triangulation and extension. With the new camera's pose fixed, triangulate any track that is now observed by two or more registered cameras but has no 3D position yet (using the multi-view triangulation techniques from Stereo Vision and Depth, extended to views via linear least squares or DLT).
Stage 5 — Bundle adjustment. Periodically (e.g., every few registered cameras, and always at the end) run bundle adjustment over all currently registered cameras and triangulated points to jointly minimize total reprojection error, correcting the drift that accumulates from doing steps 3–4 one camera at a time. ``` function IncrementalSfM(images): features[i] = DetectAndDescribe(images[i]) for all i pair_matches = { (i,j): MatchAndGeometricVerify(features[i], features[j]) for all pairs i=2 registered images, not yet in points3D for t in new_tracks: points3D[t] = TriangulateTrack(t, poses, registered) FilterOutlierPoints(points3D, poses) # reprojection error / cheirality checks if len(registered) % K == 0: BundleAdjust(poses[registered], points3D) # local, then eventually global BundleAdjust(poses[registered], points3D) # final global refinement return poses, points3D ``` Bundle adjustment itself is a structured nonlinear least-squares solve. Stack all camera parameters (rotation, translation, possibly intrinsics) and point parameters into a parameter vector , and let be the vector of all reprojection residuals. Levenberg–Marquardt solves
iteratively, where is the Jacobian of residuals with respect to , is a damping parameter, and is typically . The crucial structural fact is that any residual involving camera and point has nonzero derivative only with respect to camera 's parameters and point 's parameters — no residual couples two different cameras directly, or two different points directly. This gives (and hence the normal equations) an arrowhead / bordered block-diagonal sparsity pattern, exploited by the Schur complement trick: ``` function BundleAdjust(cameras, points, observations): p = pack(cameras, points) lambda = 1e-3 repeat: r, J = ComputeResidualsAndJacobian(p, observations) # sparse: camera & point blocks only
# Partition normal equations by camera block U and point block V (block-diagonal)
# and camera-point coupling block W:
# [ U W ] [dc] [-b_c]
# [ W^T V ] [dp] = [-b_p] U' = U + lambda diag(U) V' = V + lambda diag(V) # V is block-diagonal: one small block per point S = U' - W @ inv(V') @ W.T # Schur complement, size = #camera-params only dc = solve(S, -b_c + W @ inv(V') @ b_p) dp = inv(V') @ (-b_p - W.T @ dc) # back-substitute, cheap: block-diagonal solve p_new = p + pack(dc, dp) if Cost(p_new) < Cost(p): p = p_new; lambda /= 10 else: lambda *= 10 until converged return unpack(p) ```
Complexity & correctness
Feature matching and geometric verification. With images and up to features each, brute-force pairwise matching costs in the worst case; in practice, approximate nearest-neighbor structures (KD-trees, vocabulary trees) reduce per-pair matching to roughly , and vocabulary-tree image retrieval prunes the pair set down to a much sparser candidate set for large unordered collections. Each geometric verification (RANSAC on the fundamental/essential matrix, 8-point or 5-point minimal solver) costs for RANSAC iterations, and itself is chosen adaptively from the estimated inlier ratio via the standard RANSAC sample bound for confidence and sample size (4 or 5 for these models).
Incremental registration. Registering one new camera by PnP-RANSAC costs where is the PnP RANSAC iteration count; triangulating a new track observed by views costs per track via linear least squares (solving a small homogeneous system by SVD). Over the whole pipeline, incremental registration is camera-addition steps, each cheap in isolation — the real cost is in bundle adjustment.
Bundle adjustment: the crux. A naive dense solve of the normal equations for cameras (say 6 parameters each) and points (3 parameters each) requires inverting a matrix, costing — for even a modest reconstruction with in the hundreds of thousands, this is completely infeasible. The Schur complement trick reduces this dramatically. Because each 3D point is observed by only a small number of cameras, the point-point block of the normal equations is exactly block-diagonal (one small block per point), so costs only (invert independent blocks). Eliminating the point variables leaves the reduced camera system , a matrix that is itself sparse (camera and camera are coupled only if they share an observed point) and typically solved with sparse Cholesky or a preconditioned conjugate-gradient solver. This brings one Levenberg–Marquardt iteration down to roughly in the worst case for the sparse camera system (often far better in practice with good sparse solvers and problem structure), instead of — the difference between reconstructing thousands of cameras and reconstructing a handful.
Why bundle adjustment is the right objective (correctness argument). If the true 2D measurement noise is independent, zero-mean, isotropic Gaussian, then the reprojection error is (up to constants) exactly the negative log-likelihood of the observed image points given the camera and structure parameters. Minimizing is therefore the maximum-likelihood estimator for this noise model, which is the formal justification for treating bundle adjustment — rather than, say, minimizing algebraic epipolar error or 3D point distances — as the gold-standard refinement step: it directly optimizes the quantity that was actually measured (pixel locations) rather than a proxy. Two-view methods (essential matrix estimation, linear triangulation) only approximately optimize this likelihood and are used purely as cheap, well-conditioned initializers for the nonconvex BA landscape; without a good initializer, LM iteration converges to an arbitrary local minimum or diverges.
Honest tradeoffs. LM guarantees only convergence to a local minimum (the reprojection error surface is genuinely nonconvex due to rotation parametrization and the perspective projection nonlinearity), so incremental SfM's dependence on a good starting reconstruction is not a minor implementation detail — it is load-bearing. Global SfM avoids incremental drift accumulation by solving rotation and translation averaging problems that are closer to convex, but is more sensitive to outlier relative poses since there is no incremental error-checking step. Both approaches remain fundamentally limited by the similarity ambiguity: absolute scale must come from an external source — a known object size, stereo baseline, GPS/IMU data, or a calibrated pair of cameras with known baseline as in classic Stereo Vision and Depth rigs.
Worked example
Consider three images of a small textured box, observing four tracked corner points . Assume intrinsics are known from prior Camera Calibration, all cameras have unit focal length in normalized coordinates for simplicity, and matches have already been verified by RANSAC.
Step 1 — Tracks after matching.
| Track | Seen in images | Pixel obs. (normalized, x,y) |
|---|---|---|
| A | 1, 2, 3 | (0.10,0.05), (0.02,0.04), (-0.05,0.03) |
| B | 1, 2, 3 | (0.30,0.06), (0.24,0.05), (0.18,0.04) |
| C | 1, 2 | (0.12,0.28), (0.05,0.27) |
| D | 2, 3 | (0.20,0.29), (0.13,0.28) |
Step 2 — Initialize from pair (1,2). This pair has the most shared tracks (A, B, C) with a wide-enough baseline. Estimate the essential matrix via the 5-point algorithm inside RANSAC, decompose into four candidates, and pick the one where triangulated points A, B, C all lie in front of both cameras (the cheirality check). Suppose this yields:
(translation direction only — scale is set arbitrarily to , fixing the global scale of the whole reconstruction). Triangulating A, B, C from images 1 and 2 by linear least squares (solving the homogeneous DLT system built from the two projection equations per view) gives initial 3D estimates, e.g. .
Step 3 — Register camera 3 via PnP. Camera 3 shares tracks A, B (with known 3D positions from step 2) plus D (not yet triangulated, so unusable for PnP) — only A and B give 2D–3D correspondences, which is below the minimum of 3 needed for a non-degenerate PnP solve. The pipeline therefore defers camera 3 and instead checks whether any other 2D–3D correspondence exists; finding none sufficient, it falls back to also triangulating D using only the (1,2)/(2,3) overlap once camera 2's pose is fixed, or — more realistically in a bigger dataset — simply waits until enough tracks accumulate. This illustrates a genuine pitfall: sparse early overlap can stall registration, which real pipelines handle by trying several candidate next-images and picking the one with the most 2D–3D correspondences, not a fixed order. Suppose in this toy case a fourth image (not shown) actually supplies a third correspondence, so camera 3 registers successfully via PnP-RANSAC, yielding with a reprojection RMS error of 1.3 pixels (equivalent) on inliers — acceptable, so camera 3 is accepted.
Step 4 — Triangulate D. With camera 2 and camera 3 both posed, triangulate D from its two observations and , giving .
Step 5 — Bundle adjustment. Before BA, total reprojection error across all 10 observations (A,B,C,D across their respective images) is, say, summed squared error, concentrated mostly in point C (only seen from 2 relatively close viewpoints, poorly conditioned). Running LM for a handful of iterations jointly adjusts and all four 3D points, redistributing error and typically reducing to well under 1 px² RMS per observation for a well-conditioned scene — the joint adjustment can improve point C's depth estimate specifically because errors that were previously "hidden" in a single pairwise triangulation are now visible as inconsistencies across the whole observation set. This trace makes the two central lessons concrete: geometry from a single wide-baseline pair seeds an arbitrary but internally consistent scale, and every subsequent camera and point addition should be understood as fitting new evidence into that fixed frame, with periodic bundle adjustment as the only step that reconciles all evidence jointly rather than sequentially.
Common pitfalls & traps
| Pitfall | Why it happens | Consequence |
|---|---|---|
| Narrow-baseline initialization pair | Consecutive video frames are nearly identical viewpoints | Triangulation is ill-conditioned; huge depth uncertainty propagates through the whole reconstruction |
| Wrong cheirality branch from E decomposition | Essential matrix decomposition gives 4 candidate (R,t) pairs | Points reconstructed "behind" cameras; entire scene flipped or degenerate |
| Ignoring RANSAC in PnP registration | Assuming all 2D–3D matches from feature matching are correct | One mismatched point can pull the whole camera pose far off, corrupting all points it later triangulates |
| Drift from skipping intermediate bundle adjustment | Registering many cameras before ever re-optimizing | Small per-camera errors compound; loop closures fail even when geometrically the scene should be consistent |
| Forgetting the scale/gauge ambiguity | Treating recovered translations as metric without external reference | Absolute distances, sizes, and velocities silently wrong by an unknown global factor |
| Degenerate scene motion (pure rotation) | Camera only rotates, no translation, between some image pairs | Essential matrix becomes ill-defined / triangulation baseline is zero; those pairs must be excluded from initialization |
Pioneers & history
The mathematical roots of Structure from Motion trace to photogrammetry and projective geometry, but its formulation as a computer vision algorithm — and the recognition that bundle adjustment could be scaled to internet-sized unordered photo collections — is a late-20th/early-21st-century achievement built on decades of multi-view geometry work.
Origins · 1 minRichard Hartley and how this was discovered
Richard Hartley, with Andrew Zisserman, systematized the projective-geometric theory underlying epipolar geometry, camera calibration, and triangulation into a single coherent mathematical framework, providing the theoretical scaffolding on which practical SfM pipelines are built.
Where this matters
Structure from Motion is the geometric backbone underneath a wide range of production systems. Photogrammetry software (Agisoft Metashape, Pix4D, RealityCapture) runs SfM on drone and handheld imagery to produce surveyed 3D models for construction, agriculture, and archaeology. Google Street View, mapping companies, and autonomous-driving perception stacks use SfM-style bundle adjustment to fuse imagery with GPS/IMU data into consistent maps. Visual SLAM systems used in robotics and augmented reality (ARKit, ARCore, ORB-SLAM) run an online, incremental cousin of this same pipeline, adding local bundle adjustment ("pose-graph" or "windowed" BA) to keep tracking drift-free in real time. Most strikingly, the modern wave of neural 3D reconstruction — NeRF, 3D Gaussian Splatting, and their many derivatives — almost universally takes calibrated camera poses produced by classical SfM (typically COLMAP) as their required input, meaning that this decades-old pipeline remains the unglamorous but indispensable first stage of state-of-the-art neural rendering research.
Ask the tutor: How does the choice of rotation parametrization (quaternion vs. axis-angle vs. rotation matrix) affect the convergence and numerical stability of bundle adjustment?
Why can't SfM recover absolute scale from images alone?
Projecting a 3D scene through a pinhole camera loses all sense of absolute distance: a scene twice as large, viewed from twice as far away with twice the camera baseline, produces identical pixel coordinates in every image. This is a fundamental property of perspective projection, not a limitation of any particular algorithm — it holds for the true optimal maximum-likelihood reconstruction just as much as for a crude approximation. Recovering metric scale therefore requires information outside the images: a known real-world distance between two points, a calibrated stereo baseline as in Stereo Vision and Depth rigs, GPS/IMU measurements, or an object of known size visible in the scene. Without one of these, SfM output is correct only up to a single global scale factor applied to all camera translations and all 3D points simultaneously.
How is Structure from Motion different from stereo vision?
Stereo vision, covered in Stereo Vision and Depth, assumes two (or more) cameras with known relative pose and calibration, and solves purely for per-pixel depth via correspondence search along rectified epipolar lines. SfM assumes camera poses are unknown and must be estimated jointly with 3D structure from an arbitrary, often unordered, set of images with no assumed rig geometry. In practice SfM often uses stereo-like triangulation as a subroutine once poses are estimated, but the core unknowns — and hence the core difficulty — are different: stereo is a depth-estimation problem given geometry, while SfM is a geometry-and-structure estimation problem given only pixel correspondences.
Why is bundle adjustment run only periodically rather than after every single new camera?
Bundle adjustment over cameras and points, even with the Schur complement trick, still costs roughly per iteration for the reduced camera system solve, so re-running full BA after every single new image registration would make total pipeline cost scale unfavorably as the reconstruction grows into the thousands of cameras. Real pipelines instead run cheap local bundle adjustment over only the newest few cameras and their points after each registration, and defer expensive global bundle adjustment over the entire reconstruction to occur only periodically (e.g., every 10–20 new cameras) and always once at the very end. This balances keeping local drift in check against the overall runtime budget.
What happens if the essential matrix decomposition picks the wrong rotation/translation candidate?
Decomposing an essential matrix always yields four algebraically valid candidates, and only one places the triangulated 3D points in front of both cameras (positive depth in both camera coordinate frames) — this is the cheirality check. If the wrong candidate is chosen, triangulated points end up behind one or both cameras, or the reconstruction is mirrored, and every downstream PnP registration and bundle adjustment step will either fail outright (reprojection error explodes) or converge to a geometrically nonsensical but numerically "consistent" degenerate solution. Correct pipelines always test cheirality on a handful of triangulated points from each of the four candidates and select the one where the vast majority of points are in front of both cameras.
Does SfM work if some images have completely different, unknown camera intrinsics?
Yes — this is the common real-world case for internet photo collections shot on different phones and cameras. If EXIF metadata gives approximate focal length, that seeds an initial intrinsics estimate per camera; if not, the essential/fundamental matrix estimation can proceed with the (weaker) fundamental matrix formulation and intrinsics can be included as additional free parameters refined jointly during bundle adjustment (sometimes called "self-calibrating" bundle adjustment). This adds more unknowns and increases the risk of the optimization confusing focal length changes with depth changes (a known near-ambiguity for narrow fields of view), so pipelines usually constrain intrinsics with priors from EXIF sensor and lens metadata whenever available rather than leaving them fully unconstrained.
How does SfM relate to visual SLAM used in robots and AR devices?
Visual SLAM (Simultaneous Localization and Mapping) solves essentially the same joint pose-and-structure estimation problem as SfM, but under real-time and streaming constraints: frames arrive sequentially, poses must be output with low latency, and the system must handle loop closures (recognizing a previously visited place) to correct accumulated drift. SLAM systems use the same core machinery — feature tracking, PnP-style pose estimation, triangulation, and windowed or pose-graph bundle adjustment — but trade off some global optimality for speed, often running full offline-style bundle adjustment only in a background thread. SfM is thus SLAM's offline, unordered, typically higher-accuracy sibling.
Why does incremental SfM sometimes produce two disconnected sub-reconstructions instead of one?
If the correspondence graph across images has a genuine "gap" — a subset of images that share no reliable feature tracks with the rest, due to occlusion, extreme viewpoint change, repetitive texture confusing matches, or simply insufficient overlap — the incremental registration loop cannot bridge it, since PnP registration strictly requires 2D–3D correspondences to already-triangulated points. The pipeline then produces two or more separate reconstructions, each internally consistent but with no known relative transform between them. Merging them afterward requires finding some shared observations (even a handful of matching 3D points or a common image) and solving a small rigid/similarity alignment problem, or simply improving the initial feature-matching stage to close the gap in the first place.