vendor: OpenCV 5.0.0 snapshot at 40738fb16ceddb5fb3fea747585f7ce6abb0605b
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "opencv2/objdetect/aruco_detector.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
|
||||
TEST(CV_ArucoTutorial, can_find_singlemarkersoriginal)
|
||||
{
|
||||
string img_path = cvtest::findDataFile("aruco/singlemarkersoriginal.jpg");
|
||||
Mat image = imread(img_path);
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250));
|
||||
|
||||
vector<int> ids;
|
||||
vector<vector<Point2f> > corners, rejected;
|
||||
const size_t N = 6ull;
|
||||
// corners of ArUco markers with indices goldCornersIds
|
||||
const int goldCorners[N][8] = { {359,310, 404,310, 410,350, 362,350}, {427,255, 469,256, 477,289, 434,288},
|
||||
{233,273, 190,273, 196,241, 237,241}, {298,185, 334,186, 335,212, 297,211},
|
||||
{425,163, 430,186, 394,186, 390,162}, {195,155, 230,155, 227,178, 190,178} };
|
||||
const int goldCornersIds[N] = { 40, 98, 62, 23, 124, 203};
|
||||
map<int, const int*> mapGoldCorners;
|
||||
for (size_t i = 0; i < N; i++)
|
||||
mapGoldCorners[goldCornersIds[i]] = goldCorners[i];
|
||||
|
||||
detector.detectMarkers(image, corners, ids, rejected);
|
||||
|
||||
ASSERT_EQ(N, ids.size());
|
||||
for (size_t i = 0; i < N; i++)
|
||||
{
|
||||
int arucoId = ids[i];
|
||||
ASSERT_EQ(4ull, corners[i].size());
|
||||
ASSERT_TRUE(mapGoldCorners.find(arucoId) != mapGoldCorners.end());
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
EXPECT_NEAR(static_cast<float>(mapGoldCorners[arucoId][j * 2]), corners[i][j].x, 1.f);
|
||||
EXPECT_NEAR(static_cast<float>(mapGoldCorners[arucoId][j * 2 + 1]), corners[i][j].y, 1.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CV_ArucoTutorial, can_find_gboriginal)
|
||||
{
|
||||
string imgPath = cvtest::findDataFile("aruco/gboriginal.jpg");
|
||||
Mat image = imread(imgPath);
|
||||
string dictPath = cvtest::findDataFile("aruco/tutorial_dict.yml");
|
||||
aruco::Dictionary dictionary;
|
||||
|
||||
FileStorage fs(dictPath, FileStorage::READ);
|
||||
dictionary.aruco::Dictionary::readDictionary(fs.root()); // set marker from tutorial_dict.yml
|
||||
aruco::DetectorParameters detectorParams;
|
||||
|
||||
aruco::ArucoDetector detector(dictionary, detectorParams);
|
||||
|
||||
vector<int> ids;
|
||||
vector<vector<Point2f> > corners, rejected;
|
||||
const size_t N = 35ull;
|
||||
// corners of ArUco markers with indices 0, 1, ..., 34
|
||||
const int goldCorners[N][8] = { {252,74, 286,81, 274,102, 238,95}, {295,82, 330,89, 319,111, 282,104},
|
||||
{338,91, 375,99, 365,121, 327,113}, {383,100, 421,107, 412,130, 374,123},
|
||||
{429,109, 468,116, 461,139, 421,132}, {235,100, 270,108, 257,130, 220,122},
|
||||
{279,109, 316,117, 304,140, 266,133}, {324,119, 362,126, 352,150, 313,143},
|
||||
{371,128, 410,136, 400,161, 360,152}, {418,139, 459,145, 451,170, 410,163},
|
||||
{216,128, 253,136, 239,161, 200,152}, {262,138, 300,146, 287,172, 248,164},
|
||||
{309,148, 349,156, 337,183, 296,174}, {358,158, 398,167, 388,194, 346,185},
|
||||
{407,169, 449,176, 440,205, 397,196}, {196,158, 235,168, 218,195, 179,185},
|
||||
{243,170, 283,178, 269,206, 228,197}, {293,180, 334,190, 321,218, 279,209},
|
||||
{343,192, 385,200, 374,230, 330,220}, {395,203, 438,211, 429,241, 384,233},
|
||||
{174,192, 215,201, 197,231, 156,221}, {223,204, 265,213, 249,244, 207,234},
|
||||
{275,215, 317,225, 303,257, 259,246}, {327,227, 371,238, 359,270, 313,259},
|
||||
{381,240, 426,249, 416,282, 369,273}, {151,228, 193,238, 173,271, 130,260},
|
||||
{202,241, 245,251, 228,285, 183,274}, {255,254, 300,264, 284,299, 238,288},
|
||||
{310,267, 355,278, 342,314, 295,302}, {366,281, 413,290, 402,327, 353,317},
|
||||
{125,267, 168,278, 147,314, 102,303}, {178,281, 223,293, 204,330, 157,317},
|
||||
{233,296, 280,307, 263,346, 214,333}, {291,310, 338,322, 323,363, 274,349},
|
||||
{349,325, 399,336, 386,378, 335,366} };
|
||||
map<int, const int*> mapGoldCorners;
|
||||
for (int i = 0; i < static_cast<int>(N); i++)
|
||||
mapGoldCorners[i] = goldCorners[i];
|
||||
|
||||
detector.detectMarkers(image, corners, ids, rejected);
|
||||
|
||||
ASSERT_EQ(N, ids.size());
|
||||
for (size_t i = 0; i < N; i++)
|
||||
{
|
||||
int arucoId = ids[i];
|
||||
ASSERT_EQ(4ull, corners[i].size());
|
||||
ASSERT_TRUE(mapGoldCorners.find(arucoId) != mapGoldCorners.end());
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
EXPECT_NEAR(static_cast<float>(mapGoldCorners[arucoId][j*2]), corners[i][j].x, 1.f);
|
||||
EXPECT_NEAR(static_cast<float>(mapGoldCorners[arucoId][j*2+1]), corners[i][j].y, 1.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CV_ArucoTutorial, can_find_choriginal)
|
||||
{
|
||||
string imgPath = cvtest::findDataFile("aruco/choriginal.jpg");
|
||||
Mat image = imread(imgPath);
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250));
|
||||
|
||||
vector< int > ids;
|
||||
vector< vector< Point2f > > corners, rejected;
|
||||
const size_t N = 17ull;
|
||||
// corners of aruco markers with indices goldCornersIds
|
||||
const int goldCorners[N][8] = { {268,77, 290,80, 286,97, 263,94}, {360,90, 382,93, 379,111, 357,108},
|
||||
{211,106, 233,109, 228,127, 205,123}, {306,120, 328,124, 325,142, 302,138},
|
||||
{402,135, 425,139, 423,157, 400,154}, {247,152, 271,155, 267,174, 242,171},
|
||||
{347,167, 371,171, 369,191, 344,187}, {185,185, 209,189, 203,210, 178,206},
|
||||
{288,201, 313,206, 309,227, 284,223}, {393,218, 418,222, 416,245, 391,241},
|
||||
{223,240, 250,244, 244,268, 217,263}, {333,258, 359,262, 356,286, 329,282},
|
||||
{152,281, 179,285, 171,312, 143,307}, {267,300, 294,305, 289,331, 261,327},
|
||||
{383,319, 410,324, 408,351, 380,347}, {194,347, 223,352, 216,382, 186,377},
|
||||
{315,368, 345,373, 341,403, 310,398} };
|
||||
map<int, const int*> mapGoldCorners;
|
||||
for (int i = 0; i < static_cast<int>(N); i++)
|
||||
mapGoldCorners[i] = goldCorners[i];
|
||||
|
||||
detector.detectMarkers(image, corners, ids, rejected);
|
||||
|
||||
ASSERT_EQ(N, ids.size());
|
||||
for (size_t i = 0; i < N; i++)
|
||||
{
|
||||
int arucoId = ids[i];
|
||||
ASSERT_EQ(4ull, corners[i].size());
|
||||
ASSERT_TRUE(mapGoldCorners.find(arucoId) != mapGoldCorners.end());
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
EXPECT_NEAR(static_cast<float>(mapGoldCorners[arucoId][j * 2]), corners[i][j].x, 1.f);
|
||||
EXPECT_NEAR(static_cast<float>(mapGoldCorners[arucoId][j * 2 + 1]), corners[i][j].y, 1.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CV_ArucoTutorial, can_find_chocclusion)
|
||||
{
|
||||
string imgPath = cvtest::findDataFile("aruco/chocclusion_original.jpg");
|
||||
Mat image = imread(imgPath);
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250));
|
||||
|
||||
vector< int > ids;
|
||||
vector< vector< Point2f > > corners, rejected;
|
||||
const size_t N = 13ull;
|
||||
// corners of aruco markers with indices goldCornersIds
|
||||
const int goldCorners[N][8] = { {301,57, 322,62, 317,79, 295,73}, {391,80, 413,85, 408,103, 386,97},
|
||||
{242,79, 264,85, 256,102, 234,96}, {334,103, 357,109, 352,126, 329,121},
|
||||
{428,129, 451,134, 448,152, 425,146}, {274,128, 296,134, 290,153, 266,147},
|
||||
{371,154, 394,160, 390,180, 366,174}, {208,155, 232,161, 223,181, 199,175},
|
||||
{309,182, 333,188, 327,209, 302,203}, {411,210, 436,216, 432,238, 407,231},
|
||||
{241,212, 267,219, 258,242, 232,235}, {167,244, 194,252, 183,277, 156,269},
|
||||
{202,314, 230,322, 220,349, 191,341} };
|
||||
map<int, const int*> mapGoldCorners;
|
||||
const int goldCornersIds[N] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15};
|
||||
for (int i = 0; i < static_cast<int>(N); i++)
|
||||
mapGoldCorners[goldCornersIds[i]] = goldCorners[i];
|
||||
|
||||
detector.detectMarkers(image, corners, ids, rejected);
|
||||
|
||||
ASSERT_EQ(N, ids.size());
|
||||
for (size_t i = 0; i < N; i++)
|
||||
{
|
||||
int arucoId = ids[i];
|
||||
ASSERT_EQ(4ull, corners[i].size());
|
||||
ASSERT_TRUE(mapGoldCorners.find(arucoId) != mapGoldCorners.end());
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
EXPECT_NEAR(static_cast<float>(mapGoldCorners[arucoId][j * 2]), corners[i][j].x, 1.f);
|
||||
EXPECT_NEAR(static_cast<float>(mapGoldCorners[arucoId][j * 2 + 1]), corners[i][j].y, 1.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CV_ArucoTutorial, can_find_diamondmarkers)
|
||||
{
|
||||
string imgPath = cvtest::findDataFile("aruco/diamondmarkers.jpg");
|
||||
Mat image = imread(imgPath);
|
||||
|
||||
string dictPath = cvtest::findDataFile("aruco/tutorial_dict.yml");
|
||||
aruco::Dictionary dictionary;
|
||||
FileStorage fs(dictPath, FileStorage::READ);
|
||||
dictionary.aruco::Dictionary::readDictionary(fs.root()); // set marker from tutorial_dict.yml
|
||||
|
||||
string detectorPath = cvtest::findDataFile("aruco/detector_params.yml");
|
||||
fs = FileStorage(detectorPath, FileStorage::READ);
|
||||
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);
|
||||
|
||||
vector<int> ids;
|
||||
vector<vector<Point2f> > corners, diamondCorners;
|
||||
vector<Vec4i> diamondIds;
|
||||
const size_t N = 12ull;
|
||||
// corner indices of ArUco markers
|
||||
const int goldCornersIds[N] = { 4, 12, 11, 3, 12, 10, 12, 10, 10, 11, 2, 11 };
|
||||
map<int, int> counterGoldCornersIds;
|
||||
for (int i = 0; i < static_cast<int>(N); i++)
|
||||
counterGoldCornersIds[goldCornersIds[i]]++;
|
||||
|
||||
const size_t diamondsN = 3;
|
||||
// corners of diamonds with Vec4i indices
|
||||
// Note: Values adjusted by -0.5px after fixing the systematic offset bug in charuco_detector.cpp
|
||||
// The fix removes the incorrect +0.5 offset that was added after cornerSubPix
|
||||
const float goldDiamondCorners[diamondsN][8] = {{195.1f,150.4f, 213.0f,200.7f, 135.9f,214.8f, 121.9f,163.0f},
|
||||
{500.6f,170.8f, 501.4f,208.0f, 445.7f,199.3f, 447.3f,162.8f},
|
||||
{342.9f,360.7f, 359.2f,328.2f, 400.3f,344.1f, 385.2f,377.9f}};
|
||||
auto comp = [](const Vec4i& a, const Vec4i& b) {
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (a[i] != b[i]) return a[i] < b[i];
|
||||
return a[3] < b[3];
|
||||
};
|
||||
map<Vec4i, const float*, decltype(comp)> goldDiamonds(comp);
|
||||
goldDiamonds[Vec4i(10, 4, 11, 12)] = goldDiamondCorners[0];
|
||||
goldDiamonds[Vec4i(10, 3, 11, 12)] = goldDiamondCorners[1];
|
||||
goldDiamonds[Vec4i(10, 2, 11, 12)] = goldDiamondCorners[2];
|
||||
|
||||
detector.detectDiamonds(image, diamondCorners, diamondIds, corners, ids);
|
||||
map<int, int> counterRes;
|
||||
|
||||
ASSERT_EQ(N, ids.size());
|
||||
for (size_t i = 0; i < N; i++)
|
||||
{
|
||||
int arucoId = ids[i];
|
||||
counterRes[arucoId]++;
|
||||
}
|
||||
|
||||
ASSERT_EQ(counterGoldCornersIds, counterRes); // check the number of ArUco markers
|
||||
ASSERT_EQ(goldDiamonds.size(), diamondIds.size()); // check the number of diamonds
|
||||
|
||||
for (size_t i = 0; i < goldDiamonds.size(); i++)
|
||||
{
|
||||
Vec4i diamondId = diamondIds[i];
|
||||
ASSERT_TRUE(goldDiamonds.find(diamondId) != goldDiamonds.end());
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
EXPECT_NEAR(goldDiamonds[diamondId][j * 2], diamondCorners[i][j].x, 0.5f);
|
||||
EXPECT_NEAR(goldDiamonds[diamondId][j * 2 + 1], diamondCorners[i][j].y, 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,205 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
#include "test_aruco_utils.hpp"
|
||||
|
||||
namespace opencv_test {
|
||||
|
||||
vector<Point2f> getAxis(InputArray _cameraMatrix, InputArray _distCoeffs, InputArray _rvec,
|
||||
InputArray _tvec, float length, const Point2f offset) {
|
||||
vector<Point3f> axis;
|
||||
axis.push_back(Point3f(offset.x, offset.y, 0.f));
|
||||
axis.push_back(Point3f(length+offset.x, offset.y, 0.f));
|
||||
axis.push_back(Point3f(offset.x, length+offset.y, 0.f));
|
||||
axis.push_back(Point3f(offset.x, offset.y, length));
|
||||
vector<Point2f> axis_to_img;
|
||||
projectPoints(axis, _rvec, _tvec, _cameraMatrix, _distCoeffs, axis_to_img);
|
||||
return axis_to_img;
|
||||
}
|
||||
|
||||
vector<Point2f> getMarkerById(int id, const vector<vector<Point2f> >& corners, const vector<int>& ids) {
|
||||
for (size_t i = 0ull; i < ids.size(); i++)
|
||||
if (ids[i] == id)
|
||||
return corners[i];
|
||||
return vector<Point2f>();
|
||||
}
|
||||
|
||||
void getSyntheticRT(double yaw, double pitch, double distance, Mat& rvec, Mat& tvec) {
|
||||
rvec = Mat::zeros(3, 1, CV_64FC1);
|
||||
tvec = Mat::zeros(3, 1, CV_64FC1);
|
||||
|
||||
// rotate "scene" in pitch axis (x-axis)
|
||||
Mat rotPitch(3, 1, CV_64FC1);
|
||||
rotPitch.at<double>(0) = -pitch;
|
||||
rotPitch.at<double>(1) = 0;
|
||||
rotPitch.at<double>(2) = 0;
|
||||
|
||||
// rotate "scene" in yaw (y-axis)
|
||||
Mat rotYaw(3, 1, CV_64FC1);
|
||||
rotYaw.at<double>(0) = 0;
|
||||
rotYaw.at<double>(1) = yaw;
|
||||
rotYaw.at<double>(2) = 0;
|
||||
|
||||
// compose both rotations
|
||||
composeRT(rotPitch, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotYaw,
|
||||
Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec);
|
||||
|
||||
// Tvec, just move in z (camera) direction the specific distance
|
||||
tvec.at<double>(0) = 0.;
|
||||
tvec.at<double>(1) = 0.;
|
||||
tvec.at<double>(2) = distance;
|
||||
}
|
||||
|
||||
void projectMarker(Mat& img, const aruco::Board& board, int markerIndex, Mat cameraMatrix, Mat rvec, Mat tvec,
|
||||
int markerBorder) {
|
||||
// canonical image
|
||||
Mat markerImg;
|
||||
const int markerSizePixels = 100;
|
||||
aruco::generateImageMarker(board.getDictionary(), board.getIds()[markerIndex], markerSizePixels, markerImg, markerBorder);
|
||||
|
||||
// projected corners
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
vector<Point2f> corners;
|
||||
|
||||
// get max coordinate of board
|
||||
Point3f maxCoord = board.getRightBottomCorner();
|
||||
// copy objPoints
|
||||
vector<Point3f> objPoints = board.getObjPoints()[markerIndex];
|
||||
// move the marker to the origin
|
||||
for (size_t i = 0; i < objPoints.size(); i++)
|
||||
objPoints[i] -= (maxCoord / 2.f);
|
||||
|
||||
projectPoints(objPoints, rvec, tvec, cameraMatrix, distCoeffs, corners);
|
||||
|
||||
// get perspective transform
|
||||
vector<Point2f> originalCorners;
|
||||
originalCorners.push_back(Point2f(0, 0));
|
||||
originalCorners.push_back(Point2f((float)markerSizePixels, 0));
|
||||
originalCorners.push_back(Point2f((float)markerSizePixels, (float)markerSizePixels));
|
||||
originalCorners.push_back(Point2f(0, (float)markerSizePixels));
|
||||
Mat transformation = getPerspectiveTransform(originalCorners, corners);
|
||||
|
||||
// apply transformation
|
||||
Mat aux;
|
||||
const char borderValue = 127;
|
||||
warpPerspective(markerImg, aux, transformation, img.size(), INTER_NEAREST, BORDER_CONSTANT,
|
||||
Scalar::all(borderValue));
|
||||
|
||||
// copy only not-border pixels
|
||||
for (int y = 0; y < aux.rows; y++) {
|
||||
for (int x = 0; x < aux.cols; x++) {
|
||||
if (aux.at< unsigned char >(y, x) == borderValue) continue;
|
||||
img.at< unsigned char >(y, x) = aux.at< unsigned char >(y, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Mat projectBoard(const aruco::GridBoard& board, Mat cameraMatrix, double yaw, double pitch, double distance,
|
||||
Size imageSize, int markerBorder) {
|
||||
Mat rvec, tvec;
|
||||
getSyntheticRT(yaw, pitch, distance, rvec, tvec);
|
||||
|
||||
Mat img = Mat(imageSize, CV_8UC1, Scalar::all(255));
|
||||
for (unsigned int index = 0; index < board.getIds().size(); index++)
|
||||
projectMarker(img, board, index, cameraMatrix, rvec, tvec, markerBorder);
|
||||
return img;
|
||||
}
|
||||
|
||||
/** Check if a set of 3d points are enough for calibration. Z coordinate is ignored.
|
||||
* Only axis parallel lines are considered */
|
||||
static bool _arePointsEnoughForPoseEstimation(const std::vector<Point3f> &points) {
|
||||
if(points.size() < 4) return false;
|
||||
|
||||
std::vector<double> sameXValue; // different x values in points
|
||||
std::vector<int> sameXCounter; // number of points with the x value in sameXValue
|
||||
for(unsigned int i = 0; i < points.size(); i++) {
|
||||
bool found = false;
|
||||
for(unsigned int j = 0; j < sameXValue.size(); j++) {
|
||||
if(sameXValue[j] == points[i].x) {
|
||||
found = true;
|
||||
sameXCounter[j]++;
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
sameXValue.push_back(points[i].x);
|
||||
sameXCounter.push_back(1);
|
||||
}
|
||||
}
|
||||
|
||||
// count how many x values has more than 2 points
|
||||
int moreThan2 = 0;
|
||||
for(unsigned int i = 0; i < sameXCounter.size(); i++) {
|
||||
if(sameXCounter[i] >= 2) moreThan2++;
|
||||
}
|
||||
|
||||
// if we have more than 1 two xvalues with more than 2 points, calibration is ok
|
||||
if(moreThan2 > 1)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool getCharucoBoardPose(InputArray charucoCorners, InputArray charucoIds, const aruco::CharucoBoard &board,
|
||||
InputArray cameraMatrix, InputArray distCoeffs, InputOutputArray rvec, InputOutputArray tvec,
|
||||
bool useExtrinsicGuess) {
|
||||
CV_Assert((charucoCorners.getMat().total() == charucoIds.getMat().total()));
|
||||
if(charucoIds.getMat().total() < 4) return false; // need, at least, 4 corners
|
||||
|
||||
std::vector<Point3f> objPoints;
|
||||
objPoints.reserve(charucoIds.getMat().total());
|
||||
for(unsigned int i = 0; i < charucoIds.getMat().total(); i++) {
|
||||
int currId = charucoIds.getMat().at< int >(i);
|
||||
CV_Assert(currId >= 0 && currId < (int)board.getChessboardCorners().size());
|
||||
objPoints.push_back(board.getChessboardCorners()[currId]);
|
||||
}
|
||||
|
||||
// points need to be in different lines, check if detected points are enough
|
||||
if(!_arePointsEnoughForPoseEstimation(objPoints)) return false;
|
||||
|
||||
solvePnP(objPoints, charucoCorners, cameraMatrix, distCoeffs, rvec, tvec, useExtrinsicGuess);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return object points for the system centered in a middle (by default) or in a top left corner of single
|
||||
* marker, given the marker length
|
||||
*/
|
||||
static Mat _getSingleMarkerObjectPoints(float markerLength, bool use_aruco_ccw_center) {
|
||||
CV_Assert(markerLength > 0);
|
||||
Mat objPoints(4, 1, CV_32FC3);
|
||||
// set coordinate system in the top-left corner of the marker, with Z pointing out
|
||||
if (use_aruco_ccw_center) {
|
||||
objPoints.ptr<Vec3f>(0)[0] = Vec3f(-markerLength/2.f, markerLength/2.f, 0);
|
||||
objPoints.ptr<Vec3f>(0)[1] = Vec3f(markerLength/2.f, markerLength/2.f, 0);
|
||||
objPoints.ptr<Vec3f>(0)[2] = Vec3f(markerLength/2.f, -markerLength/2.f, 0);
|
||||
objPoints.ptr<Vec3f>(0)[3] = Vec3f(-markerLength/2.f, -markerLength/2.f, 0);
|
||||
}
|
||||
else {
|
||||
objPoints.ptr<Vec3f>(0)[0] = Vec3f(0.f, 0.f, 0);
|
||||
objPoints.ptr<Vec3f>(0)[1] = Vec3f(markerLength, 0.f, 0);
|
||||
objPoints.ptr<Vec3f>(0)[2] = Vec3f(markerLength, markerLength, 0);
|
||||
objPoints.ptr<Vec3f>(0)[3] = Vec3f(0.f, markerLength, 0);
|
||||
}
|
||||
return objPoints;
|
||||
}
|
||||
|
||||
void getMarkersPoses(InputArrayOfArrays corners, float markerLength, InputArray cameraMatrix, InputArray distCoeffs,
|
||||
OutputArray _rvecs, OutputArray _tvecs, OutputArray objPoints,
|
||||
bool use_aruco_ccw_center, SolvePnPMethod solvePnPMethod) {
|
||||
CV_Assert(markerLength > 0);
|
||||
Mat markerObjPoints = _getSingleMarkerObjectPoints(markerLength, use_aruco_ccw_center);
|
||||
int nMarkers = (int)corners.total();
|
||||
_rvecs.create(nMarkers, 1, CV_64FC3);
|
||||
_tvecs.create(nMarkers, 1, CV_64FC3);
|
||||
|
||||
Mat rvecs = _rvecs.getMat(), tvecs = _tvecs.getMat();
|
||||
for (int i = 0; i < nMarkers; i++)
|
||||
solvePnP(markerObjPoints, corners.getMat(i), cameraMatrix, distCoeffs, rvecs.at<Vec3d>(i), tvecs.at<Vec3d>(i),
|
||||
false, solvePnPMethod);
|
||||
|
||||
if(objPoints.needed())
|
||||
markerObjPoints.convertTo(objPoints, -1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "opencv2/geometry.hpp"
|
||||
|
||||
namespace opencv_test {
|
||||
|
||||
static inline double deg2rad(double deg) { return deg * CV_PI / 180.; }
|
||||
|
||||
vector<Point2f> getAxis(InputArray _cameraMatrix, InputArray _distCoeffs, InputArray _rvec, InputArray _tvec,
|
||||
float length, const Point2f offset = Point2f(0, 0));
|
||||
|
||||
vector<Point2f> getMarkerById(int id, const vector<vector<Point2f> >& corners, const vector<int>& ids);
|
||||
|
||||
/**
|
||||
* @brief Get rvec and tvec from yaw, pitch and distance
|
||||
*/
|
||||
void getSyntheticRT(double yaw, double pitch, double distance, Mat& rvec, Mat& tvec);
|
||||
|
||||
/**
|
||||
* @brief Project a synthetic marker
|
||||
*/
|
||||
void projectMarker(Mat& img, const aruco::Board& board, int markerIndex, Mat cameraMatrix, Mat rvec, Mat tvec,
|
||||
int markerBorder);
|
||||
|
||||
/**
|
||||
* @brief Get a synthetic image of GridBoard in perspective
|
||||
*/
|
||||
Mat projectBoard(const aruco::GridBoard& board, Mat cameraMatrix, double yaw, double pitch, double distance,
|
||||
Size imageSize, int markerBorder);
|
||||
|
||||
bool getCharucoBoardPose(InputArray charucoCorners, InputArray charucoIds, const aruco::CharucoBoard &board,
|
||||
InputArray cameraMatrix, InputArray distCoeffs, InputOutputArray rvec,
|
||||
InputOutputArray tvec, bool useExtrinsicGuess = false);
|
||||
|
||||
void getMarkersPoses(InputArrayOfArrays corners, float markerLength, InputArray cameraMatrix, InputArray distCoeffs,
|
||||
OutputArray _rvecs, OutputArray _tvecs, OutputArray objPoints = noArray(),
|
||||
bool use_aruco_ccw_center = true, SolvePnPMethod solvePnPMethod = SolvePnPMethod::SOLVEPNP_ITERATIVE);
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,253 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "opencv2/objdetect/barcode.hpp"
|
||||
#include <set>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace opencv_test{namespace{
|
||||
|
||||
typedef std::set<string> StringSet;
|
||||
|
||||
// Convert ';'-separated strings to a set
|
||||
inline static StringSet toSet(const string &line)
|
||||
{
|
||||
StringSet res;
|
||||
string::size_type it = 0, ti;
|
||||
while (true)
|
||||
{
|
||||
ti = line.find(';', it);
|
||||
if (ti == string::npos)
|
||||
{
|
||||
res.insert(string(line, it, line.size() - it));
|
||||
break;
|
||||
}
|
||||
res.insert(string(line, it, ti - it));
|
||||
it = ti + 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Convert vector of strings to a set
|
||||
inline static StringSet toSet(const vector<string> &lines)
|
||||
{
|
||||
StringSet res;
|
||||
for (const string & line : lines)
|
||||
res.insert(line);
|
||||
return res;
|
||||
}
|
||||
|
||||
// Get all keys of a map in a vector
|
||||
template<typename T, typename V>
|
||||
inline static vector<T> getKeys(const map<T, V> &m)
|
||||
{
|
||||
vector<T> res;
|
||||
for (const auto & it : m)
|
||||
res.push_back(it.first);
|
||||
return res;
|
||||
}
|
||||
|
||||
struct BarcodeResult
|
||||
{
|
||||
string type;
|
||||
string data;
|
||||
};
|
||||
|
||||
map<string, BarcodeResult> testResults {
|
||||
{ "single/book.jpg", {"EAN_13", "9787115279460"} },
|
||||
{ "single/bottle_1.jpg", {"EAN_13", "6922255451427"} },
|
||||
{ "single/bottle_2.jpg", {"EAN_13", "6921168509256"} },
|
||||
{ "multiple/4_barcodes.jpg", {"EAN_13;EAN_13;EAN_13;EAN_13", "9787564350840;9783319200064;9787118081473;9787122276124"} },
|
||||
};
|
||||
|
||||
typedef testing::TestWithParam< string > BarcodeDetector_main;
|
||||
|
||||
TEST_P(BarcodeDetector_main, interface)
|
||||
{
|
||||
const string fname = GetParam();
|
||||
const string image_path = findDataFile(string("barcode/") + fname);
|
||||
const StringSet expected_lines = toSet(testResults[fname].data);
|
||||
const StringSet expected_types = toSet(testResults[fname].type);
|
||||
const size_t expected_count = expected_lines.size(); // assume codes are unique
|
||||
// TODO: verify points location
|
||||
|
||||
Mat img = imread(image_path);
|
||||
ASSERT_FALSE(img.empty()) << "Can't read image: " << image_path;
|
||||
|
||||
barcode::BarcodeDetector det;
|
||||
vector<Point2f> points;
|
||||
vector<string> types;
|
||||
vector<string> lines;
|
||||
|
||||
// common interface (single)
|
||||
{
|
||||
bool res = det.detect(img, points);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(expected_count * 4, points.size());
|
||||
}
|
||||
|
||||
{
|
||||
string res = det.decode(img, points);
|
||||
ASSERT_FALSE(res.empty());
|
||||
EXPECT_EQ(1u, expected_lines.count(res));
|
||||
}
|
||||
|
||||
{
|
||||
string res = det.detectAndDecode(img, points);
|
||||
ASSERT_FALSE(res.empty());
|
||||
EXPECT_EQ(1u, expected_lines.count(res));
|
||||
EXPECT_EQ(4u, points.size());
|
||||
}
|
||||
|
||||
// common interface (multi)
|
||||
{
|
||||
bool res = det.detectMulti(img, points);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(expected_count * 4, points.size());
|
||||
}
|
||||
|
||||
{
|
||||
bool res = det.decodeMulti(img, points, lines);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(expected_lines, toSet(lines));
|
||||
}
|
||||
|
||||
// specific interface
|
||||
{
|
||||
bool res = det.decodeWithType(img, points, lines, types);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(expected_types, toSet(types));
|
||||
EXPECT_EQ(expected_lines, toSet(lines));
|
||||
}
|
||||
|
||||
{
|
||||
bool res = det.detectAndDecodeWithType(img, lines, types, points);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(expected_types, toSet(types));
|
||||
EXPECT_EQ(expected_lines, toSet(lines));
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/**/, BarcodeDetector_main, testing::ValuesIn(getKeys(testResults)));
|
||||
|
||||
TEST(BarcodeDetector_base, invalid)
|
||||
{
|
||||
auto bardet = barcode::BarcodeDetector();
|
||||
std::vector<Point> corners;
|
||||
vector<cv::String> decoded_info;
|
||||
Mat zero_image = Mat::zeros(256, 256, CV_8UC1);
|
||||
EXPECT_FALSE(bardet.detectMulti(zero_image, corners));
|
||||
corners = std::vector<Point>(4);
|
||||
EXPECT_ANY_THROW(bardet.decodeMulti(zero_image, corners, decoded_info));
|
||||
}
|
||||
|
||||
struct ParamStruct
|
||||
{
|
||||
double down_thresh;
|
||||
vector<float> scales;
|
||||
double grad_thresh;
|
||||
unsigned res_count;
|
||||
};
|
||||
|
||||
inline static std::ostream &operator<<(std::ostream &out, const ParamStruct &p)
|
||||
{
|
||||
out << "(" << p.down_thresh << ", ";
|
||||
for(float val : p.scales)
|
||||
out << val << ", ";
|
||||
out << p.grad_thresh << ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
ParamStruct param_list[] = {
|
||||
{ 512, {0.01f, 0.03f, 0.06f, 0.08f}, 64, 4 }, // default values -> 4 codes
|
||||
{ 512, {0.01f, 0.03f, 0.06f, 0.08f}, 1024, 2 },
|
||||
{ 512, {0.01f, 0.03f, 0.06f, 0.08f}, 2048, 0 },
|
||||
{ 128, {0.01f, 0.03f, 0.06f, 0.08f}, 64, 3 },
|
||||
{ 64, {0.01f, 0.03f, 0.06f, 0.08f}, 64, 2 },
|
||||
{ 128, {0.0000001f}, 64, 1 },
|
||||
{ 128, {0.0000001f, 0.0001f}, 64, 1 },
|
||||
{ 128, {0.0000001f, 0.1f}, 64, 1 },
|
||||
{ 512, {0.1f}, 64, 0 },
|
||||
};
|
||||
|
||||
typedef testing::TestWithParam<ParamStruct> BarcodeDetector_parameters_tune;
|
||||
|
||||
TEST_P(BarcodeDetector_parameters_tune, accuracy)
|
||||
{
|
||||
const ParamStruct param = GetParam();
|
||||
|
||||
const string fname = "multiple/4_barcodes.jpg";
|
||||
const string image_path = findDataFile(string("barcode/") + fname);
|
||||
|
||||
const Mat img = imread(image_path);
|
||||
ASSERT_FALSE(img.empty()) << "Can't read image: " << image_path;
|
||||
|
||||
auto bardet = barcode::BarcodeDetector();
|
||||
bardet.setDownsamplingThreshold(param.down_thresh);
|
||||
bardet.setDetectorScales(param.scales);
|
||||
bardet.setGradientThreshold(param.grad_thresh);
|
||||
vector<Point2f> points;
|
||||
bardet.detectMulti(img, points);
|
||||
EXPECT_EQ(points.size() / 4, param.res_count);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/**/, BarcodeDetector_parameters_tune, testing::ValuesIn(param_list));
|
||||
|
||||
TEST(BarcodeDetector_parameters, regression)
|
||||
{
|
||||
const double expected_dt = 1024, expected_gt = 256;
|
||||
const vector<float> expected_ds = {0.1f};
|
||||
vector<float> ds_value = {0.0f};
|
||||
|
||||
auto bardet = barcode::BarcodeDetector();
|
||||
|
||||
bardet.setDownsamplingThreshold(expected_dt).setDetectorScales(expected_ds).setGradientThreshold(expected_gt);
|
||||
|
||||
double dt_value = bardet.getDownsamplingThreshold();
|
||||
bardet.getDetectorScales(ds_value);
|
||||
double gt_value = bardet.getGradientThreshold();
|
||||
|
||||
EXPECT_EQ(expected_dt, dt_value);
|
||||
EXPECT_EQ(expected_ds, ds_value);
|
||||
EXPECT_EQ(expected_gt, gt_value);
|
||||
}
|
||||
|
||||
TEST(BarcodeDetector_parameters, invalid)
|
||||
{
|
||||
auto bardet = barcode::BarcodeDetector();
|
||||
|
||||
EXPECT_ANY_THROW(bardet.setDownsamplingThreshold(-1));
|
||||
EXPECT_ANY_THROW(bardet.setDetectorScales(vector<float> {}));
|
||||
EXPECT_ANY_THROW(bardet.setDetectorScales(vector<float> {-1}));
|
||||
EXPECT_ANY_THROW(bardet.setDetectorScales(vector<float> {1.5}));
|
||||
EXPECT_ANY_THROW(bardet.setDetectorScales(vector<float> (17, 0.5)));
|
||||
EXPECT_ANY_THROW(bardet.setGradientThreshold(-0.1));
|
||||
}
|
||||
|
||||
TEST(BarcodeDetector_super_resolution, accuracy)
|
||||
{
|
||||
// Reuse the existing WeChat Super Resolution ONNX model shipped in opencv_extra.
|
||||
const string sr_path = findDataFile("dnn/wechat_2021-01/sr.onnx", false);
|
||||
if (sr_path.empty())
|
||||
throw SkipTestException("Missing super resolution model (dnn/wechat_2021-01/sr.onnx)");
|
||||
|
||||
const string fname = "single/book.jpg";
|
||||
const string image_path = findDataFile("barcode/" + fname);
|
||||
Mat img = imread(image_path);
|
||||
ASSERT_FALSE(img.empty()) << "Can't read image: " << image_path;
|
||||
|
||||
// Construct with the ONNX super resolution model enabled.
|
||||
barcode::BarcodeDetector det(sr_path);
|
||||
|
||||
vector<string> lines, types;
|
||||
vector<Point2f> points;
|
||||
bool res = det.detectAndDecodeWithType(img, lines, types, points);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(toSet(testResults[fname].type), toSet(types));
|
||||
EXPECT_EQ(toSet(testResults[fname].data), toSet(lines));
|
||||
}
|
||||
|
||||
}} // opencv_test::<anonymous>::
|
||||
@@ -0,0 +1,379 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_aruco_utils.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
enum class ArucoAlgParams
|
||||
{
|
||||
USE_DEFAULT = 0,
|
||||
USE_ARUCO3 = 1
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Check pose estimation of aruco board
|
||||
*/
|
||||
class CV_ArucoBoardPose : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_ArucoBoardPose(ArucoAlgParams arucoAlgParams)
|
||||
{
|
||||
aruco::DetectorParameters params;
|
||||
aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
|
||||
params.minDistanceToBorder = 3;
|
||||
if (arucoAlgParams == ArucoAlgParams::USE_ARUCO3) {
|
||||
params.useAruco3Detection = true;
|
||||
params.cornerRefinementMethod = (int)aruco::CORNER_REFINE_SUBPIX;
|
||||
params.minSideLengthCanonicalImg = 16;
|
||||
params.errorCorrectionRate = 0.8;
|
||||
}
|
||||
detector = aruco::ArucoDetector(dictionary, params);
|
||||
}
|
||||
|
||||
protected:
|
||||
aruco::ArucoDetector detector;
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
void CV_ArucoBoardPose::run(int) {
|
||||
int iter = 0;
|
||||
Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1);
|
||||
Size imgSize(500, 500);
|
||||
cameraMatrix.at< double >(0, 0) = cameraMatrix.at< double >(1, 1) = 650;
|
||||
cameraMatrix.at< double >(0, 2) = imgSize.width / 2;
|
||||
cameraMatrix.at< double >(1, 2) = imgSize.height / 2;
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
const int sizeX = 3, sizeY = 3;
|
||||
aruco::DetectorParameters detectorParameters = detector.getDetectorParameters();
|
||||
|
||||
// for different perspectives
|
||||
for(double distance : {0.2, 0.35}) {
|
||||
for(int yaw = -55; yaw <= 50; yaw += 25) {
|
||||
for(int pitch = -55; pitch <= 50; pitch += 25) {
|
||||
vector<int> tmpIds;
|
||||
for(int i = 0; i < sizeX*sizeY; i++)
|
||||
tmpIds.push_back((iter + int(i)) % 250);
|
||||
aruco::GridBoard gridboard(Size(sizeX, sizeY), 0.02f, 0.005f, detector.getDictionary(), tmpIds);
|
||||
int markerBorder = iter % 2 + 1;
|
||||
iter++;
|
||||
// create synthetic image
|
||||
Mat img = projectBoard(gridboard, cameraMatrix, deg2rad(yaw), deg2rad(pitch), distance,
|
||||
imgSize, markerBorder);
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
detectorParameters.markerBorderBits = markerBorder;
|
||||
detectorParameters.validBitIdThreshold = 0.5f;
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
detector.detectMarkers(img, corners, ids);
|
||||
|
||||
ASSERT_EQ(ids.size(), gridboard.getIds().size());
|
||||
|
||||
// estimate pose
|
||||
Mat rvec, tvec;
|
||||
{
|
||||
Mat objPoints, imgPoints; // get object and image points for the solvePnP function
|
||||
gridboard.matchImagePoints(corners, ids, objPoints, imgPoints);
|
||||
solvePnP(objPoints, imgPoints, cameraMatrix, distCoeffs, rvec, tvec);
|
||||
}
|
||||
|
||||
// check axes
|
||||
vector<Point2f> axes = getAxis(cameraMatrix, distCoeffs, rvec, tvec, gridboard.getRightBottomCorner().x);
|
||||
vector<Point2f> topLeft = getMarkerById(gridboard.getIds()[0], corners, ids);
|
||||
ASSERT_NEAR(topLeft[0].x, axes[0].x, 2.f);
|
||||
ASSERT_NEAR(topLeft[0].y, axes[0].y, 2.f);
|
||||
vector<Point2f> topRight = getMarkerById(gridboard.getIds()[2], corners, ids);
|
||||
ASSERT_NEAR(topRight[1].x, axes[1].x, 2.f);
|
||||
ASSERT_NEAR(topRight[1].y, axes[1].y, 2.f);
|
||||
vector<Point2f> bottomLeft = getMarkerById(gridboard.getIds()[6], corners, ids);
|
||||
ASSERT_NEAR(bottomLeft[3].x, axes[2].x, 2.f);
|
||||
ASSERT_NEAR(bottomLeft[3].y, axes[2].y, 2.f);
|
||||
|
||||
// check estimate result
|
||||
for(unsigned int i = 0; i < ids.size(); i++) {
|
||||
int foundIdx = -1;
|
||||
for(unsigned int j = 0; j < gridboard.getIds().size(); j++) {
|
||||
if(gridboard.getIds()[j] == ids[i]) {
|
||||
foundIdx = int(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(foundIdx == -1) {
|
||||
ts->printf(cvtest::TS::LOG, "Marker detected with wrong ID in Board test");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
vector< Point2f > projectedCorners;
|
||||
projectPoints(gridboard.getObjPoints()[foundIdx], rvec, tvec, cameraMatrix, distCoeffs,
|
||||
projectedCorners);
|
||||
|
||||
for(int c = 0; c < 4; c++) {
|
||||
double repError = cv::norm(projectedCorners[c] - corners[i][c]); // TODO cvtest
|
||||
if(repError > 5.) {
|
||||
ts->printf(cvtest::TS::LOG, "Corner reprojection error too high");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check refine strategy
|
||||
*/
|
||||
class CV_ArucoRefine : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_ArucoRefine(ArucoAlgParams arucoAlgParams)
|
||||
{
|
||||
vector<aruco::Dictionary> dictionaries = {aruco::getPredefinedDictionary(aruco::DICT_6X6_250),
|
||||
aruco::getPredefinedDictionary(aruco::DICT_5X5_250),
|
||||
aruco::getPredefinedDictionary(aruco::DICT_4X4_250),
|
||||
aruco::getPredefinedDictionary(aruco::DICT_7X7_250)};
|
||||
aruco::DetectorParameters params;
|
||||
params.minDistanceToBorder = 3;
|
||||
params.cornerRefinementMethod = (int)aruco::CORNER_REFINE_SUBPIX;
|
||||
if (arucoAlgParams == ArucoAlgParams::USE_ARUCO3)
|
||||
params.useAruco3Detection = true;
|
||||
aruco::RefineParameters refineParams(10.f, 3.f, true);
|
||||
detector = aruco::ArucoDetector(dictionaries, params, refineParams);
|
||||
}
|
||||
|
||||
protected:
|
||||
aruco::ArucoDetector detector;
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
void CV_ArucoRefine::run(int) {
|
||||
|
||||
int iter = 0;
|
||||
Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1);
|
||||
Size imgSize(500, 500);
|
||||
cameraMatrix.at< double >(0, 0) = cameraMatrix.at< double >(1, 1) = 650;
|
||||
cameraMatrix.at< double >(0, 2) = imgSize.width / 2;
|
||||
cameraMatrix.at< double >(1, 2) = imgSize.height / 2;
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
aruco::DetectorParameters detectorParameters = detector.getDetectorParameters();
|
||||
|
||||
// for different perspectives
|
||||
for(double distance : {0.2, 0.4}) {
|
||||
for(int yaw = -60; yaw < 60; yaw += 30) {
|
||||
for(int pitch = -60; pitch <= 60; pitch += 30) {
|
||||
aruco::GridBoard gridboard(Size(3, 3), 0.02f, 0.005f, detector.getDictionary());
|
||||
int markerBorder = iter % 2 + 1;
|
||||
iter++;
|
||||
|
||||
// create synthetic image
|
||||
Mat img = projectBoard(gridboard, cameraMatrix, deg2rad(yaw), deg2rad(pitch), distance,
|
||||
imgSize, markerBorder);
|
||||
// detect markers
|
||||
vector<vector<Point2f> > corners, rejected;
|
||||
vector<int> ids;
|
||||
detectorParameters.markerBorderBits = markerBorder;
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
detector.detectMarkers(img, corners, ids, rejected);
|
||||
|
||||
// remove a marker from detection
|
||||
int markersBeforeDelete = (int)ids.size();
|
||||
if(markersBeforeDelete < 2) continue;
|
||||
|
||||
rejected.push_back(corners[0]);
|
||||
corners.erase(corners.begin(), corners.begin() + 1);
|
||||
ids.erase(ids.begin(), ids.begin() + 1);
|
||||
|
||||
// try to refind the erased marker
|
||||
detector.refineDetectedMarkers(img, gridboard, corners, ids, rejected, cameraMatrix,
|
||||
distCoeffs, noArray());
|
||||
|
||||
// check result
|
||||
if((int)ids.size() < markersBeforeDelete) {
|
||||
ts->printf(cvtest::TS::LOG, "Error in refine detected markers");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CV_ArucoBoardPose, accuracy) {
|
||||
CV_ArucoBoardPose test(ArucoAlgParams::USE_DEFAULT);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
typedef CV_ArucoBoardPose CV_Aruco3BoardPose;
|
||||
TEST(CV_Aruco3BoardPose, accuracy) {
|
||||
CV_Aruco3BoardPose test(ArucoAlgParams::USE_ARUCO3);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
typedef CV_ArucoRefine CV_Aruco3Refine;
|
||||
|
||||
TEST(CV_ArucoRefine, accuracy) {
|
||||
CV_ArucoRefine test(ArucoAlgParams::USE_DEFAULT);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_Aruco3Refine, accuracy) {
|
||||
CV_Aruco3Refine test(ArucoAlgParams::USE_ARUCO3);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_ArucoBoardPose, CheckNegativeZ)
|
||||
{
|
||||
double matrixData[9] = { -3.9062571886921410e+02, 0., 4.2350000000000000e+02,
|
||||
0., 3.9062571886921410e+02, 2.3950000000000000e+02,
|
||||
0., 0., 1 };
|
||||
cv::Mat cameraMatrix = cv::Mat(3, 3, CV_64F, matrixData);
|
||||
|
||||
vector<cv::Point3f> pts3d1, pts3d2;
|
||||
pts3d1.push_back(cv::Point3f(0.326198f, -0.030621f, 0.303620f));
|
||||
pts3d1.push_back(cv::Point3f(0.325340f, -0.100594f, 0.301862f));
|
||||
pts3d1.push_back(cv::Point3f(0.255859f, -0.099530f, 0.293416f));
|
||||
pts3d1.push_back(cv::Point3f(0.256717f, -0.029557f, 0.295174f));
|
||||
|
||||
pts3d2.push_back(cv::Point3f(-0.033144f, -0.034819f, 0.245216f));
|
||||
pts3d2.push_back(cv::Point3f(-0.035507f, -0.104705f, 0.241987f));
|
||||
pts3d2.push_back(cv::Point3f(-0.105289f, -0.102120f, 0.237120f));
|
||||
pts3d2.push_back(cv::Point3f(-0.102926f, -0.032235f, 0.240349f));
|
||||
|
||||
vector<int> tmpIds = {0, 1};
|
||||
vector<vector<Point3f> > tmpObjectPoints = {pts3d1, pts3d2};
|
||||
aruco::Board board(tmpObjectPoints, aruco::getPredefinedDictionary(0), tmpIds);
|
||||
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<Point2f> pts2d;
|
||||
pts2d.push_back(cv::Point2f(37.7f, 203.3f));
|
||||
pts2d.push_back(cv::Point2f(38.5f, 120.5f));
|
||||
pts2d.push_back(cv::Point2f(105.5f, 115.8f));
|
||||
pts2d.push_back(cv::Point2f(104.2f, 202.7f));
|
||||
corners.push_back(pts2d);
|
||||
pts2d.clear();
|
||||
pts2d.push_back(cv::Point2f(476.0f, 184.2f));
|
||||
pts2d.push_back(cv::Point2f(479.6f, 73.8f));
|
||||
pts2d.push_back(cv::Point2f(590.9f, 77.0f));
|
||||
pts2d.push_back(cv::Point2f(587.5f, 188.1f));
|
||||
corners.push_back(pts2d);
|
||||
|
||||
Vec3d rvec, tvec;
|
||||
int nUsed = 0;
|
||||
{
|
||||
Mat objPoints, imgPoints; // get object and image points for the solvePnP function
|
||||
board.matchImagePoints(corners, board.getIds(), objPoints, imgPoints);
|
||||
nUsed = (int)objPoints.total()/4;
|
||||
solvePnP(objPoints, imgPoints, cameraMatrix, Mat(), rvec, tvec);
|
||||
}
|
||||
ASSERT_EQ(nUsed, 2);
|
||||
|
||||
cv::Matx33d rotm; cv::Point3d out;
|
||||
cv::Rodrigues(rvec, rotm);
|
||||
out = cv::Point3d(tvec) + rotm*Point3d(board.getObjPoints()[0][0]);
|
||||
ASSERT_GT(out.z, 0);
|
||||
|
||||
corners.clear(); pts2d.clear();
|
||||
pts2d.push_back(cv::Point2f(38.4f, 204.5f));
|
||||
pts2d.push_back(cv::Point2f(40.0f, 124.7f));
|
||||
pts2d.push_back(cv::Point2f(102.0f, 119.1f));
|
||||
pts2d.push_back(cv::Point2f(99.9f, 203.6f));
|
||||
corners.push_back(pts2d);
|
||||
pts2d.clear();
|
||||
pts2d.push_back(cv::Point2f(476.0f, 184.3f));
|
||||
pts2d.push_back(cv::Point2f(479.2f, 75.1f));
|
||||
pts2d.push_back(cv::Point2f(588.7f, 79.2f));
|
||||
pts2d.push_back(cv::Point2f(586.3f, 188.5f));
|
||||
corners.push_back(pts2d);
|
||||
|
||||
nUsed = 0;
|
||||
{
|
||||
Mat objPoints, imgPoints; // get object and image points for the solvePnP function
|
||||
board.matchImagePoints(corners, board.getIds(), objPoints, imgPoints);
|
||||
nUsed = (int)objPoints.total()/4;
|
||||
solvePnP(objPoints, imgPoints, cameraMatrix, Mat(), rvec, tvec, true);
|
||||
}
|
||||
ASSERT_EQ(nUsed, 2);
|
||||
|
||||
cv::Rodrigues(rvec, rotm);
|
||||
out = cv::Point3d(tvec) + rotm*Point3d(board.getObjPoints()[0][0]);
|
||||
ASSERT_GT(out.z, 0);
|
||||
}
|
||||
|
||||
TEST(CV_ArucoGenerateBoard, regression_1226) {
|
||||
int bwidth = 1600;
|
||||
int bheight = 1200;
|
||||
|
||||
cv::aruco::Dictionary dict = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_4X4_50);
|
||||
cv::aruco::CharucoBoard board(Size(7, 5), 1.0, 0.75, dict);
|
||||
cv::Size sz(bwidth, bheight);
|
||||
cv::Mat mat;
|
||||
|
||||
ASSERT_NO_THROW(
|
||||
{
|
||||
board.generateImage(sz, mat, 0, 1);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(CV_ArucoDictionary, extendDictionary) {
|
||||
aruco::Dictionary base_dictionary = aruco::getPredefinedDictionary(aruco::DICT_4X4_250);
|
||||
aruco::Dictionary custom_dictionary = aruco::extendDictionary(150, 4, base_dictionary);
|
||||
|
||||
ASSERT_EQ(custom_dictionary.bytesList.rows, 150);
|
||||
ASSERT_EQ(cv::norm(custom_dictionary.bytesList, base_dictionary.bytesList.rowRange(0, 150)), 0.);
|
||||
}
|
||||
TEST(CV_ArucoBoardGenerateImage_RotationTest, HandlesRotatedMarkersWithoutBoundingBoxError)
|
||||
{
|
||||
using namespace cv;
|
||||
using namespace cv::aruco;
|
||||
Dictionary dict = getPredefinedDictionary(DICT_4X4_50);
|
||||
DetectorParameters detectorParams;
|
||||
ArucoDetector detector(dict, detectorParams);
|
||||
std::vector<float> angles = {0.0f, 45.0f, 90.0f, 135.0f};
|
||||
for (auto angle_deg : angles)
|
||||
{
|
||||
float angle_rad = angle_deg * static_cast<float>(CV_PI) / 180.0f;
|
||||
float c = cos(angle_rad);
|
||||
float s = sin(angle_rad);
|
||||
std::vector<Point3f> markerCorners(4);
|
||||
markerCorners[0] = Point3f(0.f, 0.f, 0.f);
|
||||
markerCorners[1] = Point3f(1.f, 0.f, 0.f);
|
||||
markerCorners[2] = Point3f(1.f, 1.f, 0.f);
|
||||
markerCorners[3] = Point3f(0.f, 1.f, 0.f);
|
||||
for (auto &p : markerCorners)
|
||||
{
|
||||
float xNew = p.x * c - p.y * s;
|
||||
float yNew = p.x * s + p.y * c;
|
||||
p.x = xNew;
|
||||
p.y = yNew;
|
||||
}
|
||||
std::vector<std::vector<Point3f>> allObjPoints{markerCorners};
|
||||
std::vector<int> ids{0};
|
||||
Board board(allObjPoints, dict, ids);
|
||||
float markerSize = 1.0f;
|
||||
float rotatedSize = markerSize * std::sqrt(2.0f);
|
||||
int borderBits = 1;
|
||||
int marginSize = 20;
|
||||
int sidePixels = static_cast<int>((rotatedSize + 2.0f * borderBits) * 500) + 2 * marginSize;
|
||||
Mat outImg;
|
||||
Size outSize(sidePixels, sidePixels);
|
||||
ASSERT_NO_THROW(board.generateImage(outSize, outImg, marginSize, borderBits))
|
||||
<< "board.generateImage() threw an exception at angle " << angle_deg;
|
||||
std::vector<int> detectedIds;
|
||||
std::vector<std::vector<Point2f>> detectedCorners;
|
||||
detector.detectMarkers(outImg, detectedCorners, detectedIds);
|
||||
ASSERT_EQ(detectedIds.size(), (size_t)1)
|
||||
<< "Failed to detect single marker at angle: " << angle_deg;
|
||||
EXPECT_EQ(detectedIds[0], 0)
|
||||
<< "Marker ID mismatch at angle: " << angle_deg;
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,980 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_aruco_utils.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
/**
|
||||
* @brief Get a synthetic image of Chessboard in perspective
|
||||
*/
|
||||
static Mat projectChessboard(int squaresX, int squaresY, float squareSize, Size imageSize,
|
||||
Mat cameraMatrix, Mat rvec, Mat tvec, bool legacyPattern) {
|
||||
|
||||
Mat img(imageSize, CV_8UC1, Scalar::all(255));
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
|
||||
for(int y = 0; y < squaresY; y++) {
|
||||
float startY = float(y) * squareSize;
|
||||
for(int x = 0; x < squaresX; x++) {
|
||||
if(legacyPattern && (squaresY % 2 == 0)) {
|
||||
if((y + 1) % 2 != x % 2) continue;
|
||||
} else {
|
||||
if(y % 2 != x % 2) continue;
|
||||
}
|
||||
float startX = float(x) * squareSize;
|
||||
|
||||
vector< Point3f > squareCorners;
|
||||
squareCorners.push_back(Point3f(startX, startY, 0) - Point3f(squaresX*squareSize/2.f, squaresY*squareSize/2.f, 0.f));
|
||||
squareCorners.push_back(squareCorners[0] + Point3f(squareSize, 0, 0));
|
||||
squareCorners.push_back(squareCorners[0] + Point3f(squareSize, squareSize, 0));
|
||||
squareCorners.push_back(squareCorners[0] + Point3f(0, squareSize, 0));
|
||||
|
||||
vector< vector< Point2f > > projectedCorners;
|
||||
projectedCorners.push_back(vector< Point2f >());
|
||||
projectPoints(squareCorners, rvec, tvec, cameraMatrix, distCoeffs, projectedCorners[0]);
|
||||
|
||||
vector< vector< Point > > projectedCornersInt;
|
||||
projectedCornersInt.push_back(vector< Point >());
|
||||
|
||||
for(int k = 0; k < 4; k++)
|
||||
projectedCornersInt[0]
|
||||
.push_back(Point((int)projectedCorners[0][k].x, (int)projectedCorners[0][k].y));
|
||||
|
||||
fillPoly(img, projectedCornersInt, Scalar::all(0));
|
||||
}
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check pose estimation of charuco board
|
||||
*/
|
||||
static Mat projectCharucoBoard(aruco::CharucoBoard& board, Mat cameraMatrix, double yaw,
|
||||
double pitch, double distance, Size imageSize, int markerBorder,
|
||||
Mat &rvec, Mat &tvec) {
|
||||
|
||||
getSyntheticRT(yaw, pitch, distance, rvec, tvec);
|
||||
|
||||
// project markers
|
||||
Mat img = Mat(imageSize, CV_8UC1, Scalar::all(255));
|
||||
for(unsigned int indexMarker = 0; indexMarker < board.getIds().size(); indexMarker++) {
|
||||
projectMarker(img, board, indexMarker, cameraMatrix, rvec, tvec, markerBorder);
|
||||
}
|
||||
|
||||
// project chessboard
|
||||
Mat chessboard =
|
||||
projectChessboard(board.getChessboardSize().width, board.getChessboardSize().height,
|
||||
board.getSquareLength(), imageSize, cameraMatrix, rvec, tvec, board.getLegacyPattern());
|
||||
|
||||
for(unsigned int i = 0; i < chessboard.total(); i++) {
|
||||
if(chessboard.ptr< unsigned char >()[i] == 0) {
|
||||
img.ptr< unsigned char >()[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
static bool borderPixelsHaveSameColor(const Mat& image, uint8_t color) {
|
||||
for (int j = 0; j < image.cols; j++) {
|
||||
if (image.at<uint8_t>(0, j) != color || image.at<uint8_t>(image.rows-1, j) != color)
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < image.rows; i++) {
|
||||
if (image.at<uint8_t>(i, 0) != color || image.at<uint8_t>(i, image.cols-1) != color)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check Charuco detection
|
||||
*/
|
||||
class CV_CharucoDetection : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_CharucoDetection(bool _legacyPattern) : legacyPattern(_legacyPattern) {}
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
|
||||
bool legacyPattern;
|
||||
};
|
||||
|
||||
|
||||
void CV_CharucoDetection::run(int) {
|
||||
|
||||
int iter = 0;
|
||||
Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1);
|
||||
Size imgSize(500, 500);
|
||||
aruco::DetectorParameters params;
|
||||
params.minDistanceToBorder = 3;
|
||||
aruco::CharucoBoard board(Size(4, 4), 0.03f, 0.015f, aruco::getPredefinedDictionary(aruco::DICT_6X6_250));
|
||||
board.setLegacyPattern(legacyPattern);
|
||||
aruco::CharucoDetector detector(board, aruco::CharucoParameters(), params);
|
||||
|
||||
cameraMatrix.at<double>(0, 0) = cameraMatrix.at<double>(1, 1) = 600;
|
||||
cameraMatrix.at<double>(0, 2) = imgSize.width / 2;
|
||||
cameraMatrix.at<double>(1, 2) = imgSize.height / 2;
|
||||
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
|
||||
// for different perspectives
|
||||
for(double distance : {0.2, 0.4}) {
|
||||
for(int yaw = -55; yaw <= 50; yaw += 25) {
|
||||
for(int pitch = -55; pitch <= 50; pitch += 25) {
|
||||
|
||||
int markerBorder = iter % 2 + 1;
|
||||
iter++;
|
||||
|
||||
// create synthetic image
|
||||
Mat rvec, tvec;
|
||||
Mat img = projectCharucoBoard(board, cameraMatrix, deg2rad(yaw), deg2rad(pitch),
|
||||
distance, imgSize, markerBorder, rvec, tvec);
|
||||
|
||||
// detect markers and interpolate charuco corners
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<Point2f> charucoCorners;
|
||||
vector<int> ids, charucoIds;
|
||||
|
||||
params.markerBorderBits = markerBorder;
|
||||
detector.setDetectorParameters(params);
|
||||
|
||||
//detector.detectMarkers(img, corners, ids);
|
||||
if(iter % 2 == 0) {
|
||||
detector.detectBoard(img, charucoCorners, charucoIds, corners, ids);
|
||||
} else {
|
||||
aruco::CharucoParameters charucoParameters;
|
||||
charucoParameters.cameraMatrix = cameraMatrix;
|
||||
charucoParameters.distCoeffs = distCoeffs;
|
||||
detector.setCharucoParameters(charucoParameters);
|
||||
detector.detectBoard(img, charucoCorners, charucoIds, corners, ids);
|
||||
}
|
||||
|
||||
ASSERT_GT(ids.size(), std::vector< int >::size_type(0)) << "Marker detection failed";
|
||||
|
||||
// check results
|
||||
vector< Point2f > projectedCharucoCorners;
|
||||
|
||||
// copy chessboardCorners
|
||||
vector<Point3f> copyChessboardCorners = board.getChessboardCorners();
|
||||
// move copyChessboardCorners points
|
||||
for (size_t i = 0; i < copyChessboardCorners.size(); i++)
|
||||
copyChessboardCorners[i] -= board.getRightBottomCorner() / 2.f;
|
||||
projectPoints(copyChessboardCorners, rvec, tvec, cameraMatrix, distCoeffs,
|
||||
projectedCharucoCorners);
|
||||
|
||||
for(unsigned int i = 0; i < charucoIds.size(); i++) {
|
||||
|
||||
int currentId = charucoIds[i];
|
||||
|
||||
ASSERT_LT(currentId, (int)board.getChessboardCorners().size()) << "Invalid Charuco corner id";
|
||||
|
||||
double repError = cv::norm(charucoCorners[i] - projectedCharucoCorners[currentId]); // TODO cvtest
|
||||
|
||||
ASSERT_LE(repError, 5.) << "Charuco corner reprojection error too high";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check charuco pose estimation
|
||||
*/
|
||||
class CV_CharucoPoseEstimation : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_CharucoPoseEstimation(bool _legacyPattern) : legacyPattern(_legacyPattern) {}
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
|
||||
bool legacyPattern;
|
||||
};
|
||||
|
||||
|
||||
void CV_CharucoPoseEstimation::run(int) {
|
||||
int iter = 0;
|
||||
Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1);
|
||||
Size imgSize(750, 750);
|
||||
aruco::DetectorParameters params;
|
||||
params.minDistanceToBorder = 3;
|
||||
aruco::CharucoBoard board(Size(4, 4), 0.03f, 0.015f, aruco::getPredefinedDictionary(aruco::DICT_6X6_250));
|
||||
board.setLegacyPattern(legacyPattern);
|
||||
aruco::CharucoDetector detector(board, aruco::CharucoParameters(), params);
|
||||
|
||||
cameraMatrix.at<double>(0, 0) = cameraMatrix.at< double >(1, 1) = 1000;
|
||||
cameraMatrix.at<double>(0, 2) = imgSize.width / 2;
|
||||
cameraMatrix.at<double>(1, 2) = imgSize.height / 2;
|
||||
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
|
||||
// for different perspectives
|
||||
for(double distance : {0.2, 0.25}) {
|
||||
for(int yaw = -55; yaw <= 50; yaw += 25) {
|
||||
for(int pitch = -55; pitch <= 50; pitch += 25) {
|
||||
|
||||
int markerBorder = iter % 2 + 1;
|
||||
iter++;
|
||||
|
||||
// get synthetic image
|
||||
Mat rvec, tvec;
|
||||
Mat img = projectCharucoBoard(board, cameraMatrix, deg2rad(yaw), deg2rad(pitch),
|
||||
distance, imgSize, markerBorder, rvec, tvec);
|
||||
|
||||
// detect markers
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
params.markerBorderBits = markerBorder;
|
||||
detector.setDetectorParameters(params);
|
||||
|
||||
// detect markers and interpolate charuco corners
|
||||
vector<Point2f> charucoCorners;
|
||||
vector<int> charucoIds;
|
||||
|
||||
if(iter % 2 == 0) {
|
||||
detector.detectBoard(img, charucoCorners, charucoIds, corners, ids);
|
||||
} else {
|
||||
aruco::CharucoParameters charucoParameters;
|
||||
charucoParameters.cameraMatrix = cameraMatrix;
|
||||
charucoParameters.distCoeffs = distCoeffs;
|
||||
detector.setCharucoParameters(charucoParameters);
|
||||
detector.detectBoard(img, charucoCorners, charucoIds, corners, ids);
|
||||
}
|
||||
ASSERT_EQ(ids.size(), board.getIds().size());
|
||||
if(charucoIds.size() == 0) continue;
|
||||
|
||||
// estimate charuco pose
|
||||
getCharucoBoardPose(charucoCorners, charucoIds, board, cameraMatrix, distCoeffs, rvec, tvec);
|
||||
|
||||
|
||||
// check axes
|
||||
const float aruco_offset = (board.getSquareLength() - board.getMarkerLength()) / 2.f;
|
||||
Point2f offset;
|
||||
vector<Point2f> topLeft, bottomLeft;
|
||||
if(legacyPattern) { // white box in upper left corner for even row count chessboard patterns
|
||||
offset = Point2f(aruco_offset + board.getSquareLength(), aruco_offset);
|
||||
topLeft = getMarkerById(board.getIds()[1], corners, ids);
|
||||
bottomLeft = getMarkerById(board.getIds()[2], corners, ids);
|
||||
} else { // always a black box in the upper left corner
|
||||
offset = Point2f(aruco_offset, aruco_offset);
|
||||
topLeft = getMarkerById(board.getIds()[0], corners, ids);
|
||||
bottomLeft = getMarkerById(board.getIds()[2], corners, ids);
|
||||
}
|
||||
vector<Point2f> axes = getAxis(cameraMatrix, distCoeffs, rvec, tvec, board.getSquareLength(), offset);
|
||||
ASSERT_NEAR(topLeft[0].x, axes[1].x, 3.f);
|
||||
ASSERT_NEAR(topLeft[0].y, axes[1].y, 3.f);
|
||||
ASSERT_NEAR(bottomLeft[0].x, axes[2].x, 3.f);
|
||||
ASSERT_NEAR(bottomLeft[0].y, axes[2].y, 3.f);
|
||||
|
||||
// check estimate result
|
||||
vector< Point2f > projectedCharucoCorners;
|
||||
|
||||
projectPoints(board.getChessboardCorners(), rvec, tvec, cameraMatrix, distCoeffs,
|
||||
projectedCharucoCorners);
|
||||
|
||||
for(unsigned int i = 0; i < charucoIds.size(); i++) {
|
||||
|
||||
int currentId = charucoIds[i];
|
||||
|
||||
ASSERT_LT(currentId, (int)board.getChessboardCorners().size()) << "Invalid Charuco corner id";
|
||||
|
||||
double repError = cv::norm(charucoCorners[i] - projectedCharucoCorners[currentId]); // TODO cvtest
|
||||
|
||||
ASSERT_LE(repError, 5.) << "Charuco corner reprojection error too high";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check diamond detection
|
||||
*/
|
||||
class CV_CharucoDiamondDetection : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_CharucoDiamondDetection();
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
CV_CharucoDiamondDetection::CV_CharucoDiamondDetection() {}
|
||||
|
||||
|
||||
void CV_CharucoDiamondDetection::run(int) {
|
||||
|
||||
int iter = 0;
|
||||
Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1);
|
||||
Size imgSize(500, 500);
|
||||
aruco::DetectorParameters params;
|
||||
params.minDistanceToBorder = 0;
|
||||
float squareLength = 0.03f;
|
||||
float markerLength = 0.015f;
|
||||
aruco::CharucoBoard board(Size(3, 3), squareLength, markerLength,
|
||||
aruco::getPredefinedDictionary(aruco::DICT_6X6_250));
|
||||
aruco::CharucoDetector detector(board);
|
||||
|
||||
|
||||
cameraMatrix.at<double>(0, 0) = cameraMatrix.at< double >(1, 1) = 650;
|
||||
cameraMatrix.at<double>(0, 2) = imgSize.width / 2;
|
||||
cameraMatrix.at<double>(1, 2) = imgSize.height / 2;
|
||||
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
aruco::CharucoParameters charucoParameters;
|
||||
charucoParameters.cameraMatrix = cameraMatrix;
|
||||
charucoParameters.distCoeffs = distCoeffs;
|
||||
detector.setCharucoParameters(charucoParameters);
|
||||
|
||||
// for different perspectives
|
||||
for(double distance : {0.2, 0.22}) {
|
||||
for(int yaw = -50; yaw <= 50; yaw += 25) {
|
||||
for(int pitch = -50; pitch <= 50; pitch += 25) {
|
||||
|
||||
int markerBorder = iter % 2 + 1;
|
||||
vector<int> idsTmp;
|
||||
for(int i = 0; i < 4; i++)
|
||||
idsTmp.push_back(4 * iter + i);
|
||||
board = aruco::CharucoBoard(Size(3, 3), squareLength, markerLength,
|
||||
aruco::getPredefinedDictionary(aruco::DICT_6X6_250), idsTmp);
|
||||
detector.setBoard(board);
|
||||
iter++;
|
||||
|
||||
// get synthetic image
|
||||
Mat rvec, tvec;
|
||||
Mat img = projectCharucoBoard(board, cameraMatrix, deg2rad(yaw), deg2rad(pitch),
|
||||
distance, imgSize, markerBorder, rvec, tvec);
|
||||
|
||||
// detect markers
|
||||
vector<vector<Point2f>> corners;
|
||||
vector<int> ids;
|
||||
params.markerBorderBits = markerBorder;
|
||||
detector.setDetectorParameters(params);
|
||||
//detector.detectMarkers(img, corners, ids);
|
||||
|
||||
|
||||
// detect diamonds
|
||||
vector<vector<Point2f>> diamondCorners;
|
||||
vector<Vec4i> diamondIds;
|
||||
|
||||
detector.detectDiamonds(img, diamondCorners, diamondIds, corners, ids);
|
||||
|
||||
// check detect
|
||||
if(ids.size() != 4) {
|
||||
ts->printf(cvtest::TS::LOG, "Not enough markers for diamond detection");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
// check results
|
||||
if(diamondIds.size() != 1) {
|
||||
ts->printf(cvtest::TS::LOG, "Diamond not detected correctly");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < 4; i++) {
|
||||
if(diamondIds[0][i] != board.getIds()[i]) {
|
||||
ts->printf(cvtest::TS::LOG, "Incorrect diamond ids");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
vector< Point2f > projectedDiamondCorners;
|
||||
|
||||
// copy chessboardCorners
|
||||
vector<Point3f> copyChessboardCorners = board.getChessboardCorners();
|
||||
// move copyChessboardCorners points
|
||||
for (size_t i = 0; i < copyChessboardCorners.size(); i++)
|
||||
copyChessboardCorners[i] -= board.getRightBottomCorner() / 2.f;
|
||||
|
||||
projectPoints(copyChessboardCorners, rvec, tvec, cameraMatrix, distCoeffs,
|
||||
projectedDiamondCorners);
|
||||
|
||||
vector< Point2f > projectedDiamondCornersReorder(4);
|
||||
projectedDiamondCornersReorder[0] = projectedDiamondCorners[0];
|
||||
projectedDiamondCornersReorder[1] = projectedDiamondCorners[1];
|
||||
projectedDiamondCornersReorder[2] = projectedDiamondCorners[3];
|
||||
projectedDiamondCornersReorder[3] = projectedDiamondCorners[2];
|
||||
|
||||
|
||||
for(unsigned int i = 0; i < 4; i++) {
|
||||
|
||||
double repError = cv::norm(diamondCorners[0][i] - projectedDiamondCornersReorder[i]); // TODO cvtest
|
||||
|
||||
if(repError > 5.) {
|
||||
ts->printf(cvtest::TS::LOG, "Diamond corner reprojection error too high");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// estimate diamond pose
|
||||
vector< Vec3d > estimatedRvec, estimatedTvec;
|
||||
getMarkersPoses(diamondCorners, squareLength, cameraMatrix, distCoeffs, estimatedRvec,
|
||||
estimatedTvec, noArray(), false);
|
||||
|
||||
// check result
|
||||
vector< Point2f > projectedDiamondCornersPose;
|
||||
vector< Vec3f > diamondObjPoints(4);
|
||||
diamondObjPoints[0] = Vec3f(0.f, 0.f, 0);
|
||||
diamondObjPoints[1] = Vec3f(squareLength, 0.f, 0);
|
||||
diamondObjPoints[2] = Vec3f(squareLength, squareLength, 0);
|
||||
diamondObjPoints[3] = Vec3f(0.f, squareLength, 0);
|
||||
projectPoints(diamondObjPoints, estimatedRvec[0], estimatedTvec[0], cameraMatrix,
|
||||
distCoeffs, projectedDiamondCornersPose);
|
||||
|
||||
for(unsigned int i = 0; i < 4; i++) {
|
||||
double repError = cv::norm(projectedDiamondCornersReorder[i] - projectedDiamondCornersPose[i]); // TODO cvtest
|
||||
|
||||
if(repError > 5.) {
|
||||
ts->printf(cvtest::TS::LOG, "Charuco pose error too high");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check charuco board creation
|
||||
*/
|
||||
class CV_CharucoBoardCreation : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_CharucoBoardCreation();
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
CV_CharucoBoardCreation::CV_CharucoBoardCreation() {}
|
||||
|
||||
void CV_CharucoBoardCreation::run(int)
|
||||
{
|
||||
aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_5X5_250);
|
||||
int n = 6;
|
||||
|
||||
float markerSizeFactor = 0.5f;
|
||||
|
||||
for (float squareSize_mm = 5.0f; squareSize_mm < 35.0f; squareSize_mm += 0.1f)
|
||||
{
|
||||
aruco::CharucoBoard board_meters(Size(n, n), squareSize_mm*1e-3f,
|
||||
squareSize_mm * markerSizeFactor * 1e-3f, dictionary);
|
||||
|
||||
aruco::CharucoBoard board_millimeters(Size(n, n), squareSize_mm,
|
||||
squareSize_mm * markerSizeFactor, dictionary);
|
||||
|
||||
for (size_t i = 0; i < board_meters.getNearestMarkerIdx().size(); i++)
|
||||
{
|
||||
if (board_meters.getNearestMarkerIdx()[i].size() != board_millimeters.getNearestMarkerIdx()[i].size() ||
|
||||
board_meters.getNearestMarkerIdx()[i][0] != board_millimeters.getNearestMarkerIdx()[i][0])
|
||||
{
|
||||
ts->printf(cvtest::TS::LOG,
|
||||
cv::format("Charuco board topology is sensitive to scale with squareSize=%.1f\n",
|
||||
squareSize_mm).c_str());
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(CV_CharucoDetection, accuracy) {
|
||||
const bool legacyPattern = false;
|
||||
CV_CharucoDetection test(legacyPattern);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_CharucoDetection, accuracy_legacyPattern) {
|
||||
const bool legacyPattern = true;
|
||||
CV_CharucoDetection test(legacyPattern);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_CharucoPoseEstimation, accuracy) {
|
||||
const bool legacyPattern = false;
|
||||
CV_CharucoPoseEstimation test(legacyPattern);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_CharucoPoseEstimation, accuracy_legacyPattern) {
|
||||
const bool legacyPattern = true;
|
||||
CV_CharucoPoseEstimation test(legacyPattern);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_CharucoDiamondDetection, accuracy) {
|
||||
CV_CharucoDiamondDetection test;
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_CharucoBoardCreation, accuracy) {
|
||||
CV_CharucoBoardCreation test;
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(Charuco, testCharucoCornersCollinear_true)
|
||||
{
|
||||
int squaresX = 13;
|
||||
int squaresY = 28;
|
||||
float squareLength = 300;
|
||||
float markerLength = 150;
|
||||
int dictionaryId = 11;
|
||||
|
||||
aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::PredefinedDictionaryType(dictionaryId));
|
||||
|
||||
aruco::CharucoBoard charucoBoard(Size(squaresX, squaresY), squareLength, markerLength, dictionary);
|
||||
|
||||
// consistency with C++98
|
||||
const int arrLine[9] = {192, 204, 216, 228, 240, 252, 264, 276, 288};
|
||||
vector<int> charucoIdsAxisLine(9, 0);
|
||||
|
||||
for (int i = 0; i < 9; i++){
|
||||
charucoIdsAxisLine[i] = arrLine[i];
|
||||
}
|
||||
|
||||
const int arrDiag[7] = {198, 209, 220, 231, 242, 253, 264};
|
||||
|
||||
vector<int> charucoIdsDiagonalLine(7, 0);
|
||||
|
||||
for (int i = 0; i < 7; i++){
|
||||
charucoIdsDiagonalLine[i] = arrDiag[i];
|
||||
}
|
||||
|
||||
bool resultAxisLine = charucoBoard.checkCharucoCornersCollinear(charucoIdsAxisLine);
|
||||
EXPECT_TRUE(resultAxisLine);
|
||||
|
||||
bool resultDiagonalLine = charucoBoard.checkCharucoCornersCollinear(charucoIdsDiagonalLine);
|
||||
EXPECT_TRUE(resultDiagonalLine);
|
||||
}
|
||||
|
||||
TEST(Charuco, testCharucoCornersCollinear_false)
|
||||
{
|
||||
int squaresX = 13;
|
||||
int squaresY = 28;
|
||||
float squareLength = 300;
|
||||
float markerLength = 150;
|
||||
int dictionaryId = 11;
|
||||
|
||||
aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::PredefinedDictionaryType(dictionaryId));
|
||||
|
||||
aruco::CharucoBoard charucoBoard(Size(squaresX, squaresY), squareLength, markerLength, dictionary);
|
||||
|
||||
// consistency with C++98
|
||||
const int arr[63] = {192, 193, 194, 195, 196, 197, 198, 204, 205, 206, 207, 208,
|
||||
209, 210, 216, 217, 218, 219, 220, 221, 222, 228, 229, 230,
|
||||
231, 232, 233, 234, 240, 241, 242, 243, 244, 245, 246, 252,
|
||||
253, 254, 255, 256, 257, 258, 264, 265, 266, 267, 268, 269,
|
||||
270, 276, 277, 278, 279, 280, 281, 282, 288, 289, 290, 291,
|
||||
292, 293, 294};
|
||||
|
||||
vector<int> charucoIds(63, 0);
|
||||
for (int i = 0; i < 63; i++){
|
||||
charucoIds[i] = arr[i];
|
||||
}
|
||||
|
||||
bool result = charucoBoard.checkCharucoCornersCollinear(charucoIds);
|
||||
|
||||
EXPECT_FALSE(result);
|
||||
}
|
||||
|
||||
// test that ChArUco board detection is subpixel accurate
|
||||
TEST(Charuco, testBoardSubpixelCoords)
|
||||
{
|
||||
cv::Size res{500, 500};
|
||||
cv::Mat K = (cv::Mat_<double>(3,3) <<
|
||||
0.5*res.width, 0, 0.5*res.width,
|
||||
0, 0.5*res.height, 0.5*res.height,
|
||||
0, 0, 1);
|
||||
|
||||
// set expected_corners values
|
||||
// Note: Values adjusted by -0.5px after fixing the systematic offset bug in charuco_detector.cpp
|
||||
// The fix removes the incorrect +0.5 offset that was added after cornerSubPix
|
||||
cv::Mat expected_corners = (cv::Mat_<float>(9,2) <<
|
||||
199.5, 199.5,
|
||||
249.5, 199.5,
|
||||
299.5, 199.5,
|
||||
199.5, 249.5,
|
||||
249.5, 249.5,
|
||||
299.5, 249.5,
|
||||
199.5, 299.5,
|
||||
249.5, 299.5,
|
||||
299.5, 299.5
|
||||
);
|
||||
std::vector<int> shape={expected_corners.rows};
|
||||
expected_corners = expected_corners.reshape(2, shape);
|
||||
|
||||
cv::Mat gray;
|
||||
|
||||
aruco::Dictionary dict = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_APRILTAG_36h11);
|
||||
aruco::CharucoBoard board(Size(4, 4), 1.f, .8f, dict);
|
||||
|
||||
// generate ChArUco board
|
||||
board.generateImage(Size(res.width, res.height), gray, 150);
|
||||
cv::GaussianBlur(gray, gray, Size(5, 5), 1.0);
|
||||
|
||||
aruco::DetectorParameters params;
|
||||
params.cornerRefinementMethod = (int)cv::aruco::CORNER_REFINE_APRILTAG;
|
||||
|
||||
aruco::CharucoParameters charucoParameters;
|
||||
charucoParameters.cameraMatrix = K;
|
||||
aruco::CharucoDetector detector(board, charucoParameters);
|
||||
detector.setDetectorParameters(params);
|
||||
|
||||
std::vector<int> ids;
|
||||
std::vector<std::vector<cv::Point2f>> corners;
|
||||
cv::Mat c_ids, c_corners;
|
||||
|
||||
detector.detectBoard(gray, c_corners, c_ids, corners, ids);
|
||||
|
||||
ASSERT_EQ(ids.size(), size_t(8));
|
||||
ASSERT_EQ(c_corners.cols, expected_corners.cols);
|
||||
EXPECT_NEAR(0, cvtest::norm(expected_corners, c_corners, NORM_INF), 1e-1);
|
||||
}
|
||||
|
||||
TEST(Charuco, issue_14014)
|
||||
{
|
||||
string imgPath = cvtest::findDataFile("aruco/recover.png");
|
||||
Mat img = imread(imgPath);
|
||||
|
||||
aruco::DetectorParameters detectorParams;
|
||||
detectorParams.cornerRefinementMethod = (int)aruco::CORNER_REFINE_SUBPIX;
|
||||
detectorParams.cornerRefinementMinAccuracy = 0.01;
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_7X7_250), detectorParams);
|
||||
aruco::CharucoBoard board(Size(8, 5), 0.03455f, 0.02164f, detector.getDictionary());
|
||||
|
||||
vector<Mat> corners, rejectedPoints;
|
||||
vector<int> ids;
|
||||
|
||||
detector.detectMarkers(img, corners, ids, rejectedPoints);
|
||||
|
||||
ASSERT_EQ(corners.size(), 19ull);
|
||||
EXPECT_EQ(Size(4, 1), corners[0].size()); // check dimension of detected corners
|
||||
|
||||
size_t numRejPoints = rejectedPoints.size();
|
||||
ASSERT_EQ(rejectedPoints.size(), 24ull); // optional check to track regressions
|
||||
EXPECT_EQ(Size(4, 1), rejectedPoints[0].size()); // check dimension of detected corners
|
||||
|
||||
detector.refineDetectedMarkers(img, board, corners, ids, rejectedPoints);
|
||||
|
||||
ASSERT_EQ(corners.size(), 20ull);
|
||||
EXPECT_EQ(Size(4, 1), corners[0].size()); // check dimension of rejected corners after successfully refine
|
||||
|
||||
ASSERT_EQ(rejectedPoints.size() + 1, numRejPoints);
|
||||
EXPECT_EQ(Size(4, 1), rejectedPoints[0].size()); // check dimension of rejected corners after successfully refine
|
||||
}
|
||||
|
||||
|
||||
TEST(Charuco, testmatchImagePoints)
|
||||
{
|
||||
aruco::CharucoBoard board(Size(2, 3), 1.f, 0.5f, aruco::getPredefinedDictionary(aruco::DICT_4X4_50));
|
||||
auto chessboardPoints = board.getChessboardCorners();
|
||||
|
||||
vector<int> detectedIds;
|
||||
vector<Point2f> detectedCharucoCorners;
|
||||
for (const Point3f& point : chessboardPoints) {
|
||||
detectedIds.push_back((int)detectedCharucoCorners.size());
|
||||
detectedCharucoCorners.push_back({2.f*point.x, 2.f*point.y});
|
||||
}
|
||||
|
||||
vector<Point3f> objPoints;
|
||||
vector<Point2f> imagePoints;
|
||||
board.matchImagePoints(detectedCharucoCorners, detectedIds, objPoints, imagePoints);
|
||||
|
||||
ASSERT_EQ(detectedCharucoCorners.size(), objPoints.size());
|
||||
ASSERT_EQ(detectedCharucoCorners.size(), imagePoints.size());
|
||||
|
||||
for (size_t i = 0ull; i < detectedCharucoCorners.size(); i++) {
|
||||
EXPECT_EQ(detectedCharucoCorners[i], imagePoints[i]);
|
||||
EXPECT_EQ(chessboardPoints[i].x, objPoints[i].x);
|
||||
EXPECT_EQ(chessboardPoints[i].y, objPoints[i].y);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Charuco, detectDiamondsClearsOutputsWithLessThanFourMarkers)
|
||||
{
|
||||
aruco::CharucoBoard board(Size(3, 3), 1.f, 0.5f, aruco::getPredefinedDictionary(aruco::DICT_4X4_50));
|
||||
aruco::CharucoDetector detector(board);
|
||||
|
||||
vector<vector<Point2f>> markerCorners = {
|
||||
{Point2f(10.f, 10.f), Point2f(20.f, 10.f), Point2f(20.f, 20.f), Point2f(10.f, 20.f)},
|
||||
{Point2f(30.f, 10.f), Point2f(40.f, 10.f), Point2f(40.f, 20.f), Point2f(30.f, 20.f)},
|
||||
{Point2f(10.f, 30.f), Point2f(20.f, 30.f), Point2f(20.f, 40.f), Point2f(10.f, 40.f)}
|
||||
};
|
||||
vector<int> markerIds = {0, 1, 2};
|
||||
|
||||
vector<vector<Point2f>> diamondCorners = {{Point2f(1.f, 1.f), Point2f(2.f, 1.f), Point2f(2.f, 2.f), Point2f(1.f, 2.f)}};
|
||||
vector<Vec4i> diamondIds = {Vec4i(0, 1, 2, 3)};
|
||||
|
||||
detector.detectDiamonds(Mat(), diamondCorners, diamondIds, markerCorners, markerIds);
|
||||
|
||||
EXPECT_TRUE(diamondCorners.empty());
|
||||
EXPECT_TRUE(diamondIds.empty());
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam<int> CharucoDraw;
|
||||
INSTANTIATE_TEST_CASE_P(/**/, CharucoDraw, testing::Values(CV_8UC2, CV_8SC2, CV_16UC2, CV_16SC2, CV_32SC2, CV_32FC2, CV_64FC2));
|
||||
TEST_P(CharucoDraw, testDrawDetected) {
|
||||
vector<vector<Point>> detected_golds = {{Point(20, 20), Point(80, 20), Point(80, 80), Point2f(20, 80)}};
|
||||
Point center_gold = (detected_golds[0][0] + detected_golds[0][1] + detected_golds[0][2] + detected_golds[0][3]) / 4;
|
||||
int type = GetParam();
|
||||
vector<Mat> detected(detected_golds[0].size(), Mat(4, 1, type));
|
||||
// copy detected_golds to detected with any 2 channels type
|
||||
for (size_t i = 0ull; i < detected_golds[0].size(); i++) {
|
||||
detected[0].row((int)i) = Scalar(detected_golds[0][i].x, detected_golds[0][i].y);
|
||||
}
|
||||
vector<vector<Point>> contours;
|
||||
Point detectedCenter;
|
||||
Moments m;
|
||||
Mat img;
|
||||
|
||||
// check drawDetectedMarkers
|
||||
img = Mat::zeros(100, 100, CV_8UC1);
|
||||
ASSERT_NO_THROW(aruco::drawDetectedMarkers(img, detected, noArray(), Scalar(255, 255, 255)));
|
||||
// check that the marker borders are painted
|
||||
findContours(img, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
|
||||
ASSERT_EQ(contours.size(), 1ull);
|
||||
m = moments(contours[0]);
|
||||
detectedCenter = Point(cvRound(m.m10/m.m00), cvRound(m.m01/m.m00));
|
||||
ASSERT_EQ(detectedCenter, center_gold);
|
||||
|
||||
|
||||
// check drawDetectedCornersCharuco
|
||||
img = Mat::zeros(100, 100, CV_8UC1);
|
||||
ASSERT_NO_THROW(aruco::drawDetectedCornersCharuco(img, detected[0], noArray(), Scalar(255, 255, 255)));
|
||||
// check that the 4 charuco corners are painted
|
||||
findContours(img, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
|
||||
ASSERT_EQ(contours.size(), 4ull);
|
||||
for (size_t i = 0ull; i < 4ull; i++) {
|
||||
m = moments(contours[i]);
|
||||
detectedCenter = Point(cvRound(m.m10/m.m00), cvRound(m.m01/m.m00));
|
||||
// detectedCenter must be in detected_golds
|
||||
ASSERT_TRUE(find(detected_golds[0].begin(), detected_golds[0].end(), detectedCenter) != detected_golds[0].end());
|
||||
}
|
||||
|
||||
|
||||
// check drawDetectedDiamonds
|
||||
img = Mat::zeros(100, 100, CV_8UC1);
|
||||
ASSERT_NO_THROW(aruco::drawDetectedDiamonds(img, detected, noArray(), Scalar(255, 255, 255)));
|
||||
// check that the diamonds borders are painted
|
||||
findContours(img, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
|
||||
ASSERT_EQ(contours.size(), 1ull);
|
||||
m = moments(contours[0]);
|
||||
detectedCenter = Point(cvRound(m.m10/m.m00), cvRound(m.m01/m.m00));
|
||||
ASSERT_EQ(detectedCenter, center_gold);
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam<cv::Size> CharucoBoard;
|
||||
INSTANTIATE_TEST_CASE_P(/**/, CharucoBoard, testing::Values(Size(3, 2), Size(3, 2), Size(6, 2), Size(2, 6),
|
||||
Size(3, 4), Size(4, 3), Size(7, 3), Size(3, 7)));
|
||||
TEST_P(CharucoBoard, testWrongSizeDetection)
|
||||
{
|
||||
cv::Size boardSize = GetParam();
|
||||
ASSERT_FALSE(boardSize.width == boardSize.height);
|
||||
aruco::CharucoBoard board(boardSize, 1.f, 0.5f, aruco::getPredefinedDictionary(aruco::DICT_4X4_50));
|
||||
|
||||
Mat boardImage;
|
||||
board.generateImage(boardSize*40, boardImage);
|
||||
|
||||
swap(boardSize.width, boardSize.height);
|
||||
aruco::CharucoDetector detector(aruco::CharucoBoard(boardSize, 1.f, 0.5f, aruco::getPredefinedDictionary(aruco::DICT_4X4_50)));
|
||||
// try detect board with wrong size
|
||||
for(int i: {0, 1}) {
|
||||
vector<int> detectedCharucoIds, detectedArucoIds;
|
||||
vector<Point2f> detectedCharucoCorners;
|
||||
vector<vector<Point2f>> detectedArucoCorners;
|
||||
if (i == 0) {
|
||||
detector.detectBoard(boardImage, detectedCharucoCorners, detectedCharucoIds, detectedArucoCorners, detectedArucoIds);
|
||||
// aruco markers must be found
|
||||
ASSERT_EQ(detectedArucoIds.size(), board.getIds().size());
|
||||
ASSERT_EQ(detectedArucoCorners.size(), board.getIds().size());
|
||||
} else {
|
||||
detector.detectBoard(boardImage, detectedCharucoCorners, detectedCharucoIds);
|
||||
}
|
||||
|
||||
// charuco corners should not be found in board with wrong size
|
||||
ASSERT_TRUE(detectedCharucoCorners.empty());
|
||||
ASSERT_TRUE(detectedCharucoIds.empty());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef testing::TestWithParam<std::tuple<cv::Size, float, cv::Size, int>> CharucoBoardGenerate;
|
||||
INSTANTIATE_TEST_CASE_P(/**/, CharucoBoardGenerate, testing::Values(make_tuple(Size(7, 4), 13.f, Size(400, 300), 24),
|
||||
make_tuple(Size(12, 2), 13.f, Size(200, 150), 1),
|
||||
make_tuple(Size(12, 2), 13.1f, Size(400, 300), 1)));
|
||||
TEST_P(CharucoBoardGenerate, issue_24806)
|
||||
{
|
||||
aruco::Dictionary dict = aruco::getPredefinedDictionary(aruco::DICT_4X4_1000);
|
||||
auto params = GetParam();
|
||||
const Size boardSize = std::get<0>(params);
|
||||
const float squareLength = std::get<1>(params), markerLength = 10.f;
|
||||
Size imgSize = std::get<2>(params);
|
||||
const aruco::CharucoBoard board(boardSize, squareLength, markerLength, dict);
|
||||
const int marginSize = std::get<3>(params);
|
||||
Mat boardImg;
|
||||
|
||||
// generate chessboard image
|
||||
board.generateImage(imgSize, boardImg, marginSize);
|
||||
// This condition checks that the width of the image determines the dimensions of the chessboard in this test
|
||||
CV_Assert((float)(boardImg.cols) / (float)boardSize.width <=
|
||||
(float)(boardImg.rows) / (float)boardSize.height);
|
||||
|
||||
// prepare data for chessboard image test
|
||||
Mat noMarginsImg = boardImg(Range(marginSize, boardImg.rows - marginSize),
|
||||
Range(marginSize, boardImg.cols - marginSize));
|
||||
const float pixInSquare = (float)(noMarginsImg.cols) / (float)boardSize.width;
|
||||
|
||||
Size pixInChessboard(cvRound(pixInSquare*boardSize.width), cvRound(pixInSquare*boardSize.height));
|
||||
const Point startChessboard((noMarginsImg.cols - pixInChessboard.width) / 2,
|
||||
(noMarginsImg.rows - pixInChessboard.height) / 2);
|
||||
Mat chessboardZoneImg = noMarginsImg(Rect(startChessboard, pixInChessboard));
|
||||
|
||||
// B - black pixel, W - white pixel
|
||||
// chessboard corner 1:
|
||||
// B W
|
||||
// W B
|
||||
Mat goldCorner1 = (Mat_<uint8_t>(2, 2) <<
|
||||
0, 255,
|
||||
255, 0);
|
||||
// B - black pixel, W - white pixel
|
||||
// chessboard corner 2:
|
||||
// W B
|
||||
// B W
|
||||
Mat goldCorner2 = (Mat_<uint8_t>(2, 2) <<
|
||||
255, 0,
|
||||
0, 255);
|
||||
|
||||
// test chessboard corners in generated image
|
||||
for (const Point3f& p: board.getChessboardCorners()) {
|
||||
Point2f chessCorner(pixInSquare*(p.x/squareLength),
|
||||
pixInSquare*(p.y/squareLength));
|
||||
Mat winCorner = chessboardZoneImg(Rect(Point(cvRound(chessCorner.x) - 1, cvRound(chessCorner.y) - 1), Size(2, 2)));
|
||||
bool eq = (cv::countNonZero(goldCorner1 != winCorner) == 0) || (cv::countNonZero(goldCorner2 != winCorner) == 0);
|
||||
ASSERT_TRUE(eq);
|
||||
}
|
||||
|
||||
// marker size in pixels
|
||||
const float pixInMarker = markerLength/squareLength*pixInSquare;
|
||||
// the size of the marker margin in pixels
|
||||
const float pixInMarginMarker = 0.5f*(pixInSquare - pixInMarker);
|
||||
|
||||
// determine the zone where the aruco markers are located
|
||||
int endArucoX = cvRound(pixInSquare*(boardSize.width-1)+pixInMarginMarker+pixInMarker);
|
||||
int endArucoY = cvRound(pixInSquare*(boardSize.height-1)+pixInMarginMarker+pixInMarker);
|
||||
Mat arucoZone = chessboardZoneImg(Range(cvRound(pixInMarginMarker), endArucoY), Range(cvRound(pixInMarginMarker), endArucoX));
|
||||
|
||||
const auto& markerCorners = board.getObjPoints();
|
||||
float minX, maxX, minY, maxY;
|
||||
minX = maxX = markerCorners[0][0].x;
|
||||
minY = maxY = markerCorners[0][0].y;
|
||||
for (const auto& marker : markerCorners) {
|
||||
for (const Point3f& objCorner : marker) {
|
||||
minX = min(minX, objCorner.x);
|
||||
maxX = max(maxX, objCorner.x);
|
||||
minY = min(minY, objCorner.y);
|
||||
maxY = max(maxY, objCorner.y);
|
||||
}
|
||||
}
|
||||
|
||||
Point2f outCorners[3];
|
||||
for (const auto& marker : markerCorners) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
outCorners[i] = Point2f(marker[i].x, marker[i].y) - Point2f(minX, minY);
|
||||
outCorners[i].x = outCorners[i].x / (maxX - minX) * float(arucoZone.cols);
|
||||
outCorners[i].y = outCorners[i].y / (maxY - minY) * float(arucoZone.rows);
|
||||
}
|
||||
Size dst_sz(outCorners[2] - outCorners[0]); // assuming CCW order
|
||||
dst_sz.width = dst_sz.height = std::min(dst_sz.width, dst_sz.height);
|
||||
Rect borderRect = Rect(outCorners[0], dst_sz);
|
||||
|
||||
//The test checks the inner and outer borders of the Aruco markers.
|
||||
//In the inner border of Aruco marker, all pixels should be black.
|
||||
//In the outer border of Aruco marker, all pixels should be white.
|
||||
|
||||
Mat markerImg = arucoZone(borderRect);
|
||||
bool markerBorderIsBlack = borderPixelsHaveSameColor(markerImg, 0);
|
||||
ASSERT_EQ(markerBorderIsBlack, true);
|
||||
|
||||
Mat markerOuterBorder = markerImg;
|
||||
markerOuterBorder.adjustROI(1, 1, 1, 1);
|
||||
bool markerOuterBorderIsWhite = borderPixelsHaveSameColor(markerOuterBorder, 255);
|
||||
ASSERT_EQ(markerOuterBorderIsWhite, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary disabled in https://github.com/opencv/opencv/pull/24338
|
||||
// 5.x version produces conrnes with different shape than 4.x (32F_C2 instead of 2x 32FC1)
|
||||
TEST(Charuco, DISABLED_testSeveralBoardsWithCustomIds)
|
||||
{
|
||||
Size res{500, 500};
|
||||
Mat K = (Mat_<double>(3,3) <<
|
||||
0.5*res.width, 0, 0.5*res.width,
|
||||
0, 0.5*res.height, 0.5*res.height,
|
||||
0, 0, 1);
|
||||
|
||||
// Expected corner coordinates adjusted by -0.5px after fixing the systematic offset bug
|
||||
// The fix removes the incorrect +0.5 offset that was added after cornerSubPix
|
||||
Mat expected_corners = (Mat_<float>(9,2) <<
|
||||
199.5, 199.5,
|
||||
249.5, 199.5,
|
||||
299.5, 199.5,
|
||||
199.5, 249.5,
|
||||
249.5, 249.5,
|
||||
299.5, 249.5,
|
||||
199.5, 299.5,
|
||||
249.5, 299.5,
|
||||
299.5, 299.5
|
||||
);
|
||||
|
||||
|
||||
aruco::Dictionary dict = cv::aruco::getPredefinedDictionary(aruco::DICT_4X4_50);
|
||||
vector<int> ids1 = {0, 1, 33, 3, 4, 5, 6, 8}, ids2 = {7, 9, 44, 11, 12, 13, 14, 15};
|
||||
aruco::CharucoBoard board1(Size(4, 4), 1.f, .8f, dict, ids1), board2(Size(4, 4), 1.f, .8f, dict, ids2);
|
||||
|
||||
// generate ChArUco board
|
||||
Mat gray;
|
||||
{
|
||||
Mat gray1, gray2;
|
||||
board1.generateImage(Size(res.width, res.height), gray1, 150);
|
||||
board2.generateImage(Size(res.width, res.height), gray2, 150);
|
||||
hconcat(gray1, gray2, gray);
|
||||
}
|
||||
|
||||
aruco::CharucoParameters charucoParameters;
|
||||
charucoParameters.cameraMatrix = K;
|
||||
aruco::CharucoDetector detector1(board1, charucoParameters), detector2(board2, charucoParameters);
|
||||
|
||||
vector<int> ids;
|
||||
vector<Mat> corners;
|
||||
Mat c_ids1, c_ids2, c_corners1, c_corners2;
|
||||
|
||||
detector1.detectBoard(gray, c_corners1, c_ids1, corners, ids);
|
||||
detector2.detectBoard(gray, c_corners2, c_ids2, corners, ids);
|
||||
|
||||
ASSERT_EQ(ids.size(), size_t(16));
|
||||
// In 4.x detectBoard() returns the charuco corners in a 2D Mat with shape (N_corners, 1)
|
||||
// In 5.x, after PR #23473, detectBoard() returns the charuco corners in a 1D Mat with shape (1, N_corners)
|
||||
ASSERT_EQ(expected_corners.total(), c_corners1.total()*c_corners1.channels());
|
||||
EXPECT_NEAR(0., cvtest::norm(expected_corners.reshape(1, 1), c_corners1.reshape(1, 1), NORM_INF), 0.1);
|
||||
|
||||
ASSERT_EQ(expected_corners.total(), c_corners2.total()*c_corners2.channels());
|
||||
expected_corners.col(0) += 500;
|
||||
EXPECT_NEAR(0., cvtest::norm(expected_corners.reshape(1, 1), c_corners2.reshape(1, 1), NORM_INF), 0.1);
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,331 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_chessboardgenerator.hpp"
|
||||
|
||||
namespace cv {
|
||||
|
||||
ChessBoardGenerator::ChessBoardGenerator(const Size& _patternSize) : sensorWidth(32), sensorHeight(24),
|
||||
squareEdgePointsNum(200), min_cos(std::sqrt(3.f)*0.5f), cov(0.5),
|
||||
patternSize(_patternSize), rendererResolutionMultiplier(4), tvec(Mat::zeros(1, 3, CV_32F))
|
||||
{
|
||||
rvec.create(3, 1, CV_32F);
|
||||
Rodrigues(Mat::eye(3, 3, CV_32F), rvec);
|
||||
}
|
||||
|
||||
void ChessBoardGenerator::generateEdge(const Point3f& p1, const Point3f& p2, vector<Point3f>& out) const
|
||||
{
|
||||
Point3f step = (p2 - p1) * (1.f/squareEdgePointsNum);
|
||||
for(size_t n = 0; n < squareEdgePointsNum; ++n)
|
||||
out.push_back( p1 + step * (float)n);
|
||||
}
|
||||
|
||||
Size ChessBoardGenerator::cornersSize() const
|
||||
{
|
||||
return Size(patternSize.width-1, patternSize.height-1);
|
||||
}
|
||||
|
||||
struct Mult
|
||||
{
|
||||
float m;
|
||||
Mult(int mult) : m((float)mult) {}
|
||||
Point2f operator()(const Point2f& p)const { return p * m; }
|
||||
};
|
||||
|
||||
void ChessBoardGenerator::generateBasis(Point3f& pb1, Point3f& pb2) const
|
||||
{
|
||||
RNG& rng = theRNG();
|
||||
|
||||
Vec3f n;
|
||||
for(;;)
|
||||
{
|
||||
n[0] = rng.uniform(-1.f, 1.f);
|
||||
n[1] = rng.uniform(-1.f, 1.f);
|
||||
n[2] = rng.uniform(0.0f, 1.f);
|
||||
float len = (float)norm(n);
|
||||
if (len < 1e-3)
|
||||
continue;
|
||||
n[0]/=len;
|
||||
n[1]/=len;
|
||||
n[2]/=len;
|
||||
|
||||
if (n[2] > min_cos)
|
||||
break;
|
||||
}
|
||||
|
||||
Vec3f n_temp = n; n_temp[0] += 100;
|
||||
Vec3f b1 = n.cross(n_temp);
|
||||
Vec3f b2 = n.cross(b1);
|
||||
float len_b1 = (float)norm(b1);
|
||||
float len_b2 = (float)norm(b2);
|
||||
|
||||
pb1 = Point3f(b1[0]/len_b1, b1[1]/len_b1, b1[2]/len_b1);
|
||||
pb2 = Point3f(b2[0]/len_b1, b2[1]/len_b2, b2[2]/len_b2);
|
||||
}
|
||||
|
||||
|
||||
Mat ChessBoardGenerator::generateChessBoard(const Mat& bg, const Mat& camMat, const Mat& distCoeffs,
|
||||
const Point3f& zero, const Point3f& pb1, const Point3f& pb2,
|
||||
float sqWidth, float sqHeight, const vector<Point3f>& whole,
|
||||
vector<Point2f>& corners) const
|
||||
{
|
||||
vector< vector<Point> > squares_black;
|
||||
for(int i = 0; i < patternSize.width; ++i)
|
||||
for(int j = 0; j < patternSize.height; ++j)
|
||||
if ( (i % 2 == 0 && j % 2 == 0) || (i % 2 != 0 && j % 2 != 0) )
|
||||
{
|
||||
vector<Point3f> pts_square3d;
|
||||
vector<Point2f> pts_square2d;
|
||||
|
||||
Point3f p1 = zero + (i + 0) * sqWidth * pb1 + (j + 0) * sqHeight * pb2;
|
||||
Point3f p2 = zero + (i + 1) * sqWidth * pb1 + (j + 0) * sqHeight * pb2;
|
||||
Point3f p3 = zero + (i + 1) * sqWidth * pb1 + (j + 1) * sqHeight * pb2;
|
||||
Point3f p4 = zero + (i + 0) * sqWidth * pb1 + (j + 1) * sqHeight * pb2;
|
||||
generateEdge(p1, p2, pts_square3d);
|
||||
generateEdge(p2, p3, pts_square3d);
|
||||
generateEdge(p3, p4, pts_square3d);
|
||||
generateEdge(p4, p1, pts_square3d);
|
||||
|
||||
projectPoints(pts_square3d, rvec, tvec, camMat, distCoeffs, pts_square2d);
|
||||
squares_black.resize(squares_black.size() + 1);
|
||||
vector<Point2f> temp;
|
||||
approxPolyDP(pts_square2d, temp, 1.0, true);
|
||||
transform(temp.begin(), temp.end(), back_inserter(squares_black.back()), Mult(rendererResolutionMultiplier));
|
||||
}
|
||||
|
||||
/* calculate corners */
|
||||
corners3d.clear();
|
||||
for(int j = 0; j < patternSize.height - 1; ++j)
|
||||
for(int i = 0; i < patternSize.width - 1; ++i)
|
||||
corners3d.push_back(zero + (i + 1) * sqWidth * pb1 + (j + 1) * sqHeight * pb2);
|
||||
corners.clear();
|
||||
projectPoints(corners3d, rvec, tvec, camMat, distCoeffs, corners);
|
||||
|
||||
vector<Point3f> whole3d;
|
||||
vector<Point2f> whole2d;
|
||||
generateEdge(whole[0], whole[1], whole3d);
|
||||
generateEdge(whole[1], whole[2], whole3d);
|
||||
generateEdge(whole[2], whole[3], whole3d);
|
||||
generateEdge(whole[3], whole[0], whole3d);
|
||||
projectPoints(whole3d, rvec, tvec, camMat, distCoeffs, whole2d);
|
||||
vector<Point2f> temp_whole2d;
|
||||
approxPolyDP(whole2d, temp_whole2d, 1.0, true);
|
||||
|
||||
vector< vector<Point > > whole_contour(1);
|
||||
transform(temp_whole2d.begin(), temp_whole2d.end(),
|
||||
back_inserter(whole_contour.front()), Mult(rendererResolutionMultiplier));
|
||||
|
||||
Mat result;
|
||||
if (rendererResolutionMultiplier == 1)
|
||||
{
|
||||
result = bg.clone();
|
||||
drawContours(result, whole_contour, -1, Scalar::all(255), FILLED, LINE_AA);
|
||||
drawContours(result, squares_black, -1, Scalar::all(0), FILLED, LINE_AA);
|
||||
}
|
||||
else
|
||||
{
|
||||
Mat tmp;
|
||||
resize(bg, tmp, bg.size() * rendererResolutionMultiplier, 0, 0, INTER_LINEAR_EXACT);
|
||||
drawContours(tmp, whole_contour, -1, Scalar::all(255), FILLED, LINE_AA);
|
||||
drawContours(tmp, squares_black, -1, Scalar::all(0), FILLED, LINE_AA);
|
||||
resize(tmp, result, bg.size(), 0, 0, INTER_AREA);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Mat ChessBoardGenerator::operator ()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs, vector<Point2f>& corners) const
|
||||
{
|
||||
cov = std::min(cov, 0.8);
|
||||
double fovx, fovy, focalLen;
|
||||
Point2d principalPoint;
|
||||
double aspect;
|
||||
calibrationMatrixValues( camMat, bg.size(), sensorWidth, sensorHeight,
|
||||
fovx, fovy, focalLen, principalPoint, aspect);
|
||||
|
||||
RNG& rng = theRNG();
|
||||
|
||||
float d1 = static_cast<float>(rng.uniform(0.1, 10.0));
|
||||
float ah = static_cast<float>(rng.uniform(-fovx/2 * cov, fovx/2 * cov) * CV_PI / 180);
|
||||
float av = static_cast<float>(rng.uniform(-fovy/2 * cov, fovy/2 * cov) * CV_PI / 180);
|
||||
|
||||
Point3f p;
|
||||
p.z = std::cos(ah) * d1;
|
||||
p.x = std::sin(ah) * d1;
|
||||
p.y = p.z * std::tan(av);
|
||||
|
||||
Point3f pb1, pb2;
|
||||
generateBasis(pb1, pb2);
|
||||
|
||||
float cbHalfWidth = static_cast<float>(norm(p) * std::sin( std::min(fovx, fovy) * 0.5 * CV_PI / 180));
|
||||
float cbHalfHeight = cbHalfWidth * patternSize.height / patternSize.width;
|
||||
|
||||
float cbHalfWidthEx = cbHalfWidth * ( patternSize.width + 1) / patternSize.width;
|
||||
float cbHalfHeightEx = cbHalfHeight * (patternSize.height + 1) / patternSize.height;
|
||||
|
||||
vector<Point3f> pts3d(4);
|
||||
vector<Point2f> pts2d(4);
|
||||
for(;;)
|
||||
{
|
||||
pts3d[0] = p + pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
|
||||
pts3d[1] = p + pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
|
||||
pts3d[2] = p - pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
|
||||
pts3d[3] = p - pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
|
||||
|
||||
/* can remake with better perf */
|
||||
projectPoints(pts3d, rvec, tvec, camMat, distCoeffs, pts2d);
|
||||
|
||||
bool inrect1 = pts2d[0].x < bg.cols && pts2d[0].y < bg.rows && pts2d[0].x > 0 && pts2d[0].y > 0;
|
||||
bool inrect2 = pts2d[1].x < bg.cols && pts2d[1].y < bg.rows && pts2d[1].x > 0 && pts2d[1].y > 0;
|
||||
bool inrect3 = pts2d[2].x < bg.cols && pts2d[2].y < bg.rows && pts2d[2].x > 0 && pts2d[2].y > 0;
|
||||
bool inrect4 = pts2d[3].x < bg.cols && pts2d[3].y < bg.rows && pts2d[3].x > 0 && pts2d[3].y > 0;
|
||||
|
||||
if (inrect1 && inrect2 && inrect3 && inrect4)
|
||||
break;
|
||||
|
||||
cbHalfWidth*=0.8f;
|
||||
cbHalfHeight = cbHalfWidth * patternSize.height / patternSize.width;
|
||||
|
||||
cbHalfWidthEx = cbHalfWidth * ( patternSize.width + 1) / patternSize.width;
|
||||
cbHalfHeightEx = cbHalfHeight * (patternSize.height + 1) / patternSize.height;
|
||||
}
|
||||
|
||||
Point3f zero = p - pb1 * cbHalfWidth - cbHalfHeight * pb2;
|
||||
float sqWidth = 2 * cbHalfWidth/patternSize.width;
|
||||
float sqHeight = 2 * cbHalfHeight/patternSize.height;
|
||||
|
||||
return generateChessBoard(bg, camMat, distCoeffs, zero, pb1, pb2, sqWidth, sqHeight, pts3d, corners);
|
||||
}
|
||||
|
||||
|
||||
Mat ChessBoardGenerator::operator ()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs,
|
||||
const Size2f& squareSize, vector<Point2f>& corners) const
|
||||
{
|
||||
cov = std::min(cov, 0.8);
|
||||
double fovx, fovy, focalLen;
|
||||
Point2d principalPoint;
|
||||
double aspect;
|
||||
calibrationMatrixValues( camMat, bg.size(), sensorWidth, sensorHeight,
|
||||
fovx, fovy, focalLen, principalPoint, aspect);
|
||||
|
||||
RNG& rng = theRNG();
|
||||
|
||||
float d1 = static_cast<float>(rng.uniform(0.1, 10.0));
|
||||
float ah = static_cast<float>(rng.uniform(-fovx/2 * cov, fovx/2 * cov) * CV_PI / 180);
|
||||
float av = static_cast<float>(rng.uniform(-fovy/2 * cov, fovy/2 * cov) * CV_PI / 180);
|
||||
|
||||
Point3f p;
|
||||
p.z = std::cos(ah) * d1;
|
||||
p.x = std::sin(ah) * d1;
|
||||
p.y = p.z * std::tan(av);
|
||||
|
||||
Point3f pb1, pb2;
|
||||
generateBasis(pb1, pb2);
|
||||
|
||||
float cbHalfWidth = squareSize.width * patternSize.width * 0.5f;
|
||||
float cbHalfHeight = squareSize.height * patternSize.height * 0.5f;
|
||||
|
||||
float cbHalfWidthEx = cbHalfWidth * ( patternSize.width + 1) / patternSize.width;
|
||||
float cbHalfHeightEx = cbHalfHeight * (patternSize.height + 1) / patternSize.height;
|
||||
|
||||
vector<Point3f> pts3d(4);
|
||||
vector<Point2f> pts2d(4);
|
||||
for(;;)
|
||||
{
|
||||
pts3d[0] = p + pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
|
||||
pts3d[1] = p + pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
|
||||
pts3d[2] = p - pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
|
||||
pts3d[3] = p - pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
|
||||
|
||||
/* can remake with better perf */
|
||||
projectPoints(pts3d, rvec, tvec, camMat, distCoeffs, pts2d);
|
||||
|
||||
bool inrect1 = pts2d[0].x < bg.cols && pts2d[0].y < bg.rows && pts2d[0].x > 0 && pts2d[0].y > 0;
|
||||
bool inrect2 = pts2d[1].x < bg.cols && pts2d[1].y < bg.rows && pts2d[1].x > 0 && pts2d[1].y > 0;
|
||||
bool inrect3 = pts2d[2].x < bg.cols && pts2d[2].y < bg.rows && pts2d[2].x > 0 && pts2d[2].y > 0;
|
||||
bool inrect4 = pts2d[3].x < bg.cols && pts2d[3].y < bg.rows && pts2d[3].x > 0 && pts2d[3].y > 0;
|
||||
|
||||
if ( inrect1 && inrect2 && inrect3 && inrect4)
|
||||
break;
|
||||
|
||||
p.z *= 1.1f;
|
||||
}
|
||||
|
||||
Point3f zero = p - pb1 * cbHalfWidth - cbHalfHeight * pb2;
|
||||
|
||||
return generateChessBoard(bg, camMat, distCoeffs, zero, pb1, pb2,
|
||||
squareSize.width, squareSize.height, pts3d, corners);
|
||||
}
|
||||
|
||||
Mat ChessBoardGenerator::operator ()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs,
|
||||
const Size2f& squareSize, const Point3f& pos, vector<Point2f>& corners) const
|
||||
{
|
||||
cov = std::min(cov, 0.8);
|
||||
Point3f p = pos;
|
||||
Point3f pb1, pb2;
|
||||
generateBasis(pb1, pb2);
|
||||
|
||||
float cbHalfWidth = squareSize.width * patternSize.width * 0.5f;
|
||||
float cbHalfHeight = squareSize.height * patternSize.height * 0.5f;
|
||||
|
||||
float cbHalfWidthEx = cbHalfWidth * ( patternSize.width + 1) / patternSize.width;
|
||||
float cbHalfHeightEx = cbHalfHeight * (patternSize.height + 1) / patternSize.height;
|
||||
|
||||
vector<Point3f> pts3d(4);
|
||||
vector<Point2f> pts2d(4);
|
||||
|
||||
pts3d[0] = p + pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
|
||||
pts3d[1] = p + pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
|
||||
pts3d[2] = p - pb1 * cbHalfWidthEx - cbHalfHeightEx * pb2;
|
||||
pts3d[3] = p - pb1 * cbHalfWidthEx + cbHalfHeightEx * pb2;
|
||||
|
||||
/* can remake with better perf */
|
||||
projectPoints(pts3d, rvec, tvec, camMat, distCoeffs, pts2d);
|
||||
|
||||
Point3f zero = p - pb1 * cbHalfWidth - cbHalfHeight * pb2;
|
||||
|
||||
return generateChessBoard(bg, camMat, distCoeffs, zero, pb1, pb2,
|
||||
squareSize.width, squareSize.height, pts3d, corners);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,43 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
#ifndef CV_CHESSBOARDGENERATOR_H143KJTVYM389YTNHKFDHJ89NYVMO3VLMEJNTBGUEIYVCM203P
|
||||
#define CV_CHESSBOARDGENERATOR_H143KJTVYM389YTNHKFDHJ89NYVMO3VLMEJNTBGUEIYVCM203P
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
using std::vector;
|
||||
|
||||
class ChessBoardGenerator
|
||||
{
|
||||
public:
|
||||
double sensorWidth;
|
||||
double sensorHeight;
|
||||
size_t squareEdgePointsNum;
|
||||
double min_cos;
|
||||
mutable double cov;
|
||||
Size patternSize;
|
||||
int rendererResolutionMultiplier;
|
||||
|
||||
ChessBoardGenerator(const Size& patternSize = Size(8, 6));
|
||||
Mat operator()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs, std::vector<Point2f>& corners) const;
|
||||
Mat operator()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs, const Size2f& squareSize, std::vector<Point2f>& corners) const;
|
||||
Mat operator()(const Mat& bg, const Mat& camMat, const Mat& distCoeffs, const Size2f& squareSize, const Point3f& pos, std::vector<Point2f>& corners) const;
|
||||
Size cornersSize() const;
|
||||
|
||||
mutable std::vector<Point3f> corners3d;
|
||||
private:
|
||||
void generateEdge(const Point3f& p1, const Point3f& p2, std::vector<Point3f>& out) const;
|
||||
Mat generateChessBoard(const Mat& bg, const Mat& camMat, const Mat& distCoeffs,
|
||||
const Point3f& zero, const Point3f& pb1, const Point3f& pb2,
|
||||
float sqWidth, float sqHeight, const std::vector<Point3f>& whole, std::vector<Point2f>& corners) const;
|
||||
void generateBasis(Point3f& pb1, Point3f& pb2) const;
|
||||
|
||||
Mat rvec, tvec;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,870 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// Intel License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_chessboardgenerator.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
#define _L2_ERR
|
||||
|
||||
//#define DEBUG_CHESSBOARD
|
||||
|
||||
#ifdef DEBUG_CHESSBOARD
|
||||
void show_points( const Mat& gray, const Mat& expected, const vector<Point2f>& actual, bool was_found )
|
||||
{
|
||||
Mat rgb( gray.size(), CV_8U);
|
||||
merge(vector<Mat>(3, gray), rgb);
|
||||
|
||||
for(size_t i = 0; i < actual.size(); i++ )
|
||||
circle( rgb, actual[i], 5, Scalar(0, 0, 200), 1, LINE_AA);
|
||||
|
||||
if( !expected.empty() )
|
||||
{
|
||||
const Point2f* u_data = expected.ptr<Point2f>();
|
||||
size_t count = expected.cols * expected.rows;
|
||||
for(size_t i = 0; i < count; i++ )
|
||||
circle(rgb, u_data[i], 4, Scalar(0, 240, 0), 1, LINE_AA);
|
||||
}
|
||||
putText(rgb, was_found ? "FOUND !!!" : "NOT FOUND", Point(5, 20), FONT_HERSHEY_PLAIN, 1, Scalar(0, 240, 0));
|
||||
imshow( "test", rgb ); while ((uchar)waitKey(0) != 'q') {};
|
||||
}
|
||||
#else
|
||||
#define show_points(...)
|
||||
#endif
|
||||
|
||||
enum Pattern { CHESSBOARD, CHESSBOARD_SB, CHESSBOARD_PLAIN, CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID};
|
||||
|
||||
class CV_ChessboardDetectorTest : public cvtest::BaseTest
|
||||
{
|
||||
public:
|
||||
CV_ChessboardDetectorTest( Pattern pattern, int algorithmFlags = 0 );
|
||||
protected:
|
||||
void run(int);
|
||||
void run_batch(const string& filename);
|
||||
bool checkByGenerator();
|
||||
bool checkByGeneratorHighAccuracy();
|
||||
|
||||
// wraps calls based on the given pattern
|
||||
bool findChessboardCornersWrapper(InputArray image, Size patternSize, OutputArray corners,int flags);
|
||||
|
||||
Pattern pattern;
|
||||
int algorithmFlags;
|
||||
};
|
||||
|
||||
CV_ChessboardDetectorTest::CV_ChessboardDetectorTest( Pattern _pattern, int _algorithmFlags )
|
||||
{
|
||||
pattern = _pattern;
|
||||
algorithmFlags = _algorithmFlags;
|
||||
}
|
||||
|
||||
double calcError(const vector<Point2f>& v, const Mat& u)
|
||||
{
|
||||
int count_exp = u.cols * u.rows;
|
||||
const Point2f* u_data = u.ptr<Point2f>();
|
||||
|
||||
double err = std::numeric_limits<double>::max();
|
||||
for( int k = 0; k < 2; ++k )
|
||||
{
|
||||
double err1 = 0;
|
||||
for( int j = 0; j < count_exp; ++j )
|
||||
{
|
||||
int j1 = k == 0 ? j : count_exp - j - 1;
|
||||
double dx = fabs( v[j].x - u_data[j1].x );
|
||||
double dy = fabs( v[j].y - u_data[j1].y );
|
||||
|
||||
#if defined(_L2_ERR)
|
||||
err1 += dx*dx + dy*dy;
|
||||
#else
|
||||
dx = MAX( dx, dy );
|
||||
if( dx > err1 )
|
||||
err1 = dx;
|
||||
#endif //_L2_ERR
|
||||
//printf("dx = %f\n", dx);
|
||||
}
|
||||
//printf("\n");
|
||||
err = min(err, err1);
|
||||
}
|
||||
|
||||
#if defined(_L2_ERR)
|
||||
err = sqrt(err/count_exp);
|
||||
#endif //_L2_ERR
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
const double rough_success_error_level = 2.5;
|
||||
const double precise_success_error_level = 2;
|
||||
|
||||
|
||||
/* ///////////////////// chess_corner_test ///////////////////////// */
|
||||
void CV_ChessboardDetectorTest::run( int /*start_from */)
|
||||
{
|
||||
ts->set_failed_test_info( cvtest::TS::OK );
|
||||
|
||||
/*if (!checkByGenerator())
|
||||
return;*/
|
||||
switch( pattern )
|
||||
{
|
||||
case CHESSBOARD_SB:
|
||||
checkByGeneratorHighAccuracy(); // not supported by CHESSBOARD
|
||||
/* fallthrough */
|
||||
case CHESSBOARD_PLAIN:
|
||||
checkByGenerator();
|
||||
if (ts->get_err_code() != cvtest::TS::OK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
run_batch("negative_list.dat");
|
||||
if (ts->get_err_code() != cvtest::TS::OK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
run_batch("chessboard_list.dat");
|
||||
if (ts->get_err_code() != cvtest::TS::OK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case CHESSBOARD:
|
||||
checkByGenerator();
|
||||
if (ts->get_err_code() != cvtest::TS::OK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
run_batch("negative_list.dat");
|
||||
if (ts->get_err_code() != cvtest::TS::OK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
run_batch("chessboard_list.dat");
|
||||
if (ts->get_err_code() != cvtest::TS::OK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
run_batch("chessboard_list_subpixel.dat");
|
||||
break;
|
||||
case CIRCLES_GRID:
|
||||
run_batch("circles_list.dat");
|
||||
break;
|
||||
case ASYMMETRIC_CIRCLES_GRID:
|
||||
run_batch("acircles_list.dat");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CV_ChessboardDetectorTest::run_batch( const string& filename )
|
||||
{
|
||||
ts->printf(cvtest::TS::LOG, "\nRunning batch %s\n", filename.c_str());
|
||||
//#define WRITE_POINTS 1
|
||||
#ifndef WRITE_POINTS
|
||||
double max_rough_error = 0, max_precise_error = 0;
|
||||
#endif
|
||||
string folder;
|
||||
switch( pattern )
|
||||
{
|
||||
case CHESSBOARD:
|
||||
case CHESSBOARD_SB:
|
||||
case CHESSBOARD_PLAIN:
|
||||
folder = string(ts->get_data_path()) + "cameracalibration/";
|
||||
break;
|
||||
case CIRCLES_GRID:
|
||||
folder = string(ts->get_data_path()) + "cameracalibration/circles/";
|
||||
break;
|
||||
case ASYMMETRIC_CIRCLES_GRID:
|
||||
folder = string(ts->get_data_path()) + "cameracalibration/asymmetric_circles/";
|
||||
break;
|
||||
}
|
||||
|
||||
FileStorage fs( folder + filename, FileStorage::READ );
|
||||
FileNode board_list = fs["boards"];
|
||||
|
||||
if( !fs.isOpened() || board_list.empty() || !board_list.isSeq() || board_list.size() % 2 != 0 )
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "%s can not be read or is not valid\n", (folder + filename).c_str() );
|
||||
ts->printf( cvtest::TS::LOG, "fs.isOpened=%d, board_list.empty=%d, board_list.isSeq=%d,board_list.size()%2=%d\n",
|
||||
fs.isOpened(), (int)board_list.empty(), board_list.isSeq(), board_list.size()%2);
|
||||
ts->set_failed_test_info( cvtest::TS::FAIL_MISSING_TEST_DATA );
|
||||
return;
|
||||
}
|
||||
|
||||
int progress = 0;
|
||||
int max_idx = (int)board_list.size()/2;
|
||||
if(filename.compare("chessboard_list.dat") == 0 && pattern == CHESSBOARD_PLAIN)
|
||||
max_idx = 7;
|
||||
|
||||
double sum_error = 0.0;
|
||||
int count = 0;
|
||||
|
||||
for(int idx = 0; idx < max_idx; ++idx )
|
||||
{
|
||||
ts->update_context( this, idx, true );
|
||||
|
||||
/* read the image */
|
||||
String img_file = board_list[idx * 2];
|
||||
Mat gray = imread( folder + img_file, IMREAD_GRAYSCALE);
|
||||
|
||||
if( gray.empty() )
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "one of chessboard images can't be read: %s\n", img_file.c_str() );
|
||||
ts->set_failed_test_info( cvtest::TS::FAIL_MISSING_TEST_DATA );
|
||||
return;
|
||||
}
|
||||
|
||||
String _filename = folder + (String)board_list[idx * 2 + 1];
|
||||
bool doesContatinChessboard;
|
||||
float sharpness;
|
||||
Mat expected;
|
||||
{
|
||||
FileStorage fs1(_filename, FileStorage::READ);
|
||||
fs1["corners"] >> expected;
|
||||
fs1["isFound"] >> doesContatinChessboard;
|
||||
fs1["sharpness"] >> sharpness ;
|
||||
fs1.release();
|
||||
}
|
||||
size_t count_exp = static_cast<size_t>(expected.cols * expected.rows);
|
||||
Size pattern_size = expected.size();
|
||||
|
||||
Mat ori;
|
||||
vector<Point2f> v;
|
||||
int flags = 0;
|
||||
switch( pattern )
|
||||
{
|
||||
case CHESSBOARD:
|
||||
flags = CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE;
|
||||
break;
|
||||
case CHESSBOARD_PLAIN: {
|
||||
flags = CALIB_CB_PLAIN;
|
||||
ori = gray.clone();
|
||||
int min_size = cvRound((gray.cols * gray.rows * 0.05) / ((pattern_size.width+1) * (pattern_size.height+1)));
|
||||
if(min_size%2==0) min_size += 1;
|
||||
adaptiveThreshold(gray, gray, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, min_size, 0);
|
||||
dilate(gray, gray, Mat(), Point(-1, -1), 1);
|
||||
break;
|
||||
}
|
||||
case CIRCLES_GRID:
|
||||
case CHESSBOARD_SB:
|
||||
case ASYMMETRIC_CIRCLES_GRID:
|
||||
default:
|
||||
flags = 0;
|
||||
}
|
||||
|
||||
bool result = findChessboardCornersWrapper(gray, pattern_size,v,flags);
|
||||
|
||||
if(result && pattern == CHESSBOARD_PLAIN) {
|
||||
gray = ori;
|
||||
cornerSubPix(gray, v, Size(6,6), Size(-1,-1), TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 30, 0.1));
|
||||
}
|
||||
|
||||
if(result && sharpness && (pattern == CHESSBOARD_SB || pattern == CHESSBOARD || pattern == CHESSBOARD_PLAIN))
|
||||
{
|
||||
Scalar s= estimateChessboardSharpness(gray,pattern_size,v);
|
||||
if(fabs(s[0] - sharpness) > 0.1)
|
||||
{
|
||||
ts->printf(cvtest::TS::LOG, "chessboard image has a wrong sharpness in %s. Expected %f but measured %f\n", img_file.c_str(),sharpness,s[0]);
|
||||
ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
|
||||
show_points( gray, expected, v, result );
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(result ^ doesContatinChessboard || (doesContatinChessboard && v.size() != count_exp))
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "chessboard is detected incorrectly in %s\n", img_file.c_str() );
|
||||
ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
|
||||
show_points( gray, expected, v, result );
|
||||
return;
|
||||
}
|
||||
|
||||
if( result )
|
||||
{
|
||||
|
||||
#ifndef WRITE_POINTS
|
||||
double err = calcError(v, expected);
|
||||
max_rough_error = MAX( max_rough_error, err );
|
||||
#endif
|
||||
if( pattern == CHESSBOARD || pattern == CHESSBOARD_PLAIN )
|
||||
cornerSubPix( gray, v, Size(5, 5), Size(-1,-1), TermCriteria(TermCriteria::EPS|TermCriteria::MAX_ITER, 30, 0.1));
|
||||
//find4QuadCornerSubpix(gray, v, Size(5, 5));
|
||||
show_points( gray, expected, v, result );
|
||||
#ifndef WRITE_POINTS
|
||||
// printf("called find4QuadCornerSubpix\n");
|
||||
err = calcError(v, expected);
|
||||
sum_error += err;
|
||||
count++;
|
||||
if( err > precise_success_error_level )
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "Image %s: bad accuracy of adjusted corners %f\n", img_file.c_str(), err );
|
||||
ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );
|
||||
return;
|
||||
}
|
||||
ts->printf(cvtest::TS::LOG, "Error on %s is %f\n", img_file.c_str(), err);
|
||||
max_precise_error = MAX( max_precise_error, err );
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
show_points( gray, Mat(), v, result );
|
||||
}
|
||||
|
||||
#ifdef WRITE_POINTS
|
||||
Mat mat_v(pattern_size, CV_32FC2, (void*)&v[0]);
|
||||
FileStorage fs(_filename, FileStorage::WRITE);
|
||||
fs << "isFound" << result;
|
||||
fs << "corners" << mat_v;
|
||||
fs.release();
|
||||
#endif
|
||||
progress = update_progress( progress, idx, max_idx, 0 );
|
||||
}
|
||||
|
||||
if (count != 0)
|
||||
sum_error /= count;
|
||||
ts->printf(cvtest::TS::LOG, "Average error is %f (%d patterns have been found)\n", sum_error, count);
|
||||
}
|
||||
|
||||
double calcErrorMinError(const Size& cornSz, const vector<Point2f>& corners_found, const vector<Point2f>& corners_generated)
|
||||
{
|
||||
Mat m1(cornSz, CV_32FC2, (Point2f*)&corners_generated[0]);
|
||||
Mat m2; flip(m1, m2, 0);
|
||||
|
||||
Mat m3; flip(m1, m3, 1); m3 = m3.t(); flip(m3, m3, 1);
|
||||
|
||||
Mat m4 = m1.t(); flip(m4, m4, 1);
|
||||
|
||||
double min1 = min(calcError(corners_found, m1), calcError(corners_found, m2));
|
||||
double min2 = min(calcError(corners_found, m3), calcError(corners_found, m4));
|
||||
return min(min1, min2);
|
||||
}
|
||||
|
||||
bool validateData(const ChessBoardGenerator& cbg, const Size& imgSz,
|
||||
const vector<Point2f>& corners_generated)
|
||||
{
|
||||
Size cornersSize = cbg.cornersSize();
|
||||
Mat_<Point2f> mat(cornersSize.height, cornersSize.width, (Point2f*)&corners_generated[0]);
|
||||
|
||||
double minNeibDist = std::numeric_limits<double>::max();
|
||||
double tmp = 0;
|
||||
for(int i = 1; i < mat.rows - 2; ++i)
|
||||
for(int j = 1; j < mat.cols - 2; ++j)
|
||||
{
|
||||
const Point2f& cur = mat(i, j);
|
||||
|
||||
tmp = cv::norm(cur - mat(i + 1, j + 1)); // TODO cvtest
|
||||
if (tmp < minNeibDist)
|
||||
minNeibDist = tmp;
|
||||
|
||||
tmp = cv::norm(cur - mat(i - 1, j + 1)); // TODO cvtest
|
||||
if (tmp < minNeibDist)
|
||||
minNeibDist = tmp;
|
||||
|
||||
tmp = cv::norm(cur - mat(i + 1, j - 1)); // TODO cvtest
|
||||
if (tmp < minNeibDist)
|
||||
minNeibDist = tmp;
|
||||
|
||||
tmp = cv::norm(cur - mat(i - 1, j - 1)); // TODO cvtest
|
||||
if (tmp < minNeibDist)
|
||||
minNeibDist = tmp;
|
||||
}
|
||||
|
||||
const double threshold = 0.25;
|
||||
double cbsize = (max(cornersSize.width, cornersSize.height) + 1) * minNeibDist;
|
||||
int imgsize = min(imgSz.height, imgSz.width);
|
||||
return imgsize * threshold < cbsize;
|
||||
}
|
||||
|
||||
bool CV_ChessboardDetectorTest::findChessboardCornersWrapper(InputArray image, Size patternSize, OutputArray corners,int flags)
|
||||
{
|
||||
switch(pattern)
|
||||
{
|
||||
case CHESSBOARD:
|
||||
case CHESSBOARD_PLAIN:
|
||||
return findChessboardCorners(image,patternSize,corners,flags);
|
||||
case CHESSBOARD_SB:
|
||||
// check default settings until flags have been specified
|
||||
return findChessboardCornersSB(image,patternSize,corners,0);
|
||||
case ASYMMETRIC_CIRCLES_GRID:
|
||||
flags |= CALIB_CB_ASYMMETRIC_GRID | algorithmFlags;
|
||||
return findCirclesGrid(image, patternSize,corners,flags);
|
||||
case CIRCLES_GRID:
|
||||
flags |= CALIB_CB_SYMMETRIC_GRID;
|
||||
return findCirclesGrid(image, patternSize,corners,flags);
|
||||
default:
|
||||
ts->printf( cvtest::TS::LOG, "Internal Error: unsupported chessboard pattern" );
|
||||
ts->set_failed_test_info( cvtest::TS::FAIL_GENERIC);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CV_ChessboardDetectorTest::checkByGenerator()
|
||||
{
|
||||
bool res = true;
|
||||
|
||||
//theRNG() = 0x58e6e895b9913160;
|
||||
//cv::DefaultRngAuto dra;
|
||||
//theRNG() = *ts->get_rng();
|
||||
|
||||
Mat bg(Size(800, 600), CV_8UC3, Scalar::all(255));
|
||||
randu(bg, Scalar::all(0), Scalar::all(255));
|
||||
GaussianBlur(bg, bg, Size(5, 5), 0.0);
|
||||
|
||||
Mat_<float> camMat(3, 3);
|
||||
camMat << 300.f, 0.f, bg.cols/2.f, 0, 300.f, bg.rows/2.f, 0.f, 0.f, 1.f;
|
||||
|
||||
Mat_<float> distCoeffs(1, 5);
|
||||
distCoeffs << 1.2f, 0.2f, 0.f, 0.f, 0.f;
|
||||
|
||||
const Size sizes[] = { Size(6, 6), Size(8, 6), Size(11, 12), Size(5, 4) };
|
||||
const size_t sizes_num = sizeof(sizes)/sizeof(sizes[0]);
|
||||
const int test_num = 16;
|
||||
int progress = 0;
|
||||
for(int i = 0; i < test_num; ++i)
|
||||
{
|
||||
SCOPED_TRACE(cv::format("test_num=%d", test_num));
|
||||
|
||||
progress = update_progress( progress, i, test_num, 0 );
|
||||
ChessBoardGenerator cbg(sizes[i % sizes_num]);
|
||||
|
||||
vector<Point2f> corners_generated;
|
||||
|
||||
Mat cb = cbg(bg, camMat, distCoeffs, corners_generated);
|
||||
|
||||
if(!validateData(cbg, cb.size(), corners_generated))
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "Chess board skipped - too small" );
|
||||
continue;
|
||||
}
|
||||
|
||||
/*cb = cb * 0.8 + Scalar::all(30);
|
||||
GaussianBlur(cb, cb, Size(3, 3), 0.8); */
|
||||
//cv::addWeighted(cb, 0.8, bg, 0.2, 20, cb);
|
||||
//cv::namedWindow("CB"); cv::imshow("CB", cb); cv::waitKey();
|
||||
|
||||
vector<Point2f> corners_found;
|
||||
int flags = i % 8; // need to check branches for all flags
|
||||
bool found = findChessboardCornersWrapper(cb, cbg.cornersSize(), corners_found, flags);
|
||||
if (!found)
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "Chess board corners not found\n" );
|
||||
ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );
|
||||
res = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
double err = calcErrorMinError(cbg.cornersSize(), corners_found, corners_generated);
|
||||
EXPECT_LE(err, rough_success_error_level) << "bad accuracy of corner guesses";
|
||||
#if 0
|
||||
if (err >= rough_success_error_level)
|
||||
{
|
||||
imshow("cb", cb);
|
||||
Mat cb_corners = cb.clone();
|
||||
cv::drawChessboardCorners(cb_corners, cbg.cornersSize(), Mat(corners_found), found);
|
||||
imshow("corners", cb_corners);
|
||||
waitKey(0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ***** negative ***** */
|
||||
{
|
||||
vector<Point2f> corners_found;
|
||||
bool found = findChessboardCornersWrapper(bg, Size(8, 7), corners_found,0);
|
||||
if (found)
|
||||
res = false;
|
||||
|
||||
ChessBoardGenerator cbg(Size(8, 7));
|
||||
|
||||
vector<Point2f> cg;
|
||||
Mat cb = cbg(bg, camMat, distCoeffs, cg);
|
||||
|
||||
found = findChessboardCornersWrapper(cb, Size(3, 4), corners_found,0);
|
||||
if (found)
|
||||
res = false;
|
||||
|
||||
Point2f c = std::accumulate(cg.begin(), cg.end(), Point2f(), std::plus<Point2f>()) * (1.f/cg.size());
|
||||
|
||||
Mat_<double> aff(2, 3);
|
||||
aff << 1.0, 0.0, -(double)c.x, 0.0, 1.0, 0.0;
|
||||
Mat sh;
|
||||
warpAffine(cb, sh, aff, cb.size());
|
||||
|
||||
found = findChessboardCornersWrapper(sh, cbg.cornersSize(), corners_found,0);
|
||||
if (found)
|
||||
res = false;
|
||||
|
||||
vector< vector<Point> > cnts(1);
|
||||
vector<Point>& cnt = cnts[0];
|
||||
cnt.push_back(cg[ 0]); cnt.push_back(cg[0+2]);
|
||||
cnt.push_back(cg[7+0]); cnt.push_back(cg[7+2]);
|
||||
cv::drawContours(cb, cnts, -1, Scalar::all(128), FILLED);
|
||||
|
||||
found = findChessboardCornersWrapper(cb, cbg.cornersSize(), corners_found,0);
|
||||
if (found)
|
||||
res = false;
|
||||
|
||||
cv::drawChessboardCorners(cb, cbg.cornersSize(), Mat(corners_found), found);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// generates artificial checkerboards using warpPerspective which supports
|
||||
// subpixel rendering. The transformation is found by transferring corners to
|
||||
// the camera image using a virtual plane.
|
||||
bool CV_ChessboardDetectorTest::checkByGeneratorHighAccuracy()
|
||||
{
|
||||
// draw 2D pattern
|
||||
cv::Size pattern_size(6,5);
|
||||
int cell_size = 80;
|
||||
bool bwhite = true;
|
||||
cv::Mat image = cv::Mat::ones((pattern_size.height+3)*cell_size,(pattern_size.width+3)*cell_size,CV_8UC1)*255;
|
||||
cv::Mat pimage = image(Rect(cell_size,cell_size,(pattern_size.width+1)*cell_size,(pattern_size.height+1)*cell_size));
|
||||
pimage = 0;
|
||||
for(int row=0;row<=pattern_size.height;++row)
|
||||
{
|
||||
int y = int(cell_size*row+0.5F);
|
||||
bool bwhite2 = bwhite;
|
||||
for(int col=0;col<=pattern_size.width;++col)
|
||||
{
|
||||
if(bwhite2)
|
||||
{
|
||||
int x = int(cell_size*col+0.5F);
|
||||
pimage(cv::Rect(x,y,cell_size,cell_size)) = 255;
|
||||
}
|
||||
bwhite2 = !bwhite2;
|
||||
|
||||
}
|
||||
bwhite = !bwhite;
|
||||
}
|
||||
|
||||
// generate 2d points
|
||||
std::vector<Point2f> pts1,pts2,pts1_all,pts2_all;
|
||||
std::vector<Point3f> pts3d;
|
||||
for(int row=0;row<pattern_size.height;++row)
|
||||
{
|
||||
int y = int(cell_size*(row+2));
|
||||
for(int col=0;col<pattern_size.width;++col)
|
||||
{
|
||||
int x = int(cell_size*(col+2));
|
||||
pts1_all.push_back(cv::Point2f(x-0.5F,y-0.5F));
|
||||
}
|
||||
}
|
||||
|
||||
// back project chessboard corners to a virtual plane
|
||||
double fx = 500;
|
||||
double fy = 500;
|
||||
cv::Point2f center(250,250);
|
||||
double fxi = 1.0/fx;
|
||||
double fyi = 1.0/fy;
|
||||
for(auto &&pt : pts1_all)
|
||||
{
|
||||
// calc camera ray
|
||||
cv::Vec3f ray(float((pt.x-center.x)*fxi),float((pt.y-center.y)*fyi),1.0F);
|
||||
ray /= cv::norm(ray);
|
||||
|
||||
// intersect ray with virtual plane
|
||||
cv::Scalar plane(0,0,1,-1);
|
||||
cv::Vec3f n(float(plane(0)),float(plane(1)),float(plane(2)));
|
||||
cv::Point3f p0(0,0,0);
|
||||
|
||||
cv::Point3f l0(0,0,0); // camera center in world coordinates
|
||||
p0.z = float(-plane(3)/plane(2));
|
||||
double val1 = ray.dot(n);
|
||||
if(val1 == 0)
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "Internal Error: ray and plane are parallel" );
|
||||
ts->set_failed_test_info( cvtest::TS::FAIL_GENERIC);
|
||||
return false;
|
||||
}
|
||||
pts3d.push_back(Point3f(ray/val1*cv::Vec3f((p0-l0)).dot(n))+l0);
|
||||
}
|
||||
|
||||
// generate multiple rotations
|
||||
for(int i=15;i<90;i=i+15)
|
||||
{
|
||||
// project 3d points to new camera
|
||||
Vec3f rvec(0.0F,0.05F,float(float(i)/180.0*CV_PI));
|
||||
Vec3f tvec(0,0,0);
|
||||
cv::Mat k = (cv::Mat_<double>(3,3) << fx/2,0,center.x*2, 0,fy/2,center.y, 0,0,1);
|
||||
cv::projectPoints(pts3d,rvec,tvec,k,cv::Mat(),pts2_all);
|
||||
|
||||
// get perspective transform using four correspondences and wrap original image
|
||||
pts1.clear();
|
||||
pts2.clear();
|
||||
pts1.push_back(pts1_all[0]);
|
||||
pts1.push_back(pts1_all[pattern_size.width-1]);
|
||||
pts1.push_back(pts1_all[pattern_size.width*pattern_size.height-1]);
|
||||
pts1.push_back(pts1_all[pattern_size.width*(pattern_size.height-1)]);
|
||||
pts2.push_back(pts2_all[0]);
|
||||
pts2.push_back(pts2_all[pattern_size.width-1]);
|
||||
pts2.push_back(pts2_all[pattern_size.width*pattern_size.height-1]);
|
||||
pts2.push_back(pts2_all[pattern_size.width*(pattern_size.height-1)]);
|
||||
Mat m2 = getPerspectiveTransform(pts1,pts2);
|
||||
Mat out(image.size(),image.type());
|
||||
warpPerspective(image,out,m2,out.size());
|
||||
|
||||
// find checkerboard
|
||||
vector<Point2f> corners_found;
|
||||
bool found = findChessboardCornersWrapper(out,pattern_size,corners_found,0);
|
||||
if (!found)
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "Chess board corners not found\n" );
|
||||
ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );
|
||||
return false;
|
||||
}
|
||||
double err = calcErrorMinError(pattern_size,corners_found,pts2_all);
|
||||
if(err > 0.08)
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "bad accuracy of corner guesses" );
|
||||
ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );
|
||||
return false;
|
||||
}
|
||||
//cv::cvtColor(out,out,cv::COLOR_GRAY2BGR);
|
||||
//cv::drawChessboardCorners(out,pattern_size,corners_found,true);
|
||||
//cv::imshow("img",out);
|
||||
//cv::waitKey(-1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST(Calib3d_ChessboardDetector, accuracy) { CV_ChessboardDetectorTest test( CHESSBOARD ); test.safe_run(); }
|
||||
TEST(Calib3d_ChessboardDetector2, accuracy) { CV_ChessboardDetectorTest test( CHESSBOARD_SB ); test.safe_run(); }
|
||||
TEST(Calib3d_ChessboardDetector3, accuracy) { CV_ChessboardDetectorTest test( CHESSBOARD_PLAIN ); test.safe_run(); }
|
||||
TEST(Calib3d_CirclesPatternDetector, accuracy) { CV_ChessboardDetectorTest test( CIRCLES_GRID ); test.safe_run(); }
|
||||
TEST(Calib3d_AsymmetricCirclesPatternDetector, accuracy) { CV_ChessboardDetectorTest test( ASYMMETRIC_CIRCLES_GRID ); test.safe_run(); }
|
||||
#ifdef HAVE_OPENCV_FLANN
|
||||
TEST(Calib3d_AsymmetricCirclesPatternDetectorWithClustering, accuracy) { CV_ChessboardDetectorTest test( ASYMMETRIC_CIRCLES_GRID, CALIB_CB_CLUSTERING ); test.safe_run(); }
|
||||
#endif
|
||||
|
||||
TEST(Calib3d_ChessboardWithMarkers, regression_25806_white)
|
||||
{
|
||||
const cv::String dataDir = string(TS::ptr()->get_data_path()) + "cameracalibration/";
|
||||
const cv::Mat image = cv::imread(dataDir + "checkerboard_marker_white.png");
|
||||
ASSERT_FALSE(image.empty());
|
||||
|
||||
std::vector<Point2f> corners;
|
||||
const bool success = cv::findChessboardCornersSB(image, Size(9, 14), corners, CALIB_CB_MARKER);
|
||||
ASSERT_TRUE(success);
|
||||
}
|
||||
|
||||
TEST(Calib3d_ChessboardWithMarkers, regression_25806_black)
|
||||
{
|
||||
const cv::String dataDir = string(TS::ptr()->get_data_path()) + "cameracalibration/";
|
||||
const cv::Mat image = cv::imread(dataDir + "checkerboard_marker_black.png");
|
||||
ASSERT_FALSE(image.empty());
|
||||
|
||||
std::vector<Point2f> corners;
|
||||
const bool success = cv::findChessboardCornersSB(image, Size(9, 14), corners, CALIB_CB_MARKER);
|
||||
ASSERT_TRUE(success);
|
||||
}
|
||||
|
||||
TEST(Calib3d_CirclesPatternDetectorWithClustering, accuracy)
|
||||
{
|
||||
cv::String dataDir = string(TS::ptr()->get_data_path()) + "cameracalibration/circles/";
|
||||
|
||||
cv::Mat expected;
|
||||
FileStorage fs(dataDir + "circles_corners15.dat", FileStorage::READ);
|
||||
fs["corners"] >> expected;
|
||||
fs.release();
|
||||
|
||||
cv::Mat image = cv::imread(dataDir + "circles15.png");
|
||||
ASSERT_FALSE(image.empty());
|
||||
|
||||
std::vector<Point2f> centers;
|
||||
cv::findCirclesGrid(image, Size(10, 8), centers, CALIB_CB_SYMMETRIC_GRID | CALIB_CB_CLUSTERING);
|
||||
ASSERT_EQ(expected.total(), centers.size());
|
||||
|
||||
double error = calcError(centers, expected);
|
||||
ASSERT_LE(error, precise_success_error_level);
|
||||
}
|
||||
|
||||
TEST(Calib3d_AsymmetricCirclesPatternDetector, regression_18713)
|
||||
{
|
||||
float pts_[][2] = {
|
||||
{ 166.5, 107 }, { 146, 236 }, { 147, 92 }, { 184, 162 }, { 150, 185.5 },
|
||||
{ 215, 105 }, { 270.5, 186 }, { 159, 142 }, { 6, 205.5 }, { 32, 148.5 },
|
||||
{ 126, 163.5 }, { 181, 208.5 }, { 240.5, 62 }, { 84.5, 76.5 }, { 190, 120.5 },
|
||||
{ 10, 189 }, { 266, 104 }, { 307.5, 207.5 }, { 97, 184 }, { 116.5, 210 },
|
||||
{ 114, 139 }, { 84.5, 233 }, { 269.5, 139 }, { 136, 126.5 }, { 120, 107.5 },
|
||||
{ 129.5, 65.5 }, { 212.5, 140.5 }, { 204.5, 60.5 }, { 207.5, 241 }, { 61.5, 94.5 },
|
||||
{ 186.5, 61.5 }, { 220, 63 }, { 239, 120.5 }, { 212, 186 }, { 284, 87.5 },
|
||||
{ 62, 114.5 }, { 283, 61.5 }, { 238.5, 88.5 }, { 243, 159 }, { 245, 208 },
|
||||
{ 298.5, 158.5 }, { 57, 129 }, { 156.5, 63.5 }, { 192, 90.5 }, { 281, 235.5 },
|
||||
{ 172, 62.5 }, { 291.5, 119.5 }, { 90, 127 }, { 68.5, 166.5 }, { 108.5, 83.5 },
|
||||
{ 22, 176 }
|
||||
};
|
||||
Mat candidates(51, 1, CV_32FC2, (void*)pts_);
|
||||
Size patternSize(4, 9);
|
||||
|
||||
std::vector< Point2f > result;
|
||||
bool res = false;
|
||||
|
||||
// issue reports about hangs
|
||||
EXPECT_NO_THROW(res = findCirclesGrid(candidates, patternSize, result, CALIB_CB_ASYMMETRIC_GRID, Ptr<FeatureDetector>()/*blobDetector=NULL*/));
|
||||
EXPECT_FALSE(res);
|
||||
|
||||
if (cvtest::debugLevel > 0)
|
||||
{
|
||||
std::cout << Mat(candidates) << std::endl;
|
||||
std::cout << Mat(result) << std::endl;
|
||||
Mat img(Size(400, 300), CV_8UC3, Scalar::all(0));
|
||||
|
||||
std::vector< Point2f > centers;
|
||||
candidates.copyTo(centers);
|
||||
|
||||
for (size_t i = 0; i < centers.size(); i++)
|
||||
{
|
||||
const Point2f& pt = centers[i];
|
||||
//printf("{ %g, %g }, \n", pt.x, pt.y);
|
||||
circle(img, pt, 5, Scalar(0, 255, 0));
|
||||
}
|
||||
for (size_t i = 0; i < result.size(); i++)
|
||||
{
|
||||
const Point2f& pt = result[i];
|
||||
circle(img, pt, 10, Scalar(0, 0, 255));
|
||||
}
|
||||
imwrite("test_18713.png", img);
|
||||
if (cvtest::debugLevel >= 10)
|
||||
{
|
||||
imshow("result", img);
|
||||
waitKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Calib3d_AsymmetricCirclesPatternDetector, regression_19498)
|
||||
{
|
||||
float pts_[121][2] = {
|
||||
{ 84.7462f, 404.504f }, { 49.1586f, 404.092f }, { 12.3362f, 403.434f }, { 102.542f, 386.214f }, { 67.6042f, 385.475f },
|
||||
{ 31.4982f, 384.569f }, { 141.231f, 377.856f }, { 332.834f, 370.745f }, { 85.7663f, 367.261f }, { 50.346f, 366.051f },
|
||||
{ 13.7726f, 364.663f }, { 371.746f, 362.011f }, { 68.8543f, 347.883f }, { 32.9334f, 346.263f }, { 331.926f, 343.291f },
|
||||
{ 351.535f, 338.112f }, { 51.7951f, 328.247f }, { 15.4613f, 326.095f }, { 311.719f, 319.578f }, { 330.947f, 313.708f },
|
||||
{ 256.706f, 307.584f }, { 34.6834f, 308.167f }, { 291.085f, 295.429f }, { 17.4316f, 287.824f }, { 252.928f, 277.92f },
|
||||
{ 270.19f, 270.93f }, { 288.473f, 263.484f }, { 216.401f, 260.94f }, { 232.195f, 253.656f }, { 266.757f, 237.708f },
|
||||
{ 211.323f, 229.005f }, { 227.592f, 220.498f }, { 154.749f, 188.52f }, { 222.52f, 184.906f }, { 133.85f, 163.968f },
|
||||
{ 200.024f, 158.05f }, { 147.485f, 153.643f }, { 161.967f, 142.633f }, { 177.396f, 131.059f }, { 125.909f, 128.116f },
|
||||
{ 139.817f, 116.333f }, { 91.8639f, 114.454f }, { 104.343f, 102.542f }, { 117.635f, 89.9116f }, { 70.9465f, 89.4619f },
|
||||
{ 82.8524f, 76.7862f }, { 131.738f, 76.4741f }, { 95.5012f, 63.3351f }, { 109.034f, 49.0424f }, { 314.886f, 374.711f },
|
||||
{ 351.735f, 366.489f }, { 279.113f, 357.05f }, { 313.371f, 348.131f }, { 260.123f, 335.271f }, { 276.346f, 330.325f },
|
||||
{ 293.588f, 325.133f }, { 240.86f, 313.143f }, { 273.436f, 301.667f }, { 206.762f, 296.574f }, { 309.877f, 288.796f },
|
||||
{ 187.46f, 274.319f }, { 201.521f, 267.804f }, { 248.973f, 245.918f }, { 181.644f, 244.655f }, { 196.025f, 237.045f },
|
||||
{ 148.41f, 229.131f }, { 161.604f, 221.215f }, { 175.455f, 212.873f }, { 244.748f, 211.459f }, { 128.661f, 206.109f },
|
||||
{ 190.217f, 204.108f }, { 141.346f, 197.568f }, { 205.876f, 194.781f }, { 168.937f, 178.948f }, { 121.006f, 173.714f },
|
||||
{ 183.998f, 168.806f }, { 88.9095f, 159.731f }, { 100.559f, 149.867f }, { 58.553f, 146.47f }, { 112.849f, 139.302f },
|
||||
{ 80.0968f, 125.74f }, { 39.24f, 123.671f }, { 154.582f, 103.85f }, { 59.7699f, 101.49f }, { 266.334f, 385.387f },
|
||||
{ 234.053f, 368.718f }, { 263.347f, 361.184f }, { 244.763f, 339.958f }, { 198.16f, 328.214f }, { 211.675f, 323.407f },
|
||||
{ 225.905f, 318.426f }, { 192.98f, 302.119f }, { 221.267f, 290.693f }, { 161.437f, 286.46f }, { 236.656f, 284.476f },
|
||||
{ 168.023f, 251.799f }, { 105.385f, 221.988f }, { 116.724f, 214.25f }, { 97.2959f, 191.81f }, { 108.89f, 183.05f },
|
||||
{ 77.9896f, 169.242f }, { 48.6763f, 156.088f }, { 68.9635f, 136.415f }, { 29.8484f, 133.886f }, { 49.1966f, 112.826f },
|
||||
{ 113.059f, 29.003f }, { 251.698f, 388.562f }, { 281.689f, 381.929f }, { 297.875f, 378.518f }, { 248.376f, 365.025f },
|
||||
{ 295.791f, 352.763f }, { 216.176f, 348.586f }, { 230.143f, 344.443f }, { 179.89f, 307.457f }, { 174.083f, 280.51f },
|
||||
{ 142.867f, 265.085f }, { 155.127f, 258.692f }, { 124.187f, 243.661f }, { 136.01f, 236.553f }, { 86.4651f, 200.13f },
|
||||
{ 67.5711f, 178.221f }
|
||||
};
|
||||
|
||||
Mat candidates(121, 1, CV_32FC2, (void*)pts_);
|
||||
Size patternSize(13, 8);
|
||||
|
||||
std::vector< Point2f > result;
|
||||
bool res = false;
|
||||
|
||||
EXPECT_NO_THROW(res = findCirclesGrid(candidates, patternSize, result, CALIB_CB_SYMMETRIC_GRID, Ptr<FeatureDetector>()/*blobDetector=NULL*/));
|
||||
EXPECT_FALSE(res);
|
||||
}
|
||||
|
||||
TEST(Calib3d_RotatedCirclesPatternDetector, issue_24964)
|
||||
{
|
||||
string path = cvtest::findDataFile("cameracalibration/circles/circles_24964.png");
|
||||
Mat image = cv::imread(path);
|
||||
ASSERT_FALSE(image.empty()) << "Can't read image: " << path;
|
||||
|
||||
vector<Point2f> centers;
|
||||
Size parrernSize(7, 6);
|
||||
Mat goldCenters(parrernSize.height, parrernSize.width, CV_32FC2);
|
||||
Point2f firstGoldCenter(380.f, 430.f);
|
||||
for (int i = 0; i < parrernSize.height; i++)
|
||||
{
|
||||
for (int j = 0; j < parrernSize.width; j++)
|
||||
{
|
||||
goldCenters.at<Point2f>(i, j) = Point2f(firstGoldCenter.x + j * 100.f, firstGoldCenter.y + i * 100.f);
|
||||
}
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
found = findCirclesGrid(image, parrernSize, centers, CALIB_CB_SYMMETRIC_GRID);
|
||||
|
||||
EXPECT_TRUE(found);
|
||||
ASSERT_EQ(centers.size(), (size_t)parrernSize.area());
|
||||
double error = calcError(centers, goldCenters);
|
||||
EXPECT_LE(error, precise_success_error_level);
|
||||
|
||||
// "rotate" the circle grid by 90 degrees
|
||||
swap(parrernSize.height, parrernSize.width);
|
||||
|
||||
found = findCirclesGrid(image, parrernSize, centers, CALIB_CB_SYMMETRIC_GRID);
|
||||
error = calcError(centers, goldCenters.t());
|
||||
|
||||
EXPECT_TRUE(found);
|
||||
ASSERT_EQ(centers.size(), (size_t)parrernSize.area());
|
||||
EXPECT_LE(error, precise_success_error_level);
|
||||
}
|
||||
|
||||
TEST(Calib3d_CornerOrdering, issue_26830) {
|
||||
const cv::String dataDir = string(TS::ptr()->get_data_path()) + "cameracalibration/";
|
||||
const cv::Mat image = cv::imread(dataDir + "checkerboard_marker_white.png");
|
||||
|
||||
std::vector<Point2f> cornersMinimumSizeMatchesPatternSize;
|
||||
ASSERT_TRUE(cv::findChessboardCornersSB(image, Size(14, 9), cornersMinimumSizeMatchesPatternSize, CALIB_CB_MARKER | CALIB_CB_LARGER));
|
||||
|
||||
std::vector<Point2f> cornersMinimumSizeSmallerThanPatternSize;
|
||||
ASSERT_TRUE(cv::findChessboardCornersSB(image, Size(4, 4), cornersMinimumSizeSmallerThanPatternSize, CALIB_CB_MARKER | CALIB_CB_LARGER));
|
||||
|
||||
ASSERT_EQ(cornersMinimumSizeMatchesPatternSize, cornersMinimumSizeSmallerThanPatternSize);
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
/* End of file. */
|
||||
@@ -0,0 +1,115 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// Intel License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_chessboardgenerator.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
class CV_ChessboardDetectorBadArgTest : public cvtest::BadArgTest
|
||||
{
|
||||
public:
|
||||
CV_ChessboardDetectorBadArgTest() { flags0 = 0; }
|
||||
protected:
|
||||
void run(int);
|
||||
bool checkByGenerator();
|
||||
|
||||
Mat img;
|
||||
Size pattern_size, pattern_size0;
|
||||
int flags, flags0;
|
||||
vector<Point2f> corners;
|
||||
_InputArray img_arg;
|
||||
_OutputArray corners_arg;
|
||||
|
||||
void initArgs()
|
||||
{
|
||||
img_arg = img;
|
||||
corners_arg = corners;
|
||||
pattern_size = pattern_size0;
|
||||
flags = flags0;
|
||||
}
|
||||
|
||||
void run_func()
|
||||
{
|
||||
findChessboardCorners(img_arg, pattern_size, corners_arg, flags);
|
||||
}
|
||||
};
|
||||
|
||||
/* ///////////////////// chess_corner_test ///////////////////////// */
|
||||
void CV_ChessboardDetectorBadArgTest::run( int /*start_from */)
|
||||
{
|
||||
Mat bg(800, 600, CV_8U, Scalar(0));
|
||||
Mat_<float> camMat(3, 3);
|
||||
camMat << 300.f, 0.f, bg.cols/2.f, 0, 300.f, bg.rows/2.f, 0.f, 0.f, 1.f;
|
||||
Mat_<float> distCoeffs(1, 5);
|
||||
distCoeffs << 1.2f, 0.2f, 0.f, 0.f, 0.f;
|
||||
|
||||
ChessBoardGenerator cbg(Size(8,6));
|
||||
vector<Point2f> exp_corn;
|
||||
Mat cb = cbg(bg, camMat, distCoeffs, exp_corn);
|
||||
|
||||
/* /*//*/ */
|
||||
int errors = 0;
|
||||
flags = CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE;
|
||||
|
||||
img = cb.clone();
|
||||
initArgs();
|
||||
pattern_size = Size(2,2);
|
||||
errors += run_test_case( Error::StsOutOfRange, "Invalid pattern size" );
|
||||
|
||||
pattern_size = cbg.cornersSize();
|
||||
|
||||
cb.convertTo(img, CV_32F);
|
||||
errors += run_test_case( Error::StsUnsupportedFormat, "Not 8-bit image" );
|
||||
|
||||
cv::merge(vector<Mat>(2, cb), img);
|
||||
errors += run_test_case( Error::StsUnsupportedFormat, "2 channel image" );
|
||||
|
||||
if (errors)
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
else
|
||||
ts->set_failed_test_info(cvtest::TS::OK);
|
||||
}
|
||||
|
||||
TEST(Calib3d_ChessboardDetector, badarg) { CV_ChessboardDetectorBadArgTest test; test.safe_run(); }
|
||||
|
||||
}} // namespace
|
||||
/* End of file. */
|
||||
@@ -0,0 +1,160 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// Intel License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
class CV_ChessboardDetectorTimingTest : public cvtest::BaseTest
|
||||
{
|
||||
public:
|
||||
CV_ChessboardDetectorTimingTest();
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
CV_ChessboardDetectorTimingTest::CV_ChessboardDetectorTimingTest()
|
||||
{
|
||||
}
|
||||
|
||||
/* ///////////////////// chess_corner_test ///////////////////////// */
|
||||
void CV_ChessboardDetectorTimingTest::run( int start_from )
|
||||
{
|
||||
int code = cvtest::TS::OK;
|
||||
|
||||
/* test parameters */
|
||||
std::string filepath;
|
||||
std::string filename;
|
||||
|
||||
std::vector<Point2f> v;
|
||||
Mat img, gray, thresh;
|
||||
|
||||
int idx, max_idx;
|
||||
int progress = 0;
|
||||
|
||||
filepath = cv::format("%scameracalibration/", ts->get_data_path().c_str() );
|
||||
filename = cv::format("%schessboard_timing_list.dat", filepath.c_str() );
|
||||
cv::FileStorage fs( filename, FileStorage::READ );
|
||||
cv::FileNode board_list = fs["boards"];
|
||||
cv::FileNodeIterator bl_it = board_list.begin();
|
||||
|
||||
if( !fs.isOpened() || !board_list.isSeq() || board_list.size() % 4 != 0 )
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "chessboard_timing_list.dat can not be read or is not valid" );
|
||||
code = cvtest::TS::FAIL_MISSING_TEST_DATA;
|
||||
goto _exit_;
|
||||
}
|
||||
|
||||
max_idx = (int)(board_list.size()/4);
|
||||
for( idx = 0; idx < start_from; idx++ )
|
||||
{
|
||||
bl_it += 4;
|
||||
}
|
||||
|
||||
for( idx = start_from; idx < max_idx; idx++ )
|
||||
{
|
||||
Size pattern_size;
|
||||
|
||||
std::string imgname; read(*bl_it++, imgname, "dummy.txt");
|
||||
int is_chessboard = 0;
|
||||
read(*bl_it++, is_chessboard, 0);
|
||||
read(*bl_it++, pattern_size.width, -1);
|
||||
read(*bl_it++, pattern_size.height, -1);
|
||||
|
||||
ts->update_context( this, idx-1, true );
|
||||
|
||||
/* read the image */
|
||||
filename = cv::format("%s%s", filepath.c_str(), imgname.c_str() );
|
||||
|
||||
img = cv::imread( filename );
|
||||
if( img.empty() )
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "one of chessboard images can't be read: %s\n", filename.c_str() );
|
||||
code = cvtest::TS::FAIL_MISSING_TEST_DATA;
|
||||
continue;
|
||||
}
|
||||
|
||||
ts->printf(cvtest::TS::LOG, "%s: chessboard %d:\n", imgname.c_str(), is_chessboard);
|
||||
|
||||
cvtColor(img, gray, COLOR_BGR2GRAY);
|
||||
|
||||
int64 _time0 = cv::getTickCount();
|
||||
bool result = cv::checkChessboard(gray, pattern_size);
|
||||
int64 _time01 = cv::getTickCount();
|
||||
bool result1 = findChessboardCorners(gray, pattern_size, v, 15);
|
||||
int64 _time1 = cv::getTickCount();
|
||||
|
||||
if( result != (is_chessboard != 0))
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "Error: chessboard was %sdetected in the image %s\n",
|
||||
result ? "" : "not ", imgname.c_str() );
|
||||
code = cvtest::TS::FAIL_INVALID_OUTPUT;
|
||||
goto _exit_;
|
||||
}
|
||||
if(result != result1)
|
||||
{
|
||||
ts->printf( cvtest::TS::LOG, "Warning: results differ cvCheckChessboard %d, cvFindChessboardCorners %d\n",
|
||||
(int)result, (int)result1);
|
||||
}
|
||||
|
||||
int num_pixels = gray.cols*gray.rows;
|
||||
float check_chessboard_time = float(_time01 - _time0)/(float)cv::getTickFrequency(); // in s
|
||||
ts->printf(cvtest::TS::LOG, " cvCheckChessboard time s: %f, us per pixel: %f\n",
|
||||
check_chessboard_time, check_chessboard_time*1e6/num_pixels);
|
||||
|
||||
float find_chessboard_time = float(_time1 - _time01)/(float)cv::getTickFrequency();
|
||||
ts->printf(cvtest::TS::LOG, " cvFindChessboard time s: %f, us per pixel: %f\n",
|
||||
find_chessboard_time, find_chessboard_time*1e6/num_pixels);
|
||||
progress = update_progress( progress, idx-1, max_idx, 0 );
|
||||
}
|
||||
|
||||
_exit_:
|
||||
|
||||
if( code < 0 )
|
||||
ts->set_failed_test_info( code );
|
||||
}
|
||||
|
||||
TEST(Calib3d_ChessboardDetector, timing) { CV_ChessboardDetectorTimingTest test; test.safe_run(); }
|
||||
|
||||
}} // namespace
|
||||
/* End of file. */
|
||||
@@ -0,0 +1,258 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// Intel License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "opencv2/core/types.hpp"
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_chessboardgenerator.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
class CV_ChessboardSubpixelTest : public cvtest::BaseTest
|
||||
{
|
||||
public:
|
||||
CV_ChessboardSubpixelTest();
|
||||
|
||||
protected:
|
||||
Mat intrinsic_matrix_;
|
||||
Mat distortion_coeffs_;
|
||||
Size image_size_;
|
||||
|
||||
void run(int);
|
||||
void generateIntrinsicParams();
|
||||
};
|
||||
|
||||
|
||||
int calcDistance(const vector<Point2f>& set1, const vector<Point2f>& set2, double& mean_dist)
|
||||
{
|
||||
if(set1.size() != set2.size())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<int> indices;
|
||||
double sum_dist = 0.0;
|
||||
for(size_t i = 0; i < set1.size(); i++)
|
||||
{
|
||||
double min_dist = std::numeric_limits<double>::max();
|
||||
int min_idx = -1;
|
||||
|
||||
for(int j = 0; j < (int)set2.size(); j++)
|
||||
{
|
||||
double dist = cv::norm(set1[i] - set2[j]); // TODO cvtest
|
||||
if(dist < min_dist)
|
||||
{
|
||||
min_idx = j;
|
||||
min_dist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
// check validity of min_idx
|
||||
if(min_idx == -1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
std::vector<int>::iterator it = std::find(indices.begin(), indices.end(), min_idx);
|
||||
if(it != indices.end())
|
||||
{
|
||||
// there are two points in set1 corresponding to the same point in set2
|
||||
return 0;
|
||||
}
|
||||
indices.push_back(min_idx);
|
||||
|
||||
// printf("dist %d = %f\n", (int)i, min_dist);
|
||||
|
||||
sum_dist += min_dist*min_dist;
|
||||
}
|
||||
|
||||
mean_dist = sqrt(sum_dist/set1.size());
|
||||
// printf("sum_dist = %f, set1.size() = %d, mean_dist = %f\n", sum_dist, (int)set1.size(), mean_dist);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
CV_ChessboardSubpixelTest::CV_ChessboardSubpixelTest() :
|
||||
intrinsic_matrix_(Size(3, 3), CV_64FC1), distortion_coeffs_(Size(1, 4), CV_64FC1),
|
||||
image_size_(640, 480)
|
||||
{
|
||||
}
|
||||
|
||||
/* ///////////////////// chess_corner_test ///////////////////////// */
|
||||
void CV_ChessboardSubpixelTest::run( int )
|
||||
{
|
||||
int code = cvtest::TS::OK;
|
||||
int progress = 0;
|
||||
|
||||
RNG& rng = ts->get_rng();
|
||||
|
||||
const int runs_count = 20;
|
||||
const int max_pattern_size = 8;
|
||||
const int min_pattern_size = 5;
|
||||
Mat bg(image_size_, CV_8UC1);
|
||||
bg = Scalar(0);
|
||||
|
||||
double sum_dist = 0.0;
|
||||
int count = 0;
|
||||
for(int i = 0; i < runs_count; i++)
|
||||
{
|
||||
const int pattern_width = min_pattern_size + cvtest::randInt(rng) % (max_pattern_size - min_pattern_size);
|
||||
const int pattern_height = min_pattern_size + cvtest::randInt(rng) % (max_pattern_size - min_pattern_size);
|
||||
Size pattern_size;
|
||||
if(pattern_width > pattern_height)
|
||||
{
|
||||
pattern_size = Size(pattern_height, pattern_width);
|
||||
}
|
||||
else
|
||||
{
|
||||
pattern_size = Size(pattern_width, pattern_height);
|
||||
}
|
||||
ChessBoardGenerator gen_chessboard(Size(pattern_size.width + 1, pattern_size.height + 1));
|
||||
|
||||
// generates intrinsic camera and distortion matrices
|
||||
generateIntrinsicParams();
|
||||
|
||||
vector<Point2f> corners;
|
||||
Mat chessboard_image = gen_chessboard(bg, intrinsic_matrix_, distortion_coeffs_, corners);
|
||||
|
||||
vector<Point2f> test_corners;
|
||||
bool result = findChessboardCorners(chessboard_image, pattern_size, test_corners, 15);
|
||||
if (!result && cvtest::debugLevel > 0)
|
||||
{
|
||||
ts->printf(cvtest::TS::LOG, "Warning: chessboard was not detected! Writing image to test.png\n");
|
||||
ts->printf(cvtest::TS::LOG, "Size = %d, %d\n", pattern_size.width, pattern_size.height);
|
||||
ts->printf(cvtest::TS::LOG, "Intrinsic params: fx = %f, fy = %f, cx = %f, cy = %f\n",
|
||||
intrinsic_matrix_.at<double>(0, 0), intrinsic_matrix_.at<double>(1, 1),
|
||||
intrinsic_matrix_.at<double>(0, 2), intrinsic_matrix_.at<double>(1, 2));
|
||||
ts->printf(cvtest::TS::LOG, "Distortion matrix: %f, %f, %f, %f, %f\n",
|
||||
distortion_coeffs_.at<double>(0, 0), distortion_coeffs_.at<double>(0, 1),
|
||||
distortion_coeffs_.at<double>(0, 2), distortion_coeffs_.at<double>(0, 3),
|
||||
distortion_coeffs_.at<double>(0, 4));
|
||||
|
||||
imwrite("test.png", chessboard_image);
|
||||
}
|
||||
if (!result)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
double dist1 = 0.0;
|
||||
int ret = calcDistance(corners, test_corners, dist1);
|
||||
if(ret == 0)
|
||||
{
|
||||
ts->printf(cvtest::TS::LOG, "findChessboardCorners returns invalid corner coordinates!\n");
|
||||
code = cvtest::TS::FAIL_INVALID_OUTPUT;
|
||||
break;
|
||||
}
|
||||
|
||||
cornerSubPix(chessboard_image, test_corners,
|
||||
Size(3, 3), Size(1, 1), TermCriteria(TermCriteria::EPS|TermCriteria::MAX_ITER, 300, 0.1));
|
||||
find4QuadCornerSubpix(chessboard_image, test_corners, Size(5, 5));
|
||||
|
||||
double dist2 = 0.0;
|
||||
ret = calcDistance(corners, test_corners, dist2);
|
||||
if(ret == 0)
|
||||
{
|
||||
ts->printf(cvtest::TS::LOG, "findCornerSubpix returns invalid corner coordinates!\n");
|
||||
code = cvtest::TS::FAIL_INVALID_OUTPUT;
|
||||
break;
|
||||
}
|
||||
|
||||
ts->printf(cvtest::TS::LOG, "Error after findChessboardCorners: %f, after findCornerSubPix: %f\n",
|
||||
dist1, dist2);
|
||||
sum_dist += dist2;
|
||||
count++;
|
||||
|
||||
const double max_reduce_factor = 0.8;
|
||||
if(dist1 < dist2*max_reduce_factor)
|
||||
{
|
||||
ts->printf(cvtest::TS::LOG, "findCornerSubPix increases average error!\n");
|
||||
code = cvtest::TS::FAIL_INVALID_OUTPUT;
|
||||
break;
|
||||
}
|
||||
|
||||
progress = update_progress( progress, i-1, runs_count, 0 );
|
||||
}
|
||||
ASSERT_NE(0, count);
|
||||
sum_dist /= count;
|
||||
ts->printf(cvtest::TS::LOG, "Average error after findCornerSubpix: %f\n", sum_dist);
|
||||
|
||||
if( code < 0 )
|
||||
ts->set_failed_test_info( code );
|
||||
}
|
||||
|
||||
void CV_ChessboardSubpixelTest::generateIntrinsicParams()
|
||||
{
|
||||
RNG& rng = ts->get_rng();
|
||||
const double max_focus_length = 1000.0;
|
||||
const double max_focus_diff = 5.0;
|
||||
|
||||
double fx = cvtest::randReal(rng)*max_focus_length;
|
||||
double fy = fx + cvtest::randReal(rng)*max_focus_diff;
|
||||
double cx = image_size_.width/2;
|
||||
double cy = image_size_.height/2;
|
||||
|
||||
double k1 = 0.5*cvtest::randReal(rng);
|
||||
double k2 = 0.05*cvtest::randReal(rng);
|
||||
double p1 = 0.05*cvtest::randReal(rng);
|
||||
double p2 = 0.05*cvtest::randReal(rng);
|
||||
double k3 = 0.0;
|
||||
|
||||
intrinsic_matrix_ = (Mat_<double>(3, 3) << fx, 0.0, cx, 0.0, fy, cy, 0.0, 0.0, 1.0);
|
||||
distortion_coeffs_ = (Mat_<double>(1, 5) << k1, k2, p1, p2, k3);
|
||||
}
|
||||
|
||||
TEST(Calib3d_ChessboardSubPixDetector, accuracy) { CV_ChessboardSubpixelTest test; test.safe_run(); }
|
||||
|
||||
TEST(Calib3d_CornerSubPix, regression_7204)
|
||||
{
|
||||
cv::Mat image(cv::Size(70, 38), CV_8UC1, cv::Scalar::all(0));
|
||||
image(cv::Rect(65, 26, 5, 5)).setTo(cv::Scalar::all(255));
|
||||
image(cv::Rect(55, 31, 8, 1)).setTo(cv::Scalar::all(255));
|
||||
image(cv::Rect(56, 35, 14, 2)).setTo(cv::Scalar::all(255));
|
||||
image(cv::Rect(66, 24, 4, 2)).setTo(cv::Scalar::all(255));
|
||||
image.at<uchar>(24, 69) = 0;
|
||||
std::vector<cv::Point2f> corners;
|
||||
corners.push_back(cv::Point2f(65, 30));
|
||||
cv::cornerSubPix(image, corners, cv::Size(3, 3), cv::Size(-1, -1),
|
||||
cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.1));
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
/* End of file. */
|
||||
@@ -0,0 +1,218 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
// label format:
|
||||
// image_name
|
||||
// num_face
|
||||
// face_1
|
||||
// face_..
|
||||
// face_num
|
||||
std::map<std::string, Mat> blobFromTXT(const std::string& path, int numCoords)
|
||||
{
|
||||
std::ifstream ifs(path.c_str());
|
||||
CV_Assert(ifs.is_open());
|
||||
|
||||
std::map<std::string, Mat> gt;
|
||||
|
||||
Mat faces;
|
||||
int faceNum = -1;
|
||||
int faceCount = 0;
|
||||
for (std::string line, key; getline(ifs, line); )
|
||||
{
|
||||
std::istringstream iss(line);
|
||||
if (line.find(".png") != std::string::npos)
|
||||
{
|
||||
// Get filename
|
||||
iss >> key;
|
||||
}
|
||||
else if (line.find(" ") == std::string::npos)
|
||||
{
|
||||
// Get the number of faces
|
||||
iss >> faceNum;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get faces
|
||||
Mat face(1, numCoords, CV_32FC1);
|
||||
for (int j = 0; j < numCoords; j++)
|
||||
{
|
||||
iss >> face.at<float>(0, j);
|
||||
}
|
||||
faces.push_back(face);
|
||||
faceCount++;
|
||||
}
|
||||
|
||||
if (faceCount == faceNum)
|
||||
{
|
||||
// Store faces
|
||||
gt[key] = faces;
|
||||
|
||||
faces.release();
|
||||
faceNum = -1;
|
||||
faceCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return gt;
|
||||
}
|
||||
|
||||
TEST(Objdetect_face_detection, regression)
|
||||
{
|
||||
// Pre-set params
|
||||
float scoreThreshold = 0.7f;
|
||||
float matchThreshold = 0.7f;
|
||||
float l2disThreshold = 15.0f;
|
||||
int numLM = 5;
|
||||
int numCoords = 4 + 2 * numLM;
|
||||
|
||||
// Load ground truth labels
|
||||
std::map<std::string, Mat> gt = blobFromTXT(findDataFile("dnn_face/detection/cascades_labels.txt"), numCoords);
|
||||
|
||||
// Initialize detector
|
||||
std::string model = findDataFile("dnn/onnx/models/yunet-202303.onnx", false);
|
||||
Ptr<FaceDetectorYN> faceDetector = FaceDetectorYN::create(model, "", Size(300, 300));
|
||||
faceDetector->setScoreThreshold(0.7f);
|
||||
|
||||
// Detect and match
|
||||
for (auto item: gt)
|
||||
{
|
||||
std::string imagePath = findDataFile("cascadeandhog/images/" + item.first);
|
||||
Mat image = imread(imagePath);
|
||||
|
||||
// Set input size
|
||||
faceDetector->setInputSize(image.size());
|
||||
|
||||
// Run detection
|
||||
Mat faces;
|
||||
faceDetector->detect(image, faces);
|
||||
// std::cout << item.first << " " << item.second.rows << " " << faces.rows << std::endl;
|
||||
|
||||
// Match bboxes and landmarks
|
||||
std::vector<bool> matchedItem(item.second.rows, false);
|
||||
for (int i = 0; i < faces.rows; i++)
|
||||
{
|
||||
if (faces.at<float>(i, numCoords) < scoreThreshold)
|
||||
continue;
|
||||
|
||||
bool boxMatched = false;
|
||||
std::vector<bool> lmMatched(numLM, false);
|
||||
cv::Rect2f resBox(faces.at<float>(i, 0), faces.at<float>(i, 1), faces.at<float>(i, 2), faces.at<float>(i, 3));
|
||||
for (int j = 0; j < item.second.rows && !boxMatched; j++)
|
||||
{
|
||||
if (matchedItem[j])
|
||||
continue;
|
||||
|
||||
// Retrieve bbox and compare IoU
|
||||
cv::Rect2f gtBox(item.second.at<float>(j, 0), item.second.at<float>(j, 1), item.second.at<float>(j, 2), item.second.at<float>(j, 3));
|
||||
double interArea = (resBox & gtBox).area();
|
||||
double iou = interArea / (resBox.area() + gtBox.area() - interArea);
|
||||
if (iou >= matchThreshold)
|
||||
{
|
||||
boxMatched = true;
|
||||
matchedItem[j] = true;
|
||||
}
|
||||
|
||||
// Match landmarks if bbox is matched
|
||||
if (!boxMatched)
|
||||
continue;
|
||||
for (int lmIdx = 0; lmIdx < numLM; lmIdx++)
|
||||
{
|
||||
float gtX = item.second.at<float>(j, 4 + 2 * lmIdx);
|
||||
float gtY = item.second.at<float>(j, 4 + 2 * lmIdx + 1);
|
||||
float resX = faces.at<float>(i, 4 + 2 * lmIdx);
|
||||
float resY = faces.at<float>(i, 4 + 2 * lmIdx + 1);
|
||||
float l2dis = cv::sqrt((gtX - resX) * (gtX - resX) + (gtY - resY) * (gtY - resY));
|
||||
|
||||
if (l2dis <= l2disThreshold)
|
||||
{
|
||||
lmMatched[lmIdx] = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
EXPECT_TRUE(boxMatched) << "In image " << item.first << ", cannot match resBox " << resBox << " with any ground truth.";
|
||||
if (boxMatched)
|
||||
{
|
||||
EXPECT_TRUE(std::all_of(lmMatched.begin(), lmMatched.end(), [](bool v) { return v; })) << "In image " << item.first << ", resBox " << resBox << " matched but its landmarks failed to match.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Objdetect_face_recognition, regression)
|
||||
{
|
||||
// Pre-set params
|
||||
float score_thresh = 0.9f;
|
||||
float nms_thresh = 0.3f;
|
||||
double cosine_similar_thresh = 0.363;
|
||||
double l2norm_similar_thresh = 1.128;
|
||||
|
||||
// Load ground truth labels
|
||||
std::ifstream ifs(findDataFile("dnn_face/recognition/cascades_label.txt").c_str());
|
||||
CV_Assert(ifs.is_open());
|
||||
|
||||
std::set<std::string> fSet;
|
||||
std::map<std::string, Mat> featureMap;
|
||||
std::map<std::pair<std::string, std::string>, int> gtMap;
|
||||
|
||||
|
||||
for (std::string line, key; getline(ifs, line);)
|
||||
{
|
||||
std::string fname1, fname2;
|
||||
int label;
|
||||
std::istringstream iss(line);
|
||||
iss>>fname1>>fname2>>label;
|
||||
// std::cout<<fname1<<" "<<fname2<<" "<<label<<std::endl;
|
||||
|
||||
fSet.insert(fname1);
|
||||
fSet.insert(fname2);
|
||||
gtMap[std::make_pair(fname1, fname2)] = label;
|
||||
}
|
||||
|
||||
// Initialize detector
|
||||
std::string detect_model = findDataFile("dnn/onnx/models/yunet-202303.onnx", false);
|
||||
Ptr<FaceDetectorYN> faceDetector = FaceDetectorYN::create(detect_model, "", Size(150, 150), score_thresh, nms_thresh);
|
||||
|
||||
std::string recog_model = findDataFile("dnn/onnx/models/face_recognizer_fast.onnx", false);
|
||||
Ptr<FaceRecognizerSF> faceRecognizer = FaceRecognizerSF::create(recog_model, "");
|
||||
|
||||
// Detect and match
|
||||
for (auto fname: fSet)
|
||||
{
|
||||
std::string imagePath = findDataFile("dnn_face/recognition/" + fname);
|
||||
Mat image = imread(imagePath);
|
||||
|
||||
Mat faces;
|
||||
faceDetector->detect(image, faces);
|
||||
|
||||
ASSERT_EQ(faces.rows, 1);
|
||||
|
||||
Mat aligned_face;
|
||||
faceRecognizer->alignCrop(image, faces.row(0), aligned_face);
|
||||
|
||||
Mat feature;
|
||||
faceRecognizer->feature(aligned_face, feature);
|
||||
|
||||
featureMap[fname] = feature.clone();
|
||||
}
|
||||
|
||||
for (auto item: gtMap)
|
||||
{
|
||||
Mat feature1 = featureMap[item.first.first];
|
||||
Mat feature2 = featureMap[item.first.second];
|
||||
int label = item.second;
|
||||
|
||||
double cos_score = faceRecognizer->match(feature1, feature2, FaceRecognizerSF::DisType::FR_COSINE);
|
||||
double L2_score = faceRecognizer->match(feature1, feature2, FaceRecognizerSF::DisType::FR_NORM_L2);
|
||||
|
||||
EXPECT_TRUE(label == 0 ? cos_score <= cosine_similar_thresh : cos_score > cosine_similar_thresh) << "Cosine match result of images " << item.first.first << " and " << item.first.second << " is different from ground truth (score: "<< cos_score <<";Thresh: "<< cosine_similar_thresh <<").";
|
||||
EXPECT_TRUE(label == 0 ? L2_score > l2norm_similar_thresh : L2_score <= l2norm_similar_thresh) << "L2norm match result of images " << item.first.first << " and " << item.first.second << " is different from ground truth (score: "<< L2_score <<";Thresh: "<< l2norm_similar_thresh <<").";
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,18 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
#if defined(HAVE_HPX)
|
||||
#include <hpx/hpx_main.hpp>
|
||||
#endif
|
||||
|
||||
static
|
||||
void initTests()
|
||||
{
|
||||
#ifdef HAVE_OPENCV_DNN
|
||||
cvtest::addDataSearchEnv("OPENCV_DNN_TEST_DATA_PATH");
|
||||
#endif // HAVE_OPENCV_DNN
|
||||
}
|
||||
|
||||
CV_TEST_MAIN("cv", initTests())
|
||||
@@ -0,0 +1,46 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace opencv_test
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
using namespace std;
|
||||
using namespace cv::mcc;
|
||||
|
||||
/****************************************************************************************\
|
||||
* Test detection works properly on the simplest images
|
||||
\****************************************************************************************/
|
||||
|
||||
void runCCheckerDetectorBasic(std::string image_name, ColorChart chartType)
|
||||
{
|
||||
Ptr<CCheckerDetector> detector = CCheckerDetector::create();
|
||||
std::string path = cvtest::findDataFile("mcc/" + image_name);
|
||||
cv::Mat img = imread(path);
|
||||
ASSERT_FALSE(img.empty()) << "Test image can't be loaded: " << path;
|
||||
|
||||
detector->setColorChartType(chartType);
|
||||
ASSERT_TRUE(detector->process(img));
|
||||
}
|
||||
TEST(CV_mccRunCCheckerDetectorBasic, accuracy_SG140)
|
||||
{
|
||||
runCCheckerDetectorBasic("SG140.png", SG140);
|
||||
}
|
||||
TEST(CV_mccRunCCheckerDetectorBasic, accuracy_MCC24)
|
||||
{
|
||||
runCCheckerDetectorBasic("MCC24.png", MCC24);
|
||||
}
|
||||
|
||||
TEST(CV_mccRunCCheckerDetectorBasic, accuracy_VINYL18)
|
||||
{
|
||||
runCCheckerDetectorBasic("VINYL18.png", VINYL18);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace opencv_test
|
||||
@@ -0,0 +1,13 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
#ifndef __OPENCV_TEST_PRECOMP_HPP__
|
||||
#define __OPENCV_TEST_PRECOMP_HPP__
|
||||
|
||||
#include "opencv2/ts.hpp"
|
||||
#include "opencv2/geometry.hpp"
|
||||
#include "opencv2/objdetect.hpp"
|
||||
|
||||
#include <random>
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,78 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test {
|
||||
|
||||
static inline
|
||||
void check_qr(const string& root, const string& name_current_image, const string& config_name,
|
||||
const std::vector<Point>& corners,
|
||||
const std::vector<string>& decoded_info, const int max_pixel_error,
|
||||
bool isMulti = false) {
|
||||
const std::string dataset_config = findDataFile(root + "dataset_config.json");
|
||||
FileStorage file_config(dataset_config, FileStorage::READ);
|
||||
ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config;
|
||||
FileNode images_list = file_config[config_name];
|
||||
size_t images_count = static_cast<size_t>(images_list.size());
|
||||
ASSERT_GT(images_count, 0u) << "Can't find validation data entries in 'test_images': " << dataset_config;
|
||||
for (size_t index = 0; index < images_count; index++) {
|
||||
FileNode config = images_list[(int)index];
|
||||
std::string name_test_image = config["image_name"];
|
||||
if (name_test_image == name_current_image) {
|
||||
if (isMulti) {
|
||||
for(int j = 0; j < int(corners.size()); j += 4) {
|
||||
bool ok = false;
|
||||
for (int k = 0; k < int(corners.size() / 4); k++) {
|
||||
int count_eq_points = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int x = config["x"][k][i];
|
||||
int y = config["y"][k][i];
|
||||
if(((abs(corners[j + i].x - x)) <= max_pixel_error) && ((abs(corners[j + i].y - y)) <= max_pixel_error))
|
||||
count_eq_points++;
|
||||
}
|
||||
if (count_eq_points == 4) {
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(ok);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < (int)corners.size(); i++) {
|
||||
int x = config["x"][i];
|
||||
int y = config["y"][i];
|
||||
EXPECT_NEAR(x, corners[i].x, max_pixel_error);
|
||||
EXPECT_NEAR(y, corners[i].y, max_pixel_error);
|
||||
}
|
||||
}
|
||||
|
||||
if (decoded_info.size() == 0ull)
|
||||
return;
|
||||
if (isMulti) {
|
||||
size_t count_eq_info = 0;
|
||||
for(int i = 0; i < int(decoded_info.size()); i++) {
|
||||
for(int j = 0; j < int(decoded_info.size()); j++) {
|
||||
std::string original_info = config["info"][j];
|
||||
if(original_info == decoded_info[i]) {
|
||||
count_eq_info++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(decoded_info.size(), count_eq_info);
|
||||
}
|
||||
else {
|
||||
std::string original_info = config["info"];
|
||||
EXPECT_EQ(decoded_info[0], original_info);
|
||||
}
|
||||
|
||||
return; // done
|
||||
}
|
||||
}
|
||||
FAIL() << "Not found results for '" << name_current_image << "' image in config file:" << dataset_config <<
|
||||
"Re-run tests with enabled UPDATE_QRCODE_TEST_DATA macro to update test data.\n";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,718 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_qr_utils.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
std::string qrcode_images_name[] = {
|
||||
"version_1_down.jpg", "version_1_left.jpg", "version_1_right.jpg", "version_1_up.jpg", "version_1_top.jpg",
|
||||
"version_2_down.jpg", "version_2_left.jpg", "version_2_right.jpg", "version_2_up.jpg", "version_2_top.jpg",
|
||||
"version_3_down.jpg", "version_3_left.jpg", "version_3_right.jpg", "version_3_up.jpg", "version_3_top.jpg",
|
||||
"version_4_down.jpg", "version_4_left.jpg", "version_4_right.jpg", "version_4_up.jpg", "version_4_top.jpg",
|
||||
"version_5_down.jpg", "version_5_left.jpg", /*"version_5_right.jpg",*/ "version_5_up.jpg", "version_5_top.jpg",
|
||||
"russian.jpg", "kanji.jpg", "link_github_ocv.jpg", "link_ocv.jpg", "link_wiki_cv.jpg"
|
||||
// version_5_right.jpg DISABLED after tile fix, PR #22025
|
||||
};
|
||||
|
||||
// Todo: fix corner align in big QRs to enable close_5.png
|
||||
std::string qrcode_images_close[] = {
|
||||
"close_1.png", "close_2.png", "close_3.png", "close_4.png"//, "close_5.png"
|
||||
};
|
||||
std::string qrcode_images_monitor[] = {
|
||||
"monitor_1.png", "monitor_2.png", "monitor_3.png", "monitor_4.png", "monitor_5.png"
|
||||
};
|
||||
std::string qrcode_images_curved[] = {
|
||||
"curved_1.jpg", "curved_2.jpg", "curved_3.jpg", /*"curved_4.jpg",*/ "curved_5.jpg", /*"curved_6.jpg",*/ "curved_7.jpg", "curved_8.jpg"
|
||||
};
|
||||
// curved_4.jpg, curved_6.jpg DISABLED after tile fix, PR #22025
|
||||
std::string qrcode_images_multiple[] = {
|
||||
"2_qrcodes.png", "3_close_qrcodes.png", "3_qrcodes.png", "4_qrcodes.png",
|
||||
"5_qrcodes.png", "6_qrcodes.png", "7_qrcodes.png", "8_close_qrcodes.png"
|
||||
};
|
||||
|
||||
static std::set<std::pair<std::string, std::string>> disabled_samples = {{"5_qrcodes.png", "aruco_based"}};
|
||||
|
||||
//#define UPDATE_QRCODE_TEST_DATA
|
||||
#ifdef UPDATE_QRCODE_TEST_DATA
|
||||
|
||||
TEST(Objdetect_QRCode, generate_test_data)
|
||||
{
|
||||
const std::string root = "qrcode/";
|
||||
const std::string dataset_config = findDataFile(root + "dataset_config.json");
|
||||
FileStorage file_config(dataset_config, FileStorage::WRITE);
|
||||
|
||||
file_config << "test_images" << "[";
|
||||
size_t images_count = sizeof(qrcode_images_name) / sizeof(qrcode_images_name[0]);
|
||||
for (size_t i = 0; i < images_count; i++)
|
||||
{
|
||||
file_config << "{:" << "image_name" << qrcode_images_name[i];
|
||||
std::string image_path = findDataFile(root + qrcode_images_name[i]);
|
||||
std::vector<Point> corners;
|
||||
Mat src = imread(image_path, IMREAD_GRAYSCALE), straight_barcode;
|
||||
std::string decoded_info;
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
EXPECT_TRUE(detectQRCode(src, corners));
|
||||
EXPECT_TRUE(decodeQRCode(src, corners, decoded_info, straight_barcode));
|
||||
|
||||
file_config << "x" << "[:";
|
||||
for (size_t j = 0; j < corners.size(); j++) { file_config << corners[j].x; }
|
||||
file_config << "]";
|
||||
file_config << "y" << "[:";
|
||||
for (size_t j = 0; j < corners.size(); j++) { file_config << corners[j].y; }
|
||||
file_config << "]";
|
||||
file_config << "info" << decoded_info;
|
||||
file_config << "}";
|
||||
}
|
||||
file_config << "]";
|
||||
file_config.release();
|
||||
}
|
||||
|
||||
TEST(Objdetect_QRCode_Close, generate_test_data)
|
||||
{
|
||||
const std::string root = "qrcode/close/";
|
||||
const std::string dataset_config = findDataFile(root + "dataset_config.json");
|
||||
FileStorage file_config(dataset_config, FileStorage::WRITE);
|
||||
|
||||
file_config << "close_images" << "[";
|
||||
size_t close_count = sizeof(qrcode_images_close) / sizeof(qrcode_images_close[0]);
|
||||
for (size_t i = 0; i < close_count; i++)
|
||||
{
|
||||
file_config << "{:" << "image_name" << qrcode_images_close[i];
|
||||
std::string image_path = findDataFile(root + qrcode_images_close[i]);
|
||||
std::vector<Point> corners;
|
||||
Mat src = imread(image_path, IMREAD_GRAYSCALE), barcode, straight_barcode;
|
||||
std::string decoded_info;
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
const double min_side = std::min(src.size().width, src.size().height);
|
||||
double coeff_expansion = 1024.0 / min_side;
|
||||
const int width = cvRound(src.size().width * coeff_expansion);
|
||||
const int height = cvRound(src.size().height * coeff_expansion);
|
||||
Size new_size(width, height);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
EXPECT_TRUE(detectQRCode(barcode, corners));
|
||||
EXPECT_TRUE(decodeQRCode(barcode, corners, decoded_info, straight_barcode));
|
||||
|
||||
file_config << "x" << "[:";
|
||||
for (size_t j = 0; j < corners.size(); j++) { file_config << corners[j].x; }
|
||||
file_config << "]";
|
||||
file_config << "y" << "[:";
|
||||
for (size_t j = 0; j < corners.size(); j++) { file_config << corners[j].y; }
|
||||
file_config << "]";
|
||||
file_config << "info" << decoded_info;
|
||||
file_config << "}";
|
||||
}
|
||||
file_config << "]";
|
||||
file_config.release();
|
||||
}
|
||||
TEST(Objdetect_QRCode_Monitor, generate_test_data)
|
||||
{
|
||||
const std::string root = "qrcode/monitor/";
|
||||
const std::string dataset_config = findDataFile(root + "dataset_config.json");
|
||||
FileStorage file_config(dataset_config, FileStorage::WRITE);
|
||||
|
||||
file_config << "monitor_images" << "[";
|
||||
size_t monitor_count = sizeof(qrcode_images_monitor) / sizeof(qrcode_images_monitor[0]);
|
||||
for (size_t i = 0; i < monitor_count; i++)
|
||||
{
|
||||
file_config << "{:" << "image_name" << qrcode_images_monitor[i];
|
||||
std::string image_path = findDataFile(root + qrcode_images_monitor[i]);
|
||||
std::vector<Point> corners;
|
||||
Mat src = imread(image_path, IMREAD_GRAYSCALE), barcode, straight_barcode;
|
||||
std::string decoded_info;
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
const double min_side = std::min(src.size().width, src.size().height);
|
||||
double coeff_expansion = 1024.0 / min_side;
|
||||
const int width = cvRound(src.size().width * coeff_expansion);
|
||||
const int height = cvRound(src.size().height * coeff_expansion);
|
||||
Size new_size(width, height);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
EXPECT_TRUE(detectQRCode(barcode, corners));
|
||||
EXPECT_TRUE(decodeQRCode(barcode, corners, decoded_info, straight_barcode));
|
||||
|
||||
file_config << "x" << "[:";
|
||||
for (size_t j = 0; j < corners.size(); j++) { file_config << corners[j].x; }
|
||||
file_config << "]";
|
||||
file_config << "y" << "[:";
|
||||
for (size_t j = 0; j < corners.size(); j++) { file_config << corners[j].y; }
|
||||
file_config << "]";
|
||||
file_config << "info" << decoded_info;
|
||||
file_config << "}";
|
||||
}
|
||||
file_config << "]";
|
||||
file_config.release();
|
||||
}
|
||||
TEST(Objdetect_QRCode_Curved, generate_test_data)
|
||||
{
|
||||
const std::string root = "qrcode/curved/";
|
||||
const std::string dataset_config = findDataFile(root + "dataset_config.json");
|
||||
FileStorage file_config(dataset_config, FileStorage::WRITE);
|
||||
|
||||
file_config << "test_images" << "[";
|
||||
size_t images_count = sizeof(qrcode_images_curved) / sizeof(qrcode_images_curved[0]);
|
||||
for (size_t i = 0; i < images_count; i++)
|
||||
{
|
||||
file_config << "{:" << "image_name" << qrcode_images_curved[i];
|
||||
std::string image_path = findDataFile(root + qrcode_images_curved[i]);
|
||||
std::vector<Point> corners;
|
||||
Mat src = imread(image_path, IMREAD_GRAYSCALE), straight_barcode;
|
||||
std::string decoded_info;
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
EXPECT_TRUE(detectQRCode(src, corners));
|
||||
EXPECT_TRUE(decodeCurvedQRCode(src, corners, decoded_info, straight_barcode));
|
||||
|
||||
file_config << "x" << "[:";
|
||||
for (size_t j = 0; j < corners.size(); j++) { file_config << corners[j].x; }
|
||||
file_config << "]";
|
||||
file_config << "y" << "[:";
|
||||
for (size_t j = 0; j < corners.size(); j++) { file_config << corners[j].y; }
|
||||
file_config << "]";
|
||||
file_config << "info" << decoded_info;
|
||||
file_config << "}";
|
||||
}
|
||||
file_config << "]";
|
||||
file_config.release();
|
||||
}
|
||||
TEST(Objdetect_QRCode_Multi, generate_test_data)
|
||||
{
|
||||
const std::string root = "qrcode/multiple/";
|
||||
const std::string dataset_config = findDataFile(root + "dataset_config.json");
|
||||
FileStorage file_config(dataset_config, FileStorage::WRITE);
|
||||
|
||||
file_config << "multiple_images" << "[:";
|
||||
size_t multiple_count = sizeof(qrcode_images_multiple) / sizeof(qrcode_images_multiple[0]);
|
||||
for (size_t i = 0; i < multiple_count; i++)
|
||||
{
|
||||
file_config << "{:" << "image_name" << qrcode_images_multiple[i];
|
||||
std::string image_path = findDataFile(root + qrcode_images_multiple[i]);
|
||||
Mat src = imread(image_path);
|
||||
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
std::vector<Point> corners;
|
||||
QRCodeDetector qrcode;
|
||||
EXPECT_TRUE(qrcode.detectMulti(src, corners));
|
||||
std::vector<cv::String> decoded_info;
|
||||
std::vector<Mat> straight_barcode;
|
||||
EXPECT_TRUE(qrcode.decodeMulti(src, corners, decoded_info, straight_barcode));
|
||||
|
||||
file_config << "x" << "[:";
|
||||
for(size_t j = 0; j < corners.size(); j += 4)
|
||||
{
|
||||
file_config << "[:";
|
||||
for (size_t k = 0; k < 4; k++)
|
||||
{
|
||||
file_config << corners[j + k].x;
|
||||
}
|
||||
file_config << "]";
|
||||
}
|
||||
file_config << "]";
|
||||
file_config << "y" << "[:";
|
||||
for(size_t j = 0; j < corners.size(); j += 4)
|
||||
{
|
||||
file_config << "[:";
|
||||
for (size_t k = 0; k < 4; k++)
|
||||
{
|
||||
file_config << corners[j + k].y;
|
||||
}
|
||||
file_config << "]";
|
||||
}
|
||||
file_config << "]";
|
||||
file_config << "info";
|
||||
file_config << "[:";
|
||||
|
||||
for(size_t j = 0; j < decoded_info.size(); j++)
|
||||
{
|
||||
file_config << decoded_info[j];
|
||||
}
|
||||
file_config << "]";
|
||||
file_config << "}";
|
||||
}
|
||||
|
||||
file_config << "]";
|
||||
file_config.release();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
typedef testing::TestWithParam< std::string > Objdetect_QRCode;
|
||||
TEST_P(Objdetect_QRCode, regression)
|
||||
{
|
||||
const std::string name_current_image = GetParam();
|
||||
const std::string root = "qrcode/";
|
||||
const int pixels_error = 3;
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path, IMREAD_GRAYSCALE), straight_barcode;
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
|
||||
std::vector<Point> corners;
|
||||
std::string decoded_info;
|
||||
QRCodeDetector qrcode;
|
||||
decoded_info = qrcode.detectAndDecode(src, corners, straight_barcode);
|
||||
ASSERT_FALSE(corners.empty());
|
||||
ASSERT_FALSE(decoded_info.empty());
|
||||
int expected_barcode_type = CV_8UC1;
|
||||
EXPECT_EQ(expected_barcode_type, straight_barcode.type());
|
||||
check_qr(root, name_current_image, "test_images", corners, {decoded_info}, pixels_error);
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam< std::string > Objdetect_QRCode_Close;
|
||||
TEST_P(Objdetect_QRCode_Close, regression)
|
||||
{
|
||||
const std::string name_current_image = GetParam();
|
||||
const std::string root = "qrcode/close/";
|
||||
const int pixels_error = 3;
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path, IMREAD_GRAYSCALE), barcode, straight_barcode;
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
const double min_side = std::min(src.size().width, src.size().height);
|
||||
double coeff_expansion = 1024.0 / min_side;
|
||||
const int width = cvRound(src.size().width * coeff_expansion);
|
||||
const int height = cvRound(src.size().height * coeff_expansion);
|
||||
Size new_size(width, height);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
std::vector<Point> corners;
|
||||
std::string decoded_info;
|
||||
QRCodeDetector qrcode;
|
||||
decoded_info = qrcode.detectAndDecode(barcode, corners, straight_barcode);
|
||||
ASSERT_FALSE(corners.empty());
|
||||
ASSERT_FALSE(decoded_info.empty());
|
||||
int expected_barcode_type = CV_8UC1;
|
||||
EXPECT_EQ(expected_barcode_type, straight_barcode.type());
|
||||
check_qr(root, name_current_image, "close_images", corners, {decoded_info}, pixels_error);
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam< std::string > Objdetect_QRCode_Monitor;
|
||||
TEST_P(Objdetect_QRCode_Monitor, regression)
|
||||
{
|
||||
const std::string name_current_image = GetParam();
|
||||
const std::string root = "qrcode/monitor/";
|
||||
const int pixels_error = 3;
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path, IMREAD_GRAYSCALE), barcode, straight_barcode;
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
const double min_side = std::min(src.size().width, src.size().height);
|
||||
double coeff_expansion = 1024.0 / min_side;
|
||||
const int width = cvRound(src.size().width * coeff_expansion);
|
||||
const int height = cvRound(src.size().height * coeff_expansion);
|
||||
Size new_size(width, height);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
std::vector<Point> corners;
|
||||
std::string decoded_info;
|
||||
QRCodeDetector qrcode;
|
||||
decoded_info = qrcode.detectAndDecode(barcode, corners, straight_barcode);
|
||||
ASSERT_FALSE(corners.empty());
|
||||
ASSERT_FALSE(decoded_info.empty());
|
||||
int expected_barcode_type = CV_8UC1;
|
||||
EXPECT_EQ(expected_barcode_type, straight_barcode.type());
|
||||
check_qr(root, name_current_image, "monitor_images", corners, {decoded_info}, pixels_error);
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam< std::string > Objdetect_QRCode_Curved;
|
||||
TEST_P(Objdetect_QRCode_Curved, regression)
|
||||
{
|
||||
const std::string name_current_image = GetParam();
|
||||
const std::string root = "qrcode/curved/";
|
||||
const int pixels_error = 3;
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path, IMREAD_GRAYSCALE), straight_barcode;
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
|
||||
std::vector<Point> corners;
|
||||
std::string decoded_info;
|
||||
QRCodeDetector qrcode;
|
||||
decoded_info = qrcode.detectAndDecodeCurved(src, corners, straight_barcode);
|
||||
ASSERT_FALSE(corners.empty());
|
||||
ASSERT_FALSE(decoded_info.empty());
|
||||
int expected_barcode_type = CV_8UC1;
|
||||
EXPECT_EQ(expected_barcode_type, straight_barcode.type());
|
||||
check_qr(root, name_current_image, "test_images", corners, {decoded_info}, pixels_error);
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam<std::tuple<std::string, std::string>> Objdetect_QRCode_Multi;
|
||||
TEST_P(Objdetect_QRCode_Multi, regression)
|
||||
{
|
||||
const std::string name_current_image = get<0>(GetParam());
|
||||
const std::string root = "qrcode/multiple/";
|
||||
const std::string method = get<1>(GetParam());
|
||||
const int pixels_error = 4;
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path);
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
if (disabled_samples.find({name_current_image, method}) != disabled_samples.end())
|
||||
throw SkipTestException(name_current_image + " is disabled sample for method " + method);
|
||||
GraphicalCodeDetector qrcode = QRCodeDetector();
|
||||
if (method == "aruco_based") {
|
||||
qrcode = QRCodeDetectorAruco();
|
||||
}
|
||||
std::vector<Point> corners;
|
||||
std::vector<cv::String> decoded_info;
|
||||
std::vector<Mat> straight_barcode;
|
||||
EXPECT_TRUE(qrcode.detectAndDecodeMulti(src, decoded_info, corners, straight_barcode));
|
||||
ASSERT_FALSE(corners.empty());
|
||||
ASSERT_FALSE(decoded_info.empty());
|
||||
int expected_barcode_type = CV_8UC1;
|
||||
for(size_t i = 0; i < straight_barcode.size(); i++)
|
||||
EXPECT_EQ(expected_barcode_type, straight_barcode[i].type());
|
||||
check_qr(root, name_current_image, "multiple_images", corners, decoded_info, pixels_error, true);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode, testing::ValuesIn(qrcode_images_name));
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Close, testing::ValuesIn(qrcode_images_close));
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Monitor, testing::ValuesIn(qrcode_images_monitor));
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Curved, testing::ValuesIn(qrcode_images_curved));
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Multi, testing::Combine(testing::ValuesIn(qrcode_images_multiple),
|
||||
testing::Values("contours_based", "aruco_based")));
|
||||
|
||||
TEST(Objdetect_QRCode_decodeMulti, decode_regression_16491)
|
||||
{
|
||||
Mat zero_image = Mat::zeros(256, 256, CV_8UC1);
|
||||
Point corners_[] = {Point(16, 16), Point(128, 16), Point(128, 128), Point(16, 128),
|
||||
Point(16, 16), Point(128, 16), Point(128, 128), Point(16, 128)};
|
||||
std::vector<Point> vec_corners;
|
||||
int array_size = 8;
|
||||
vec_corners.assign(corners_, corners_ + array_size);
|
||||
std::vector<cv::String> decoded_info;
|
||||
std::vector<Mat> straight_barcode;
|
||||
QRCodeDetector vec_qrcode;
|
||||
EXPECT_NO_THROW(vec_qrcode.decodeMulti(zero_image, vec_corners, decoded_info, straight_barcode));
|
||||
|
||||
Mat mat_corners(2, 4, CV_32SC2, (void*)&vec_corners[0]);
|
||||
QRCodeDetector mat_qrcode;
|
||||
EXPECT_NO_THROW(mat_qrcode.decodeMulti(zero_image, mat_corners, decoded_info, straight_barcode));
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam<std::string> Objdetect_QRCode_detectMulti;
|
||||
TEST_P(Objdetect_QRCode_detectMulti, detect_regression_16961)
|
||||
{
|
||||
const std::string method = GetParam();
|
||||
const std::string name_current_image = "9_qrcodes.jpg";
|
||||
const std::string root = "qrcode/multiple/";
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path);
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
|
||||
GraphicalCodeDetector qrcode = QRCodeDetector();
|
||||
if (method == "aruco_based") {
|
||||
qrcode = QRCodeDetectorAruco();
|
||||
}
|
||||
std::vector<Point> corners;
|
||||
EXPECT_TRUE(qrcode.detectMulti(src, corners));
|
||||
ASSERT_FALSE(corners.empty());
|
||||
size_t expect_corners_size = 36;
|
||||
EXPECT_EQ(corners.size(), expect_corners_size);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_detectMulti, testing::Values("contours_based", "aruco_based"));
|
||||
typedef testing::TestWithParam<std::string> Objdetect_QRCode_detectAndDecodeMulti;
|
||||
TEST_P(Objdetect_QRCode_detectAndDecodeMulti, check_output_parameters_type_19363)
|
||||
{
|
||||
const std::string name_current_image = "9_qrcodes.jpg";
|
||||
const std::string root = "qrcode/multiple/";
|
||||
const std::string method = GetParam();
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path);
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
GraphicalCodeDetector qrcode = QRCodeDetector();
|
||||
if (method == "aruco_based") {
|
||||
qrcode = QRCodeDetectorAruco();
|
||||
}
|
||||
std::vector<Point> corners;
|
||||
std::vector<cv::String> decoded_info;
|
||||
#if 0 // FIXIT: OutputArray::create() type check
|
||||
std::vector<Mat2b> straight_barcode_nchannels;
|
||||
EXPECT_ANY_THROW(qrcode->detectAndDecodeMulti(src, decoded_info, corners, straight_barcode_nchannels));
|
||||
#endif
|
||||
|
||||
int expected_barcode_type = CV_8UC1;
|
||||
std::vector<Mat1b> straight_barcode;
|
||||
EXPECT_TRUE(qrcode.detectAndDecodeMulti(src, decoded_info, corners, straight_barcode));
|
||||
ASSERT_FALSE(corners.empty());
|
||||
for(size_t i = 0; i < straight_barcode.size(); i++)
|
||||
EXPECT_EQ(expected_barcode_type, straight_barcode[i].type());
|
||||
}
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_detectAndDecodeMulti, testing::Values("contours_based", "aruco_based"));
|
||||
|
||||
|
||||
TEST(Objdetect_QRCode_detect, detect_regression_20882)
|
||||
{
|
||||
const std::string name_current_image = "qrcode_near_the_end.jpg";
|
||||
const std::string root = "qrcode/";
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path);
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
|
||||
QRCodeDetector qrcode;
|
||||
std::vector<Point> corners;
|
||||
Mat straight_barcode;
|
||||
cv::String decoded_info;
|
||||
EXPECT_TRUE(qrcode.detect(src, corners));
|
||||
EXPECT_TRUE(!corners.empty());
|
||||
EXPECT_NO_THROW(qrcode.decode(src, corners, straight_barcode));
|
||||
}
|
||||
|
||||
TEST(Objdetect_QRCode_basic, not_found_qrcode)
|
||||
{
|
||||
std::vector<Point> corners;
|
||||
Mat straight_barcode;
|
||||
std::string decoded_info;
|
||||
Mat zero_image = Mat::zeros(256, 256, CV_8UC1);
|
||||
QRCodeDetector qrcode;
|
||||
EXPECT_FALSE(qrcode.detect(zero_image, corners));
|
||||
corners = std::vector<Point>(4);
|
||||
EXPECT_ANY_THROW(qrcode.decode(zero_image, corners, straight_barcode));
|
||||
}
|
||||
|
||||
TEST(Objdetect_QRCode_detect, detect_regression_21287)
|
||||
{
|
||||
const std::string name_current_image = "issue_21287.png";
|
||||
const std::string root = "qrcode/";
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path);
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
|
||||
QRCodeDetector qrcode;
|
||||
std::vector<Point> corners;
|
||||
Mat straight_barcode;
|
||||
cv::String decoded_info;
|
||||
EXPECT_TRUE(qrcode.detect(src, corners));
|
||||
EXPECT_TRUE(!corners.empty());
|
||||
EXPECT_NO_THROW(qrcode.decode(src, corners, straight_barcode));
|
||||
}
|
||||
|
||||
TEST(Objdetect_QRCode_detect_flipped, regression_23249)
|
||||
{
|
||||
|
||||
const std::vector<std::pair<std::string, std::string>> flipped_images =
|
||||
// image name , expected result
|
||||
{{"flipped_1.png", "The key is /qrcod_OMevpf"},
|
||||
{"flipped_2.png", "A26"}};
|
||||
|
||||
const std::string root = "qrcode/flipped/";
|
||||
|
||||
for(const auto &flipped_image : flipped_images){
|
||||
const std::string &image_name = flipped_image.first;
|
||||
|
||||
std::string image_path = findDataFile(root + image_name);
|
||||
Mat src = imread(image_path);
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
QRCodeDetector qrcode;
|
||||
std::vector<Point> corners;
|
||||
Mat straight_barcode;
|
||||
cv::String decoded_info;
|
||||
EXPECT_TRUE(qrcode.detect(src, corners));
|
||||
EXPECT_TRUE(!corners.empty());
|
||||
std::string decoded_msg;
|
||||
const std::string &expect_msg = flipped_image.second;
|
||||
EXPECT_NO_THROW(decoded_msg = qrcode.decode(src, corners, straight_barcode));
|
||||
ASSERT_FALSE(straight_barcode.empty()) << "Can't decode qrimage.";
|
||||
EXPECT_EQ(expect_msg, decoded_msg);
|
||||
}
|
||||
}
|
||||
|
||||
// @author Kumataro, https://github.com/Kumataro
|
||||
TEST(Objdetect_QRCode_decode, decode_regression_21929)
|
||||
{
|
||||
const cv::String expect_msg = "OpenCV";
|
||||
Mat qrImg;
|
||||
QRCodeEncoder::Params params;
|
||||
params.version = 8; // 49x49
|
||||
Ptr<QRCodeEncoder> qrcode_enc = cv::QRCodeEncoder::create(params);
|
||||
qrcode_enc->encode(expect_msg, qrImg);
|
||||
|
||||
Mat src;
|
||||
cv::resize(qrImg, src, Size(200,200), 1.0, 1.0, INTER_NEAREST);
|
||||
|
||||
QRCodeDetector qrcode;
|
||||
std::vector<Point> corners;
|
||||
Mat straight_barcode;
|
||||
|
||||
EXPECT_TRUE(qrcode.detect(src, corners));
|
||||
EXPECT_TRUE(!corners.empty());
|
||||
cv::String decoded_msg;
|
||||
EXPECT_NO_THROW(decoded_msg = qrcode.decode(src, corners, straight_barcode));
|
||||
ASSERT_FALSE(straight_barcode.empty()) << "Can't decode qrimage.";
|
||||
EXPECT_EQ(expect_msg, decoded_msg);
|
||||
}
|
||||
|
||||
TEST(Objdetect_QRCode_decode, decode_regression_version_25)
|
||||
{
|
||||
const cv::String expect_msg = "OpenCV";
|
||||
Mat qrImg;
|
||||
QRCodeEncoder::Params params;
|
||||
params.version = 25; // 117x117
|
||||
Ptr<QRCodeEncoder> qrcode_enc = cv::QRCodeEncoder::create(params);
|
||||
qrcode_enc->encode(expect_msg, qrImg);
|
||||
|
||||
Mat src;
|
||||
cv::resize(qrImg, src, qrImg.size()*3, 1.0, 1.0, INTER_NEAREST);
|
||||
|
||||
QRCodeDetector qrcode;
|
||||
std::vector<Point> corners;
|
||||
Mat straight_barcode;
|
||||
|
||||
EXPECT_TRUE(qrcode.detect(src, corners));
|
||||
EXPECT_TRUE(!corners.empty());
|
||||
|
||||
cv::String decoded_msg;
|
||||
EXPECT_NO_THROW(decoded_msg = qrcode.decode(src, corners, straight_barcode));
|
||||
ASSERT_FALSE(straight_barcode.empty()) << "Can't decode qrimage.";
|
||||
EXPECT_EQ(expect_msg, decoded_msg);
|
||||
}
|
||||
|
||||
TEST_P(Objdetect_QRCode_detectAndDecodeMulti, decode_9_qrcodes_version7)
|
||||
{
|
||||
const std::string name_current_image = "9_qrcodes_version7.jpg";
|
||||
const std::string root = "qrcode/multiple/";
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path);
|
||||
const std::string method = GetParam();
|
||||
GraphicalCodeDetector qrcode = QRCodeDetector();
|
||||
if (method == "aruco_based") {
|
||||
qrcode = QRCodeDetectorAruco();
|
||||
}
|
||||
std::vector<Point> corners;
|
||||
std::vector<cv::String> decoded_info;
|
||||
|
||||
std::vector<Mat1b> straight_barcode;
|
||||
qrcode.detectAndDecodeMulti(src, decoded_info, corners, straight_barcode);
|
||||
EXPECT_EQ(9ull, decoded_info.size());
|
||||
const string gold_info = "I love OpenCV, QR Code version = 7, error correction = level Quartile";
|
||||
for (const auto& info : decoded_info) {
|
||||
EXPECT_EQ(info, gold_info);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // UPDATE_QRCODE_TEST_DATA
|
||||
|
||||
TEST(Objdetect_QRCode_detectAndDecode, utf8_output)
|
||||
{
|
||||
const std::string name_current_image = "umlaut.png";
|
||||
const std::string root = "qrcode/";
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path);
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
|
||||
QRCodeDetector qrcode;
|
||||
std::vector<Point> corners;
|
||||
Mat straight;
|
||||
std::string decoded_info = qrcode.detectAndDecode(src, corners, straight);
|
||||
EXPECT_FALSE(decoded_info.empty());
|
||||
EXPECT_NE(decoded_info.find("M\xc3\xbcllheimstrasse"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST_P(Objdetect_QRCode_detectAndDecodeMulti, detect_regression_24679)
|
||||
{
|
||||
const std::string name_current_image = "issue_24679.png";
|
||||
const std::string root = "qrcode/";
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat img = imread(image_path);
|
||||
const std::string method = GetParam();
|
||||
GraphicalCodeDetector qrcode = QRCodeDetector();
|
||||
if (method == "aruco_based") {
|
||||
qrcode = QRCodeDetectorAruco();
|
||||
}
|
||||
std::vector<cv::String> decoded_info;
|
||||
ASSERT_TRUE(qrcode.detectAndDecodeMulti(img, decoded_info));
|
||||
EXPECT_EQ(decoded_info.size(), 4U);
|
||||
}
|
||||
|
||||
TEST_P(Objdetect_QRCode_detectAndDecodeMulti, detect_regression_24011)
|
||||
{
|
||||
const std::string name_current_image = "issue_24011.jpg";
|
||||
const std::string root = "qrcode/";
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat img = imread(image_path);
|
||||
const std::string method = GetParam();
|
||||
GraphicalCodeDetector qrcode = QRCodeDetector();
|
||||
if (method == "aruco_based") {
|
||||
qrcode = QRCodeDetectorAruco();
|
||||
}
|
||||
std::vector<cv::String> decoded_info;
|
||||
ASSERT_TRUE(qrcode.detectAndDecodeMulti(img, decoded_info));
|
||||
EXPECT_EQ(decoded_info.size(), 2U);
|
||||
}
|
||||
|
||||
TEST(Objdetect_QRCode_detect, detect_regression_24450)
|
||||
{
|
||||
const std::string name_current_image = "issue_24450.png";
|
||||
const std::string root = "qrcode/";
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat img = imread(image_path);
|
||||
GraphicalCodeDetector qrcode = QRCodeDetector();
|
||||
std::vector<Point2f> points;
|
||||
ASSERT_TRUE(qrcode.detect(img, points));
|
||||
EXPECT_EQ(points.size(), 4U);
|
||||
img.at<Vec3b>(img.rows - 1, 296) = {};
|
||||
ASSERT_TRUE(qrcode.detect(img, points));
|
||||
EXPECT_EQ(points.size(), 4U);
|
||||
}
|
||||
|
||||
TEST(Objdetect_QRCode_detect, detect_regression_22892)
|
||||
{
|
||||
const std::string name_current_image = "issue_22892.png";
|
||||
const std::string root = "qrcode/";
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat img = imread(image_path);
|
||||
|
||||
QRCodeDetector qrcode;
|
||||
std::vector<Point> corners;
|
||||
Mat straight_code;
|
||||
qrcode.detectAndDecodeCurved(img, corners, straight_code);
|
||||
EXPECT_EQ(corners.size(), 4U);
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/27783
|
||||
TEST(Objdetect_QRCode_detect, detect_regression_27783)
|
||||
{
|
||||
const std::string name_current_image = "9_qrcodes.jpg";
|
||||
const std::string root = "qrcode/multiple/";
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path);
|
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path;
|
||||
|
||||
std::vector<std::string> info;
|
||||
std::vector<Point> corners;
|
||||
|
||||
{
|
||||
// If default, we can decode 9 QRs.
|
||||
QRCodeDetector qrcode;
|
||||
EXPECT_TRUE(qrcode.detectAndDecodeMulti(src, info, corners));
|
||||
ASSERT_EQ(info.size(), 9UL);
|
||||
ASSERT_EQ(corners.size(), 36UL);
|
||||
}
|
||||
|
||||
{
|
||||
// If setEpsX is too small, we can decode no QRs.
|
||||
QRCodeDetector qrcode;
|
||||
qrcode.setEpsX(0.01);
|
||||
EXPECT_FALSE(qrcode.detectAndDecodeMulti(src, info, corners));
|
||||
}
|
||||
|
||||
{
|
||||
// If setEpsY is too small, we can decode no QRs.
|
||||
QRCodeDetector qrcode;
|
||||
qrcode.setEpsY(0.01);
|
||||
EXPECT_FALSE(qrcode.detectAndDecodeMulti(src, info, corners));
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,630 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
std::string encode_qrcode_images_name[] = {
|
||||
"version1_mode1.png", "version1_mode2.png", "version1_mode4.png",
|
||||
"version2_mode1.png", "version2_mode2.png", "version2_mode4.png",
|
||||
"version3_mode2.png", "version3_mode4.png",
|
||||
"version4_mode4.png"
|
||||
};
|
||||
|
||||
std::string encode_qrcode_eci_images_name[] = {
|
||||
"version1_mode7.png",
|
||||
"version2_mode7.png",
|
||||
"version3_mode7.png",
|
||||
"version4_mode7.png",
|
||||
"version5_mode7.png"
|
||||
};
|
||||
|
||||
const Size fixed_size = Size(200, 200);
|
||||
const float border_width = 2.0;
|
||||
|
||||
int establishCapacity(QRCodeEncoder::EncodeMode mode, int version, int capacity)
|
||||
{
|
||||
int result = 0;
|
||||
capacity *= 8;
|
||||
capacity -= 4;
|
||||
switch (mode)
|
||||
{
|
||||
case QRCodeEncoder::MODE_NUMERIC:
|
||||
{
|
||||
if (version >= 10)
|
||||
capacity -= 12;
|
||||
else
|
||||
capacity -= 10;
|
||||
int tmp = capacity / 10;
|
||||
result = tmp * 3;
|
||||
if (tmp * 10 + 7 <= capacity)
|
||||
result += 2;
|
||||
else if (tmp * 10 + 4 <= capacity)
|
||||
result += 1;
|
||||
break;
|
||||
}
|
||||
case QRCodeEncoder::MODE_ALPHANUMERIC:
|
||||
{
|
||||
if (version < 10)
|
||||
capacity -= 9;
|
||||
else
|
||||
capacity -= 13;
|
||||
int tmp = capacity / 11;
|
||||
result = tmp * 2;
|
||||
if (tmp * 11 + 6 <= capacity)
|
||||
result++;
|
||||
break;
|
||||
}
|
||||
case QRCodeEncoder::MODE_BYTE:
|
||||
{
|
||||
if (version > 9)
|
||||
capacity -= 16;
|
||||
else
|
||||
capacity -= 8;
|
||||
result = capacity / 8;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// #define UPDATE_TEST_DATA
|
||||
#ifdef UPDATE_TEST_DATA
|
||||
|
||||
TEST(Objdetect_QRCode_Encode, generate_test_data)
|
||||
{
|
||||
const std::string root = "qrcode/encode";
|
||||
const std::string dataset_config = findDataFile(root + "/" + "dataset_config.json");
|
||||
FileStorage file_config(dataset_config, FileStorage::WRITE);
|
||||
|
||||
file_config << "test_images" << "[";
|
||||
size_t images_count = sizeof(encode_qrcode_images_name) / sizeof(encode_qrcode_images_name[0]);
|
||||
for (size_t i = 0; i < images_count; i++)
|
||||
{
|
||||
file_config << "{:" << "image_name" << encode_qrcode_images_name[i];
|
||||
std::string image_path = findDataFile(root + "/" + encode_qrcode_images_name[i]);
|
||||
|
||||
Mat src = imread(image_path, IMREAD_GRAYSCALE);
|
||||
Mat straight_barcode;
|
||||
EXPECT_TRUE(!src.empty()) << "Can't read image: " << image_path;
|
||||
|
||||
std::vector<Point2f> corners(4);
|
||||
corners[0] = Point2f(border_width, border_width);
|
||||
corners[1] = Point2f(qrcode.cols * 1.0f - border_width, border_width);
|
||||
corners[2] = Point2f(qrcode.cols * 1.0f - border_width, qrcode.rows * 1.0f - border_width);
|
||||
corners[3] = Point2f(border_width, qrcode.rows * 1.0f - border_width);
|
||||
|
||||
Mat resized_src;
|
||||
resize(qrcode, resized_src, fixed_size, 0, 0, INTER_AREA);
|
||||
float width_ratio = resized_src.cols * 1.0f / qrcode.cols;
|
||||
float height_ratio = resized_src.rows * 1.0f / qrcode.rows;
|
||||
for(size_t j = 0; j < corners.size(); j++)
|
||||
{
|
||||
corners[j].x = corners[j].x * width_ratio;
|
||||
corners[j].y = corners[j].y * height_ratio;
|
||||
}
|
||||
|
||||
std::string decoded_info = "";
|
||||
EXPECT_TRUE(decodeQRCode(resized_src, corners, decoded_info, straight_barcode)) << "The QR code cannot be decoded: " << image_path;
|
||||
file_config << "info" << decoded_info;
|
||||
file_config << "}";
|
||||
}
|
||||
file_config << "]";
|
||||
file_config.release();
|
||||
}
|
||||
#else
|
||||
|
||||
typedef testing::TestWithParam< std::string > Objdetect_QRCode_Encode;
|
||||
TEST_P(Objdetect_QRCode_Encode, regression) {
|
||||
const int pixels_error = 3;
|
||||
const std::string name_current_image = GetParam();
|
||||
const std::string root = "qrcode/encode";
|
||||
|
||||
std::string image_path = findDataFile(root + "/" + name_current_image);
|
||||
const std::string dataset_config = findDataFile(root + "/" + "dataset_config.json");
|
||||
FileStorage file_config(dataset_config, FileStorage::READ);
|
||||
|
||||
ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config;
|
||||
{
|
||||
FileNode images_list = file_config["test_images"];
|
||||
size_t images_count = static_cast<size_t>(images_list.size());
|
||||
ASSERT_GT(images_count, 0u) << "Can't find validation data entries in 'test_images': " << dataset_config;
|
||||
|
||||
for (size_t index = 0; index < images_count; index++)
|
||||
{
|
||||
FileNode config = images_list[(int)index];
|
||||
std::string name_test_image = config["image_name"];
|
||||
if (name_test_image == name_current_image)
|
||||
{
|
||||
std::string original_info = config["info"];
|
||||
Ptr<QRCodeEncoder> encoder = QRCodeEncoder::create();
|
||||
Mat result;
|
||||
encoder->encode(original_info, result);
|
||||
EXPECT_FALSE(result.empty()) << "Can't generate QR code image";
|
||||
|
||||
Mat src = imread(image_path, IMREAD_GRAYSCALE);
|
||||
Mat straight_barcode;
|
||||
EXPECT_TRUE(!src.empty()) << "Can't read image: " << image_path;
|
||||
|
||||
double diff_norm = cvtest::norm(result - src, NORM_L1);
|
||||
EXPECT_NEAR(diff_norm, 0.0, pixels_error) << "The generated QRcode is not same as test data. The difference: " << diff_norm;
|
||||
|
||||
return; // done
|
||||
}
|
||||
}
|
||||
FAIL() << "Not found results in config file:" << dataset_config
|
||||
<< "\nRe-run tests with enabled UPDATE_ENCODE_TEST_DATA macro to update test data.";
|
||||
}
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam< std::string > Objdetect_QRCode_Encode_ECI;
|
||||
TEST_P(Objdetect_QRCode_Encode_ECI, regression) {
|
||||
const int pixels_error = 3;
|
||||
const std::string name_current_image = GetParam();
|
||||
const std::string root = "qrcode/encode";
|
||||
|
||||
std::string image_path = findDataFile(root + "/" + name_current_image);
|
||||
const std::string dataset_config = findDataFile(root + "/" + "dataset_config.json");
|
||||
FileStorage file_config(dataset_config, FileStorage::READ);
|
||||
|
||||
ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config;
|
||||
{
|
||||
FileNode images_list = file_config["test_images"];
|
||||
size_t images_count = static_cast<size_t>(images_list.size());
|
||||
ASSERT_GT(images_count, 0u) << "Can't find validation data entries in 'test_images': " << dataset_config;
|
||||
QRCodeEncoder::Params params;
|
||||
params.mode = QRCodeEncoder::MODE_ECI;
|
||||
|
||||
for (size_t index = 0; index < images_count; index++)
|
||||
{
|
||||
FileNode config = images_list[(int)index];
|
||||
std::string name_test_image = config["image_name"];
|
||||
if (name_test_image == name_current_image)
|
||||
{
|
||||
std::string original_info = config["info"];
|
||||
Mat result;
|
||||
Ptr<QRCodeEncoder> encoder = QRCodeEncoder::create(params);
|
||||
encoder->encode(original_info, result);
|
||||
EXPECT_FALSE(result.empty()) << "Can't generate QR code image";
|
||||
|
||||
Mat src = imread(image_path, IMREAD_GRAYSCALE);
|
||||
Mat straight_barcode;
|
||||
EXPECT_TRUE(!src.empty()) << "Can't read image: " << image_path;
|
||||
|
||||
double diff_norm = cvtest::norm(result - src, NORM_L1);
|
||||
EXPECT_NEAR(diff_norm, 0.0, pixels_error) << "The generated QRcode is not same as test data. The difference: " << diff_norm;
|
||||
|
||||
return; // done
|
||||
}
|
||||
}
|
||||
FAIL() << "Not found results in config file:" << dataset_config
|
||||
<< "\nRe-run tests with enabled UPDATE_ENCODE_TEST_DATA macro to update test data.";
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Encode, testing::ValuesIn(encode_qrcode_images_name));
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Encode_ECI, testing::ValuesIn(encode_qrcode_eci_images_name));
|
||||
|
||||
TEST(Objdetect_QRCode_Encode_Decode, regression)
|
||||
{
|
||||
const std::string root = "qrcode/decode_encode";
|
||||
const int min_version = 1;
|
||||
const int test_max_version = 5;
|
||||
const int max_ec_level = 3;
|
||||
const std::string dataset_config = findDataFile(root + "/" + "symbol_sets.json");
|
||||
const std::string version_config = findDataFile(root + "/" + "capacity.json");
|
||||
|
||||
FileStorage file_config(dataset_config, FileStorage::READ);
|
||||
FileStorage capacity_config(version_config, FileStorage::READ);
|
||||
ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config;
|
||||
ASSERT_TRUE(capacity_config.isOpened()) << "Can't read validation data: " << version_config;
|
||||
|
||||
FileNode mode_list = file_config["symbols_sets"];
|
||||
FileNode capacity_list = capacity_config["version_ecc_capacity"];
|
||||
|
||||
size_t mode_count = static_cast<size_t>(mode_list.size());
|
||||
ASSERT_GT(mode_count, 0u) << "Can't find validation data entries in 'test_images': " << dataset_config;
|
||||
|
||||
const int testing_modes = 3;
|
||||
QRCodeEncoder::EncodeMode modes[testing_modes] = {
|
||||
QRCodeEncoder::MODE_NUMERIC,
|
||||
QRCodeEncoder::MODE_ALPHANUMERIC,
|
||||
QRCodeEncoder::MODE_BYTE
|
||||
};
|
||||
|
||||
for (int i = 0; i < testing_modes; i++)
|
||||
{
|
||||
QRCodeEncoder::EncodeMode mode = modes[i];
|
||||
FileNode config = mode_list[i];
|
||||
|
||||
std::string symbol_set = config["symbols_set"];
|
||||
|
||||
for(int version = min_version; version <= test_max_version; version++)
|
||||
{
|
||||
FileNode capa_config = capacity_list[version - 1];
|
||||
for(int level = 0; level <= max_ec_level; level++)
|
||||
{
|
||||
const int cur_capacity = capa_config["ecc_level"][level];
|
||||
|
||||
int true_capacity = establishCapacity(mode, version, cur_capacity);
|
||||
|
||||
std::string input_info = symbol_set;
|
||||
std::mt19937 rand_gen {1};
|
||||
std::shuffle(input_info.begin(), input_info.end(), rand_gen);
|
||||
int count = 0;
|
||||
if((int)input_info.length() > true_capacity)
|
||||
{
|
||||
input_info = input_info.substr(0, true_capacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
while ((int)input_info.length() != true_capacity)
|
||||
{
|
||||
input_info += input_info.substr(count%(int)input_info.length(), 1);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
QRCodeEncoder::Params params;
|
||||
params.version = version;
|
||||
params.correction_level = static_cast<QRCodeEncoder::CorrectionLevel>(level);
|
||||
params.mode = mode;
|
||||
Ptr<QRCodeEncoder> encoder = QRCodeEncoder::create(params);
|
||||
Mat qrcode;
|
||||
encoder->encode(input_info, qrcode);
|
||||
EXPECT_TRUE(!qrcode.empty()) << "Can't generate this QR image (" << "mode: " << (int)mode <<
|
||||
" version: "<< version <<" error correction level: "<< (int)level <<")";
|
||||
|
||||
std::vector<Point2f> corners(4);
|
||||
corners[0] = Point2f(border_width, border_width);
|
||||
corners[1] = Point2f(qrcode.cols * 1.0f - border_width, border_width);
|
||||
corners[2] = Point2f(qrcode.cols * 1.0f - border_width, qrcode.rows * 1.0f - border_width);
|
||||
corners[3] = Point2f(border_width, qrcode.rows * 1.0f - border_width);
|
||||
|
||||
Mat resized_src;
|
||||
resize(qrcode, resized_src, fixed_size, 0, 0, INTER_AREA);
|
||||
float width_ratio = resized_src.cols * 1.0f / qrcode.cols;
|
||||
float height_ratio = resized_src.rows * 1.0f / qrcode.rows;
|
||||
for(size_t k = 0; k < corners.size(); k++)
|
||||
{
|
||||
corners[k].x = corners[k].x * width_ratio;
|
||||
corners[k].y = corners[k].y * height_ratio;
|
||||
}
|
||||
|
||||
Mat straight_barcode;
|
||||
std::string output_info = QRCodeDetector().decode(resized_src, corners, straight_barcode);
|
||||
EXPECT_FALSE(output_info.empty())
|
||||
<< "The generated QRcode cannot be decoded." << " Mode: " << (int)mode
|
||||
<< " version: " << version << " error correction level: " << (int)level;
|
||||
EXPECT_EQ(input_info, output_info) << "The generated QRcode is not same as test data." << " Mode: " << (int)mode <<
|
||||
" version: " << version << " error correction level: " << (int)level;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST(Objdetect_QRCode_Encode_Kanji, regression)
|
||||
{
|
||||
QRCodeEncoder::Params params;
|
||||
params.mode = QRCodeEncoder::MODE_KANJI;
|
||||
|
||||
Mat qrcode;
|
||||
|
||||
const int testing_versions = 3;
|
||||
std::string input_infos[testing_versions] = {"\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd\x90\xa2\x8a\x45", // "Hello World" in Japanese
|
||||
"\x82\xa8\x95\xa0\x82\xaa\x8b\xf3\x82\xa2\x82\xc4\x82\xa2\x82\xdc\x82\xb7", // "I am hungry" in Japanese
|
||||
"\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd\x81\x41\x8e\x84\x82\xcd\x8f\xad\x82\xb5\x93\xfa\x96\x7b\x8c\xea\x82\xf0\x98\x62\x82\xb5\x82\xdc\x82\xb7" // "Hello, I speak a little Japanese" in Japanese
|
||||
};
|
||||
|
||||
for (int i = 0; i < testing_versions; i++)
|
||||
{
|
||||
std::string input_info = input_infos[i];
|
||||
Ptr<QRCodeEncoder> encoder = QRCodeEncoder::create(params);
|
||||
encoder->encode(input_info, qrcode);
|
||||
|
||||
std::vector<Point2f> corners(4);
|
||||
corners[0] = Point2f(border_width, border_width);
|
||||
corners[1] = Point2f(qrcode.cols * 1.0f - border_width, border_width);
|
||||
corners[2] = Point2f(qrcode.cols * 1.0f - border_width, qrcode.rows * 1.0f - border_width);
|
||||
corners[3] = Point2f(border_width, qrcode.rows * 1.0f - border_width);
|
||||
|
||||
Mat resized_src;
|
||||
resize(qrcode, resized_src, fixed_size, 0, 0, INTER_AREA);
|
||||
float width_ratio = resized_src.cols * 1.0f / qrcode.cols;
|
||||
float height_ratio = resized_src.rows * 1.0f / qrcode.rows;
|
||||
for(size_t j = 0; j < corners.size(); j++)
|
||||
{
|
||||
corners[j].x = corners[j].x * width_ratio;
|
||||
corners[j].y = corners[j].y * height_ratio;
|
||||
}
|
||||
|
||||
Mat straight_barcode;
|
||||
QRCodeDetector detector;
|
||||
std::string decoded_info = detector.decode(resized_src, corners, straight_barcode);
|
||||
EXPECT_FALSE(decoded_info.empty()) << "The generated QRcode cannot be decoded.";
|
||||
EXPECT_EQ(input_info, decoded_info);
|
||||
EXPECT_EQ(detector.getEncoding(), QRCodeEncoder::ECIEncodings::ECI_SHIFT_JIS);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Objdetect_QRCode_Encode_Decode_Structured_Append, regression)
|
||||
{
|
||||
// disabled since QR decoder probably doesn't support structured append mode qr codes
|
||||
const std::string root = "qrcode/decode_encode";
|
||||
const std::string dataset_config = findDataFile(root + "/" + "symbol_sets.json");
|
||||
const std::string version_config = findDataFile(root + "/" + "capacity.json");
|
||||
|
||||
FileStorage file_config(dataset_config, FileStorage::READ);
|
||||
ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config;
|
||||
|
||||
FileNode mode_list = file_config["symbols_sets"];
|
||||
|
||||
size_t mode_count = static_cast<size_t>(mode_list.size());
|
||||
ASSERT_GT(mode_count, 0u) << "Can't find validation data entries in 'test_images': " << dataset_config;
|
||||
|
||||
int modes[] = {1, 2, 4};
|
||||
const int min_stuctures_num = 2;
|
||||
const int max_stuctures_num = 5;
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
int mode = modes[i];
|
||||
FileNode config = mode_list[i];
|
||||
|
||||
std::string symbol_set = config["symbols_set"];
|
||||
|
||||
std::string input_info = symbol_set;
|
||||
std::mt19937 rand_gen {1};
|
||||
std::shuffle(input_info.begin(), input_info.end(), rand_gen);
|
||||
for (int j = min_stuctures_num; j < max_stuctures_num; j++)
|
||||
{
|
||||
QRCodeEncoder::Params params;
|
||||
params.structure_number = j;
|
||||
Ptr<QRCodeEncoder> encoder = QRCodeEncoder::create(params);
|
||||
vector<Mat> qrcodes;
|
||||
encoder->encodeStructuredAppend(input_info, qrcodes);
|
||||
EXPECT_TRUE(!qrcodes.empty()) << "Can't generate this QR images";
|
||||
CV_CheckEQ(qrcodes.size(), (size_t)j, "Number of QR codes");
|
||||
|
||||
std::vector<Point2f> corners(4 * qrcodes.size());
|
||||
for (size_t k = 0; k < qrcodes.size(); k++)
|
||||
{
|
||||
Mat qrcode = qrcodes[k];
|
||||
corners[4 * k] = Point2f(border_width, border_width);
|
||||
corners[4 * k + 1] = Point2f(qrcode.cols * 1.0f - border_width, border_width);
|
||||
corners[4 * k + 2] = Point2f(qrcode.cols * 1.0f - border_width, qrcode.rows * 1.0f - border_width);
|
||||
corners[4 * k + 3] = Point2f(border_width, qrcode.rows * 1.0f - border_width);
|
||||
|
||||
float width_ratio = fixed_size.width * 1.0f / qrcode.cols;
|
||||
float height_ratio = fixed_size.height * 1.0f / qrcode.rows;
|
||||
resize(qrcode, qrcodes[k], fixed_size, 0, 0, INTER_AREA);
|
||||
|
||||
for (size_t ki = 0; ki < 4; ki++)
|
||||
{
|
||||
corners[4 * k + ki].x = corners[4 * k + ki].x * width_ratio + fixed_size.width * k;
|
||||
corners[4 * k + ki].y = corners[4 * k + ki].y * height_ratio;
|
||||
}
|
||||
}
|
||||
|
||||
Mat resized_src;
|
||||
hconcat(qrcodes, resized_src);
|
||||
|
||||
std::vector<cv::String> decoded_info;
|
||||
cv::String output_info;
|
||||
EXPECT_TRUE(QRCodeDetector().decodeMulti(resized_src, corners, decoded_info));
|
||||
for (size_t k = 0; k < decoded_info.size(); ++k)
|
||||
{
|
||||
if (!decoded_info[k].empty())
|
||||
output_info = decoded_info[k];
|
||||
}
|
||||
EXPECT_FALSE(output_info.empty())
|
||||
<< "The generated QRcode cannot be decoded." << " Mode: " << modes[i]
|
||||
<< " structures number: " << j;
|
||||
|
||||
EXPECT_EQ(input_info, output_info) << "The generated QRcode is not same as test data." << " Mode: " << mode <<
|
||||
" structures number: " << j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // UPDATE_QRCODE_TEST_DATA
|
||||
|
||||
CV_ENUM(EncodeModes, QRCodeEncoder::EncodeMode::MODE_NUMERIC,
|
||||
QRCodeEncoder::EncodeMode::MODE_ALPHANUMERIC,
|
||||
QRCodeEncoder::EncodeMode::MODE_BYTE)
|
||||
|
||||
typedef ::testing::TestWithParam<EncodeModes> Objdetect_QRCode_Encode_Decode_Structured_Append_Parameterized;
|
||||
TEST_P(Objdetect_QRCode_Encode_Decode_Structured_Append_Parameterized, regression_22205)
|
||||
{
|
||||
const std::string input_data = "the quick brown fox jumps over the lazy dog";
|
||||
|
||||
std::vector<cv::Mat> result_qrcodes;
|
||||
|
||||
cv::QRCodeEncoder::Params params;
|
||||
int encode_mode = GetParam();
|
||||
params.mode = static_cast<cv::QRCodeEncoder::EncodeMode>(encode_mode);
|
||||
|
||||
for(size_t struct_num = 2; struct_num < 5; ++struct_num)
|
||||
{
|
||||
params.structure_number = static_cast<int>(struct_num);
|
||||
cv::Ptr<cv::QRCodeEncoder> encoder = cv::QRCodeEncoder::create(params);
|
||||
encoder->encodeStructuredAppend(input_data, result_qrcodes);
|
||||
EXPECT_EQ(result_qrcodes.size(), struct_num) << "The number of QR Codes requested is not equal"<<
|
||||
"to the one returned";
|
||||
}
|
||||
}
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Encode_Decode_Structured_Append_Parameterized, EncodeModes::all());
|
||||
|
||||
TEST(Objdetect_QRCode_Encode_Decode, regression_issue22029)
|
||||
{
|
||||
const cv::String msg = "OpenCV";
|
||||
const int min_version = 1;
|
||||
const int max_version = 40;
|
||||
|
||||
for ( int v = min_version ; v <= max_version ; v++ )
|
||||
{
|
||||
SCOPED_TRACE(cv::format("version=%d",v));
|
||||
|
||||
Mat qrimg;
|
||||
QRCodeEncoder::Params params;
|
||||
params.version = v;
|
||||
Ptr<QRCodeEncoder> qrcode_enc = cv::QRCodeEncoder::create(params);
|
||||
qrcode_enc->encode(msg, qrimg);
|
||||
|
||||
const int white_margin = 2;
|
||||
const int finder_width = 7;
|
||||
|
||||
const int timing_pos = white_margin + 6;
|
||||
int i;
|
||||
|
||||
// Horizontal Check
|
||||
// (1) White margin(Left)
|
||||
for(i = 0; i < white_margin ; i++ )
|
||||
{
|
||||
ASSERT_EQ((uint8_t)255, qrimg.at<uint8_t>(i, timing_pos)) << "i=" << i;
|
||||
}
|
||||
// (2) Finder pattern(Left)
|
||||
for( ; i < white_margin + finder_width ; i++ )
|
||||
{
|
||||
ASSERT_EQ((uint8_t)0, qrimg.at<uint8_t>(i, timing_pos)) << "i=" << i;
|
||||
}
|
||||
// (3) Timing pattern
|
||||
for( ; i < qrimg.rows - finder_width - white_margin; i++ )
|
||||
{
|
||||
ASSERT_EQ((uint8_t)(i % 2 == 0)?0:255, qrimg.at<uint8_t>(i, timing_pos)) << "i=" << i;
|
||||
}
|
||||
// (4) Finder pattern(Right)
|
||||
for( ; i < qrimg.rows - white_margin; i++ )
|
||||
{
|
||||
ASSERT_EQ((uint8_t)0, qrimg.at<uint8_t>(i, timing_pos)) << "i=" << i;
|
||||
}
|
||||
// (5) White margin(Right)
|
||||
for( ; i < qrimg.rows ; i++ )
|
||||
{
|
||||
ASSERT_EQ((uint8_t)255, qrimg.at<uint8_t>(i, timing_pos)) << "i=" << i;
|
||||
}
|
||||
|
||||
// Vertical Check
|
||||
// (1) White margin(Top)
|
||||
for(i = 0; i < white_margin ; i++ )
|
||||
{
|
||||
ASSERT_EQ((uint8_t)255, qrimg.at<uint8_t>(timing_pos, i)) << "i=" << i;
|
||||
}
|
||||
// (2) Finder pattern(Top)
|
||||
for( ; i < white_margin + finder_width ; i++ )
|
||||
{
|
||||
ASSERT_EQ((uint8_t)0, qrimg.at<uint8_t>(timing_pos, i)) << "i=" << i;
|
||||
}
|
||||
// (3) Timing pattern
|
||||
for( ; i < qrimg.rows - finder_width - white_margin; i++ )
|
||||
{
|
||||
ASSERT_EQ((uint8_t)(i % 2 == 0)?0:255, qrimg.at<uint8_t>(timing_pos, i)) << "i=" << i;
|
||||
}
|
||||
// (4) Finder pattern(Bottom)
|
||||
for( ; i < qrimg.rows - white_margin; i++ )
|
||||
{
|
||||
ASSERT_EQ((uint8_t)0, qrimg.at<uint8_t>(timing_pos, i)) << "i=" << i;
|
||||
}
|
||||
// (5) White margin(Bottom)
|
||||
for( ; i < qrimg.rows ; i++ )
|
||||
{
|
||||
ASSERT_EQ((uint8_t)255, qrimg.at<uint8_t>(timing_pos, i)) << "i=" << i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This test reproduces issue https://github.com/opencv/opencv/issues/24366 only in a loop
|
||||
TEST(Objdetect_QRCode_Encode_Decode, auto_version_pick)
|
||||
{
|
||||
cv::QRCodeEncoder::Params params;
|
||||
params.correction_level = cv::QRCodeEncoder::CORRECT_LEVEL_L;
|
||||
params.mode = cv::QRCodeEncoder::EncodeMode::MODE_AUTO;
|
||||
|
||||
cv::Ptr<cv::QRCodeEncoder> encoder = cv::QRCodeEncoder::create(params);
|
||||
|
||||
for (int len = 1; len < 19; len++) {
|
||||
std::string input;
|
||||
input.resize(len);
|
||||
cv::randu(Mat(1, len, CV_8U, &input[0]), 'a', 'z' + 1);
|
||||
cv::Mat qrcode;
|
||||
encoder->encode(input, qrcode);
|
||||
}
|
||||
}
|
||||
|
||||
// Test two QR codes which error correction procedure requires more number of
|
||||
// syndroms that described in the ISO/IEC 18004
|
||||
typedef testing::TestWithParam<std::pair<std::string, std::string>> Objdetect_QRCode_decoding;
|
||||
TEST_P(Objdetect_QRCode_decoding, error_correction)
|
||||
{
|
||||
const std::string filename = get<0>(GetParam());
|
||||
const std::string expected = get<1>(GetParam());
|
||||
|
||||
QRCodeDetector qrcode;
|
||||
cv::String decoded_msg;
|
||||
Mat src = cv::imread(findDataFile("qrcode/" + filename), IMREAD_GRAYSCALE);
|
||||
|
||||
std::vector<Point2f> corners(4);
|
||||
corners[0] = Point2f(0, 0);
|
||||
corners[1] = Point2f(src.cols * 1.0f, 0);
|
||||
corners[2] = Point2f(src.cols * 1.0f, src.rows * 1.0f);
|
||||
corners[3] = Point2f(0, src.rows * 1.0f);
|
||||
|
||||
Mat resized_src;
|
||||
resize(src, resized_src, fixed_size, 0, 0, INTER_AREA);
|
||||
float width_ratio = resized_src.cols * 1.0f / src.cols;
|
||||
float height_ratio = resized_src.rows * 1.0f / src.rows;
|
||||
for(size_t m = 0; m < corners.size(); m++)
|
||||
{
|
||||
corners[m].x = corners[m].x * width_ratio;
|
||||
corners[m].y = corners[m].y * height_ratio;
|
||||
}
|
||||
|
||||
Mat straight_barcode;
|
||||
EXPECT_NO_THROW(decoded_msg = qrcode.decode(resized_src, corners, straight_barcode));
|
||||
ASSERT_FALSE(straight_barcode.empty()) << "Can't decode qrimage " << filename;
|
||||
EXPECT_EQ(expected, decoded_msg);
|
||||
}
|
||||
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_decoding, testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
|
||||
{"err_correct_1M.png", "New"},
|
||||
{"err_correct_2L.png", "Version 2 QR Code Test Image"},
|
||||
}));
|
||||
|
||||
TEST(Objdetect_QRCode_Encode_Decode_Long_Text, regression_issue27183)
|
||||
{
|
||||
const int len = 135;
|
||||
Ptr<QRCodeEncoder> encoder = QRCodeEncoder::create();
|
||||
|
||||
std::string input;
|
||||
input.resize(len);
|
||||
cv::randu(Mat(1, len, CV_8U, &input[0]), 'a', 'z' + 1);
|
||||
Mat qrcode;
|
||||
encoder->encode(input, qrcode);
|
||||
|
||||
std::vector<Point2f> corners(4);
|
||||
corners[0] = Point2f(border_width, border_width);
|
||||
corners[1] = Point2f(qrcode.cols * 1.0f - border_width, border_width);
|
||||
corners[2] = Point2f(qrcode.cols * 1.0f - border_width, qrcode.rows * 1.0f - border_width);
|
||||
corners[3] = Point2f(border_width, qrcode.rows * 1.0f - border_width);
|
||||
|
||||
Mat resized_src;
|
||||
resize(qrcode, resized_src, fixed_size, 0, 0, INTER_AREA);
|
||||
float width_ratio = resized_src.cols * 1.0f / qrcode.cols;
|
||||
float height_ratio = resized_src.rows * 1.0f / qrcode.rows;
|
||||
for(size_t j = 0; j < corners.size(); j++)
|
||||
{
|
||||
corners[j].x = corners[j].x * width_ratio;
|
||||
corners[j].y = corners[j].y * height_ratio;
|
||||
}
|
||||
|
||||
QRCodeDetector detector;
|
||||
cv::String decoded_msg;
|
||||
Mat straight_barcode;
|
||||
EXPECT_NO_THROW(decoded_msg = detector.decode(resized_src, corners, straight_barcode));
|
||||
ASSERT_FALSE(straight_barcode.empty());
|
||||
EXPECT_EQ(input, decoded_msg);
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
Reference in New Issue
Block a user