Geometry & 3D Vision
Stereo Rectification
Warping a pair of stereo images by one homography each so that corresponding epipolar lines become the same image row, turning the correspondence search into a scan along rows.
intermediate
Stereo rectification resamples the two images of a stereo pair so that every pair of corresponding epipolar lines becomes one and the same horizontal row. Stereo matching then reduces to a one-dimensional search along a row, whose result is the disparity. Stereo vision explains why this configuration is wanted; this article covers how the transformations are computed, applied, and checked.
Problem
Given two images with known epipolar geometry, find a homography for each so that all epipolar lines become horizontal and corresponding lines share a coordinate: both epipoles go to the point at infinity . Among the many such pairs, choose one that distorts the images little.
Inputs and Outputs
- Calibrated: the intrinsics , the relative pose , and the distortion coefficients from camera calibration. Output: a rotation per camera, a common intrinsic matrix , the rectified projection matrices, and the resampling homographies or lookup maps.
- Uncalibrated: the fundamental matrix , plus point matches and image sizes for some methods. Output: two homographies .
Intuition
Rotating a camera about its center does not change which rays it sees, only the plane on which it records them, so the new image is a homography of the old one whatever the depth of the scene. Rectification keeps both camera centers fixed and turns both cameras, in software, to a common orientation with the axis along the baseline. With identical intrinsics, they form the ideal parallel rig, whose epipolar lines are the image rows.
Mathematical Formulation
The notation follows the epipolar geometry article: , , camera centers and , and .
What a rectified pair looks like
A pair is rectified when its fundamental matrix is, up to scale,
Both epipoles are , and the epipolar constraint says exactly that matching points share a row. If the images are warped by and , the fundamental matrix becomes , so the rectification condition is
The condition leaves several degrees of freedom. For example, a common vertical scale and shift of both images, or an independent horizontal scale, shear, and shift of either image, keeps corresponding rows together and the pair rectified. The methods below differ in how they spend this freedom.
Calibrated rectification
Fusiello, Trucco, and Verri (2000) construct the rectified cameras directly. Both keep their centers and receive the same orientation and the same intrinsics :
The rows of are the new camera axes, expressed in the first camera’s frame:
where is the old optical axis of the first camera, in this frame. Then for baseline length : the standard parallel rig. Fusiello et al. write ; the sign only flips the new axis, and this choice gives for a rig that is already rectified. Fusiello’s code sets to the average of the two intrinsic matrices, with zero skew.
Since and share a center, a pixel transforms by the homography formed from their left blocks:
The two images must share at least the vertical focal length and vertical principal point of , or rows stop corresponding. Different horizontal principal points only shift every disparity by a constant.
Uncalibrated rectification
Without calibration, Hartley (1999) builds the pair in two steps.
-
Send one epipole to infinity, nearly rigidly. Choose a point , typically the image center. Let translate to the origin, let rotate the epipole onto the axis at , and let
which sends to infinity. Then maps to and is, to first order, a rigid motion near .
-
Find the matching transformation. Write . Hartley shows that the homographies that match , in the sense of mapping corresponding epipolar lines to the same row, are exactly with
The parameters are chosen to minimize the horizontal disparities of the matched points, , where and are the matches transformed by and . This is a linear least-squares problem.
Loop and Zhang (1999) decompose each rectifying homography into a projective part, a similarity, and a shear. The projective parts send the epipoles to infinity and are chosen jointly to be as close to affine as possible over all pixels, by minimizing the variation of the homogeneous coordinate the pixels receive. The similarities make the epipolar lines horizontal and align the rows, and the shears, which change only , preserve, as far as possible, the perpendicularity and aspect ratio of the lines joining the midpoints of the image edges.
Algorithm
For a calibrated rig, the usual pipeline is:
- Calibrate both cameras and their relative pose, including lens distortion.
- Compute from the baseline and choose (focal length, principal points, output size).
- For every pixel of each rectified image, find its source: , rotate it back into the original camera’s frame, apply the forward lens distortion model, multiply by the original , and store the result in two lookup maps per camera.
- For every frame, resample each image at its map positions with interpolation, a backward image warp.
Steps 1–3 run once per rig. Combining undistortion and rectification in one map is necessary as well as efficient: a homography cannot straighten epipolar lines that distortion has curved, and one resampling blurs less than two. OpenCV’s initUndistortRectifyMap builds this joint map from a rectifying rotation and a new camera matrix, and remap applies it.
Parameters
- Rotation about the baseline. Any rotation of about preserves rectification. Fusiello ties to the first camera’s optical axis. OpenCV’s
stereoRectifysource instead first rotates the two cameras by half the relative rotation each, in opposite directions, to a common orientation, and then applies one shared rotation that aligns the baseline with , splitting the relative rotation evenly between the two images. - Common intrinsics and output size. A longer focal length in magnifies and crops; a shorter one keeps the whole field of view but leaves invalid borders and lowers resolution. OpenCV’s
stereoRectifyexposes this as the free scaling parameteralpha:alpha=0zooms and shifts the images so that only valid pixels are visible,alpha=1retains all source pixels, and intermediate values interpolate. It also returns a valid-pixel rectangle per image and accepts a larger output size, which its documentation says can preserve detail under strong radial distortion. - Principal points. OpenCV’s
CALIB_ZERO_DISPARITYflag gives both images the same principal point, so points at infinity have zero disparity; otherwise the images may be shifted to maximize the useful area, and the returned disparity-to-depth matrixQcarries the offset.
Complexity
Computing the transformations is negligible: a few matrix products in the calibrated case, a small linear least-squares problem for Hartley’s method. Applying them costs one interpolated lookup per output pixel, per image.
Implementation
The code rectifies two synthetic cameras with different intrinsics, a 12 cm baseline, and a few degrees of relative rotation, and measures the vertical disparity of projected 3D points before and after.
import numpy as np
from scipy.spatial.transform import Rotation
rng = np.random.default_rng(1)
# Two cameras: P = K [I | 0], P' = K' [R | t]. Slightly different intrinsics,
# a 12 cm baseline mostly along x, and a few degrees of relative rotation.
K1 = np.array([[700.0, 0, 320], [0, 705.0, 240], [0, 0, 1]])
K2 = np.array([[690.0, 0, 330], [0, 695.0, 235], [0, 0, 1]])
R = Rotation.from_euler("xyz", [2.0, -4.0, 1.5], degrees=True).as_matrix()
C1 = np.zeros(3)
C2 = np.array([0.12, 0.01, 0.005]) # second centre, first-camera frame
t = -R @ C2 # so that C' = -R^T t
def project(K, R, C, X):
x = (X - C) @ R.T @ K.T
return x[:, :2] / x[:, 2:]
def rectify(K1, R1, C1, K2, R2, C2):
"""Fusiello-style calibrated rectification. Ri are world-to-camera
rotations and Ci camera centres. Returns pixel homographies H1, H2 and the
common rotation Rn and intrinsics Kn of the rectified cameras."""
r1 = (C2 - C1) / np.linalg.norm(C2 - C1) # new x axis: along the baseline
r2 = np.cross(R1[2], r1) # new y axis: _|_ to old z and new x
r2 /= np.linalg.norm(r2)
r3 = np.cross(r1, r2) # new z axis completes the frame
Rn = np.vstack([r1, r2, r3])
Kn = (K1 + K2) / 2
Kn[0, 1] = 0.0 # common intrinsics, no skew
# Same centre, new orientation: x_new ~ Kn Rn Ri^T Ki^{-1} x_old
H1 = Kn @ Rn @ R1.T @ np.linalg.inv(K1)
H2 = Kn @ Rn @ R2.T @ np.linalg.inv(K2)
return H1, H2, Rn, Kn
def apply_h(H, x):
xh = np.hstack([x, np.ones((len(x), 1))]) @ H.T
return xh[:, :2] / xh[:, 2:]
X = rng.uniform([-1.5, -1.0, 2.0], [1.5, 1.0, 10.0], size=(200, 3))
x1 = project(K1, np.eye(3), C1, X)
x2 = project(K2, R, C2, X)
H1, H2, Rn, Kn = rectify(K1, np.eye(3), C1, K2, R, C2)
y1, y2 = apply_h(H1, x1), apply_h(H2, x2)
print("vertical disparity |y - y'| (px) max mean")
print(f" original images {np.abs(x1[:, 1] - x2[:, 1]).max():9.3f} {np.abs(x1[:, 1] - x2[:, 1]).mean():9.3f}")
print(f" rectified images {np.abs(y1[:, 1] - y2[:, 1]).max():9.1e} {np.abs(y1[:, 1] - y2[:, 1]).mean():9.1e}")
# Disparity in the rectified pair against f B / Z, with Z measured along the new axis.
B = np.linalg.norm(C2 - C1)
Z = (X - C1) @ Rn[2]
d = y1[:, 0] - y2[:, 0]
print(f" max |d - f B / Z| (px) {np.abs(d - Kn[0, 0] * B / Z).max():9.1e}")
# Same rotations, but each camera keeps its own intrinsics: rows no longer agree.
H1o = K1 @ Rn @ np.linalg.inv(K1)
H2o = K2 @ Rn @ R.T @ np.linalg.inv(K2)
z1, z2 = apply_h(H1o, x1), apply_h(H2o, x2)
print(f" own intrinsics, no common K {np.abs(z1[:, 1] - z2[:, 1]).max():9.3f} {np.abs(z1[:, 1] - z2[:, 1]).mean():9.3f}")
# Rectify with a calibration whose relative rotation is off by 0.1 degree.
dR = Rotation.from_euler("x", 0.1, degrees=True).as_matrix()
G1, G2, _, _ = rectify(K1, np.eye(3), C1, K2, dR @ R, C2)
w1, w2 = apply_h(G1, x1), apply_h(G2, x2)
print(f" 0.1 deg rotation error {np.abs(w1[:, 1] - w2[:, 1]).max():9.3f} {np.abs(w1[:, 1] - w2[:, 1]).mean():9.3f}")
Output:
vertical disparity |y - y'| (px) max mean
original images 55.387 33.014
rectified images 2.3e-13 4.4e-14
max |d - f B / Z| (px) 3.7e-13
own intrinsics, no common K 10.114 5.043
0.1 deg rotation error 1.465 1.228
Matching points that differed by up to 55 pixels vertically share a row after rectification, and the horizontal disparity equals , as for an ideal parallel rig. Rotation alone is not enough: if each camera keeps its own intrinsics, rows drift apart by up to 10 pixels. A relative pitch error of only 0.1° (about the axis, at a focal length near 700 pixels) leaves vertical disparities above a pixel, enough to hurt a matcher that searches a single row.
Properties and Behavior
- Exact for any scene. The homographies depend only on the cameras, so rectification holds for every scene point if the calibration or is accurate and distortion has been removed. Only calibrated rectification yields a metric parallel rig.
Checking rectification
The practical test is the vertical disparity of matched features: match features in the rectified pair, keep the inliers, and inspect . In a well-rectified pair it stays near zero, within the matcher’s sub-pixel accuracy. Its pattern suggests a cause: a near-constant offset points to a vertical principal-point mismatch or a relative pitch error (a pitch error shifts rows by about , so the two look alike), an offset that changes linearly across the image to a roll about the optical axis, and residuals that grow toward the corners to uncorrected distortion. Errors in calibration become vertical disparities that most dense matchers do not search, so the check is worth repeating whenever the rig may have been bumped or flexed.
Limitations
- Epipoles inside or near the image. A homography that sends an epipole to infinity sends a whole line through it to infinity. If the epipole lies inside the image, that line cuts the image and the warped image splits into two unbounded parts; just outside, the image is stretched enormously. Hartley (1999) analyzes when the homography is well behaved on the image (quasi-affine) and suggests resampling a smaller window otherwise. Forward motion, as for a camera on a vehicle, puts the epipole near the image center, and planar rectification is then unusable. In Fusiello’s construction the same case appears as , when the baseline is parallel to the optical axis.
Variants
- Vertical rigs. OpenCV’s
stereoRectifyalso handles cameras stacked vertically, aligning epipolar lines with columns instead of rows. ItsstereoRectifyUncalibratedimplements Hartley’s method, according to its documentation. - Polar rectification. Pollefeys, Koch, and Van Gool (1999) reparameterize each image in polar coordinates around its epipole: each row of the rectified image is an angular sector, and distance along the epipolar line becomes the column. Using the oriented fundamental matrix, they keep only half epipolar lines, and they choose the angular step so that no pixel is compressed. This handles any camera motion, including an epipole inside the image, at the cost of a nonlinear resampling stored as a lookup table. Cylindrical rectification, by Roy, Meunier, and Cox, was an earlier answer to the same problem.
Related
- Stereo Vision
Recovering depth from two images taken by cameras with a known relative pose, by measuring how far each point shifts between the views.
- Stereo Matching
Finding, for every pixel of one rectified stereo image, its corresponding pixel on the same row of the other image, producing a disparity map from which depth follows.
- Disparity
The horizontal shift, in pixels, between the images of the same scene point in a rectified stereo pair; it is inversely proportional to depth.
- Fundamental Matrix
The rank-2 3 × 3 matrix F that encodes the epipolar geometry of two uncalibrated views through the constraint x'ᵀFx = 0, and how it is estimated from point correspondences.
- Image Warping
Transforming the geometry of an image by a coordinate mapping, usually computed by sampling the input image at the inverse-mapped position of every output pixel.
- Lens Distortion
The systematic deviation of a real lens from the pinhole model's straight-line projection, which bends straight lines in the image and is modeled, estimated, and removed as part of camera calibration.
References
- Fusiello, A., Trucco, E. & Verri, A. (2000). A Compact Algorithm for Rectification of Stereo Pairs. Machine Vision and Applications, 12(1), 16–22.
- Hartley, R. I. (1999). Theory and Practice of Projective Rectification. International Journal of Computer Vision, 35(2), 115–127.
- Loop, C. & Zhang, Z. (1999). Computing Rectifying Homographies for Stereo Vision. Proceedings of the IEEE Computer Society Conference on Computer Vision and Pattern Recognition (CVPR), Vol. 1, 125–131.
- Pollefeys, M., Koch, R. & Van Gool, L. (1999). A Simple and Efficient Rectification Method for General Motion. Proceedings of the Seventh IEEE International Conference on Computer Vision (ICCV), Vol. 1, 496–501.
- OpenCV. Camera Calibration and 3D Reconstruction (calib3d module): stereoRectify, stereoRectifyUncalibrated, and initUndistortRectifyMap. OpenCV 4.x documentation.