test_boarddetection.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. // This file is part of OpenCV project.
  2. // It is subject to the license terms in the LICENSE file found in the top-level directory
  3. // of this distribution and at http://opencv.org/license.html.
  4. #include "test_precomp.hpp"
  5. #include "test_aruco_utils.hpp"
  6. namespace opencv_test { namespace {
  7. enum class ArucoAlgParams
  8. {
  9. USE_DEFAULT = 0,
  10. USE_ARUCO3 = 1
  11. };
  12. /**
  13. * @brief Check pose estimation of aruco board
  14. */
  15. class CV_ArucoBoardPose : public cvtest::BaseTest {
  16. public:
  17. CV_ArucoBoardPose(ArucoAlgParams arucoAlgParams)
  18. {
  19. aruco::DetectorParameters params;
  20. aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
  21. params.minDistanceToBorder = 3;
  22. if (arucoAlgParams == ArucoAlgParams::USE_ARUCO3) {
  23. params.useAruco3Detection = true;
  24. params.cornerRefinementMethod = (int)aruco::CORNER_REFINE_SUBPIX;
  25. params.minSideLengthCanonicalImg = 16;
  26. params.errorCorrectionRate = 0.8;
  27. }
  28. detector = aruco::ArucoDetector(dictionary, params);
  29. }
  30. protected:
  31. aruco::ArucoDetector detector;
  32. void run(int);
  33. };
  34. void CV_ArucoBoardPose::run(int) {
  35. int iter = 0;
  36. Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1);
  37. Size imgSize(500, 500);
  38. cameraMatrix.at< double >(0, 0) = cameraMatrix.at< double >(1, 1) = 650;
  39. cameraMatrix.at< double >(0, 2) = imgSize.width / 2;
  40. cameraMatrix.at< double >(1, 2) = imgSize.height / 2;
  41. Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
  42. const int sizeX = 3, sizeY = 3;
  43. aruco::DetectorParameters detectorParameters = detector.getDetectorParameters();
  44. // for different perspectives
  45. for(double distance : {0.2, 0.35}) {
  46. for(int yaw = -55; yaw <= 50; yaw += 25) {
  47. for(int pitch = -55; pitch <= 50; pitch += 25) {
  48. vector<int> tmpIds;
  49. for(int i = 0; i < sizeX*sizeY; i++)
  50. tmpIds.push_back((iter + int(i)) % 250);
  51. aruco::GridBoard gridboard(Size(sizeX, sizeY), 0.02f, 0.005f, detector.getDictionary(), tmpIds);
  52. int markerBorder = iter % 2 + 1;
  53. iter++;
  54. // create synthetic image
  55. Mat img = projectBoard(gridboard, cameraMatrix, deg2rad(yaw), deg2rad(pitch), distance,
  56. imgSize, markerBorder);
  57. vector<vector<Point2f> > corners;
  58. vector<int> ids;
  59. detectorParameters.markerBorderBits = markerBorder;
  60. detector.setDetectorParameters(detectorParameters);
  61. detector.detectMarkers(img, corners, ids);
  62. ASSERT_EQ(ids.size(), gridboard.getIds().size());
  63. // estimate pose
  64. Mat rvec, tvec;
  65. {
  66. Mat objPoints, imgPoints; // get object and image points for the solvePnP function
  67. gridboard.matchImagePoints(corners, ids, objPoints, imgPoints);
  68. solvePnP(objPoints, imgPoints, cameraMatrix, distCoeffs, rvec, tvec);
  69. }
  70. // check axes
  71. vector<Point2f> axes = getAxis(cameraMatrix, distCoeffs, rvec, tvec, gridboard.getRightBottomCorner().x);
  72. vector<Point2f> topLeft = getMarkerById(gridboard.getIds()[0], corners, ids);
  73. ASSERT_NEAR(topLeft[0].x, axes[0].x, 2.f);
  74. ASSERT_NEAR(topLeft[0].y, axes[0].y, 2.f);
  75. vector<Point2f> topRight = getMarkerById(gridboard.getIds()[2], corners, ids);
  76. ASSERT_NEAR(topRight[1].x, axes[1].x, 2.f);
  77. ASSERT_NEAR(topRight[1].y, axes[1].y, 2.f);
  78. vector<Point2f> bottomLeft = getMarkerById(gridboard.getIds()[6], corners, ids);
  79. ASSERT_NEAR(bottomLeft[3].x, axes[2].x, 2.f);
  80. ASSERT_NEAR(bottomLeft[3].y, axes[2].y, 2.f);
  81. // check estimate result
  82. for(unsigned int i = 0; i < ids.size(); i++) {
  83. int foundIdx = -1;
  84. for(unsigned int j = 0; j < gridboard.getIds().size(); j++) {
  85. if(gridboard.getIds()[j] == ids[i]) {
  86. foundIdx = int(j);
  87. break;
  88. }
  89. }
  90. if(foundIdx == -1) {
  91. ts->printf(cvtest::TS::LOG, "Marker detected with wrong ID in Board test");
  92. ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
  93. return;
  94. }
  95. vector< Point2f > projectedCorners;
  96. projectPoints(gridboard.getObjPoints()[foundIdx], rvec, tvec, cameraMatrix, distCoeffs,
  97. projectedCorners);
  98. for(int c = 0; c < 4; c++) {
  99. double repError = cv::norm(projectedCorners[c] - corners[i][c]); // TODO cvtest
  100. if(repError > 5.) {
  101. ts->printf(cvtest::TS::LOG, "Corner reprojection error too high");
  102. ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
  103. return;
  104. }
  105. }
  106. }
  107. }
  108. }
  109. }
  110. }
  111. /**
  112. * @brief Check refine strategy
  113. */
  114. class CV_ArucoRefine : public cvtest::BaseTest {
  115. public:
  116. CV_ArucoRefine(ArucoAlgParams arucoAlgParams)
  117. {
  118. vector<aruco::Dictionary> dictionaries = {aruco::getPredefinedDictionary(aruco::DICT_6X6_250),
  119. aruco::getPredefinedDictionary(aruco::DICT_5X5_250),
  120. aruco::getPredefinedDictionary(aruco::DICT_4X4_250),
  121. aruco::getPredefinedDictionary(aruco::DICT_7X7_250)};
  122. aruco::DetectorParameters params;
  123. params.minDistanceToBorder = 3;
  124. params.cornerRefinementMethod = (int)aruco::CORNER_REFINE_SUBPIX;
  125. if (arucoAlgParams == ArucoAlgParams::USE_ARUCO3)
  126. params.useAruco3Detection = true;
  127. aruco::RefineParameters refineParams(10.f, 3.f, true);
  128. detector = aruco::ArucoDetector(dictionaries, params, refineParams);
  129. }
  130. protected:
  131. aruco::ArucoDetector detector;
  132. void run(int);
  133. };
  134. void CV_ArucoRefine::run(int) {
  135. int iter = 0;
  136. Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1);
  137. Size imgSize(500, 500);
  138. cameraMatrix.at< double >(0, 0) = cameraMatrix.at< double >(1, 1) = 650;
  139. cameraMatrix.at< double >(0, 2) = imgSize.width / 2;
  140. cameraMatrix.at< double >(1, 2) = imgSize.height / 2;
  141. Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
  142. aruco::DetectorParameters detectorParameters = detector.getDetectorParameters();
  143. // for different perspectives
  144. for(double distance : {0.2, 0.4}) {
  145. for(int yaw = -60; yaw < 60; yaw += 30) {
  146. for(int pitch = -60; pitch <= 60; pitch += 30) {
  147. aruco::GridBoard gridboard(Size(3, 3), 0.02f, 0.005f, detector.getDictionary());
  148. int markerBorder = iter % 2 + 1;
  149. iter++;
  150. // create synthetic image
  151. Mat img = projectBoard(gridboard, cameraMatrix, deg2rad(yaw), deg2rad(pitch), distance,
  152. imgSize, markerBorder);
  153. // detect markers
  154. vector<vector<Point2f> > corners, rejected;
  155. vector<int> ids;
  156. detectorParameters.markerBorderBits = markerBorder;
  157. detector.setDetectorParameters(detectorParameters);
  158. detector.detectMarkers(img, corners, ids, rejected);
  159. // remove a marker from detection
  160. int markersBeforeDelete = (int)ids.size();
  161. if(markersBeforeDelete < 2) continue;
  162. rejected.push_back(corners[0]);
  163. corners.erase(corners.begin(), corners.begin() + 1);
  164. ids.erase(ids.begin(), ids.begin() + 1);
  165. // try to refind the erased marker
  166. detector.refineDetectedMarkers(img, gridboard, corners, ids, rejected, cameraMatrix,
  167. distCoeffs, noArray());
  168. // check result
  169. if((int)ids.size() < markersBeforeDelete) {
  170. ts->printf(cvtest::TS::LOG, "Error in refine detected markers");
  171. ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
  172. return;
  173. }
  174. }
  175. }
  176. }
  177. }
  178. TEST(CV_ArucoBoardPose, accuracy) {
  179. CV_ArucoBoardPose test(ArucoAlgParams::USE_DEFAULT);
  180. test.safe_run();
  181. }
  182. typedef CV_ArucoBoardPose CV_Aruco3BoardPose;
  183. TEST(CV_Aruco3BoardPose, accuracy) {
  184. CV_Aruco3BoardPose test(ArucoAlgParams::USE_ARUCO3);
  185. test.safe_run();
  186. }
  187. typedef CV_ArucoRefine CV_Aruco3Refine;
  188. TEST(CV_ArucoRefine, accuracy) {
  189. CV_ArucoRefine test(ArucoAlgParams::USE_DEFAULT);
  190. test.safe_run();
  191. }
  192. TEST(CV_Aruco3Refine, accuracy) {
  193. CV_Aruco3Refine test(ArucoAlgParams::USE_ARUCO3);
  194. test.safe_run();
  195. }
  196. TEST(CV_ArucoBoardPose, CheckNegativeZ)
  197. {
  198. double matrixData[9] = { -3.9062571886921410e+02, 0., 4.2350000000000000e+02,
  199. 0., 3.9062571886921410e+02, 2.3950000000000000e+02,
  200. 0., 0., 1 };
  201. cv::Mat cameraMatrix = cv::Mat(3, 3, CV_64F, matrixData);
  202. vector<cv::Point3f> pts3d1, pts3d2;
  203. pts3d1.push_back(cv::Point3f(0.326198f, -0.030621f, 0.303620f));
  204. pts3d1.push_back(cv::Point3f(0.325340f, -0.100594f, 0.301862f));
  205. pts3d1.push_back(cv::Point3f(0.255859f, -0.099530f, 0.293416f));
  206. pts3d1.push_back(cv::Point3f(0.256717f, -0.029557f, 0.295174f));
  207. pts3d2.push_back(cv::Point3f(-0.033144f, -0.034819f, 0.245216f));
  208. pts3d2.push_back(cv::Point3f(-0.035507f, -0.104705f, 0.241987f));
  209. pts3d2.push_back(cv::Point3f(-0.105289f, -0.102120f, 0.237120f));
  210. pts3d2.push_back(cv::Point3f(-0.102926f, -0.032235f, 0.240349f));
  211. vector<int> tmpIds = {0, 1};
  212. vector<vector<Point3f> > tmpObjectPoints = {pts3d1, pts3d2};
  213. aruco::Board board(tmpObjectPoints, aruco::getPredefinedDictionary(0), tmpIds);
  214. vector<vector<Point2f> > corners;
  215. vector<Point2f> pts2d;
  216. pts2d.push_back(cv::Point2f(37.7f, 203.3f));
  217. pts2d.push_back(cv::Point2f(38.5f, 120.5f));
  218. pts2d.push_back(cv::Point2f(105.5f, 115.8f));
  219. pts2d.push_back(cv::Point2f(104.2f, 202.7f));
  220. corners.push_back(pts2d);
  221. pts2d.clear();
  222. pts2d.push_back(cv::Point2f(476.0f, 184.2f));
  223. pts2d.push_back(cv::Point2f(479.6f, 73.8f));
  224. pts2d.push_back(cv::Point2f(590.9f, 77.0f));
  225. pts2d.push_back(cv::Point2f(587.5f, 188.1f));
  226. corners.push_back(pts2d);
  227. Vec3d rvec, tvec;
  228. int nUsed = 0;
  229. {
  230. Mat objPoints, imgPoints; // get object and image points for the solvePnP function
  231. board.matchImagePoints(corners, board.getIds(), objPoints, imgPoints);
  232. nUsed = (int)objPoints.total()/4;
  233. solvePnP(objPoints, imgPoints, cameraMatrix, Mat(), rvec, tvec);
  234. }
  235. ASSERT_EQ(nUsed, 2);
  236. cv::Matx33d rotm; cv::Point3d out;
  237. cv::Rodrigues(rvec, rotm);
  238. out = cv::Point3d(tvec) + rotm*Point3d(board.getObjPoints()[0][0]);
  239. ASSERT_GT(out.z, 0);
  240. corners.clear(); pts2d.clear();
  241. pts2d.push_back(cv::Point2f(38.4f, 204.5f));
  242. pts2d.push_back(cv::Point2f(40.0f, 124.7f));
  243. pts2d.push_back(cv::Point2f(102.0f, 119.1f));
  244. pts2d.push_back(cv::Point2f(99.9f, 203.6f));
  245. corners.push_back(pts2d);
  246. pts2d.clear();
  247. pts2d.push_back(cv::Point2f(476.0f, 184.3f));
  248. pts2d.push_back(cv::Point2f(479.2f, 75.1f));
  249. pts2d.push_back(cv::Point2f(588.7f, 79.2f));
  250. pts2d.push_back(cv::Point2f(586.3f, 188.5f));
  251. corners.push_back(pts2d);
  252. nUsed = 0;
  253. {
  254. Mat objPoints, imgPoints; // get object and image points for the solvePnP function
  255. board.matchImagePoints(corners, board.getIds(), objPoints, imgPoints);
  256. nUsed = (int)objPoints.total()/4;
  257. solvePnP(objPoints, imgPoints, cameraMatrix, Mat(), rvec, tvec, true);
  258. }
  259. ASSERT_EQ(nUsed, 2);
  260. cv::Rodrigues(rvec, rotm);
  261. out = cv::Point3d(tvec) + rotm*Point3d(board.getObjPoints()[0][0]);
  262. ASSERT_GT(out.z, 0);
  263. }
  264. TEST(CV_ArucoGenerateBoard, regression_1226) {
  265. int bwidth = 1600;
  266. int bheight = 1200;
  267. cv::aruco::Dictionary dict = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_4X4_50);
  268. cv::aruco::CharucoBoard board(Size(7, 5), 1.0, 0.75, dict);
  269. cv::Size sz(bwidth, bheight);
  270. cv::Mat mat;
  271. ASSERT_NO_THROW(
  272. {
  273. board.generateImage(sz, mat, 0, 1);
  274. });
  275. }
  276. TEST(CV_ArucoDictionary, extendDictionary) {
  277. aruco::Dictionary base_dictionary = aruco::getPredefinedDictionary(aruco::DICT_4X4_250);
  278. aruco::Dictionary custom_dictionary = aruco::extendDictionary(150, 4, base_dictionary);
  279. ASSERT_EQ(custom_dictionary.bytesList.rows, 150);
  280. ASSERT_EQ(cv::norm(custom_dictionary.bytesList, base_dictionary.bytesList.rowRange(0, 150)), 0.);
  281. }
  282. TEST(CV_ArucoBoardGenerateImage_RotationTest, HandlesRotatedMarkersWithoutBoundingBoxError)
  283. {
  284. using namespace cv;
  285. using namespace cv::aruco;
  286. Dictionary dict = getPredefinedDictionary(DICT_4X4_50);
  287. DetectorParameters detectorParams;
  288. ArucoDetector detector(dict, detectorParams);
  289. std::vector<float> angles = {0.0f, 45.0f, 90.0f, 135.0f};
  290. for (auto angle_deg : angles)
  291. {
  292. float angle_rad = angle_deg * static_cast<float>(CV_PI) / 180.0f;
  293. float c = cos(angle_rad);
  294. float s = sin(angle_rad);
  295. std::vector<Point3f> markerCorners(4);
  296. markerCorners[0] = Point3f(0.f, 0.f, 0.f);
  297. markerCorners[1] = Point3f(1.f, 0.f, 0.f);
  298. markerCorners[2] = Point3f(1.f, 1.f, 0.f);
  299. markerCorners[3] = Point3f(0.f, 1.f, 0.f);
  300. for (auto &p : markerCorners)
  301. {
  302. float xNew = p.x * c - p.y * s;
  303. float yNew = p.x * s + p.y * c;
  304. p.x = xNew;
  305. p.y = yNew;
  306. }
  307. std::vector<std::vector<Point3f>> allObjPoints{markerCorners};
  308. std::vector<int> ids{0};
  309. Board board(allObjPoints, dict, ids);
  310. float markerSize = 1.0f;
  311. float rotatedSize = markerSize * std::sqrt(2.0f);
  312. int borderBits = 1;
  313. int marginSize = 20;
  314. int sidePixels = static_cast<int>((rotatedSize + 2.0f * borderBits) * 500) + 2 * marginSize;
  315. Mat outImg;
  316. Size outSize(sidePixels, sidePixels);
  317. ASSERT_NO_THROW(board.generateImage(outSize, outImg, marginSize, borderBits))
  318. << "board.generateImage() threw an exception at angle " << angle_deg;
  319. std::vector<int> detectedIds;
  320. std::vector<std::vector<Point2f>> detectedCorners;
  321. detector.detectMarkers(outImg, detectedCorners, detectedIds);
  322. ASSERT_EQ(detectedIds.size(), (size_t)1)
  323. << "Failed to detect single marker at angle: " << angle_deg;
  324. EXPECT_EQ(detectedIds[0], 0)
  325. << "Marker ID mismatch at angle: " << angle_deg;
  326. }
  327. }
  328. }} // namespace