From ea5fb7b2ff6f86d3f4664ab131fdeb4959917217 Mon Sep 17 00:00:00 2001 From: Alexander Panov Date: Thu, 14 Apr 2022 14:43:21 +0300 Subject: [PATCH] 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 | 47 ++- 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(+), 89 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 4a9675fa4e6..32d011404fe 100644 --- a/modules/aruco/include/opencv2/aruco.hpp +++ b/modules/aruco/include/opencv2/aruco.hpp @@ -169,7 +169,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 27374d8f53b..40174162e4b 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 edf1cc3db93..9b0f57956fb 100644 --- a/modules/aruco/misc/python/test/test_aruco.py +++ b/modules/aruco/misc/python/test/test_aruco.py @@ -35,5 +35,34 @@ def test_drawCharucoDiamond(self): img = cv.aruco.drawCharucoDiamond(aruco_dict, np.array([0, 1, 2, 3]), 100, 80) self.assertTrue(img is not None) + 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 00000000000..465513b3410 --- /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 3162ae7ca29..a52bab5b161 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 112f9059773..57b09bd96f0 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 a11eeda38f1..d2482bce853 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 7c8c4ba1fe3..c9ece573b3f 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 a3b2172960b..fe31ec2972f 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 0534a5e80ee..883be8dd2e3 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 a6336377078..f413cef9fb3 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 bd8aa51d9eb..e5255a6602c 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 5085a346ee5..f7d17b9f8a8 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 93116ab3534..5b3233bf20d 100644 --- a/modules/aruco/src/aruco.cpp +++ b/modules/aruco/src/aruco.cpp @@ -117,36 +117,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); - // new aruco 3 functionality - checkRead |= readParameter(fn["useAruco3Detection"], params->useAruco3Detection); - checkRead |= readParameter(fn["minSideLengthCanonicalImg"], params->minSideLengthCanonicalImg); - checkRead |= readParameter(fn["minMarkerLengthRatioOriginalImg"], params->minMarkerLengthRatioOriginalImg); + 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 75431da4f7c..20d2526a47d 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 9e7a0db48d5..827ff2a58a2 100644 --- a/modules/aruco/test/test_arucodetection.cpp +++ b/modules/aruco/test/test_arucodetection.cpp @@ -605,10 +605,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 e9cf6665dfe..08e289b99db 100644 --- a/modules/aruco/test/test_charucodetection.cpp +++ b/modules/aruco/test/test_charucodetection.cpp @@ -750,14 +750,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 492b321de34..9d1183ef791 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).