mirror of
https://github.com/opencv/opencv.git
synced 2025-06-16 06:40:49 +08:00
Update unit test coverage to include perspective-distorted cases
This commit is contained in:
parent
c26776b515
commit
92a24f0499
@ -313,7 +313,7 @@ static void _detectInitialCandidates(const Mat &grey, vector<vector<Point2f> > &
|
|||||||
* the border bits
|
* the border bits
|
||||||
*/
|
*/
|
||||||
static Mat _extractBits(InputArray _image, const vector<Point2f>& corners, int markerSize,
|
static Mat _extractBits(InputArray _image, const vector<Point2f>& corners, int markerSize,
|
||||||
int markerBorderBits, int cellSize, double cellMarginRate, double minStdDevOtsu, OutputArray _whitePixRatio = noArray()) {
|
int markerBorderBits, int cellSize, double cellMarginRate, double minStdDevOtsu, OutputArray _cellPixelRatio = noArray()) {
|
||||||
CV_Assert(_image.getMat().channels() == 1);
|
CV_Assert(_image.getMat().channels() == 1);
|
||||||
CV_Assert(corners.size() == 4ull);
|
CV_Assert(corners.size() == 4ull);
|
||||||
CV_Assert(markerBorderBits > 0 && cellSize > 0 && cellMarginRate >= 0 && cellMarginRate <= 0.5);
|
CV_Assert(markerBorderBits > 0 && cellSize > 0 && cellMarginRate >= 0 && cellMarginRate <= 0.5);
|
||||||
@ -339,7 +339,7 @@ static Mat _extractBits(InputArray _image, const vector<Point2f>& corners, int m
|
|||||||
|
|
||||||
// output image containing the bits
|
// output image containing the bits
|
||||||
Mat bits(markerSizeWithBorders, markerSizeWithBorders, CV_8UC1, Scalar::all(0));
|
Mat bits(markerSizeWithBorders, markerSizeWithBorders, CV_8UC1, Scalar::all(0));
|
||||||
Mat whitePixRatio(markerSizeWithBorders, markerSizeWithBorders, CV_32FC1, Scalar::all(0));
|
Mat cellPixelRatio(markerSizeWithBorders, markerSizeWithBorders, CV_32FC1, Scalar::all(0));
|
||||||
|
|
||||||
// check if standard deviation is enough to apply Otsu
|
// 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 bits are the same color (black or white)
|
||||||
@ -352,13 +352,13 @@ static Mat _extractBits(InputArray _image, const vector<Point2f>& corners, int m
|
|||||||
// all black or all white, depending on mean value
|
// 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);
|
bits.setTo(1);
|
||||||
whitePixRatio.setTo(1);
|
cellPixelRatio.setTo(1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
bits.setTo(0);
|
bits.setTo(0);
|
||||||
whitePixRatio.setTo(0);
|
cellPixelRatio.setTo(0);
|
||||||
}
|
}
|
||||||
if(_whitePixRatio.needed()) whitePixRatio.copyTo(_whitePixRatio);
|
if(_cellPixelRatio.needed()) cellPixelRatio.copyTo(_cellPixelRatio);
|
||||||
return bits;
|
return bits;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,7 +376,8 @@ static Mat _extractBits(InputArray _image, const vector<Point2f>& corners, int m
|
|||||||
size_t nZ = (size_t) countNonZero(square);
|
size_t nZ = (size_t) countNonZero(square);
|
||||||
if(nZ > square.total() / 2) bits.at<unsigned char>(y, x) = 1;
|
if(nZ > square.total() / 2) bits.at<unsigned char>(y, x) = 1;
|
||||||
|
|
||||||
if(_whitePixRatio.needed()){
|
// define the cell pixel ratio as the ratio of the white pixels. For inverted markers, the ratio will be inverted.
|
||||||
|
if(_cellPixelRatio.needed()){
|
||||||
|
|
||||||
// Get white pixel ratio from the complete cell
|
// Get white pixel ratio from the complete cell
|
||||||
if(cellMarginPixels > 0){
|
if(cellMarginPixels > 0){
|
||||||
@ -397,16 +398,16 @@ static Mat _extractBits(InputArray _image, const vector<Point2f>& corners, int m
|
|||||||
nZMarginPixels += (size_t) countNonZero(rightRect);
|
nZMarginPixels += (size_t) countNonZero(rightRect);
|
||||||
totalMarginPixels += rightRect.total();
|
totalMarginPixels += rightRect.total();
|
||||||
|
|
||||||
whitePixRatio.at<float>(y, x) = (nZ + nZMarginPixels) / (float)(square.total() + totalMarginPixels);
|
cellPixelRatio.at<float>(y, x) = (nZ + nZMarginPixels) / (float)(square.total() + totalMarginPixels);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
whitePixRatio.at<float>(y, x) = (nZ / (float)square.total());
|
cellPixelRatio.at<float>(y, x) = (nZ / (float)square.total());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_whitePixRatio.needed()) whitePixRatio.copyTo(_whitePixRatio);
|
if(_cellPixelRatio.needed()) cellPixelRatio.copyTo(_cellPixelRatio);
|
||||||
|
|
||||||
return bits;
|
return bits;
|
||||||
}
|
}
|
||||||
@ -443,29 +444,29 @@ static int _getBorderErrors(const Mat &bits, int markerSize, int borderSize) {
|
|||||||
* The uncertainty is defined as percentage of incorrect pixel detections, with 0 describing a pixel perfect detection.
|
* 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.
|
* The rotation is set to 0,1,2,3 for [0, 90, 180, 270] deg CCW rotations.
|
||||||
*/
|
*/
|
||||||
static float _getMarkerUnc(const Dictionary& dictionary, const Mat &whitePixRatio, const int id,
|
static float _getMarkerUnc(const Dictionary& dictionary, const Mat &cellPixelRatio, const int id,
|
||||||
const int rotation, const int borderSize) {
|
const int rotation, const int borderSize) {
|
||||||
|
|
||||||
CV_Assert(id >= 0 && id < dictionary.bytesList.rows);
|
CV_Assert(id >= 0 && id < dictionary.bytesList.rows);
|
||||||
const int markerSize = dictionary.markerSize;
|
const int markerSize = dictionary.markerSize;
|
||||||
const int sizeWithBorders = markerSize + 2 * borderSize;
|
const int sizeWithBorders = markerSize + 2 * borderSize;
|
||||||
|
|
||||||
CV_Assert(markerSize > 0 && whitePixRatio.cols == sizeWithBorders && whitePixRatio.rows == sizeWithBorders);
|
CV_Assert(markerSize > 0 && cellPixelRatio.cols == sizeWithBorders && cellPixelRatio.rows == sizeWithBorders);
|
||||||
|
|
||||||
// Get border uncertainty. Assuming black borders, the uncertainty is the ratio of white pixels.
|
// Get border uncertainty. cellPixelRatio has the opposite color as the borders --> it is the uncertainty.
|
||||||
float tempBorderUnc = 0.f;
|
float tempBorderUnc = 0.f;
|
||||||
for(int y = 0; y < sizeWithBorders; y++) {
|
for(int y = 0; y < sizeWithBorders; y++) {
|
||||||
for(int k = 0; k < borderSize; k++) {
|
for(int k = 0; k < borderSize; k++) {
|
||||||
// Left and right vertical sides
|
// Left and right vertical sides
|
||||||
tempBorderUnc += whitePixRatio.ptr<float>(y)[k];
|
tempBorderUnc += cellPixelRatio.ptr<float>(y)[k];
|
||||||
tempBorderUnc += whitePixRatio.ptr<float>(y)[sizeWithBorders - 1 - k];
|
tempBorderUnc += cellPixelRatio.ptr<float>(y)[sizeWithBorders - 1 - k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(int x = borderSize; x < sizeWithBorders - borderSize; x++) {
|
for(int x = borderSize; x < sizeWithBorders - borderSize; x++) {
|
||||||
for(int k = 0; k < borderSize; k++) {
|
for(int k = 0; k < borderSize; k++) {
|
||||||
// Top and bottom horizontal sides
|
// Top and bottom horizontal sides
|
||||||
tempBorderUnc += whitePixRatio.ptr<float>(k)[x];
|
tempBorderUnc += cellPixelRatio.ptr<float>(k)[x];
|
||||||
tempBorderUnc += whitePixRatio.ptr<float>(sizeWithBorders - 1 - k)[x];
|
tempBorderUnc += cellPixelRatio.ptr<float>(sizeWithBorders - 1 - k)[x];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -492,7 +493,7 @@ static float _getMarkerUnc(const Dictionary& dictionary, const Mat &whitePixRati
|
|||||||
float tempInnerUnc = 0.f;
|
float tempInnerUnc = 0.f;
|
||||||
for(int y = borderSize; y < markerSize + borderSize; y++) {
|
for(int y = borderSize; y < markerSize + borderSize; y++) {
|
||||||
for(int x = borderSize; x < markerSize + borderSize; x++) {
|
for(int x = borderSize; x < markerSize + borderSize; x++) {
|
||||||
tempInnerUnc += abs(groundTruthbits.ptr<unsigned char>(y - borderSize)[x - borderSize] - whitePixRatio.ptr<float>(y)[x]);
|
tempInnerUnc += abs(groundTruthbits.ptr<unsigned char>(y - borderSize)[x - borderSize] - cellPixelRatio.ptr<float>(y)[x]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -524,12 +525,12 @@ static uint8_t _identifyOneCandidate(const Dictionary& dictionary, const Mat& _i
|
|||||||
scaled_corners[i].y = _corners[i].y * scale;
|
scaled_corners[i].y = _corners[i].y * scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
Mat whitePixRatio;
|
Mat cellPixelRatio;
|
||||||
Mat candidateBits =
|
Mat candidateBits =
|
||||||
_extractBits(_image, scaled_corners, dictionary.markerSize, params.markerBorderBits,
|
_extractBits(_image, scaled_corners, dictionary.markerSize, params.markerBorderBits,
|
||||||
params.perspectiveRemovePixelPerCell,
|
params.perspectiveRemovePixelPerCell,
|
||||||
params.perspectiveRemoveIgnoredMarginPerCell, params.minOtsuStdDev,
|
params.perspectiveRemoveIgnoredMarginPerCell, params.minOtsuStdDev,
|
||||||
whitePixRatio);
|
cellPixelRatio);
|
||||||
|
|
||||||
// analyze border bits
|
// analyze border bits
|
||||||
int maximumErrorsInBorder =
|
int maximumErrorsInBorder =
|
||||||
@ -542,11 +543,11 @@ static uint8_t _identifyOneCandidate(const Dictionary& dictionary, const Mat& _i
|
|||||||
// to get from 255 to 1
|
// to get from 255 to 1
|
||||||
Mat invertedImg = ~candidateBits-254;
|
Mat invertedImg = ~candidateBits-254;
|
||||||
int invBError = _getBorderErrors(invertedImg, dictionary.markerSize, params.markerBorderBits);
|
int invBError = _getBorderErrors(invertedImg, dictionary.markerSize, params.markerBorderBits);
|
||||||
|
cellPixelRatio = -1.0 * cellPixelRatio + 1;
|
||||||
// white marker
|
// white marker
|
||||||
if(invBError<borderErrors){
|
if(invBError<borderErrors){
|
||||||
borderErrors = invBError;
|
borderErrors = invBError;
|
||||||
invertedImg.copyTo(candidateBits);
|
invertedImg.copyTo(candidateBits);
|
||||||
whitePixRatio = -1.0 * whitePixRatio + 1;
|
|
||||||
typ=2;
|
typ=2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -558,18 +559,12 @@ static uint8_t _identifyOneCandidate(const Dictionary& dictionary, const Mat& _i
|
|||||||
candidateBits.rows - params.markerBorderBits)
|
candidateBits.rows - params.markerBorderBits)
|
||||||
.colRange(params.markerBorderBits, candidateBits.cols - 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
|
// try to indentify the marker
|
||||||
if(!dictionary.identify(onlyBits, idx, rotation, params.errorCorrectionRate))
|
if(!dictionary.identify(onlyBits, idx, rotation, params.errorCorrectionRate))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// compute the candidate's uncertainty
|
// compute the candidate's uncertainty
|
||||||
markerUnc = _getMarkerUnc(dictionary, whitePixRatio, idx, rotation, params.markerBorderBits);
|
markerUnc = _getMarkerUnc(dictionary, cellPixelRatio, idx, rotation, params.markerBorderBits);
|
||||||
|
|
||||||
return typ;
|
return typ;
|
||||||
}
|
}
|
||||||
|
@ -321,17 +321,125 @@ void CV_ArucoDetectionPerspective::run(int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper struc and functions for CV_ArucoDetectionUnc
|
||||||
|
struct ArucoUncTestConfig {
|
||||||
|
// Number of bits (per dimension) for each cell of the marker when removing the perspective (default 4).
|
||||||
|
int perspectiveRemovePixelPerCell;
|
||||||
|
// Width of the margin of pixels on each cell not considered for the determination of the cell bit.
|
||||||
|
// This parameter is relative to the total size of the cell.
|
||||||
|
// For instance if the cell size is 40 pixels and the value of this parameter is 0.1, a margin of 40*0.1=4 pixels is ignored in the cells.
|
||||||
|
float perspectiveRemoveIgnoredMarginPerCell;
|
||||||
|
// Number of bits of the marker border, i.e. marker border width (default 1).
|
||||||
|
int markerBorderBits;
|
||||||
|
// Fraction of tempered (inverted) pixels per cell (area ratio, e.g. 0.02 for 2%)
|
||||||
|
float invertPixelPercent;
|
||||||
|
// Percentage of offset used for perspective distortion, bigger means more distorted
|
||||||
|
float distortionRatio;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MarkerCreationConfig {
|
||||||
|
int id; // Unique marker ID (will be offset per test run)
|
||||||
|
int markerSidePixels; // Marker size (in pixels)
|
||||||
|
int rotation; // Rotation of the marker in degrees (0, 90, 180, 270)
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Draw 2D synthetic markers, temper with some pixels, detect them and compute their uncertainty.
|
* @brief Create a synthetic image of a marker
|
||||||
|
* Applies an optional rotation and an optional perspective warp to simulate a distorted marker.
|
||||||
|
* Inverts a square region within each cell (including borders) to simulate uncertainty in detection.
|
||||||
|
* Computes the ground-truth uncertainty as the ratio of inverted area to the total marker area.
|
||||||
|
*/
|
||||||
|
Mat generateMarkerImage(const MarkerCreationConfig &markerConfig, const ArucoUncTestConfig &detectorConfig,
|
||||||
|
const aruco::Dictionary &dictionary, double &groundTruthUnc)
|
||||||
|
{
|
||||||
|
Mat marker;
|
||||||
|
// Generate the synthetic marker image
|
||||||
|
aruco::generateImageMarker(dictionary, markerConfig.id, markerConfig.markerSidePixels,
|
||||||
|
marker, detectorConfig.markerBorderBits);
|
||||||
|
|
||||||
|
// Rotate the marker if needed.
|
||||||
|
if (markerConfig.rotation == 90) {
|
||||||
|
cv::transpose(marker, marker);
|
||||||
|
cv::flip(marker, marker, 0);
|
||||||
|
} else if (markerConfig.rotation == 180) {
|
||||||
|
cv::flip(marker, marker, -1);
|
||||||
|
} else if (markerConfig.rotation == 270) {
|
||||||
|
cv::transpose(marker, marker);
|
||||||
|
cv::flip(marker, marker, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the number of cells in one dimension.
|
||||||
|
const int markerSizeWithBorders = dictionary.markerSize + 2 * detectorConfig.markerBorderBits;
|
||||||
|
const int cellSidePixelsSize = markerConfig.markerSidePixels / markerSizeWithBorders;
|
||||||
|
// We want the inverted square area to have an area ratio equal to invertPixelPercent.
|
||||||
|
// That is: (cellSidePixelsInvert/cellSidePixelsSize)^2 = invertPixelPercent.
|
||||||
|
int cellSidePixelsInvert = int(cellSidePixelsSize * std::sqrt(detectorConfig.invertPixelPercent));
|
||||||
|
int cellMarginPixels = (cellSidePixelsSize - cellSidePixelsInvert) / 2;
|
||||||
|
|
||||||
|
int numCellsInverted = 0;
|
||||||
|
// Loop over each cell in the marker grid.
|
||||||
|
if (cellSidePixelsInvert > 0) {
|
||||||
|
for (int row = 0; row < markerSizeWithBorders; row++) {
|
||||||
|
for (int col = 0; col < markerSizeWithBorders; col++) {
|
||||||
|
int xStart = col * cellSidePixelsSize + cellMarginPixels;
|
||||||
|
int yStart = row * cellSidePixelsSize + cellMarginPixels;
|
||||||
|
Rect cellRect(xStart, yStart, cellSidePixelsInvert, cellSidePixelsInvert);
|
||||||
|
Mat cellROI = marker(cellRect);
|
||||||
|
bitwise_not(cellROI, cellROI);
|
||||||
|
numCellsInverted++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute ground-truth uncertainty as (inverted area)/(total marker area).
|
||||||
|
groundTruthUnc = (numCellsInverted * cellSidePixelsInvert * cellSidePixelsInvert) /
|
||||||
|
static_cast<double>(markerConfig.markerSidePixels * markerConfig.markerSidePixels);
|
||||||
|
|
||||||
|
// Optionally apply a distortion (a perspective warp) to simulate a non-ideal capture.
|
||||||
|
if (detectorConfig.distortionRatio > 0.f) {
|
||||||
|
vector<Point2f> src = { {0, 0},
|
||||||
|
{static_cast<float>(marker.cols), 0},
|
||||||
|
{static_cast<float>(marker.cols), static_cast<float>(marker.rows)},
|
||||||
|
{0, static_cast<float>(marker.rows)} };
|
||||||
|
float offset = marker.cols * detectorConfig.distortionRatio; // distortionRatio % offset for distortion
|
||||||
|
vector<Point2f> dst = { {offset, offset},
|
||||||
|
{marker.cols - offset, 0},
|
||||||
|
{marker.cols - offset, marker.rows - offset},
|
||||||
|
{0, marker.rows - offset} };
|
||||||
|
Mat M = getPerspectiveTransform(src, dst);
|
||||||
|
warpPerspective(marker, marker, M, marker.size(), INTER_LINEAR, BORDER_CONSTANT, Scalar(255));
|
||||||
|
}
|
||||||
|
|
||||||
|
return marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copies a marker image into a larger image at the given top-left position.
|
||||||
|
*/
|
||||||
|
void placeMarker(Mat &img, const Mat &marker, const Point2f &topLeft)
|
||||||
|
{
|
||||||
|
Rect roi(Point(static_cast<int>(topLeft.x), static_cast<int>(topLeft.y)), marker.size());
|
||||||
|
marker.copyTo(img(roi));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test the marker uncertainty computations.
|
||||||
|
* Loops over a set of detector configurations (expected uncertainty, 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).
|
||||||
|
* Finally, it runs the detector and checks that each marker is detected and
|
||||||
|
* that its computed uncertainty is close to the ground truth value.
|
||||||
*/
|
*/
|
||||||
class CV_ArucoDetectionUnc : public cvtest::BaseTest {
|
class CV_ArucoDetectionUnc : public cvtest::BaseTest {
|
||||||
public:
|
public:
|
||||||
CV_ArucoDetectionUnc(ArucoAlgParams arucoAlgParam) : arucoAlgParams(arucoAlgParam) {}
|
// The parameter arucoAlgParam allows switching between detecting normal and inverted markers.
|
||||||
|
CV_ArucoDetectionUnc(ArucoAlgParams algParam) : arucoAlgParam(algParam) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void run(int);
|
void run(int);
|
||||||
ArucoAlgParams arucoAlgParams;
|
ArucoAlgParams arucoAlgParam;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -340,123 +448,116 @@ void CV_ArucoDetectionUnc::run(int) {
|
|||||||
aruco::DetectorParameters params;
|
aruco::DetectorParameters params;
|
||||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250), params);
|
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250), params);
|
||||||
|
|
||||||
// Params to test
|
const bool detectInvertedMarker = (arucoAlgParam == ArucoAlgParams::DETECT_INVERTED_MARKER);
|
||||||
const float ingnoreMarginPerCell[3] = {0.0f, 0.1f, 0.2f};
|
|
||||||
const int borderBitsTest[3] = {1,2,3};
|
|
||||||
|
|
||||||
const int markerSidePixels = 150;
|
// Define several detector configurations to test different settings.
|
||||||
const int imageSize = (markerSidePixels * 2) + 3 * (markerSidePixels / 2);
|
// perspectiveRemovePixelPerCell, perspectiveRemoveIgnoredMarginPerCell, markerBorderBits, invertPixelPercent, distortionRatio
|
||||||
|
vector<ArucoUncTestConfig> detectorConfigs = {
|
||||||
|
// No margins, No distortion
|
||||||
|
{8, 0.0f, 1, 0.0f, 0.f},
|
||||||
|
{8, 0.0f, 1, 0.01f, 0.f},
|
||||||
|
{8, 0.0f, 2, 0.05f, 0.f},
|
||||||
|
{8, 0.0f, 1, 0.1f, 0.f},
|
||||||
|
// Margins, No distortion
|
||||||
|
{8, 0.05f, 1, 0.0f, 0.f},
|
||||||
|
{8, 0.05f, 2, 0.01f, 0.f},
|
||||||
|
{8, 0.1f, 3, 0.05f, 0.f},
|
||||||
|
{8, 0.15f, 1, 0.1f, 0.f},
|
||||||
|
// No margins, distortion
|
||||||
|
{8, 0.0f, 1, 0.0f, 0.01f},
|
||||||
|
{8, 0.0f, 1, 0.01f, 0.02f},
|
||||||
|
{8, 0.0f, 2, 0.05f, 0.05f},
|
||||||
|
{8, 0.0f, 1, 0.1f, 0.1f},
|
||||||
|
{8, 0.0f, 2, 0.1f, 0.2f},
|
||||||
|
// Margins, distortion
|
||||||
|
{8, 0.05f, 2, 0.0f, 0.01f},
|
||||||
|
{8, 0.05f, 1, 0.01f, 0.02f},
|
||||||
|
{8, 0.1f, 2, 0.05f, 0.05f},
|
||||||
|
{8, 0.15f, 1, 0.1f, 0.1f},
|
||||||
|
{8, 0.0f, 1, 0.1f, 0.2f},
|
||||||
|
};
|
||||||
|
|
||||||
// 25 images containing 4 markers.
|
// Define marker configurations for the 4 markers.
|
||||||
for(int i = 0; i < 25; i++) {
|
const int markerSidePixels = 480;
|
||||||
|
// id, markerSidePixels, rotation
|
||||||
|
vector<MarkerCreationConfig> markerCreationConfig = {
|
||||||
|
{0, markerSidePixels, 90, },
|
||||||
|
{1, markerSidePixels, 270,},
|
||||||
|
{2, markerSidePixels, 0, },
|
||||||
|
{3, markerSidePixels, 180,}
|
||||||
|
};
|
||||||
|
|
||||||
// Modify default params
|
// Loop over each detector configuration.
|
||||||
params.perspectiveRemovePixelPerCell = 6 + i;
|
for (size_t cfgIdx = 0; cfgIdx < detectorConfigs.size(); cfgIdx++) {
|
||||||
params.perspectiveRemoveIgnoredMarginPerCell = ingnoreMarginPerCell[i % 3];
|
ArucoUncTestConfig detCfg = detectorConfigs[cfgIdx];
|
||||||
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%]
|
|
||||||
const float invertPixelPercent = 2 * i / 100.f;
|
|
||||||
const int markerSizeWithBorders = 6 + 2 * params.markerBorderBits;
|
|
||||||
const int cellSidePixelsSize = markerSidePixels / markerSizeWithBorders;
|
|
||||||
const int cellSidePixelsInvert = int(sqrt(invertPixelPercent) * cellSidePixelsSize);
|
|
||||||
const 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;
|
|
||||||
const 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++) {
|
|
||||||
const int Xstart = p * (cellSidePixelsSize) + cellMarginPixels;
|
|
||||||
const 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Update detector parameters.
|
||||||
|
params.perspectiveRemovePixelPerCell = detCfg.perspectiveRemovePixelPerCell;
|
||||||
|
params.perspectiveRemoveIgnoredMarginPerCell = detCfg.perspectiveRemoveIgnoredMarginPerCell;
|
||||||
|
params.markerBorderBits = detCfg.markerBorderBits;
|
||||||
|
params.detectInvertedMarker = detectInvertedMarker;
|
||||||
detector.setDetectorParameters(params);
|
detector.setDetectorParameters(params);
|
||||||
|
|
||||||
// detect markers and compute uncertainty
|
// 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<double> groundTruthUncs;
|
||||||
|
vector<int> groundTruthIds;
|
||||||
|
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());
|
||||||
|
groundTruthIds.push_back(markerCfg.id);
|
||||||
|
|
||||||
|
double gtUnc = 0.0;
|
||||||
|
Mat markerImg = generateMarkerImage(markerCfg, detCfg, dictionary, gtUnc);
|
||||||
|
groundTruthUncs.push_back(gtUnc);
|
||||||
|
|
||||||
|
// Place marker in the image.
|
||||||
|
Point2f topLeft(margin + col * (markerSidePixels + margin),
|
||||||
|
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<vector<Point2f>> corners, rejected;
|
||||||
vector<int> ids;
|
vector<int> ids;
|
||||||
vector<float> markerUnc;
|
vector<float> markerUnc;
|
||||||
|
|
||||||
detector.detectMarkersWithUnc(img, corners, ids, rejected, markerUnc);
|
detector.detectMarkersWithUnc(img, corners, ids, rejected, markerUnc);
|
||||||
|
|
||||||
// check detection results
|
// Verify that every marker is detected and its uncertainty is within tolerance.
|
||||||
for(unsigned int m = 0; m < groundTruthIds.size(); m++) {
|
for (size_t m = 0; m < groundTruthIds.size(); m++) {
|
||||||
int idx = -1;
|
int detectedIdx = -1;
|
||||||
for(unsigned int k = 0; k < ids.size(); k++) {
|
for (size_t k = 0; k < ids.size(); k++) {
|
||||||
if (groundTruthIds[m] == ids[k]) {
|
if (groundTruthIds[m] == ids[k]) {
|
||||||
idx = (int)k;
|
detectedIdx = static_cast<int>(k);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(idx == -1) {
|
if (detectedIdx == -1) {
|
||||||
ts->printf(cvtest::TS::LOG, "Marker not detected");
|
ts->printf(cvtest::TS::LOG, "Marker id %d: not detected (detector config %zu)\n",
|
||||||
|
groundTruthIds[m], cfgIdx);
|
||||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
double dist = (double)cv::abs(groundTruthUncs[m] - markerUnc[idx]); // TODO cvtest
|
double diff = fabs(groundTruthUncs[m] - markerUnc[detectedIdx]);
|
||||||
if(dist > 0.05) {
|
if (diff > 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->printf(cvtest::TS::LOG, "");
|
"Marker id %d: computed uncertainty %.2f differs from ground truth %.2f (diff=%.2f) (detector config %zu)\n",
|
||||||
|
groundTruthIds[m], markerUnc[detectedIdx], groundTruthUncs[m], diff, cfgIdx);
|
||||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user