mirror of
https://github.com/opencv/opencv.git
synced 2025-06-11 03:33:28 +08:00
Address more comments
Use map to manage unique marker size candidate trees. Avoid code duplication. Add a test to show double detection with overlapping dictionaries. Generalize to marker sizes of not only predefined dictionaries.
This commit is contained in:
parent
3084f950cf
commit
1aa658fa75
@ -10,7 +10,7 @@
|
|||||||
#include "apriltag/apriltag_quad_thresh.hpp"
|
#include "apriltag/apriltag_quad_thresh.hpp"
|
||||||
#include "aruco_utils.hpp"
|
#include "aruco_utils.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <unordered_set>
|
#include <map>
|
||||||
|
|
||||||
namespace cv {
|
namespace cv {
|
||||||
namespace aruco {
|
namespace aruco {
|
||||||
@ -752,44 +752,20 @@ struct ArucoDetector::ArucoDetectorImpl {
|
|||||||
|
|
||||||
/// STEP 3: Corner refinement :: use corner subpix
|
/// STEP 3: Corner refinement :: use corner subpix
|
||||||
if (detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_SUBPIX) {
|
if (detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_SUBPIX) {
|
||||||
CV_Assert(detectorParams.cornerRefinementWinSize > 0 && detectorParams.cornerRefinementMaxIterations > 0 &&
|
performCornerSubpixRefinement(grey, grey_pyramid, closest_pyr_image_idx, candidates, dictionary);
|
||||||
detectorParams.cornerRefinementMinAccuracy > 0);
|
|
||||||
// Do subpixel estimation. In Aruco3 start on the lowest pyramid level and upscale the corners
|
|
||||||
parallel_for_(Range(0, (int)candidates.size()), [&](const Range& range) {
|
|
||||||
const int begin = range.start;
|
|
||||||
const int end = range.end;
|
|
||||||
|
|
||||||
for (int i = begin; i < end; i++) {
|
|
||||||
if (detectorParams.useAruco3Detection) {
|
|
||||||
const float scale_init = (float) grey_pyramid[closest_pyr_image_idx].cols / grey.cols;
|
|
||||||
findCornerInPyrImage(scale_init, closest_pyr_image_idx, grey_pyramid, Mat(candidates[i]), detectorParams);
|
|
||||||
} else {
|
|
||||||
int cornerRefinementWinSize = std::max(1, cvRound(detectorParams.relativeCornerRefinmentWinSize*
|
|
||||||
getAverageModuleSize(candidates[i], dictionary.markerSize, detectorParams.markerBorderBits)));
|
|
||||||
cornerRefinementWinSize = min(cornerRefinementWinSize, detectorParams.cornerRefinementWinSize);
|
|
||||||
cornerSubPix(grey, Mat(candidates[i]), Size(cornerRefinementWinSize, cornerRefinementWinSize), Size(-1, -1),
|
|
||||||
TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS,
|
|
||||||
detectorParams.cornerRefinementMaxIterations,
|
|
||||||
detectorParams.cornerRefinementMinAccuracy));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else if (DictionaryMode::Multi == dictMode) {
|
} else if (DictionaryMode::Multi == dictMode) {
|
||||||
unordered_set<int> uniqueMarkerSizes;
|
map<size_t, vector<MarkerCandidateTree>> candidatesPerDictionarySize;
|
||||||
for (const Dictionary& dictionary : dictionaries) {
|
for (const Dictionary& dictionary : dictionaries) {
|
||||||
uniqueMarkerSizes.insert(dictionary.markerSize);
|
candidatesPerDictionarySize.emplace(dictionary.markerSize, vector<MarkerCandidateTree>());
|
||||||
}
|
}
|
||||||
|
|
||||||
// create at max 4 marker candidate trees for each dictionary size
|
// create candidate trees for each dictionary size
|
||||||
vector<vector<MarkerCandidateTree>> candidatesPerDictionarySize = {{}, {}, {}, {}};
|
for (auto& candidatesTreeEntry : candidatesPerDictionarySize) {
|
||||||
for (int markerSize : uniqueMarkerSizes) {
|
|
||||||
// min marker size is 4, so subtract 4 to get index
|
|
||||||
const auto dictionarySizeIndex = markerSize - 4;
|
|
||||||
// copy candidates
|
// copy candidates
|
||||||
vector<vector<Point2f>> candidatesCopy = candidates;
|
vector<vector<Point2f>> candidatesCopy = candidates;
|
||||||
vector<vector<Point> > contoursCopy = contours;
|
vector<vector<Point> > contoursCopy = contours;
|
||||||
candidatesPerDictionarySize[dictionarySizeIndex] = filterTooCloseCandidates(candidatesCopy, contoursCopy, markerSize);
|
candidatesTreeEntry.second = filterTooCloseCandidates(candidatesCopy, contoursCopy, candidatesTreeEntry.first);
|
||||||
}
|
}
|
||||||
candidates.clear();
|
candidates.clear();
|
||||||
contours.clear();
|
contours.clear();
|
||||||
@ -797,10 +773,9 @@ struct ArucoDetector::ArucoDetectorImpl {
|
|||||||
/// STEP 2: Check candidate codification (identify markers)
|
/// STEP 2: Check candidate codification (identify markers)
|
||||||
int dictIndex = 0;
|
int dictIndex = 0;
|
||||||
for (const Dictionary& currentDictionary : dictionaries) {
|
for (const Dictionary& currentDictionary : dictionaries) {
|
||||||
const auto dictionarySizeIndex = currentDictionary.markerSize - 4;
|
|
||||||
// temporary variable to store the current candidates
|
// temporary variable to store the current candidates
|
||||||
vector<vector<Point2f>> currentCandidates;
|
vector<vector<Point2f>> currentCandidates;
|
||||||
identifyCandidates(grey, grey_pyramid, candidatesPerDictionarySize[dictionarySizeIndex], currentCandidates, contours,
|
identifyCandidates(grey, grey_pyramid, candidatesPerDictionarySize.at(currentDictionary.markerSize), currentCandidates, contours,
|
||||||
ids, currentDictionary, rejectedImgPoints);
|
ids, currentDictionary, rejectedImgPoints);
|
||||||
if (_dictIndices.needed()) {
|
if (_dictIndices.needed()) {
|
||||||
dictIndices.insert(dictIndices.end(), currentCandidates.size(), dictIndex);
|
dictIndices.insert(dictIndices.end(), currentCandidates.size(), dictIndex);
|
||||||
@ -808,29 +783,7 @@ struct ArucoDetector::ArucoDetectorImpl {
|
|||||||
|
|
||||||
/// STEP 3: Corner refinement :: use corner subpix
|
/// STEP 3: Corner refinement :: use corner subpix
|
||||||
if (detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_SUBPIX) {
|
if (detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_SUBPIX) {
|
||||||
CV_Assert(detectorParams.cornerRefinementWinSize > 0 && detectorParams.cornerRefinementMaxIterations > 0 &&
|
performCornerSubpixRefinement(grey, grey_pyramid, closest_pyr_image_idx, currentCandidates, currentDictionary);
|
||||||
detectorParams.cornerRefinementMinAccuracy > 0);
|
|
||||||
// Do subpixel estimation. In Aruco3 start on the lowest pyramid level and upscale the corners
|
|
||||||
parallel_for_(Range(0, (int)currentCandidates.size()), [&](const Range& range) {
|
|
||||||
const int begin = range.start;
|
|
||||||
const int end = range.end;
|
|
||||||
|
|
||||||
for (int i = begin; i < end; i++) {
|
|
||||||
if (detectorParams.useAruco3Detection) {
|
|
||||||
const float scale_init = (float) grey_pyramid[closest_pyr_image_idx].cols / grey.cols;
|
|
||||||
findCornerInPyrImage(scale_init, closest_pyr_image_idx, grey_pyramid, Mat(currentCandidates[i]), detectorParams);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int cornerRefinementWinSize = std::max(1, cvRound(detectorParams.relativeCornerRefinmentWinSize*
|
|
||||||
getAverageModuleSize(currentCandidates[i], currentDictionary.markerSize, detectorParams.markerBorderBits)));
|
|
||||||
cornerRefinementWinSize = min(cornerRefinementWinSize, detectorParams.cornerRefinementWinSize);
|
|
||||||
cornerSubPix(grey, Mat(currentCandidates[i]), Size(cornerRefinementWinSize, cornerRefinementWinSize), Size(-1, -1),
|
|
||||||
TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS,
|
|
||||||
detectorParams.cornerRefinementMaxIterations,
|
|
||||||
detectorParams.cornerRefinementMinAccuracy));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
candidates.insert(candidates.end(), currentCandidates.begin(), currentCandidates.end());
|
candidates.insert(candidates.end(), currentCandidates.begin(), currentCandidates.end());
|
||||||
dictIndex++;
|
dictIndex++;
|
||||||
@ -1105,6 +1058,30 @@ struct ArucoDetector::ArucoDetectorImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void performCornerSubpixRefinement(const Mat& grey, const vector<Mat>& grey_pyramid, int closest_pyr_image_idx, const vector<vector<Point2f>>& candidates, const Dictionary& dictionary) const {
|
||||||
|
CV_Assert(detectorParams.cornerRefinementWinSize > 0 && detectorParams.cornerRefinementMaxIterations > 0 &&
|
||||||
|
detectorParams.cornerRefinementMinAccuracy > 0);
|
||||||
|
// Do subpixel estimation. In Aruco3 start on the lowest pyramid level and upscale the corners
|
||||||
|
parallel_for_(Range(0, (int)candidates.size()), [&](const Range& range) {
|
||||||
|
const int begin = range.start;
|
||||||
|
const int end = range.end;
|
||||||
|
|
||||||
|
for (int i = begin; i < end; i++) {
|
||||||
|
if (detectorParams.useAruco3Detection) {
|
||||||
|
const float scale_init = (float) grey_pyramid[closest_pyr_image_idx].cols / grey.cols;
|
||||||
|
findCornerInPyrImage(scale_init, closest_pyr_image_idx, grey_pyramid, Mat(candidates[i]), detectorParams);
|
||||||
|
} else {
|
||||||
|
int cornerRefinementWinSize = std::max(1, cvRound(detectorParams.relativeCornerRefinmentWinSize*
|
||||||
|
getAverageModuleSize(candidates[i], dictionary.markerSize, detectorParams.markerBorderBits)));
|
||||||
|
cornerRefinementWinSize = min(cornerRefinementWinSize, detectorParams.cornerRefinementWinSize);
|
||||||
|
cornerSubPix(grey, Mat(candidates[i]), Size(cornerRefinementWinSize, cornerRefinementWinSize), Size(-1, -1),
|
||||||
|
TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS,
|
||||||
|
detectorParams.cornerRefinementMaxIterations,
|
||||||
|
detectorParams.cornerRefinementMinAccuracy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ArucoDetector::ArucoDetector(const Dictionary &_dictionary,
|
ArucoDetector::ArucoDetector(const Dictionary &_dictionary,
|
||||||
|
@ -727,25 +727,55 @@ TEST(CV_ArucoMultiDict, multiMarkerDetection)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(CV_ArucoMultiDict, multiMarkerDoubleDetection)
|
||||||
|
{
|
||||||
|
const int markerSidePixels = 100;
|
||||||
|
const int imageWidth = 2 * markerSidePixels + 3 * (markerSidePixels / 2);
|
||||||
|
const int imageHeight = markerSidePixels + 2 * (markerSidePixels / 2);
|
||||||
|
vector<aruco::Dictionary> usedDictionaries = {
|
||||||
|
aruco::getPredefinedDictionary(aruco::DICT_5X5_50),
|
||||||
|
aruco::getPredefinedDictionary(aruco::DICT_5X5_100)
|
||||||
|
};
|
||||||
|
|
||||||
|
// draw synthetic image
|
||||||
|
Mat img = Mat(imageHeight, imageWidth, CV_8UC1, Scalar::all(255));
|
||||||
|
for(int y = 0; y < 2; y++) {
|
||||||
|
Mat marker;
|
||||||
|
int id = 49 + y;
|
||||||
|
auto dict = aruco::getPredefinedDictionary(aruco::DICT_5X5_100);
|
||||||
|
aruco::generateImageMarker(dict, id, markerSidePixels, marker);
|
||||||
|
Point2f firstCorner(markerSidePixels / 2.f + y * (1.5f * markerSidePixels),
|
||||||
|
markerSidePixels / 2.f);
|
||||||
|
Mat aux = img(Rect((int)firstCorner.x, (int)firstCorner.y, markerSidePixels, markerSidePixels));
|
||||||
|
marker.copyTo(aux);
|
||||||
|
}
|
||||||
|
img.convertTo(img, CV_8UC3);
|
||||||
|
|
||||||
|
aruco::ArucoDetector detector(usedDictionaries);
|
||||||
|
|
||||||
|
vector<vector<Point2f> > markerCorners;
|
||||||
|
vector<int> markerIds;
|
||||||
|
vector<vector<Point2f> > rejectedImgPts;
|
||||||
|
vector<int> dictIds;
|
||||||
|
detector.detectMarkersMultiDict(img, markerCorners, markerIds, rejectedImgPts, dictIds);
|
||||||
|
ASSERT_EQ(markerIds.size(), 3u);
|
||||||
|
ASSERT_EQ(dictIds.size(), 3u);
|
||||||
|
EXPECT_EQ(dictIds[0], 0); // 5X5_50
|
||||||
|
EXPECT_EQ(dictIds[1], 1); // 5X5_100
|
||||||
|
EXPECT_EQ(dictIds[2], 1); // 5X5_100
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST(CV_ArucoMultiDict, serialization)
|
TEST(CV_ArucoMultiDict, serialization)
|
||||||
{
|
{
|
||||||
const std::string fileName("test_aruco_serialization.json");
|
|
||||||
aruco::ArucoDetector detector;
|
aruco::ArucoDetector detector;
|
||||||
{
|
{
|
||||||
FileStorage fs(fileName, FileStorage::Mode::WRITE);
|
FileStorage fs_out(".json", FileStorage::WRITE + FileStorage::MEMORY);
|
||||||
if (!fs.isOpened()) {
|
ASSERT_TRUE(fs_out.isOpened());
|
||||||
cout << "[ SKIPPED ] " << "CV_ArucoMultiDict.serialization"
|
detector.write(fs_out);
|
||||||
<< " - Skipping due to 'File system write error.'" << endl;
|
std::string serialized_string = fs_out.releaseAndGetString();
|
||||||
return;
|
FileStorage test_fs(serialized_string, FileStorage::Mode::READ + FileStorage::MEMORY);
|
||||||
}
|
ASSERT_TRUE(test_fs.isOpened());
|
||||||
detector.write(fs);
|
|
||||||
fs.release();
|
|
||||||
FileStorage test_fs(fileName, FileStorage::Mode::READ);
|
|
||||||
if (!test_fs.isOpened()) {
|
|
||||||
cout << "[ SKIPPED ] " << "CV_ArucoMultiDict.serialization"
|
|
||||||
<< " - Skipping due to 'File system read error.'" << endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
aruco::ArucoDetector test_detector;
|
aruco::ArucoDetector test_detector;
|
||||||
test_detector.read(test_fs.root());
|
test_detector.read(test_fs.root());
|
||||||
// compare default constructor result
|
// compare default constructor result
|
||||||
@ -753,20 +783,12 @@ TEST(CV_ArucoMultiDict, serialization)
|
|||||||
}
|
}
|
||||||
detector.setDictionaries({aruco::getPredefinedDictionary(aruco::DICT_4X4_50), aruco::getPredefinedDictionary(aruco::DICT_5X5_100)});
|
detector.setDictionaries({aruco::getPredefinedDictionary(aruco::DICT_4X4_50), aruco::getPredefinedDictionary(aruco::DICT_5X5_100)});
|
||||||
{
|
{
|
||||||
FileStorage fs(fileName, FileStorage::Mode::WRITE);
|
FileStorage fs_out(".json", FileStorage::WRITE + FileStorage::MEMORY);
|
||||||
if (!fs.isOpened()) {
|
ASSERT_TRUE(fs_out.isOpened());
|
||||||
cout << "[ SKIPPED ] " << "CV_ArucoMultiDict.serialization"
|
detector.write(fs_out);
|
||||||
<< " - Skipping due to 'File system write error.'" << endl;
|
std::string serialized_string = fs_out.releaseAndGetString();
|
||||||
return;
|
FileStorage test_fs(serialized_string, FileStorage::Mode::READ + FileStorage::MEMORY);
|
||||||
}
|
ASSERT_TRUE(test_fs.isOpened());
|
||||||
detector.write(fs);
|
|
||||||
fs.release();
|
|
||||||
FileStorage test_fs(fileName, FileStorage::Mode::READ);
|
|
||||||
if (!test_fs.isOpened()) {
|
|
||||||
cout << "[ SKIPPED ] " << "CV_ArucoMultiDict.serialization"
|
|
||||||
<< " - Skipping due to 'File system read error.'" << endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
aruco::ArucoDetector test_detector;
|
aruco::ArucoDetector test_detector;
|
||||||
test_detector.read(test_fs.root());
|
test_detector.read(test_fs.root());
|
||||||
// check for one additional dictionary
|
// check for one additional dictionary
|
||||||
|
Loading…
Reference in New Issue
Block a user