| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- #include <opencv2/objdetect/aruco_detector.hpp>
- #include <iostream>
- using namespace cv;
- using namespace std;
- static int _getSelfDistance(const Mat &marker) {
- Mat bytes = aruco::Dictionary::getByteListFromBits(marker);
- double minHamming = (double)marker.total() + 1;
- for(int r = 1; r < 4; r++) {
- cv::Mat tmp1(1, bytes.cols, CV_8UC1, Scalar::all(0));
- cv::Mat tmp2(1, bytes.cols, CV_8UC1, Scalar::all(0));
- uchar* rot0 = tmp1.ptr();
- uchar* rot1 = tmp2.ptr();
- for (int i = 0; i < bytes.cols; ++i) {
- rot0[i] = bytes.ptr()[i];
- rot1[i] = bytes.ptr()[bytes.cols*r + i];
- }
- double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
- if (currentHamming < minHamming) minHamming = currentHamming;
- }
- Mat b;
- flip(marker, b, 0);
- Mat flipBytes = aruco::Dictionary::getByteListFromBits(b);
- for(int r = 0; r < 4; r++) {
- cv::Mat tmp1(1, flipBytes.cols, CV_8UC1, Scalar::all(0));
- cv::Mat tmp2(1, bytes.cols, CV_8UC1, Scalar::all(0));
- uchar* rot0 = tmp1.ptr();
- uchar* rot1 = tmp2.ptr();
- for (int i = 0; i < bytes.cols; ++i) {
- rot0[i] = flipBytes.ptr()[i];
- rot1[i] = bytes.ptr()[bytes.cols*r + i];
- }
- double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
- if(currentHamming < minHamming) minHamming = currentHamming;
- }
- flip(marker, b, 1);
- flipBytes = aruco::Dictionary::getByteListFromBits(b);
- for(int r = 0; r < 4; r++) {
- cv::Mat tmp1(1, flipBytes.cols, CV_8UC1, Scalar::all(0));
- cv::Mat tmp2(1, bytes.cols, CV_8UC1, Scalar::all(0));
- uchar* rot0 = tmp1.ptr();
- uchar* rot1 = tmp2.ptr();
- for (int i = 0; i < bytes.cols; ++i) {
- rot0[i] = flipBytes.ptr()[i];
- rot1[i] = bytes.ptr()[bytes.cols*r + i];
- }
- double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
- if(currentHamming < minHamming) minHamming = currentHamming;
- }
- return cvRound(minHamming);
- }
- static inline int getFlipDistanceToId(const aruco::Dictionary& 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());
- double currentMinDistance = int(bits.total() * bits.total());
- for(unsigned int r = 0; r < nRotations; r++) {
- cv::Mat tmp1(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
- cv::Mat tmp2(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
- uchar* rot0 = tmp1.ptr();
- uchar* rot1 = tmp2.ptr();
- for (int i = 0; i < candidateBytes.cols; ++i) {
- rot0[i] = bytesList.ptr(id)[r*candidateBytes.cols + i];
- rot1[i] = candidateBytes.ptr()[i];
- }
- double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
- 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++) {
- cv::Mat tmp1(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
- cv::Mat tmp2(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
- uchar* rot0 = tmp1.ptr();
- uchar* rot1 = tmp2.ptr();
- for (int i = 0; i < candidateBytes.cols; ++i) {
- rot0[i] = bytesList.ptr(id)[r*candidateBytes.cols + i];
- rot1[i] = candidateBytes.ptr()[i];
- }
- double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
- if (currentHamming < currentMinDistance) {
- currentMinDistance = currentHamming;
- }
- }
- flip(bits.getMat(), b, 1);
- candidateBytes = aruco::Dictionary::getByteListFromBits(b);
- for(unsigned int r = 0; r < nRotations; r++) {
- cv::Mat tmp1(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
- cv::Mat tmp2(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
- uchar* rot0 = tmp1.ptr();
- uchar* rot1 = tmp2.ptr();
- for (int i = 0; i < candidateBytes.cols; ++i) {
- rot0[i] = bytesList.ptr(id)[r*candidateBytes.cols + i];
- rot1[i] = candidateBytes.ptr()[i];
- }
- double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
- if (currentHamming < currentMinDistance) {
- currentMinDistance = currentHamming;
- }
- }
- return cvRound(currentMinDistance);
- }
- static inline aruco::Dictionary generateCustomAsymmetricDictionary(int nMarkers, int markerSize,
- const aruco::Dictionary &baseDictionary,
- int randomSeed) {
- RNG rng((uint64)(randomSeed));
- aruco::Dictionary out;
- 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 aruco::Dictionary& 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 aruco::Dictionary& 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 |<none> | Output file with custom dict }"
- "{r | false | Calculate the metric considering flipped markers }"
- "{d | | Dictionary Name: DICT_4X4_50, 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_APRILTAG_16h5, DICT_APRILTAG_25h9, DICT_APRILTAG_36h10,"
- "DICT_APRILTAG_36h11}"
- "{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<String>(0);
- int nMarkers = parser.get<int>("nMarkers");
- int markerSize = parser.get<int>("markerSize");
- bool checkFlippedMarkers = parser.get<bool>("r");
- aruco::Dictionary dictionary = aruco::getPredefinedDictionary(cv::aruco::DICT_4X4_50);
- if (parser.has("d")) {
- string arucoDictName = parser.get<string>("d");
- cv::aruco::PredefinedDictionaryType arucoDict;
- if (arucoDictName == "DICT_4X4_50") { arucoDict = cv::aruco::DICT_4X4_50; }
- else if (arucoDictName == "DICT_4X4_100") { arucoDict = cv::aruco::DICT_4X4_100; }
- else if (arucoDictName == "DICT_4X4_250") { arucoDict = cv::aruco::DICT_4X4_250; }
- else if (arucoDictName == "DICT_4X4_1000") { arucoDict = cv::aruco::DICT_4X4_1000; }
- else if (arucoDictName == "DICT_5X5_50") { arucoDict = cv::aruco::DICT_5X5_50; }
- else if (arucoDictName == "DICT_5X5_100") { arucoDict = cv::aruco::DICT_5X5_100; }
- else if (arucoDictName == "DICT_5X5_250") { arucoDict = cv::aruco::DICT_5X5_250; }
- else if (arucoDictName == "DICT_5X5_1000") { arucoDict = cv::aruco::DICT_5X5_1000; }
- else if (arucoDictName == "DICT_6X6_50") { arucoDict = cv::aruco::DICT_6X6_50; }
- else if (arucoDictName == "DICT_6X6_100") { arucoDict = cv::aruco::DICT_6X6_100; }
- else if (arucoDictName == "DICT_6X6_250") { arucoDict = cv::aruco::DICT_6X6_250; }
- else if (arucoDictName == "DICT_6X6_1000") { arucoDict = cv::aruco::DICT_6X6_1000; }
- else if (arucoDictName == "DICT_7X7_50") { arucoDict = cv::aruco::DICT_7X7_50; }
- else if (arucoDictName == "DICT_7X7_100") { arucoDict = cv::aruco::DICT_7X7_100; }
- else if (arucoDictName == "DICT_7X7_250") { arucoDict = cv::aruco::DICT_7X7_250; }
- else if (arucoDictName == "DICT_7X7_1000") { arucoDict = cv::aruco::DICT_7X7_1000; }
- else if (arucoDictName == "DICT_ARUCO_ORIGINAL") { arucoDict = cv::aruco::DICT_ARUCO_ORIGINAL; }
- else if (arucoDictName == "DICT_APRILTAG_16h5") { arucoDict = cv::aruco::DICT_APRILTAG_16h5; }
- else if (arucoDictName == "DICT_APRILTAG_25h9") { arucoDict = cv::aruco::DICT_APRILTAG_25h9; }
- else if (arucoDictName == "DICT_APRILTAG_36h10") { arucoDict = cv::aruco::DICT_APRILTAG_36h10; }
- else if (arucoDictName == "DICT_APRILTAG_36h11") { arucoDict = cv::aruco::DICT_APRILTAG_36h11; }
- else {
- cout << "incorrect name of aruco dictionary \n";
- return 1;
- }
- dictionary = aruco::getPredefinedDictionary(arucoDict);
- }
- else if (parser.has("cd")) {
- FileStorage fs(parser.get<std::string>("cd"), FileStorage::READ);
- bool readOk = 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)
- {
- FileStorage fs(outputFile, FileStorage::WRITE);
- if (checkFlippedMarkers)
- dictionary = generateCustomAsymmetricDictionary(nMarkers, markerSize, aruco::Dictionary(), 0);
- else
- dictionary = aruco::extendDictionary(nMarkers, markerSize, aruco::Dictionary(), 0);
- dictionary.writeDictionary(fs);
- }
- if (checkFlippedMarkers) {
- cout << "Hamming distance: " << getMinAsymDistForDict(dictionary) << endl;
- }
- else {
- cout << "Hamming distance: " << getMinDistForDict(dictionary) << endl;
- }
- return 0;
- }
|