diff --git a/modules/aruco/include/opencv2/aruco.hpp b/modules/aruco/include/opencv2/aruco.hpp index 45a3f6c24d5..aa8405de376 100644 --- a/modules/aruco/include/opencv2/aruco.hpp +++ b/modules/aruco/include/opencv2/aruco.hpp @@ -144,6 +144,8 @@ enum CornerRefineMethod{ * done at full resolution. * - aprilTagQuadSigma: What Gaussian blur should be applied to the segmented image (used for quad detection?) * Parameter is the standard deviation in pixels. Very noisy images benefit from non-zero values (e.g. 0.8). + * - detectInvertedMarker: to check if there is a white marker. In order to generate a "white" marker just + * invert a normal marker by using a tilde, ~markerImage. (default false) */ struct CV_EXPORTS_W DetectorParameters { @@ -183,6 +185,9 @@ struct CV_EXPORTS_W DetectorParameters { CV_PROP_RW float aprilTagMaxLineFitMse; CV_PROP_RW int aprilTagMinWhiteBlackDiff; CV_PROP_RW int aprilTagDeglitch; + + // to detect white (inverted) markers + CV_PROP_RW bool detectInvertedMarker; }; diff --git a/modules/aruco/src/aruco.cpp b/modules/aruco/src/aruco.cpp index 8022076cf94..a275ee7ccfd 100644 --- a/modules/aruco/src/aruco.cpp +++ b/modules/aruco/src/aruco.cpp @@ -86,7 +86,8 @@ DetectorParameters::DetectorParameters() aprilTagCriticalRad( (float)(10* CV_PI /180) ), aprilTagMaxLineFitMse(10.0), aprilTagMinWhiteBlackDiff(5), - aprilTagDeglitch(0){} + aprilTagDeglitch(0), + detectInvertedMarker(false){} /** @@ -512,7 +513,19 @@ static bool _identifyOneCandidate(const Ptr& dictionary, InputArray int(dictionary->markerSize * dictionary->markerSize * params->maxErroneousBitsInBorderRate); int borderErrors = _getBorderErrors(candidateBits, dictionary->markerSize, params->markerBorderBits); - if(borderErrors > maximumErrorsInBorder) return false; // border is wrong + + // check if it is a white marker + if(params->detectInvertedMarker){ + // to get from 255 to 1 + Mat invertedImg = ~candidateBits-254; + int invBError = _getBorderErrors(invertedImg, dictionary->markerSize, params->markerBorderBits); + // white marker + if(invBError maximumErrorsInBorder) return false; // take only inner bits Mat onlyBits = diff --git a/modules/aruco/test/test_arucodetection.cpp b/modules/aruco/test/test_arucodetection.cpp index d37fcc68d94..3d860dc05e9 100644 --- a/modules/aruco/test/test_arucodetection.cpp +++ b/modules/aruco/test/test_arucodetection.cpp @@ -182,12 +182,29 @@ static void getSyntheticRT(double yaw, double pitch, double distance, Mat &rvec, */ static Mat projectMarker(Ptr &dictionary, int id, Mat cameraMatrix, double yaw, double pitch, double distance, Size imageSize, int markerBorder, - vector< Point2f > &corners) { + vector< Point2f > &corners, int encloseMarker=0) { // canonical image - Mat markerImg; + Mat marker, markerImg; const int markerSizePixels = 100; - aruco::drawMarker(dictionary, id, markerSizePixels, markerImg, markerBorder); + + aruco::drawMarker(dictionary, id, markerSizePixels, marker, markerBorder); + marker.copyTo(markerImg); + + if(encloseMarker){ //to enclose the marker + int enclose = int(marker.rows/4); + markerImg = Mat::zeros(marker.rows+(2*enclose), marker.cols+(enclose*2), CV_8UC1); + + Mat field= markerImg.rowRange(int(enclose), int(markerImg.rows-enclose)) + .colRange(int(0), int(markerImg.cols)); + field.setTo(255); + field= markerImg.rowRange(int(0), int(markerImg.rows)) + .colRange(int(enclose), int(markerImg.cols-enclose)); + field.setTo(255); + + field = markerImg(Rect(enclose,enclose,marker.rows,marker.cols)); + marker.copyTo(field); + } // get rvec and tvec for the perspective Mat rvec, tvec; @@ -205,10 +222,10 @@ static Mat projectMarker(Ptr &dictionary, int id, Mat cameraM projectPoints(markerObjPoints, rvec, tvec, cameraMatrix, distCoeffs, corners); vector< Point2f > originalCorners; - originalCorners.push_back(Point2f(0, 0)); - originalCorners.push_back(Point2f((float)markerSizePixels, 0)); - originalCorners.push_back(Point2f((float)markerSizePixels, (float)markerSizePixels)); - originalCorners.push_back(Point2f(0, (float)markerSizePixels)); + originalCorners.push_back(Point2f(0+float(encloseMarker*markerSizePixels/4), 0+float(encloseMarker*markerSizePixels/4))); + originalCorners.push_back(originalCorners[0]+Point2f((float)markerSizePixels, 0)); + originalCorners.push_back(originalCorners[0]+Point2f((float)markerSizePixels, (float)markerSizePixels)); + originalCorners.push_back(originalCorners[0]+Point2f(0, (float)markerSizePixels)); Mat transformation = getPerspectiveTransform(originalCorners, corners); @@ -238,6 +255,11 @@ class CV_ArucoDetectionPerspective : public cvtest::BaseTest { public: CV_ArucoDetectionPerspective(); + enum checkWithParameter{ + USE_APRILTAG=1, /// Detect marker candidates :: using AprilTag + DETECT_INVERTED_MARKER, /// Check if there is a white marker + }; + protected: void run(int); }; @@ -246,9 +268,10 @@ class CV_ArucoDetectionPerspective : public cvtest::BaseTest { CV_ArucoDetectionPerspective::CV_ArucoDetectionPerspective() {} -void CV_ArucoDetectionPerspective::run(int aprilDecimate) { +void CV_ArucoDetectionPerspective::run(int tryWith) { int iter = 0; + int szEnclosed = 0; Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1); Size imgSize(500, 500); cameraMatrix.at< double >(0, 0) = cameraMatrix.at< double >(1, 1) = 650; @@ -257,29 +280,35 @@ void CV_ArucoDetectionPerspective::run(int aprilDecimate) { Ptr dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250); // detect from different positions - for(double distance = 0.1; distance <= 0.5; distance += 0.2) { - for(int pitch = 0; pitch < 360; pitch += 60) { - for(int yaw = 30; yaw <= 90; yaw += 50) { + for(double distance = 0.1; distance < 0.7; distance += 0.2) { + for(int pitch = 0; pitch < 360; pitch += (distance == 0.1? 60:180)) { + for(int yaw = 70; yaw <= 120; yaw += 40){ int currentId = iter % 250; int markerBorder = iter % 2 + 1; iter++; vector< Point2f > groundTruthCorners; - // create synthetic image - Mat img = - projectMarker(dictionary, currentId, cameraMatrix, deg2rad(yaw), deg2rad(pitch), - distance, imgSize, markerBorder, groundTruthCorners); - // detect markers - vector< vector< Point2f > > corners; - vector< int > ids; Ptr params = aruco::DetectorParameters::create(); params->minDistanceToBorder = 1; params->markerBorderBits = markerBorder; - if(aprilDecimate == 1){ - params->cornerRefinementMethod = cv::aruco::CORNER_REFINE_APRILTAG; + /// create synthetic image + Mat img= + projectMarker(dictionary, currentId, cameraMatrix, deg2rad(yaw), deg2rad(pitch), + distance, imgSize, markerBorder, groundTruthCorners, szEnclosed); + // marker :: Inverted + if(CV_ArucoDetectionPerspective::DETECT_INVERTED_MARKER == tryWith){ + img = ~img; + params->detectInvertedMarker = true; + } + + if(CV_ArucoDetectionPerspective::USE_APRILTAG == tryWith){ + params->cornerRefinementMethod = cv::aruco::CORNER_REFINE_APRILTAG; } + // detect markers + vector< vector< Point2f > > corners; + vector< int > ids; aruco::detectMarkers(img, dictionary, corners, ids, params); // check results @@ -293,14 +322,33 @@ void CV_ArucoDetectionPerspective::run(int aprilDecimate) { } for(int c = 0; c < 4; c++) { double dist = cv::norm(groundTruthCorners[c] - corners[0][c]); // TODO cvtest - if(dist > 5) { - ts->printf(cvtest::TS::LOG, "Incorrect marker corners position"); - ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); - return; + if(CV_ArucoDetectionPerspective::DETECT_INVERTED_MARKER == tryWith){ + if(szEnclosed && dist > 3){ + ts->printf(cvtest::TS::LOG, "Incorrect marker corners position"); + ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); + return; + } + if(!szEnclosed && dist >15){ + ts->printf(cvtest::TS::LOG, "Incorrect marker corners position"); + ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); + return; + } + } + else{ + if(dist > 5) { + ts->printf(cvtest::TS::LOG, "Incorrect marker corners position"); + ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); + return; + } } } } } + // change the state :: to detect an enclosed inverted marker + if( CV_ArucoDetectionPerspective::DETECT_INVERTED_MARKER == tryWith && distance == 0.1 ){ + distance -= 0.1; + szEnclosed++; + } } } @@ -488,10 +536,16 @@ void CV_ArucoBitCorrection::run(int) { } typedef CV_ArucoDetectionPerspective CV_AprilTagDetectionPerspective; +typedef CV_ArucoDetectionPerspective CV_InvertedArucoDetectionPerspective; + +TEST(CV_InvertedArucoDetectionPerspective, algorithmic) { + CV_InvertedArucoDetectionPerspective test; + test.safe_run(CV_ArucoDetectionPerspective::DETECT_INVERTED_MARKER); +} TEST(CV_AprilTagDetectionPerspective, algorithmic) { CV_AprilTagDetectionPerspective test; - test.safe_run(1); + test.safe_run(CV_ArucoDetectionPerspective::USE_APRILTAG); } TEST(CV_ArucoDetectionSimple, algorithmic) {