论文阅读模块将分享点云处理,SLAM,三维视觉,高精地图相关的文章。
opencv中ArUco模块实践(1) ChAruco标定板
ArUCo标记板是非常有用的,因为他们的快速检测和多功能性。然而,ArUco标记的一个问题是,即使在应用亚像素细化后,其角点位置的精度也不太高。相反,棋盘图案的角点可以更精确地细化,因为每个角点被两个黑色正方形包围。然而,寻找棋盘图案并不像寻找aruco棋盘那样通用:它必须是完全可见的,并且不允许遮挡。
ChAruco标记板试图结合这两种方法的优点:
ArUco部分用于内插棋盘转角的位置,因此它具有标记板的多功能性,因为它允许遮挡或局部视图。此外,由于插值的角点属于棋盘,因此它们在亚像素精度方面非常精确。
当对角点加测的要求是高精度且必要的,如在相机校准,Charuco板是一个比标准aruco板更好的选择。
ChArUco标记板的创建
aruco模块提供cv::aruco::CharucoBoard类,该类表示Charuco板,并从Board类继承。
该类与ChArUco的其他功能一样,定义如下:
#include <opencv2/aruco/charuco.hpp>
要定义Charuco标记板,必须:
对于GridBoard对象,aruco模块提供了一个创建CharucoBoards的函数。
cv::aruco::CharucoBoard::create():
cv::aruco::CharucoBoard board = cv::aruco::CharucoBoard::create(5, 7, 0.04, 0.02, dictionary);
默认情况下,每个标记的ID都是从0开始按升序分配的,就像在GridBoard::create()中一样。这可以通过board.ids访问ids向量来轻松定制,就像在board父类中一样。
一旦我们有了CharucoBoard对象,我们就可以创建一个图像来打印它。这可以通过
cv::aruco::CharucoBoard board = cv::aruco::CharucoBoard::create(5, 7, 0.04, 0.02, dictionary);
cv::Mat boardImage;
board.draw( cv::Size(600, 500), boardImage, 10, 1 );
ChArUco标定板检测
当你检测到一个ChArUco棋盘时,实际检测到的是棋盘的每个棋盘格角点。
ChArUco板上的每个角落都分配了一个唯一标识符(id)。这些ID从0到板中的角总数。
因此,检测到的ChArUco板包括:
vector<Point2f>charucoCorners:检测到的角点的图像位置列表。
vector<int>charucoIds:charucoCorners中每个检测到的角点的ID。
ChArUco角点的检测基于先前检测到的标记。因此,首先检测标记,然后从标记中插值ChArUco角点。检测ChArUco角点的函数是
cv::aruco::interpolateCornersCharuco()
这个例子展示了整个过程。首先,检测标记,然后从这些标记中插值ChArUco角点。
cv::Mat inputImage;
cv::Mat cameraMatrix, distCoeffs;
// camera parameters are read from somewhere
readCameraParameters(cameraMatrix, distCoeffs);
cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
cv::aruco::CharucoBoard board = cv::aruco::CharucoBoard::create(5, 7, 0.04, 0.02, dictionary);
...
vector< int > markerIds;
vector< vector<Point2f> > markerCorners;
cv::aruco::detectMarkers(inputImage, board.dictionary, markerCorners, markerIds);
// if at least one marker detected
if(markerIds.size() > 0) {
std::vector<cv::Point2f> charucoCorners;
std::vector<int> charucoIds;
cv::aruco::interpolateCornersCharuco(markerCorners, markerIds, inputImage, board, charucoCorners, charucoIds, cameraMatrix, distCoeffs);
}
interpolateCornersCharuco()函数的参数为:
markerCorners和markerIds:从detectMarkers()函数中检测到的标记物。
inputimage:检测到标记的原始图像。图像是必要的执行亚像素细化在Aruco角点。
board:CharucoBoard对象
charucockerners和charucoIds:输出插值Charuco角点
cameraMatrix和distcoefs:可选的摄像机校准参数
函数返回插值的Charuco角点的数目。
在这种情况下,我们调用interpolateCornersCharuco()来提供相机校准参数。但是,这些参数是可选的。没有这些参数的类似示例如下:
cv::Mat inputImage;
cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
cv::aruco::CharucoBoard board = cv::aruco::CharucoBoard::create(5, 7, 0.04, 0.02, dictionary);
...
vector< int > markerIds;
vector< vector<Point2f> > markerCorners;
DetectorParameters params;
params.doCornerRefinement = false;
cv::aruco::detectMarkers(inputImage, board.dictionary, markerCorners, markerIds, params);
// if at least one marker detected
if(markerIds.size() > 0) {
std::vector<cv::Point2f> charucoCorners;
std::vector<int> charucoIds;
cv::aruco::interpolateCornersCharuco(markerCorners, markerIds, inputImage, board, charucoCorners, charucoIds);
}
如果提供了校准参数,ChArUco角点插值方法是,首先从ArUco标记估计一个粗略姿态,然后将ChArUco角点重新投影回图像。另一方面,如果不提供校准参数,则通过计算ChArUco平面和ChArUco图像投影之间的对应单应来插值ChArUco角点。
使用单应的主要问题是插值对图像失真更敏感。实际上,单应仅使用每个ChArUco角点的最近标记位来执行,以减少失真的影响。
在检测ChArUco板的标记时,特别是在使用单应性时,建议禁用标记的角点细化。其原因是,由于棋盘方块的接近性,亚像素过程会在角点位置产生重要的偏差,这些偏差会传播到ChArUco角点插值,产生较差的结果。
此外,仅返回其两个周围标记已找到的角点。如果没有检测到周围的两个标记中的任何一个,这通常意味着该区域存在某种遮挡或图像质量不好。在任何情况下,最好不要考虑该角点,因为我们想要的是确保插值的ChArUco角点非常精确。
在对ChArUco角点进行插值之后,执行亚像素细化。
一旦我们内插了ChArUco角点,我们可能会想画出来看看他们的检测是否正确。使用drawDetectedCornersCharuco()函数可以轻松完成此操作:
cv::aruco::drawDetectedCornersCharuco(image, charucoCorners, charucoIds, color);
最后,这是ChArUco检测的完整示例(不使用校准参数)
cv::VideoCapture inputVideo;
inputVideo.open(0);
cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
cv::aruco::CharucoBoard board = cv::aruco::CharucoBoard::create(5, 7, 0.04, 0.02, dictionary);
DetectorParameters params;
params.doCornerRefinement = false;
while (inputVideo.grab()) {
cv::Mat image, imageCopy;
inputVideo.retrieve(image);
image.copyTo(imageCopy);
std::vector<int> ids;
std::vector<std::vector<cv::Point2f> > corners;
cv::aruco::detectMarkers(image, dictionary, corners, ids, params);
// if at least one marker detected
if (ids.size() > 0) {
cv::aruco::drawDetectedMarkers(imageCopy, corners, ids);
std::vector<cv::Point2f> charucoCorners;
std::vector<int> charucoIds;
cv::aruco::interpolateCornersCharuco(corners, ids, image, board, charucoCorners, charucoIds);
// if at least one charuco corner detected
if(charucoIds.size() > 0)
cv::aruco::drawDetectedCornersCharuco(imageCopy, charucoCorners, charucoIds, cv::Scalar(255, 0, 0));
}
cv::imshow("out", imageCopy);
char key = (char) cv::waitKey(waitTime);
if (key == 27)
break;
}
ChArUco姿态估计
ChArUco板的最终目标是非常精确地找到角点,以便进行高精度校准或姿态估计。
aruco模块提供了一个简单的ChArUco姿态估计功能。与在GridBoard中一样,CharucoBoard的坐标系放置在板平面中,Z轴指向外,并居中于板的左下角。
姿态估计的函数是estimatePosecharocboard():
cv::aruco::EstimatePoseCharocboard(charucoCorners、charucoIds、board、cameraMatrix、Distceffs、rvec、tvec);
charucoCorners和charucoIds参数是从interpolateCornersCharuco()函数中检测到的charuco角点。
如果正确估计了姿势,则函数返回true,否则返回false。失败的主要原因是没有足够的角点进行姿态估计或它们在同一条直线上。可以使用drawAxis()绘制轴,以检查姿势是否正确估计。结果是:(X:红色,Y:绿色,Z:蓝色)
完整的的位姿估计的代码
cv::VideoCapture inputVideo;
inputVideo.open(0);
cv::Mat cameraMatrix, distCoeffs;
// camera parameters are read from somewhere
readCameraParameters(cameraMatrix, distCoeffs);
cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
cv::aruco::CharucoBoard board = cv::aruco::CharucoBoard::create(5, 7, 0.04, 0.02, dictionary);
while (inputVideo.grab()) {
cv::Mat image, imageCopy;
inputVideo.retrieve(image);
image.copyTo(imageCopy);
std::vector<int> ids;
std::vector<std::vector<cv::Point2f> > corners;
cv::aruco::detectMarkers(image, dictionary, corners, ids);
// if at least one marker detected
if (ids.size() > 0) {
std::vector<cv::Point2f> charucoCorners;
std::vector<int> charucoIds;
cv::aruco::interpolateCornersCharuco(corners, ids, image, board, charucoCorners, charucoIds, cameraMatrix, distCoeffs);
// if at least one charuco corner detected
if(charucoIds.size() > 0) {
cv::aruco::drawDetectedCornersCharuco(imageCopy, charucoCorners, charucoIds, cv::Scalar(255, 0, 0));
cv::Vec3d rvec, tvec;
bool valid = cv::aruco::estimatePoseCharucoBoard(charucoCorners, charucoIds, board, cameraMatrix, distCoeffs, rvec, tvec);
// if charuco pose is valid
if(valid)
cv::aruco::drawAxis(imageCopy, cameraMatrix, distCoeffs, rvec, tvec, 0.1);
}
}
cv::imshow("out", imageCopy);
char key = (char) cv::waitKey(waitTime);
if (key == 27)
break;
}