opencv中ArUco模块实践(1)

2022-02-10 12:27:57 浏览数 (3)

论文阅读模块将分享点云处理,SLAM,三维视觉,高精地图相关的文章。

aruco标记板的检测与识别

在上一节中我们介绍了aruco单个的标记 板的检测和识别这里我们将介绍aruco标记板的检测和识别的过程。

ArUco标定板是一组标记板的组合,其作用类似于单个标记,因为它为相机提供了单个姿势。最流行的标记板是在同一平面上有所有标记的标定板,因为它很容易打印:

然而,标定板是不限于此情况的,并且可以在任何2d或3d物体上进行布局。

标定板和一组独立标记之间的区别在于,标定板中的标记物中的相对位置是先验的。这使得所有标记的角点可以用于估计相机相对于整个板子的姿势,使用一组独立的标记时,可以单独估计每个标记的姿势,因为您不知道标记物在环境中的相对位置。

使用标定板的主要好处是:

  • 姿态估计更为通用,只需要一些标记就可以进行姿态估计,因此,即使存在遮挡或局部视图,也可以计算姿势。
  • 由于采用了更多的点对应(标记角点),因此获得的姿势通常更精确。

ArUco模块允许使用标定板。主类是cv::aruco::Board类,它定义了标定板布局的函数:

代码语言:javascript复制
class Board {
public:
std::vector<std::vector<cv::Point3f> > objPoints;
cv::aruco::Dictionary dictionary;
std::vector<int> ids;
 };

Board类型的对象有三个参数:

  • objPoints结构是三维板参考系统中的角点位置列表,即其布局。对于每个标记,其四个角按标准顺序存储,即顺时针顺序并从左上角开始。
  • dictionary参数指示板标记属于哪个标记字典。
  • 最后,ids结构指示objPoints中每个标记相对于指定字典的标识符。

标定板的检测

标记板检测与标准标记检测类似。唯一的区别在于姿势估计步骤。事实上,要使用标记板,在估计板姿势之前,应该先进行标准的标记检测。

aruco模块提供了一个特定的函数estimatePoseBoard(),用于对标记板执行姿势估计:

代码语言:javascript复制
cv::Mat inputImage;
// camera parameters are read from somewhere
cv::Mat cameraMatrix, distCoeffs;
readCameraParameters(cameraMatrix, distCoeffs);
// assume we have a function to create the board object
 cv::aruco::Board board = createBoard();
 ...
 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) {
 cv::Vec3d rvec, tvec;
  int valid = cv::aruco::estimatePoseBoard(markerCorners, markerIds, board, cameraMatrix, distCoeffs, rvec, tvec);
 }

estimatePoseBoard的参数为:

  • markerCorners和markerIds:detectMarkers()函数中检测到的标记的结构。
  • board:定义board布局及其id的board对象
  • cameraMatrix和distcoefs:姿态估计所需的摄像机校准参数。
  • rvec和tvec:董事会的估计姿势。如果不为空,则视为初始猜测。
  • 函数返回用于估计棋盘姿势的标记总数。注意,不应该使用markerCorners和markerIds中提供的所有标记,因为只考虑Board::ids结构中列出了其id的标记。
  • drawAxis() 函数的作用是:检查获得的姿势。

标记板带有坐标系可视化的结果

被遮挡的标记物的检测并带有坐标系的可视化的结果

栅格标记板

创建栅格的标记板对象需要指定环境中每个标记的角位置。然而,在许多情况下,栅格标记板将只是同一平面和网格布局中的一组标记,因此可以轻松打印和使用,幸运的是,aruco模块提供了创建和打印这些类型标记的基本功能。

GridBoard类是一个专门的类,它继承了Board类,它表示一个板,其中包含同一平面和网格布局中的所有标记,如下图所示:

可以使用以下参数定义GridBoard对象:

  • X方向上的标记数。
  • Y方向上的标记数。
  • 标记侧的长度。
  • 标记物分隔的长度。
  • 标记物词典。
  • 所有标记的ID(X*Y标记)。

可以使用cv::aruco::GridBoard::create()静态函数从这些参数轻松创建此对象:

代码语言:javascript复制
cv::aruco::GridBoard board = cv::aruco::GridBoard::create(5, 7, 0.04, 0.01, dictionary);
  • 第一和第二参数分别是X和Y方向上的标记物的个数。
  • 第三和第四个参数分别是标记长度和标记间距。它们可以以任何单位提供,记住该板的估计姿势将以相同单位测量(通常使用米)。
  • 最后给出了标记的字典。

因此,该板将由5x7=35个标记组成。默认情况下,从0开始按升序分配每个标记的ID,因此它们将是0、1、2、…、。这可以通过board.ids访问ids向量来轻松定制,就像在board父类中一样。

创建网格板之后,我们可能要打印并使用它。

代码语言:javascript复制
cv::aruco::GridBoard::draw()

中提供了生成网格板图像的函数。例如

代码语言:javascript复制
cv::aruco::GridBoard board = cv::aruco::GridBoard::create(5, 7, 0.04, 0.01, dictionary);
cv::Mat boardImage;
board.draw( cv::Size(600, 500), boardImage, 10, 1 );
  • 第一个参数是输出图像的像素大小。在这种情况下,600x500像素。如果这与标记板尺寸不成比例,则将以图像为中心。
  • boardImage:带板的输出图像。
  • 第三个参数是像素的(可选)边距,因此没有任何标记触及图像边界。
  • 最后,标记边框的大小,类似于drawMarker()函数,默认值为1。

输出图像如下所示:

创建标定板的完整工作示例包含在module samples文件夹中的create_board.cpp中。

标记检测的细化

如果我们检测到属于标定板的标记的子集,我们可以使用这些标记和标记板布局信息来尝试查找以前未检测到的标记。这可以使用refinedDetectedMarkers()函数来完成,该函数应该在调用detectMarkers()之后调用。此函数的主要参数是检测标记的原始图像、板对象、检测到的标记角点、检测到的标记ID和拒绝的标记角点。拒绝的角点可以从detectMarkers()函数获得,也被称为候选标记。这些候选者是在原始图像中发现的方形,但未能通过识别步骤(即其内部编码呈现太多错误),因此未被识别为标记。

然而,这些候选标记有时是由于图像中的高噪声、非常低的分辨率或其他影响二进制代码提取的相关问题而未被正确识别的实际标记。函数的作用是:

查找这些候选标记与标记板上丢失的标记之间的对应关系。此搜索基于两个参数:

候选标记与缺失标记的投影之间的距离:要获得这些投影,必须检测到标定板的至少一个标记,投影是使用相机参数(相机矩阵和失真系数)获得的,如果提供的话,如果不是,则从局部单应获得投影,并且只允许平面标记板(即所有标记角的Z坐标应相同),refinedDetectedMarkers()中的minRepDistance参数确定候选角点和投影标记角点之间的最小欧氏距离(默认值10)。

二进制编码:如果候选标记超过最小距离条件,则再次分析其内部位以确定它是否实际是投影标记,然而,在这种情况下,条件不是那么强,并且允许的错误比特的数目可以更高,这在errorCorrectionRate参数(默认值3.0)中表示,如果提供负值,则根本不分析内部位,只计算角距离。

这是使用finitedetectedmarkers()函数的示例:

代码语言:javascript复制
 cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
 cv::aruco::GridBoard board = cv::aruco::GridBoard::create(5, 7, 0.04, 0.01, dictionary);
 vector< int > markerIds;
 vector< vector<Point2f> > markerCorners, rejectedCandidates;
 cv::aruco::detectMarkers(inputImage, dictionary, markerCorners, markerIds, cv::aruco::DetectorParameters(), rejectedCandidates);

 cv::aruco::refineDetectedMarkersinputImage, board, markerCorners, markerIds, rejectedCandidates);
 // After calling this function, if any new marker has been detected it will be removed from rejectedCandidates and included
 // at the end of markerCorners and markerIds

还必须注意的是,在某些情况下,如果首先检测到的标记数量太少(例如只有1或2个标记),丢失标记的投影质量可能很差,从而产生错误的对应。

0 人点赞