Merge branch 4.x

This commit is contained in:
Alexander Smorkalov
2026-03-19 09:19:35 +03:00
67 changed files with 1881 additions and 555 deletions

View File

@@ -20,6 +20,8 @@ enum CornerRefineMethod{
CORNER_REFINE_APRILTAG, ///< Tag and corners detection based on the AprilTag 2 approach @cite wang2016iros
};
static constexpr float DEFAULT_VALID_BIT_ID_THRESHOLD{0.49f};
/** @brief struct DetectorParameters is used by ArucoDetector
*/
struct CV_EXPORTS_W_SIMPLE DetectorParameters {
@@ -49,7 +51,7 @@ struct CV_EXPORTS_W_SIMPLE DetectorParameters {
aprilTagQuadSigma = 0.0;
aprilTagMinClusterPixels = 5;
aprilTagMaxNmaxima = 10;
aprilTagCriticalRad = (float)(10* CV_PI /180);
aprilTagCriticalRad = (float)(10 * CV_PI / 180);
aprilTagMaxLineFitMse = 10.0;
aprilTagMinWhiteBlackDiff = 5;
aprilTagDeglitch = 0;
@@ -57,6 +59,7 @@ struct CV_EXPORTS_W_SIMPLE DetectorParameters {
useAruco3Detection = false;
minSideLengthCanonicalImg = 32;
minMarkerLengthRatioOriginalImg = 0.0;
validBitIdThreshold = DEFAULT_VALID_BIT_ID_THRESHOLD;
}
/** @brief Read a new set of DetectorParameters from FileNode (use FileStorage.root()).
@@ -168,12 +171,12 @@ struct CV_EXPORTS_W_SIMPLE DetectorParameters {
*/
CV_PROP_RW double maxErroneousBitsInBorderRate;
/** @brief minimun standard deviation in pixels values during the decodification step to apply Otsu
/** @brief minimum standard deviation in pixels values during the decodification step to apply Otsu
* thresholding (otherwise, all the bits are set to 0 or 1 depending on mean higher than 128 or not) (default 5.0)
*/
CV_PROP_RW double minOtsuStdDev;
/// error correction rate respect to the maximun error correction capability for each dictionary (default 0.6).
/// error correction rate respect to the maximum error correction capability for each dictionary (default 0.6).
CV_PROP_RW double errorCorrectionRate;
/** @brief April :: User-configurable parameters.
@@ -231,6 +234,9 @@ struct CV_EXPORTS_W_SIMPLE DetectorParameters {
/// range [0,1], eq (2) from paper. The parameter tau_i has a direct influence on the processing speed.
CV_PROP_RW float minMarkerLengthRatioOriginalImg;
/// range [0,1], define the acceptable threshold when comparing the detected marker to the dictionary during marker identification.
CV_PROP_RW float validBitIdThreshold;
};
/** @brief struct RefineParameters is used by ArucoDetector

View File

@@ -65,6 +65,12 @@ class CV_EXPORTS_W_SIMPLE Dictionary {
*/
CV_WRAP bool identify(const Mat &onlyBits, CV_OUT int &idx, CV_OUT int &rotation, double maxCorrectionRate) const;
/** @brief Given a matrix of pixel ratio raging from 0 to 1. Returns whether if marker is identified or not.
*
* Returns reference to the marker id in the dictionary (if any) and its rotation.
*/
CV_WRAP bool identify(const Mat &onlyCellPixelRatio, CV_OUT int &idx, CV_OUT int &rotation, double maxCorrectionRate, float validBitIdThreshold) const;
/** @brief Returns Hamming distance of the input bits to the specific id.
*
* If `allRotations` flag is set, the four possible marker rotations are considered
@@ -84,6 +90,10 @@ class CV_EXPORTS_W_SIMPLE Dictionary {
/** @brief Transform list of bytes to matrix of bits
*/
CV_WRAP static Mat getBitsFromByteList(const Mat &byteList, int markerSize, int rotationId = 0);
/** @brief Get ground truth bits float
*/
CV_WRAP Mat getMarkerBits(int markerId, int rotationId = 0) const;
};

View File

@@ -323,6 +323,7 @@ class aruco_objdetect_test(NewOpenCVTests):
imgSize = (500, 500)
params = cv.aruco.DetectorParameters()
params.minDistanceToBorder = 3
params.validBitIdThreshold = 0.5
board = cv.aruco.CharucoBoard((4, 4), 0.03, 0.015, cv.aruco.getPredefinedDictionary(cv.aruco.DICT_6X6_250))
detector = cv.aruco.CharucoDetector(board, detectorParams=params)

View File

@@ -310,12 +310,11 @@ static void _detectInitialCandidates(const Mat &grey, vector<vector<Point2f> > &
/**
* @brief Given an input image and candidate corners, extract the bits of the candidate, including
* @brief Given an input image and candidate corners, extract the cell pixel ratio of the candidate, including
* the border bits
*/
static Mat _extractBits(InputArray _image, const vector<Point2f>& corners, int markerSize,
int markerBorderBits, int cellSize, double cellMarginRate, double minStdDevOtsu,
OutputArray _cellPixelRatio = noArray()) {
static Mat _extractCellPixelRatio(InputArray _image, const vector<Point2f>& corners, int markerSize,
int markerBorderBits, int cellSize, double cellMarginRate, double minStdDevOtsu) {
CV_Assert(_image.getMat().channels() == 1);
CV_Assert(corners.size() == 4ull);
CV_Assert(markerBorderBits > 0 && cellSize > 0 && cellMarginRate >= 0 && cellMarginRate <= 0.5);
@@ -339,11 +338,11 @@ static Mat _extractBits(InputArray _image, const vector<Point2f>& corners, int m
warpPerspective(_image, resultImg, transformation, Size(resultImgSize, resultImgSize),
INTER_NEAREST);
// output image containing the bits
Mat bits(markerSizeWithBorders, markerSizeWithBorders, CV_8UC1, Scalar::all(0));
// output image containing the ratio of white pixels in each cell
Mat cellPixelRatio(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)
// if not enough, it probably means all pixels are the same color (black or white)
Mat mean, stddev;
// Remove some border just to avoid border noise from perspective transformation
Mat innerRegion = resultImg.colRange(cellSize / 2, resultImg.cols - cellSize / 2)
@@ -351,18 +350,13 @@ 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)
bits.setTo(1);
else
bits.setTo(0);
if(_cellPixelRatio.needed()) bits.convertTo(_cellPixelRatio, CV_32F);
return bits;
}
if(mean.ptr< double >(0)[0] > 127){
cellPixelRatio.setTo(1);
} else {
cellPixelRatio.setTo(0);
}
Mat cellPixelRatio;
if (_cellPixelRatio.needed()) {
_cellPixelRatio.create(markerSizeWithBorders, markerSizeWithBorders, CV_32FC1);
cellPixelRatio = _cellPixelRatio.getMatRef();
return cellPixelRatio;
}
// now extract code, first threshold using Otsu
@@ -377,38 +371,40 @@ static Mat _extractBits(InputArray _image, const vector<Point2f>& corners, int m
cellSize - 2 * cellMarginPixels));
// 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;
// define the cell pixel ratio as the ratio of the white pixels. For inverted markers, the ratio will be inverted.
if(_cellPixelRatio.needed()) cellPixelRatio.at<float>(y, x) = (nZ / (float)square.total());
cellPixelRatio.at<float>(y, x) = (nZ / (float)square.total());
}
}
return bits;
return cellPixelRatio;
}
/**
* @brief Return number of erroneous bits in border, i.e. number of white bits in border.
* @brief Return number of erroneous bits in border, i.e. bits for which pixel ratio > validBitIdThreshold.
*/
static int _getBorderErrors(const Mat &bits, int markerSize, int borderSize) {
static int _getBorderErrors(const Mat &cellPixelRatio, int markerSize, int borderSize, float validBitIdThreshold) {
int sizeWithBorders = markerSize + 2 * borderSize;
CV_Assert(markerSize > 0 && bits.cols == sizeWithBorders && bits.rows == sizeWithBorders);
CV_Assert(markerSize > 0 && cellPixelRatio.cols == sizeWithBorders && cellPixelRatio.rows == sizeWithBorders);
// Get border error. cellPixelRatio has the opposite color as the borders.
int totalErrors = 0;
for(int y = 0; y < sizeWithBorders; y++) {
for(int k = 0; k < borderSize; k++) {
if(bits.ptr<unsigned char>(y)[k] != 0) totalErrors++;
if(bits.ptr<unsigned char>(y)[sizeWithBorders - 1 - k] != 0) totalErrors++;
// Left and right vertical sides
if(cellPixelRatio.ptr<float>(y)[k] > validBitIdThreshold) totalErrors++;
if(cellPixelRatio.ptr<float>(y)[sizeWithBorders - 1 - k] > validBitIdThreshold) totalErrors++;
}
}
for(int x = borderSize; x < sizeWithBorders - borderSize; x++) {
for(int k = 0; k < borderSize; k++) {
if(bits.ptr<unsigned char>(k)[x] != 0) totalErrors++;
if(bits.ptr<unsigned char>(sizeWithBorders - 1 - k)[x] != 0) totalErrors++;
// Top and bottom horizontal sides
if(cellPixelRatio.ptr<float>(k)[x] > validBitIdThreshold) totalErrors++;
if(cellPixelRatio.ptr<float>(sizeWithBorders - 1 - k)[x] > validBitIdThreshold) totalErrors++;
}
}
return totalErrors;
@@ -482,49 +478,43 @@ static uint8_t _identifyOneCandidate(const Dictionary& dictionary, const Mat& _i
scaled_corners[i].y = _corners[i].y * scale;
}
Mat cellPixelRatio;
Mat candidateBits =
_extractBits(_image, scaled_corners, dictionary.markerSize, params.markerBorderBits,
params.perspectiveRemovePixelPerCell,
params.perspectiveRemoveIgnoredMarginPerCell, params.minOtsuStdDev,
cellPixelRatio);
Mat cellPixelRatio =
_extractCellPixelRatio(_image, scaled_corners, dictionary.markerSize, params.markerBorderBits,
params.perspectiveRemovePixelPerCell,
params.perspectiveRemoveIgnoredMarginPerCell, params.minOtsuStdDev);
// analyze border bits
int maximumErrorsInBorder =
int(dictionary.markerSize * dictionary.markerSize * params.maxErroneousBitsInBorderRate);
int(dictionary.markerSize * dictionary.markerSize * params.maxErroneousBitsInBorderRate);
int borderErrors =
_getBorderErrors(candidateBits, dictionary.markerSize, params.markerBorderBits);
_getBorderErrors(cellPixelRatio, dictionary.markerSize, params.markerBorderBits, params.validBitIdThreshold);
// check if it is a white marker
if(params.detectInvertedMarker){
// to get from 255 to 1
Mat invertedImg = ~candidateBits-254;
int invBError = _getBorderErrors(invertedImg, dictionary.markerSize, params.markerBorderBits);
Mat invCellPixelRatio = 1.f - cellPixelRatio;
int invBError = _getBorderErrors(invCellPixelRatio, dictionary.markerSize, params.markerBorderBits, params.validBitIdThreshold);
// white marker
if(invBError<borderErrors){
cellPixelRatio = 1.f - cellPixelRatio;
borderErrors = invBError;
invertedImg.copyTo(candidateBits);
invCellPixelRatio.copyTo(cellPixelRatio);
typ=2;
}
}
if(borderErrors > maximumErrorsInBorder) return 0; // border is wrong
// take only inner bits
Mat onlyBits =
candidateBits.rowRange(params.markerBorderBits,
candidateBits.rows - params.markerBorderBits)
.colRange(params.markerBorderBits, candidateBits.cols - params.markerBorderBits);
Mat onlyCellPixelRatio =
cellPixelRatio.rowRange(params.markerBorderBits,
cellPixelRatio.rows - params.markerBorderBits)
.colRange(params.markerBorderBits, cellPixelRatio.cols - params.markerBorderBits);
// try to indentify the marker
if(!dictionary.identify(onlyBits, idx, rotation, params.errorCorrectionRate))
// try to identify the marker
if(!dictionary.identify(onlyCellPixelRatio, idx, rotation, params.errorCorrectionRate, params.validBitIdThreshold))
return 0;
// compute the candidate's confidence
if(confidenceNeeded) {
Mat groundTruthbits;
Mat bitsUints = dictionary.getBitsFromByteList(dictionary.bytesList.rowRange(idx, idx + 1), dictionary.markerSize, rotation);
bitsUints.convertTo(groundTruthbits, CV_32F);
Mat groundTruthbits = dictionary.getMarkerBits(idx, rotation);
markerConfidence = _getMarkerConfidence(groundTruthbits, cellPixelRatio, dictionary.markerSize, params.markerBorderBits);
}
@@ -1403,11 +1393,14 @@ void ArucoDetector::refineDetectedMarkers(InputArray _image, const Board& _board
if(refineParams.errorCorrectionRate >= 0) {
// extract bits
Mat bits = _extractBits(
Mat cellPixelRatio = _extractCellPixelRatio(
grey, rotatedMarker, dictionary.markerSize, detectorParams.markerBorderBits,
detectorParams.perspectiveRemovePixelPerCell,
detectorParams.perspectiveRemoveIgnoredMarginPerCell, detectorParams.minOtsuStdDev);
Mat bits;
cellPixelRatio.convertTo(bits, CV_8UC1);
Mat onlyBits =
bits.rowRange(detectorParams.markerBorderBits, bits.rows - detectorParams.markerBorderBits)
.colRange(detectorParams.markerBorderBits, bits.rows - detectorParams.markerBorderBits);

View File

@@ -73,25 +73,32 @@ void Dictionary::writeDictionary(FileStorage& fs, const String &name)
}
bool Dictionary::identify(const Mat &onlyBits, int &idx, int &rotation, double maxCorrectionRate) const {
CV_Assert(onlyBits.rows == markerSize && onlyBits.cols == markerSize);
bool Dictionary::identify(const Mat &onlyCellPixelRatio, CV_OUT int &idx, CV_OUT int &rotation, double maxCorrectionRate, float validBitIdThreshold) const {
CV_Assert(onlyCellPixelRatio.rows == markerSize && onlyCellPixelRatio.cols == markerSize);
int maxCorrectionRecalculed = int(double(maxCorrectionBits) * maxCorrectionRate);
// get as a byte list
Mat candidateBytes = getByteListFromBits(onlyBits);
idx = -1; // by default, not found
// search closest marker in dict
for(int m = 0; m < bytesList.rows; m++) {
int currentMinDistance = markerSize * markerSize + 1;
int currentRotation = -1;
for(unsigned int r = 0; r < 4; r++) {
int currentHamming = cv::hal::normHamming(
bytesList.ptr(m)+r*candidateBytes.cols,
candidateBytes.ptr(),
candidateBytes.cols);
for(int r = 0; r < 4; r++) {
Mat bitsRot = getBitsFromByteList(bytesList.rowRange(m, m + 1), markerSize, r);
bitsRot.convertTo(bitsRot, CV_32F);
// Loop over all bits dictBitsList [m, markerSize * markerSize, 4]; onlyCellPixelRatio [markerSize, markerSize]
int currentHamming = 0;
for(int i = 0; i < markerSize; i++) {
for(int j = 0; j < markerSize; j++) {
// If detected bit is too far from the ground truth, consider it false.
if(fabs(onlyCellPixelRatio.at<float>(i, j) - static_cast<float>(bitsRot.at<float>(i, j))) > validBitIdThreshold){
currentHamming++;
}
}
}
if(currentHamming < currentMinDistance) {
currentMinDistance = currentHamming;
@@ -111,6 +118,16 @@ bool Dictionary::identify(const Mat &onlyBits, int &idx, int &rotation, double m
}
bool Dictionary::identify(const Mat &onlyBits, CV_OUT int &idx, CV_OUT int &rotation, double maxCorrectionRate) const {
CV_Assert(onlyBits.rows == markerSize && onlyBits.cols == markerSize);
Mat candidateBitRatio;
onlyBits.convertTo(candidateBitRatio, CV_32F);
const float validBitIdThreshold = DEFAULT_VALID_BIT_ID_THRESHOLD;
return identify(candidateBitRatio, idx, rotation, maxCorrectionRate, validBitIdThreshold);
}
int Dictionary::getDistanceToId(InputArray bits, int id, bool allRotations) const {
CV_Assert(id >= 0 && id < bytesList.rows);
@@ -147,7 +164,8 @@ void Dictionary::generateImageMarker(int id, int sidePixels, OutputArray _img, i
Mat innerRegion = tinyMarker.rowRange(borderBits, tinyMarker.rows - borderBits)
.colRange(borderBits, tinyMarker.cols - borderBits);
// put inner bits
Mat bits = 255 * getBitsFromByteList(bytesList.rowRange(id, id + 1), markerSize);
Mat bits = getMarkerBits(id);
bits.convertTo(bits, CV_8U, 255.0);
CV_Assert(innerRegion.total() == bits.total());
bits.copyTo(innerRegion);
@@ -194,12 +212,28 @@ Mat Dictionary::getByteListFromBits(const Mat &bits) {
}
Mat Dictionary::getMarkerBits(int markerId, int rotationId) const {
const int nbRotations = 4;
CV_Assert(markerId < bytesList.rows);
CV_Assert(rotationId < nbRotations);
Mat bits(markerSize, markerSize, CV_32F, Scalar::all(0));
Mat bitsUints = getBitsFromByteList(bytesList.rowRange(markerId, markerId + 1), markerSize, rotationId);
bitsUints.convertTo(bits, CV_32F);
CV_Assert(bits.rows == markerSize && bits.cols == markerSize);
return bits;
}
Mat Dictionary::getBitsFromByteList(const Mat &byteList, int markerSize, int rotationId) {
CV_Assert(byteList.total() > 0 &&
byteList.total() >= (unsigned int)markerSize * markerSize / 8 &&
byteList.total() <= (unsigned int)markerSize * markerSize / 8 + 1);
CV_Assert(rotationId >=0 && rotationId < 4);
CV_Assert(byteList.channels() >= 4);
CV_Assert(rotationId >= 0 && rotationId < 4);
Mat bits(markerSize, markerSize, CV_8UC1, Scalar::all(0));

View File

@@ -189,6 +189,7 @@ TEST(CV_ArucoTutorial, can_find_diamondmarkers)
aruco::DetectorParameters detectorParams;
detectorParams.readDetectorParameters(fs.root());
detectorParams.cornerRefinementMethod = aruco::CORNER_REFINE_APRILTAG;
detectorParams.validBitIdThreshold = 0.5f;
aruco::CharucoBoard charucoBoard(Size(3, 3), 0.4f, 0.25f, dictionary);
aruco::CharucoDetector detector(charucoBoard, aruco::CharucoParameters(), detectorParams);

View File

@@ -473,7 +473,7 @@ markerDetectionGT applyTemperingToMarkerCells(cv::Mat &marker,
++cellsTempered;
// cell too tempered, no detection expected
if(cellTempConfig.cellRatioToTemper > 0.5f) {
if(cellTempConfig.cellRatioToTemper > params.validBitIdThreshold) {
if(isBorder){
++borderErrors;
} else {
@@ -556,7 +556,7 @@ static void runArucoDetectionConfidence(ArucoAlgParams arucoAlgParam) {
// make sure there are no bits have any detection errors
params.maxErroneousBitsInBorderRate = 0.0;
params.errorCorrectionRate = 0.0;
params.perspectiveRemovePixelPerCell = 8; // esnsure that there is enough resolution to properly handle distortions
params.perspectiveRemovePixelPerCell = 8; // ensure that there is enough resolution to properly handle distortions
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250), params);
const bool detectInvertedMarker = (arucoAlgParam == ArucoAlgParams::DETECT_INVERTED_MARKER);
@@ -674,6 +674,144 @@ static void runArucoDetectionConfidence(ArucoAlgParams arucoAlgParam) {
}
}
// Helper struc and functions for CV_ArucoDetectionUnc
struct ArucoThresholdTestConfig {
MarkerTemperingConfig markerTemperingConfig; // Configuration of cells to invert (percentage, number and markerRegionToTemper)
float validBitIdThreshold; // range [0,1], define the acceptable threshold when comparing the detected marker to the dictionary during marker identification.
float perspectiveRemoveIgnoredMarginPerCell; // Width of the margin of pixels on each cell not considered for the marker identification
int markerBorderBits; // Number of bits of the marker border
float distortionRatio; // Percentage of offset used for perspective distortion, bigger means more distorted
};
/**
* @brief Test the param validBitIdThreshold
* Loops over a set of detector configurations (validBitIdThreshold, distortion, DetectorParameters such as markerBorderBits)
* For each configuration, it creates a synthetic image containing four markers arranged in a 2x2 grid.
* Each marker is generated with its own configuration (id, size, rotation).
* Make sure that markers are detected or not based on validBitIdThreshold and percentage of tempering.
* Finally, it runs the detector and checks that each marker is detected or not based on the threshold.
*
*/
static void runArucoDetectionThreshold(ArucoAlgParams arucoAlgParam) {
aruco::DetectorParameters params;
// make sure there are no bits have any detection errors
params.perspectiveRemovePixelPerCell = 20; // ensure that there is enough resolution to properly handle distortions
params.maxErroneousBitsInBorderRate = 0.f;
params.errorCorrectionRate = 0.f;
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_5X5_250), params); // Max correction: 6bits
const bool detectInvertedMarker = (arucoAlgParam == ArucoAlgParams::DETECT_INVERTED_MARKER);
// define several detector configurations to test different settings
// {{MarkerTemperingConfig}, validBitIdThreshold, perspectiveRemoveIgnoredMarginPerCell, markerBorderBits, distortionRatio}
vector<ArucoThresholdTestConfig> detectorConfigs = {
// No tempering, expect detection for every threshold
{{0.f, 0, MarkerRegionToTemper::ALL}, 0.3f, 0.f, 1, 0.f},
{{0.f, 0, MarkerRegionToTemper::ALL}, 0.5f, 0.f, 1, 0.f},
{{0.f, 0, MarkerRegionToTemper::ALL}, 0.9f, 0.f, 1, 0.f},
// Include distortions
{{0.f, 0, MarkerRegionToTemper::ALL}, 0.3f, 0.f, 1, 0.05f},
{{0.f, 0, MarkerRegionToTemper::ALL}, 0.5f, 0.f, 1, 0.1f},
{{0.f, 0, MarkerRegionToTemper::ALL}, 0.9f, 0.f, 1, 0.2f},
// 20% temper, expect detection with threshold above 0.2
{{0.2f, 5, MarkerRegionToTemper::BORDER}, 0.30f, 0.f, 1, 0.f}, // Detection
{{0.2f, 1, MarkerRegionToTemper::BORDER}, 0.18f, 0.f, 1, 0.f}, // No detection
{{0.2f, 1, MarkerRegionToTemper::BORDER}, 0.18f, 0.f, 1, 0.f}, // No detection
{{0.2f, 10, MarkerRegionToTemper::INNER}, 0.22f, 0.f, 1, 0.f}, // Detection
{{0.2f, 1, MarkerRegionToTemper::INNER}, 0.18f, 0.f, 1, 0.f} // No detection
// distortions
};
// define marker configurations for the 4 markers in each image
const int markerSidePixels = 700; // To simplify the cell division, markerSidePixels is a multiple of 7. (5x5 dict + 2 border bits)
vector<MarkerCreationConfig> markerCreationConfig = {
{0, markerSidePixels, markerRot::ROT_90}, // {id, markerSidePixels, rotation}
{1, markerSidePixels, markerRot::ROT_270},
{2, markerSidePixels, markerRot::NONE},
{3, markerSidePixels, markerRot::ROT_180}
};
// loop over each detector configuration
for (size_t cfgIdx = 0; cfgIdx < detectorConfigs.size(); cfgIdx++) {
ArucoThresholdTestConfig detCfg = detectorConfigs[cfgIdx];
// update detector parameters
params.validBitIdThreshold =detCfg.validBitIdThreshold;
params.perspectiveRemoveIgnoredMarginPerCell = detCfg.perspectiveRemoveIgnoredMarginPerCell;
params.markerBorderBits = detCfg.markerBorderBits;
params.detectInvertedMarker = detectInvertedMarker;
detector.setDetectorParameters(params);
// create a blank image large enough to hold 4 markers in a 2x2 grid
const int margin = markerSidePixels / 2;
const int imageSize = (markerSidePixels * 2) + margin * 3;
Mat img(imageSize, imageSize, CV_8UC1, Scalar(255));
vector<markerDetectionGT> groundTruths;
const aruco::Dictionary &dictionary = detector.getDictionary();
// place each marker into the image
for (int row = 0; row < 2; row++) {
for (int col = 0; col < 2; col++) {
int index = row * 2 + col;
MarkerCreationConfig markerCfg = markerCreationConfig[index];
// adjust marker id to be unique for each detector configuration
markerCfg.id += static_cast<int>(cfgIdx * markerCreationConfig.size());
// generate img
Mat markerImg;
markerDetectionGT gt = generateTemperedMarkerImage(markerImg, markerCfg, detCfg.markerTemperingConfig, params, dictionary, detCfg.distortionRatio);
groundTruths.push_back(gt);
// place marker in the image
Point2f topLeft(static_cast<float>(margin + col * (markerSidePixels + margin)),
static_cast<float>(margin + row * (markerSidePixels + margin)));
placeMarker(img, markerImg, topLeft);
}
}
// if testing inverted markers globally, invert the whole image
if (detectInvertedMarker) {
bitwise_not(img, img);
}
// run detection.
vector<vector<Point2f>> corners, rejected;
vector<int> ids;
vector<float> markerConfidence;
detector.detectMarkersWithConfidence(img, corners, ids, markerConfidence, rejected);
ASSERT_EQ(ids.size(), corners.size());
ASSERT_EQ(ids.size(), markerConfidence.size());
std::map<int, float> confidenceById;
for (size_t i = 0; i < ids.size(); i++) {
confidenceById[ids[i]] = markerConfidence[i];
}
// verify that every marker is detected and its confidence is within tolerance
for (const auto& currentGT : groundTruths) {
const auto it = confidenceById.find(currentGT.id);
const bool detected = it != confidenceById.end();
EXPECT_EQ(currentGT.expectDetection, detected)
<< "Marker id: " << currentGT.id << " (detector config " << cfgIdx << ")";
if (currentGT.expectDetection && detected) {
EXPECT_NEAR(currentGT.confidence, it->second, 0.05)
<< "Marker id: " << currentGT.id << " (detector config " << cfgIdx << ")";
}
}
}
}
/**
* @brief Check max and min size in marker detection parameters
*/
@@ -981,6 +1119,14 @@ TEST(CV_InvertedFlagArucoDetectionConfidence, algorithmic) {
}
}
TEST(CV_ArucoDetectionThreshold, algorithmic) {
runArucoDetectionThreshold(ArucoAlgParams::USE_DEFAULT);
}
TEST(CV_InvertedArucoDetectionThreshold, algorithmic) {
runArucoDetectionThreshold(ArucoAlgParams::DETECT_INVERTED_MARKER);
}
TEST(CV_ArucoDetectMarkers, regression_3192)
{
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_4X4_50));
@@ -1304,6 +1450,7 @@ TEST_P(ArucoThreading, number_of_threads_does_not_change_results)
aruco::DetectorParameters detectorParameters = detector.getDetectorParameters();
detectorParameters.cornerRefinementMethod = (int)GetParam();
detectorParameters.validBitIdThreshold = 0.5f;
detector.setDetectorParameters(detectorParameters);
vector<vector<Point2f> > original_corners;

View File

@@ -66,6 +66,7 @@ void CV_ArucoBoardPose::run(int) {
vector<vector<Point2f> > corners;
vector<int> ids;
detectorParameters.markerBorderBits = markerBorder;
detectorParameters.validBitIdThreshold = 0.5f;
detector.setDetectorParameters(detectorParameters);
detector.detectMarkers(img, corners, ids);