Linear Triangulation

Triangulation reconstructs a 3D point from its projections in two or more calibrated views. It is the inverse of the projection operation: given pixel observations and camera poses, recover the world point.

Problem Statement

Given: views with poses (world to camera ) and, in each, the bearing along which the point is seen: the unit ray from the camera's own unprojection of the observed pixel.

Find: 3D point in the world frame such that .

Assumptions:

  • Camera poses and models are known
  • Correspondences are correct
  • The point is visible from all views (not occluded)

Working on bearings rather than on image points serves every camera model: a pinhole's bearing is of its normalized coordinates, and a fisheye's may point more than 90° from its axis, where normalized coordinates do not exist.

Derivation (DLT)

With and the point , the constraint says is parallel to , i.e. their cross product vanishes:

This gives three equations per view, of rank two. Stacking all views gives a system:

Solve via the smallest eigenvector of (the smallest-singular direction): is the right singular vector corresponding to the smallest singular value. Dehomogenize: . A near-zero second-smallest singular value (identical cameras, coincident rays) is reported as a degenerate system.

Limitations

  • Algebraic error: The DLT minimizes algebraic error, not geometric (reprojection) error, and carries no covariance
  • Baseline sensitivity: For small baselines (nearly parallel views), the triangulation is poorly conditioned — small pixel errors lead to large depth errors
  • Outlier sensitivity: A single incorrect correspondence corrupts the result; no robustness mechanism

For accurate points, refine them on the reprojection error with their noise stated: the calibrated entry point, mvg::triangulation::triangulate, does so, and returns the points' covariance with the calibration's share (see Pose Recovery and Triangulation). The linear estimate is its starting point.

API

#![allow(unused)]
fn main() {
let X = triangulate_point_linear(&cam_se3_world, &bearings)?;
}

Where cam_se3_world holds each view's Iso3 pose and bearings the matching rays (any nonzero length). The calibrated entry point, mvg::triangulation::triangulate, builds the bearings from pixels through a CalibratedRig, refines the points and adds diagnostics and covariance; see Pose Recovery and Triangulation.

In Python, vision_calibration.mvg.triangulate is the calibrated entry point; the raw linear solver is Rust only (numpy and OpenCV cover linear algebra on plain arrays).

Deterministic two-view solvers, all free functions in per-topic modules:

ModuleFunctions
homographydlt_homography, dlt_homography_ransac
epipolarfundamental_8point, essential_5point
triangulationtriangulate_point_linear
camera_matrixdlt_camera_matrix

The facade re-exports the main solvers at the root of its mvg module (vision_calibration::mvg::dlt_homography, …); the full set is in the vision-geometry crate.