1// This file is part of OpenCV project.
2// It is subject to the license terms in the LICENSE file found in the top-level directory
3// of this distribution and at http://opencv.org/license.html
4
5#include "../precomp.hpp"
6#include "opencv2/core/hal/hal.hpp"
7
8#include "aruco_utils.hpp"
9#include "predefined_dictionaries.hpp"
10#include "apriltag/predefined_dictionaries_apriltag.hpp"
11#include <opencv2/objdetect/aruco_dictionary.hpp>
12
13namespace cv {
14namespace aruco {
15
16using namespace std;
17
18Dictionary::Dictionary(): markerSize(0), maxCorrectionBits(0) {}
19
20
21Dictionary::Dictionary(const Mat &_bytesList, int _markerSize, int _maxcorr) {
22 markerSize = _markerSize;
23 maxCorrectionBits = _maxcorr;
24 bytesList = _bytesList;
25}
26
27
28bool Dictionary::readDictionary(const cv::FileNode& fn) {
29 int nMarkers = 0, _markerSize = 0;
30 if (fn.empty() || !readParameter(name: "nmarkers", parameter&: nMarkers, node: fn) || !readParameter(name: "markersize", parameter&: _markerSize, node: fn))
31 return false;
32 Mat bytes(0, 0, CV_8UC1), marker(_markerSize, _markerSize, CV_8UC1);
33 std::string markerString;
34 for (int i = 0; i < nMarkers; i++) {
35 std::ostringstream ostr;
36 ostr << i;
37 if (!readParameter(name: "marker_" + ostr.str(), parameter&: markerString, node: fn))
38 return false;
39 for (int j = 0; j < (int) markerString.size(); j++)
40 marker.at<unsigned char>(i0: j) = (markerString[j] == '0') ? 0 : 1;
41 bytes.push_back(m: Dictionary::getByteListFromBits(bits: marker));
42 }
43 int _maxCorrectionBits = 0;
44 readParameter(name: "maxCorrectionBits", parameter&: _maxCorrectionBits, node: fn);
45 *this = Dictionary(bytes, _markerSize, _maxCorrectionBits);
46 return true;
47}
48
49void Dictionary::writeDictionary(FileStorage& fs, const String &name)
50{
51 CV_Assert(fs.isOpened());
52
53 if (!name.empty())
54 fs << name << "{";
55
56 fs << "nmarkers" << bytesList.rows;
57 fs << "markersize" << markerSize;
58 fs << "maxCorrectionBits" << maxCorrectionBits;
59 for (int i = 0; i < bytesList.rows; i++) {
60 Mat row = bytesList.row(y: i);;
61 Mat bitMarker = getBitsFromByteList(byteList: row, markerSize);
62 std::ostringstream ostr;
63 ostr << i;
64 string markerName = "marker_" + ostr.str();
65 string marker;
66 for (int j = 0; j < markerSize * markerSize; j++)
67 marker.push_back(c: bitMarker.at<uint8_t>(i0: j) + '0');
68 fs << markerName << marker;
69 }
70
71 if (!name.empty())
72 fs << "}";
73}
74
75
76bool Dictionary::identify(const Mat &onlyBits, int &idx, int &rotation, double maxCorrectionRate) const {
77 CV_Assert(onlyBits.rows == markerSize && onlyBits.cols == markerSize);
78
79 int maxCorrectionRecalculed = int(double(maxCorrectionBits) * maxCorrectionRate);
80
81 // get as a byte list
82 Mat candidateBytes = getByteListFromBits(bits: onlyBits);
83
84 idx = -1; // by default, not found
85
86 // search closest marker in dict
87 for(int m = 0; m < bytesList.rows; m++) {
88 int currentMinDistance = markerSize * markerSize + 1;
89 int currentRotation = -1;
90 for(unsigned int r = 0; r < 4; r++) {
91 int currentHamming = cv::hal::normHamming(
92 a: bytesList.ptr(y: m)+r*candidateBytes.cols,
93 b: candidateBytes.ptr(),
94 n: candidateBytes.cols);
95
96 if(currentHamming < currentMinDistance) {
97 currentMinDistance = currentHamming;
98 currentRotation = r;
99 }
100 }
101
102 // if maxCorrection is fulfilled, return this one
103 if(currentMinDistance <= maxCorrectionRecalculed) {
104 idx = m;
105 rotation = currentRotation;
106 break;
107 }
108 }
109
110 return idx != -1;
111}
112
113
114int Dictionary::getDistanceToId(InputArray bits, int id, bool allRotations) const {
115
116 CV_Assert(id >= 0 && id < bytesList.rows);
117
118 unsigned int nRotations = 4;
119 if(!allRotations) nRotations = 1;
120
121 Mat candidateBytes = getByteListFromBits(bits: bits.getMat());
122 int currentMinDistance = int(bits.total() * bits.total());
123 for(unsigned int r = 0; r < nRotations; r++) {
124 int currentHamming = cv::hal::normHamming(
125 a: bytesList.ptr(y: id) + r*candidateBytes.cols,
126 b: candidateBytes.ptr(),
127 n: candidateBytes.cols);
128
129 if(currentHamming < currentMinDistance) {
130 currentMinDistance = currentHamming;
131 }
132 }
133 return currentMinDistance;
134}
135
136
137void Dictionary::generateImageMarker(int id, int sidePixels, OutputArray _img, int borderBits) const {
138 CV_Assert(sidePixels >= (markerSize + 2*borderBits));
139 CV_Assert(id < bytesList.rows);
140 CV_Assert(borderBits > 0);
141
142 _img.create(rows: sidePixels, cols: sidePixels, CV_8UC1);
143
144 // create small marker with 1 pixel per bin
145 Mat tinyMarker(markerSize + 2 * borderBits, markerSize + 2 * borderBits, CV_8UC1,
146 Scalar::all(v0: 0));
147 Mat innerRegion = tinyMarker.rowRange(startrow: borderBits, endrow: tinyMarker.rows - borderBits)
148 .colRange(startcol: borderBits, endcol: tinyMarker.cols - borderBits);
149 // put inner bits
150 Mat bits = 255 * getBitsFromByteList(byteList: bytesList.rowRange(startrow: id, endrow: id + 1), markerSize);
151 CV_Assert(innerRegion.total() == bits.total());
152 bits.copyTo(m: innerRegion);
153
154 // resize tiny marker to output size
155 cv::resize(src: tinyMarker, dst: _img.getMat(), dsize: _img.getMat().size(), fx: 0, fy: 0, interpolation: INTER_NEAREST);
156}
157
158
159Mat Dictionary::getByteListFromBits(const Mat &bits) {
160 // integer ceil
161 int nbytes = (bits.cols * bits.rows + 8 - 1) / 8;
162
163 Mat candidateByteList(1, nbytes, CV_8UC4, Scalar::all(v0: 0));
164 unsigned char currentBit = 0;
165 int currentByte = 0;
166
167 // the 4 rotations
168 uchar* rot0 = candidateByteList.ptr();
169 uchar* rot1 = candidateByteList.ptr() + 1*nbytes;
170 uchar* rot2 = candidateByteList.ptr() + 2*nbytes;
171 uchar* rot3 = candidateByteList.ptr() + 3*nbytes;
172
173 for(int row = 0; row < bits.rows; row++) {
174 for(int col = 0; col < bits.cols; col++) {
175 // circular shift
176 rot0[currentByte] <<= 1;
177 rot1[currentByte] <<= 1;
178 rot2[currentByte] <<= 1;
179 rot3[currentByte] <<= 1;
180 // set bit
181 rot0[currentByte] |= bits.at<uchar>(i0: row, i1: col);
182 rot1[currentByte] |= bits.at<uchar>(i0: col, i1: bits.cols - 1 - row);
183 rot2[currentByte] |= bits.at<uchar>(i0: bits.rows - 1 - row, i1: bits.cols - 1 - col);
184 rot3[currentByte] |= bits.at<uchar>(i0: bits.rows - 1 - col, i1: row);
185 currentBit++;
186 if(currentBit == 8) {
187 // next byte
188 currentBit = 0;
189 currentByte++;
190 }
191 }
192 }
193 return candidateByteList;
194}
195
196
197Mat Dictionary::getBitsFromByteList(const Mat &byteList, int markerSize) {
198 CV_Assert(byteList.total() > 0 &&
199 byteList.total() >= (unsigned int)markerSize * markerSize / 8 &&
200 byteList.total() <= (unsigned int)markerSize * markerSize / 8 + 1);
201 Mat bits(markerSize, markerSize, CV_8UC1, Scalar::all(v0: 0));
202
203 unsigned char base2List[] = { 128, 64, 32, 16, 8, 4, 2, 1 };
204 int currentByteIdx = 0;
205 // we only need the bytes in normal rotation
206 unsigned char currentByte = byteList.ptr()[0];
207 int currentBit = 0;
208 for(int row = 0; row < bits.rows; row++) {
209 for(int col = 0; col < bits.cols; col++) {
210 if(currentByte >= base2List[currentBit]) {
211 bits.at<unsigned char>(i0: row, i1: col) = 1;
212 currentByte -= base2List[currentBit];
213 }
214 currentBit++;
215 if(currentBit == 8) {
216 currentByteIdx++;
217 currentByte = byteList.ptr()[currentByteIdx];
218 // if not enough bits for one more byte, we are in the end
219 // update bit position accordingly
220 if(8 * (currentByteIdx + 1) > (int)bits.total())
221 currentBit = 8 * (currentByteIdx + 1) - (int)bits.total();
222 else
223 currentBit = 0; // ok, bits enough for next byte
224 }
225 }
226 }
227 return bits;
228}
229
230
231Dictionary getPredefinedDictionary(PredefinedDictionaryType name) {
232 // DictionaryData constructors calls
233 // moved out of globals so construted on first use, which allows lazy-loading of opencv dll
234 static const Dictionary DICT_ARUCO_DATA = Dictionary(Mat(1024, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_ARUCO_BYTES), 5, 0);
235
236 static const Dictionary DICT_4X4_50_DATA = Dictionary(Mat(50, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 1);
237 static const Dictionary DICT_4X4_100_DATA = Dictionary(Mat(100, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 1);
238 static const Dictionary DICT_4X4_250_DATA = Dictionary(Mat(250, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 1);
239 static const Dictionary DICT_4X4_1000_DATA = Dictionary(Mat(1000, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 0);
240
241 static const Dictionary DICT_5X5_50_DATA = Dictionary(Mat(50, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 3);
242 static const Dictionary DICT_5X5_100_DATA = Dictionary(Mat(100, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 3);
243 static const Dictionary DICT_5X5_250_DATA = Dictionary(Mat(250, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 2);
244 static const Dictionary DICT_5X5_1000_DATA = Dictionary(Mat(1000, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 2);
245
246 static const Dictionary DICT_6X6_50_DATA = Dictionary(Mat(50, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 6);
247 static const Dictionary DICT_6X6_100_DATA = Dictionary(Mat(100, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 5);
248 static const Dictionary DICT_6X6_250_DATA = Dictionary(Mat(250, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 5);
249 static const Dictionary DICT_6X6_1000_DATA = Dictionary(Mat(1000, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 4);
250
251 static const Dictionary DICT_7X7_50_DATA = Dictionary(Mat(50, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 9);
252 static const Dictionary DICT_7X7_100_DATA = Dictionary(Mat(100, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 8);
253 static const Dictionary DICT_7X7_250_DATA = Dictionary(Mat(250, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 8);
254 static const Dictionary DICT_7X7_1000_DATA = Dictionary(Mat(1000, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 6);
255
256 static const Dictionary DICT_APRILTAG_16h5_DATA = Dictionary(Mat(30, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_16h5_BYTES), 4, 0);
257 static const Dictionary DICT_APRILTAG_25h9_DATA = Dictionary(Mat(35, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_25h9_BYTES), 5, 0);
258 static const Dictionary DICT_APRILTAG_36h10_DATA = Dictionary(Mat(2320, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h10_BYTES), 6, 0);
259 static const Dictionary DICT_APRILTAG_36h11_DATA = Dictionary(Mat(587, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h11_BYTES), 6, 0);
260
261 static const Dictionary DICT_ARUCO_MIP_36h12_DATA = Dictionary(Mat(250, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_ARUCO_MIP_36h12_BYTES), 6, 12);
262
263 switch(name) {
264
265 case DICT_ARUCO_ORIGINAL:
266 return Dictionary(DICT_ARUCO_DATA);
267
268 case DICT_4X4_50:
269 return Dictionary(DICT_4X4_50_DATA);
270 case DICT_4X4_100:
271 return Dictionary(DICT_4X4_100_DATA);
272 case DICT_4X4_250:
273 return Dictionary(DICT_4X4_250_DATA);
274 case DICT_4X4_1000:
275 return Dictionary(DICT_4X4_1000_DATA);
276
277 case DICT_5X5_50:
278 return Dictionary(DICT_5X5_50_DATA);
279 case DICT_5X5_100:
280 return Dictionary(DICT_5X5_100_DATA);
281 case DICT_5X5_250:
282 return Dictionary(DICT_5X5_250_DATA);
283 case DICT_5X5_1000:
284 return Dictionary(DICT_5X5_1000_DATA);
285
286 case DICT_6X6_50:
287 return Dictionary(DICT_6X6_50_DATA);
288 case DICT_6X6_100:
289 return Dictionary(DICT_6X6_100_DATA);
290 case DICT_6X6_250:
291 return Dictionary(DICT_6X6_250_DATA);
292 case DICT_6X6_1000:
293 return Dictionary(DICT_6X6_1000_DATA);
294
295 case DICT_7X7_50:
296 return Dictionary(DICT_7X7_50_DATA);
297 case DICT_7X7_100:
298 return Dictionary(DICT_7X7_100_DATA);
299 case DICT_7X7_250:
300 return Dictionary(DICT_7X7_250_DATA);
301 case DICT_7X7_1000:
302 return Dictionary(DICT_7X7_1000_DATA);
303
304 case DICT_APRILTAG_16h5:
305 return Dictionary(DICT_APRILTAG_16h5_DATA);
306 case DICT_APRILTAG_25h9:
307 return Dictionary(DICT_APRILTAG_25h9_DATA);
308 case DICT_APRILTAG_36h10:
309 return Dictionary(DICT_APRILTAG_36h10_DATA);
310 case DICT_APRILTAG_36h11:
311 return Dictionary(DICT_APRILTAG_36h11_DATA);
312
313 case DICT_ARUCO_MIP_36h12:
314 return Dictionary(DICT_ARUCO_MIP_36h12_DATA);
315 }
316 return Dictionary(DICT_4X4_50_DATA);
317}
318
319
320Dictionary getPredefinedDictionary(int dict) {
321 return getPredefinedDictionary(name: PredefinedDictionaryType(dict));
322}
323
324
325/**
326 * @brief Generates a random marker Mat of size markerSize x markerSize
327 */
328static Mat _generateRandomMarker(int markerSize, RNG &rng) {
329 Mat marker(markerSize, markerSize, CV_8UC1, Scalar::all(v0: 0));
330 for(int i = 0; i < markerSize; i++) {
331 for(int j = 0; j < markerSize; j++) {
332 unsigned char bit = (unsigned char) (rng.uniform(a: 0,b: 2));
333 marker.at<unsigned char>(i0: i, i1: j) = bit;
334 }
335 }
336 return marker;
337}
338
339/**
340 * @brief Calculate selfDistance of the codification of a marker Mat. Self distance is the Hamming
341 * distance of the marker to itself in the other rotations.
342 * See S. Garrido-Jurado, R. Muñoz-Salinas, F. J. Madrid-Cuevas, and M. J. Marín-Jiménez. 2014.
343 * "Automatic generation and detection of highly reliable fiducial markers under occlusion".
344 * Pattern Recogn. 47, 6 (June 2014), 2280-2292. DOI=10.1016/j.patcog.2014.01.005
345 */
346static int _getSelfDistance(const Mat &marker) {
347 Mat bytes = Dictionary::getByteListFromBits(bits: marker);
348 int minHamming = (int)marker.total() + 1;
349 for(int r = 1; r < 4; r++) {
350 int currentHamming = cv::hal::normHamming(a: bytes.ptr(), b: bytes.ptr() + bytes.cols*r, n: bytes.cols);
351 if(currentHamming < minHamming) minHamming = currentHamming;
352 }
353 return minHamming;
354}
355
356
357Dictionary extendDictionary(int nMarkers, int markerSize, const Dictionary &baseDictionary, int randomSeed) {
358 CV_Assert(nMarkers > 0);
359 RNG rng((uint64)(randomSeed));
360
361 Dictionary out = Dictionary(Mat(), markerSize);
362 out.markerSize = markerSize;
363
364 // theoretical maximum intermarker distance
365 // See S. Garrido-Jurado, R. Muñoz-Salinas, F. J. Madrid-Cuevas, and M. J. Marín-Jiménez. 2014.
366 // "Automatic generation and detection of highly reliable fiducial markers under occlusion".
367 // Pattern Recogn. 47, 6 (June 2014), 2280-2292. DOI=10.1016/j.patcog.2014.01.005
368 int C = (int)std::floor(x: float(markerSize * markerSize) / 4.f);
369 int tau = 2 * (int)std::floor(x: float(C) * 4.f / 3.f);
370
371 // if baseDictionary is provided, calculate its intermarker distance
372 if(baseDictionary.bytesList.rows > 0) {
373 CV_Assert(baseDictionary.markerSize == markerSize);
374 out.bytesList = baseDictionary.bytesList.rowRange(startrow: 0, endrow: min(a: nMarkers, b: baseDictionary.bytesList.rows)).clone();
375
376 int minDistance = markerSize * markerSize + 1;
377 for(int i = 0; i < out.bytesList.rows; i++) {
378 Mat markerBytes = out.bytesList.rowRange(startrow: i, endrow: i + 1);
379 Mat markerBits = Dictionary::getBitsFromByteList(byteList: markerBytes, markerSize);
380 minDistance = min(a: minDistance, b: _getSelfDistance(marker: markerBits));
381 for(int j = i + 1; j < out.bytesList.rows; j++) {
382 minDistance = min(a: minDistance, b: out.getDistanceToId(bits: markerBits, id: j));
383 }
384 }
385 tau = minDistance;
386 }
387
388 // current best option
389 int bestTau = 0;
390 Mat bestMarker;
391
392 // after these number of unproductive iterations, the best option is accepted
393 const int maxUnproductiveIterations = 5000;
394 int unproductiveIterations = 0;
395
396 while(out.bytesList.rows < nMarkers) {
397 Mat currentMarker = _generateRandomMarker(markerSize, rng);
398
399 int selfDistance = _getSelfDistance(marker: currentMarker);
400 int minDistance = selfDistance;
401
402 // if self distance is better or equal than current best option, calculate distance
403 // to previous accepted markers
404 if(selfDistance >= bestTau) {
405 for(int i = 0; i < out.bytesList.rows; i++) {
406 int currentDistance = out.getDistanceToId(bits: currentMarker, id: i);
407 minDistance = min(a: currentDistance, b: minDistance);
408 if(minDistance <= bestTau) {
409 break;
410 }
411 }
412 }
413
414 // if distance is high enough, accept the marker
415 if(minDistance >= tau) {
416 unproductiveIterations = 0;
417 bestTau = 0;
418 Mat bytes = Dictionary::getByteListFromBits(bits: currentMarker);
419 out.bytesList.push_back(m: bytes);
420 } else {
421 unproductiveIterations++;
422
423 // if distance is not enough, but is better than the current best option
424 if(minDistance > bestTau) {
425 bestTau = minDistance;
426 bestMarker = currentMarker;
427 }
428
429 // if number of unproductive iterarions has been reached, accept the current best option
430 if(unproductiveIterations == maxUnproductiveIterations) {
431 unproductiveIterations = 0;
432 tau = bestTau;
433 bestTau = 0;
434 Mat bytes = Dictionary::getByteListFromBits(bits: bestMarker);
435 out.bytesList.push_back(m: bytes);
436 }
437 }
438 }
439
440 // update the maximum number of correction bits for the generated dictionary
441 out.maxCorrectionBits = (tau - 1) / 2;
442
443 return out;
444}
445
446}
447}
448

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

source code of opencv/modules/objdetect/src/aruco/aruco_dictionary.cpp