From 1276bb86cbffd8b6eeceeba69bbc4c5ecfc79ee7 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Fri, 1 Apr 2022 09:50:37 +0200 Subject: [PATCH 1/4] Remove unused nrot variable. This is to quiet the -Wunused-but-set-parameter clang diagnostic warning. --- modules/ximgproc/src/edge_drawing.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/modules/ximgproc/src/edge_drawing.cpp b/modules/ximgproc/src/edge_drawing.cpp index ff2e0fb3b..675738643 100644 --- a/modules/ximgproc/src/edge_drawing.cpp +++ b/modules/ximgproc/src/edge_drawing.cpp @@ -229,7 +229,7 @@ private: static void DeallocateMatrix(double** m, int noRows); static void AperB_T(double** A_, double** B_, double** _res, int _righA, int _colA, int _righB, int _colB); static void AperB(double** A_, double** B_, double** _res, int _righA, int _colA, int _righB, int _colB); - static void jacobi(double** a, int n, double d[], double** v, int nrot); + static void jacobi(double** a, int n, double d[], double** v); static void ROTATE(double** a, int i, int j, int k, int l, double tau, double s); static double computeEllipsePerimeter(EllipseEquation* eq); static double ComputeEllipseError(EllipseEquation* eq, double* px, double* py, int noPoints); @@ -5340,7 +5340,6 @@ bool EdgeDrawingImpl::EllipseFit(double* x, double* y, int noPoints, EllipseEqua double** V = AllocateMatrix(7, 7); double** sol = AllocateMatrix(7, 7); double tx, ty; - int nrot = 0; memset(d, 0, sizeof(double) * 7); @@ -5384,7 +5383,7 @@ bool EdgeDrawingImpl::EllipseFit(double* x, double* y, int noPoints, EllipseEqua AperB_T(Const, invL, temp, 6, 6, 6, 6); AperB(invL, temp, C, 6, 6, 6, 6); - jacobi(C, 6, d, V, nrot); + jacobi(C, 6, d, V); A_TperB(invL, V, sol, 6, 6, 6, 6); @@ -5634,7 +5633,7 @@ void EdgeDrawingImpl::AperB(double** A_, double** B_, double** _res, int _righA, } } -void EdgeDrawingImpl::jacobi(double** a, int n, double d[], double** v, int nrot) +void EdgeDrawingImpl::jacobi(double** a, int n, double d[], double** v) { int j, iq, ip, i; double tresh, theta, tau, t, sm, s, h, g, c; @@ -5655,7 +5654,6 @@ void EdgeDrawingImpl::jacobi(double** a, int n, double d[], double** v, int nrot b[ip] = d[ip] = a[ip][ip]; z[ip] = 0.0; } - nrot = 0; for (i = 1; i <= 50; i++) { sm = 0.0; @@ -5719,7 +5717,6 @@ void EdgeDrawingImpl::jacobi(double** a, int n, double d[], double** v, int nrot { ROTATE(v, j, ip, j, iq, tau, s); } - ++nrot; } } } From 0596c05087d7f3b40d0183b6b10db6eb9fdb2d31 Mon Sep 17 00:00:00 2001 From: Matti Jukola Date: Thu, 14 Apr 2022 13:42:21 +0200 Subject: [PATCH 2/4] Merge pull request #3220 from buq2:aruco-apriltag-infinite-loop-fix Fix infinite loop on ArUco apriltag refinement * Fix infinite loop on ArUco apriltag refinement Software entered infinite loop when image height was smaller than 10*cv::getNumThreads(). With high core count machines this could happen with very reasonable image sizes. Fix is to ensure that chunksize is at least 1. * Test aruco detection with different number of threads Test ensures that different aruco detection methods do not produce different results based on number of threads. Test was created after observing infinite loop caused by small image and large number of threads when using apriltag corner refinement. * Test refactoring. * Syntax fix for pre-C++11 compilers. Co-authored-by: Alexander Smorkalov --- modules/aruco/src/apriltag_quad_thresh.cpp | 2 +- modules/aruco/test/test_arucodetection.cpp | 87 ++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/modules/aruco/src/apriltag_quad_thresh.cpp b/modules/aruco/src/apriltag_quad_thresh.cpp index 296309267..20c193725 100644 --- a/modules/aruco/src/apriltag_quad_thresh.cpp +++ b/modules/aruco/src/apriltag_quad_thresh.cpp @@ -1523,7 +1523,7 @@ out = Mat::zeros(h, w, CV_8UC3); zarray_t *quads = _zarray_create(sizeof(struct sQuad)); //int chunksize = 1 + sz / (APRILTAG_TASKS_PER_THREAD_TARGET * numberOfThreads); - int chunksize = h / (10 * getNumThreads()); + int chunksize = std::max(1, h / (10 * getNumThreads())); int sz = _zarray_size(clusters); // TODO PARALLELIZE diff --git a/modules/aruco/test/test_arucodetection.cpp b/modules/aruco/test/test_arucodetection.cpp index b2a82f4a1..f1e77f7aa 100644 --- a/modules/aruco/test/test_arucodetection.cpp +++ b/modules/aruco/test/test_arucodetection.cpp @@ -717,4 +717,91 @@ TEST(CV_ArucoDetectMarkers, regression_2492) } } +struct ArucoThreading: public testing::TestWithParam +{ + struct NumThreadsSetter { + NumThreadsSetter(const int num_threads) + : original_num_threads_(cv::getNumThreads()) { + cv::setNumThreads(num_threads); + } + + ~NumThreadsSetter() { + cv::setNumThreads(original_num_threads_); + } + private: + int original_num_threads_; + }; +}; + +TEST_P(ArucoThreading, number_of_threads_does_not_change_results) +{ + cv::Ptr params = cv::aruco::DetectorParameters::create(); + // We are not testing against different dictionaries + // As we are interested mostly in small images, smaller + // markers is better -> 4x4 + cv::Ptr dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_4X4_50); + + // Height of the test image can be chosen quite freely + // We aim to test against small images as in those the + // number of threads has most effect + const int height_img = 20; + // Just to get nice white boarder + const int shift = height_img > 10 ? 5 : 1; + const int height_marker = height_img-2*shift; + + // Create a test image + cv::Mat img_marker; + cv::aruco::drawMarker(dictionary, 23, height_marker, img_marker, 1); + + // Copy to bigger image to get a white border + cv::Mat img(height_img, height_img, CV_8UC1, cv::Scalar(255)); + img_marker.copyTo(img(cv::Rect(shift, shift, height_marker, height_marker))); + + params->cornerRefinementMethod = GetParam(); + + std::vector > original_corners; + std::vector original_ids; + { + NumThreadsSetter thread_num_setter(1); + cv::aruco::detectMarkers(img, dictionary, original_corners, original_ids, params); + } + + ASSERT_EQ(original_ids.size(), 1); + ASSERT_EQ(original_corners.size(), 1); + + int num_threads_to_test[] = { 2, 8, 16, 32, height_img-1, height_img, height_img+1}; + + for (size_t i_num_threads = 0; i_num_threads < sizeof(num_threads_to_test)/sizeof(int); ++i_num_threads) { + NumThreadsSetter thread_num_setter(num_threads_to_test[i_num_threads]); + + std::vector > corners; + std::vector ids; + cv::aruco::detectMarkers(img, dictionary, corners, ids, params); + + // If we don't find any markers, the test is broken + ASSERT_EQ(ids.size(), 1); + + // Make sure we got the same result as the first time + ASSERT_EQ(corners.size(), original_corners.size()); + ASSERT_EQ(ids.size(), original_ids.size()); + ASSERT_EQ(ids.size(), corners.size()); + for (size_t i = 0; i < corners.size(); ++i) { + EXPECT_EQ(ids[i], original_ids[i]); + for (size_t j = 0; j < corners[i].size(); ++j) { + EXPECT_NEAR(corners[i][j].x, original_corners[i][j].x, 0.1f); + EXPECT_NEAR(corners[i][j].y, original_corners[i][j].y, 0.1f); + } + } + } +} + +INSTANTIATE_TEST_CASE_P( + CV_ArucoDetectMarkers, ArucoThreading, + ::testing::Values( + cv::aruco::CORNER_REFINE_NONE, + cv::aruco::CORNER_REFINE_SUBPIX, + cv::aruco::CORNER_REFINE_CONTOUR, + cv::aruco::CORNER_REFINE_APRILTAG + )); + }} // namespace From 76cd35ec1ab01821677484a58ac0e64dc440b331 Mon Sep 17 00:00:00 2001 From: Alexander Panov Date: Thu, 14 Apr 2022 14:43:21 +0300 Subject: [PATCH 3/4] Merge pull request #3200 from AleksandrPanov:aruco_improvements Aruco improvements * add writeDictionary(), dict distance, fix readDictionary(), readDetectorParameters() * add aruco_dict_utils.cpp * add py test_write_read_dict * update tutorial --- modules/aruco/include/opencv2/aruco.hpp | 2 +- .../include/opencv2/aruco/dictionary.hpp | 51 ++-- modules/aruco/misc/python/test/test_aruco.py | 29 ++ modules/aruco/samples/aruco_dict_utils.cpp | 267 ++++++++++++++++++ modules/aruco/samples/calibrate_camera.cpp | 6 +- .../samples/calibrate_camera_charuco.cpp | 6 +- modules/aruco/samples/create_board.cpp | 4 +- .../aruco/samples/create_board_charuco.cpp | 4 +- modules/aruco/samples/create_marker.cpp | 4 +- modules/aruco/samples/detect_board.cpp | 6 +- .../aruco/samples/detect_board_charuco.cpp | 6 +- modules/aruco/samples/detect_diamonds.cpp | 6 +- modules/aruco/samples/detect_markers.cpp | 6 +- modules/aruco/src/aruco.cpp | 43 ++- modules/aruco/src/dictionary.cpp | 32 ++- modules/aruco/test/test_arucodetection.cpp | 4 +- modules/aruco/test/test_charucodetection.cpp | 8 +- .../tutorials/aruco_faq/aruco_faq.markdown | 16 +- 18 files changed, 415 insertions(+), 85 deletions(-) create mode 100644 modules/aruco/samples/aruco_dict_utils.cpp diff --git a/modules/aruco/include/opencv2/aruco.hpp b/modules/aruco/include/opencv2/aruco.hpp index 99d31794b..91d57cef2 100644 --- a/modules/aruco/include/opencv2/aruco.hpp +++ b/modules/aruco/include/opencv2/aruco.hpp @@ -151,7 +151,7 @@ struct CV_EXPORTS_W DetectorParameters { DetectorParameters(); CV_WRAP static Ptr create(); - CV_WRAP static bool readDetectorParameters(const FileNode& fn, Ptr& params); + CV_WRAP bool readDetectorParameters(const FileNode& fn); CV_PROP_RW int adaptiveThreshWinSizeMin; CV_PROP_RW int adaptiveThreshWinSizeMax; diff --git a/modules/aruco/include/opencv2/aruco/dictionary.hpp b/modules/aruco/include/opencv2/aruco/dictionary.hpp index 27374d8f5..40174162e 100644 --- a/modules/aruco/include/opencv2/aruco/dictionary.hpp +++ b/modules/aruco/include/opencv2/aruco/dictionary.hpp @@ -94,15 +94,20 @@ class CV_EXPORTS_W Dictionary { const Ptr &baseDictionary, int randomSeed=0); /** - * @brief Read a new dictionary from FileNode. Format: - * nmarkers: 35 - * markersize: 6 - * marker_0: "101011111011111001001001101100000000" - * ... + * @brief Read a new dictionary from FileNode. Format:\n + * nmarkers: 35\n + * markersize: 6\n + * maxCorrectionBits: 5\n + * marker_0: "101011111011111001001001101100000000"\n + * ...\n * marker_34: "011111010000111011111110110101100101" */ - CV_WRAP static bool readDictionary(const cv::FileNode& fn, cv::Ptr &dictionary); + CV_WRAP bool readDictionary(const cv::FileNode& fn); + /** + * @brief Write a dictionary to FileStorage. Format is the same as in readDictionary(). + */ + CV_WRAP void writeDictionary(Ptr& fs); /** * @see getPredefinedDictionary */ @@ -149,23 +154,23 @@ class CV_EXPORTS_W Dictionary { distance */ enum PREDEFINED_DICTIONARY_NAME { - DICT_4X4_50 = 0, - DICT_4X4_100, - DICT_4X4_250, - DICT_4X4_1000, - DICT_5X5_50, - DICT_5X5_100, - DICT_5X5_250, - DICT_5X5_1000, - DICT_6X6_50, - DICT_6X6_100, - DICT_6X6_250, - DICT_6X6_1000, - DICT_7X7_50, - DICT_7X7_100, - DICT_7X7_250, - DICT_7X7_1000, - DICT_ARUCO_ORIGINAL, + DICT_4X4_50 = 0, ///< 4x4 bits, minimum hamming distance between any two codes = 4, 50 codes + DICT_4X4_100, ///< 4x4 bits, minimum hamming distance between any two codes = 3, 100 codes + DICT_4X4_250, ///< 4x4 bits, minimum hamming distance between any two codes = 3, 250 codes + DICT_4X4_1000, ///< 4x4 bits, minimum hamming distance between any two codes = 2, 1000 codes + DICT_5X5_50, ///< 5x5 bits, minimum hamming distance between any two codes = 8, 50 codes + DICT_5X5_100, ///< 5x5 bits, minimum hamming distance between any two codes = 7, 100 codes + DICT_5X5_250, ///< 5x5 bits, minimum hamming distance between any two codes = 6, 250 codes + DICT_5X5_1000, ///< 5x5 bits, minimum hamming distance between any two codes = 5, 1000 codes + DICT_6X6_50, ///< 6x6 bits, minimum hamming distance between any two codes = 13, 50 codes + DICT_6X6_100, ///< 6x6 bits, minimum hamming distance between any two codes = 12, 100 codes + DICT_6X6_250, ///< 6x6 bits, minimum hamming distance between any two codes = 11, 250 codes + DICT_6X6_1000, ///< 6x6 bits, minimum hamming distance between any two codes = 9, 1000 codes + DICT_7X7_50, ///< 7x7 bits, minimum hamming distance between any two codes = 19, 50 codes + DICT_7X7_100, ///< 7x7 bits, minimum hamming distance between any two codes = 18, 100 codes + DICT_7X7_250, ///< 7x7 bits, minimum hamming distance between any two codes = 17, 250 codes + DICT_7X7_1000, ///< 7x7 bits, minimum hamming distance between any two codes = 14, 1000 codes + DICT_ARUCO_ORIGINAL, ///< 6x6 bits, minimum hamming distance between any two codes = 3, 1024 codes DICT_APRILTAG_16h5, ///< 4x4 bits, minimum hamming distance between any two codes = 5, 30 codes DICT_APRILTAG_25h9, ///< 5x5 bits, minimum hamming distance between any two codes = 9, 35 codes DICT_APRILTAG_36h10, ///< 6x6 bits, minimum hamming distance between any two codes = 10, 2320 codes diff --git a/modules/aruco/misc/python/test/test_aruco.py b/modules/aruco/misc/python/test/test_aruco.py index 836d5ce08..f91693511 100644 --- a/modules/aruco/misc/python/test/test_aruco.py +++ b/modules/aruco/misc/python/test/test_aruco.py @@ -30,5 +30,34 @@ class aruco_test(NewOpenCVTests): with self.assertRaises(cv.error): board.setIds(np.array([0])) + def test_write_read_dict(self): + + try: + aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_5X5_50) + markers_gold = aruco_dict.bytesList + + # write aruco_dict + filename = "test_dict.yml" + fs_write = cv.FileStorage(filename, cv.FileStorage_WRITE) + aruco_dict.writeDictionary(fs_write) + fs_write.release() + + # reset aruco_dict + aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_6X6_250) + + # read aruco_dict + fs_read = cv.FileStorage(filename, cv.FileStorage_READ) + aruco_dict.readDictionary(fs_read.root()) + fs_read.release() + + # check equal + self.assertEqual(aruco_dict.markerSize, 5) + self.assertEqual(aruco_dict.maxCorrectionBits, 3) + np.testing.assert_array_equal(aruco_dict.bytesList, markers_gold) + + finally: + if os.path.exists(filename): + os.remove(filename) + if __name__ == '__main__': NewOpenCVTests.bootstrap() diff --git a/modules/aruco/samples/aruco_dict_utils.cpp b/modules/aruco/samples/aruco_dict_utils.cpp new file mode 100644 index 000000000..465513b34 --- /dev/null +++ b/modules/aruco/samples/aruco_dict_utils.cpp @@ -0,0 +1,267 @@ +#include +#include +#include + +using namespace cv; +using namespace std; + +static inline int _getSelfDistance(const Mat &marker) { + Mat bytes = aruco::Dictionary::getByteListFromBits(marker); + int minHamming = (int)marker.total() + 1; + for(int r = 1; r < 4; r++) { + int currentHamming = cv::hal::normHamming(bytes.ptr(), bytes.ptr() + bytes.cols*r, bytes.cols); + if(currentHamming < minHamming) minHamming = currentHamming; + } + Mat b; + flip(marker, b, 0); + Mat flipBytes = aruco::Dictionary::getByteListFromBits(b); + for(int r = 0; r < 4; r++) { + int currentHamming = cv::hal::normHamming(flipBytes.ptr(), bytes.ptr() + bytes.cols*r, bytes.cols); + if(currentHamming < minHamming) minHamming = currentHamming; + } + flip(marker, b, 1); + flipBytes = aruco::Dictionary::getByteListFromBits(b); + for(int r = 0; r < 4; r++) { + int currentHamming = cv::hal::normHamming(flipBytes.ptr(), bytes.ptr() + bytes.cols*r, bytes.cols); + if(currentHamming < minHamming) minHamming = currentHamming; + } + return minHamming; +} + +static inline int getFlipDistanceToId(Ptr dict, InputArray bits, int id, bool allRotations = true) { + Mat bytesList = dict->bytesList; + CV_Assert(id >= 0 && id < bytesList.rows); + + unsigned int nRotations = 4; + if(!allRotations) nRotations = 1; + + Mat candidateBytes = aruco::Dictionary::getByteListFromBits(bits.getMat()); + int currentMinDistance = int(bits.total() * bits.total()); + for(unsigned int r = 0; r < nRotations; r++) { + int currentHamming = cv::hal::normHamming( + bytesList.ptr(id) + r*candidateBytes.cols, + candidateBytes.ptr(), + candidateBytes.cols); + + if(currentHamming < currentMinDistance) { + currentMinDistance = currentHamming; + } + } + Mat b; + flip(bits.getMat(), b, 0); + candidateBytes = aruco::Dictionary::getByteListFromBits(b); + for(unsigned int r = 0; r < nRotations; r++) { + int currentHamming = cv::hal::normHamming( + bytesList.ptr(id) + r * candidateBytes.cols, + candidateBytes.ptr(), + candidateBytes.cols); + if (currentHamming < currentMinDistance) { + currentMinDistance = currentHamming; + } + } + + flip(bits.getMat(), b, 1); + candidateBytes = aruco::Dictionary::getByteListFromBits(b); + for(unsigned int r = 0; r < nRotations; r++) { + int currentHamming = cv::hal::normHamming( + bytesList.ptr(id) + r * candidateBytes.cols, + candidateBytes.ptr(), + candidateBytes.cols); + if (currentHamming < currentMinDistance) { + currentMinDistance = currentHamming; + } + } + return currentMinDistance; +} + +static inline Ptr generateCustomAsymmetricDictionary(int nMarkers, int markerSize, + const Ptr &baseDictionary, int randomSeed) { + RNG rng((uint64)(randomSeed)); + + Ptr out = makePtr(); + out->markerSize = markerSize; + + // theoretical maximum intermarker distance + // See S. Garrido-Jurado, R. Muñoz-Salinas, F. J. Madrid-Cuevas, and M. J. Marín-Jiménez. 2014. + // "Automatic generation and detection of highly reliable fiducial markers under occlusion". + // Pattern Recogn. 47, 6 (June 2014), 2280-2292. DOI=10.1016/j.patcog.2014.01.005 + int C = (int)std::floor(float(markerSize * markerSize) / 4.f); + int tau = 2 * (int)std::floor(float(C) * 4.f / 3.f); + + // if baseDictionary is provided, calculate its intermarker distance + if(baseDictionary->bytesList.rows > 0) { + CV_Assert(baseDictionary->markerSize == markerSize); + out->bytesList = baseDictionary->bytesList.clone(); + + int minDistance = markerSize * markerSize + 1; + for(int i = 0; i < out->bytesList.rows; i++) { + Mat markerBytes = out->bytesList.rowRange(i, i + 1); + Mat markerBits = aruco::Dictionary::getBitsFromByteList(markerBytes, markerSize); + minDistance = min(minDistance, _getSelfDistance(markerBits)); + for(int j = i + 1; j < out->bytesList.rows; j++) { + minDistance = min(minDistance, getFlipDistanceToId(out, markerBits, j)); + } + } + tau = minDistance; + } + + // current best option + int bestTau = 0; + Mat bestMarker; + + // after these number of unproductive iterations, the best option is accepted + const int maxUnproductiveIterations = 5000; + int unproductiveIterations = 0; + + while(out->bytesList.rows < nMarkers) { + Mat currentMarker(markerSize, markerSize, CV_8UC1, Scalar::all(0)); + rng.fill(currentMarker, RNG::UNIFORM, 0, 2); + + int selfDistance = _getSelfDistance(currentMarker); + int minDistance = selfDistance; + + // if self distance is better or equal than current best option, calculate distance + // to previous accepted markers + if(selfDistance >= bestTau) { + for(int i = 0; i < out->bytesList.rows; i++) { + int currentDistance = getFlipDistanceToId(out, currentMarker, i); + minDistance = min(currentDistance, minDistance); + if(minDistance <= bestTau) { + break; + } + } + } + + // if distance is high enough, accept the marker + if(minDistance >= tau) { + unproductiveIterations = 0; + bestTau = 0; + Mat bytes = aruco::Dictionary::getByteListFromBits(currentMarker); + out->bytesList.push_back(bytes); + } else { + unproductiveIterations++; + + // if distance is not enough, but is better than the current best option + if(minDistance > bestTau) { + bestTau = minDistance; + bestMarker = currentMarker; + } + + // if number of unproductive iterarions has been reached, accept the current best option + if(unproductiveIterations == maxUnproductiveIterations) { + unproductiveIterations = 0; + tau = bestTau; + bestTau = 0; + Mat bytes = aruco::Dictionary::getByteListFromBits(bestMarker); + out->bytesList.push_back(bytes); + } + } + } + + // update the maximum number of correction bits for the generated dictionary + out->maxCorrectionBits = (tau - 1) / 2; + + return out; +} + +static inline int getMinDistForDict(const Ptr& dict) { + const int dict_size = dict->bytesList.rows; + const int marker_size = dict->markerSize; + int minDist = marker_size * marker_size; + for (int i = 0; i < dict_size; i++) { + Mat row = dict->bytesList.row(i); + Mat marker = dict->getBitsFromByteList(row, marker_size); + for (int j = 0; j < dict_size; j++) { + if (j != i) { + minDist = min(dict->getDistanceToId(marker, j), minDist); + } + } + } + return minDist; +} + +static inline int getMinAsymDistForDict(const Ptr& dict) { + const int dict_size = dict->bytesList.rows; + const int marker_size = dict->markerSize; + int minDist = marker_size * marker_size; + for (int i = 0; i < dict_size; i++) + { + Mat row = dict->bytesList.row(i); + Mat marker = dict->getBitsFromByteList(row, marker_size); + for (int j = 0; j < dict_size; j++) + { + if (j != i) + { + minDist = min(getFlipDistanceToId(dict, marker, j), minDist); + } + } + } + return minDist; +} + +const char* keys = + "{@outfile | | Output file with custom dict }" + "{r | false | Calculate the metric considering flipped markers }" + "{d | | Dictionary: DICT_4X4_50=0, DICT_4X4_100=1, DICT_4X4_250=2," + "DICT_4X4_1000=3, DICT_5X5_50=4, DICT_5X5_100=5, DICT_5X5_250=6, DICT_5X5_1000=7, " + "DICT_6X6_50=8, DICT_6X6_100=9, DICT_6X6_250=10, DICT_6X6_1000=11, DICT_7X7_50=12," + "DICT_7X7_100=13, DICT_7X7_250=14, DICT_7X7_1000=15, DICT_ARUCO_ORIGINAL = 16}" + "{nMarkers | | Number of markers in the dictionary }" + "{markerSize | | Marker size }" + "{cd | | Input file with custom dictionary }"; + +const char* about = + "This program can be used to calculate the ArUco dictionary metric.\n" + "To calculate the metric considering flipped markers use -'r' flag.\n" + "This program can be used to create and write the custom ArUco dictionary.\n"; + +int main(int argc, char *argv[]) +{ + CommandLineParser parser(argc, argv, keys); + parser.about(about); + + if(argc < 2) { + parser.printMessage(); + return 0; + } + string outputFile = parser.get(0); + int nMarkers = parser.get("nMarkers"); + int markerSize = parser.get("markerSize"); + bool checkFlippedMarkers = parser.get("r"); + + Ptr dictionary = aruco::getPredefinedDictionary(0); + if (parser.has("d")) { + int dictionaryId = parser.get("d"); + dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId)); + } + else if (parser.has("cd")) { + FileStorage fs(parser.get("cd"), FileStorage::READ); + bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root()); + if(!readOk) { + cerr << "Invalid dictionary file" << endl; + return 0; + } + } + else if (outputFile.empty() || nMarkers == 0 || markerSize == 0) { + cerr << "Dictionary not specified" << endl; + return 0; + } + + if (!outputFile.empty() && nMarkers > 0 && markerSize > 0) + { + Ptr fs = makePtr(outputFile, FileStorage::WRITE); + if (checkFlippedMarkers) + dictionary = generateCustomAsymmetricDictionary(nMarkers, markerSize, makePtr(), 0); + else + dictionary = aruco::generateCustomDictionary(nMarkers, markerSize, makePtr(), 0); + dictionary->writeDictionary(fs); + } + + if (checkFlippedMarkers) { + cout << getMinAsymDistForDict(dictionary) << endl; + } + else { + cout << getMinDistForDict(dictionary) << endl; + } + return 0; +} diff --git a/modules/aruco/samples/calibrate_camera.cpp b/modules/aruco/samples/calibrate_camera.cpp index 3162ae7ca..a52bab5b1 100644 --- a/modules/aruco/samples/calibrate_camera.cpp +++ b/modules/aruco/samples/calibrate_camera.cpp @@ -104,7 +104,7 @@ int main(int argc, char *argv[]) { Ptr detectorParams = aruco::DetectorParameters::create(); if(parser.has("dp")) { FileStorage fs(parser.get("dp"), FileStorage::READ); - bool readOk = aruco::DetectorParameters::readDetectorParameters(fs.root(), detectorParams); + bool readOk = detectorParams->readDetectorParameters(fs.root()); if(!readOk) { cerr << "Invalid detector parameters file" << endl; return 0; @@ -134,14 +134,14 @@ int main(int argc, char *argv[]) { waitTime = 10; } - Ptr dictionary; + Ptr dictionary = aruco::getPredefinedDictionary(0); if (parser.has("d")) { int dictionaryId = parser.get("d"); dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId)); } else if (parser.has("cd")) { FileStorage fs(parser.get("cd"), FileStorage::READ); - bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary); + bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root()); if(!readOk) { cerr << "Invalid dictionary file" << endl; return 0; diff --git a/modules/aruco/samples/calibrate_camera_charuco.cpp b/modules/aruco/samples/calibrate_camera_charuco.cpp index 112f90597..57b09bd96 100644 --- a/modules/aruco/samples/calibrate_camera_charuco.cpp +++ b/modules/aruco/samples/calibrate_camera_charuco.cpp @@ -105,7 +105,7 @@ int main(int argc, char *argv[]) { Ptr detectorParams = aruco::DetectorParameters::create(); if(parser.has("dp")) { FileStorage fs(parser.get("dp"), FileStorage::READ); - bool readOk = aruco::DetectorParameters::readDetectorParameters(fs.root(), detectorParams); + bool readOk = detectorParams->readDetectorParameters(fs.root()); if(!readOk) { cerr << "Invalid detector parameters file" << endl; return 0; @@ -135,14 +135,14 @@ int main(int argc, char *argv[]) { waitTime = 10; } - Ptr dictionary; + Ptr dictionary = aruco::getPredefinedDictionary(0); if (parser.has("d")) { int dictionaryId = parser.get("d"); dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId)); } else if (parser.has("cd")) { FileStorage fs(parser.get("cd"), FileStorage::READ); - bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary); + bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root()); if(!readOk) { cerr << "Invalid dictionary file" << endl; return 0; diff --git a/modules/aruco/samples/create_board.cpp b/modules/aruco/samples/create_board.cpp index a11eeda38..d2482bce8 100644 --- a/modules/aruco/samples/create_board.cpp +++ b/modules/aruco/samples/create_board.cpp @@ -96,14 +96,14 @@ int main(int argc, char *argv[]) { imageSize.height = markersY * (markerLength + markerSeparation) - markerSeparation + 2 * margins; - Ptr dictionary; + Ptr dictionary = aruco::getPredefinedDictionary(0); if (parser.has("d")) { int dictionaryId = parser.get("d"); dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId)); } else if (parser.has("cd")) { FileStorage fs(parser.get("cd"), FileStorage::READ); - bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary); + bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root()); if(!readOk) { std::cerr << "Invalid dictionary file" << std::endl; diff --git a/modules/aruco/samples/create_board_charuco.cpp b/modules/aruco/samples/create_board_charuco.cpp index 7c8c4ba1f..c9ece573b 100644 --- a/modules/aruco/samples/create_board_charuco.cpp +++ b/modules/aruco/samples/create_board_charuco.cpp @@ -93,14 +93,14 @@ int main(int argc, char *argv[]) { return 0; } - Ptr dictionary; + Ptr dictionary = aruco::getPredefinedDictionary(0); if (parser.has("d")) { int dictionaryId = parser.get("d"); dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId)); } else if (parser.has("cd")) { FileStorage fs(parser.get("cd"), FileStorage::READ); - bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary); + bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root()); if(!readOk) { std::cerr << "Invalid dictionary file" << std::endl; return 0; diff --git a/modules/aruco/samples/create_marker.cpp b/modules/aruco/samples/create_marker.cpp index a3b217296..fe31ec297 100644 --- a/modules/aruco/samples/create_marker.cpp +++ b/modules/aruco/samples/create_marker.cpp @@ -84,14 +84,14 @@ int main(int argc, char *argv[]) { return 0; } - Ptr dictionary; + Ptr dictionary = aruco::getPredefinedDictionary(0); if (parser.has("d")) { int dictionaryId = parser.get("d"); dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId)); } else if (parser.has("cd")) { FileStorage fs(parser.get("cd"), FileStorage::READ); - bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary); + bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root()); if(!readOk) { std::cerr << "Invalid dictionary file" << std::endl; return 0; diff --git a/modules/aruco/samples/detect_board.cpp b/modules/aruco/samples/detect_board.cpp index 0534a5e80..883be8dd2 100644 --- a/modules/aruco/samples/detect_board.cpp +++ b/modules/aruco/samples/detect_board.cpp @@ -100,7 +100,7 @@ int main(int argc, char *argv[]) { Ptr detectorParams = aruco::DetectorParameters::create(); if(parser.has("dp")) { FileStorage fs(parser.get("dp"), FileStorage::READ); - bool readOk = aruco::DetectorParameters::readDetectorParameters(fs.root(), detectorParams); + bool readOk = detectorParams->readDetectorParameters(fs.root()); if(!readOk) { cerr << "Invalid detector parameters file" << endl; return 0; @@ -118,14 +118,14 @@ int main(int argc, char *argv[]) { return 0; } - Ptr dictionary; + Ptr dictionary = aruco::getPredefinedDictionary(0); if (parser.has("d")) { int dictionaryId = parser.get("d"); dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId)); } else if (parser.has("cd")) { FileStorage fs(parser.get("cd"), FileStorage::READ); - bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary); + bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root()); if(!readOk) { cerr << "Invalid dictionary file" << endl; return 0; diff --git a/modules/aruco/samples/detect_board_charuco.cpp b/modules/aruco/samples/detect_board_charuco.cpp index a63363770..f413cef9f 100644 --- a/modules/aruco/samples/detect_board_charuco.cpp +++ b/modules/aruco/samples/detect_board_charuco.cpp @@ -102,7 +102,7 @@ int main(int argc, char *argv[]) { Ptr detectorParams = aruco::DetectorParameters::create(); if(parser.has("dp")) { FileStorage fs(parser.get("dp"), FileStorage::READ); - bool readOk = aruco::DetectorParameters::readDetectorParameters(fs.root(), detectorParams); + bool readOk = detectorParams->readDetectorParameters(fs.root()); if(!readOk) { cerr << "Invalid detector parameters file" << endl; return 0; @@ -114,14 +114,14 @@ int main(int argc, char *argv[]) { return 0; } - Ptr dictionary; + Ptr dictionary = aruco::getPredefinedDictionary(0); if (parser.has("d")) { int dictionaryId = parser.get("d"); dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId)); } else if (parser.has("cd")) { FileStorage fs(parser.get("cd"), FileStorage::READ); - bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary); + bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root()); if(!readOk) { cerr << "Invalid dictionary file" << endl; return 0; diff --git a/modules/aruco/samples/detect_diamonds.cpp b/modules/aruco/samples/detect_diamonds.cpp index bd8aa51d9..e5255a660 100644 --- a/modules/aruco/samples/detect_diamonds.cpp +++ b/modules/aruco/samples/detect_diamonds.cpp @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) { Ptr detectorParams = aruco::DetectorParameters::create(); if(parser.has("dp")) { FileStorage fs(parser.get("dp"), FileStorage::READ); - bool readOk = aruco::DetectorParameters::readDetectorParameters(fs.root(), detectorParams); + bool readOk = detectorParams->readDetectorParameters(fs.root()); if(!readOk) { cerr << "Invalid detector parameters file" << endl; return 0; @@ -114,14 +114,14 @@ int main(int argc, char *argv[]) { return 0; } - Ptr dictionary; + Ptr dictionary = aruco::getPredefinedDictionary(0); if (parser.has("d")) { int dictionaryId = parser.get("d"); dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId)); } else if (parser.has("cd")) { FileStorage fs(parser.get("cd"), FileStorage::READ); - bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary); + bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root()); if(!readOk) { cerr << "Invalid dictionary file" << endl; return 0; diff --git a/modules/aruco/samples/detect_markers.cpp b/modules/aruco/samples/detect_markers.cpp index 5085a346e..f7d17b9f8 100644 --- a/modules/aruco/samples/detect_markers.cpp +++ b/modules/aruco/samples/detect_markers.cpp @@ -83,7 +83,7 @@ int main(int argc, char *argv[]) { Ptr detectorParams = aruco::DetectorParameters::create(); if(parser.has("dp")) { FileStorage fs(parser.get("dp"), FileStorage::READ); - bool readOk = aruco::DetectorParameters::readDetectorParameters(fs.root(), detectorParams); + bool readOk = detectorParams->readDetectorParameters(fs.root()); if(!readOk) { cerr << "Invalid detector parameters file" << endl; return 0; @@ -108,14 +108,14 @@ int main(int argc, char *argv[]) { return 0; } - Ptr dictionary; + Ptr dictionary = aruco::getPredefinedDictionary(0); if (parser.has("d")) { int dictionaryId = parser.get("d"); dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId)); } else if (parser.has("cd")) { FileStorage fs(parser.get("cd"), FileStorage::READ); - bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary); + bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root()); if(!readOk) { std::cerr << "Invalid dictionary file" << std::endl; return 0; diff --git a/modules/aruco/src/aruco.cpp b/modules/aruco/src/aruco.cpp index c5d3d8322..c4e895b90 100644 --- a/modules/aruco/src/aruco.cpp +++ b/modules/aruco/src/aruco.cpp @@ -111,32 +111,31 @@ static inline bool readParameter(const FileNode& node, T& parameter) /** * @brief Read a new set of DetectorParameters from FileStorage. */ -bool DetectorParameters::readDetectorParameters(const FileNode& fn, Ptr& params) +bool DetectorParameters::readDetectorParameters(const FileNode& fn) { if(fn.empty()) return true; - params = DetectorParameters::create(); bool checkRead = false; - checkRead |= readParameter(fn["adaptiveThreshWinSizeMin"], params->adaptiveThreshWinSizeMin); - checkRead |= readParameter(fn["adaptiveThreshWinSizeMax"], params->adaptiveThreshWinSizeMax); - checkRead |= readParameter(fn["adaptiveThreshWinSizeStep"], params->adaptiveThreshWinSizeStep); - checkRead |= readParameter(fn["adaptiveThreshConstant"], params->adaptiveThreshConstant); - checkRead |= readParameter(fn["minMarkerPerimeterRate"], params->minMarkerPerimeterRate); - checkRead |= readParameter(fn["maxMarkerPerimeterRate"], params->maxMarkerPerimeterRate); - checkRead |= readParameter(fn["polygonalApproxAccuracyRate"], params->polygonalApproxAccuracyRate); - checkRead |= readParameter(fn["minCornerDistanceRate"], params->minCornerDistanceRate); - checkRead |= readParameter(fn["minDistanceToBorder"], params->minDistanceToBorder); - checkRead |= readParameter(fn["minMarkerDistanceRate"], params->minMarkerDistanceRate); - checkRead |= readParameter(fn["cornerRefinementMethod"], params->cornerRefinementMethod); - checkRead |= readParameter(fn["cornerRefinementWinSize"], params->cornerRefinementWinSize); - checkRead |= readParameter(fn["cornerRefinementMaxIterations"], params->cornerRefinementMaxIterations); - checkRead |= readParameter(fn["cornerRefinementMinAccuracy"], params->cornerRefinementMinAccuracy); - checkRead |= readParameter(fn["markerBorderBits"], params->markerBorderBits); - checkRead |= readParameter(fn["perspectiveRemovePixelPerCell"], params->perspectiveRemovePixelPerCell); - checkRead |= readParameter(fn["perspectiveRemoveIgnoredMarginPerCell"], params->perspectiveRemoveIgnoredMarginPerCell); - checkRead |= readParameter(fn["maxErroneousBitsInBorderRate"], params->maxErroneousBitsInBorderRate); - checkRead |= readParameter(fn["minOtsuStdDev"], params->minOtsuStdDev); - checkRead |= readParameter(fn["errorCorrectionRate"], params->errorCorrectionRate); + checkRead |= readParameter(fn["adaptiveThreshWinSizeMin"], this->adaptiveThreshWinSizeMin); + checkRead |= readParameter(fn["adaptiveThreshWinSizeMax"], this->adaptiveThreshWinSizeMax); + checkRead |= readParameter(fn["adaptiveThreshWinSizeStep"], this->adaptiveThreshWinSizeStep); + checkRead |= readParameter(fn["adaptiveThreshConstant"], this->adaptiveThreshConstant); + checkRead |= readParameter(fn["minMarkerPerimeterRate"], this->minMarkerPerimeterRate); + checkRead |= readParameter(fn["maxMarkerPerimeterRate"], this->maxMarkerPerimeterRate); + checkRead |= readParameter(fn["polygonalApproxAccuracyRate"], this->polygonalApproxAccuracyRate); + checkRead |= readParameter(fn["minCornerDistanceRate"], this->minCornerDistanceRate); + checkRead |= readParameter(fn["minDistanceToBorder"], this->minDistanceToBorder); + checkRead |= readParameter(fn["minMarkerDistanceRate"], this->minMarkerDistanceRate); + checkRead |= readParameter(fn["cornerRefinementMethod"], this->cornerRefinementMethod); + checkRead |= readParameter(fn["cornerRefinementWinSize"], this->cornerRefinementWinSize); + checkRead |= readParameter(fn["cornerRefinementMaxIterations"], this->cornerRefinementMaxIterations); + checkRead |= readParameter(fn["cornerRefinementMinAccuracy"], this->cornerRefinementMinAccuracy); + checkRead |= readParameter(fn["markerBorderBits"], this->markerBorderBits); + checkRead |= readParameter(fn["perspectiveRemovePixelPerCell"], this->perspectiveRemovePixelPerCell); + checkRead |= readParameter(fn["perspectiveRemoveIgnoredMarginPerCell"], this->perspectiveRemoveIgnoredMarginPerCell); + checkRead |= readParameter(fn["maxErroneousBitsInBorderRate"], this->maxErroneousBitsInBorderRate); + checkRead |= readParameter(fn["minOtsuStdDev"], this->minOtsuStdDev); + checkRead |= readParameter(fn["errorCorrectionRate"], this->errorCorrectionRate); return checkRead; } diff --git a/modules/aruco/src/dictionary.cpp b/modules/aruco/src/dictionary.cpp index 75431da4f..20d2526a4 100644 --- a/modules/aruco/src/dictionary.cpp +++ b/modules/aruco/src/dictionary.cpp @@ -94,27 +94,45 @@ static inline bool readParameter(const FileNode& node, T& parameter) return false; } -bool Dictionary::readDictionary(const cv::FileNode& fn, cv::Ptr &dictionary) +bool Dictionary::readDictionary(const cv::FileNode& fn) { - int nMarkers = 0, markerSize = 0; - if(fn.empty() || !readParameter(fn["nmarkers"], nMarkers) || !readParameter(fn["markersize"], markerSize)) + int nMarkers = 0, _markerSize = 0; + if (fn.empty() || !readParameter(fn["nmarkers"], nMarkers) || !readParameter(fn["markersize"], _markerSize)) return false; - cv::Mat bytes(0, 0, CV_8UC1), marker(markerSize, markerSize, CV_8UC1); + Mat bytes(0, 0, CV_8UC1), marker(_markerSize, _markerSize, CV_8UC1); std::string markerString; for (int i = 0; i < nMarkers; i++) { std::ostringstream ostr; ostr << i; if (!readParameter(fn["marker_" + ostr.str()], markerString)) return false; - for (int j = 0; j < (int) markerString.size(); j++) marker.at(j) = (markerString[j] == '0') ? 0 : 1; - bytes.push_back(cv::aruco::Dictionary::getByteListFromBits(marker)); + bytes.push_back(Dictionary::getByteListFromBits(marker)); } - dictionary = cv::makePtr(bytes, markerSize); + int _maxCorrectionBits = 0; + readParameter(fn["maxCorrectionBits"], _maxCorrectionBits); + *this = Dictionary(bytes, _markerSize, _maxCorrectionBits); return true; } +void Dictionary::writeDictionary(Ptr& fs) { + *fs << "nmarkers" << bytesList.rows; + *fs << "markersize" << markerSize; + *fs << "maxCorrectionBits" << maxCorrectionBits; + for (int i = 0; i < bytesList.rows; i++) { + Mat row = bytesList.row(i);; + Mat bitMarker = getBitsFromByteList(row, markerSize); + std::ostringstream ostr; + ostr << i; + string markerName = "marker_" + ostr.str(); + string marker; + for (int j = 0; j < markerSize * markerSize; j++) + marker.push_back(bitMarker.at(j) + '0'); + *fs << markerName << marker; + } +} + /** */ Ptr Dictionary::get(int dict) { diff --git a/modules/aruco/test/test_arucodetection.cpp b/modules/aruco/test/test_arucodetection.cpp index f1e77f7aa..732c2a9dc 100644 --- a/modules/aruco/test/test_arucodetection.cpp +++ b/modules/aruco/test/test_arucodetection.cpp @@ -594,10 +594,10 @@ TEST(CV_ArucoTutorial, can_find_gboriginal) string imgPath = cvtest::findDataFile("gboriginal.png", false); Mat image = imread(imgPath); string dictPath = cvtest::findDataFile("tutorial_dict.yml", false); - cv::Ptr dictionary; + Ptr dictionary = makePtr(); FileStorage fs(dictPath, FileStorage::READ); - aruco::Dictionary::readDictionary(fs.root(), dictionary); // set marker from tutorial_dict.yml + dictionary->aruco::Dictionary::readDictionary(fs.root()); // set marker from tutorial_dict.yml Ptr detectorParams = aruco::DetectorParameters::create(); diff --git a/modules/aruco/test/test_charucodetection.cpp b/modules/aruco/test/test_charucodetection.cpp index 0f38ac1ca..de474009a 100644 --- a/modules/aruco/test/test_charucodetection.cpp +++ b/modules/aruco/test/test_charucodetection.cpp @@ -695,14 +695,14 @@ TEST(CV_ArucoTutorial, can_find_diamondmarkers) Mat image = imread(imgPath); string dictPath = cvtest::findDataFile("tutorial_dict.yml", false); - cv::Ptr dictionary; + Ptr dictionary = makePtr(); FileStorage fs(dictPath, FileStorage::READ); - aruco::Dictionary::readDictionary(fs.root(), dictionary); // set marker from tutorial_dict.yml + dictionary->aruco::Dictionary::readDictionary(fs.root()); // set marker from tutorial_dict.yml string detectorPath = cvtest::findDataFile("detector_params.yml", false); fs = FileStorage(detectorPath, FileStorage::READ); - Ptr detectorParams; - aruco::DetectorParameters::readDetectorParameters(fs.root(), detectorParams); + Ptr detectorParams = aruco::DetectorParameters::create(); + detectorParams->readDetectorParameters(fs.root()); detectorParams->cornerRefinementMethod = 3; vector< int > ids; diff --git a/modules/aruco/tutorials/aruco_faq/aruco_faq.markdown b/modules/aruco/tutorials/aruco_faq/aruco_faq.markdown index 492b321de..9d1183ef7 100644 --- a/modules/aruco/tutorials/aruco_faq/aruco_faq.markdown +++ b/modules/aruco/tutorials/aruco_faq/aruco_faq.markdown @@ -26,6 +26,8 @@ in the ```DetectorParameters``` object. The first thing you can do is checking i as rejected candidates by the ```detectMarkers()``` function. Depending on this, you should try to modify different parameters. If you are using a ArUco board, you can also try the ```refineDetectedMarkers()``` function. +If you are [using big markers](https://github.com/opencv/opencv_contrib/issues/2811) (400x400 pixels and more), try increasing ```adaptiveThreshWinSizeMax``` value. +Also avoid [narrow borders](https://github.com/opencv/opencv_contrib/issues/2492) (5% or less of the marker perimeter, adjusted by ```minMarkerDistanceRate```) around markers. - What are the benefits of ArUco boards? What are the drawbacks? @@ -101,7 +103,7 @@ correction during the identification step. Dictionary generation should only be done once at the beginning of your application and it should take some seconds. If you are generating the dictionary on each iteration of your detection loop, you are doing it wrong. -Furthermore, it is recommendable to save the dictionary to a file and read it on every execution so you dont need to generate it. +Furthermore, it is recommendable to save the dictionary to a file with ```cv::aruco::Dictionary::writeDictionary()``` and read it with ```cv::aruco::Dictionary::readDictionary()``` on every execution, so you don't need to generate it. - I would like to use some markers of the original ArUco library that I have already printed, can I use them? @@ -131,7 +133,7 @@ If you manually modify the marker ids of the boards, or if you use a different t - Does the aruco module provide functions to save the Dictionary or Board to file? -Not right now. However the data member of both the dictionary and board classes are public and can be easily stored. +You can use ```cv::aruco::Dictionary::writeDictionary()``` and ```cv::aruco::Dictionary::readDictionary()``` for ```cv::aruco::Dictionary```. The data member of board classes are public and can be easily stored. - Alright, but how can I render a 3d model to create an augmented reality application? @@ -149,3 +151,13 @@ You can cite the original ArUco library: > S. Garrido-Jurado, R. Muñoz-Salinas, F. J. Madrid-Cuevas, and M. J. Marín-Jiménez. 2014. > "Automatic generation and detection of highly reliable fiducial markers under occlusion". > Pattern Recogn. 47, 6 (June 2014), 2280-2292. DOI=10.1016/j.patcog.2014.01.005 + +- Pose estimation markers are not being detected correctly, what can I do? + +It is important to remark that the estimation of the pose using only 4 coplanar points is subject to ambiguity. +In general, the ambiguity can be solved, if the camera is near to the marker. +However, as the marker becomes small, the errors in the corner estimation grows and ambiguity comes as a problem. +Try increasing the size of the marker you're using, and you can also try non-symmetrical (aruco_dict_utils.cpp) markers to avoid collisions. +Use multiple markers (ArUco/ChArUco/Diamonds boards) and pose estimation with estimatePoseBoard(), estimatePoseCharucoBoard(). +Use solvePnP() with the ```SOLVEPNP_IPPE_SQUARE``` option. +More in [this issue](https://github.com/opencv/opencv/issues/8813). From 7b707a051ab024457e9c465a9538e7dae3a09a56 Mon Sep 17 00:00:00 2001 From: AleksandrPanov Date: Fri, 15 Apr 2022 17:58:47 +0300 Subject: [PATCH 4/4] fix ArucoThreading test --- modules/aruco/test/test_arucodetection.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/aruco/test/test_arucodetection.cpp b/modules/aruco/test/test_arucodetection.cpp index 732c2a9dc..6e4105047 100644 --- a/modules/aruco/test/test_arucodetection.cpp +++ b/modules/aruco/test/test_arucodetection.cpp @@ -766,8 +766,8 @@ TEST_P(ArucoThreading, number_of_threads_does_not_change_results) cv::aruco::detectMarkers(img, dictionary, original_corners, original_ids, params); } - ASSERT_EQ(original_ids.size(), 1); - ASSERT_EQ(original_corners.size(), 1); + ASSERT_EQ(original_ids.size(), 1ull); + ASSERT_EQ(original_corners.size(), 1ull); int num_threads_to_test[] = { 2, 8, 16, 32, height_img-1, height_img, height_img+1}; @@ -779,7 +779,7 @@ TEST_P(ArucoThreading, number_of_threads_does_not_change_results) cv::aruco::detectMarkers(img, dictionary, corners, ids, params); // If we don't find any markers, the test is broken - ASSERT_EQ(ids.size(), 1); + ASSERT_EQ(ids.size(), 1ull); // Make sure we got the same result as the first time ASSERT_EQ(corners.size(), original_corners.size());