4. 对极几何与极线约束:从原理到OpenCV实战

说到立体匹配,有个概念绕不开——对极几何

我记得刚入行那会儿,第一次看到对极几何的图,满脑子都是「这线怎么歪歪扭扭的?」后来真正做双目测距项目时才发现,不理解对极几何,你连匹配搜索范围都定不下来。说白了,它就是立体匹配的「交通规则」。

4.1 对极几何基础

先看一个最简单的场景:两台相机拍同一个物体。

假设左相机看到空间点 P 成像在 pl,右相机看到同一个点成像在 pr。那么问题来了——plpr 之间有什么关系?

对极几何给出了答案:

  • 基线:两个相机光心的连线
  • 对极点:基线与成像平面的交点
  • 对极平面:光心 + 空间点 P 构成的平面
  • 极线:对极平面与成像平面的交线

关键结论来了:左图上的一个点,在右图上对应的匹配点一定在它的极线上

这就是极线约束——把二维搜索降为一维搜索。原来你要在整个图像上找匹配点,现在只需要沿着一条线找。效率提升不是一点半点。

我在项目中遇到过一个问题:相机标定稍微有点偏差,极线就不准了。后来我养成了一个习惯——每次做立体匹配前,先画几根极线看看,确认标定没问题再往下走。

4.2 本质矩阵与基础矩阵

极线约束怎么用数学表达?两个矩阵搞定:本质矩阵 E基础矩阵 F

4.2.1 本质矩阵 E

本质矩阵描述的是归一化坐标系下两个相机之间的几何关系。它只包含旋转 R 和平移 t 的信息,跟相机内参无关。

数学形式:

E = [t]× R

其中 [t]× 是平移向量的反对称矩阵。E 是一个 3×3 矩阵,秩为 2。

极线约束用 E 表示为:

prT E pl = 0

这里 pl 和 pr 都是归一化坐标。

4.2.2 基础矩阵 F

基础矩阵是本质矩阵的「升级版」——它把相机内参也考虑进去了。

关系很简单:

F = Kr-T E Kl-1

其中 Kl 和 Kr 分别是左右相机的内参矩阵。

极线约束用 F 表示为:

qrT F ql = 0

这里 ql 和 qr 是像素坐标。

矩阵 输入坐标 自由度 需要内参吗?
本质矩阵 E 归一化坐标 5 不需要
基础矩阵 F 像素坐标 7 不需要(可自动估计)

我个人习惯:如果相机内参已知且标定准确,直接用本质矩阵。如果内参不确定或者想快速验证,用基础矩阵更省事。

4.3 极线校正(Rectification)

极线约束虽然好,但极线是斜的。你想想看,沿着一条斜线做匹配,代码写起来多麻烦?

极线校正就是把这个斜线「掰直」——让左右图像的极线变成水平线,而且对应行对齐。

校正后的好处:

  • 匹配搜索从「沿斜线」变成「沿同一行」
  • 视差直接等于列坐标之差
  • 代码实现简单,计算效率高

校正的核心步骤:

  1. 计算本质矩阵 E(或基础矩阵 F)
  2. 对 E 做 SVD 分解,得到 R 和 t
  3. 构造两个新的旋转矩阵 Rl 和 Rr
  4. 重映射左右图像

我曾经踩过一个坑:校正后图像边缘出现大量黑色区域。后来发现是重映射时没有处理好边界。建议校正后裁剪掉无效区域,或者用缩放方式保持图像完整。

4.4 OpenCV实现

理论说完了,上代码。OpenCV 把极线校正封装得相当完善,我们直接调用就行。

4.4.1 计算基础矩阵

import cv2
import numpy as np

# 假设我们有匹配点对
pts1 = np.array([[x1, y1], [x2, y2], ...], dtype=np.float32)
pts2 = np.array([[x1', y1'], [x2', y2'], ...], dtype=np.float32)

# 计算基础矩阵
F, mask = cv2.findFundamentalMat(pts1, pts2, cv2.FM_RANSAC, ransacReprojThreshold=1.0)

# 筛选内点
inliers1 = pts1[mask.ravel() == 1]
inliers2 = pts2[mask.ravel() == 1]

4.4.2 极线校正

# 假设我们有标定好的相机内参和畸变系数
K1, dist1 = camera_matrix_left, dist_coeffs_left
K2, dist2 = camera_matrix_right, dist_coeffs_right

# 图像尺寸
h, w = img_left.shape[:2]

# 立体校正
R1, R2, P1, P2, Q, roi1, roi2 = cv2.stereoRectify(
    K1, dist1, K2, dist2, (w, h), R, T,
    alpha=0,  # 0表示裁剪到最大有效区域
    flags=cv2.CALIB_ZERO_DISPARITY
)

# 计算映射表
map1x, map1y = cv2.initUndistortRectifyMap(K1, dist1, R1, P1, (w, h), cv2.CV_32FC1)
map2x, map2y = cv2.initUndistortRectifyMap(K2, dist2, R2, P2, (w, h), cv2.CV_32FC1)

# 应用校正
rectified_left = cv2.remap(img_left, map1x, map1y, cv2.INTER_LINEAR)
rectified_right = cv2.remap(img_right, map2x, map2y, cv2.INTER_LINEAR)

4.4.3 验证校正效果

# 画水平线检查对齐情况
def draw_horizontal_lines(img, num_lines=20):
    h, w = img.shape[:2]
    step = h // num_lines
    for y in range(0, h, step):
        cv2.line(img, (0, y), (w, y), (0, 255, 0), 1)
    return img

# 左右图拼在一起检查
check_img = np.hstack([rectified_left, rectified_right])
check_img = draw_horizontal_lines(check_img)

cv2.imshow('Rectification Check', check_img)
cv2.waitKey(0)

校正后怎么判断效果好不好?我的经验是:看左右图同一行上的特征点是否在同一水平线上。比如窗户角、路灯边缘,如果左右偏差不超过1-2个像素,就算合格。

4.5 本章知识体系

下面这张图把对极几何的核心逻辑串起来了:

对极几何与极线约束知识体系 对极几何基础 本质矩阵 E 基础矩阵 F 极线约束:匹配搜索从二维降为一维 极线校正(Rectification) 极线变为水平线 对应行完全对齐 OpenCV 实现:stereoRectify + remap

从对极几何基础出发,到本质矩阵和基础矩阵,再到极线校正,最后用 OpenCV 落地——这就是完整的知识链路。每一步都有它的物理意义和数学支撑,缺一不可。

记住一句话:极线约束是立体匹配的「骨架」,极线校正是让这个骨架「站直」的关键一步。没有校正,后面的匹配算法再强也跑不快。


公众号:蓝海资料掘金营,微信deep3321