From 472eba324af899e192c30970fe8dda98332a0f63 Mon Sep 17 00:00:00 2001 From: Alexander Panov Date: Fri, 31 May 2024 17:03:24 +0300 Subject: [PATCH] Merge pull request #25673 from AleksandrPanov:fix_charuco_board_markers fixed marker generation in charuco board #25673 When generating Charuco board markers in `generateImage()`, a problem occurs as in https://github.com/opencv/opencv/issues/24806, https://github.com/opencv/opencv/pull/24873: In low resolution: ![charucoImg_before_fix2](https://github.com/opencv/opencv/assets/22337800/aab7b443-2d2a-4287-b869-708ac5976ea5) In medium resolution: ![charucoImg_before_fix](https://github.com/opencv/opencv/assets/22337800/8c21ae42-d9c8-4cb5-9fcc-7814dfc07b80) This PR fixed this problems: ![charucoImg_after_fix2](https://github.com/opencv/opencv/assets/22337800/93256dbb-8544-46eb-be78-844234e42ca9) ![charucoImg_after_fix](https://github.com/opencv/opencv/assets/22337800/f4d6794e-bee9-4ce4-8f9b-67a40800cbe5) The test checks the inner and outer borders of the Aruco markers. In the outer border of Aruco marker, all pixels should be white. In the inner border of Aruco marker, all pixels should be black. ![image](https://github.com/opencv/opencv/assets/22337800/010a9c64-e03c-4239-9ac9-2cda9170793b) ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- modules/objdetect/src/aruco/aruco_board.cpp | 15 ---- .../objdetect/test/test_charucodetection.cpp | 78 +++++++++++++++++-- 2 files changed, 72 insertions(+), 21 deletions(-) diff --git a/modules/objdetect/src/aruco/aruco_board.cpp b/modules/objdetect/src/aruco/aruco_board.cpp index f8d3d3c108..f5f274ec0c 100644 --- a/modules/objdetect/src/aruco/aruco_board.cpp +++ b/modules/objdetect/src/aruco/aruco_board.cpp @@ -99,21 +99,6 @@ void Board::Impl::generateImage(Size outSize, OutputArray img, int marginSize, i float sizeX = maxX - minX; float sizeY = maxY - minY; - // proportion transformations - float xReduction = sizeX / float(out.cols); - float yReduction = sizeY / float(out.rows); - - // determine the zone where the markers are placed - if(xReduction > yReduction) { - int nRows = int(sizeY / xReduction); - int rowsMargins = (out.rows - nRows) / 2; - out.adjustROI(-rowsMargins, -rowsMargins, 0, 0); - } else { - int nCols = int(sizeX / yReduction); - int colsMargins = (out.cols - nCols) / 2; - out.adjustROI(0, 0, -colsMargins, -colsMargins); - } - // now paint each marker Mat marker; Point2f outCorners[3]; diff --git a/modules/objdetect/test/test_charucodetection.cpp b/modules/objdetect/test/test_charucodetection.cpp index 47166fa78e..c0f6c93d50 100644 --- a/modules/objdetect/test/test_charucodetection.cpp +++ b/modules/objdetect/test/test_charucodetection.cpp @@ -81,6 +81,18 @@ static Mat projectCharucoBoard(aruco::CharucoBoard& board, Mat cameraMatrix, dou return img; } +static bool borderPixelsHaveSameColor(const Mat& image, uint8_t color) { + for (int j = 0; j < image.cols; j++) { + if (image.at(0, j) != color || image.at(image.rows-1, j) != color) + return false; + } + for (int i = 0; i < image.rows; i++) { + if (image.at(i, 0) != color || image.at(i, image.cols-1) != color) + return false; + } + return true; +} + /** * @brief Check Charuco detection */ @@ -769,17 +781,24 @@ TEST_P(CharucoBoard, testWrongSizeDetection) ASSERT_TRUE(detectedCharucoIds.empty()); } -TEST(CharucoBoardGenerate, issue_24806) + +typedef testing::TestWithParam> CharucoBoardGenerate; +INSTANTIATE_TEST_CASE_P(/**/, CharucoBoardGenerate, testing::Values(make_tuple(Size(7, 4), 13.f, Size(400, 300), 24), + make_tuple(Size(12, 2), 13.f, Size(200, 150), 1), + make_tuple(Size(12, 2), 13.1f, Size(400, 300), 1))); +TEST_P(CharucoBoardGenerate, issue_24806) { aruco::Dictionary dict = aruco::getPredefinedDictionary(aruco::DICT_4X4_1000); - const float squareLength = 13.f, markerLength = 10.f; - const Size boardSize(7ull, 4ull); + auto params = GetParam(); + const Size boardSize = std::get<0>(params); + const float squareLength = std::get<1>(params), markerLength = 10.f; + Size imgSize = std::get<2>(params); const aruco::CharucoBoard board(boardSize, squareLength, markerLength, dict); - const int marginSize = 24; + const int marginSize = std::get<3>(params); Mat boardImg; // generate chessboard image - board.generateImage(Size(400, 300), boardImg, marginSize); + board.generateImage(imgSize, boardImg, marginSize); // This condition checks that the width of the image determines the dimensions of the chessboard in this test CV_Assert((float)(boardImg.cols) / (float)boardSize.width <= (float)(boardImg.rows) / (float)boardSize.height); @@ -817,7 +836,54 @@ TEST(CharucoBoardGenerate, issue_24806) bool eq = (cv::countNonZero(goldCorner1 != winCorner) == 0) || (cv::countNonZero(goldCorner2 != winCorner) == 0); ASSERT_TRUE(eq); } - // TODO: fix aruco generateImage and add test aruco corners for generated image + + // marker size in pixels + const float pixInMarker = markerLength/squareLength*pixInSquare; + // the size of the marker margin in pixels + const float pixInMarginMarker = 0.5f*(pixInSquare - pixInMarker); + + // determine the zone where the aruco markers are located + int endArucoX = cvRound(pixInSquare*(boardSize.width-1)+pixInMarginMarker+pixInMarker); + int endArucoY = cvRound(pixInSquare*(boardSize.height-1)+pixInMarginMarker+pixInMarker); + Mat arucoZone = chessboardZoneImg(Range(cvRound(pixInMarginMarker), endArucoY), Range(cvRound(pixInMarginMarker), endArucoX)); + + const auto& markerCorners = board.getObjPoints(); + float minX, maxX, minY, maxY; + minX = maxX = markerCorners[0][0].x; + minY = maxY = markerCorners[0][0].y; + for (const auto& marker : markerCorners) { + for (const Point3f& objCorner : marker) { + minX = min(minX, objCorner.x); + maxX = max(maxX, objCorner.x); + minY = min(minY, objCorner.y); + maxY = max(maxY, objCorner.y); + } + } + + Point2f outCorners[3]; + for (const auto& marker : markerCorners) { + for (int i = 0; i < 3; i++) { + outCorners[i] = Point2f(marker[i].x, marker[i].y) - Point2f(minX, minY); + outCorners[i].x = outCorners[i].x / (maxX - minX) * float(arucoZone.cols); + outCorners[i].y = outCorners[i].y / (maxY - minY) * float(arucoZone.rows); + } + Size dst_sz(outCorners[2] - outCorners[0]); // assuming CCW order + dst_sz.width = dst_sz.height = std::min(dst_sz.width, dst_sz.height); + Rect borderRect = Rect(outCorners[0], dst_sz); + + //The test checks the inner and outer borders of the Aruco markers. + //In the inner border of Aruco marker, all pixels should be black. + //In the outer border of Aruco marker, all pixels should be white. + + Mat markerImg = arucoZone(borderRect); + bool markerBorderIsBlack = borderPixelsHaveSameColor(markerImg, 0); + ASSERT_EQ(markerBorderIsBlack, true); + + Mat markerOuterBorder = markerImg; + markerOuterBorder.adjustROI(1, 1, 1, 1); + bool markerOuterBorderIsWhite = borderPixelsHaveSameColor(markerOuterBorder, 255); + ASSERT_EQ(markerOuterBorderIsWhite, true); + } } TEST(Charuco, testSeveralBoardsWithCustomIds)