立体校正(Stereo Rectification)的原理
将两个相机的图像平面重投影到一个完全共面且行对齐的公共平面上
第一步:消除畸变 (Undistortion)
在进行真正的立体校正之前,必须首先消除镜头畸变的影响。这是一个独立的预处理步骤。
-
原理: 根据标定得到的畸变系数(k1,k2,p1,p2,k3,...k_1, k_2, p_1, p_2, k_3, ...k1,k2,p1,p2,k3,...),使用畸变模型将图像中的每个像素点 (u,v)(u, v)(u,v) 反向映射到其本应该在的无畸变图像上的位置。
-
操作: 对左右图像分别调用 OpenCV 的 cv::undistort() 函数,得到无畸变的图像 ULU_LUL 和 URU_RUR。后续所有步骤都基于无畸变图像进行。
第二步:极线几何与校正目标
-
校正的核心思想源于对极几何(Epipolar Geometry)。
-
1.对极几何回顾:
-
极平面 (Epipolar Plane): 由空间点 PPP 和两个相机光心 OLO_LOL, ORO_ROR 构成的平面。
-
极线 (Epipolar Line): 极平面与相机图像平面的交线。左图像上的点 pLp_LpL 的对应点 pRp_RpR 必然位于右图像的某条极线 lRl_RlR 上。
-
极点 (Epipole): 相机光心 OLO_LOL(或 ORO_ROR)在另一相机图像上的投影点 eRe_ReR(或 eLe_LeL)。它是所有极线的交汇点。
-
-
2.校正的目标:
在未校正的图像中,极线是倾斜的。立体校正的目标是通过透视变换(Homography),将两个相机的图像平面旋转到一个新的公共平面上,使得:-
共面 (Coplanar): 两个图像平面完全平行于基线 OLORO_LO_ROLOR。
-
行对齐 (Row-aligned): 两个图像平面的所有对应极线都位于同一水平线上。
-
效果: 极点被投影到无穷远处 e′=[1,0,0]Te' = [1, 0, 0]^Te′=[1,0,0]T,这意味着极线变成了水平线。对应点具有相同的纵坐标(v坐标),搜索只需在同一行进行。
-
第三步:推导校正变换(Bouguet’s Algorithm)
最常用的方法是 Bouguet算法。它的核心思想是将旋转矩阵 RRR(从右相机到左相机)平分给两个相机,使它们共同旋转到一个朝向一致的姿态。
-
输入参数:
-
左相机内参:KLK_LKL
-
右相机内参:KRK_RKR
-
右相机相对于左相机的旋转和平移:RRR, TTT
-
(假设图像已去畸变)
-
-
计算每个相机所需的旋转矩阵:
a. 计算左相机的校正旋转 RrectLR_{rect}^LRrectL:
目标是让新的图像平面平行于基线。我们通过构造一组新的标准正交基来实现:-
新X轴方向: 直接取归一化的基线方向(平移向量方向)。这是新坐标系的“视线”方向。
r1=T∣T∣r_1 = \frac{T}{|T|}r1=∣T∣T,其中 T=[Tx,Ty,Tz]TT = [T_x, T_y, T_z]^TT=[Tx,Ty,Tz]T -
新Y轴方向: 需要与原左相机光轴方向( approx. [0,0,1]T[0, 0, 1]^T[0,0,1]T)垂直,同时又要在新平面内。我们通过叉乘来构造。
- 首先,取原左相机光轴方向(ZoldZ_{\text{old}}Zold)与新X轴 r1r_1r1 叉乘,得到一个临时向量:[−Tz,0,Tx]T[-T_z, 0, T_x]^T[−Tz,0,Tx]T(近似)。
然后,将其与新X轴 r1r_1r1 叉乘,得到与新X轴垂直且与原光轴接近的Y轴方向。
r2=[−TyTx, Tz2+Tx2, −TyTz]T∣...∣r_2 = \frac{ [ -T_y T_x, \ T_z^2 + T_x^2, \ -T_y T_z ]^T }{| ... |}r2=∣...∣[−TyTx, Tz2+Tx2, −TyTz]T (一种数值稳定的计算方式) -
新Z轴方向: 为了构成右手坐标系,Z轴由X轴和Y轴叉乘得到。
r3=r1×r2r_3 = r_1 \times r_2r3=r1×r2 -
左相机的校正旋转矩阵: RrectL=[r1T;r2T;r3T]R_{\text{rect}}^L = [r_1^T; r_2^T; r_3^T]RrectL=[r1T;r2T;r3T]。这个矩阵的作用是将左相机的坐标系旋转到新的公共坐标系。
b. 计算右相机的校正旋转 RrectRR_{rect}^RRrectR:
Bouguet算法的巧妙之处在于,让右相机也经历一个相对于左相机的“平分”旋转。
RrectR=RrectL⋅RTR_{\text{rect}}^R = R_{\text{rect}}^L \cdot R^TRrectR=RrectL⋅RT
这里 RTR^TRT 是 RRR 的逆(因为 RRR 是从左到右的旋转,RTR^TRT 则是从右到左的旋转)。这样,右相机先通过 RTR^TRT “回到”左相机的原始姿态,然后再和左相机一起旋转到新的公共姿态 RrectLR_{\text{rect}}^LRrectL。 -
-
计算投影矩阵 (Projection Matrices)
校正后,我们希望两个相机拥有相同的虚拟内参 KnewK_{\text{new}}Knew。通常 KnewK_{\text{new}}Knew 可以取 KLK_LKL 或 KRK_RKR,或者取它们的平均值,或者根据校正后的有效图像区域重新计算。-
左相机的投影矩阵: PL=Knew⋅[I∣0]P_L = K_{\text{new}} \cdot [I | 0]PL=Knew⋅[I∣0]
-
右相机的投影矩阵: PR=Knew⋅[RrectR(RrectL)T∣Tnew]P_R = K_{\text{new}} \cdot [R_{\text{rect}}^R (R_{\text{rect}}^L)^T | T_{\text{new}}]PR=Knew⋅[RrectR(RrectL)T∣Tnew]
- 由于两个相机已经共面且行对齐,平移向量 TnewT_{\text{new}}Tnew 只剩下X分量 [∣T∣,0,0]T[ |T|, 0, 0 ]^T[∣T∣,0,0]T。
-
第四步:计算重映射映射表 (Remap Maps)
上面的变换是整体性的。对于图像上的每一个像素,我们需要知道它校正后应该去哪个位置。
-
初始化映射表: 创建两个数组 map_x 和 map_y,大小与原图相同。
-
反向映射: 对于校正后图像上的每一个像素 (urect,vrect)(u_{\text{rect}}, v_{\text{rect}})(urect,vrect):
-
将其投影到归一化平面(除以虚拟内参 KnewK_{\text{new}}Knew): pnorm=Knew−1[urect,vrect,1]Tp_{\text{norm}} = K_{\text{new}}^{-1} [u_{\text{rect}}, v_{\text{rect}}, 1]^Tpnorm=Knew−1[urect,vrect,1]T
-
应用校正旋转的逆变换,将其映射回原始无畸变图像的相机坐标系下:
poriginal=(RrectL)−1⋅pnormp_{\text{original}} = (R_{\text{rect}}^L)^{-1} \cdot p_{\text{norm}}poriginal=(RrectL)−1⋅pnorm (对于左图) -
将 poriginalp_{\text{original}}poriginal 重新投影到原始图像像素坐标: [uorig,vorig,1]T≈KL⋅poriginal[u_{\text{orig}}, v_{\text{orig}}, 1]^T \approx K_L \cdot p_{\text{original}}[uorig,vorig,1]T≈KL⋅poriginal
-
将 map_x(u_rect, v_rect) = u_orig, map_y(u_rect, v_rect) = v_orig
-
-
执行重映射: 对原始无畸变图像调用 cv::remap(src, dst, map_x, map_y, …)。这个函数会根据 map_x 和 map_y 的指示,为校正后图像的每个位置 (urect,vrect)(u_{\text{rect}}, v_{\text{rect}})(urect,vrect) 去寻找原始图像中对应位置 (uorig,vorig)(u_{\text{orig}}, v_{\text{orig}})(uorig,vorig) 的像素值并进行插值。
总结与意义
-
数学本质: 立体校正是通过施加一个旋转变换 RrectR_{\text{rect}}Rrect 来改变相机的“视角”,使得两个图像平面共面且行对齐。
-
Bouguet算法的核心: 将两个相机之间的相对旋转 RRR “平分”,使它们共同旋转到一个新的姿态,该姿态的X轴与基线 TTT 方向一致。
-
最终输出: 两幅校正后的图像。在这两幅图像中,任何一对对应点都拥有完全相同的纵坐标(v坐标)。这使得立体匹配算法只需在同一行进行一维搜索,极大提高了匹配的效率和可靠性。
在实际应用中(如OpenCV),我们使用 cv::stereoRectify() 函数来计算所有这些变换矩阵(RrectLR_{\text{rect}}^LRrectL, RrectRR_{\text{rect}}^RRrectR, PLP_LPL, PRP_RPR, …),然后使用 cv::initUndistortRectifyMap() 来计算重映射映射表 map_x, map_y,最后使用 cv::remap() 得到校正后的图像。理解其背后的原理对于调试和优化立体视觉系统至关重要。