基于双目Webcam系统获取特征点三维世界坐标的技术咨询
获取双目摄像头特征点的三维世界坐标(OpenCV 3.4.10 C++)
咱们已经有了双目系统的全套参数,不管是左摄像头还是右摄像头的特征点,都有两种靠谱的方法来计算三维坐标,我给你一步步梳理清楚:
一、左摄像头图像特征点的三维坐标获取
方法1:直接利用已有的视差图(最简单高效)
既然你已经用StereoSGBM生成了视差图,那直接用OpenCV的reprojectImageTo3D函数就能快速得到整个图像的三维点云,然后直接提取特征点对应的坐标就行,步骤如下:
- 首先把SGBM输出的16位视差图转换成真实的视差值(SGBM的输出是放大16倍的,所以要除以16);
- 用你已经拿到的重投影矩阵Q,调用
reprojectImageTo3D生成三维点云; - 遍历左图的特征点,根据像素坐标从点云中取出对应的三维坐标,注意过滤掉无效的视差点(比如NaN值)。
对应的C++代码示例:
// 假设你已经有左图特征点集合:vector<Point2f> left_keypoints; // 视差图disparity是StereoSGBM输出的CV_16S单通道图 Mat disparity_float; disparity.convertTo(disparity_float, CV_32F, 1.0/16.0); // 转换为真实视差值 Mat Q; // 你的双目重投影矩阵(校正后得到的Q矩阵) Mat point_cloud; // true表示将无效视差对应的三维点设为NaN,方便后续过滤 reprojectImageTo3D(disparity_float, point_cloud, Q, true); // 提取左特征点对应的三维坐标 vector<Point3f> left_3d_points; for (const auto& kp : left_keypoints) { // 先检查像素坐标是否在视差图范围内 if (kp.x >= 0 && kp.x < disparity.cols && kp.y >= 0 && kp.y < disparity.rows) { Vec3f point = point_cloud.at<Vec3f>(kp.y, kp.x); // 过滤无效点 if (!isnan(point[0]) && !isnan(point[1]) && !isnan(point[2])) { left_3d_points.emplace_back(point[0], point[1], point[2]); } } }
方法2:特征匹配+三角化(精度更高,适合对匹配点有控制的场景)
如果想更精确地控制匹配过程(比如用极线约束过滤错误匹配),可以用三角化的方法:
- 对左图的特征点,在右图中找到对应的匹配点(可以用BFMatcher/FLANN匹配,结合你已有的基础矩阵F做极线约束,过滤掉不符合极线关系的错误匹配);
- 构建左右相机的投影矩阵:左相机的投影矩阵
P1 = [K | 0](K是内参),右相机的投影矩阵P2 = [K*R | K*t](R、t是右相机相对左的旋转和平移向量); - 用OpenCV的
triangulatePoints函数,输入两个投影矩阵和匹配点对,得到齐次坐标的三维点,再转换成非齐次坐标(除以最后一个元素)。
代码示例:
// 你的相机参数:内参K,右相对左的旋转矩阵R,平移向量t Mat K = ...; // 3x3内参矩阵 Mat R = ...; // 3x3旋转矩阵 Mat t = ...; // 3x1平移向量 // 构建投影矩阵 Mat P1 = (Mat_<double>(3,4) << K.at<double>(0,0), K.at<double>(0,1), K.at<double>(0,2), 0, K.at<double>(1,0), K.at<double>(1,1), K.at<double>(1,2), 0, K.at<double>(2,0), K.at<double>(2,1), K.at<double>(2,2), 0); Mat P2; hconcat(K*R, K*t, P2); // P2 = [K*R | K*t] // 假设左图特征点left_keypoints,右图匹配点right_matched_keypoints vector<Point2f> left_points = left_keypoints; vector<Point2f> right_points = right_matched_keypoints; // 转换为double类型的Mat,便于三角化计算 Mat left_mat(left_points); Mat right_mat(right_points); left_mat.convertTo(left_mat, CV_64F); right_mat.convertTo(right_mat, CV_64F); Mat points_4d; // 注意输入的点要转置(因为函数要求列向量形式) triangulatePoints(P1, P2, left_mat.t(), right_mat.t(), points_4d); // 转换为非齐次三维坐标 vector<Point3f> 3d_points; for (int i = 0; i < points_4d.cols; ++i) { double w = points_4d.at<double>(3, i); double x = points_4d.at<double>(0, i) / w; double y = points_4d.at<double>(1, i) / w; double z = points_4d.at<double>(2, i) / w; 3d_points.emplace_back(x, y, z); }
二、右摄像头图像特征点的三维坐标获取
流程和左图类似,但有几个关键的小变化:
方法1:利用视差图
因为SGBM生成的视差图通常是以左图为基准的,视差值d = x_left - x_right,所以右图特征点(x_right, y)对应的左图像素坐标是(x_right + d, y)。你需要先根据右图特征点的坐标找到对应的视差值,再去点云中提取三维坐标:
// 右图特征点集合:vector<Point2f> right_keypoints; vector<Point3f> right_3d_points; for (const auto& kp : right_keypoints) { if (kp.x >= 0 && kp.x < disparity.cols && kp.y >= 0 && kp.y < disparity.rows) { float d = disparity_float.at<float>(kp.y, kp.x); if (d > 0) { // 有效视差(视差值为正才合理) int left_x = static_cast<int>(kp.x + d); if (left_x >=0 && left_x < disparity.cols) { Vec3f point = point_cloud.at<Vec3f>(kp.y, left_x); if (!isnan(point[0]) && !isnan(point[1]) && !isnan(point[2])) { right_3d_points.emplace_back(point[0], point[1], point[2]); } } } } }
方法2:特征匹配+三角化
和左图的区别是:你需要先找到右图特征点在左图中的匹配点,然后用同样的投影矩阵P1和P2进行三角化;或者你也可以交换投影矩阵的顺序,把右相机的投影矩阵作为P1,左相机的作为P2,输入右图和左图的匹配点对,得到的三维坐标是完全一致的。
另外要注意:如果你的图像已经做了极线水平的重投影,那不管是左图还是右图的特征点,都要先通过对应的重投影矩阵变换到校正后的坐标系,再进行后续的视差查询或匹配操作。
内容的提问来源于stack exchange,提问作者Harshit Kaushik
相关产品推荐
相关产品推荐

