mirror of
https://github.com/opencv/opencv.git
synced 2025-06-11 20:09:23 +08:00
include pixel-based uncertainty for aruco marker detection
This commit is contained in:
parent
2a8d4b8e43
commit
2ad416ed00
@ -318,6 +318,32 @@ public:
|
||||
CV_WRAP void detectMarkers(InputArray image, OutputArrayOfArrays corners, OutputArray ids,
|
||||
OutputArrayOfArrays rejectedImgPoints = noArray()) const;
|
||||
|
||||
/** @brief Marker detection with uncertainty computation
|
||||
*
|
||||
* @param image input image
|
||||
* @param corners vector of detected marker corners. For each marker, its four corners
|
||||
* are provided, (e.g std::vector<std::vector<cv::Point2f> > ). For N detected markers,
|
||||
* the dimensions of this array is Nx4. The order of the corners is clockwise.
|
||||
* @param ids vector of identifiers of the detected markers. The identifier is of type int
|
||||
* (e.g. std::vector<int>). For N detected markers, the size of ids is also N.
|
||||
* The identifiers have the same order than the markers in the imgPoints array.
|
||||
* @param rejectedImgPoints contains the imgPoints of those squares whose inner code has not a
|
||||
* correct codification. Useful for debugging purposes.
|
||||
* @param markersUnc contains the normalized uncertainty [0;1] of the markers' detection,
|
||||
* defined as percentage of incorrect pixel detections, with 0 describing a pixel perfect detection.
|
||||
* The uncertainties are of type float (e.g. std::vector<float>)
|
||||
*
|
||||
* Performs marker detection in the input image. Only markers included in the first specified dictionary
|
||||
* are searched. For each detected marker, it returns the 2D position of its corner in the image
|
||||
* and its corresponding identifier.
|
||||
* Note that this function does not perform pose estimation.
|
||||
* @note The function does not correct lens distortion or takes it into account. It's recommended to undistort
|
||||
* input image with corresponding camera model, if camera parameters are known
|
||||
* @sa undistort, estimatePoseSingleMarkers, estimatePoseBoard
|
||||
*/
|
||||
CV_WRAP void detectMarkersWithUnc(InputArray image, OutputArrayOfArrays corners, OutputArray ids,
|
||||
OutputArrayOfArrays rejectedImgPoints = noArray(), OutputArray markersUnc = noArray()) const;
|
||||
|
||||
/** @brief Refine not detected markers based on the already detected and the board layout
|
||||
*
|
||||
* @param image input image
|
||||
|
@ -71,6 +71,11 @@ class CV_EXPORTS_W_SIMPLE Dictionary {
|
||||
*/
|
||||
CV_WRAP int getDistanceToId(InputArray bits, int id, bool allRotations = true) const;
|
||||
|
||||
/** @brief Given a matrix containing the percentage of white pixels in each marker cell, returns the normalized marker uncertainty [0;1] for the specific id.
|
||||
* The uncertainty is defined as percentage of incorrect pixel detections, with 0 describing a pixel perfect detection.
|
||||
* The rotation is set to 0,1,2,3 for [0, 90, 180, 270] deg CCW rotations.
|
||||
*/
|
||||
CV_WRAP float getMarkerUnc(InputArray whitePixelRatio, int id, int rotation = 0, int borderBits = 1) const;
|
||||
|
||||
/** @brief Generate a canonical marker image
|
||||
*/
|
||||
|
@ -313,10 +313,10 @@ static void _detectInitialCandidates(const Mat &grey, vector<vector<Point2f> > &
|
||||
* the border bits
|
||||
*/
|
||||
static Mat _extractBits(InputArray _image, const vector<Point2f>& corners, int markerSize,
|
||||
int markerBorderBits, int cellSize, double cellMarginRate, double minStdDevOtsu) {
|
||||
int markerBorderBits, int cellSize, double cellMarginRate, double minStdDevOtsu, OutputArray _whitePixRatio = noArray()) {
|
||||
CV_Assert(_image.getMat().channels() == 1);
|
||||
CV_Assert(corners.size() == 4ull);
|
||||
CV_Assert(markerBorderBits > 0 && cellSize > 0 && cellMarginRate >= 0 && cellMarginRate <= 1);
|
||||
CV_Assert(markerBorderBits > 0 && cellSize > 0 && cellMarginRate >= 0 && cellMarginRate <= 0.5);
|
||||
CV_Assert(minStdDevOtsu >= 0);
|
||||
|
||||
// number of bits in the marker
|
||||
@ -339,6 +339,7 @@ static Mat _extractBits(InputArray _image, const vector<Point2f>& corners, int m
|
||||
|
||||
// output image containing the bits
|
||||
Mat bits(markerSizeWithBorders, markerSizeWithBorders, CV_8UC1, Scalar::all(0));
|
||||
Mat whitePixRatio(markerSizeWithBorders, markerSizeWithBorders, CV_32FC1, Scalar::all(0));
|
||||
|
||||
// check if standard deviation is enough to apply Otsu
|
||||
// if not enough, it probably means all bits are the same color (black or white)
|
||||
@ -349,10 +350,15 @@ static Mat _extractBits(InputArray _image, const vector<Point2f>& corners, int m
|
||||
meanStdDev(innerRegion, mean, stddev);
|
||||
if(stddev.ptr< double >(0)[0] < minStdDevOtsu) {
|
||||
// all black or all white, depending on mean value
|
||||
if(mean.ptr< double >(0)[0] > 127)
|
||||
if(mean.ptr< double >(0)[0] > 127){
|
||||
bits.setTo(1);
|
||||
else
|
||||
whitePixRatio.setTo(1);
|
||||
}
|
||||
else {
|
||||
bits.setTo(0);
|
||||
whitePixRatio.setTo(0);
|
||||
}
|
||||
if(_whitePixRatio.needed()) whitePixRatio.copyTo(_whitePixRatio);
|
||||
return bits;
|
||||
}
|
||||
|
||||
@ -369,9 +375,39 @@ static Mat _extractBits(InputArray _image, const vector<Point2f>& corners, int m
|
||||
// count white pixels on each cell to assign its value
|
||||
size_t nZ = (size_t) countNonZero(square);
|
||||
if(nZ > square.total() / 2) bits.at<unsigned char>(y, x) = 1;
|
||||
|
||||
if(_whitePixRatio.needed()){
|
||||
|
||||
// Get white pixel ratio from the complete cell
|
||||
if(cellMarginPixels > 0){
|
||||
// Consider the full cell. If perspectiveRemoveIgnoredMarginPerCell != 0, manually include the pixels of the margins
|
||||
Mat topRect = resultImg(Rect(Xstart - cellMarginPixels, Ystart - cellMarginPixels, cellSize, cellMarginPixels));
|
||||
size_t nZMarginPixels = (size_t) countNonZero(topRect);
|
||||
size_t totalMarginPixels = topRect.total();
|
||||
|
||||
Mat leftRect = resultImg(Rect(Xstart - cellMarginPixels, Ystart, cellMarginPixels, cellSize - 2 * cellMarginPixels));
|
||||
nZMarginPixels += (size_t) countNonZero(leftRect);
|
||||
totalMarginPixels += leftRect.total();
|
||||
|
||||
Mat bottomRect = resultImg(Rect(Xstart - cellMarginPixels, Ystart + cellSize - 2 * cellMarginPixels, cellSize, cellMarginPixels));
|
||||
nZMarginPixels += (size_t) countNonZero(bottomRect);
|
||||
totalMarginPixels += bottomRect.total();
|
||||
|
||||
Mat rightRect = resultImg(Rect(Xstart + cellSize - 2 * cellMarginPixels, Ystart, cellMarginPixels, cellSize - 2 * cellMarginPixels));
|
||||
nZMarginPixels += (size_t) countNonZero(rightRect);
|
||||
totalMarginPixels += rightRect.total();
|
||||
|
||||
whitePixRatio.at<float>(y, x) = (nZ + nZMarginPixels) / (float)(square.total() + totalMarginPixels);
|
||||
}
|
||||
else {
|
||||
whitePixRatio.at<float>(y, x) = (nZ / (float)square.total());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(_whitePixRatio.needed()) whitePixRatio.copyTo(_whitePixRatio);
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
@ -412,6 +448,7 @@ static int _getBorderErrors(const Mat &bits, int markerSize, int borderSize) {
|
||||
static uint8_t _identifyOneCandidate(const Dictionary& dictionary, const Mat& _image,
|
||||
const vector<Point2f>& _corners, int& idx,
|
||||
const DetectorParameters& params, int& rotation,
|
||||
float &markerUnc,
|
||||
const float scale = 1.f) {
|
||||
CV_DbgAssert(params.markerBorderBits > 0);
|
||||
uint8_t typ=1;
|
||||
@ -423,10 +460,12 @@ static uint8_t _identifyOneCandidate(const Dictionary& dictionary, const Mat& _i
|
||||
scaled_corners[i].y = _corners[i].y * scale;
|
||||
}
|
||||
|
||||
Mat whitePixRatio;
|
||||
Mat candidateBits =
|
||||
_extractBits(_image, scaled_corners, dictionary.markerSize, params.markerBorderBits,
|
||||
params.perspectiveRemovePixelPerCell,
|
||||
params.perspectiveRemoveIgnoredMarginPerCell, params.minOtsuStdDev);
|
||||
params.perspectiveRemoveIgnoredMarginPerCell, params.minOtsuStdDev,
|
||||
whitePixRatio);
|
||||
|
||||
// analyze border bits
|
||||
int maximumErrorsInBorder =
|
||||
@ -443,6 +482,7 @@ static uint8_t _identifyOneCandidate(const Dictionary& dictionary, const Mat& _i
|
||||
if(invBError<borderErrors){
|
||||
borderErrors = invBError;
|
||||
invertedImg.copyTo(candidateBits);
|
||||
whitePixRatio = -1.0 * whitePixRatio + 1;
|
||||
typ=2;
|
||||
}
|
||||
}
|
||||
@ -454,10 +494,19 @@ static uint8_t _identifyOneCandidate(const Dictionary& dictionary, const Mat& _i
|
||||
candidateBits.rows - params.markerBorderBits)
|
||||
.colRange(params.markerBorderBits, candidateBits.cols - params.markerBorderBits);
|
||||
|
||||
Mat onlyWhitePixRatio =
|
||||
whitePixRatio.rowRange(params.markerBorderBits,
|
||||
whitePixRatio.rows - params.markerBorderBits)
|
||||
.colRange(params.markerBorderBits, whitePixRatio.cols - params.markerBorderBits);
|
||||
|
||||
|
||||
// try to indentify the marker
|
||||
if(!dictionary.identify(onlyBits, idx, rotation, params.errorCorrectionRate))
|
||||
return 0;
|
||||
|
||||
// compute the candidate's uncertainty
|
||||
markerUnc = dictionary.getMarkerUnc(whitePixRatio, idx, rotation, params.markerBorderBits);
|
||||
|
||||
return typ;
|
||||
}
|
||||
|
||||
@ -657,7 +706,7 @@ struct ArucoDetector::ArucoDetectorImpl {
|
||||
* @brief Detect markers either using multiple or just first dictionary
|
||||
*/
|
||||
void detectMarkers(InputArray _image, OutputArrayOfArrays _corners, OutputArray _ids,
|
||||
OutputArrayOfArrays _rejectedImgPoints, OutputArray _dictIndices, DictionaryMode dictMode) {
|
||||
OutputArrayOfArrays _rejectedImgPoints, OutputArray _dictIndices, OutputArray _markersUnc, DictionaryMode dictMode) {
|
||||
CV_Assert(!_image.empty());
|
||||
|
||||
CV_Assert(detectorParams.markerBorderBits > 0);
|
||||
@ -717,6 +766,7 @@ struct ArucoDetector::ArucoDetectorImpl {
|
||||
vector<vector<Point2f> > candidates;
|
||||
vector<vector<Point> > contours;
|
||||
vector<int> ids;
|
||||
vector<float> markersUnc;
|
||||
|
||||
/// STEP 2.a Detect marker candidates :: using AprilTag
|
||||
if(detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_APRILTAG){
|
||||
@ -738,7 +788,7 @@ struct ArucoDetector::ArucoDetectorImpl {
|
||||
|
||||
/// STEP 2: Check candidate codification (identify markers)
|
||||
identifyCandidates(grey, grey_pyramid, selectedCandidates, candidates, contours,
|
||||
ids, dictionary, rejectedImgPoints);
|
||||
ids, dictionary, rejectedImgPoints, markersUnc);
|
||||
|
||||
/// STEP 3: Corner refinement :: use corner subpix
|
||||
if (detectorParams.cornerRefinementMethod == (int)CORNER_REFINE_SUBPIX) {
|
||||
@ -766,7 +816,7 @@ struct ArucoDetector::ArucoDetectorImpl {
|
||||
// temporary variable to store the current candidates
|
||||
vector<vector<Point2f>> currentCandidates;
|
||||
identifyCandidates(grey, grey_pyramid, candidatesPerDictionarySize.at(currentDictionary.markerSize), currentCandidates, contours,
|
||||
ids, currentDictionary, rejectedImgPoints);
|
||||
ids, currentDictionary, rejectedImgPoints, markersUnc);
|
||||
if (_dictIndices.needed()) {
|
||||
dictIndices.insert(dictIndices.end(), currentCandidates.size(), dictIndex);
|
||||
}
|
||||
@ -849,6 +899,9 @@ struct ArucoDetector::ArucoDetectorImpl {
|
||||
if (_dictIndices.needed()) {
|
||||
Mat(dictIndices).copyTo(_dictIndices);
|
||||
}
|
||||
if (_markersUnc.needed()) {
|
||||
Mat(markersUnc).copyTo(_markersUnc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -982,9 +1035,10 @@ struct ArucoDetector::ArucoDetectorImpl {
|
||||
*/
|
||||
void identifyCandidates(const Mat& grey, const vector<Mat>& image_pyr, vector<MarkerCandidateTree>& selectedContours,
|
||||
vector<vector<Point2f> >& accepted, vector<vector<Point> >& contours,
|
||||
vector<int>& ids, const Dictionary& currentDictionary, vector<vector<Point2f>>& rejected) const {
|
||||
vector<int>& ids, const Dictionary& currentDictionary, vector<vector<Point2f>>& rejected, vector<float>& markersUnc) const {
|
||||
size_t ncandidates = selectedContours.size();
|
||||
|
||||
vector<float> markersUncTmp(ncandidates, 1.f);
|
||||
vector<int> idsTmp(ncandidates, -1);
|
||||
vector<int> rotated(ncandidates, 0);
|
||||
vector<uint8_t> validCandidates(ncandidates, 0);
|
||||
@ -1018,11 +1072,11 @@ struct ArucoDetector::ArucoDetectorImpl {
|
||||
}
|
||||
const float scale = detectorParams.useAruco3Detection ? img.cols / static_cast<float>(grey.cols) : 1.f;
|
||||
|
||||
validCandidates[v] = _identifyOneCandidate(currentDictionary, img, selectedContours[v].corners, idsTmp[v], detectorParams, rotated[v], scale);
|
||||
validCandidates[v] = _identifyOneCandidate(currentDictionary, img, selectedContours[v].corners, idsTmp[v], detectorParams, rotated[v], markersUncTmp[v], scale);
|
||||
|
||||
if (validCandidates[v] == 0 && checkCloseContours) {
|
||||
for (const MarkerCandidate& closeMarkerCandidate: selectedContours[v].closeContours) {
|
||||
validCandidates[v] = _identifyOneCandidate(currentDictionary, img, closeMarkerCandidate.corners, idsTmp[v], detectorParams, rotated[v], scale);
|
||||
validCandidates[v] = _identifyOneCandidate(currentDictionary, img, closeMarkerCandidate.corners, idsTmp[v], detectorParams, rotated[v], markersUncTmp[v], scale);
|
||||
if (validCandidates[v] > 0) {
|
||||
selectedContours[v].corners = closeMarkerCandidate.corners;
|
||||
selectedContours[v].contour = closeMarkerCandidate.contour;
|
||||
@ -1058,6 +1112,7 @@ struct ArucoDetector::ArucoDetectorImpl {
|
||||
accepted.push_back(selectedContours[i].corners);
|
||||
contours.push_back(selectedContours[i].contour);
|
||||
ids.push_back(idsTmp[i]);
|
||||
markersUnc.push_back(markersUncTmp[i]);
|
||||
}
|
||||
else {
|
||||
rejected.push_back(selectedContours[i].corners);
|
||||
@ -1103,14 +1158,19 @@ ArucoDetector::ArucoDetector(const vector<Dictionary> &_dictionaries,
|
||||
arucoDetectorImpl = makePtr<ArucoDetectorImpl>(_dictionaries, _detectorParams, _refineParams);
|
||||
}
|
||||
|
||||
void ArucoDetector::detectMarkersWithUnc(InputArray _image, OutputArrayOfArrays _corners, OutputArray _ids,
|
||||
OutputArrayOfArrays _rejectedImgPoints, OutputArray _markersUnc) const {
|
||||
arucoDetectorImpl->detectMarkers(_image, _corners, _ids, _rejectedImgPoints, noArray(), _markersUnc, DictionaryMode::Single);
|
||||
}
|
||||
|
||||
void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corners, OutputArray _ids,
|
||||
OutputArrayOfArrays _rejectedImgPoints) const {
|
||||
arucoDetectorImpl->detectMarkers(_image, _corners, _ids, _rejectedImgPoints, noArray(), DictionaryMode::Single);
|
||||
arucoDetectorImpl->detectMarkers(_image, _corners, _ids, _rejectedImgPoints, noArray(), noArray(), DictionaryMode::Single);
|
||||
}
|
||||
|
||||
void ArucoDetector::detectMarkersMultiDict(InputArray _image, OutputArrayOfArrays _corners, OutputArray _ids,
|
||||
OutputArrayOfArrays _rejectedImgPoints, OutputArray _dictIndices) const {
|
||||
arucoDetectorImpl->detectMarkers(_image, _corners, _ids, _rejectedImgPoints, _dictIndices, DictionaryMode::Multi);
|
||||
arucoDetectorImpl->detectMarkers(_image, _corners, _ids, _rejectedImgPoints, _dictIndices, noArray(), DictionaryMode::Multi);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,6 +110,65 @@ bool Dictionary::identify(const Mat &onlyBits, int &idx, int &rotation, double m
|
||||
return idx != -1;
|
||||
}
|
||||
|
||||
float Dictionary::getMarkerUnc(InputArray _whitePixRatio, int id, int rotation, int borderSize) const {
|
||||
|
||||
CV_Assert(id >= 0 && id < bytesList.rows);
|
||||
const int sizeWithBorders = markerSize + 2 * borderSize;
|
||||
|
||||
Mat whitePixRatio = _whitePixRatio.getMat();
|
||||
|
||||
CV_Assert(markerSize > 0 && whitePixRatio.cols == sizeWithBorders && whitePixRatio.rows == sizeWithBorders);
|
||||
|
||||
// Get border uncertainty. Assuming black borders, the uncertainty is the ratio of white pixels.
|
||||
float tempBorderUnc = 0.f;
|
||||
for(int y = 0; y < sizeWithBorders; y++) {
|
||||
for(int k = 0; k < borderSize; k++) {
|
||||
// Left and right vertical sides
|
||||
tempBorderUnc += whitePixRatio.ptr<float>(y)[k];
|
||||
tempBorderUnc += whitePixRatio.ptr<float>(y)[sizeWithBorders - 1 - k];
|
||||
}
|
||||
}
|
||||
for(int x = borderSize; x < sizeWithBorders - borderSize; x++) {
|
||||
for(int k = 0; k < borderSize; k++) {
|
||||
// Top and bottom horizontal sides
|
||||
tempBorderUnc += whitePixRatio.ptr<float>(k)[x];
|
||||
tempBorderUnc += whitePixRatio.ptr<float>(sizeWithBorders - 1 - k)[x];
|
||||
}
|
||||
}
|
||||
|
||||
// Get the ground truth bits and rotate them:
|
||||
Mat groundTruthbits = getBitsFromByteList(bytesList.rowRange(id, id + 1), markerSize);
|
||||
CV_Assert(groundTruthbits.cols == markerSize && groundTruthbits.rows == markerSize);
|
||||
|
||||
if(rotation == 1){
|
||||
// 90 deg CCW
|
||||
transpose(groundTruthbits, groundTruthbits);
|
||||
flip(groundTruthbits, groundTruthbits,0);
|
||||
|
||||
} else if (rotation == 2){
|
||||
// 180 deg CCW
|
||||
flip(groundTruthbits, groundTruthbits,-1);
|
||||
|
||||
} else if (rotation == 3){
|
||||
// 90 deg CW
|
||||
transpose(groundTruthbits, groundTruthbits);
|
||||
flip(groundTruthbits, groundTruthbits,1);
|
||||
}
|
||||
|
||||
// Get the inner marker uncertainty. For a white or black cell, the uncertainty is the ratio of black or white pixels respectively.
|
||||
float tempInnerUnc = 0.f;
|
||||
for(int y = borderSize; y < markerSize + borderSize; y++) {
|
||||
for(int x = borderSize; x < markerSize + borderSize; x++) {
|
||||
tempInnerUnc += abs(groundTruthbits.ptr<unsigned char>(y - borderSize)[x - borderSize] - whitePixRatio.ptr<float>(y)[x]);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the overall normalized marker uncertainty
|
||||
float normalizedMarkerUnc = (tempInnerUnc + tempBorderUnc) / (sizeWithBorders * sizeWithBorders);
|
||||
|
||||
return normalizedMarkerUnc;
|
||||
}
|
||||
|
||||
|
||||
int Dictionary::getDistanceToId(InputArray bits, int id, bool allRotations) const {
|
||||
|
||||
|
@ -322,6 +322,148 @@ void CV_ArucoDetectionPerspective::run(int) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Draw 2D synthetic markers, temper with some pixels, detect them and compute their uncertainty.
|
||||
*/
|
||||
class CV_ArucoDetectionUnc : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_ArucoDetectionUnc(ArucoAlgParams arucoAlgParam) : arucoAlgParams(arucoAlgParam) {}
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
ArucoAlgParams arucoAlgParams;
|
||||
};
|
||||
|
||||
|
||||
void CV_ArucoDetectionUnc::run(int) {
|
||||
|
||||
aruco::DetectorParameters params;
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250), params);
|
||||
|
||||
// Params to test
|
||||
float ingnoreMarginPerCell[3] = {0.0, 0.1, 0.2};
|
||||
int borderBitsTest[3] = {1,2,3};
|
||||
|
||||
const int markerSidePixels = 150;
|
||||
const int imageSize = (markerSidePixels * 2) + 3 * (markerSidePixels / 2);
|
||||
|
||||
// 25 images containing 4 markers.
|
||||
for(int i = 0; i < 25; i++) {
|
||||
|
||||
// Modify default params
|
||||
params.perspectiveRemovePixelPerCell = 6 + i;
|
||||
params.perspectiveRemoveIgnoredMarginPerCell = ingnoreMarginPerCell[i % 3];
|
||||
params.markerBorderBits = borderBitsTest[i % 3];
|
||||
|
||||
// draw synthetic image
|
||||
vector<float > groundTruthUncs;
|
||||
vector<int> groundTruthIds;
|
||||
Mat img = Mat(imageSize, imageSize, CV_8UC1, Scalar::all(255));
|
||||
|
||||
// Invert the pixel value of a % of each cell [0%, 2%, 4%, ..., 48%]
|
||||
float invertPixelPercent = 2 * i / 100.f;
|
||||
int markerSizeWithBorders = 6 + 2 * params.markerBorderBits;
|
||||
int cellSidePixelsSize = markerSidePixels / markerSizeWithBorders;
|
||||
int cellSidePixelsInvert = int(sqrt(invertPixelPercent) * cellSidePixelsSize);
|
||||
int cellMarginPixels = (cellSidePixelsSize - cellSidePixelsInvert) / 2; // Invert center of the cell
|
||||
|
||||
float groundTruthUnc;
|
||||
|
||||
// Generate 4 markers
|
||||
for(int y = 0; y < 2; y++) {
|
||||
for(int x = 0; x < 2; x++) {
|
||||
Mat marker;
|
||||
int id = i * 4 + y * 2 + x;
|
||||
groundTruthIds.push_back(id);
|
||||
|
||||
// Generate marker
|
||||
aruco::generateImageMarker(detector.getDictionary(), id, markerSidePixels, marker, params.markerBorderBits);
|
||||
|
||||
// Test all 4 rotations: [0, 90, 180, 270]
|
||||
if(y == 0 && x == 0){
|
||||
// Rotate 90 deg CCW
|
||||
cv::transpose(marker, marker);
|
||||
cv::flip(marker, marker,0);
|
||||
} else if (y == 0 && x == 1){
|
||||
// Rotate 90 deg CW
|
||||
cv::transpose(marker, marker);
|
||||
cv::flip(marker, marker,1);
|
||||
} else if (y == 1 && x == 0){
|
||||
// Rotate 180 deg CCW
|
||||
cv::flip(marker, marker,-1);
|
||||
}
|
||||
|
||||
// Invert the pixel value of a % of each cell [0%, 2%, 4%, ..., 48%]
|
||||
if(cellSidePixelsInvert > 0){
|
||||
// loop over each cell
|
||||
for(int k = 0; k < markerSizeWithBorders; k++) {
|
||||
for(int p = 0; p < markerSizeWithBorders; p++) {
|
||||
int Xstart = p * (cellSidePixelsSize) + cellMarginPixels;
|
||||
int Ystart = k * (cellSidePixelsSize) + cellMarginPixels;
|
||||
Mat square(marker, Rect(Xstart, Ystart, cellSidePixelsInvert, cellSidePixelsInvert));
|
||||
square = ~square;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assume a perfect marker detection and thus a ground truth equal to the percentage of inverted pixels.
|
||||
groundTruthUnc = markerSizeWithBorders * markerSizeWithBorders * cellSidePixelsInvert * cellSidePixelsInvert / (float)(markerSidePixels * markerSidePixels);
|
||||
groundTruthUncs.push_back(groundTruthUnc);
|
||||
|
||||
// Make sure that the marker is still detected when it was highly tempered.
|
||||
if(groundTruthUnc >= 0.2) params.perspectiveRemoveIgnoredMarginPerCell = 0;
|
||||
|
||||
// Copy marker into full image
|
||||
Point2f firstCorner =
|
||||
Point2f(markerSidePixels / 2.f + x * (1.5f * markerSidePixels),
|
||||
markerSidePixels / 2.f + y * (1.5f * markerSidePixels));
|
||||
Mat aux = img.colRange((int)firstCorner.x, (int)firstCorner.x + markerSidePixels)
|
||||
.rowRange((int)firstCorner.y, (int)firstCorner.y + markerSidePixels);
|
||||
|
||||
marker.copyTo(aux);
|
||||
}
|
||||
}
|
||||
|
||||
// Test inverted markers
|
||||
if(ArucoAlgParams::DETECT_INVERTED_MARKER == arucoAlgParams){
|
||||
img = ~img;
|
||||
params.detectInvertedMarker = true;
|
||||
}
|
||||
|
||||
detector.setDetectorParameters(params);
|
||||
|
||||
// detect markers and compute uncertainty
|
||||
vector<vector<Point2f> > corners, rejected;
|
||||
vector<int> ids;
|
||||
vector<float> markerUnc;
|
||||
|
||||
detector.detectMarkersWithUnc(img, corners, ids, rejected, markerUnc);
|
||||
|
||||
// check detection results
|
||||
for(unsigned int m = 0; m < groundTruthIds.size(); m++) {
|
||||
int idx = -1;
|
||||
for(unsigned int k = 0; k < ids.size(); k++) {
|
||||
if(groundTruthIds[m] == ids[k]) {
|
||||
idx = (int)k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(idx == -1) {
|
||||
ts->printf(cvtest::TS::LOG, "Marker not detected");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
double dist = (double)cv::abs(groundTruthUncs[m] - markerUnc[idx]); // TODO cvtest
|
||||
if(dist > 0.05) {
|
||||
ts->printf(cvtest::TS::LOG, "Marker: %d is incorrect: uncertainty: %.2f (GT: %.2f) ", m, markerUnc[idx], groundTruthUncs[m]);
|
||||
ts->printf(cvtest::TS::LOG, "");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check max and min size in marker detection parameters
|
||||
*/
|
||||
@ -552,6 +694,18 @@ TEST(CV_ArucoBitCorrection, algorithmic) {
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
typedef CV_ArucoDetectionUnc CV_InvertedArucoDetectionUnc;
|
||||
|
||||
TEST(CV_ArucoDetectionUnc, algorithmic) {
|
||||
CV_ArucoDetectionUnc test(ArucoAlgParams::USE_DEFAULT);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_InvertedArucoDetectionUnc, algorithmic) {
|
||||
CV_InvertedArucoDetectionUnc test(ArucoAlgParams::DETECT_INVERTED_MARKER);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_ArucoDetectMarkers, regression_3192)
|
||||
{
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_4X4_50));
|
||||
|
Loading…
Reference in New Issue
Block a user