Goal
Given a calibrated stereo rig — the two known perspective projection matrices (PPMs) , of the original cameras, each factorizable as — compute a pair of rectifying PPMs , describing virtual cameras that keep the original optical centres but share a common orientation and common intrinsics, together with the per-image homographies that warp the original images into the rectified ones. Because both input PPMs are metric (intrinsics and extrinsics known from prior calibration, not recovered from point correspondences), the result is a Euclidean rectification: the shared new orientation is built directly from the baseline and a viewing-direction reference, disparity between rectified images inverts metrically as , and 3-D points can be triangulated from the rectified images with the new PPMs. This differs from uncalibrated (weakly calibrated) rectification methods, which start from a fundamental matrix or point correspondences alone and can only produce a projective rectification, undetermined up to the residual freedom left by .
Algorithm
Symbols: — an old (input) PPM, factored into intrinsic matrix and extrinsic rotation/translation ; — the optical centre recovered from an old PPM; — the row vectors of the new shared rotation matrix; — the old left camera's -axis (its viewing direction), used as an arbitrary reference vector; — the leading block of an old / new PPM; — the per-image rectifying homography.
The new PPMs keep the old optical centres and change only orientation and intrinsics:
The shared new orientation is assembled row-by-row as an orthonormal frame from three geometric constraints:
is the new -axis, pointed along the baseline; this drives both epipoles to infinity and makes epipolar lines parallel and horizontal. is the new -axis, orthogonal to the baseline and to ; the paper fixes to the old left camera's -axis, which keeps the rectified cameras looking roughly in the original left-camera viewing direction. completes the right-handed frame. The shared intrinsic matrix can be chosen arbitrarily as long as it is identical for both new PPMs — equal intrinsics for both cameras is what makes conjugate points share the same row. A common choice is the average of the two old intrinsic matrices with the skew term zeroed.
Once the new PPMs are fixed, rectifying an image is a per-image projective warp derived from the constraint that rectification does not move the optical centre:
Applying to an original image produces the rectified image; because rectified integer pixel positions generally map to non-integer positions on the original image plane, gray levels are resampled by bilinear interpolation.
Procedure
- Factor each old PPM into intrinsics and extrinsics, , and recover its optical centre .
- Compute the new -axis along the baseline.
- Compute the new -axis , with the old left camera's -axis.
- Compute the new -axis and assemble .
- Choose a shared new intrinsic matrix for both rectified cameras.
- Form the new PPMs with unchanged optical centres, and .
- Compute the rectifying homography for each image, , from the leading blocks of the new and old PPMs.
- Warp each original image with its , resampling non-integer source coordinates by bilinear interpolation.
Step 3 degenerates when the optical axis is parallel to the baseline (pure forward motion): becomes parallel to , the cross product vanishes, and the new -axis is undefined.
Implementation
The full construction in Rust, corresponding line-by-line to the procedure above:
type Mat3 = [[f64; 3]; 3];
type Vec3 = [f64; 3];
fn sub(a: Vec3, b: Vec3) -> Vec3 { [a[0]-b[0], a[1]-b[1], a[2]-b[2]] }
fn cross(a: Vec3, b: Vec3) -> Vec3 {
[a[1]*b[2]-a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0]]
}
fn normalize(a: Vec3) -> Vec3 {
let n = (a[0]*a[0] + a[1]*a[1] + a[2]*a[2]).sqrt();
[a[0]/n, a[1]/n, a[2]/n]
}
/// Steps 2-4: shared new rotation from the baseline and the old left camera's Z-axis.
fn new_rotation(c1: Vec3, c2: Vec3, k: Vec3) -> Mat3 {
let r1 = normalize(sub(c1, c2)); // step 2: new X-axis along the baseline
let r2 = normalize(cross(k, r1)); // step 3: new Y-axis, k = old left Z-axis
let r3 = cross(r1, r2); // step 4: new Z-axis completes the frame
[r1, r2, r3]
}
fn mat3_mul(a: Mat3, b: Mat3) -> Mat3 {
let mut m = [[0.0; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
m[i][j] += a[i][k] * b[k][j];
}
}
}
m
}
/// Step 6: new PPM's leading 3x3 block, Q_n = A * R.
/// Step 7: rectifying homography T = Q_n * Q_o^-1 (Q_o^-1 supplied by caller).
fn rectifying_homography(a: Mat3, r: Mat3, q_old_inv: Mat3) -> Mat3 {
let q_new = mat3_mul(a, r);
mat3_mul(q_new, q_old_inv)
}
PPM factorization (step 1), matrix inversion for , and the shared-intrinsics choice of step 5 are omitted — the first two are standard linear-algebra routines, the last an implementation choice (the reference MATLAB code averages the two old intrinsic matrices and zeroes the skew term). Step 8's bilinear-interpolation warp is a standard image-resampling routine, orthogonal to the geometric construction shown here.
Remarks
- The comparison between calibrated and uncalibrated rectification methods, including the full decision table, is hosted on the stereo rectification survey.
- A related calibrated variant, used in the widely-adopted Camera Calibration Toolbox / OpenCV rectification routines, splits the rectifying rotation half-and-half between the two cameras instead of anchoring the shared orientation to one camera's axis: each camera rotates by half the angle needed to align the pair, which halves the maximum per-camera reprojection distortion relative to rotating only one camera fully onto the other. That toolbox lineage also exposes a scaling parameter trading off between a cropped rectified field of view (no black borders, some source pixels lost) and the full original field of view (all source pixels kept, black borders introduced) — a valid-ROI concern this construction does not address.
- The new shared intrinsic matrix is not derived from the physical cameras; it is an implementation choice constrained only by being identical for both rectified views.
- The construction fails outright when the optical axis is parallel to the baseline (pure forward motion), since the cross product defining the new -axis degenerates to zero; the same construction is numerically ill-conditioned as the optical axis approaches that alignment.
- Rectification here is a re-orientation of the cameras about their fixed optical centres — it never translates them, and the warp from old to rectified image is a plain projective resampling, not a re-triangulation.
- Reconstructing 3-D points by triangulation directly from the rectified images, using the new PPMs, introduces no appreciable accuracy loss relative to reconstructing from the originals — reported qualitatively in the source paper, without a specific numeric figure.
References
- A. Fusiello, E. Trucco, A. Verri. A Compact Algorithm for Rectification of Stereo Pairs. Machine Vision and Applications 12(1):16–22, 2000. DOI: 10.1007/s001380050120
- R. I. Hartley. Theory and Practice of Projective Rectification. International Journal of Computer Vision 35(2):1–16, 1999.
- C. Loop, Z. Zhang. Computing Rectifying Homographies for Stereo Vision. CVPR, 1999, pp. I:125–131.
- M. Pollefeys, R. Koch, L. Van Gool. A Simple and Efficient Rectification Method for General Motion. ICCV, 1999, pp. 496–501.