OpenCV立体相机三角化求特征世界坐标结果异常,求技术指导
问题描述
使用两台相机,以地面贴纸定义世界坐标系:黄色圆形贴纸为世界原点,绿色圆形贴纸位于(500mm, 0, 0),红色圆形贴纸位于(0, 500mm, 0),小黄贴纸位于(151mm, 194mm, 0)。目标是获取小红贴纸的毫米级世界坐标,该贴纸在相机1图像中的像素坐标为(1175, 440),在相机2图像中的像素坐标为(1401, 298)。
采用的流程是依次调用cv::calibrateCamera分别校准两台相机,再调用cv::stereoCalibrate、cv::stereoRectify,最后调用cv::triangulatePoints求解,但得到的齐次坐标为[-0.60382962, -0.0076272688, 0.79707688, 0.00036873418],转换为非齐次坐标[-1637.574, -20.685, 2161.657]后结果明显不合理。
以下是使用的代码:
// 四个真实世界校准点,单位为毫米 // 黄色圆形为原点 // 绿色圆形在x轴上,距离原点500mm // 红色圆形在y轴上,距离原点500mm // 第四个校准点是小黄贴纸,位于(151mm, 194mm, 0mm) // z轴向上,垂直于地面 std::vector<cv::Point3f> _ObjectPointsWorldMM; _ObjectPointsWorldMM.push_back(cv::Point3f(0.0f, 0.0f, 0.0f)); _ObjectPointsWorldMM.push_back(cv::Point3f(500.0f, 0.0f, 0.0f)); _ObjectPointsWorldMM.push_back(cv::Point3f(0.0f, 500.0f, 0.0f)); _ObjectPointsWorldMM.push_back(cv::Point3f(151.0f, 194.0f, 0.0f)); std::vector<std::vector<cv::Point3f>> ObjectPointsWorldMM; ObjectPointsWorldMM.push_back(_ObjectPointsWorldMM); // // 相机1 calibrateCamera() // // 获取相机1图像 cv::Mat mCamera1Image = cv::imread(std::string("Camera1.jpg"), CV_LOAD_IMAGE_COLOR); // 相机1图像中对应四个校准点的像素位置 std::vector<cv::Point2f> _Camera1ImagePointsPx; _Camera1ImagePointsPx.push_back(cv::Point2f(791.0f, 220.0f)); // 对应黄色原点贴纸 _Camera1ImagePointsPx.push_back(cv::Point2f(864.0f, 643.0f)); // 对应绿色x=500mm贴纸 _Camera1ImagePointsPx.push_back(cv::Point2f(1277.0f, 113.0f)); // 对应红色y=500mm贴纸 _Camera1ImagePointsPx.push_back(cv::Point2f(1010.0f, 287.0f)); // 对应第四个校准点的小黄贴纸(见上文) std::vector<std::vector<cv::Point2f>> Camera1ImagePointsPx; Camera1ImagePointsPx.push_back(_Camera1ImagePointsPx); // 校准相机1 cv::Mat mCamera1IntrinsicMatrix; cv::Mat mCamera1DistortionCoefficients; std::vector<cv::Mat> Camera1RotationVecs; std::vector<cv::Mat> Camera1TranslationVecs; const double dCamera1RMSReProjectionError = cv::calibrateCamera( ObjectPointsWorldMM, // 输入:四个校准点的世界毫米坐标 Camera1ImagePointsPx, // 输入:相机1图像中对应校准点的像素位置 mCamera1Image.size(), // 输入:相机1校准图像的尺寸 mCamera1IntrinsicMatrix, mCamera1DistortionCoefficients, // 输出:相机内参矩阵和畸变系数 Camera1RotationVecs, Camera1TranslationVecs // 输出:相机旋转和平移向量 ); // // 相机2 calibrateCamera() // // 获取相机2图像 cv::Mat mCamera2Image = cv::imread(std::string("Camera2.jpg"), CV_LOAD_IMAGE_COLOR); // 验证假设 assert((mCamera1Image.size() == mCamera2Image.size())); // 相机2图像中对应四个校准点的像素位置 std::vector<cv::Point2f> _Camera2ImagePointsPx; _Camera2ImagePointsPx.push_back(cv::Point2f(899.0f, 439.0f)); // 对应黄色原点贴纸 _Camera2ImagePointsPx.push_back(cv::Point2f(1472.0f, 608.0f)); // 对应绿色x=500mm贴纸 _Camera2ImagePointsPx.push_back(cv::Point2f(1101.0f, 74.0f)); // 对应红色y=500mm贴纸 _Camera2ImagePointsPx.push_back(cv::Point2f(1136.0f, 322.0f)); // 对应第四个校准点的小黄贴纸(见上文) std::vector<std::vector<cv::Point2f>> Camera2ImagePointsPx; Camera2ImagePointsPx.push_back(_Camera2ImagePointsPx); // 校准相机2 cv::Mat mCamera2IntrinsicMatrix; cv::Mat mCamera2DistortionCoefficients; std::vector<cv::Mat> Camera2RotationVecs; std::vector<cv::Mat> Camera2TranslationVecs; const double dCamera2RMSReProjectionError = cv::calibrateCamera( ObjectPointsWorldMM, // 输入:四个校准点的世界毫米坐标 Camera2ImagePointsPx, // 输入:相机2图像中对应校准点的像素位置 mCamera2Image.size(), // 输入:相机2校准图像的尺寸 mCamera2IntrinsicMatrix, mCamera2DistortionCoefficients, // 输出:相机内参矩阵和畸变系数 Camera2RotationVecs, Camera2TranslationVecs // 输出:相机旋转和平移向量 ); // // stereoCalibrate() // // 校准立体相机系统 cv::Mat InterCameraRotationMatrix, InterCameraTranslationMatrix; cv::Mat InterCameraEssentialMatrix, InterCameraFundamentalMatrix; const double dStereoCalReProjectionError = cv::stereoCalibrate( ObjectPointsWorldMM, // 输入:四个校准点的世界毫米坐标 Camera1ImagePointsPx, // 输入:相机1图像中对应校准点的像素位置 Camera2ImagePointsPx, // 输入:相机2图像中对应校准点的像素位置 mCamera1IntrinsicMatrix, mCamera1DistortionCoefficients, // 输入:相机1的内参矩阵和畸变系数 mCamera2IntrinsicMatrix, mCamera2DistortionCoefficients, // 输入:相机2的内参矩阵和畸变系数 mCamera1Image.size(), // 输入:每张图像的尺寸 InterCameraRotationMatrix, InterCameraTranslationMatrix, // 输出:相机间旋转和平移矩阵 InterCameraEssentialMatrix, InterCameraFundamentalMatrix // 输出:相机间本质矩阵和基础矩阵 ); // // stereoRectify() // // 计算已校准立体相机的校正变换 cv::Mat Camera1RectificationTransform, Camera2RectificationTransform; cv::Mat Camera1ProjectionMatrix, Camera2ProjectionMatrix; cv::Mat DisparityToDepthMappingMatrix; cv::stereoRectify( mCamera1IntrinsicMatrix, mCamera1DistortionCoefficients, // 输入:相机1的内参矩阵和畸变系数 mCamera2IntrinsicMatrix, mCamera2DistortionCoefficients, // 输入:相机2的内参矩阵和畸变系数 mCamera1Image.size(), // 输入:每张图像的尺寸 InterCameraRotationMatrix, InterCameraTranslationMatrix, // 输入:相机间旋转和平移矩阵 Camera1RectificationTransform, Camera2RectificationTransform, // 输出:每个相机的3x3校正变换矩阵 Camera1ProjectionMatrix, Camera2ProjectionMatrix, // 输出:每个相机的3x4投影矩阵 DisparityToDepthMappingMatrix // 输出:4x4视差转深度映射矩阵 ); // // triangulatePoints() // // 相机1图像中待查找特征的像素位置 std::vector<cv::Point2f> FeaturePointsCamera1; FeaturePointsCamera1.push_back(cv::Point2f(1175.0f, 440.0f)); // 小红贴纸的像素位置 // 相机2图像中待查找特征的像素位置 std::vector<cv::Point2f> FeaturePointsCamera2; FeaturePointsCamera2.push_back(cv::Point2f(1401.0f, 298.0f)); // 小红贴纸的像素位置 // 执行三角化以找到特征位置 cv::Mat FeatureLocationHomog; cv::triangulatePoints( Camera1ProjectionMatrix, Camera2ProjectionMatrix, // 输入:每个相机的3x4投影矩阵 FeaturePointsCamera1, FeaturePointsCamera2, // 输入:每个相机的特征像素点 FeatureLocationHomog // 输出:齐次坐标下的重建特征位置 ); // 验证假设 assert((FeatureLocationHomog.cols == static_cast<int>(FeaturePointsCamera1.size())));
问题分析与解决建议
核心问题:校准点数量严重不足
cv::calibrateCamera需要足够多的校准点(通常至少15-20个,分布在画面不同区域)才能准确求解内参和畸变系数。仅用4个点校准单相机,会导致内参矩阵、畸变系数的求解严重不稳定,后续立体校准和三角化的结果自然不可靠。
其他可能的问题点
- 像素坐标与世界点对应错误:需再次核对每个贴纸在两张图像中的像素坐标是否完全匹配,尤其是绿色/红色贴纸的位置是否搞反。
- 未校正特征点畸变:
cv::triangulatePoints输入的像素点应该是经过畸变校正后的坐标,直接使用原始像素点会引入畸变误差。 - 立体校准参数设置不当:单独校准相机后,调用
stereoCalibrate时未固定内参,可能导致已校准的内参被二次修改。
修正步骤
- 增加校准点数量:使用8x6以上的棋盘格,拍摄15-20张不同角度、不同位置的图像,重新进行单相机校准,确保重投影误差低于1像素。
- 校正特征点畸变:用
cv::undistortPoints对小红贴纸的原始像素坐标进行畸变校正,再输入到triangulatePoints中。 - 优化立体校准流程:完成单相机校准后,调用
stereoCalibrate时添加CALIB_FIX_INTRINSIC标志,固定已校准的内参。 - 验证校准结果:用已知的校准点(比如小黄贴纸)进行三角化测试,看是否能还原其世界坐标,验证流程正确性。
内容的提问来源于stack exchange,提问作者user2062604
相关产品推荐
相关产品推荐

