diff --git a/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp b/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp index f6ad677099..15170b4d2e 100644 --- a/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp +++ b/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp @@ -13,7 +13,7 @@ namespace aruco { //! @{ struct CV_EXPORTS_W_SIMPLE CharucoParameters { - CharucoParameters() { + CV_WRAP CharucoParameters() { minMarkers = 2; tryRefineMarkers = false; } diff --git a/modules/objdetect/misc/python/test/test_objdetect_aruco.py b/modules/objdetect/misc/python/test/test_objdetect_aruco.py index c72691d003..23234603d8 100644 --- a/modules/objdetect/misc/python/test/test_objdetect_aruco.py +++ b/modules/objdetect/misc/python/test/test_objdetect_aruco.py @@ -4,11 +4,96 @@ from __future__ import print_function import os, tempfile, numpy as np +from math import pi import cv2 as cv from tests_common import NewOpenCVTests +def getSyntheticRT(yaw, pitch, distance): + rvec = np.zeros((3, 1), np.float64) + tvec = np.zeros((3, 1), np.float64) + + rotPitch = np.array([[-pitch], [0], [0]]) + rotYaw = np.array([[0], [yaw], [0]]) + + rvec, tvec = cv.composeRT(rotPitch, np.zeros((3, 1), np.float64), + rotYaw, np.zeros((3, 1), np.float64))[:2] + + tvec = np.array([[0], [0], [distance]]) + return rvec, tvec + +# see test_aruco_utils.cpp +def projectMarker(img, board, markerIndex, cameraMatrix, rvec, tvec, markerBorder): + markerSizePixels = 100 + markerImg = cv.aruco.generateImageMarker(board.getDictionary(), board.getIds()[markerIndex], markerSizePixels, borderBits=markerBorder) + + distCoeffs = np.zeros((5, 1), np.float64) + maxCoord = board.getRightBottomCorner() + objPoints = board.getObjPoints()[markerIndex] + for i in range(len(objPoints)): + objPoints[i][0] -= maxCoord[0] / 2 + objPoints[i][1] -= maxCoord[1] / 2 + objPoints[i][2] -= maxCoord[2] / 2 + + corners, _ = cv.projectPoints(objPoints, rvec, tvec, cameraMatrix, distCoeffs) + + originalCorners = np.array([ + [0, 0], + [markerSizePixels, 0], + [markerSizePixels, markerSizePixels], + [0, markerSizePixels], + ], np.float32) + + transformation = cv.getPerspectiveTransform(originalCorners, corners) + + borderValue = 127 + aux = cv.warpPerspective(markerImg, transformation, img.shape, None, cv.INTER_NEAREST, cv.BORDER_CONSTANT, borderValue) + + assert(img.shape == aux.shape) + mask = (aux == borderValue).astype(np.uint8) + img = img * mask + aux * (1 - mask) + return img + +def projectChessboard(squaresX, squaresY, squareSize, imageSize, cameraMatrix, rvec, tvec): + img = np.ones(imageSize, np.uint8) * 255 + distCoeffs = np.zeros((5, 1), np.float64) + for y in range(squaresY): + startY = y * squareSize + for x in range(squaresX): + if (y % 2 != x % 2): + continue + startX = x * squareSize + + squareCorners = np.array([[startX - squaresX*squareSize/2, + startY - squaresY*squareSize/2, + 0]], np.float32) + squareCorners = np.stack((squareCorners[0], + squareCorners[0] + [squareSize, 0, 0], + squareCorners[0] + [squareSize, squareSize, 0], + squareCorners[0] + [0, squareSize, 0])) + + projectedCorners, _ = cv.projectPoints(squareCorners, rvec, tvec, cameraMatrix, distCoeffs) + projectedCorners = projectedCorners.astype(np.int64) + projectedCorners = projectedCorners.reshape(1, 4, 2) + img = cv.fillPoly(img, [projectedCorners], 0) + + return img + +def projectCharucoBoard(board, cameraMatrix, yaw, pitch, distance, imageSize, markerBorder): + rvec, tvec = getSyntheticRT(yaw, pitch, distance) + + img = np.ones(imageSize, np.uint8) * 255 + for indexMarker in range(len(board.getIds())): + img = projectMarker(img, board, indexMarker, cameraMatrix, rvec, tvec, markerBorder) + + chessboard = projectChessboard(board.getChessboardSize()[0], board.getChessboardSize()[1], + board.getSquareLength(), imageSize, cameraMatrix, rvec, tvec) + + chessboard = (chessboard != 0).astype(np.uint8) + img = img * chessboard + return img, rvec, tvec + class aruco_objdetect_test(NewOpenCVTests): def test_board(self): @@ -153,5 +238,80 @@ class aruco_objdetect_test(NewOpenCVTests): self.assertEqual(charucoIds[i], i) np.testing.assert_allclose(gold_corners, charucoCorners.reshape(-1, 2), 0.01, 0.1) + # check no segfault when cameraMatrix or distCoeffs are not initialized + def test_charuco_no_segfault_params(self): + dictionary = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_1000) + board = cv.aruco.CharucoBoard((10, 10), 0.019, 0.015, dictionary) + charuco_parameters = cv.aruco.CharucoParameters() + detector = cv.aruco.CharucoDetector(board) + detector.setCharucoParameters(charuco_parameters) + + self.assertIsNone(detector.getCharucoParameters().cameraMatrix) + self.assertIsNone(detector.getCharucoParameters().distCoeffs) + + def test_charuco_no_segfault_params_constructor(self): + dictionary = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_1000) + board = cv.aruco.CharucoBoard((10, 10), 0.019, 0.015, dictionary) + charuco_parameters = cv.aruco.CharucoParameters() + detector = cv.aruco.CharucoDetector(board, charucoParams=charuco_parameters) + + self.assertIsNone(detector.getCharucoParameters().cameraMatrix) + self.assertIsNone(detector.getCharucoParameters().distCoeffs) + + # similar to C++ test CV_CharucoDetection.accuracy + def test_charuco_detector_accuracy(self): + iteration = 0 + cameraMatrix = np.eye(3, 3, dtype=np.float64) + imgSize = (500, 500) + params = cv.aruco.DetectorParameters() + params.minDistanceToBorder = 3 + + board = cv.aruco.CharucoBoard((4, 4), 0.03, 0.015, cv.aruco.getPredefinedDictionary(cv.aruco.DICT_6X6_250)) + detector = cv.aruco.CharucoDetector(board, detectorParams=params) + + cameraMatrix[0, 0] = cameraMatrix[1, 1] = 600 + cameraMatrix[0, 2] = imgSize[0] / 2 + cameraMatrix[1, 2] = imgSize[1] / 2 + + # for different perspectives + distCoeffs = np.zeros((5, 1), dtype=np.float64) + for distance in [0.2, 0.4]: + for yaw in range(-55, 51, 25): + for pitch in range(-55, 51, 25): + markerBorder = iteration % 2 + 1 + iteration += 1 + + # create synthetic image + img, rvec, tvec = projectCharucoBoard(board, cameraMatrix, yaw * pi / 180, pitch * pi / 180, distance, imgSize, markerBorder) + + params.markerBorderBits = markerBorder + detector.setDetectorParameters(params) + + if (iteration % 2 != 0): + charucoParameters = cv.aruco.CharucoParameters() + charucoParameters.cameraMatrix = cameraMatrix + charucoParameters.distCoeffs = distCoeffs + detector.setCharucoParameters(charucoParameters) + + charucoCorners, charucoIds, corners, ids = detector.detectBoard(img) + + self.assertGreater(len(ids), 0) + + copyChessboardCorners = board.getChessboardCorners() + copyChessboardCorners -= np.array(board.getRightBottomCorner()) / 2 + + projectedCharucoCorners, _ = cv.projectPoints(copyChessboardCorners, rvec, tvec, cameraMatrix, distCoeffs) + + if charucoIds is None: + self.assertEqual(iteration, 46) + continue + + for i in range(len(charucoIds)): + currentId = charucoIds[i] + self.assertLess(currentId, len(board.getChessboardCorners())) + + reprErr = cv.norm(charucoCorners[i] - projectedCharucoCorners[currentId]) + self.assertLessEqual(reprErr, 5) + if __name__ == '__main__': NewOpenCVTests.bootstrap()