mirror of https://github.com/opencv/opencv.git
Open Source Computer Vision Library
https://opencv.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
348 lines
14 KiB
348 lines
14 KiB
#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(0); |
|
|
|
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; |
|
}
|
|
|