1. 三角测量的核心共线方程与超定问题当你用双目相机拍摄同一个物体时物体点、相机光心和成像点三者其实在同一条直线上——这就是共线方程的物理意义。想象一下你左右眼分别看到的同一个物体就像两条从眼睛出发的射线理论上这两条线应该在物体位置相交。但由于噪声和误差它们往往不会完美相交这时候就需要数学工具来找到最佳交点。OpenCV中的triangulatePoints函数本质上就是在解决这个问题。具体来说它会将共线条件转化为一个形如AX0的矩阵方程。这里A是一个4x4的系数矩阵X是我们要求解的三维齐次坐标。为什么是超定问题呢因为从两个相机视角可以得到4个方程每个视角x,y两个坐标而未知数只有3个X,Y,Z坐标方程数量超过了未知数个数。在实际操作中我经常发现初学者容易卡在系数矩阵A的构造上。其实A矩阵的每一行都对应着一个共线约束条件。比如第一行可能对应左相机x坐标的约束第二行对应左相机y坐标约束以此类推。通过这种方式我们就把几何问题转化成了线性代数问题。2. 从投影矩阵到系数矩阵构造A的详细步骤让我们拆解一下A矩阵的构造过程。假设我们有两个相机它们的投影矩阵分别是P1和P2。对于左相机的一个特征点(u1,v1)我们可以写出两个方程u1 (P1[0]·X)/(P1[2]·X) v1 (P1[1]·X)/(P1[2]·X)这里X是三维点的齐次坐标P1[i]表示P1矩阵的第i行。这两个方程可以重写为(u1*P1[2] - P1[0])·X 0 (v1*P1[2] - P1[1])·X 0这就是A矩阵中两行的来源同理右相机的特征点也会贡献两行。在OpenCV源码中这部分对应注释注1处的代码matrA(j*20, k) x * cvmGet(projMatrs[j],2,k) - cvmGet(projMatrs[j],0,k) matrA(j*21, k) y * cvmGet(projMatrs[j],2,k) - cvmGet(projMatrs[j],1,k)我曾经在一个项目中犯过错误把投影矩阵的行列顺序搞混了结果重建出的3D点全是错的。调试后发现是因为OpenCV的投影矩阵存储顺序是行优先而我误以为是列优先。这个小细节让我花了整整一天时间排查3. SVD分解的几何解释与实现细节得到AX0的方程后OpenCV使用**奇异值分解(SVD)**来求解。为什么选择SVD因为这是一个超定齐次方程组传统求逆方法不适用。SVD可以找到使||AX||最小的单位向量X这正是我们需要的解。从几何上看SVD在寻找矩阵A的零空间——也就是使得AX0的那些X方向。具体来说A的SVD分解为UΣV^T解就是V的最后一列。这是因为Σ是对角矩阵最后一个奇异值最小理想情况下为零对应的V列就是最优解。OpenCV源码中这部分非常简洁cv::SVD::compute(matrA, matrW, matrU, matrV); cvmSet(points4D,0,i,matrV(3,0)); # X cvmSet(points4D,1,i,matrV(3,1)); # Y cvmSet(points4D,2,i,matrV(3,2)); # Z cvmSet(points4D,3,i,matrV(3,3)); # W这里有个关键点SVD解出来的是齐次坐标需要将XYZ除以W分量得到真实3D坐标。我遇到过W接近零的情况这通常意味着匹配点有问题或者相机位姿估计不准确。4. 实战中的陷阱与优化技巧在实际使用triangulatePoints时有几个坑需要注意输入点要先去除畸变原始图像点必须先用undistortPoints处理否则共线方程不成立。我曾经忘记这一步结果重建误差大得离谱。特征匹配质量至关重要错误的匹配会导致完全错误的重建。建议先用RANSAC过滤误匹配。数值稳定性问题当物体距离相机很远时齐次坐标的W分量可能非常小导致数值不稳定。这时可以考虑对场景进行适当的缩放。多视角融合虽然本文讨论双视角但实际项目中可以融合更多视角。多视角情况下A矩阵的行数会增加每多一个视角多两行SVD解会更鲁棒。一个实用的优化技巧是在构造A矩阵前对所有二维点坐标进行归一化减去均值除以标准差。这能显著提高SVD的数值稳定性特别是当像素坐标数值较大时。