diff --git a/modules/objdetect/src/aruco/aruco_board.cpp b/modules/objdetect/src/aruco/aruco_board.cpp index 3d4217e02a..f8d3d3c108 100644 --- a/modules/objdetect/src/aruco/aruco_board.cpp +++ b/modules/objdetect/src/aruco/aruco_board.cpp @@ -483,39 +483,44 @@ void CharucoBoardImpl::generateImage(Size outSize, OutputArray img, int marginSi Mat noMarginsImg = out.colRange(marginSize, out.cols - marginSize).rowRange(marginSize, out.rows - marginSize); - double totalLengthX, totalLengthY; - totalLengthX = squareLength * size.width; - totalLengthY = squareLength * size.height; - - // proportional transformation - double xReduction = totalLengthX / double(noMarginsImg.cols); - double yReduction = totalLengthY / double(noMarginsImg.rows); + // the size of the chessboard square depends on the location of the chessboard + float pixInSquare = 0.f; + // the size of the chessboard in pixels + Size pixInChessboard(noMarginsImg.cols, noMarginsImg.rows); // determine the zone where the chessboard is placed - Mat chessboardZoneImg; - if(xReduction > yReduction) { - int nRows = int(totalLengthY / xReduction); - int rowsMargins = (noMarginsImg.rows - nRows) / 2; - chessboardZoneImg = noMarginsImg.rowRange(rowsMargins, noMarginsImg.rows - rowsMargins); - } else { - int nCols = int(totalLengthX / yReduction); - int colsMargins = (noMarginsImg.cols - nCols) / 2; - chessboardZoneImg = noMarginsImg.colRange(colsMargins, noMarginsImg.cols - colsMargins); + float pixInSquareX = (float)noMarginsImg.cols / (float)size.width; + float pixInSquareY = (float)noMarginsImg.rows / (float)size.height; + Point startChessboard(0, 0); + if (pixInSquareX <= pixInSquareY) { + // the width of "noMarginsImg" image determines the dimensions of the chessboard + pixInSquare = pixInSquareX; + pixInChessboard.height = cvRound(pixInSquare*size.height); + int rowsMargin = (noMarginsImg.rows - pixInChessboard.height) / 2; + startChessboard.y = rowsMargin; } + else { + // the height of "noMarginsImg" image determines the dimensions of the chessboard + pixInSquare = pixInSquareY; + pixInChessboard.width = cvRound(pixInSquare*size.width); + int colsMargin = (noMarginsImg.cols - pixInChessboard.width) / 2; + startChessboard.x = colsMargin; + } + // determine the zone where the chessboard is located + Mat chessboardZoneImg = noMarginsImg(Rect(startChessboard, pixInChessboard)); - // determine the margins to draw only the markers - // take the minimum just to be sure - double squareSizePixels = min(double(chessboardZoneImg.cols) / double(size.width), - double(chessboardZoneImg.rows) / double(size.height)); + // 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); - double diffSquareMarkerLength = (squareLength - markerLength) / 2; - int diffSquareMarkerLengthPixels = - int(diffSquareMarkerLength * squareSizePixels / squareLength); + // determine the zone where the aruco markers are located + int endArucoX = cvRound(pixInSquare*(size.width-1)+pixInMarginMarker+pixInMarker); + int endArucoY = cvRound(pixInSquare*(size.height-1)+pixInMarginMarker+pixInMarker); + Mat arucoZone = chessboardZoneImg(Range(cvRound(pixInMarginMarker), endArucoY), Range(cvRound(pixInMarginMarker), endArucoX)); // draw markers - Mat markersImg; - Board::Impl::generateImage(chessboardZoneImg.size(), markersImg, diffSquareMarkerLengthPixels, borderBits); - markersImg.copyTo(chessboardZoneImg); + Board::Impl::generateImage(arucoZone.size(), arucoZone, 0, borderBits); // now draw black squares for(int y = 0; y < size.height; y++) { @@ -527,12 +532,11 @@ void CharucoBoardImpl::generateImage(Size outSize, OutputArray img, int marginSi if(y % 2 != x % 2) continue; // white corner, dont do anything } - double startX, startY; - startX = squareSizePixels * double(x); - startY = squareSizePixels * double(y); + float startX = pixInSquare * float(x); + float startY = pixInSquare * float(y); - Mat squareZone = chessboardZoneImg.rowRange(int(startY), int(startY + squareSizePixels)) - .colRange(int(startX), int(startX + squareSizePixels)); + Mat squareZone = chessboardZoneImg(Range(cvRound(startY), cvRound(startY + pixInSquare)), + Range(cvRound(startX), cvRound(startX + pixInSquare))); squareZone.setTo(0); } diff --git a/modules/objdetect/test/test_charucodetection.cpp b/modules/objdetect/test/test_charucodetection.cpp index 87520c873e..20a7036b40 100644 --- a/modules/objdetect/test/test_charucodetection.cpp +++ b/modules/objdetect/test/test_charucodetection.cpp @@ -769,6 +769,57 @@ TEST_P(CharucoBoard, testWrongSizeDetection) ASSERT_TRUE(detectedCharucoIds.empty()); } +TEST(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); + const aruco::CharucoBoard board(boardSize, squareLength, markerLength, dict); + const int marginSize = 24; + Mat boardImg; + + // generate chessboard image + board.generateImage(Size(400, 300), 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); + + // prepare data for chessboard image test + Mat noMarginsImg = boardImg(Range(marginSize, boardImg.rows - marginSize), + Range(marginSize, boardImg.cols - marginSize)); + const float pixInSquare = (float)(noMarginsImg.cols) / (float)boardSize.width; + + Size pixInChessboard(cvRound(pixInSquare*boardSize.width), cvRound(pixInSquare*boardSize.height)); + const Point startChessboard((noMarginsImg.cols - pixInChessboard.width) / 2, + (noMarginsImg.rows - pixInChessboard.height) / 2); + Mat chessboardZoneImg = noMarginsImg(Rect(startChessboard, pixInChessboard)); + + // B - black pixel, W - white pixel + // chessboard corner 1: + // B W + // W B + Mat goldCorner1 = (Mat_(2, 2) << + 0, 255, + 255, 0); + // B - black pixel, W - white pixel + // chessboard corner 2: + // W B + // B W + Mat goldCorner2 = (Mat_(2, 2) << + 255, 0, + 0, 255); + + // test chessboard corners in generated image + for (const Point3f& p: board.getChessboardCorners()) { + Point2f chessCorner(pixInSquare*(p.x/squareLength), + pixInSquare*(p.y/squareLength)); + Mat winCorner = chessboardZoneImg(Rect(Point(cvRound(chessCorner.x) - 1, cvRound(chessCorner.y) - 1), Size(2, 2))); + 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 +} + TEST(Charuco, testSeveralBoardsWithCustomIds) { Size res{500, 500};