Contents — find the section you need
Background and reading guide

Background Basic vectors, matrices and camera projection.

Fundamentals · Includes code examples.

Suppose an image taken with a camera has several known 3D points on a map that correspond to points in it. The problem of finding where the camera was placed and which way it was pointing is PnP (Perspective-n-Point). It's used in common across Visual SLAM map tracking, overlaying virtual objects in AR, hand-eye calibration for robots, and pose estimation for surveying cameras.

0. 30-Second Summary

  • The input is the camera intrinsic matrix K, known 3D points \mathbf X_i, and their corresponding image points \mathbf u_i. The output is a rotation R and translation t.
  • Minimize reprojection error under \mathbf u_i\sim K(R\mathbf X_i+t). Three-point P3P can return multiple poses; additional points help select a solution. Four points alone do not guarantee outlier detection; use more correspondences and RANSAC.
  • EPnP expresses each point as a linear combination of 4 virtual control points, solving for many points quickly. A final nonlinear optimization such as Levenberg–Marquardt then refines the result.
  • If outliers get mixed into the correspondences, the entire pose estimate can collapse, so it's verified with RANSAC-PnP, positive-depth checks, and frame-to-frame consistency.
  • Estimates can become unstable when image points are concentrated in a small area, intrinsics are wrong, or rolling shutter or moving objects violate the model. Planar targets also have dedicated solvers such as IPPE.

1. The Projection Model

Diagram 1 · Use the button to switch views
PnP flow that projects known 3D points with a pose hypothesis and uses residuals to update the pose through RANSAC and nonlinear refinement

Figure 1 — Matching IDs define 3D–2D correspondences. PnP forms a pose hypothesis, rejects outliers by reprojection residual, and refines R,t, which map world coordinates into camera coordinates.

Let a point in the camera coordinate frame be \mathbf X_c=R\mathbf X_w+t. In the pinhole model, the normalized image coordinates are

x=\frac{X_c}{Z_c},\qquad y=\frac{Y_c}{Z_c}

and the pixel coordinates are obtained via the intrinsic matrix

K=\begin{bmatrix}f_x&0&c_x\\0&f_y&c_y\\0&0&1\end{bmatrix}

as \mathbf u\sim K\mathbf X_c. R\in SO(3) is the rotation and t the translation. If there's lens distortion, distortion correction is needed before and after projection.

The unknowns are the 6 degrees of freedom of 3 rotational plus 3 translational. Given n correspondences between 3D points \mathbf X_i and observations \mathbf u_i, the reprojection error

E(R,t)=\sum_{i=1}^{n}\rho\left(\left\|\mathbf u_i-\pi(K(R\mathbf X_i+t))\right\|^2\right)

is minimized. \pi is the perspective division and \rho is a robust loss such as Huber's.

Do not confuse the transform with the camera position

OpenCV's solvePnP returns rvec, tvec for the transform that maps object/world points into the camera frame. To obtain the camera center in world coordinates, use \mathbf C_w=-R^Tt; for a camera pose, invert T_{cw} to obtain T_{wc}. Treating tvec itself as the camera's world position is a common error. Also avoid applying distortion twice when the input image points have already been undistorted.

2. P3P, AP3P, and EPnP

P3P (Perspective-3-Point), which recovers the distance from the camera center using the image angles of 3 points and the distances between the 3D points, has up to 4 solutions. The correct one is selected by checking against a 4th point or against the map's known pose. AP3P is a fast variant that reorganizes the solution algebraically.

When there are many points, EPnP (Efficient PnP) expresses every 3D point as a weighted sum of 4 virtual control points.

\mathbf X_i=\sum_{j=1}^{4}\alpha_{ij}\mathbf C_j,\qquad \sum_j\alpha_{ij}=1

The control points' camera coordinates are found from linear equations, and the rotation and translation are recovered from them. Because its computational cost is nearly linear in the number of points, it's well suited to building an initial pose from SLAM's many landmarks. After the initial solution, the reprojection error is refined iteratively with Levenberg–Marquardt.

3. RANSAC-PnP

Feature-point correspondences get mixed with similar-looking patterns, moving objects, and wrong map IDs. The standard approach is RANSAC: build a tentative pose from a minimal point set, reproject every correspondence, and count how many inliers fall within a threshold. The number of iterations N needed, given outlier rate \epsilon, minimal sample size s, and success probability p, is determined by

N\ge\frac{\log(1-p)}{\log(1-(1-\epsilon)^s)}

Since the required number of iterations grows sharply with the outlier rate, lower \epsilon beforehand using a feature-point ratio test, grid-based dispersion, or a dynamic-object mask. OpenCV's solvePnPRansac is used by explicitly specifying the point count, flags (EPNP, P3P, SQPNP, etc.), reprojection threshold, and confidence.

4. Spotting Degeneracy

Coplanar point sets

Planar points can support pose estimation; OpenCV provides IPPE specifically for this configuration. However, near-frontal views or distant targets can make competing poses difficult to distinguish under noise. A homography also describes the correspondences on a plane. Use a suitable solver and inspect candidate poses rather than treating every planar target as unsolvable.

Narrow image coverage and long range

PnP directly uses one image and known 3D points, so inter-frame parallax is not itself a required input. It is nevertheless poorly conditioned when correspondences occupy only a small image region, the target is distant and appears small, or the 3D points have little depth variation. Do not accept a result from its inlier count alone: inspect image coverage, reprojection RMSE, and pose covariance or sensitivity to perturbations, then fuse an IMU or depth sensor when needed.

Calibration and timing

Errors in focal length, principal point, and distortion turn into a systematic reprojection error across every point. A lens whose intrinsic matrix changes with zoom, temperature, or focus needs to be recalibrated. In vehicles and drones, if the row timing of a rolling shutter isn't synchronized with the IMU, PnP will return a "bent" camera pose.

5. The Role of PnP in Visual SLAM

In Visual SLAM, as the number of previously-triangulated map points grows, the camera pose can be tracked frame by frame with PnP. With the pose held fixed, new points are triangulated, and once enough keyframes have accumulated, Bundle Adjustment jointly optimizes the pose and the map. It's easiest to understand this as a division of labor: PnP is the lightweight front end, and Bundle Adjustment handles global consistency.

6. Implementation Checklist

  1. Calibrate K and the distortion with a checkerboard or similar, and record the reprojection error.
  2. Align the units (m/mm) and coordinate frame of the 3D points with the distortion-correction state of the image points.
  3. Narrow down correspondences with a ratio test, mutual nearest neighbors, and temporal tracking.
  4. Remove outliers with RANSAC-PnP, and save the inlier distribution and reprojection error.
  5. Check that depth is positive, that the pose change is physically plausible, and that the difference from the previous frame is reasonable.
  6. If conditions are poor, fall back to an IMU, depth, a homography, or reinitialization.

Minimal implementation sequence

With OpenCV, first obtain rvec, tvec, inliers from solvePnPRansac, pass only the inliers to solvePnPRefineLM, and finally use projectPoints to compute the inlier RMSE and error distribution yourself. A successful API return alone does not detect excessive mismatches, clustered points, or a physically impossible pose.

ok, rvec, tvec, inliers = cv2.solvePnPRansac(
    object_points, image_points, K, dist,
    flags=cv2.SOLVEPNP_EPNP,
    reprojectionError=3.0, confidence=0.999, iterationsCount=200,
)
if not ok or inliers is None or len(inliers) < 6:
    raise RuntimeError("PnP failed or has too few inliers")

idx = inliers.ravel()
rvec, tvec = cv2.solvePnPRefineLM(
    object_points[idx], image_points[idx], K, dist, rvec, tvec
)
projected, _ = cv2.projectPoints(object_points[idx], rvec, tvec, K, dist)
rmse = np.sqrt(np.mean(np.sum(
    (projected.reshape(-1, 2) - image_points[idx].reshape(-1, 2)) ** 2,
    axis=1,
)))
R, _ = cv2.Rodrigues(rvec)
camera_center_world = -R.T @ tvec.reshape(3, 1)

Neither 3.0 px nor six inliers is a universal acceptance threshold; they are only starting values for this example. Derive thresholds from image resolution, feature precision, and the application's allowed pose error. Also verify that the inliers are not clustered in one image corner and that every point has camera-frame depth Z_c>0.

A reproducible synthetic example: outliers versus wrong focal lengths

This is a synthetic calculation run by Duskcoil, not a measurement of a physical camera. Project 48 non-planar 3D points (metres) into a 1280×720 image, then add independent Gaussian noise with standard deviation 0.5 px to each image coordinate. In the outlier cases, replace 12 of those observations with random coordinates inside the image. The random seed is 20261001.

Ground truth uses f_x=f_y=800 px, principal point (640,360), and no distortion. The world-to-camera Rodrigues vector is (0.15,-0.10,0.05) rad and translation is (0.20,-0.10,5.0) m. Only the wrong-focal case changes the estimation focal lengths to 920 px, keeping the observations unchanged. EPnP initializes each pose. RANSAC uses a 3 px threshold, 200 iterations and confidence 0.999; LM refines the selected points. Without RANSAC, LM uses all 48 points.

Condition Points used for refinement Fit RMSE (px) Camera-centre error (m)
No outliers + RANSAC 48 0.660 0.0036
12 outliers, no RANSAC 48 185.826 1.9522
12 outliers + RANSAC 36 0.673 0.0077
Focal lengths 15% too large + RANSAC 48 1.242 0.7456

RMSE is evaluated on the correspondences used for refinement. Camera-centre error is \|\hat C_w-C_w\|, measured against the known synthetic truth. The fitted point sets differ between rows, so do not rank the methods solely by these RMSE values.

With outliers and no RANSAC, the API still returned success and every estimated depth was positive, yet the camera centre was about 1.95 m from the truth. RANSAC retained 36 points and reduced the centre error to about 0.0077 m on this input. With focal lengths 15% too large, all 48 points were retained: an RMSE of about 1.24 px still accompanied a centre error of about 0.75 m. A low reprojection error or positive depth alone does not establish that the intrinsics are correct.

This uses one layout and one seed. Results depend on point distribution, range, noise and outlier rate. In real monocular images the true camera centre is usually unknown, so this error cannot be computed directly. Validate against independent calibration or reference poses and multiple conditions.

Run it yourself

Executable reproduce.py · Input points CSV · Full results JSON

Recorded environment: Python 3.12.3 / OpenCV 4.6.0 / NumPy 1.26.4. Save reproduce.py and run the command below in an environment with NumPy and OpenCV installed. The script generates the inputs from its fixed seed; the downloadable CSV is an exported reference, not an input file consumed by the script. Different OpenCV versions, builds or random-number implementations can change final digits or selected inliers. Keep the environment information in the output JSON.

python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS / Linux: source .venv/bin/activate
python -m pip install numpy==1.26.4 opencv-python-headless==4.6.0.66
python reproduce.py --output results.json --inputs inputs.csv

7. Summary

PnP is the bridge that converts correspondences between a 3D map and a 2D image into a camera pose with 6 degrees of freedom. Build an initial solution with P3P/EPnP, remove outliers with RANSAC, and refine with nonlinear optimization. Only by managing point layout, calibration, image-point spread, and time synchronization together does this become a stable pose estimate for Visual SLAM or AR.

Check your understanding
Does PnP only need two images?

Its basic inputs are known 3D points, their 2D image correspondences, and camera intrinsics. This differs from estimating motion from 2D-to-2D matches.

References

Current step · 11 / 19 · Computer Vision Primer

What to read next

Review the backgroundEpipolar Geometry — Reading Depth and Camera Motion from Two ImagesContinue the seriesStructure from Motion Primer — Recovering 3D and Camera Positions Together From an Unordered Set of PhotosExplore another aspect of this fieldImage brightness and luminance Lab — exposure, gamma and clipping