Skip to content

ArUco 标定板检测

ArUco 标定板(board)是一组标记(marker)的集合,它的作用类似于单个标记,因为它为相机提供了一个单一的位姿。

最常用的标定板是所有标记位于同一平面上的那种,因为它可以很容易地打印出来:

然而,标定板并不局限于这种排列方式,它可以表示任何二维或三维的布局。

标定板与一组独立标记之间的区别在于:标定板中各标记之间的相对位置是事先已知的。这使得所有标记的角点都可以用来估计相机相对于整个标定板的位姿。

当你使用一组独立标记时,你只能逐个估计每个标记的位姿,因为你不知道这些标记在环境中的相对位置。

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

  • 位姿估计更加通用。只需部分标记即可完成位姿估计。因此,即使在存在遮挡或部分可见的情况下,也能计算出位姿。
  • 由于采用了更多的点对应关系(标记角点),所获得的位姿通常更精确。

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

要对标定板进行位姿估计,你应当使用 solvePnP() 函数,如下面的 samples/cpp/tutorial_code/objectDetection/detect_board.cpp 所示。

这些参数是:

  • objPoints、imgPoints:物体点和图像点,由 cv::aruco::GridBoard::matchImagePoints() 进行匹配,而该函数又以 cv::aruco::ArucoDetector::detectMarkers() 函数检测到的标记的 markerCorners 和 markerIds 结构作为输入。
  • board:定义标定板布局及其 id 的 cv::aruco::Board 对象。
  • cameraMatrix 和 distCoeffs:位姿估计所需的相机标定参数。
  • rvec 和 tvec:估计的标定板位姿。如果不为空,则被视为初始猜测。
  • 该函数返回用于估计标定板位姿的标记总数。

可以使用 drawFrameAxes() 函数来检查所获得的位姿。例如:

这是另一个标定板部分被遮挡的示例:

可以看出,尽管一些标记没有被检测到,但标定板的位姿仍然可以从其余标记中估计出来。

示例视频:

视频演示

samples/cpp/tutorial_code/objectDetection/ 中的 detect_board.cpp 包含了一个完整的可运行示例。

现在,这些示例通过 cv::CommandLineParser 从命令行获取输入。对于此文件,示例参数如下:

-w=5 -h=7 -l=100 -s=10
-v=/path_to_opencv/opencv/doc/tutorials/objdetect/aruco_board_detection/gboriginal.jpg
-c=/path_to_opencv/opencv/samples/cpp/tutorial_code/objectDetection/tutorial_camera_params.yml
-cd=/path_to_opencv/opencv/samples/cpp/tutorial_code/objectDetection/tutorial_dict.yml

gboriginal.jpg gboriginal.jpg gbaxis.jpg board.png

detect_board.cpp 的参数:

创建 cv::aruco::Board 对象需要指定环境中每个标记的角点位置。然而,在许多情况下,标定板只是同一平面上呈网格排列的一组标记,因此可以很容易地打印和使用。

幸运的是,aruco 模块提供了创建和打印这些类型标记的基本功能。

cv::aruco::GridBoard 类是一个专用类,它继承自 cv::aruco::Board 类,表示所有标记位于同一平面并呈网格排列的标定板,如下图所示:

具体而言,网格标定板中的坐标系位于标定板平面上,以标定板左下角为中心,Z 轴朝外指向,如下图所示(X:红色,Y:绿色,Z:蓝色):

可以使用以下参数定义一个 cv::aruco::GridBoard 对象:

  • X 方向上的标记数量。
  • Y 方向上的标记数量。
  • 标记边长。
  • 标记间距长度。
  • 标记的字典。
  • 所有标记的 id(X*Y 个标记)。

可以使用 cv::aruco::GridBoard 构造函数从这些参数轻松创建该对象:

  • 第一和第二个参数分别是 X 和 Y 方向上的标记数量。
  • 第三和第四个参数分别是标记边长和标记间距。它们可以用任何单位提供,但请记住,此标定板估计的位姿将以相同的单位来度量(通常使用米)。
  • 最后,提供标记的字典。

因此,这个标定板将由 5x7=35 个标记组成。每个标记的 id 默认按从 0 开始的升序分配,因此它们将是 0、1、2、……、34。

在创建网格标定板之后,我们可能想要打印并使用它。有两种方法可以做到这一点:

  1. 使用脚本 apps/pattern-tools/generate_pattern.py,参见 @subpage tutorial_camera_calibration_pattern。
  2. 使用函数 cv::aruco::GridBoard::generateImage()。

cv::aruco::GridBoard::generateImage() 函数在 cv::aruco::GridBoard 类中提供,可以使用以下代码调用:

  • 第一个参数是输出图像的大小(以像素为单位)。本例中为 600x500 像素。如果这与标定板尺寸不成比例,它将居中于图像中。
  • boardImage:带有标定板的输出图像。
  • 第三个参数是(可选的)边距(以像素为单位),这样不会有任何标记碰到图像边界。本例中边距为 10。
  • 最后是标记边框的大小,类似于 generateImageMarker() 函数。默认值为 1。

samples/cpp/tutorial_code/objectDetection/create_board.cpp 中包含了一个完整的标定板创建示例。

输出图像将类似于这样:

现在,这些示例通过 cv::CommandLineParser 从命令行获取输入。对于此文件,示例参数如下:

"_output_path_/aboard.png" -w=5 -h=7 -l=100 -s=10 -d=10

gbmarkersaxis.jpg gbocclusion.jpg

ArUco 标定板也可用于改进标记的检测。如果我们已经检测到了属于该标定板的标记子集,就可以利用这些标记和标定板的布局信息,尝试找到之前未被检测到的标记。

这可以使用 cv::aruco::refineDetectedMarkers() 函数来完成,该函数应在调用 cv::aruco::ArucoDetector::detectMarkers() 之后调用。

该函数的主要参数是检测到标记的原始图像、标定板对象、检测到的标记角点、检测到的标记 id 以及被拒绝的标记角点。

被拒绝的角点可以从 cv::aruco::ArucoDetector::detectMarkers() 函数中获得,也称为标记候选项(marker candidates)。这些候选项是在原始图像中找到的方形形状,但未能通过识别步骤(即其内部编码存在太多错误),因此没有被识别为标记。

然而,这些候选项有时实际上是真正的标记,由于图像中存在大量噪声、分辨率非常低或其他影响二进制码提取的相关问题而未被正确识别。cv::aruco::ArucoDetector::refineDetectedMarkers() 函数在这些候选项与标定板缺失标记之间寻找对应关系。此搜索基于两个参数:

  • 候选项与缺失标记投影之间的距离。要获得这些投影,至少需要检测到标定板上的一个标记。如果提供了相机参数(相机矩阵和畸变系数),则使用它们来获得投影。如果没有提供,则从局部单应性(homography)获得投影,并且仅允许平面标定板(即所有标记角点的 Z 坐标应当相同)。refineDetectedMarkers() 中的 minRepDistance 参数确定了候选项角点与投影标记角点之间的最小欧几里得距离(默认值为 10)。

  • 二进制编码。如果某个候选项超过了最小距离条件,将再次分析其内部比特位,以确定它是否实际上就是投影的标记。然而,在这种情况下,条件没有那么严格,允许的错误比特位数可以更高。这由 errorCorrectionRate 参数指示(默认值为 3.0)。如果提供负值,则完全不分析内部比特位,仅评估角点距离。

这是使用 cv::aruco::ArucoDetector::refineDetectedMarkers() 函数的一个示例:

还需要注意的是,在某些情况下,如果一开始检测到的标记数量太少(例如只有 1 或 2 个标记),缺失标记的投影质量可能会很差,从而产生错误的对应关系。

有关更详细的实现,请参见模块示例。