• 对极几何与三角化求3D空间坐标


    一,使用对极几何约束求R,T

    第一步:特征匹配。提取出有效的匹配点

    1. void find_feature_matches(const Mat &img_1, const Mat &img_2,
    2. std::vector &keypoints_1,
    3. std::vector &keypoints_2,
    4. std::vector &matches) {
    5. //-- 初始化
    6. Mat descriptors_1, descriptors_2;
    7. // used in OpenCV3
    8. Ptr detector = ORB::create();
    9. Ptr descriptor = ORB::create();
    10. // use this if you are in OpenCV2
    11. // Ptr detector = FeatureDetector::create ( "ORB" );
    12. // Ptr descriptor = DescriptorExtractor::create ( "ORB" );
    13. Ptr matcher = DescriptorMatcher::create("BruteForce-Hamming");
    14. //-- 第一步:检测 Oriented FAST 角点位置
    15. detector->detect(img_1, keypoints_1);
    16. detector->detect(img_2, keypoints_2);
    17. //-- 第二步:根据角点位置计算 BRIEF 描述子
    18. descriptor->compute(img_1, keypoints_1, descriptors_1);
    19. descriptor->compute(img_2, keypoints_2, descriptors_2);
    20. //-- 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
    21. vector match;
    22. // BFMatcher matcher ( NORM_HAMMING );
    23. matcher->match(descriptors_1, descriptors_2, match);
    24. //-- 第四步:匹配点对筛选
    25. double min_dist = 10000, max_dist = 0;
    26. //找出所有匹配之间的最小距离和最大距离, 即是最相似的和最不相似的两组点之间的距离
    27. for (int i = 0; i < descriptors_1.rows; i++) {
    28. double dist = match[i].distance;
    29. if (dist < min_dist) min_dist = dist;
    30. if (dist > max_dist) max_dist = dist;
    31. }
    32. printf("-- Max dist : %f \n", max_dist);
    33. printf("-- Min dist : %f \n", min_dist);
    34. //当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
    35. for (int i = 0; i < descriptors_1.rows; i++) {
    36. if (match[i].distance <= max(2 * min_dist, 30.0)) {
    37. matches.push_back(match[i]);
    38. }
    39. }
    40. }

    二、使用本质矩阵求解R,T

    第二步:根据匹配点对,依据对极几何约束原理,求相机运动的R,t

    1. void pose_estimation_2d2d(
    2. const std::vector &keypoints_1,
    3. const std::vector &keypoints_2,
    4. const std::vector &matches,
    5. Mat &R, Mat &t) {
    6. // 相机内参,TUM Freiburg2
    7. Mat K = (Mat_<double>(3, 3) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1);
    8. //-- 把匹配点转换为vector的形式
    9. vector points1;
    10. vector points2;
    11. for (int i = 0; i < (int) matches.size(); i++) {
    12. points1.push_back(keypoints_1[matches[i].queryIdx].pt);
    13. points2.push_back(keypoints_2[matches[i].trainIdx].pt);
    14. }
    15. //-- 计算本质矩阵
    16. Point2d principal_point(325.1, 249.7); //相机主点, TUM dataset标定值
    17. int focal_length = 521; //相机焦距, TUM dataset标定值
    18. Mat essential_matrix;
    19. essential_matrix = findEssentialMat(points1, points2, focal_length, principal_point);
    20. //-- 从本质矩阵中恢复旋转和平移信息.
    21. recoverPose(essential_matrix, points1, points2, R, t, focal_length, principal_point);
    22. }

    三、由R,T三角化空间坐标

    第三步:根据针孔相机模型的公式,由 R,t估计特征点的空间坐标

    1. //三角化,根据匹配点和求解到的三维点。存储在points中
    2. void triangulation(
    3. const vector &keypoint_1,
    4. const vector &keypoint_2,
    5. const std::vector &matches,
    6. const Mat &R, const Mat &t,
    7. vector &points) {
    8. Mat T1 = (Mat_<float>(3, 4) <<
    9. 1, 0, 0, 0,
    10. 0, 1, 0, 0,
    11. 0, 0, 1, 0);
    12. //根据求解到的RT构造T2矩阵
    13. Mat T2 = (Mat_<float>(3, 4) <<
    14. R.at<double>(0, 0), R.at<double>(0, 1), R.at<double>(0, 2), t.at<double>(0, 0),
    15. R.at<double>(1, 0), R.at<double>(1, 1), R.at<double>(1, 2), t.at<double>(1, 0),
    16. R.at<double>(2, 0), R.at<double>(2, 1), R.at<double>(2, 2), t.at<double>(2, 0)
    17. );
    18. //相机内参
    19. Mat K = (Mat_<double>(3, 3) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1);
    20. vector pts_1, pts_2;
    21. for (DMatch m:matches) {
    22. // 将像素坐标转换至相机坐标
    23. pts_1.push_back(pixel2cam(keypoint_1[m.queryIdx].pt, K));
    24. pts_2.push_back(pixel2cam(keypoint_2[m.trainIdx].pt, K));
    25. }
    26. Mat pts_4d;
    27. cv::triangulatePoints(T1, T2, pts_1, pts_2, pts_4d);
    28. // 转换成非齐次坐标
    29. for (int i = 0; i < pts_4d.cols; i++) {
    30. Mat x = pts_4d.col(i);
    31. x /= x.at<float>(3, 0); // 归一化
    32. Point3d p(
    33. x.at<float>(0, 0),
    34. x.at<float>(1, 0),
    35. x.at<float>(2, 0)
    36. );
    37. points.push_back(p);
    38. }
    39. }

    其中 triangulatePoints()的具体用法为

    1. triangulatePoints(T1, T2, left, right, points_final) ;
    2. Mat T1 = (Mat_<float>(3, 4) <<
    3. 1, 0, 0, 0,
    4. 0, 1, 0, 0,
    5. 0, 0, 1, 0);
    6. Mat T2 = (Mat_<float>(3, 4) <<
    7. R.at<double>(0, 0), R.at<double>(0, 1), R.at<double>(0, 2), T.at<double>(0, 0),
    8. R.at<double>(1, 0), R.at<double>(1, 1), R.at<double>(1, 2), T.at<double>(1, 0),
    9. R.at<double>(2, 0), R.at<double>(2, 1), R.at<double>(2, 2), T.at<double>(2, 0)
    10. );`
    11. triangulatePoints(T1, T2, left, right, points_final) ;
    12. 其中T2为3x4的[R|T]矩阵,left、right为相机坐标系下的归一化坐标,
    13. 因此不能直接使用提取到的像素坐标。应首先将像素坐标通过相机内参转化到相机坐标系下。

    所以通过函数pixel2cam可将像素坐标转换到归一化相机坐标系下
    归一化坐标:X=(u-u0)/fx

    1. //像素坐标到归一化平面相机坐标的转换
    2. Point2f pixel2cam(const Point2f& p, const Mat& K)
    3. {
    4. return Point2f
    5. (
    6. (p.x - K.at<double>(0, 2)) / K.at<double>(0, 0),
    7. (p.y - K.at<double>(1, 2)) / K.at<double>(1, 1)
    8. );
    9. }

    四、代码demo

    总的代码为:

    1. #include
    2. #include
    3. // #include "extra.h" // used in opencv2
    4. using namespace std;
    5. using namespace cv;
    6. void find_feature_matches(
    7. const Mat &img_1, const Mat &img_2,
    8. std::vector &keypoints_1,
    9. std::vector &keypoints_2,
    10. std::vector &matches);
    11. void pose_estimation_2d2d(
    12. const std::vector &keypoints_1,
    13. const std::vector &keypoints_2,
    14. const std::vector &matches,
    15. Mat &R, Mat &t);
    16. void triangulation(
    17. const vector &keypoint_1,
    18. const vector &keypoint_2,
    19. const std::vector &matches,
    20. const Mat &R, const Mat &t,
    21. vector &points
    22. );
    23. /// 作图用
    24. inline cv::Scalar get_color(float depth) {
    25. float up_th = 50, low_th = 10, th_range = up_th - low_th;
    26. if (depth > up_th) depth = up_th;
    27. if (depth < low_th) depth = low_th;
    28. return cv::Scalar(255 * depth / th_range, 0, 255 * (1 - depth / th_range));
    29. }
    30. // 像素坐标转相机归一化坐标
    31. Point2f pixel2cam(const Point2d &p, const Mat &K);
    32. int main(int argc, char **argv) {
    33. if (argc != 3) {
    34. cout << "usage: triangulation img1 img2" << endl;
    35. return 1;
    36. }
    37. //-- 读取图像
    38. Mat img_1 = imread(argv[1], CV_LOAD_IMAGE_COLOR);
    39. Mat img_2 = imread(argv[2], CV_LOAD_IMAGE_COLOR);
    40. vector keypoints_1, keypoints_2;
    41. vector matches;
    42. find_feature_matches(img_1, img_2, keypoints_1, keypoints_2, matches);
    43. cout << "一共找到了" << matches.size() << "组匹配点" << endl;
    44. //-- 估计两张图像间运动
    45. Mat R, t;
    46. pose_estimation_2d2d(keypoints_1, keypoints_2, matches, R, t);
    47. //-- 三角化
    48. vector points;
    49. //tr是三维点
    50. triangulation(keypoints_1, keypoints_2, matches, R, t, tr);
    51. //-- 验证三角化点与特征点的重投影关系
    52. Mat K = (Mat_<double>(3, 3) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1);
    53. Mat img1_plot = img_1.clone();
    54. Mat img2_plot = img_2.clone();
    55. for (int i = 0; i < matches.size(); i++) {
    56. // 第一个图
    57. float depth1 = points[i].z;
    58. cout << "depth: " << depth1 << endl;
    59. Point2d pt1_cam = pixel2cam(keypoints_1[matches[i].queryIdx].pt, K);
    60. cv::circle(img1_plot, keypoints_1[matches[i].queryIdx].pt, 2, get_color(depth1), 2);
    61. // 第二个图
    62. Mat pt2_trans = R * (Mat_<double>(3, 1) << points[i].x, points[i].y, points[i].z) + t;
    63. float depth2 = pt2_trans.at<double>(2, 0);
    64. cv::circle(img2_plot, keypoints_2[matches[i].trainIdx].pt, 2, get_color(depth2), 2);
    65. }
    66. cv::imshow("img 1", img1_plot);
    67. cv::imshow("img 2", img2_plot);
    68. cv::waitKey();
    69. return 0;
    70. }
    71. void find_feature_matches(const Mat &img_1, const Mat &img_2,
    72. std::vector &keypoints_1,
    73. std::vector &keypoints_2,
    74. std::vector &matches) {
    75. //-- 初始化
    76. Mat descriptors_1, descriptors_2;
    77. // used in OpenCV3
    78. Ptr detector = ORB::create();
    79. Ptr descriptor = ORB::create();
    80. // use this if you are in OpenCV2
    81. // Ptr detector = FeatureDetector::create ( "ORB" );
    82. // Ptr descriptor = DescriptorExtractor::create ( "ORB" );
    83. Ptr matcher = DescriptorMatcher::create("BruteForce-Hamming");
    84. //-- 第一步:检测 Oriented FAST 角点位置
    85. detector->detect(img_1, keypoints_1);
    86. detector->detect(img_2, keypoints_2);
    87. //-- 第二步:根据角点位置计算 BRIEF 描述子
    88. descriptor->compute(img_1, keypoints_1, descriptors_1);
    89. descriptor->compute(img_2, keypoints_2, descriptors_2);
    90. //-- 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
    91. vector match;
    92. // BFMatcher matcher ( NORM_HAMMING );
    93. matcher->match(descriptors_1, descriptors_2, match);
    94. //-- 第四步:匹配点对筛选
    95. double min_dist = 10000, max_dist = 0;
    96. //找出所有匹配之间的最小距离和最大距离, 即是最相似的和最不相似的两组点之间的距离
    97. for (int i = 0; i < descriptors_1.rows; i++) {
    98. double dist = match[i].distance;
    99. if (dist < min_dist) min_dist = dist;
    100. if (dist > max_dist) max_dist = dist;
    101. }
    102. printf("-- Max dist : %f \n", max_dist);
    103. printf("-- Min dist : %f \n", min_dist);
    104. //当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
    105. for (int i = 0; i < descriptors_1.rows; i++) {
    106. if (match[i].distance <= max(2 * min_dist, 30.0)) {
    107. matches.push_back(match[i]);
    108. }
    109. }
    110. }
    111. void pose_estimation_2d2d(
    112. const std::vector &keypoints_1,
    113. const std::vector &keypoints_2,
    114. const std::vector &matches,
    115. Mat &R, Mat &t) {
    116. // 相机内参,TUM Freiburg2
    117. Mat K = (Mat_<double>(3, 3) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1);
    118. //-- 把匹配点转换为vector的形式
    119. vector points1;
    120. vector points2;
    121. for (int i = 0; i < (int) matches.size(); i++) {
    122. points1.push_back(keypoints_1[matches[i].queryIdx].pt);
    123. points2.push_back(keypoints_2[matches[i].trainIdx].pt);
    124. }
    125. //-- 计算本质矩阵
    126. Point2d principal_point(325.1, 249.7); //相机主点, TUM dataset标定值
    127. int focal_length = 521; //相机焦距, TUM dataset标定值
    128. Mat essential_matrix;
    129. essential_matrix = findEssentialMat(points1, points2, focal_length, principal_point);
    130. //-- 从本质矩阵中恢复旋转和平移信息.
    131. recoverPose(essential_matrix, points1, points2, R, t, focal_length, principal_point);
    132. }
    133. //三角化,根据匹配点和求解到的三维点。存储在points中
    134. void triangulation(
    135. const vector &keypoint_1,
    136. const vector &keypoint_2,
    137. const std::vector &matches,
    138. const Mat &R, const Mat &t,
    139. vector &points) {
    140. Mat T1 = (Mat_<float>(3, 4) <<
    141. 1, 0, 0, 0,
    142. 0, 1, 0, 0,
    143. 0, 0, 1, 0);
    144. //根据求解到的RT构造T2矩阵
    145. Mat T2 = (Mat_<float>(3, 4) <<
    146. R.at<double>(0, 0), R.at<double>(0, 1), R.at<double>(0, 2), t.at<double>(0, 0),
    147. R.at<double>(1, 0), R.at<double>(1, 1), R.at<double>(1, 2), t.at<double>(1, 0),
    148. R.at<double>(2, 0), R.at<double>(2, 1), R.at<double>(2, 2), t.at<double>(2, 0)
    149. );
    150. //相机内参
    151. Mat K = (Mat_<double>(3, 3) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1);
    152. vector pts_1, pts_2;
    153. for (DMatch m:matches) {
    154. // 将像素坐标转换至相机坐标
    155. pts_1.push_back(pixel2cam(keypoint_1[m.queryIdx].pt, K));
    156. pts_2.push_back(pixel2cam(keypoint_2[m.trainIdx].pt, K));
    157. }
    158. Mat pts_4d;
    159. cv::triangulatePoints(T1, T2, pts_1, pts_2, pts_4d);
    160. // 转换成非齐次坐标
    161. for (int i = 0; i < pts_4d.cols; i++) {
    162. Mat x = pts_4d.col(i);
    163. x /= x.at<float>(3, 0); // 归一化
    164. Point3d p(
    165. x.at<float>(0, 0),
    166. x.at<float>(1, 0),
    167. x.at<float>(2, 0)
    168. );
    169. points.push_back(p);
    170. }
    171. }
    172. Point2f pixel2cam(const Point2d &p, const Mat &K) {
    173. return Point2f
    174. (
    175. (p.x - K.at<double>(0, 2)) / K.at<double>(0, 0),
    176. (p.y - K.at<double>(1, 2)) / K.at<double>(1, 1)
    177. );
    178. }

  • 相关阅读:
    Numpy学习
    k8s-实战——kubeadm二进制编译
    第13章 网络安全漏洞防护技术原理与应用
    vue项目中使用antvX6新手教程,附demo案例讲解(可拖拽流程图、网络拓扑图)
    uniApp监听左右滑动事件
    新库上线 | CnOpenData文化、体育和娱乐业工商注册企业基本信息数据
    Python学习之CSDN21天学习挑战赛计划之11
    ModStartBlog v6.1.0 界面显示优化,富文本升级
    Python 页面解析:Beautiful Soup库的使用
    助你成为专业终端人,阿里巴巴第三届终端练习生计划开启报名!
  • 原文地址:https://blog.csdn.net/xiao__run/article/details/132736625