当前位置:   article > 正文

【opencv】实时位姿估计(real_time_pose_estimation)—3D模型注册

【opencv】实时位姿估计(real_time_pose_estimation)—3D模型注册

c1c44688a044a89fc1d9fb09e33c26f7.jpeg

相机成像原理图

b833cb7b44664576465644ca4b326546.png

物体网格、关键点(局内点、局外点)图像

69b95fbbba6eb190b985bedcc22ec501.pnga26fb25c4e6f2d9bec7177b422bb77e5.png

box.ply

172d6cffa1e912109500fbd9f4092513.jpeg

resized_IMG_3875.JPG

主程序main_registration.cpp

主要实现了利用OpenCV库进行3D模型的注册。主要步骤包括加载3D网格模型、使用鼠标事件选择对应的3D点进行2D到3D的注册、利用solvePnP算法计算摄像机位姿、并将结果保存在yaml文件中。这通常用于计算机视觉中的对象识别和姿态估计。程序也包括了绘制相应点、调试文本和3D物体网格的功能,以便更好地视觉化注册过程。

  1. // 包含C++的输入输出流库
  2. #include <iostream>
  3. // 包含OpenCV的核心、图像处理、相机标定和特征点检测等功能的库
  4. #include <opencv2/core.hpp>
  5. #include <opencv2/imgproc.hpp>
  6. #include <opencv2/calib3d.hpp>
  7. #include <opencv2/features2d.hpp>
  8. // 包含本教程的网格、模型、PNP问题、稳健匹配和模型注册以及工具等类
  9. #include "Mesh.h"
  10. #include "Model.h"
  11. #include "PnPProblem.h"
  12. #include "RobustMatcher.h"
  13. #include "ModelRegistration.h"
  14. #include "Utils.h"
  15. // OpenCV和标准模板库的命名空间
  16. using namespace cv;
  17. using namespace std;
  18. /** 全局变量 **/
  19. // 注册是否完成的布尔型变量
  20. bool end_registration = false;
  21. // 相机的内部参数: UVC WEBCAM
  22. const double f = 45; // 焦距,以毫米为单位
  23. const double sx = 22.3, sy = 14.9; // 传感器尺寸,以毫米为单位
  24. const double width = 2592, height = 1944; // 图像宽度和高度
  25. const double params_CANON[] = { width*f/sx, // fx
  26. height*f/sy, // fy
  27. width/2, // cx
  28. height/2}; // cy
  29. // 设置在图像中要注册的点
  30. // 根据*.ply文件的顺序,从1开始
  31. const int n = 8;
  32. const int pts[] = {1, 2, 3, 4, 5, 6, 7, 8}; // 3 -> 4
  33. /*
  34. * 创建模型注册对象
  35. * 创建网格对象
  36. * 创建模型对象
  37. * 创建PNP问题对象
  38. */
  39. ModelRegistration registration;
  40. Model model;
  41. Mesh mesh;
  42. PnPProblem pnp_registration(params_CANON);
  43. /**********************************************************************************************************/
  44. // 显示帮助信息的函数
  45. static void help()
  46. {
  47. cout
  48. << "--------------------------------------------------------------------------" << endl
  49. << "这个程序展示了如何创建你的3D贴图模型。" << endl
  50. << "使用方法:" << endl
  51. << "./cpp-tutorial-pnp_registration" << endl
  52. << "--------------------------------------------------------------------------" << endl
  53. << endl;
  54. }
  55. // 鼠标事件回调函数,用于模型注册
  56. static void onMouseModelRegistration( int event, int x, int y, int, void* )
  57. {
  58. // 如果检测到鼠标左键释放事件
  59. if ( event == EVENT_LBUTTONUP )
  60. {
  61. // 检查是否可以注册
  62. bool is_registrable = registration.is_registrable();
  63. if (is_registrable)
  64. {
  65. // 获取已注册的点数
  66. int n_regist = registration.getNumRegist();
  67. // 获取需要注册的顶点编号
  68. int n_vertex = pts[n_regist];
  69. // 创建2D点
  70. Point2f point_2d = Point2f((float)x,(float)y);
  71. // 获取3D点
  72. Point3f point_3d = mesh.getVertex(n_vertex-1);
  73. // 注册点
  74. registration.registerPoint(point_2d, point_3d);
  75. // 如果达到最大注册点数,结束注册
  76. if( registration.getNumRegist() == registration.getNumMax() ) end_registration = true;
  77. }
  78. }
  79. }
  80. /** 主程序 **/
  81. int main(int argc, char *argv[])
  82. {
  83. // 调用帮助信息显示函数
  84. help();
  85. // 定义命令行参数
  86. const String keys =
  87. "{help h | | 打印帮助信息 }"
  88. "{image i | | 输入图像的路径 }"
  89. "{model | | 输出yml模型的路径 }"
  90. "{mesh | | ply网格的路径 }"
  91. "{keypoints k |2000 | 检测关键点的数量(仅用于ORB) }"
  92. "{feature |ORB | 特征点名称 (ORB, KAZE, AKAZE, BRISK, SIFT, SURF, BINBOOST, VGG)}"
  93. ;
  94. // 创建命令行解析器
  95. CommandLineParser parser(argc, argv, keys);
  96. // 定义默认图像路径、网格文件路径和输出文件路径以及其他参数
  97. string img_path = samples::findFile("samples/cpp/tutorial_code/calib3d/real_time_pose_estimation/Data/resized_IMG_3875.JPG"); // 用于注册的图像
  98. string ply_read_path = samples::findFile("samples/cpp/tutorial_code/calib3d/real_time_pose_estimation/Data/box.ply"); // 物体网格
  99. string write_path = samples::findFile("samples/cpp/tutorial_code/calib3d/real_time_pose_estimation/Data/cookies_ORB.yml"); // 输出文件
  100. int numKeyPoints = 2000;
  101. string featureName = "ORB";
  102. // 根据命令行提供的参数更新路径和参数值
  103. if (parser.has("help"))
  104. {
  105. parser.printMessage();
  106. return 0;
  107. }
  108. else
  109. {
  110. img_path = parser.get<string>("image").size() > 0 ? parser.get<string>("image") : img_path;
  111. ply_read_path = parser.get<string>("mesh").size() > 0 ? parser.get<string>("mesh") : ply_read_path;
  112. write_path = parser.get<string>("model").size() > 0 ? parser.get<string>("model") : write_path;
  113. numKeyPoints = parser.has("keypoints") ? parser.get<int>("keypoints") : numKeyPoints;
  114. featureName = parser.has("feature") ? parser.get<string>("feature") : featureName;
  115. }
  116. // 打印相关路径和参数信息
  117. std::cout << "输入图像: " << img_path << std::endl;
  118. std::cout << "CAD模型: " << ply_read_path << std::endl;
  119. std::cout << "输出训练文件: " << write_path << std::endl;
  120. std::cout << "特征点: " << featureName << std::endl;
  121. std::cout << "ORB关键点数量: " << numKeyPoints << std::endl;
  122. // 使用*.ply文件路径加载网格
  123. mesh.load(ply_read_path);
  124. // 实例化RobustMatcher类:检测器、提取器、匹配器
  125. RobustMatcher rmatcher;
  126. Ptr<Feature2D> detector, descriptor;
  127. // 创建特征
  128. createFeatures(featureName, numKeyPoints, detector, descriptor);
  129. // 设置特征检测器和描述子提取器
  130. rmatcher.setFeatureDetector(detector);
  131. rmatcher.setDescriptorExtractor(descriptor);
  132. /** 第一张图像的基本真实数据 **/
  133. // 创建并打开窗口 创建一个保持原图像比例且大小可调的显示窗口,窗口的名字为"MODEL REGISTRATION"
  134. namedWindow("MODEL REGISTRATION", WINDOW_KEEPRATIO);
  135. // 设置鼠标事件
  136. setMouseCallback("MODEL REGISTRATION", onMouseModelRegistration, 0);
  137. // 打开要注册的图像
  138.     Mat img_in = imread(img_path, IMREAD_COLOR);//从 img_path 指定的文件中以彩色方式读入图像,然后将读入的图像数据存储在 Mat 类型的 img_in 中。
  139. Mat img_vis; // 可视图像的副本
  140. // 如果读取图像失败
  141. if (img_in.empty()) {
  142. cout << "无法打开或找到图像" << endl;
  143. return -1;
  144. }
  145. // 设置要注册的点数
  146. int num_registrations = n;//8
  147. registration.setNumMax(num_registrations);
  148. // 提示用户点击箱子角落
  149. cout << "点击箱子角落..." << endl;
  150. cout << "等待..." << endl;
  151. // 定义一些基本颜色
  152. const Scalar red(0, 0, 255);
  153. const Scalar green(0,255,0);
  154. const Scalar blue(255,0,0);
  155. const Scalar yellow(0,255,255);
  156. // 循环直到所有点被注册
  157. while ( waitKey(30) < 0 )//如果在每30毫秒内没有接收到任何用户按键输入,那么就一直执行while循环中的代码。
  158. {
  159. // 刷新调试图像
  160. img_vis = img_in.clone();
  161. // 当前已注册的点
  162. vector<Point2f> list_points2d = registration.get_points2d();
  163. vector<Point3f> list_points3d = registration.get_points3d();
  164.         // 绘制当前已注册的点  圆点+坐标文字
  165. drawPoints(img_vis, list_points2d, list_points3d, red);
  166. // 如果注册未完成,绘制我们要注册的3D点。
  167. // 如果注册已完成,跳出循环。
  168. if (!end_registration)
  169. {
  170. // 绘制调试文字
  171. int n_regist = registration.getNumRegist();
  172. int n_vertex = pts[n_regist];
  173. Point3f current_poin3d = mesh.getVertex(n_vertex-1);
  174.             drawQuestion(img_vis, current_poin3d, green);//绘制当前3D点的信息
  175.             drawCounter(img_vis, registration.getNumRegist(), registration.getNumMax(), red);//绘制计数文本 
  176. }
  177. else
  178. {
  179. // 绘制调试文字
  180. drawText(img_vis, "注册结束", green);
  181. drawCounter(img_vis, registration.getNumRegist(), registration.getNumMax(), green);
  182. break;
  183. }
  184. // 显示图像
  185. imshow("MODEL REGISTRATION", img_vis);
  186. }
  187. /** 计算相机位置 **/
  188. cout << "计算位置..." << endl;
  189. // 已注册点的列表
  190. vector<Point2f> list_points2d = registration.get_points2d();
  191. vector<Point3f> list_points3d = registration.get_points3d();
  192. // 根据已注册的点估计位置 通过使用潜在空间点(list_points3d)和2D图像空间点(list_points2d)的对应关系,对相机进行姿态估计
  193. bool is_correspondence = pnp_registration.estimatePose(list_points3d, list_points2d, SOLVEPNP_ITERATIVE);
  194. if ( is_correspondence )
  195. {
  196. cout << "找到对应点" << endl;
  197. // 计算网格的所有2D点以验证算法并绘制
  198. vector<Point2f> list_points2d_mesh = pnp_registration.verify_points(&mesh);
  199. draw2DPoints(img_vis, list_points2d_mesh, green);
  200. }
  201. else {
  202. cout << "没有找到对应点" << endl << endl;
  203. }
  204. // 显示图像
  205. imshow("MODEL REGISTRATION", img_vis);
  206. // 等待直到按下ESC键
  207. waitKey(0);
  208. /** 计算图像关键点的3D **/
  209. // 模型关键点和描述子的容器
  210. vector<KeyPoint> keypoints_model;
  211. Mat descriptors;
  212. // 计算关键点和描述子
  213. rmatcher.computeKeyPoints(img_in, keypoints_model);
  214. rmatcher.computeDescriptors(img_in, keypoints_model, descriptors);
  215. // 检查关键点是否在注册图像的表面,并添加到模型中
  216.     //这段代码主要用于处理关键点模型中的每一个点,对于每个点,它创建
  217.     //一个对应的2D点,并使用PnP(Perspective-n-Point)注册并尝试
  218.     //将2D点反投影(backproject)回3D。如果反投影成功(即点在模型
  219.     //表面上),则在模型中添加对应的2D-3D点、描述符和关键点;否则,
  220.     //将该2D点添加到模型的离群点(outliers)中。这对于创建和调整3D模型
  221.     //,以及进行模型匹配和识别等任务来说,非常关键。
  222.     // 从零开始遍历关键点模型的每一个点
  223. for (unsigned int i = 0; i < keypoints_model.size(); ++i) {
  224. // 创建一个2D点,取自关键点模型的第i个点的位置
  225. Point2f point2d(keypoints_model[i].pt);
  226. // 创建一个空的3D点
  227. Point3f point3d;
  228. // 利用PnP(指视角nP点)识别并将2D点反投影到3D
  229. // 若点在物体表面上,on_surface则为真;否则,为假
  230. bool on_surface = pnp_registration.backproject2DPoint(&mesh, point2d, point3d);
  231. // 若该点在物体表面上
  232. if (on_surface)
  233. {
  234. // 在模型中添加对应的2D和3D点
  235. model.add_correspondence(point2d, point3d);
  236. // 向模型添加描述符,这是从描述符中的第i行取得的
  237. model.add_descriptor(descriptors.row(i));
  238. // 在对应模型中添加关键点,这是从关键点模型的第i个关键点取得的
  239. model.add_keypoint(keypoints_model[i]);
  240. }
  241. // 若该点不在物体表面上
  242. else
  243. {
  244. // 将该2D点添加到模型的异常值中
  245. model.add_outlier(point2d);
  246. }
  247. }
  248. // 设置训练图像路径
  249. model.set_trainingImagePath(img_path);
  250. // 保存模型到*.yaml文件
  251. model.save(write_path);
  252. // 输出图像
  253. img_vis = img_in.clone();
  254. // 模型的2D点列表
  255. vector<Point2f> list_points_in = model.get_points2d_in();
  256. vector<Point2f> list_points_out = model.get_points2d_out();
  257. // 绘制一些调试文本
  258. string num = IntToString((int)list_points_in.size());
  259. string text = "有 " + num + " 个内点";
  260. drawText(img_vis, text, green);
  261. // 绘制一些调试文本
  262. num = IntToString((int)list_points_out.size());
  263. text = "有 " + num + " 个外点";
  264. drawText2(img_vis, text, red);
  265. // 绘制物体网格
  266. drawObjectMesh(img_vis, &mesh, &pnp_registration, blue);
  267. // 根据是否在表面绘制找到的关键点
  268. draw2DPoints(img_vis, list_points_in, green);
  269. draw2DPoints(img_vis, list_points_out, red);
  270. // 显示图像
  271. imshow("MODEL REGISTRATION", img_vis);
  272. // 等待直到按下ESC键
  273. waitKey(0);
  274. // 关闭并销毁窗口
  275. destroyWindow("MODEL REGISTRATION");
  276. cout << "再见" << endl;
  277. }

知识点:

c5fd7cab3efc41e9175cd0d8eadae993.png

0cf4c26b89cb966ec3b9a552ba1a94ac.png

小孔成像模型

8e52c78e9dd50f8641288230ab80cf3b.png

941ed8d09e3fc2ebdc8f80a552d82031.png

创建PNP问题对象

a9706c6c255a9e23baffba8ba4b649d1.png

74e2e35e48d26146f213ea807ca4d3c9.png

加载ply网格

e8f3db354b82b639bcb64ad4ea8b6d83.png

不同特征检测器和描述符提取器

af84ef32756b0ca97aefed33ed0f58c0.png

估计相机的位姿:即相机相对于3D模型的旋转和平移

  1. // 给定2D/3D对应点列表和使用的方法,估计姿态的函数
  2. bool PnPProblem::estimatePose( const std::vector<cv::Point3f> &list_points3d,
  3. const std::vector<cv::Point2f> &list_points2d,
  4. int flags)
  5. {
  6. // 初始化畸变系数矩阵、旋转向量和平移向量
  7. cv::Mat distCoeffs = cv::Mat::zeros(4, 1, CV_64FC1);
  8. cv::Mat rvec = cv::Mat::zeros(3, 1, CV_64FC1);
  9. cv::Mat tvec = cv::Mat::zeros(3, 1, CV_64FC1);
  10. // 使用外部猜测?(暂不使用)
  11. bool useExtrinsicGuess = false;
  12. // 姿态估计
  13. bool correspondence = cv::solvePnP( list_points3d, list_points2d, A_matrix_, distCoeffs, rvec, tvec,
  14. useExtrinsicGuess, flags);
  15. // 将旋转向量转换为矩阵
  16. Rodrigues(rvec, R_matrix_);
  17. t_matrix_ = tvec;
  18. // 设置投影矩阵
  19. this->set_P_matrix(R_matrix_, t_matrix_);
  20. return correspondence;
  21. }

9f94b91034c1e5acecd49fd6cac0a91a.png

相机的位姿估计与物体的位姿估计区别

dd7d527f132370abe36a7d515400696f.png

参考网址:

https://zhuanlan.zhihu.com/p/389653208

https://zh.wikipedia.org/wiki/%E9%87%9D%E5%AD%94%E7%9B%B8%E6%A9%9F  针孔相机

http://www.powersensor.cn/p3_demo/demo4-camIdentify.html

https://blog.51cto.com/u_14439393/5732298

The End

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/322504
推荐阅读
相关标签
  

闽ICP备14008679号