mirror of
https://github.com/opencv/opencv.git
synced 2026-07-31 00:03:03 +04:00
Merge branch 4.x
This commit is contained in:
@@ -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 float offset) {
|
||||
vector<Point3f> axis;
|
||||
axis.push_back(Point3f(offset, offset, 0.f));
|
||||
axis.push_back(Point3f(length+offset, offset, 0.f));
|
||||
axis.push_back(Point3f(offset, length+offset, 0.f));
|
||||
axis.push_back(Point3f(offset, offset, 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/3d.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 float offset = 0.f);
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,704 @@
|
||||
// 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"
|
||||
#include "opencv2/3d.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
/**
|
||||
* @brief Draw 2D synthetic markers and detect them
|
||||
*/
|
||||
class CV_ArucoDetectionSimple : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_ArucoDetectionSimple();
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
CV_ArucoDetectionSimple::CV_ArucoDetectionSimple() {}
|
||||
|
||||
|
||||
void CV_ArucoDetectionSimple::run(int) {
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250));
|
||||
|
||||
// 20 images
|
||||
for(int i = 0; i < 20; i++) {
|
||||
|
||||
const int markerSidePixels = 100;
|
||||
int imageSize = markerSidePixels * 2 + 3 * (markerSidePixels / 2);
|
||||
|
||||
// draw synthetic image and store marker corners and ids
|
||||
vector<vector<Point2f> > groundTruthCorners;
|
||||
vector<int> groundTruthIds;
|
||||
Mat img = Mat(imageSize, imageSize, CV_8UC1, Scalar::all(255));
|
||||
for(int y = 0; y < 2; y++) {
|
||||
for(int x = 0; x < 2; x++) {
|
||||
Mat marker;
|
||||
int id = i * 4 + y * 2 + x;
|
||||
aruco::generateImageMarker(detector.getDictionary(), id, markerSidePixels, marker);
|
||||
Point2f firstCorner =
|
||||
Point2f(markerSidePixels / 2.f + x * (1.5f * markerSidePixels),
|
||||
markerSidePixels / 2.f + y * (1.5f * markerSidePixels));
|
||||
Mat aux = img.colRange((int)firstCorner.x, (int)firstCorner.x + markerSidePixels)
|
||||
.rowRange((int)firstCorner.y, (int)firstCorner.y + markerSidePixels);
|
||||
marker.copyTo(aux);
|
||||
groundTruthIds.push_back(id);
|
||||
groundTruthCorners.push_back(vector<Point2f>());
|
||||
groundTruthCorners.back().push_back(firstCorner);
|
||||
groundTruthCorners.back().push_back(firstCorner + Point2f(markerSidePixels - 1, 0));
|
||||
groundTruthCorners.back().push_back(
|
||||
firstCorner + Point2f(markerSidePixels - 1, markerSidePixels - 1));
|
||||
groundTruthCorners.back().push_back(firstCorner + Point2f(0, markerSidePixels - 1));
|
||||
}
|
||||
}
|
||||
if(i % 2 == 1) img.convertTo(img, CV_8UC3);
|
||||
|
||||
// detect markers
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
|
||||
detector.detectMarkers(img, corners, ids);
|
||||
|
||||
// check detection results
|
||||
for(unsigned int m = 0; m < groundTruthIds.size(); m++) {
|
||||
int idx = -1;
|
||||
for(unsigned int k = 0; k < ids.size(); k++) {
|
||||
if(groundTruthIds[m] == ids[k]) {
|
||||
idx = (int)k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(idx == -1) {
|
||||
ts->printf(cvtest::TS::LOG, "Marker not detected");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
for(int c = 0; c < 4; c++) {
|
||||
double dist = cv::norm(groundTruthCorners[m][c] - corners[idx][c]); // TODO cvtest
|
||||
if(dist > 0.001) {
|
||||
ts->printf(cvtest::TS::LOG, "Incorrect marker corners position");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static double deg2rad(double deg) { return deg * CV_PI / 180.; }
|
||||
|
||||
/**
|
||||
* @brief Get rvec and tvec from yaw, pitch and distance
|
||||
*/
|
||||
static void getSyntheticRT(double yaw, double pitch, double distance, Mat &rvec, Mat &tvec) {
|
||||
|
||||
rvec = Mat(3, 1, CV_64FC1);
|
||||
tvec = Mat(3, 1, CV_64FC1);
|
||||
|
||||
// Rvec
|
||||
// first put the Z axis aiming to -X (like the camera axis system)
|
||||
Mat rotZ(3, 1, CV_64FC1);
|
||||
rotZ.ptr<double>(0)[0] = 0;
|
||||
rotZ.ptr<double>(0)[1] = 0;
|
||||
rotZ.ptr<double>(0)[2] = -0.5 * CV_PI;
|
||||
|
||||
Mat rotX(3, 1, CV_64FC1);
|
||||
rotX.ptr<double>(0)[0] = 0.5 * CV_PI;
|
||||
rotX.ptr<double>(0)[1] = 0;
|
||||
rotX.ptr<double>(0)[2] = 0;
|
||||
|
||||
Mat camRvec, camTvec;
|
||||
composeRT(rotZ, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotX, Mat(3, 1, CV_64FC1, Scalar::all(0)),
|
||||
camRvec, camTvec);
|
||||
|
||||
// now pitch and yaw angles
|
||||
Mat rotPitch(3, 1, CV_64FC1);
|
||||
rotPitch.ptr<double>(0)[0] = 0;
|
||||
rotPitch.ptr<double>(0)[1] = pitch;
|
||||
rotPitch.ptr<double>(0)[2] = 0;
|
||||
|
||||
Mat rotYaw(3, 1, CV_64FC1);
|
||||
rotYaw.ptr<double>(0)[0] = yaw;
|
||||
rotYaw.ptr<double>(0)[1] = 0;
|
||||
rotYaw.ptr<double>(0)[2] = 0;
|
||||
|
||||
composeRT(rotPitch, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotYaw,
|
||||
Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec);
|
||||
|
||||
// compose both rotations
|
||||
composeRT(camRvec, Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec,
|
||||
Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec);
|
||||
|
||||
// Tvec, just move in z (camera) direction the specific distance
|
||||
tvec.ptr<double>(0)[0] = 0.;
|
||||
tvec.ptr<double>(0)[1] = 0.;
|
||||
tvec.ptr<double>(0)[2] = distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a synthetic image of a marker with perspective
|
||||
*/
|
||||
static Mat projectMarker(const aruco::Dictionary &dictionary, int id, Mat cameraMatrix, double yaw,
|
||||
double pitch, double distance, Size imageSize, int markerBorder,
|
||||
vector<Point2f> &corners, int encloseMarker=0) {
|
||||
|
||||
// canonical image
|
||||
Mat marker, markerImg;
|
||||
const int markerSizePixels = 100;
|
||||
|
||||
aruco::generateImageMarker(dictionary, id, markerSizePixels, marker, markerBorder);
|
||||
marker.copyTo(markerImg);
|
||||
|
||||
if(encloseMarker){ //to enclose the marker
|
||||
int enclose = int(marker.rows/4);
|
||||
markerImg = Mat::zeros(marker.rows+(2*enclose), marker.cols+(enclose*2), CV_8UC1);
|
||||
|
||||
Mat field= markerImg.rowRange(int(enclose), int(markerImg.rows-enclose))
|
||||
.colRange(int(0), int(markerImg.cols));
|
||||
field.setTo(255);
|
||||
field= markerImg.rowRange(int(0), int(markerImg.rows))
|
||||
.colRange(int(enclose), int(markerImg.cols-enclose));
|
||||
field.setTo(255);
|
||||
|
||||
field = markerImg(Rect(enclose,enclose,marker.rows,marker.cols));
|
||||
marker.copyTo(field);
|
||||
}
|
||||
|
||||
// get rvec and tvec for the perspective
|
||||
Mat rvec, tvec;
|
||||
getSyntheticRT(yaw, pitch, distance, rvec, tvec);
|
||||
|
||||
const float markerLength = 0.05f;
|
||||
vector<Point3f> markerObjPoints;
|
||||
markerObjPoints.push_back(Point3f(-markerLength / 2.f, +markerLength / 2.f, 0));
|
||||
markerObjPoints.push_back(markerObjPoints[0] + Point3f(markerLength, 0, 0));
|
||||
markerObjPoints.push_back(markerObjPoints[0] + Point3f(markerLength, -markerLength, 0));
|
||||
markerObjPoints.push_back(markerObjPoints[0] + Point3f(0, -markerLength, 0));
|
||||
|
||||
// project markers and draw them
|
||||
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
|
||||
projectPoints(markerObjPoints, rvec, tvec, cameraMatrix, distCoeffs, corners);
|
||||
|
||||
vector<Point2f> originalCorners;
|
||||
originalCorners.push_back(Point2f(0+float(encloseMarker*markerSizePixels/4), 0+float(encloseMarker*markerSizePixels/4)));
|
||||
originalCorners.push_back(originalCorners[0]+Point2f((float)markerSizePixels, 0));
|
||||
originalCorners.push_back(originalCorners[0]+Point2f((float)markerSizePixels, (float)markerSizePixels));
|
||||
originalCorners.push_back(originalCorners[0]+Point2f(0, (float)markerSizePixels));
|
||||
|
||||
Mat transformation = getPerspectiveTransform(originalCorners, corners);
|
||||
|
||||
Mat img(imageSize, CV_8UC1, Scalar::all(255));
|
||||
Mat aux;
|
||||
const char borderValue = 127;
|
||||
warpPerspective(markerImg, aux, transformation, imageSize, 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);
|
||||
}
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
enum class ArucoAlgParams
|
||||
{
|
||||
USE_DEFAULT = 0,
|
||||
USE_APRILTAG=1, /// Detect marker candidates :: using AprilTag
|
||||
DETECT_INVERTED_MARKER, /// Check if there is a white marker
|
||||
USE_ARUCO3 /// Check if aruco3 should be used
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Draws markers in perspective and detect them
|
||||
*/
|
||||
class CV_ArucoDetectionPerspective : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_ArucoDetectionPerspective(ArucoAlgParams arucoAlgParam) : arucoAlgParams(arucoAlgParam) {}
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
ArucoAlgParams arucoAlgParams;
|
||||
};
|
||||
|
||||
|
||||
void CV_ArucoDetectionPerspective::run(int) {
|
||||
|
||||
int iter = 0;
|
||||
int szEnclosed = 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;
|
||||
aruco::DetectorParameters params;
|
||||
params.minDistanceToBorder = 1;
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250), params);
|
||||
|
||||
// detect from different positions
|
||||
for(double distance = 0.1; distance < 0.7; distance += 0.2) {
|
||||
for(int pitch = 0; pitch < 360; pitch += (distance == 0.1? 60:180)) {
|
||||
for(int yaw = 70; yaw <= 120; yaw += 40){
|
||||
int currentId = iter % 250;
|
||||
int markerBorder = iter % 2 + 1;
|
||||
iter++;
|
||||
vector<Point2f> groundTruthCorners;
|
||||
aruco::DetectorParameters detectorParameters = params;
|
||||
detectorParameters.markerBorderBits = markerBorder;
|
||||
|
||||
/// create synthetic image
|
||||
Mat img=
|
||||
projectMarker(detector.getDictionary(), currentId, cameraMatrix, deg2rad(yaw), deg2rad(pitch),
|
||||
distance, imgSize, markerBorder, groundTruthCorners, szEnclosed);
|
||||
// marker :: Inverted
|
||||
if(ArucoAlgParams::DETECT_INVERTED_MARKER == arucoAlgParams){
|
||||
img = ~img;
|
||||
detectorParameters.detectInvertedMarker = true;
|
||||
}
|
||||
|
||||
if(ArucoAlgParams::USE_APRILTAG == arucoAlgParams){
|
||||
detectorParameters.cornerRefinementMethod = aruco::CORNER_REFINE_APRILTAG;
|
||||
}
|
||||
|
||||
if (ArucoAlgParams::USE_ARUCO3 == arucoAlgParams) {
|
||||
detectorParameters.useAruco3Detection = true;
|
||||
detectorParameters.cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX;
|
||||
}
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
|
||||
// detect markers
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
detector.detectMarkers(img, corners, ids);
|
||||
|
||||
// check results
|
||||
if(ids.size() != 1 || (ids.size() == 1 && ids[0] != currentId)) {
|
||||
if(ids.size() != 1)
|
||||
ts->printf(cvtest::TS::LOG, "Incorrect number of detected markers");
|
||||
else
|
||||
ts->printf(cvtest::TS::LOG, "Incorrect marker id");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
for(int c = 0; c < 4; c++) {
|
||||
double dist = cv::norm(groundTruthCorners[c] - corners[0][c]); // TODO cvtest
|
||||
if(dist > 5) {
|
||||
ts->printf(cvtest::TS::LOG, "Incorrect marker corners position");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// change the state :: to detect an enclosed inverted marker
|
||||
if(ArucoAlgParams::DETECT_INVERTED_MARKER == arucoAlgParams && distance == 0.1){
|
||||
distance -= 0.1;
|
||||
szEnclosed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check max and min size in marker detection parameters
|
||||
*/
|
||||
class CV_ArucoDetectionMarkerSize : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_ArucoDetectionMarkerSize();
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
CV_ArucoDetectionMarkerSize::CV_ArucoDetectionMarkerSize() {}
|
||||
|
||||
|
||||
void CV_ArucoDetectionMarkerSize::run(int) {
|
||||
aruco::DetectorParameters params;
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250), params);
|
||||
int markerSide = 20;
|
||||
int imageSize = 200;
|
||||
|
||||
// 10 cases
|
||||
for(int i = 0; i < 10; i++) {
|
||||
Mat marker;
|
||||
int id = 10 + i * 20;
|
||||
|
||||
// create synthetic image
|
||||
Mat img = Mat(imageSize, imageSize, CV_8UC1, Scalar::all(255));
|
||||
aruco::generateImageMarker(detector.getDictionary(), id, markerSide, marker);
|
||||
Mat aux = img.colRange(30, 30 + markerSide).rowRange(50, 50 + markerSide);
|
||||
marker.copyTo(aux);
|
||||
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
|
||||
// set a invalid minMarkerPerimeterRate
|
||||
aruco::DetectorParameters detectorParameters = params;
|
||||
detectorParameters.minMarkerPerimeterRate = min(4., (4. * markerSide) / float(imageSize) + 0.1);
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
detector.detectMarkers(img, corners, ids);
|
||||
if(corners.size() != 0) {
|
||||
ts->printf(cvtest::TS::LOG, "Error in DetectorParameters::minMarkerPerimeterRate");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
|
||||
// set an valid minMarkerPerimeterRate
|
||||
detectorParameters = params;
|
||||
detectorParameters.minMarkerPerimeterRate = max(0., (4. * markerSide) / float(imageSize) - 0.1);
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
detector.detectMarkers(img, corners, ids);
|
||||
if(corners.size() != 1 || (corners.size() == 1 && ids[0] != id)) {
|
||||
ts->printf(cvtest::TS::LOG, "Error in DetectorParameters::minMarkerPerimeterRate");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
|
||||
// set a invalid maxMarkerPerimeterRate
|
||||
detectorParameters = params;
|
||||
detectorParameters.maxMarkerPerimeterRate = min(4., (4. * markerSide) / float(imageSize) - 0.1);
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
detector.detectMarkers(img, corners, ids);
|
||||
if(corners.size() != 0) {
|
||||
ts->printf(cvtest::TS::LOG, "Error in DetectorParameters::maxMarkerPerimeterRate");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
|
||||
// set an valid maxMarkerPerimeterRate
|
||||
detectorParameters = params;
|
||||
detectorParameters.maxMarkerPerimeterRate = max(0., (4. * markerSide) / float(imageSize) + 0.1);
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
detector.detectMarkers(img, corners, ids);
|
||||
if(corners.size() != 1 || (corners.size() == 1 && ids[0] != id)) {
|
||||
ts->printf(cvtest::TS::LOG, "Error in DetectorParameters::maxMarkerPerimeterRate");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check error correction in marker bits
|
||||
*/
|
||||
class CV_ArucoBitCorrection : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_ArucoBitCorrection();
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
CV_ArucoBitCorrection::CV_ArucoBitCorrection() {}
|
||||
|
||||
|
||||
void CV_ArucoBitCorrection::run(int) {
|
||||
|
||||
aruco::Dictionary dictionary1 = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
|
||||
aruco::Dictionary dictionary2 = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
|
||||
aruco::DetectorParameters params;
|
||||
aruco::ArucoDetector detector1(dictionary1, params);
|
||||
int markerSide = 50;
|
||||
int imageSize = 150;
|
||||
|
||||
// 10 markers
|
||||
for(int l = 0; l < 10; l++) {
|
||||
Mat marker;
|
||||
int id = 10 + l * 20;
|
||||
|
||||
Mat currentCodeBytes = dictionary1.bytesList.rowRange(id, id + 1);
|
||||
aruco::DetectorParameters detectorParameters = detector1.getDetectorParameters();
|
||||
// 5 valid cases
|
||||
for(int i = 0; i < 5; i++) {
|
||||
// how many bit errors (the error is low enough so it can be corrected)
|
||||
detectorParameters.errorCorrectionRate = 0.2 + i * 0.1;
|
||||
detector1.setDetectorParameters(detectorParameters);
|
||||
int errors =
|
||||
(int)std::floor(dictionary1.maxCorrectionBits * detector1.getDetectorParameters().errorCorrectionRate - 1.);
|
||||
|
||||
// create erroneous marker in currentCodeBits
|
||||
Mat currentCodeBits =
|
||||
aruco::Dictionary::getBitsFromByteList(currentCodeBytes, dictionary1.markerSize);
|
||||
for(int e = 0; e < errors; e++) {
|
||||
currentCodeBits.ptr<unsigned char>()[2 * e] =
|
||||
!currentCodeBits.ptr<unsigned char>()[2 * e];
|
||||
}
|
||||
|
||||
// add erroneous marker to dictionary2 in order to create the erroneous marker image
|
||||
Mat currentCodeBytesError = aruco::Dictionary::getByteListFromBits(currentCodeBits);
|
||||
currentCodeBytesError.copyTo(dictionary2.bytesList.rowRange(id, id + 1));
|
||||
Mat img = Mat(imageSize, imageSize, CV_8UC1, Scalar::all(255));
|
||||
dictionary2.generateImageMarker(id, markerSide, marker);
|
||||
Mat aux = img.colRange(30, 30 + markerSide).rowRange(50, 50 + markerSide);
|
||||
marker.copyTo(aux);
|
||||
|
||||
// try to detect using original dictionary
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
detector1.detectMarkers(img, corners, ids);
|
||||
if(corners.size() != 1 || (corners.size() == 1 && ids[0] != id)) {
|
||||
ts->printf(cvtest::TS::LOG, "Error in bit correction");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 5 invalid cases
|
||||
for(int i = 0; i < 5; i++) {
|
||||
// how many bit errors (the error is too high to be corrected)
|
||||
detectorParameters.errorCorrectionRate = 0.2 + i * 0.1;
|
||||
detector1.setDetectorParameters(detectorParameters);
|
||||
int errors =
|
||||
(int)std::floor(dictionary1.maxCorrectionBits * detector1.getDetectorParameters().errorCorrectionRate + 1.);
|
||||
|
||||
// create erroneous marker in currentCodeBits
|
||||
Mat currentCodeBits =
|
||||
aruco::Dictionary::getBitsFromByteList(currentCodeBytes, dictionary1.markerSize);
|
||||
for(int e = 0; e < errors; e++) {
|
||||
currentCodeBits.ptr<unsigned char>()[2 * e] =
|
||||
!currentCodeBits.ptr<unsigned char>()[2 * e];
|
||||
}
|
||||
|
||||
// dictionary3 is only composed by the modified marker (in its original form)
|
||||
aruco::Dictionary _dictionary3 = aruco::Dictionary(
|
||||
dictionary2.bytesList.rowRange(id, id + 1).clone(),
|
||||
dictionary1.markerSize,
|
||||
dictionary1.maxCorrectionBits);
|
||||
aruco::ArucoDetector detector3(_dictionary3, detector1.getDetectorParameters());
|
||||
// add erroneous marker to dictionary2 in order to create the erroneous marker image
|
||||
Mat currentCodeBytesError = aruco::Dictionary::getByteListFromBits(currentCodeBits);
|
||||
currentCodeBytesError.copyTo(dictionary2.bytesList.rowRange(id, id + 1));
|
||||
Mat img = Mat(imageSize, imageSize, CV_8UC1, Scalar::all(255));
|
||||
dictionary2.generateImageMarker(id, markerSide, marker);
|
||||
Mat aux = img.colRange(30, 30 + markerSide).rowRange(50, 50 + markerSide);
|
||||
marker.copyTo(aux);
|
||||
|
||||
// try to detect using dictionary3, it should fail
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
detector3.detectMarkers(img, corners, ids);
|
||||
if(corners.size() != 0) {
|
||||
ts->printf(cvtest::TS::LOG, "Error in DetectorParameters::errorCorrectionRate");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef CV_ArucoDetectionPerspective CV_AprilTagDetectionPerspective;
|
||||
typedef CV_ArucoDetectionPerspective CV_InvertedArucoDetectionPerspective;
|
||||
typedef CV_ArucoDetectionPerspective CV_Aruco3DetectionPerspective;
|
||||
|
||||
TEST(CV_InvertedArucoDetectionPerspective, algorithmic) {
|
||||
CV_InvertedArucoDetectionPerspective test(ArucoAlgParams::DETECT_INVERTED_MARKER);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_AprilTagDetectionPerspective, algorithmic) {
|
||||
CV_AprilTagDetectionPerspective test(ArucoAlgParams::USE_APRILTAG);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_Aruco3DetectionPerspective, algorithmic) {
|
||||
CV_Aruco3DetectionPerspective test(ArucoAlgParams::USE_ARUCO3);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_ArucoDetectionSimple, algorithmic) {
|
||||
CV_ArucoDetectionSimple test;
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_ArucoDetectionPerspective, algorithmic) {
|
||||
CV_ArucoDetectionPerspective test(ArucoAlgParams::USE_DEFAULT);
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_ArucoDetectionMarkerSize, algorithmic) {
|
||||
CV_ArucoDetectionMarkerSize test;
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_ArucoBitCorrection, algorithmic) {
|
||||
CV_ArucoBitCorrection test;
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_ArucoDetectMarkers, regression_3192)
|
||||
{
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_4X4_50));
|
||||
vector<int> markerIds;
|
||||
vector<vector<Point2f> > markerCorners;
|
||||
string imgPath = cvtest::findDataFile("aruco/regression_3192.png");
|
||||
Mat image = imread(imgPath);
|
||||
const size_t N = 2ull;
|
||||
const int goldCorners[N][8] = { {345,120, 520,120, 520,295, 345,295}, {101,114, 270,112, 276,287, 101,287} };
|
||||
const int goldCornersIds[N] = { 6, 4 };
|
||||
map<int, const int*> mapGoldCorners;
|
||||
for (size_t i = 0; i < N; i++)
|
||||
mapGoldCorners[goldCornersIds[i]] = goldCorners[i];
|
||||
|
||||
detector.detectMarkers(image, markerCorners, markerIds);
|
||||
|
||||
ASSERT_EQ(N, markerIds.size());
|
||||
for (size_t i = 0; i < N; i++)
|
||||
{
|
||||
int arucoId = markerIds[i];
|
||||
ASSERT_EQ(4ull, markerCorners[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]), markerCorners[i][j].x, 1.f);
|
||||
EXPECT_NEAR(static_cast<float>(mapGoldCorners[arucoId][j * 2 + 1]), markerCorners[i][j].y, 1.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CV_ArucoDetectMarkers, regression_2492)
|
||||
{
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_5X5_50));
|
||||
aruco::DetectorParameters detectorParameters = detector.getDetectorParameters();
|
||||
detectorParameters.minMarkerDistanceRate = 0.026;
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
vector<int> markerIds;
|
||||
vector<vector<Point2f> > markerCorners;
|
||||
string imgPath = cvtest::findDataFile("aruco/regression_2492.png");
|
||||
Mat image = imread(imgPath);
|
||||
const size_t N = 8ull;
|
||||
const int goldCorners[N][8] = { {179,139, 179,95, 223,95, 223,139}, {99,139, 99,95, 143,95, 143,139},
|
||||
{19,139, 19,95, 63,95, 63,139}, {256,140, 256,93, 303,93, 303,140},
|
||||
{256,62, 259,21, 300,23, 297,64}, {99,21, 143,17, 147,60, 103,64},
|
||||
{69,61, 28,61, 14,21, 58,17}, {174,62, 182,13, 230,19, 223,68} };
|
||||
const int goldCornersIds[N] = {13, 13, 13, 13, 1, 15, 14, 4};
|
||||
map<int, vector<const int*> > mapGoldCorners;
|
||||
for (size_t i = 0; i < N; i++)
|
||||
mapGoldCorners[goldCornersIds[i]].push_back(goldCorners[i]);
|
||||
|
||||
detector.detectMarkers(image, markerCorners, markerIds);
|
||||
|
||||
ASSERT_EQ(N, markerIds.size());
|
||||
for (size_t i = 0; i < N; i++)
|
||||
{
|
||||
int arucoId = markerIds[i];
|
||||
ASSERT_EQ(4ull, markerCorners[i].size());
|
||||
ASSERT_TRUE(mapGoldCorners.find(arucoId) != mapGoldCorners.end());
|
||||
float totalDist = 8.f;
|
||||
for (size_t k = 0ull; k < mapGoldCorners[arucoId].size(); k++)
|
||||
{
|
||||
float dist = 0.f;
|
||||
for (int j = 0; j < 4; j++) // total distance up to 4 points
|
||||
{
|
||||
dist += abs(mapGoldCorners[arucoId][k][j * 2] - markerCorners[i][j].x);
|
||||
dist += abs(mapGoldCorners[arucoId][k][j * 2 + 1] - markerCorners[i][j].y);
|
||||
}
|
||||
totalDist = min(totalDist, dist);
|
||||
}
|
||||
EXPECT_LT(totalDist, 8.f);
|
||||
}
|
||||
}
|
||||
|
||||
struct ArucoThreading: public testing::TestWithParam<aruco::CornerRefineMethod>
|
||||
{
|
||||
struct NumThreadsSetter {
|
||||
NumThreadsSetter(const int num_threads)
|
||||
: original_num_threads_(getNumThreads()) {
|
||||
setNumThreads(num_threads);
|
||||
}
|
||||
|
||||
~NumThreadsSetter() {
|
||||
setNumThreads(original_num_threads_);
|
||||
}
|
||||
private:
|
||||
int original_num_threads_;
|
||||
};
|
||||
};
|
||||
|
||||
TEST_P(ArucoThreading, number_of_threads_does_not_change_results)
|
||||
{
|
||||
// We are not testing against different dictionaries
|
||||
// As we are interested mostly in small images, smaller
|
||||
// markers is better -> 4x4
|
||||
aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_4X4_50));
|
||||
|
||||
// Height of the test image can be chosen quite freely
|
||||
// We aim to test against small images as in those the
|
||||
// number of threads has most effect
|
||||
const int height_img = 20;
|
||||
// Just to get nice white boarder
|
||||
const int shift = height_img > 10 ? 5 : 1;
|
||||
const int height_marker = height_img-2*shift;
|
||||
|
||||
// Create a test image
|
||||
Mat img_marker;
|
||||
aruco::generateImageMarker(detector.getDictionary(), 23, height_marker, img_marker, 1);
|
||||
|
||||
// Copy to bigger image to get a white border
|
||||
Mat img(height_img, height_img, CV_8UC1, Scalar(255));
|
||||
img_marker.copyTo(img(Rect(shift, shift, height_marker, height_marker)));
|
||||
|
||||
aruco::DetectorParameters detectorParameters = detector.getDetectorParameters();
|
||||
detectorParameters.cornerRefinementMethod = GetParam();
|
||||
detector.setDetectorParameters(detectorParameters);
|
||||
|
||||
vector<vector<Point2f> > original_corners;
|
||||
vector<int> original_ids;
|
||||
{
|
||||
NumThreadsSetter thread_num_setter(1);
|
||||
detector.detectMarkers(img, original_corners, original_ids);
|
||||
}
|
||||
|
||||
ASSERT_EQ(original_ids.size(), 1ull);
|
||||
ASSERT_EQ(original_corners.size(), 1ull);
|
||||
|
||||
int num_threads_to_test[] = { 2, 8, 16, 32, height_img-1, height_img, height_img+1};
|
||||
|
||||
for (size_t i_num_threads = 0; i_num_threads < sizeof(num_threads_to_test)/sizeof(int); ++i_num_threads) {
|
||||
NumThreadsSetter thread_num_setter(num_threads_to_test[i_num_threads]);
|
||||
|
||||
vector<vector<Point2f> > corners;
|
||||
vector<int> ids;
|
||||
detector.detectMarkers(img, corners, ids);
|
||||
|
||||
// If we don't find any markers, the test is broken
|
||||
ASSERT_EQ(ids.size(), 1ull);
|
||||
|
||||
// Make sure we got the same result as the first time
|
||||
ASSERT_EQ(corners.size(), original_corners.size());
|
||||
ASSERT_EQ(ids.size(), original_ids.size());
|
||||
ASSERT_EQ(ids.size(), corners.size());
|
||||
for (size_t i = 0; i < corners.size(); ++i) {
|
||||
EXPECT_EQ(ids[i], original_ids[i]);
|
||||
for (size_t j = 0; j < corners[i].size(); ++j) {
|
||||
EXPECT_NEAR(corners[i][j].x, original_corners[i][j].x, 0.1f);
|
||||
EXPECT_NEAR(corners[i][j].y, original_corners[i][j].y, 0.1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
CV_ArucoDetectMarkers, ArucoThreading,
|
||||
::testing::Values(
|
||||
aruco::CORNER_REFINE_NONE,
|
||||
aruco::CORNER_REFINE_SUBPIX,
|
||||
aruco::CORNER_REFINE_CONTOUR,
|
||||
aruco::CORNER_REFINE_APRILTAG
|
||||
));
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,321 @@
|
||||
// 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 = 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; distance <= 0.4; distance += 0.15) {
|
||||
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;
|
||||
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)
|
||||
{
|
||||
aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
|
||||
aruco::DetectorParameters params;
|
||||
params.minDistanceToBorder = 3;
|
||||
params.cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX;
|
||||
if (arucoAlgParams == ArucoAlgParams::USE_ARUCO3)
|
||||
params.useAruco3Detection = true;
|
||||
aruco::RefineParameters refineParams(10.f, 3.f, true);
|
||||
detector = aruco::ArucoDetector(dictionary, 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; distance <= 0.4; distance += 0.2) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,659 @@
|
||||
// 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) {
|
||||
|
||||
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(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);
|
||||
|
||||
for(unsigned int i = 0; i < chessboard.total(); i++) {
|
||||
if(chessboard.ptr< unsigned char >()[i] == 0) {
|
||||
img.ptr< unsigned char >()[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check Charuco detection
|
||||
*/
|
||||
class CV_CharucoDetection : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_CharucoDetection();
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
CV_CharucoDetection::CV_CharucoDetection() {}
|
||||
|
||||
|
||||
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));
|
||||
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; distance <= 0.4; distance += 0.2) {
|
||||
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);
|
||||
}
|
||||
|
||||
if(ids.size() == 0) {
|
||||
ts->printf(cvtest::TS::LOG, "Marker detection failed");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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];
|
||||
|
||||
if(currentId >= (int)board.getChessboardCorners().size()) {
|
||||
ts->printf(cvtest::TS::LOG, "Invalid Charuco corner id");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
double repError = cv::norm(charucoCorners[i] - projectedCharucoCorners[currentId]); // TODO cvtest
|
||||
|
||||
|
||||
if(repError > 5.) {
|
||||
ts->printf(cvtest::TS::LOG, "Charuco corner reprojection error too high");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check charuco pose estimation
|
||||
*/
|
||||
class CV_CharucoPoseEstimation : public cvtest::BaseTest {
|
||||
public:
|
||||
CV_CharucoPoseEstimation();
|
||||
|
||||
protected:
|
||||
void run(int);
|
||||
};
|
||||
|
||||
|
||||
CV_CharucoPoseEstimation::CV_CharucoPoseEstimation() {}
|
||||
|
||||
|
||||
void CV_CharucoPoseEstimation::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));
|
||||
aruco::CharucoDetector detector(board, aruco::CharucoParameters(), params);
|
||||
|
||||
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));
|
||||
// for different perspectives
|
||||
for(double distance = 0.2; distance <= 0.3; distance += 0.1) {
|
||||
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 offset = (board.getSquareLength() - board.getMarkerLength()) / 2.f;
|
||||
vector<Point2f> axes = getAxis(cameraMatrix, distCoeffs, rvec, tvec, board.getSquareLength(), offset);
|
||||
vector<Point2f> topLeft = getMarkerById(board.getIds()[0], corners, ids);
|
||||
ASSERT_NEAR(topLeft[0].x, axes[1].x, 3.f);
|
||||
ASSERT_NEAR(topLeft[0].y, axes[1].y, 3.f);
|
||||
vector<Point2f> bottomLeft = getMarkerById(board.getIds()[2], corners, ids);
|
||||
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];
|
||||
|
||||
if(currentId >= (int)board.getChessboardCorners().size()) {
|
||||
ts->printf(cvtest::TS::LOG, "Invalid Charuco corner id");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
double repError = cv::norm(charucoCorners[i] - projectedCharucoCorners[currentId]); // TODO cvtest
|
||||
|
||||
|
||||
if(repError > 5.) {
|
||||
ts->printf(cvtest::TS::LOG, "Charuco corner reprojection error too high");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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; distance <= 0.3; distance += 0.1) {
|
||||
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) {
|
||||
CV_CharucoDetection test;
|
||||
test.safe_run();
|
||||
}
|
||||
|
||||
TEST(CV_CharucoPoseEstimation, accuracy) {
|
||||
CV_CharucoPoseEstimation test;
|
||||
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
|
||||
cv::Mat expected_corners = (cv::Mat_<float>(9,2) <<
|
||||
200, 200,
|
||||
250, 200,
|
||||
300, 200,
|
||||
200, 250,
|
||||
250, 250,
|
||||
300, 250,
|
||||
200, 300,
|
||||
250, 300,
|
||||
300, 300
|
||||
);
|
||||
|
||||
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 = 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.rows, expected_corners.rows);
|
||||
EXPECT_NEAR(0, cvtest::norm(expected_corners, c_corners.reshape(1), NORM_INF), 1e-1);
|
||||
}
|
||||
|
||||
TEST(Charuco, issue_14014)
|
||||
{
|
||||
string imgPath = cvtest::findDataFile("aruco/recover.png");
|
||||
Mat img = imread(imgPath);
|
||||
|
||||
aruco::DetectorParameters detectorParams;
|
||||
detectorParams.cornerRefinementMethod = 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(), 26ull); // 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
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
@@ -3,6 +3,7 @@
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
@@ -11,13 +12,14 @@ std::string qrcode_images_name[] = {
|
||||
"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_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"
|
||||
"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"
|
||||
@@ -30,6 +32,7 @@ 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"
|
||||
};
|
||||
|
||||
//#define UPDATE_QRCODE_TEST_DATA
|
||||
#ifdef UPDATE_QRCODE_TEST_DATA
|
||||
|
||||
@@ -87,7 +90,7 @@ TEST(Objdetect_QRCode_Close, generate_test_data)
|
||||
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);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
EXPECT_TRUE(detectQRCode(barcode, corners));
|
||||
#ifdef HAVE_QUIRC
|
||||
EXPECT_TRUE(decodeQRCode(barcode, corners, decoded_info, straight_barcode));
|
||||
@@ -125,7 +128,7 @@ TEST(Objdetect_QRCode_Monitor, generate_test_data)
|
||||
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);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
EXPECT_TRUE(detectQRCode(barcode, corners));
|
||||
#ifdef HAVE_QUIRC
|
||||
EXPECT_TRUE(decodeQRCode(barcode, corners, decoded_info, straight_barcode));
|
||||
@@ -313,7 +316,7 @@ TEST_P(Objdetect_QRCode_Close, regression)
|
||||
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);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
std::vector<Point> corners;
|
||||
std::string decoded_info;
|
||||
QRCodeDetector qrcode;
|
||||
@@ -380,7 +383,7 @@ TEST_P(Objdetect_QRCode_Monitor, regression)
|
||||
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);
|
||||
resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT);
|
||||
std::vector<Point> corners;
|
||||
std::string decoded_info;
|
||||
QRCodeDetector qrcode;
|
||||
@@ -499,7 +502,7 @@ TEST_P(Objdetect_QRCode_Multi, regression)
|
||||
{
|
||||
const std::string name_current_image = GetParam();
|
||||
const std::string root = "qrcode/multiple/";
|
||||
const int pixels_error = 3;
|
||||
const int pixels_error = 4;
|
||||
|
||||
std::string image_path = findDataFile(root + name_current_image);
|
||||
Mat src = imread(image_path);
|
||||
@@ -758,6 +761,26 @@ TEST(Objdetect_QRCode_decode, decode_regression_version_25)
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Objdetect_QRCode_decodeMulti, 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);
|
||||
QRCodeDetector qrcode;
|
||||
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
|
||||
|
||||
}} // namespace
|
||||
|
||||
Reference in New Issue
Block a user