OpenCV:使用cv::triangulatePoints()进行立体相机跟踪的问题
从你的代码和问题描述来看,三角化后的3D点无法构成平面,大概率是几个关键步骤的处理有误,我来逐个拆解可能的问题:
1. 角点精度不足:缺少亚像素细化
findChessboardCorners 只能给出角点的粗略像素坐标,精度不足以支撑高精度的立体三角化。如果没有做亚像素细化,图像点的误差会被放大到3D空间,导致点偏离平面。
修正方案:在检测到角点后,调用 cornerSubPix 细化坐标:
if (found1) { TermCriteria criteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 30, 0.001); cornerSubPix(frame1, foundCorners1, Size(11, 11), Size(-1, -1), criteria); } if (found2) { TermCriteria criteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 30, 0.001); cornerSubPix(frame2, foundCorners2, Size(11, 11), Size(-1, -1), criteria); }
2. undistortPoints 参数错误
你当前调用 undistortPoints 时,把投影矩阵 P1/P2 作为了 newCameraMatrix 参数,这是错误的——newCameraMatrix 需要是3x3的相机内参矩阵,而 P1/P2 是3x4的投影矩阵(包含旋转、平移和内参)。
正确的做法是从 P1/P2 中提取校正后的内参矩阵(前3列),作为 newCameraMatrix 传入:
// 从投影矩阵中提取校正后的相机内参 Mat K1_rect = P1.colRange(0, 3); Mat K2_rect = P2.colRange(0, 3); // 正确的畸变校正与立体校正 undistortPoints(foundCorners1, ufoundCorners1, cameraMat1, distCoeff1, R1, K1_rect); undistortPoints(foundCorners2, ufoundCorners2, cameraMat2, distCoeff2, R2, K2_rect);
这样得到的 ufoundCorners1/ufoundCorners2 才是与投影矩阵 P1/P2 匹配的校正后像素坐标。
3. 齐次坐标转换的维度错误
triangulatePoints 的输出是 4xN 的矩阵(每一列是一个3D点的齐次坐标),而 convertPointsFromHomogeneous 要求输入是 Nx4 的矩阵(每一行是一个点)。你当前的 reshape(4,1) 虽然能工作,但容易出错,更清晰的方式是先转置矩阵:
// 转置齐次坐标矩阵,从4xN变为Nx4 Mat homopnts3D_t = homopnts3D.t(); // 转换为欧几里得坐标 convertPointsFromHomogeneous(homopnts3D_t, pnts3D);
4. 额外检查点:MATLAB与OpenCV的标定参数一致性
需要确认MATLAB输出的标定参数与OpenCV的定义是否匹配:
- MATLAB的旋转矩阵
R是相机2相对于相机1的旋转吗?OpenCV的stereoRectify要求R是相机2相对于相机1的旋转矩阵(即相机1到相机2的旋转)。如果MATLAB的R是反向的,需要用R.t()转置。 - MATLAB的平移向量
T单位是否与OpenCV一致?比如都是毫米,避免单位不统一导致的缩放误差。
修正后的完整核心代码片段
int main(int argc, char** argv){ // 读取图像(假设file1、file2已定义) frame1 = imread(file1); frame2 = imread(file2); // 检测棋盘格角点 bool found1 = findChessboardCorners(frame1, chessSize, foundCorners1); bool found2 = findChessboardCorners(frame2, chessSize, foundCorners2); // 亚像素细化角点 if (found1) { TermCriteria criteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 30, 0.001); cornerSubPix(frame1, foundCorners1, Size(11, 11), Size(-1, -1), criteria); } if (found2) { TermCriteria criteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 30, 0.001); cornerSubPix(frame2, foundCorners2, Size(11, 11), Size(-1, -1), criteria); } // 立体校正,生成投影矩阵 stereoRectify(cameraMat1, distCoeff1, cameraMat2, distCoeff2, imSize, R, T, R1, R2, P1, P2, Q); // 提取校正后的相机内参 Mat K1_rect = P1.colRange(0, 3); Mat K2_rect = P2.colRange(0, 3); // 畸变校正与立体校正点 undistortPoints(foundCorners1, ufoundCorners1, cameraMat1, distCoeff1, R1, K1_rect); undistortPoints(foundCorners2, ufoundCorners2, cameraMat2, distCoeff2, R2, K2_rect); // 立体三角化 triangulatePoints(P1, P2, ufoundCorners1, ufoundCorners2, homopnts3D); // 转换为欧几里得坐标 Mat homopnts3D_t = homopnts3D.t(); convertPointsFromHomogeneous(homopnts3D_t, pnts3D); }
按照以上步骤修改后,三角化得到的3D点应该能更接近平面。如果还有问题,可以检查标定参数的正确性,或者尝试用立体校正后的图像重新检测角点(用 initUndistortRectifyMap + remap 处理图像后再检测),进一步排除误差来源。
内容的提问来源于stack exchange,提问作者rman

