提交 e55784a1 编写于 作者: S Stefan Becker 提交者: Alexander Smorkalov

ChArUco pre460 pattern support

上级 868787c3
......@@ -146,6 +146,18 @@ public:
CV_WRAP CharucoBoard(const Size& size, float squareLength, float markerLength,
const Dictionary &dictionary, InputArray ids = noArray());
/** @brief set legacy chessboard pattern.
*
* Legacy setting creates chessboard patterns starting with a white box in the upper left corner
* if there is an even row count of chessboard boxes, otherwise it starts with a black box.
* This setting ensures compatibility to patterns created with OpenCV versions prior OpenCV 4.6.0.
* See https://github.com/opencv/opencv/issues/23152.
*
* Default value: false.
*/
CV_WRAP void setLegacyPattern(bool legacyPattern);
CV_WRAP bool getLegacyPattern() const;
CV_WRAP Size getChessboardSize() const;
CV_WRAP float getSquareLength() const;
CV_WRAP float getMarkerLength() const;
......
......@@ -229,7 +229,8 @@ struct GridBoardImpl : public Board::Impl {
Board::Impl(_dictionary),
size(_size),
markerLength(_markerLength),
markerSeparation(_markerSeparation)
markerSeparation(_markerSeparation),
legacyPattern(false)
{
CV_Assert(size.width*size.height > 0 && markerLength > 0 && markerSeparation > 0);
}
......@@ -240,6 +241,8 @@ struct GridBoardImpl : public Board::Impl {
float markerLength;
// separation between markers in the grid
float markerSeparation;
// set pre4.6.0 chessboard pattern behavior (even row count patterns have a white box in the upper left corner)
bool legacyPattern;
};
GridBoard::GridBoard() {}
......@@ -297,7 +300,8 @@ struct CharucoBoardImpl : Board::Impl {
Board::Impl(_dictionary),
size(_size),
squareLength(_squareLength),
markerLength(_markerLength)
markerLength(_markerLength),
legacyPattern(false)
{}
// chessboard size
......@@ -309,6 +313,9 @@ struct CharucoBoardImpl : Board::Impl {
// Physical marker side length (normally in meters)
float markerLength;
// set pre4.6.0 chessboard pattern behavior (even row count patterns have a white box in the upper left corner)
bool legacyPattern;
// vector of chessboard 3D corners precalculated
std::vector<Point3f> chessboardCorners;
......@@ -316,6 +323,7 @@ struct CharucoBoardImpl : Board::Impl {
std::vector<std::vector<int> > nearestMarkerIdx;
std::vector<std::vector<int> > nearestMarkerCorners;
void createCharucoBoard();
void calcNearestMarkerCorners();
void matchImagePoints(InputArrayOfArrays detectedCharuco, InputArray detectedIds,
......@@ -324,8 +332,56 @@ struct CharucoBoardImpl : Board::Impl {
void generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const override;
};
void CharucoBoardImpl::createCharucoBoard() {
float diffSquareMarkerLength = (squareLength - markerLength) / 2;
int totalMarkers = (int)(ids.size());
// calculate Board objPoints
int nextId = 0;
objPoints.clear();
for(int y = 0; y < size.height; y++) {
for(int x = 0; x < size.width; x++) {
if(legacyPattern && (size.height % 2 == 0)) { // legacy behavior only for even row count patterns
if((y + 1) % 2 == x % 2) continue; // black corner, no marker here
} else {
if(y % 2 == x % 2) continue; // black corner, no marker here
}
vector<Point3f> corners(4);
corners[0] = Point3f(x * squareLength + diffSquareMarkerLength,
y * squareLength + diffSquareMarkerLength, 0);
corners[1] = corners[0] + Point3f(markerLength, 0, 0);
corners[2] = corners[0] + Point3f(markerLength, markerLength, 0);
corners[3] = corners[0] + Point3f(0, markerLength, 0);
objPoints.push_back(corners);
// first ids in dictionary
if (totalMarkers == 0)
ids.push_back(nextId);
nextId++;
}
}
if (totalMarkers > 0 && nextId != totalMarkers)
CV_Error(cv::Error::StsBadSize, "Size of ids must be equal to the number of markers: "+std::to_string(nextId));
// now fill chessboardCorners
chessboardCorners.clear();
for(int y = 0; y < size.height - 1; y++) {
for(int x = 0; x < size.width - 1; x++) {
Point3f corner;
corner.x = (x + 1) * squareLength;
corner.y = (y + 1) * squareLength;
corner.z = 0;
chessboardCorners.push_back(corner);
}
}
rightBottomBorder = Point3f(size.width * squareLength, size.height * squareLength, 0.f);
calcNearestMarkerCorners();
}
/** Fill nearestMarkerIdx and nearestMarkerCorners arrays */
void CharucoBoardImpl::calcNearestMarkerCorners() {
nearestMarkerIdx.clear();
nearestMarkerCorners.clear();
nearestMarkerIdx.resize(chessboardCorners.size());
nearestMarkerCorners.resize(chessboardCorners.size());
unsigned int nMarkers = (unsigned int)objPoints.size();
......@@ -459,7 +515,11 @@ void CharucoBoardImpl::generateImage(Size outSize, OutputArray img, int marginSi
for(int y = 0; y < size.height; y++) {
for(int x = 0; x < size.width; x++) {
if(y % 2 != x % 2) continue; // white corner, dont do anything
if(legacyPattern && (size.height % 2 == 0)) { // legacy behavior only for even row count patterns
if((y + 1) % 2 != x % 2) continue; // white corner, dont do anything
} else {
if(y % 2 != x % 2) continue; // white corner, dont do anything
}
double startX, startY;
startX = squareSizePixels * double(x);
......@@ -481,47 +541,9 @@ CharucoBoard::CharucoBoard(const Size& size, float squareLength, float markerLen
CV_Assert(size.width > 1 && size.height > 1 && markerLength > 0 && squareLength > markerLength);
vector<vector<Point3f> > objPoints;
float diffSquareMarkerLength = (squareLength - markerLength) / 2;
int totalMarkers = (int)(ids.total());
ids.copyTo(impl->ids);
// calculate Board objPoints
int nextId = 0;
for(int y = 0; y < size.height; y++) {
for(int x = 0; x < size.width; x++) {
if(y % 2 == x % 2) continue; // black corner, no marker here
vector<Point3f> corners(4);
corners[0] = Point3f(x * squareLength + diffSquareMarkerLength,
y * squareLength + diffSquareMarkerLength, 0);
corners[1] = corners[0] + Point3f(markerLength, 0, 0);
corners[2] = corners[0] + Point3f(markerLength, markerLength, 0);
corners[3] = corners[0] + Point3f(0, markerLength, 0);
objPoints.push_back(corners);
// first ids in dictionary
if (totalMarkers == 0)
impl->ids.push_back(nextId);
nextId++;
}
}
if (totalMarkers > 0 && nextId != totalMarkers)
CV_Error(cv::Error::StsBadSize, "Size of ids must be equal to the number of markers: "+std::to_string(nextId));
impl->objPoints = objPoints;
// now fill chessboardCorners
std::vector<Point3f> & c = static_pointer_cast<CharucoBoardImpl>(impl)->chessboardCorners;
for(int y = 0; y < size.height - 1; y++) {
for(int x = 0; x < size.width - 1; x++) {
Point3f corner;
corner.x = (x + 1) * squareLength;
corner.y = (y + 1) * squareLength;
corner.z = 0;
c.push_back(corner);
}
}
impl->rightBottomBorder = Point3f(size.width * squareLength, size.height * squareLength, 0.f);
static_pointer_cast<CharucoBoardImpl>(impl)->calcNearestMarkerCorners();
static_pointer_cast<CharucoBoardImpl>(impl)->createCharucoBoard();
}
Size CharucoBoard::getChessboardSize() const {
......@@ -539,6 +561,20 @@ float CharucoBoard::getMarkerLength() const {
return static_pointer_cast<CharucoBoardImpl>(impl)->markerLength;
}
void CharucoBoard::setLegacyPattern(bool legacyPattern) {
CV_Assert(impl);
if (static_pointer_cast<CharucoBoardImpl>(impl)->legacyPattern != legacyPattern)
{
static_pointer_cast<CharucoBoardImpl>(impl)->legacyPattern = legacyPattern;
static_pointer_cast<CharucoBoardImpl>(impl)->createCharucoBoard();
}
}
bool CharucoBoard::getLegacyPattern() const {
CV_Assert(impl);
return static_pointer_cast<CharucoBoardImpl>(impl)->legacyPattern;
}
bool CharucoBoard::checkCharucoCornersCollinear(InputArray charucoIds) const {
CV_Assert(impl);
Mat charucoIdsMat = charucoIds.getMat();
......
......@@ -8,12 +8,12 @@
namespace opencv_test {
vector<Point2f> getAxis(InputArray _cameraMatrix, InputArray _distCoeffs, InputArray _rvec,
InputArray _tvec, float length, const float offset) {
InputArray _tvec, float length, const Point2f 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));
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;
......
......@@ -10,7 +10,7 @@ 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);
float length, const Point2f offset = Point2f(0, 0));
vector<Point2f> getMarkerById(int id, const vector<vector<Point2f> >& corners, const vector<int>& ids);
......
......@@ -12,7 +12,7 @@ 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 cameraMatrix, Mat rvec, Mat tvec, bool legacyPattern) {
Mat img(imageSize, CV_8UC1, Scalar::all(255));
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
......@@ -20,7 +20,11 @@ static Mat projectChessboard(int squaresX, int squaresY, float squareSize, Size
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;
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;
......@@ -66,7 +70,7 @@ static Mat projectCharucoBoard(aruco::CharucoBoard& board, Mat cameraMatrix, dou
// project chessboard
Mat chessboard =
projectChessboard(board.getChessboardSize().width, board.getChessboardSize().height,
board.getSquareLength(), imageSize, cameraMatrix, rvec, tvec);
board.getSquareLength(), imageSize, cameraMatrix, rvec, tvec, board.getLegacyPattern());
for(unsigned int i = 0; i < chessboard.total(); i++) {
if(chessboard.ptr< unsigned char >()[i] == 0) {
......@@ -82,14 +86,13 @@ static Mat projectCharucoBoard(aruco::CharucoBoard& board, Mat cameraMatrix, dou
*/
class CV_CharucoDetection : public cvtest::BaseTest {
public:
CV_CharucoDetection();
CV_CharucoDetection(bool _legacyPattern) : legacyPattern(_legacyPattern) {}
protected:
void run(int);
};
CV_CharucoDetection::CV_CharucoDetection() {}
bool legacyPattern;
};
void CV_CharucoDetection::run(int) {
......@@ -100,6 +103,7 @@ void CV_CharucoDetection::run(int) {
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;
......@@ -140,11 +144,7 @@ void CV_CharucoDetection::run(int) {
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;
}
ASSERT_GT(ids.size(), std::vector< int >::size_type(0)) << "Marker detection failed";
// check results
vector< Point2f > projectedCharucoCorners;
......@@ -161,20 +161,11 @@ void CV_CharucoDetection::run(int) {
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;
}
ASSERT_LT(currentId, (int)board.getChessboardCorners().size()) << "Invalid Charuco corner id";
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;
}
ASSERT_LE(repError, 5.) << "Charuco corner reprojection error too high";
}
}
}
......@@ -188,30 +179,31 @@ void CV_CharucoDetection::run(int) {
*/
class CV_CharucoPoseEstimation : public cvtest::BaseTest {
public:
CV_CharucoPoseEstimation();
CV_CharucoPoseEstimation(bool _legacyPattern) : legacyPattern(_legacyPattern) {}
protected:
void run(int);
};
CV_CharucoPoseEstimation::CV_CharucoPoseEstimation() {}
bool legacyPattern;
};
void CV_CharucoPoseEstimation::run(int) {
int iter = 0;
Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1);
Size imgSize(500, 500);
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) = 650;
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) {
......@@ -252,12 +244,21 @@ void CV_CharucoPoseEstimation::run(int) {
// check axes
const float offset = (board.getSquareLength() - board.getMarkerLength()) / 2.f;
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);
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);
......@@ -271,20 +272,11 @@ void CV_CharucoPoseEstimation::run(int) {
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;
}
ASSERT_LT(currentId, (int)board.getChessboardCorners().size()) << "Invalid Charuco corner id";
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;
}
ASSERT_LE(repError, 5.) << "Charuco corner reprojection error too high";
}
}
}
......@@ -490,12 +482,26 @@ void CV_CharucoBoardCreation::run(int)
TEST(CV_CharucoDetection, accuracy) {
CV_CharucoDetection test;
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) {
CV_CharucoPoseEstimation test;
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();
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册