Skip to content

ChArUco 标定板检测

ArUco 标记和标定板由于其快速的检测和通用性而非常有用。然而,ArUco 标记的问题之一是其角点位置的精度不是太高,即使应用了亚像素精化也是如此。

相反,棋盘格图案的角点可以更精确地精化,因为每个角点都被两个黑色方块包围。然而,查找棋盘格图案不如查找 ArUco 标定板通用:它必须完全可见,不允许遮挡。

ChArUco 标定板试图结合这两种方法的优势:

ArUco 部分用于插值棋盘角点的位置,从而使其具有标记标定板的通用性,因为它允许遮挡或部分可见。此外,由于插值的角点属于棋盘,它们在亚像素精度方面非常精确。

当需要高精度时,例如在相机标定中,ChArUco 标定板是比标准 ArUco 标定板更好的选择。

charucodefinition.png

在本教程中,你将学到:

  • 如何创建 ChArUco 标定板?
  • 如何在不进行相机标定的情况下检测 ChArUco 角点?
  • 如何在相机标定和位姿估计的情况下检测 ChArUco 角点?

你可以在 samples/cpp/tutorial_code/objectDetection/detect_board_charuco.cpp 中找到此代码

以下是如何实现目标列表中列出的所有内容的示例代码。

aruco 模块提供了 cv::aruco::CharucoBoard 类,它表示一个 ChArUco 标定板,并继承自 cv::aruco::Board 类。

该类以及 ChArUco 的其余功能定义在:

要定义一个 cv::aruco::CharucoBoard,需要:

  • X 和 Y 方向上棋盘方格的数量。
  • 方格边长。
  • 标记边长。
  • 标记的字典。
  • 所有标记的 id。

至于 cv::aruco::GridBoard 对象,aruco 模块可以轻松创建 cv::aruco::CharucoBoard。可以使用 cv::aruco::CharucoBoard 构造函数从这些参数轻松创建该对象:

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

每个标记的 id 默认按从 0 开始的升序分配,与 cv::aruco::GridBoard 构造函数中一样。这可以通过 board.ids 访问 ids 向量来轻松自定义,就像在 cv::aruco::Board 父类中一样。

一旦我们有了 cv::aruco::CharucoBoard 对象,就可以创建一个图像来打印它。有两种方法可以做到这一点:

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

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

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

输出图像将类似于这样:

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

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

"_output_path_/chboard.png" -w=5 -h=7 -sl=100 -ml=60 -d=10

charucoboard.png choriginal.jpg chcorners.jpg chocclusion.jpg

当你检测 ChArUco 标定板时,你实际检测的是标定板的每个棋盘角点。

ChArUco 标定板上的每个角点都分配了一个唯一的标识符(id)。这些 id 从 0 到标定板中角点的总数。 ChArUco 标定板检测的步骤可以分解为以下步骤:

  • 获取输入图像

要检测标记的原始图像。该图像是进行 ChArUco 角点亚像素精化所必需的。

  • 读取相机标定参数(仅用于带相机标定的检测)

readCameraParameters 的参数是:

  • 第一个参数是相机内参矩阵和畸变系数的路径。
  • 第二和第三个参数是 cameraMatrix 和 distCoeffs。

该函数将这些参数作为输入,并返回一个表示相机标定参数是否有效的布尔值。对于不带标定的 ChArUco 角点检测,此步骤不是必需的。

  • 检测标记并从标记插值 ChArUco 角点

ChArUco 角点的检测基于先前检测到的标记。 因此,首先检测标记,然后从标记插值 ChArUco 角点。 检测 ChArUco 角点的方法是 cv::aruco::CharucoDetector::detectBoard()。

detectBoard 的参数是:

  • image - 输入图像。
  • charucoCorners - 检测到的角点的图像位置输出列表。
  • charucoIds - charucoCorners 中每个检测到的角点的输出 id。
  • markerCorners - 检测到的标记角点的输入/输出向量。
  • markerIds - 检测到的标记的标识符的输入/输出向量。

如果 markerCorners 和 markerIds 为空,该函数将检测 ArUco 标记和 id。

如果提供了标定参数,则 ChArUco 角点的插值过程是:首先从 ArUco 标记估计一个粗略的位姿,然后将 ChArUco 角点重新投影回图像。

另一方面,如果未提供标定参数,则通过计算 ChArUco 平面与 ChArUco 图像投影之间的相应单应性来插值 ChArUco 角点。

使用单应性的主要问题是,插值对图像畸变更敏感。实际上,单应性仅使用每个 ChArUco 角点最近的标记来执行,以减少畸变的影响。

当为 ChArUco 标定板检测标记时,特别是使用单应性时,建议禁用标记的角点精化。原因是,由于棋盘方格彼此邻近,亚像素过程可能在角点位置产生重要偏差,而这些偏差会传播到 ChArUco 角点插值,导致结果不佳。

注意: 为避免偏差,棋盘方格与 ArUco 标记之间的边距应大于一个标记模块的 70%。

此外,只返回其周围两个标记都被找到的那些角点。如果周围两个标记中的任何一个未被检测到,这通常意味着存在某些遮挡或该区域的图像质量不佳。无论如何,最好不要考虑该角点,因为我们要确保插值的 ChArUco 角点非常精确。

插值 ChArUco 角点之后,会执行亚像素精化。

一旦我们插值了 ChArUco 角点,我们可能想绘制它们以查看其检测是否正确。这可以使用 cv::aruco::drawDetectedCornersCharuco() 函数轻松完成:

  • imageCopy 是将要绘制角点的图像(通常与检测到角点的图像相同)。
  • outputImage 将是带有绘制角点的 inputImage 的副本。
  • charucoCorners 和 charucoIds 是来自 cv::aruco::CharucoDetector::detectBoard() 函数的检测到的 ChArUco 角点。
  • 最后,最后一个参数是我们想要绘制角点的(可选)颜色,类型为 cv::Scalar。

对于此图像:

结果将是:

在存在遮挡的情况下,如下图所示,尽管一些角点清晰可见,但由于遮挡,并非其周围所有标记都被检测到,因此它们没有被插值:

示例视频:

视频演示

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

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

-w=5 -h=7 -sl=0.04 -ml=0.02 -d=10 -v=/path_to_opencv/opencv/doc/tutorials/objdetect/charuco_detection/images/choriginal.jpg

chaxis.jpg

ChArUco 标定板的最终目标是为高精度标定或位姿估计找到非常精确的角点。

aruco 模块提供了一个可以轻松执行 ChArUco 位姿估计的函数。与 cv::aruco::GridBoard 中一样,cv::aruco::CharucoBoard 的坐标系位于标定板平面上,Z 轴朝内指向,并以标定板左下角为中心。

注意: 在 OpenCV 4.6.0 之后,标定板的坐标系有一个不兼容的变更,现在坐标系位于标定板平面上,Z 轴朝向平面内(以前该轴朝向平面外)。 CW 顺序的 objPoints 对应于朝向平面内的 Z 轴。 CCW 顺序的 objPoints 对应于朝向平面外的 Z 轴。 参见 PR https://github.com/opencv/opencv_contrib/pull/3174

要对 ChArUco 标定板进行位姿估计,你应当使用 cv::aruco::CharucoBoard::matchImagePoints() 和 cv::solvePnP():

  • charucoCorners 和 charucoIds 参数是来自 cv::aruco::CharucoDetector::detectBoard() 函数的检测到的 ChArUco 角点。
  • cameraMatrix 和 distCoeffs 是位姿估计所必需的相机标定参数。
  • 最后,rvec 和 tvec 参数是 ChArUco 标定板的输出位姿。
  • 如果位姿被正确估计,cv::solvePnP() 返回 true,否则返回 false。失败的主要原因是用于位姿估计的角点不够,或者它们位于同一条直线上。

可以使用 cv::drawFrameAxes() 绘制坐标轴来检查位姿是否被正确估计。结果将是:(X:红色,Y:绿色,Z:蓝色)

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

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

-w=5 -h=7 -sl=0.04 -ml=0.02 -d=10
-v=/path_to_opencv/opencv/doc/tutorials/objdetect/charuco_detection/images/choriginal.jpg
-c=/path_to_opencv/opencv/samples/cpp/tutorial_code/objectDetection/tutorial_camera_charuco.yml