Skip to content

Commit c9a0dca

Browse files
author
AleksandrPanov
committed
add aruco_dict_utils.cpp
1 parent ef6b2e6 commit c9a0dca

File tree

2 files changed

+276
-5
lines changed

2 files changed

+276
-5
lines changed

modules/aruco/include/opencv2/aruco/dictionary.hpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,19 @@ class CV_EXPORTS_W Dictionary {
9494
const Ptr<Dictionary> &baseDictionary, int randomSeed=0);
9595

9696
/**
97-
* @brief Read a new dictionary from FileNode. Format:
98-
* nmarkers: 35
99-
* markersize: 6
100-
* marker_0: "101011111011111001001001101100000000"
101-
* ...
97+
* @brief Read a new dictionary from FileNode. Format:\n
98+
* nmarkers: 35\n
99+
* markersize: 6\n
100+
* maxCorrectionBits: 5\n
101+
* marker_0: "101011111011111001001001101100000000"\n
102+
* ...\n
102103
* marker_34: "011111010000111011111110110101100101"
103104
*/
104105
CV_WRAP bool readDictionary(const cv::FileNode& fn);
105106

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

0 commit comments

Comments
 (0)