4. 对极几何与极线约束:从原理到OpenCV实战
说到立体匹配,有个概念绕不开——对极几何。
我记得刚入行那会儿,第一次看到对极几何的图,满脑子都是「这线怎么歪歪扭扭的?」后来真正做双目测距项目时才发现,不理解对极几何,你连匹配搜索范围都定不下来。说白了,它就是立体匹配的「交通规则」。
4.1 对极几何基础
先看一个最简单的场景:两台相机拍同一个物体。
假设左相机看到空间点 P 成像在 pl,右相机看到同一个点成像在 pr。那么问题来了——pl 和 pr 之间有什么关系?
对极几何给出了答案:
- 基线:两个相机光心的连线
- 对极点:基线与成像平面的交点
- 对极平面:光心 + 空间点 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)
极线约束虽然好,但极线是斜的。你想想看,沿着一条斜线做匹配,代码写起来多麻烦?
极线校正就是把这个斜线「掰直」——让左右图像的极线变成水平线,而且对应行对齐。
校正后的好处:
- 匹配搜索从「沿斜线」变成「沿同一行」
- 视差直接等于列坐标之差
- 代码实现简单,计算效率高
校正的核心步骤:
- 计算本质矩阵 E(或基础矩阵 F)
- 对 E 做 SVD 分解,得到 R 和 t
- 构造两个新的旋转矩阵 Rl 和 Rr
- 重映射左右图像
我曾经踩过一个坑:校正后图像边缘出现大量黑色区域。后来发现是重映射时没有处理好边界。建议校正后裁剪掉无效区域,或者用缩放方式保持图像完整。
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 本章知识体系
下面这张图把对极几何的核心逻辑串起来了:
从对极几何基础出发,到本质矩阵和基础矩阵,再到极线校正,最后用 OpenCV 落地——这就是完整的知识链路。每一步都有它的物理意义和数学支撑,缺一不可。
记住一句话:极线约束是立体匹配的「骨架」,极线校正是让这个骨架「站直」的关键一步。没有校正,后面的匹配算法再强也跑不快。
公众号:蓝海资料掘金营,微信deep3321