Search…

3D Vision Basics: SfM, Point Clouds, and Pose Estimation

In this series (30 parts)
  1. Computer Vision Roadmap: From Basics to Real Projects
  2. What is Computer Vision? From Pixels to Decisions
  3. A Short History of Computer Vision: 1545 to Now
  4. Math for CV Beginners: Vectors, Matrices, Convolutions
  5. Image Fundamentals: Color, Histograms, Noise, Filtering
  6. How Images and Video Are Stored: Colour, JPEG, Frames
  7. OpenCV Setup + First 10 Tasks in Python
  8. Vision Metrics: Accuracy, Precision, Recall, mAP, IoU
  9. CV Project Workflow: Dataset, Baseline, Error Iteration
  10. Intensity Transforms and Frequency-Domain Filtering
  11. Edge Detection and Thresholding That Actually Work
  12. Morphology, Contours, and Shape Analysis for Real Images
  13. Feature Matching (SIFT/ORB) and Image Stitching
  14. Camera Calibration and Perspective Correction
  15. Epipolar Geometry, Stereo Vision, and Depth Estimation
  16. Optical Flow and Motion Tracking in Video
  17. HOG, HOF and MBH: Descriptors Before Deep Learning
  18. Your First Image Classifier (PyTorch + Transfer Learning)
  19. Data Pipelines and Augmentation for Vision Models
  20. CNN Architectures Explained: From LeNet to ResNet
  21. YOLO Detection Pipeline: Data to Inference
  22. Semantic and Instance Segmentation: U-Net to Mask R-CNN
  23. Vision Transformers (ViT) and When to Use Them
  24. CV Explainability: Grad-CAM, Failures, Bias Checks
  25. 3D Vision Basics: SfM, Point Clouds, and Pose Estimation
  26. Multimodal Vision: CLIP, Embeddings, and Retrieval Systems
  27. Video Understanding: Flow Networks, Interpolation, Stabilisation
  28. Real-Time CV Systems: Tracking, Latency, Streaming
  29. Deploying CV Models: ONNX, TensorRT, Edge, and APIs
  30. Responsible CV: Privacy, Fairness, Security, Governance

A construction site needs to know how much gravel is in a stockpile. The old way is a surveyor with a total station, half a day, and a bill. The new way is a drone flying a grid pattern for eight minutes, then software that turns 40 overlapping photos into a 3D surface and reports 2,847 cubic metres.

Prerequisites: Epipolar geometry and stereo vision and camera calibration.

Two different questions

# Question You know You want Method target
1 Where is my camera? 3D points and their image positions camera pose PnP
2 Where are the points? camera poses and image positions 3D points Triangulation
3 Both, from nothing? just overlapping photos poses and points Structure from Motion
4 How do two scans line up? two point clouds rigid transform between them ICP

Four problems in 3D vision. SfM is the hard one because it solves for both unknowns at once.

Stereo vision from the previous post assumed you knew the baseline and both cameras’ orientations. SfM drops that assumption. A drone photographs a stockpile from 40 unknown positions, and the software works out both where the drone was and what it was looking at.

The SfM pipeline

Every step before bundle adjustment is feature matching applied at scale. The new part is doing it consistently across 40 images instead of 2.

feature track One physical 3D point, followed through every image it appears in. A track spanning 12 images gives 12 constraints on that single point's position.

Track length is the quality signal that matters. A point seen in 2 images is barely constrained and often wrong. A point seen in 12 images is solid.

# Track length Number of tracks Median reprojection error target Keep?
1 2 images 41200 3.84 px no — discard
2 3 images 12800 1.92 px marginal
3 4–6 images 9400 0.71 px yes
4 7–11 images 6300 0.44 px yes
5 12+ images 2800 0.31 px yes, these anchor the reconstruction

Track length against accuracy on a 40-image job. Two-image tracks are 12x worse than 12-image tracks.

Bundle adjustment

The heart of SfM. Adjust every camera pose and every 3D point simultaneously so that projected points land as close as possible to the observed image points.

minRi,ti,Xjijvijπ(Ki,Ri,ti,Xj)xij2\min_{\mathbf{R}_i, \mathbf{t}_i, \mathbf{X}_j} \sum_{i}\sum_{j} v_{ij}\left\| \pi(\mathbf{K}_i, \mathbf{R}_i, \mathbf{t}_i, \mathbf{X}_j) - \mathbf{x}_{ij} \right\|^2

where vijv_{ij} is 1 if point jj was observed in image ii.

reprojection error The pixel distance between where a 3D point projects into an image and where it was actually observed. It is the only error measure available, since the true 3D positions are unknown.

One reprojection error, computed. A 3D point projects to (1059.0,474.0)(1059.0, 474.0) using the current pose estimate. It was observed at (1061.3,472.5)(1061.3, 472.5):

Δx=1061.31059.0=2.3,Δy=472.5474.0=1.5\Delta x = 1061.3 - 1059.0 = 2.3, \qquad \Delta y = 472.5 - 474.0 = -1.5 e=2.32+1.52=5.29+2.25=7.54=2.746 pxe = \sqrt{2.3^2 + 1.5^2} = \sqrt{5.29 + 2.25} = \sqrt{7.54} = 2.746 \text{ px}

Bundle adjustment nudges the pose and the point to shrink that, summed over every observation.

The size of the problem

# Quantity Count Parameters each Total target
1 Cameras 40 6 (3 rotation + 3 translation) 240
2 3D points 18500 3 (x, y, z) 55500
3 Total unknowns 55740
4 Observations 142000 2 (u, v residual) 284000

A modest 40-image reconstruction. 284,000 equations for 55,740 unknowns — over-constrained by about 5x.

Over-constrained is what you want. Five times more equations than unknowns means noise averages out rather than being absorbed into a perfect but meaningless fit.

The problem is only solvable because the Jacobian is extremely sparse. Point 4,201 was seen by 6 of the 40 cameras, so its rows are zero for the other 34. Solvers exploit that structure with the Schur complement, which is why a 55,740-parameter optimisation runs in seconds rather than hours.

Scale: the thing everyone forgets

SfM cannot recover absolute scale. A large stockpile photographed from far away and a small model photographed from close up produce mathematically identical reconstructions.

scale ambiguity A reconstruction from images alone is correct up to an unknown multiplicative factor. Shapes and ratios are right; sizes are not.

The fix is one object of known length in the scene. Place a 1.200 m survey target, then measure its two endpoints in the reconstruction:

endpoint A=(0.0142,  0.0087,  0.0031)\text{endpoint A} = (0.0142,\; 0.0087,\; 0.0031) endpoint B=(0.0489,  0.0233,  0.0055)\text{endpoint B} = (0.0489,\; 0.0233,\; 0.0055)

d=(0.0347)2+(0.0146)2+(0.0024)2d = \sqrt{(0.0347)^2 + (0.0146)^2 + (0.0024)^2} =0.00120409+0.00021316+0.00000576=0.00142301=0.03772= \sqrt{0.00120409 + 0.00021316 + 0.00000576} = \sqrt{0.00142301} = 0.03772

s=1.2000.03772=31.81 metres per reconstruction units = \frac{1.200}{0.03772} = 31.81 \text{ metres per reconstruction unit}

Multiply every coordinate by 31.81 and the model is in metres. Every length, area, and volume you compute afterwards inherits this one number’s accuracy.

# Scale source Typical accuracy target Effort Note
1 Known-length target in scene ±0.5% low Measure it carefully — error propagates to everything
2 Multiple ground control points with GPS ±0.02 m absolute high Also fixes position and orientation
3 Drone GPS from image EXIF ±2–5 m absolute none Fine for scale, useless for precision
4 RTK GPS on the drone ±0.02 m moderate Standard for commercial survey work
5 Nothing unknown none Reconstruction is unitless

Ways to give a reconstruction real units.

Pose estimation with PnP

Once you have 3D points, finding a new camera’s position is a separate, much easier problem.

PnP (Perspective-n-Point) Given n known 3D points and their observed 2D image positions, find the camera's rotation and translation.

The counting argument is worth understanding. A camera pose has 6 degrees of freedom. Each observed point gives 2 equations, so 3 points give 6 equations for 6 unknowns — exactly determined. But the geometry admits up to 4 valid solutions, so a 4th point picks the right one.

# Method Minimum points target Handles Use for
1 P3P 3 (+1 to disambiguate) calibrated camera RANSAC hypothesis generation
2 EPnP 4 calibrated camera Fast, good general default
3 DLT 6 uncalibrated — solves K too When intrinsics are unknown
4 Iterative (LM) 4 refines an initial guess Final polish after RANSAC

PnP variants. In practice you run P3P inside RANSAC, then refine with iterative on the inliers.

ok, rvec, tvec, inliers = cv2.solvePnPRansac(
    object_points,          # (N,3) 3D points in world coords
    image_points,           # (N,2) their observed pixel positions
    K, dist_coeffs,
    reprojectionError=3.0,  # inlier threshold in pixels
    flags=cv2.SOLVEPNP_EPNP)

R, _ = cv2.Rodrigues(rvec)          # 3x3 rotation matrix
camera_position = -R.T @ tvec        # camera centre in world coordinates

That last line catches nearly everyone. tvec is not the camera’s position. It is the translation part of the world-to-camera transform, so the camera centre in world coordinates is Rt-\mathbf{R}^\top \mathbf{t}.

Aligning two clouds with ICP

The site is scanned in March and again in June. To compare them, they must be in the same coordinate frame.

ICP (Iterative Closest Point) Repeatedly pair each source point with its nearest target point, solve for the rigid transform that best aligns those pairs, apply it, and repeat until the change is small.

One iteration, worked. Source points PP and target points QQ, in 2D for clarity:

# Pair Source P Target Q
1 1 (1.0, 1.0) (4.0, 2.0)
2 2 (2.0, 1.0) (5.0, 2.0)
3 3 (1.5, 2.0) (4.5, 3.0)

Three matched point pairs.

Step 1 — centroids.

Pˉ=(1.0+2.0+1.53,  1.0+1.0+2.03)=(1.500,  1.333)\bar{P} = \left(\frac{1.0 + 2.0 + 1.5}{3},\; \frac{1.0 + 1.0 + 2.0}{3}\right) = (1.500,\; 1.333)

Qˉ=(4.0+5.0+4.53,  2.0+2.0+3.03)=(4.500,  2.333)\bar{Q} = \left(\frac{4.0 + 5.0 + 4.5}{3},\; \frac{2.0 + 2.0 + 3.0}{3}\right) = (4.500,\; 2.333)

Step 2 — centre both sets.

# Pair P − P̄ Q − Q̄
1 1 (−0.500, −0.333) (−0.500, −0.333)
2 2 (0.500, −0.333) (0.500, −0.333)
3 3 (0.000, 0.667) (0.000, 0.667)

The centred sets are identical, so the cross-covariance matrix is symmetric and its SVD gives R = I.

Step 3 — rotation. The SVD of the cross-covariance yields R=I\mathbf{R} = \mathbf{I}, no rotation.

Step 4 — translation.

t=QˉRPˉ=(4.5001.500,  2.3331.333)=(3.000,  1.000)\mathbf{t} = \bar{Q} - \mathbf{R}\bar{P} = (4.500 - 1.500,\; 2.333 - 1.333) = (3.000,\; 1.000)

Apply it and the clouds coincide exactly. Real data is never this clean — the correspondences are wrong on the first iteration, so the transform is approximate, which makes the next round of correspondences better, and so on.

# ICP iteration Mean point-to-point distance (m) target Correspondences correct
1 0 (initial) 2.847 31%
2 1 0.912 58%
3 2 0.341 79%
4 4 0.088 94%
5 8 0.031 98%
6 16 0.029 98%

ICP convergence. It plateaus at 0.029 m, which is the actual sensor noise floor — no further iterations help.

ICP finds a local minimum only. If the two clouds start far apart or badly rotated, it converges confidently to the wrong answer. Give it a rough initial alignment from GPS, a manual click of three corresponding points, or a global method like FPFH feature matching.

PropertyPhotogrammetry (SfM)LiDARDepth camera
Hardware costany camera£3,000–£100,000£150–£500
Works outdoors in sunyesyesno (IR washes out)
Works on textureless surfacespoorlyyesyes
Absolute scaleneeds a referencebuilt inbuilt in
Rangeany, with enough baseline50–300 m0.5–8 m
Colouryes, nativeno (needs a camera too)usually
Processing timeminutes to hoursnear real timereal time
Three ways to get 3D. Photogrammetry trades processing time for hardware cost.

From point cloud to cubic metres

The deliverable. Grid the cloud into a height map, take the height above a base plane in each cell, and sum.

Cell size 0.5 × 0.5 m, so each cell covers 0.25 m20.25\ \text{m}^2. Heights above the base plane:

Height above base (m) (4×4)
0
0.2
0.3
0
0.2
1.4
1.6
0.3
0.3
1.7
1.9
0.4
0
0.3
0.4
0.1

Row sums: 0.50.5, 3.53.5, 4.34.3, 0.80.8. Total height sum:

0.5+3.5+4.3+0.8=9.1 m0.5 + 3.5 + 4.3 + 0.8 = 9.1 \text{ m}

V=9.1×0.25=2.275 m3V = 9.1 \times 0.25 = 2.275 \text{ m}^3

Scaled up to the real stockpile’s 11,388 cells the same arithmetic gives 2,847 m³.

The base plane is where the judgement lives. A stockpile sits on ground that is rarely flat, so fitting a plane through the surrounding terrain points — rather than assuming z=0z = 0 — is what separates a usable number from a guess.

# Base plane choice Volume (m³) Error vs surveyed truth target
1 z = 0 (assumed flat) 3104 +9.0%
2 Mean height of surrounding ring 2891 +1.5%
3 Least-squares plane through ring 2847 0.0% (reference)
4 Triangulated terrain surface 2839 −0.3%

Same point cloud, four base-plane assumptions, a 265 cubic metre spread. The model was never the uncertain part.

Practice task

Use COLMAP, which is free, and any object you can walk around.

  1. Take 40 photos with clear overlap. Include a ruler or a measured object in view.
  2. Run COLMAP’s automatic reconstruction. Note how many of the 40 cameras it registers.
  3. Read the reported mean reprojection error. Anything above 1.5 px means something is wrong.
  4. Plot the histogram of track lengths. Compare it against the table above.
  5. Measure your reference object in the reconstruction and compute the scale factor.
  6. Verify by measuring a second known distance after scaling. How far off is it?
  7. Redo the capture with only 20 photos. Compare registered cameras and reprojection error.
  8. Export the dense cloud, grid it into a height map, and compute a volume two ways: assuming a flat base, and fitting a plane. Compare.

Step 7 is the one that changes how you capture. The failure with 20 photos is usually not graceful degradation — it is whole cameras failing to register and the reconstruction splitting into disconnected pieces.

Summary

SfM recovers camera poses and 3D structure together from unposed photos. Feature tracks are the raw material, and track length predicts accuracy directly: 2-image tracks had 3.84 px reprojection error, 12-image tracks had 0.31 px. That is the entire argument for 80% overlap.

Bundle adjustment refined 55,740 parameters — 40 cameras at 6 DOF plus 18,500 points at 3 — against 284,000 residuals. One reprojection error worked out to 2.746 px from a 2.3 and 1.5 pixel offset.

The reconstruction has no scale until you give it one. A 1.200 m target measuring 0.03772 units gave a factor of 31.81 m per unit, and because volume scales cubically, a 1% error there is 3% on the volume.

PnP finds a new camera’s pose from known 3D points, needing 3 plus 1 to disambiguate — and tvec is not the camera position, Rt-\mathbf{R}^\top\mathbf{t} is.

ICP aligned two scans by alternating correspondence and transform: centroids at (1.500,1.333)(1.500, 1.333) and (4.500,2.333)(4.500, 2.333) gave t=(3.000,1.000)\mathbf{t} = (3.000, 1.000). On real data it converged from 2.847 m to 0.029 m in 8 iterations.

The volume came from height times cell area: 9.1 m of summed height over cells of 0.25 m² gave 2.275 m³. The base plane choice moved the full-site answer by 265 m³.

What comes next

Everything so far has connected pixels to geometry. Multimodal vision connects pixels to language instead — putting images and text in one shared space so you can search a photo library by typing a description, and classify things the model was never explicitly trained on.

Test your understanding
Your SfM reconstruction of a stockpile looks perfect, but the reported volume is 40 times too small. What happened?
Test your understanding
ICP converges quickly and reports a low final error, but the two scans are visibly misaligned by about half the object's width. Why?

Frequently asked questions

How many photos do I need?
Enough that every part of the surface appears in at least four or five images, which for a typical object means 30 to 60 photos and for a site survey means whatever a 75% overlap flight plan produces. What matters is overlap rather than raw count: 60 well-overlapping photos beat 200 taken from scattered positions. Aim for smooth, continuous coverage rather than a handful of dramatic angles.
Why did some cameras fail to register?
Almost always insufficient overlap with the images already registered, or too few matchable features in that view. Blank walls, water, sky-dominated frames, and motion blur all produce too few keypoints. Check the match graph — a reconstruction that splits into two disconnected components means there was a gap in coverage between them, and adding a few photos bridging that gap usually fixes it entirely.
Sparse or dense reconstruction?
Sparse comes out of SfM directly and contains only the matched feature points, typically tens of thousands, which is enough for camera poses and rough structure. Dense reconstruction runs multi-view stereo afterwards to estimate depth for nearly every pixel, giving millions of points and a usable surface. You need dense for volumes, meshes, and inspection; sparse is enough for localisation and for checking whether the capture worked before committing to hours of processing.
Can I use a phone?
Yes, and modern phone cameras are good enough for most photogrammetry. Two cautions. Turn off any HDR or computational photography mode that alters geometry between frames, since SfM assumes a consistent camera model. And avoid switching between the wide and ultra-wide lens mid-capture, because that changes the intrinsics halfway through and confuses the self-calibration.
How accurate is drone photogrammetry for volumes?
With proper ground control and good overlap, typically 1 to 2% on stockpile volumes, which is comparable to a total-station survey. The dominant error source is almost never the reconstruction — it is the base plane assumption and the scale reference. Changing from an assumed flat base to a fitted plane moved one 2,847 cubic metre result by 257 cubic metres, far more than any algorithmic difference.
Start typing to search across all content
navigate Enter open Esc close