aruco_dict_utils.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. #include <opencv2/objdetect/aruco_detector.hpp>
  2. #include <iostream>
  3. using namespace cv;
  4. using namespace std;
  5. static int _getSelfDistance(const Mat &marker) {
  6. Mat bytes = aruco::Dictionary::getByteListFromBits(marker);
  7. double minHamming = (double)marker.total() + 1;
  8. for(int r = 1; r < 4; r++) {
  9. cv::Mat tmp1(1, bytes.cols, CV_8UC1, Scalar::all(0));
  10. cv::Mat tmp2(1, bytes.cols, CV_8UC1, Scalar::all(0));
  11. uchar* rot0 = tmp1.ptr();
  12. uchar* rot1 = tmp2.ptr();
  13. for (int i = 0; i < bytes.cols; ++i) {
  14. rot0[i] = bytes.ptr()[i];
  15. rot1[i] = bytes.ptr()[bytes.cols*r + i];
  16. }
  17. double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
  18. if (currentHamming < minHamming) minHamming = currentHamming;
  19. }
  20. Mat b;
  21. flip(marker, b, 0);
  22. Mat flipBytes = aruco::Dictionary::getByteListFromBits(b);
  23. for(int r = 0; r < 4; r++) {
  24. cv::Mat tmp1(1, flipBytes.cols, CV_8UC1, Scalar::all(0));
  25. cv::Mat tmp2(1, bytes.cols, CV_8UC1, Scalar::all(0));
  26. uchar* rot0 = tmp1.ptr();
  27. uchar* rot1 = tmp2.ptr();
  28. for (int i = 0; i < bytes.cols; ++i) {
  29. rot0[i] = flipBytes.ptr()[i];
  30. rot1[i] = bytes.ptr()[bytes.cols*r + i];
  31. }
  32. double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
  33. if(currentHamming < minHamming) minHamming = currentHamming;
  34. }
  35. flip(marker, b, 1);
  36. flipBytes = aruco::Dictionary::getByteListFromBits(b);
  37. for(int r = 0; r < 4; r++) {
  38. cv::Mat tmp1(1, flipBytes.cols, CV_8UC1, Scalar::all(0));
  39. cv::Mat tmp2(1, bytes.cols, CV_8UC1, Scalar::all(0));
  40. uchar* rot0 = tmp1.ptr();
  41. uchar* rot1 = tmp2.ptr();
  42. for (int i = 0; i < bytes.cols; ++i) {
  43. rot0[i] = flipBytes.ptr()[i];
  44. rot1[i] = bytes.ptr()[bytes.cols*r + i];
  45. }
  46. double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
  47. if(currentHamming < minHamming) minHamming = currentHamming;
  48. }
  49. return cvRound(minHamming);
  50. }
  51. static inline int getFlipDistanceToId(const aruco::Dictionary& dict, InputArray bits, int id, bool allRotations = true) {
  52. Mat bytesList = dict.bytesList;
  53. CV_Assert(id >= 0 && id < bytesList.rows);
  54. unsigned int nRotations = 4;
  55. if(!allRotations) nRotations = 1;
  56. Mat candidateBytes = aruco::Dictionary::getByteListFromBits(bits.getMat());
  57. double currentMinDistance = int(bits.total() * bits.total());
  58. for(unsigned int r = 0; r < nRotations; r++) {
  59. cv::Mat tmp1(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
  60. cv::Mat tmp2(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
  61. uchar* rot0 = tmp1.ptr();
  62. uchar* rot1 = tmp2.ptr();
  63. for (int i = 0; i < candidateBytes.cols; ++i) {
  64. rot0[i] = bytesList.ptr(id)[r*candidateBytes.cols + i];
  65. rot1[i] = candidateBytes.ptr()[i];
  66. }
  67. double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
  68. if(currentHamming < currentMinDistance) {
  69. currentMinDistance = currentHamming;
  70. }
  71. }
  72. Mat b;
  73. flip(bits.getMat(), b, 0);
  74. candidateBytes = aruco::Dictionary::getByteListFromBits(b);
  75. for(unsigned int r = 0; r < nRotations; r++) {
  76. cv::Mat tmp1(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
  77. cv::Mat tmp2(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
  78. uchar* rot0 = tmp1.ptr();
  79. uchar* rot1 = tmp2.ptr();
  80. for (int i = 0; i < candidateBytes.cols; ++i) {
  81. rot0[i] = bytesList.ptr(id)[r*candidateBytes.cols + i];
  82. rot1[i] = candidateBytes.ptr()[i];
  83. }
  84. double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
  85. if (currentHamming < currentMinDistance) {
  86. currentMinDistance = currentHamming;
  87. }
  88. }
  89. flip(bits.getMat(), b, 1);
  90. candidateBytes = aruco::Dictionary::getByteListFromBits(b);
  91. for(unsigned int r = 0; r < nRotations; r++) {
  92. cv::Mat tmp1(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
  93. cv::Mat tmp2(1, candidateBytes.cols, CV_8UC1, Scalar::all(0));
  94. uchar* rot0 = tmp1.ptr();
  95. uchar* rot1 = tmp2.ptr();
  96. for (int i = 0; i < candidateBytes.cols; ++i) {
  97. rot0[i] = bytesList.ptr(id)[r*candidateBytes.cols + i];
  98. rot1[i] = candidateBytes.ptr()[i];
  99. }
  100. double currentHamming = cv::norm(tmp1, tmp2, cv::NORM_HAMMING);
  101. if (currentHamming < currentMinDistance) {
  102. currentMinDistance = currentHamming;
  103. }
  104. }
  105. return cvRound(currentMinDistance);
  106. }
  107. static inline aruco::Dictionary generateCustomAsymmetricDictionary(int nMarkers, int markerSize,
  108. const aruco::Dictionary &baseDictionary,
  109. int randomSeed) {
  110. RNG rng((uint64)(randomSeed));
  111. aruco::Dictionary out;
  112. out.markerSize = markerSize;
  113. // theoretical maximum intermarker distance
  114. // See S. Garrido-Jurado, R. Muñoz-Salinas, F. J. Madrid-Cuevas, and M. J. Marín-Jiménez. 2014.
  115. // "Automatic generation and detection of highly reliable fiducial markers under occlusion".
  116. // Pattern Recogn. 47, 6 (June 2014), 2280-2292. DOI=10.1016/j.patcog.2014.01.005
  117. int C = (int)std::floor(float(markerSize * markerSize) / 4.f);
  118. int tau = 2 * (int)std::floor(float(C) * 4.f / 3.f);
  119. // if baseDictionary is provided, calculate its intermarker distance
  120. if(baseDictionary.bytesList.rows > 0) {
  121. CV_Assert(baseDictionary.markerSize == markerSize);
  122. out.bytesList = baseDictionary.bytesList.clone();
  123. int minDistance = markerSize * markerSize + 1;
  124. for(int i = 0; i < out.bytesList.rows; i++) {
  125. Mat markerBytes = out.bytesList.rowRange(i, i + 1);
  126. Mat markerBits = aruco::Dictionary::getBitsFromByteList(markerBytes, markerSize);
  127. minDistance = min(minDistance, _getSelfDistance(markerBits));
  128. for(int j = i + 1; j < out.bytesList.rows; j++) {
  129. minDistance = min(minDistance, getFlipDistanceToId(out, markerBits, j));
  130. }
  131. }
  132. tau = minDistance;
  133. }
  134. // current best option
  135. int bestTau = 0;
  136. Mat bestMarker;
  137. // after these number of unproductive iterations, the best option is accepted
  138. const int maxUnproductiveIterations = 5000;
  139. int unproductiveIterations = 0;
  140. while(out.bytesList.rows < nMarkers) {
  141. Mat currentMarker(markerSize, markerSize, CV_8UC1, Scalar::all(0));
  142. rng.fill(currentMarker, RNG::UNIFORM, 0, 2);
  143. int selfDistance = _getSelfDistance(currentMarker);
  144. int minDistance = selfDistance;
  145. // if self distance is better or equal than current best option, calculate distance
  146. // to previous accepted markers
  147. if(selfDistance >= bestTau) {
  148. for(int i = 0; i < out.bytesList.rows; i++) {
  149. int currentDistance = getFlipDistanceToId(out, currentMarker, i);
  150. minDistance = min(currentDistance, minDistance);
  151. if(minDistance <= bestTau) {
  152. break;
  153. }
  154. }
  155. }
  156. // if distance is high enough, accept the marker
  157. if(minDistance >= tau) {
  158. unproductiveIterations = 0;
  159. bestTau = 0;
  160. Mat bytes = aruco::Dictionary::getByteListFromBits(currentMarker);
  161. out.bytesList.push_back(bytes);
  162. } else {
  163. unproductiveIterations++;
  164. // if distance is not enough, but is better than the current best option
  165. if(minDistance > bestTau) {
  166. bestTau = minDistance;
  167. bestMarker = currentMarker;
  168. }
  169. // if number of unproductive iterarions has been reached, accept the current best option
  170. if(unproductiveIterations == maxUnproductiveIterations) {
  171. unproductiveIterations = 0;
  172. tau = bestTau;
  173. bestTau = 0;
  174. Mat bytes = aruco::Dictionary::getByteListFromBits(bestMarker);
  175. out.bytesList.push_back(bytes);
  176. }
  177. }
  178. }
  179. // update the maximum number of correction bits for the generated dictionary
  180. out.maxCorrectionBits = (tau - 1) / 2;
  181. return out;
  182. }
  183. static inline int getMinDistForDict(const aruco::Dictionary& dict) {
  184. const int dict_size = dict.bytesList.rows;
  185. const int marker_size = dict.markerSize;
  186. int minDist = marker_size * marker_size;
  187. for (int i = 0; i < dict_size; i++) {
  188. Mat row = dict.bytesList.row(i);
  189. Mat marker = dict.getBitsFromByteList(row, marker_size);
  190. for (int j = 0; j < dict_size; j++) {
  191. if (j != i) {
  192. minDist = min(dict.getDistanceToId(marker, j), minDist);
  193. }
  194. }
  195. }
  196. return minDist;
  197. }
  198. static inline int getMinAsymDistForDict(const aruco::Dictionary& dict) {
  199. const int dict_size = dict.bytesList.rows;
  200. const int marker_size = dict.markerSize;
  201. int minDist = marker_size * marker_size;
  202. for (int i = 0; i < dict_size; i++)
  203. {
  204. Mat row = dict.bytesList.row(i);
  205. Mat marker = dict.getBitsFromByteList(row, marker_size);
  206. for (int j = 0; j < dict_size; j++)
  207. {
  208. if (j != i)
  209. {
  210. minDist = min(getFlipDistanceToId(dict, marker, j), minDist);
  211. }
  212. }
  213. }
  214. return minDist;
  215. }
  216. const char* keys =
  217. "{@outfile |<none> | Output file with custom dict }"
  218. "{r | false | Calculate the metric considering flipped markers }"
  219. "{d | | Dictionary Name: DICT_4X4_50, DICT_4X4_100, DICT_4X4_250,"
  220. "DICT_4X4_1000, DICT_5X5_50, DICT_5X5_100, DICT_5X5_250, DICT_5X5_1000, "
  221. "DICT_6X6_50, DICT_6X6_100, DICT_6X6_250, DICT_6X6_1000, DICT_7X7_50,"
  222. "DICT_7X7_100, DICT_7X7_250, DICT_7X7_1000, DICT_ARUCO_ORIGINAL,"
  223. "DICT_APRILTAG_16h5, DICT_APRILTAG_25h9, DICT_APRILTAG_36h10,"
  224. "DICT_APRILTAG_36h11}"
  225. "{nMarkers | | Number of markers in the dictionary }"
  226. "{markerSize | | Marker size }"
  227. "{cd | | Input file with custom dictionary }";
  228. const char* about =
  229. "This program can be used to calculate the ArUco dictionary metric.\n"
  230. "To calculate the metric considering flipped markers use -'r' flag.\n"
  231. "This program can be used to create and write the custom ArUco dictionary.\n";
  232. int main(int argc, char *argv[])
  233. {
  234. CommandLineParser parser(argc, argv, keys);
  235. parser.about(about);
  236. if(argc < 2) {
  237. parser.printMessage();
  238. return 0;
  239. }
  240. string outputFile = parser.get<String>(0);
  241. int nMarkers = parser.get<int>("nMarkers");
  242. int markerSize = parser.get<int>("markerSize");
  243. bool checkFlippedMarkers = parser.get<bool>("r");
  244. aruco::Dictionary dictionary = aruco::getPredefinedDictionary(cv::aruco::DICT_4X4_50);
  245. if (parser.has("d")) {
  246. string arucoDictName = parser.get<string>("d");
  247. cv::aruco::PredefinedDictionaryType arucoDict;
  248. if (arucoDictName == "DICT_4X4_50") { arucoDict = cv::aruco::DICT_4X4_50; }
  249. else if (arucoDictName == "DICT_4X4_100") { arucoDict = cv::aruco::DICT_4X4_100; }
  250. else if (arucoDictName == "DICT_4X4_250") { arucoDict = cv::aruco::DICT_4X4_250; }
  251. else if (arucoDictName == "DICT_4X4_1000") { arucoDict = cv::aruco::DICT_4X4_1000; }
  252. else if (arucoDictName == "DICT_5X5_50") { arucoDict = cv::aruco::DICT_5X5_50; }
  253. else if (arucoDictName == "DICT_5X5_100") { arucoDict = cv::aruco::DICT_5X5_100; }
  254. else if (arucoDictName == "DICT_5X5_250") { arucoDict = cv::aruco::DICT_5X5_250; }
  255. else if (arucoDictName == "DICT_5X5_1000") { arucoDict = cv::aruco::DICT_5X5_1000; }
  256. else if (arucoDictName == "DICT_6X6_50") { arucoDict = cv::aruco::DICT_6X6_50; }
  257. else if (arucoDictName == "DICT_6X6_100") { arucoDict = cv::aruco::DICT_6X6_100; }
  258. else if (arucoDictName == "DICT_6X6_250") { arucoDict = cv::aruco::DICT_6X6_250; }
  259. else if (arucoDictName == "DICT_6X6_1000") { arucoDict = cv::aruco::DICT_6X6_1000; }
  260. else if (arucoDictName == "DICT_7X7_50") { arucoDict = cv::aruco::DICT_7X7_50; }
  261. else if (arucoDictName == "DICT_7X7_100") { arucoDict = cv::aruco::DICT_7X7_100; }
  262. else if (arucoDictName == "DICT_7X7_250") { arucoDict = cv::aruco::DICT_7X7_250; }
  263. else if (arucoDictName == "DICT_7X7_1000") { arucoDict = cv::aruco::DICT_7X7_1000; }
  264. else if (arucoDictName == "DICT_ARUCO_ORIGINAL") { arucoDict = cv::aruco::DICT_ARUCO_ORIGINAL; }
  265. else if (arucoDictName == "DICT_APRILTAG_16h5") { arucoDict = cv::aruco::DICT_APRILTAG_16h5; }
  266. else if (arucoDictName == "DICT_APRILTAG_25h9") { arucoDict = cv::aruco::DICT_APRILTAG_25h9; }
  267. else if (arucoDictName == "DICT_APRILTAG_36h10") { arucoDict = cv::aruco::DICT_APRILTAG_36h10; }
  268. else if (arucoDictName == "DICT_APRILTAG_36h11") { arucoDict = cv::aruco::DICT_APRILTAG_36h11; }
  269. else {
  270. cout << "incorrect name of aruco dictionary \n";
  271. return 1;
  272. }
  273. dictionary = aruco::getPredefinedDictionary(arucoDict);
  274. }
  275. else if (parser.has("cd")) {
  276. FileStorage fs(parser.get<std::string>("cd"), FileStorage::READ);
  277. bool readOk = dictionary.readDictionary(fs.root());
  278. if(!readOk) {
  279. cerr << "Invalid dictionary file" << endl;
  280. return 0;
  281. }
  282. }
  283. else if (outputFile.empty() || nMarkers == 0 || markerSize == 0) {
  284. cerr << "Dictionary not specified" << endl;
  285. return 0;
  286. }
  287. if (!outputFile.empty() && nMarkers > 0 && markerSize > 0)
  288. {
  289. FileStorage fs(outputFile, FileStorage::WRITE);
  290. if (checkFlippedMarkers)
  291. dictionary = generateCustomAsymmetricDictionary(nMarkers, markerSize, aruco::Dictionary(), 0);
  292. else
  293. dictionary = aruco::extendDictionary(nMarkers, markerSize, aruco::Dictionary(), 0);
  294. dictionary.writeDictionary(fs);
  295. }
  296. if (checkFlippedMarkers) {
  297. cout << "Hamming distance: " << getMinAsymDistForDict(dictionary) << endl;
  298. }
  299. else {
  300. cout << "Hamming distance: " << getMinDistForDict(dictionary) << endl;
  301. }
  302. return 0;
  303. }