Merge remote-tracking branch 'upstream/3.4' into merge-3.4

pull/3212/head
OpenCV Developers 3 years ago
commit b8e4061a3f
  1. 2
      modules/aruco/include/opencv2/aruco.hpp
  2. 51
      modules/aruco/include/opencv2/aruco/dictionary.hpp
  3. 30
      modules/aruco/misc/python/test/test_aruco.py
  4. 267
      modules/aruco/samples/aruco_dict_utils.cpp
  5. 6
      modules/aruco/samples/calibrate_camera.cpp
  6. 6
      modules/aruco/samples/calibrate_camera_charuco.cpp
  7. 4
      modules/aruco/samples/create_board.cpp
  8. 4
      modules/aruco/samples/create_board_charuco.cpp
  9. 4
      modules/aruco/samples/create_marker.cpp
  10. 6
      modules/aruco/samples/detect_board.cpp
  11. 6
      modules/aruco/samples/detect_board_charuco.cpp
  12. 6
      modules/aruco/samples/detect_diamonds.cpp
  13. 6
      modules/aruco/samples/detect_markers.cpp
  14. 2
      modules/aruco/src/apriltag_quad_thresh.cpp
  15. 49
      modules/aruco/src/aruco.cpp
  16. 32
      modules/aruco/src/dictionary.cpp
  17. 91
      modules/aruco/test/test_arucodetection.cpp
  18. 8
      modules/aruco/test/test_charucodetection.cpp
  19. 16
      modules/aruco/tutorials/aruco_faq/aruco_faq.markdown
  20. 9
      modules/ximgproc/src/edge_drawing.cpp

@ -169,7 +169,7 @@ struct CV_EXPORTS_W DetectorParameters {
DetectorParameters();
CV_WRAP static Ptr<DetectorParameters> create();
CV_WRAP static bool readDetectorParameters(const FileNode& fn, Ptr<DetectorParameters>& params);
CV_WRAP bool readDetectorParameters(const FileNode& fn);
CV_PROP_RW int adaptiveThreshWinSizeMin;
CV_PROP_RW int adaptiveThreshWinSizeMax;

@ -94,15 +94,20 @@ class CV_EXPORTS_W Dictionary {
const Ptr<Dictionary> &baseDictionary, int randomSeed=0);
/**
* @brief Read a new dictionary from FileNode. Format:
* nmarkers: 35
* markersize: 6
* marker_0: "101011111011111001001001101100000000"
* ...
* @brief Read a new dictionary from FileNode. Format:\n
* nmarkers: 35\n
* markersize: 6\n
* maxCorrectionBits: 5\n
* marker_0: "101011111011111001001001101100000000"\n
* ...\n
* marker_34: "011111010000111011111110110101100101"
*/
CV_WRAP static bool readDictionary(const cv::FileNode& fn, cv::Ptr<cv::aruco::Dictionary> &dictionary);
CV_WRAP bool readDictionary(const cv::FileNode& fn);
/**
* @brief Write a dictionary to FileStorage. Format is the same as in readDictionary().
*/
CV_WRAP void writeDictionary(Ptr<FileStorage>& fs);
/**
* @see getPredefinedDictionary
*/
@ -149,23 +154,23 @@ class CV_EXPORTS_W Dictionary {
distance
*/
enum PREDEFINED_DICTIONARY_NAME {
DICT_4X4_50 = 0,
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_4X4_50 = 0, ///< 4x4 bits, minimum hamming distance between any two codes = 4, 50 codes
DICT_4X4_100, ///< 4x4 bits, minimum hamming distance between any two codes = 3, 100 codes
DICT_4X4_250, ///< 4x4 bits, minimum hamming distance between any two codes = 3, 250 codes
DICT_4X4_1000, ///< 4x4 bits, minimum hamming distance between any two codes = 2, 1000 codes
DICT_5X5_50, ///< 5x5 bits, minimum hamming distance between any two codes = 8, 50 codes
DICT_5X5_100, ///< 5x5 bits, minimum hamming distance between any two codes = 7, 100 codes
DICT_5X5_250, ///< 5x5 bits, minimum hamming distance between any two codes = 6, 250 codes
DICT_5X5_1000, ///< 5x5 bits, minimum hamming distance between any two codes = 5, 1000 codes
DICT_6X6_50, ///< 6x6 bits, minimum hamming distance between any two codes = 13, 50 codes
DICT_6X6_100, ///< 6x6 bits, minimum hamming distance between any two codes = 12, 100 codes
DICT_6X6_250, ///< 6x6 bits, minimum hamming distance between any two codes = 11, 250 codes
DICT_6X6_1000, ///< 6x6 bits, minimum hamming distance between any two codes = 9, 1000 codes
DICT_7X7_50, ///< 7x7 bits, minimum hamming distance between any two codes = 19, 50 codes
DICT_7X7_100, ///< 7x7 bits, minimum hamming distance between any two codes = 18, 100 codes
DICT_7X7_250, ///< 7x7 bits, minimum hamming distance between any two codes = 17, 250 codes
DICT_7X7_1000, ///< 7x7 bits, minimum hamming distance between any two codes = 14, 1000 codes
DICT_ARUCO_ORIGINAL, ///< 6x6 bits, minimum hamming distance between any two codes = 3, 1024 codes
DICT_APRILTAG_16h5, ///< 4x4 bits, minimum hamming distance between any two codes = 5, 30 codes
DICT_APRILTAG_25h9, ///< 5x5 bits, minimum hamming distance between any two codes = 9, 35 codes
DICT_APRILTAG_36h10, ///< 6x6 bits, minimum hamming distance between any two codes = 10, 2320 codes

@ -35,5 +35,35 @@ class aruco_test(NewOpenCVTests):
img = cv.aruco.drawCharucoDiamond(aruco_dict, np.array([0, 1, 2, 3]), 100, 80)
self.assertTrue(img is not None)
def test_write_read_dict(self):
try:
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_5X5_50)
markers_gold = aruco_dict.bytesList
# write aruco_dict
filename = "test_dict.yml"
fs_write = cv.FileStorage(filename, cv.FileStorage_WRITE)
aruco_dict.writeDictionary(fs_write)
fs_write.release()
# reset aruco_dict
aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_6X6_250)
# read aruco_dict
fs_read = cv.FileStorage(filename, cv.FileStorage_READ)
aruco_dict.readDictionary(fs_read.root())
fs_read.release()
# check equal
self.assertEqual(aruco_dict.markerSize, 5)
self.assertEqual(aruco_dict.maxCorrectionBits, 3)
np.testing.assert_array_equal(aruco_dict.bytesList, markers_gold)
finally:
if os.path.exists(filename):
os.remove(filename)
if __name__ == '__main__':
NewOpenCVTests.bootstrap()

@ -0,0 +1,267 @@
#include <opencv2/core/hal/hal.hpp>
#include <opencv2/aruco.hpp>
#include <iostream>
using namespace cv;
using namespace std;
static inline int _getSelfDistance(const Mat &marker) {
Mat bytes = aruco::Dictionary::getByteListFromBits(marker);
int minHamming = (int)marker.total() + 1;
for(int r = 1; r < 4; r++) {
int currentHamming = cv::hal::normHamming(bytes.ptr(), bytes.ptr() + bytes.cols*r, bytes.cols);
if(currentHamming < minHamming) minHamming = currentHamming;
}
Mat b;
flip(marker, b, 0);
Mat flipBytes = aruco::Dictionary::getByteListFromBits(b);
for(int r = 0; r < 4; r++) {
int currentHamming = cv::hal::normHamming(flipBytes.ptr(), bytes.ptr() + bytes.cols*r, bytes.cols);
if(currentHamming < minHamming) minHamming = currentHamming;
}
flip(marker, b, 1);
flipBytes = aruco::Dictionary::getByteListFromBits(b);
for(int r = 0; r < 4; r++) {
int currentHamming = cv::hal::normHamming(flipBytes.ptr(), bytes.ptr() + bytes.cols*r, bytes.cols);
if(currentHamming < minHamming) minHamming = currentHamming;
}
return minHamming;
}
static inline int getFlipDistanceToId(Ptr<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());
int currentMinDistance = int(bits.total() * bits.total());
for(unsigned int r = 0; r < nRotations; r++) {
int currentHamming = cv::hal::normHamming(
bytesList.ptr(id) + r*candidateBytes.cols,
candidateBytes.ptr(),
candidateBytes.cols);
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++) {
int currentHamming = cv::hal::normHamming(
bytesList.ptr(id) + r * candidateBytes.cols,
candidateBytes.ptr(),
candidateBytes.cols);
if (currentHamming < currentMinDistance) {
currentMinDistance = currentHamming;
}
}
flip(bits.getMat(), b, 1);
candidateBytes = aruco::Dictionary::getByteListFromBits(b);
for(unsigned int r = 0; r < nRotations; r++) {
int currentHamming = cv::hal::normHamming(
bytesList.ptr(id) + r * candidateBytes.cols,
candidateBytes.ptr(),
candidateBytes.cols);
if (currentHamming < currentMinDistance) {
currentMinDistance = currentHamming;
}
}
return currentMinDistance;
}
static inline Ptr<aruco::Dictionary> generateCustomAsymmetricDictionary(int nMarkers, int markerSize,
const Ptr<aruco::Dictionary> &baseDictionary, int randomSeed) {
RNG rng((uint64)(randomSeed));
Ptr<aruco::Dictionary> out = makePtr<aruco::Dictionary>();
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 Ptr<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 Ptr<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: DICT_4X4_50=0, DICT_4X4_100=1, DICT_4X4_250=2,"
"DICT_4X4_1000=3, DICT_5X5_50=4, DICT_5X5_100=5, DICT_5X5_250=6, DICT_5X5_1000=7, "
"DICT_6X6_50=8, DICT_6X6_100=9, DICT_6X6_250=10, DICT_6X6_1000=11, DICT_7X7_50=12,"
"DICT_7X7_100=13, DICT_7X7_250=14, DICT_7X7_1000=15, DICT_ARUCO_ORIGINAL = 16}"
"{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");
Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(0);
if (parser.has("d")) {
int dictionaryId = parser.get<int>("d");
dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId));
}
else if (parser.has("cd")) {
FileStorage fs(parser.get<std::string>("cd"), FileStorage::READ);
bool readOk = dictionary->aruco::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)
{
Ptr<FileStorage> fs = makePtr<FileStorage>(outputFile, FileStorage::WRITE);
if (checkFlippedMarkers)
dictionary = generateCustomAsymmetricDictionary(nMarkers, markerSize, makePtr<aruco::Dictionary>(), 0);
else
dictionary = aruco::generateCustomDictionary(nMarkers, markerSize, makePtr<aruco::Dictionary>(), 0);
dictionary->writeDictionary(fs);
}
if (checkFlippedMarkers) {
cout << getMinAsymDistForDict(dictionary) << endl;
}
else {
cout << getMinDistForDict(dictionary) << endl;
}
return 0;
}

@ -104,7 +104,7 @@ int main(int argc, char *argv[]) {
Ptr<aruco::DetectorParameters> detectorParams = aruco::DetectorParameters::create();
if(parser.has("dp")) {
FileStorage fs(parser.get<string>("dp"), FileStorage::READ);
bool readOk = aruco::DetectorParameters::readDetectorParameters(fs.root(), detectorParams);
bool readOk = detectorParams->readDetectorParameters(fs.root());
if(!readOk) {
cerr << "Invalid detector parameters file" << endl;
return 0;
@ -134,14 +134,14 @@ int main(int argc, char *argv[]) {
waitTime = 10;
}
Ptr<aruco::Dictionary> dictionary;
Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(0);
if (parser.has("d")) {
int dictionaryId = parser.get<int>("d");
dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId));
}
else if (parser.has("cd")) {
FileStorage fs(parser.get<std::string>("cd"), FileStorage::READ);
bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary);
bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root());
if(!readOk) {
cerr << "Invalid dictionary file" << endl;
return 0;

@ -105,7 +105,7 @@ int main(int argc, char *argv[]) {
Ptr<aruco::DetectorParameters> detectorParams = aruco::DetectorParameters::create();
if(parser.has("dp")) {
FileStorage fs(parser.get<string>("dp"), FileStorage::READ);
bool readOk = aruco::DetectorParameters::readDetectorParameters(fs.root(), detectorParams);
bool readOk = detectorParams->readDetectorParameters(fs.root());
if(!readOk) {
cerr << "Invalid detector parameters file" << endl;
return 0;
@ -135,14 +135,14 @@ int main(int argc, char *argv[]) {
waitTime = 10;
}
Ptr<aruco::Dictionary> dictionary;
Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(0);
if (parser.has("d")) {
int dictionaryId = parser.get<int>("d");
dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId));
}
else if (parser.has("cd")) {
FileStorage fs(parser.get<std::string>("cd"), FileStorage::READ);
bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary);
bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root());
if(!readOk) {
cerr << "Invalid dictionary file" << endl;
return 0;

@ -96,14 +96,14 @@ int main(int argc, char *argv[]) {
imageSize.height =
markersY * (markerLength + markerSeparation) - markerSeparation + 2 * margins;
Ptr<aruco::Dictionary> dictionary;
Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(0);
if (parser.has("d")) {
int dictionaryId = parser.get<int>("d");
dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId));
}
else if (parser.has("cd")) {
FileStorage fs(parser.get<std::string>("cd"), FileStorage::READ);
bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary);
bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root());
if(!readOk)
{
std::cerr << "Invalid dictionary file" << std::endl;

@ -93,14 +93,14 @@ int main(int argc, char *argv[]) {
return 0;
}
Ptr<aruco::Dictionary> dictionary;
Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(0);
if (parser.has("d")) {
int dictionaryId = parser.get<int>("d");
dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId));
}
else if (parser.has("cd")) {
FileStorage fs(parser.get<std::string>("cd"), FileStorage::READ);
bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary);
bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root());
if(!readOk) {
std::cerr << "Invalid dictionary file" << std::endl;
return 0;

@ -84,14 +84,14 @@ int main(int argc, char *argv[]) {
return 0;
}
Ptr<aruco::Dictionary> dictionary;
Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(0);
if (parser.has("d")) {
int dictionaryId = parser.get<int>("d");
dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId));
}
else if (parser.has("cd")) {
FileStorage fs(parser.get<std::string>("cd"), FileStorage::READ);
bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary);
bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root());
if(!readOk) {
std::cerr << "Invalid dictionary file" << std::endl;
return 0;

@ -100,7 +100,7 @@ int main(int argc, char *argv[]) {
Ptr<aruco::DetectorParameters> detectorParams = aruco::DetectorParameters::create();
if(parser.has("dp")) {
FileStorage fs(parser.get<string>("dp"), FileStorage::READ);
bool readOk = aruco::DetectorParameters::readDetectorParameters(fs.root(), detectorParams);
bool readOk = detectorParams->readDetectorParameters(fs.root());
if(!readOk) {
cerr << "Invalid detector parameters file" << endl;
return 0;
@ -118,14 +118,14 @@ int main(int argc, char *argv[]) {
return 0;
}
Ptr<aruco::Dictionary> dictionary;
Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(0);
if (parser.has("d")) {
int dictionaryId = parser.get<int>("d");
dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId));
}
else if (parser.has("cd")) {
FileStorage fs(parser.get<std::string>("cd"), FileStorage::READ);
bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary);
bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root());
if(!readOk) {
cerr << "Invalid dictionary file" << endl;
return 0;

@ -102,7 +102,7 @@ int main(int argc, char *argv[]) {
Ptr<aruco::DetectorParameters> detectorParams = aruco::DetectorParameters::create();
if(parser.has("dp")) {
FileStorage fs(parser.get<string>("dp"), FileStorage::READ);
bool readOk = aruco::DetectorParameters::readDetectorParameters(fs.root(), detectorParams);
bool readOk = detectorParams->readDetectorParameters(fs.root());
if(!readOk) {
cerr << "Invalid detector parameters file" << endl;
return 0;
@ -114,14 +114,14 @@ int main(int argc, char *argv[]) {
return 0;
}
Ptr<aruco::Dictionary> dictionary;
Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(0);
if (parser.has("d")) {
int dictionaryId = parser.get<int>("d");
dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId));
}
else if (parser.has("cd")) {
FileStorage fs(parser.get<std::string>("cd"), FileStorage::READ);
bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary);
bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root());
if(!readOk) {
cerr << "Invalid dictionary file" << endl;
return 0;

@ -90,7 +90,7 @@ int main(int argc, char *argv[]) {
Ptr<aruco::DetectorParameters> detectorParams = aruco::DetectorParameters::create();
if(parser.has("dp")) {
FileStorage fs(parser.get<string>("dp"), FileStorage::READ);
bool readOk = aruco::DetectorParameters::readDetectorParameters(fs.root(), detectorParams);
bool readOk = detectorParams->readDetectorParameters(fs.root());
if(!readOk) {
cerr << "Invalid detector parameters file" << endl;
return 0;
@ -114,14 +114,14 @@ int main(int argc, char *argv[]) {
return 0;
}
Ptr<aruco::Dictionary> dictionary;
Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(0);
if (parser.has("d")) {
int dictionaryId = parser.get<int>("d");
dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId));
}
else if (parser.has("cd")) {
FileStorage fs(parser.get<std::string>("cd"), FileStorage::READ);
bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary);
bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root());
if(!readOk) {
cerr << "Invalid dictionary file" << endl;
return 0;

@ -83,7 +83,7 @@ int main(int argc, char *argv[]) {
Ptr<aruco::DetectorParameters> detectorParams = aruco::DetectorParameters::create();
if(parser.has("dp")) {
FileStorage fs(parser.get<string>("dp"), FileStorage::READ);
bool readOk = aruco::DetectorParameters::readDetectorParameters(fs.root(), detectorParams);
bool readOk = detectorParams->readDetectorParameters(fs.root());
if(!readOk) {
cerr << "Invalid detector parameters file" << endl;
return 0;
@ -108,14 +108,14 @@ int main(int argc, char *argv[]) {
return 0;
}
Ptr<aruco::Dictionary> dictionary;
Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(0);
if (parser.has("d")) {
int dictionaryId = parser.get<int>("d");
dictionary = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(dictionaryId));
}
else if (parser.has("cd")) {
FileStorage fs(parser.get<std::string>("cd"), FileStorage::READ);
bool readOk = aruco::Dictionary::readDictionary(fs.root(), dictionary);
bool readOk = dictionary->aruco::Dictionary::readDictionary(fs.root());
if(!readOk) {
std::cerr << "Invalid dictionary file" << std::endl;
return 0;

@ -1523,7 +1523,7 @@ out = Mat::zeros(h, w, CV_8UC3);
zarray_t *quads = _zarray_create(sizeof(struct sQuad));
//int chunksize = 1 + sz / (APRILTAG_TASKS_PER_THREAD_TARGET * numberOfThreads);
int chunksize = h / (10 * getNumThreads());
int chunksize = std::max(1, h / (10 * getNumThreads()));
int sz = _zarray_size(clusters);
// TODO PARALLELIZE

@ -117,36 +117,35 @@ static inline bool readParameter(const FileNode& node, T& parameter)
/**
* @brief Read a new set of DetectorParameters from FileStorage.
*/
bool DetectorParameters::readDetectorParameters(const FileNode& fn, Ptr<DetectorParameters>& params)
bool DetectorParameters::readDetectorParameters(const FileNode& fn)
{
if(fn.empty())
return true;
params = DetectorParameters::create();
bool checkRead = false;
checkRead |= readParameter(fn["adaptiveThreshWinSizeMin"], params->adaptiveThreshWinSizeMin);
checkRead |= readParameter(fn["adaptiveThreshWinSizeMax"], params->adaptiveThreshWinSizeMax);
checkRead |= readParameter(fn["adaptiveThreshWinSizeStep"], params->adaptiveThreshWinSizeStep);
checkRead |= readParameter(fn["adaptiveThreshConstant"], params->adaptiveThreshConstant);
checkRead |= readParameter(fn["minMarkerPerimeterRate"], params->minMarkerPerimeterRate);
checkRead |= readParameter(fn["maxMarkerPerimeterRate"], params->maxMarkerPerimeterRate);
checkRead |= readParameter(fn["polygonalApproxAccuracyRate"], params->polygonalApproxAccuracyRate);
checkRead |= readParameter(fn["minCornerDistanceRate"], params->minCornerDistanceRate);
checkRead |= readParameter(fn["minDistanceToBorder"], params->minDistanceToBorder);
checkRead |= readParameter(fn["minMarkerDistanceRate"], params->minMarkerDistanceRate);
checkRead |= readParameter(fn["cornerRefinementMethod"], params->cornerRefinementMethod);
checkRead |= readParameter(fn["cornerRefinementWinSize"], params->cornerRefinementWinSize);
checkRead |= readParameter(fn["cornerRefinementMaxIterations"], params->cornerRefinementMaxIterations);
checkRead |= readParameter(fn["cornerRefinementMinAccuracy"], params->cornerRefinementMinAccuracy);
checkRead |= readParameter(fn["markerBorderBits"], params->markerBorderBits);
checkRead |= readParameter(fn["perspectiveRemovePixelPerCell"], params->perspectiveRemovePixelPerCell);
checkRead |= readParameter(fn["perspectiveRemoveIgnoredMarginPerCell"], params->perspectiveRemoveIgnoredMarginPerCell);
checkRead |= readParameter(fn["maxErroneousBitsInBorderRate"], params->maxErroneousBitsInBorderRate);
checkRead |= readParameter(fn["minOtsuStdDev"], params->minOtsuStdDev);
checkRead |= readParameter(fn["errorCorrectionRate"], params->errorCorrectionRate);
checkRead |= readParameter(fn["adaptiveThreshWinSizeMin"], this->adaptiveThreshWinSizeMin);
checkRead |= readParameter(fn["adaptiveThreshWinSizeMax"], this->adaptiveThreshWinSizeMax);
checkRead |= readParameter(fn["adaptiveThreshWinSizeStep"], this->adaptiveThreshWinSizeStep);
checkRead |= readParameter(fn["adaptiveThreshConstant"], this->adaptiveThreshConstant);
checkRead |= readParameter(fn["minMarkerPerimeterRate"], this->minMarkerPerimeterRate);
checkRead |= readParameter(fn["maxMarkerPerimeterRate"], this->maxMarkerPerimeterRate);
checkRead |= readParameter(fn["polygonalApproxAccuracyRate"], this->polygonalApproxAccuracyRate);
checkRead |= readParameter(fn["minCornerDistanceRate"], this->minCornerDistanceRate);
checkRead |= readParameter(fn["minDistanceToBorder"], this->minDistanceToBorder);
checkRead |= readParameter(fn["minMarkerDistanceRate"], this->minMarkerDistanceRate);
checkRead |= readParameter(fn["cornerRefinementMethod"], this->cornerRefinementMethod);
checkRead |= readParameter(fn["cornerRefinementWinSize"], this->cornerRefinementWinSize);
checkRead |= readParameter(fn["cornerRefinementMaxIterations"], this->cornerRefinementMaxIterations);
checkRead |= readParameter(fn["cornerRefinementMinAccuracy"], this->cornerRefinementMinAccuracy);
checkRead |= readParameter(fn["markerBorderBits"], this->markerBorderBits);
checkRead |= readParameter(fn["perspectiveRemovePixelPerCell"], this->perspectiveRemovePixelPerCell);
checkRead |= readParameter(fn["perspectiveRemoveIgnoredMarginPerCell"], this->perspectiveRemoveIgnoredMarginPerCell);
checkRead |= readParameter(fn["maxErroneousBitsInBorderRate"], this->maxErroneousBitsInBorderRate);
checkRead |= readParameter(fn["minOtsuStdDev"], this->minOtsuStdDev);
checkRead |= readParameter(fn["errorCorrectionRate"], this->errorCorrectionRate);
// new aruco 3 functionality
checkRead |= readParameter(fn["useAruco3Detection"], params->useAruco3Detection);
checkRead |= readParameter(fn["minSideLengthCanonicalImg"], params->minSideLengthCanonicalImg);
checkRead |= readParameter(fn["minMarkerLengthRatioOriginalImg"], params->minMarkerLengthRatioOriginalImg);
checkRead |= readParameter(fn["useAruco3Detection"], this->useAruco3Detection);
checkRead |= readParameter(fn["minSideLengthCanonicalImg"], this->minSideLengthCanonicalImg);
checkRead |= readParameter(fn["minMarkerLengthRatioOriginalImg"], this->minMarkerLengthRatioOriginalImg);
return checkRead;
}

@ -94,27 +94,45 @@ static inline bool readParameter(const FileNode& node, T& parameter)
return false;
}
bool Dictionary::readDictionary(const cv::FileNode& fn, cv::Ptr<cv::aruco::Dictionary> &dictionary)
bool Dictionary::readDictionary(const cv::FileNode& fn)
{
int nMarkers = 0, markerSize = 0;
if(fn.empty() || !readParameter(fn["nmarkers"], nMarkers) || !readParameter(fn["markersize"], markerSize))
int nMarkers = 0, _markerSize = 0;
if (fn.empty() || !readParameter(fn["nmarkers"], nMarkers) || !readParameter(fn["markersize"], _markerSize))
return false;
cv::Mat bytes(0, 0, CV_8UC1), marker(markerSize, markerSize, CV_8UC1);
Mat bytes(0, 0, CV_8UC1), marker(_markerSize, _markerSize, CV_8UC1);
std::string markerString;
for (int i = 0; i < nMarkers; i++) {
std::ostringstream ostr;
ostr << i;
if (!readParameter(fn["marker_" + ostr.str()], markerString))
return false;
for (int j = 0; j < (int) markerString.size(); j++)
marker.at<unsigned char>(j) = (markerString[j] == '0') ? 0 : 1;
bytes.push_back(cv::aruco::Dictionary::getByteListFromBits(marker));
bytes.push_back(Dictionary::getByteListFromBits(marker));
}
dictionary = cv::makePtr<cv::aruco::Dictionary>(bytes, markerSize);
int _maxCorrectionBits = 0;
readParameter(fn["maxCorrectionBits"], _maxCorrectionBits);
*this = Dictionary(bytes, _markerSize, _maxCorrectionBits);
return true;
}
void Dictionary::writeDictionary(Ptr<FileStorage>& fs) {
*fs << "nmarkers" << bytesList.rows;
*fs << "markersize" << markerSize;
*fs << "maxCorrectionBits" << maxCorrectionBits;
for (int i = 0; i < bytesList.rows; i++) {
Mat row = bytesList.row(i);;
Mat bitMarker = getBitsFromByteList(row, markerSize);
std::ostringstream ostr;
ostr << i;
string markerName = "marker_" + ostr.str();
string marker;
for (int j = 0; j < markerSize * markerSize; j++)
marker.push_back(bitMarker.at<uint8_t>(j) + '0');
*fs << markerName << marker;
}
}
/**
*/
Ptr<Dictionary> Dictionary::get(int dict) {

@ -605,10 +605,10 @@ TEST(CV_ArucoTutorial, can_find_gboriginal)
string imgPath = cvtest::findDataFile("gboriginal.png", false);
Mat image = imread(imgPath);
string dictPath = cvtest::findDataFile("tutorial_dict.yml", false);
cv::Ptr<cv::aruco::Dictionary> dictionary;
Ptr<aruco::Dictionary> dictionary = makePtr<aruco::Dictionary>();
FileStorage fs(dictPath, FileStorage::READ);
aruco::Dictionary::readDictionary(fs.root(), dictionary); // set marker from tutorial_dict.yml
dictionary->aruco::Dictionary::readDictionary(fs.root()); // set marker from tutorial_dict.yml
Ptr<aruco::DetectorParameters> detectorParams = aruco::DetectorParameters::create();
@ -728,4 +728,91 @@ TEST(CV_ArucoDetectMarkers, regression_2492)
}
}
struct ArucoThreading: public testing::TestWithParam<cv::aruco::CornerRefineMethod>
{
struct NumThreadsSetter {
NumThreadsSetter(const int num_threads)
: original_num_threads_(cv::getNumThreads()) {
cv::setNumThreads(num_threads);
}
~NumThreadsSetter() {
cv::setNumThreads(original_num_threads_);
}
private:
int original_num_threads_;
};
};
TEST_P(ArucoThreading, number_of_threads_does_not_change_results)
{
cv::Ptr<cv::aruco::DetectorParameters> params = cv::aruco::DetectorParameters::create();
// We are not testing against different dictionaries
// As we are interested mostly in small images, smaller
// markers is better -> 4x4
cv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_4X4_50);
// Height of the test image can be chosen quite freely
// We aim to test against small images as in those the
// number of threads has most effect
const int height_img = 20;
// Just to get nice white boarder
const int shift = height_img > 10 ? 5 : 1;
const int height_marker = height_img-2*shift;
// Create a test image
cv::Mat img_marker;
cv::aruco::drawMarker(dictionary, 23, height_marker, img_marker, 1);
// Copy to bigger image to get a white border
cv::Mat img(height_img, height_img, CV_8UC1, cv::Scalar(255));
img_marker.copyTo(img(cv::Rect(shift, shift, height_marker, height_marker)));
params->cornerRefinementMethod = GetParam();
std::vector<std::vector<cv::Point2f> > original_corners;
std::vector<int> original_ids;
{
NumThreadsSetter thread_num_setter(1);
cv::aruco::detectMarkers(img, dictionary, original_corners, original_ids, params);
}
ASSERT_EQ(original_ids.size(), 1ull);
ASSERT_EQ(original_corners.size(), 1ull);
int num_threads_to_test[] = { 2, 8, 16, 32, height_img-1, height_img, height_img+1};
for (size_t i_num_threads = 0; i_num_threads < sizeof(num_threads_to_test)/sizeof(int); ++i_num_threads) {
NumThreadsSetter thread_num_setter(num_threads_to_test[i_num_threads]);
std::vector<std::vector<cv::Point2f> > corners;
std::vector<int> ids;
cv::aruco::detectMarkers(img, dictionary, corners, ids, params);
// If we don't find any markers, the test is broken
ASSERT_EQ(ids.size(), 1ull);
// Make sure we got the same result as the first time
ASSERT_EQ(corners.size(), original_corners.size());
ASSERT_EQ(ids.size(), original_ids.size());
ASSERT_EQ(ids.size(), corners.size());
for (size_t i = 0; i < corners.size(); ++i) {
EXPECT_EQ(ids[i], original_ids[i]);
for (size_t j = 0; j < corners[i].size(); ++j) {
EXPECT_NEAR(corners[i][j].x, original_corners[i][j].x, 0.1f);
EXPECT_NEAR(corners[i][j].y, original_corners[i][j].y, 0.1f);
}
}
}
}
INSTANTIATE_TEST_CASE_P(
CV_ArucoDetectMarkers, ArucoThreading,
::testing::Values(
cv::aruco::CORNER_REFINE_NONE,
cv::aruco::CORNER_REFINE_SUBPIX,
cv::aruco::CORNER_REFINE_CONTOUR,
cv::aruco::CORNER_REFINE_APRILTAG
));
}} // namespace

@ -750,14 +750,14 @@ TEST(CV_ArucoTutorial, can_find_diamondmarkers)
Mat image = imread(imgPath);
string dictPath = cvtest::findDataFile("tutorial_dict.yml", false);
cv::Ptr<cv::aruco::Dictionary> dictionary;
Ptr<aruco::Dictionary> dictionary = makePtr<aruco::Dictionary>();
FileStorage fs(dictPath, FileStorage::READ);
aruco::Dictionary::readDictionary(fs.root(), dictionary); // set marker from tutorial_dict.yml
dictionary->aruco::Dictionary::readDictionary(fs.root()); // set marker from tutorial_dict.yml
string detectorPath = cvtest::findDataFile("detector_params.yml", false);
fs = FileStorage(detectorPath, FileStorage::READ);
Ptr<aruco::DetectorParameters> detectorParams;
aruco::DetectorParameters::readDetectorParameters(fs.root(), detectorParams);
Ptr<aruco::DetectorParameters> detectorParams = aruco::DetectorParameters::create();
detectorParams->readDetectorParameters(fs.root());
detectorParams->cornerRefinementMethod = 3;
vector< int > ids;

@ -26,6 +26,8 @@ in the ```DetectorParameters``` object. The first thing you can do is checking i
as rejected candidates by the ```detectMarkers()``` function. Depending on this, you should try to modify different parameters.
If you are using a ArUco board, you can also try the ```refineDetectedMarkers()``` function.
If you are [using big markers](https://github.com/opencv/opencv_contrib/issues/2811) (400x400 pixels and more), try increasing ```adaptiveThreshWinSizeMax``` value.
Also avoid [narrow borders](https://github.com/opencv/opencv_contrib/issues/2492) (5% or less of the marker perimeter, adjusted by ```minMarkerDistanceRate```) around markers.
- What are the benefits of ArUco boards? What are the drawbacks?
@ -101,7 +103,7 @@ correction during the identification step.
Dictionary generation should only be done once at the beginning of your application and it should take some seconds. If you are
generating the dictionary on each iteration of your detection loop, you are doing it wrong.
Furthermore, it is recommendable to save the dictionary to a file and read it on every execution so you dont need to generate it.
Furthermore, it is recommendable to save the dictionary to a file with ```cv::aruco::Dictionary::writeDictionary()``` and read it with ```cv::aruco::Dictionary::readDictionary()``` on every execution, so you don't need to generate it.
- I would like to use some markers of the original ArUco library that I have already printed, can I use them?
@ -131,7 +133,7 @@ If you manually modify the marker ids of the boards, or if you use a different t
- Does the aruco module provide functions to save the Dictionary or Board to file?
Not right now. However the data member of both the dictionary and board classes are public and can be easily stored.
You can use ```cv::aruco::Dictionary::writeDictionary()``` and ```cv::aruco::Dictionary::readDictionary()``` for ```cv::aruco::Dictionary```. The data member of board classes are public and can be easily stored.
- Alright, but how can I render a 3d model to create an augmented reality application?
@ -149,3 +151,13 @@ You can cite the original ArUco library:
> 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
- Pose estimation markers are not being detected correctly, what can I do?
It is important to remark that the estimation of the pose using only 4 coplanar points is subject to ambiguity.
In general, the ambiguity can be solved, if the camera is near to the marker.
However, as the marker becomes small, the errors in the corner estimation grows and ambiguity comes as a problem.
Try increasing the size of the marker you're using, and you can also try non-symmetrical (aruco_dict_utils.cpp) markers to avoid collisions.
Use multiple markers (ArUco/ChArUco/Diamonds boards) and pose estimation with estimatePoseBoard(), estimatePoseCharucoBoard().
Use solvePnP() with the ```SOLVEPNP_IPPE_SQUARE``` option.
More in [this issue](https://github.com/opencv/opencv/issues/8813).

@ -231,7 +231,7 @@ private:
static void DeallocateMatrix(double** m, int noRows);
static void AperB_T(double** A_, double** B_, double** _res, int _righA, int _colA, int _righB, int _colB);
static void AperB(double** A_, double** B_, double** _res, int _righA, int _colA, int _righB, int _colB);
static void jacobi(double** a, int n, double d[], double** v, int nrot);
static void jacobi(double** a, int n, double d[], double** v);
static void ROTATE(double** a, int i, int j, int k, int l, double tau, double s);
static double computeEllipsePerimeter(EllipseEquation* eq);
static double ComputeEllipseError(EllipseEquation* eq, double* px, double* py, int noPoints);
@ -5350,7 +5350,6 @@ bool EdgeDrawingImpl::EllipseFit(double* x, double* y, int noPoints, EllipseEqua
double** V = AllocateMatrix(7, 7);
double** sol = AllocateMatrix(7, 7);
double tx, ty;
int nrot = 0;
memset(d, 0, sizeof(double) * 7);
@ -5394,7 +5393,7 @@ bool EdgeDrawingImpl::EllipseFit(double* x, double* y, int noPoints, EllipseEqua
AperB_T(Const, invL, temp, 6, 6, 6, 6);
AperB(invL, temp, C, 6, 6, 6, 6);
jacobi(C, 6, d, V, nrot);
jacobi(C, 6, d, V);
A_TperB(invL, V, sol, 6, 6, 6, 6);
@ -5644,7 +5643,7 @@ void EdgeDrawingImpl::AperB(double** A_, double** B_, double** _res, int _righA,
}
}
void EdgeDrawingImpl::jacobi(double** a, int n, double d[], double** v, int nrot)
void EdgeDrawingImpl::jacobi(double** a, int n, double d[], double** v)
{
int j, iq, ip, i;
double tresh, theta, tau, t, sm, s, h, g, c;
@ -5665,7 +5664,6 @@ void EdgeDrawingImpl::jacobi(double** a, int n, double d[], double** v, int nrot
b[ip] = d[ip] = a[ip][ip];
z[ip] = 0.0;
}
nrot = 0;
for (i = 1; i <= 50; i++)
{
sm = 0.0;
@ -5729,7 +5727,6 @@ void EdgeDrawingImpl::jacobi(double** a, int n, double d[], double** v, int nrot
{
ROTATE(v, j, ip, j, iq, tau, s);
}
++nrot;
}
}
}

Loading…
Cancel
Save