![]() |
OpenCV 4.12.0
开源计算机视觉
|
下一个教程: 检测ArUco标记板
| 原始作者 | Sergio Garrido, Alexander Panov |
| 兼容性 | OpenCV >= 4.7.0 |
姿态估计在许多计算机视觉应用中至关重要:机器人导航、增强现实等等。这个过程基于寻找真实环境中点及其二维图像投影之间的对应关系。这通常是困难的一步,因此,使用合成标记或参考标记来简化这一过程是常见的。
最流行的方法之一是使用二进制方形参考标记。这些标记的主要优点是单个标记提供了足够的对应关系(其四个角点)来获取相机姿态。此外,内部的二进制编码使其特别鲁棒,允许应用错误检测和纠正技术。
aruco模块基于 ArUco库,这是一个由Rafael Muñoz和Sergio Garrido开发的用于检测方形参考标记的流行库 [100]。
aruco功能包含在
ArUco标记是一种合成的方形标记,由宽阔的黑色边框和内部的二进制矩阵组成,二进制矩阵决定其标识符(id)。黑色边框便于图像中的快速检测,二进制编码则允许其识别以及应用错误检测和纠正技术。标记尺寸决定了内部矩阵的大小。例如,4x4的标记尺寸由16位组成。
ArUco标记的一些例子
必须指出的是,标记在环境中可能会被旋转,然而,检测过程需要能够确定其原始旋转,以便每个角点都能明确识别。这也是基于二进制编码完成的。
标记字典是在特定应用中考虑的标记集合。它只是每个标记的二进制编码列表。
字典的主要属性是字典大小和标记大小。
aruco模块包含一些预定义的字典,涵盖了不同字典大小和标记大小的范围。
人们可能会认为标记ID是将二进制编码转换为十进制数所得的数字。然而,这是不可能的,因为对于较大的标记尺寸,位数太高,管理如此巨大的数字不切实际。相反,标记ID仅仅是其所属字典中的标记索引。例如,字典中的前5个标记的ID分别为:0、1、2、3和4。
有关字典的更多信息,请参阅“选择字典”部分。
在检测之前,需要打印标记以便将其放置在环境中。可以使用 generateImageMarker() 函数生成标记图像。
例如,让我们分析以下调用
首先,通过选择aruco模块中预定义的字典之一来创建cv::aruco::Dictionary对象。具体来说,这个字典由250个标记和6x6位的标记尺寸组成(cv::aruco::DICT_6X6_250)。
cv::aruco::generateImageMarker() 的参数是:
cv::aruco::Dictionary对象。cv::aruco::DICT_6X6_250的标记23。请注意,每个字典由不同数量的标记组成。在这种情况下,有效ID范围从0到249。任何超出有效范围的特定ID都会引发异常。生成的图像是
一个完整的工作示例包含在 `samples/cpp/tutorial_code/objectDetection/` 目录下的 `create_marker.cpp` 中。
现在,示例使用cv::CommandLineParser从命令行获取输入。对于此文件,示例参数将如下所示
create_marker.cpp 的参数
给定包含ArUco标记的图像,检测过程必须返回检测到的标记列表。每个检测到的标记包括
标记检测过程包括两个主要步骤
考虑以下图像
以及此图像在照片中的打印件
这些是检测到的标记(绿色)。请注意,有些标记已旋转。小的红色方块表示标记的左上角
这些是在识别步骤中被拒绝的标记候选(粉色)
在aruco模块中,检测是在cv::aruco::ArucoDetector::detectMarkers()函数中执行的。这个函数是模块中最重要的,因为所有其他功能都基于cv::aruco::ArucoDetector::detectMarkers()返回的已检测标记。
标记检测的一个例子
当你创建一个 cv::aruco::ArucoDetector 对象时,你需要向构造函数传递以下参数:
cv::aruco::DICT_6X6_250)。cv::aruco::DetectorParameters的对象。此对象包含在检测过程中可自定义的所有参数。这些参数将在下一节中解释。cv::aruco::ArucoDetector::detectMarkers()的参数是
markerCorners和markerIds结构中markerCorners 是检测到的标记的角点列表。对于每个标记,其四个角点按原始顺序(顺时针从左上角开始)返回。因此,第一个角点是左上角,其次是右上角、右下角和左下角。markerIds是markerCorners中每个检测到的标记的ID列表。请注意,返回的markerCorners和markerIds向量大小相同。rejectedCandidates是返回的标记候选列表,即被找到并考虑但未包含有效标记的形状。每个候选也由其四个角点定义,其格式与markerCorners参数相同。此参数可以省略,仅用于调试目的和“refind”策略(参见cv::aruco::ArucoDetector::refineDetectedMarkers())。在cv::aruco::ArucoDetector::detectMarkers()之后,您可能想要做的下一件事是检查您的标记是否已正确检测。幸运的是,aruco模块提供了一个函数来在输入图像中绘制检测到的标记,此函数是drawDetectedMarkers()。例如
outputImage是用于绘制标记的输入/输出图像(通常与检测标记的图像相同)。markerCorners和markerIds是cv::aruco::ArucoDetector::detectMarkers()函数返回的检测到的标记结构。
请注意,此函数仅用于可视化,可以省略其使用。
通过这两个函数,我们可以创建一个基本的标记检测循环,从我们的相机中检测标记
请注意,一些可选参数已被省略,例如检测参数对象和被拒绝候选的输出向量。
一个完整的工作示例包含在 `samples/cpp/tutorial_code/objectDetection/` 目录下的 `detect_markers.cpp` 中。
现在,示例使用cv::CommandLineParser从命令行获取输入。对于此文件,示例参数将如下所示
detect_markers.cpp 的参数
检测到标记后,您可能想要做的下一件事就是使用它们来获取相机姿态。
要执行相机姿态估计,您需要知道相机的校准参数。这些参数包括相机矩阵和畸变系数。如果您不知道如何校准相机,可以查看OpenCV的`calibrateCamera()`函数和校准教程。您也可以使用aruco模块校准相机,如ArUco和ChArUco校准教程中所述。请注意,除非相机光学元件被修改(例如改变其焦点),否则这只需执行一次。
校准的结果是得到一个相机矩阵:一个3x3的矩阵,包含焦距和相机中心坐标(又称内参),以及畸变系数:一个包含5个或更多元素的向量,用于模拟相机产生的畸变。
当您使用 ArUco 标记估计姿态时,您可以单独估计每个标记的姿态。如果您想从一组标记中估计一个姿态,请使用 ArUco 标记板(请参阅检测 ArUco 标记板教程)。使用 ArUco 标记板而不是单个标记允许部分标记被遮挡。
相机相对于标记的姿态是从标记坐标系到相机坐标系的3D变换。它由旋转向量和平移向量指定。OpenCV提供了cv::solvePnP()函数来完成此操作。
corners参数是cv::aruco::ArucoDetector::detectMarkers()函数返回的标记角点向量。camMatrix和distCoeffs是相机校准过程中创建的相机校准参数。rvecs和tvecs分别是corners中每个检测到的标记的旋转向量和平移向量。该函数假设的标记坐标系放置在标记中心(默认情况下)或左上角,Z轴指向外,如下图所示。轴-颜色对应关系为:X:红色,Y:绿色,Z:蓝色。请注意此图像中旋转标记的轴方向。
OpenCV提供了一个函数来绘制如上图所示的轴线,以便检查姿态估计
imageCopy是输入/输出图像,其中将显示检测到的标记。camMatrix和distCoeffs是相机校准参数。rvecs[i]和tvecs[i]分别是每个检测到的标记的旋转向量和平移向量。示例视频
一个完整的工作示例包含在 `samples/cpp/tutorial_code/objectDetection/` 目录下的 `detect_markers.cpp` 中。
现在,示例使用cv::CommandLineParser从命令行获取输入。对于此文件,示例参数将如下所示
detect_markers.cpp 的参数
aruco模块提供了Dictionary类来表示标记字典。
除了字典中的标记大小和标记数量之外,字典还有一个重要的参数——标记间距离。标记间距离是字典标记之间的最小汉明距离,它决定了字典检测和纠错的能力。
一般来说,较小的字典大小和较大的标记大小会增加标记间距离,反之亦然。然而,由于需要从图像中提取的位数较多,较大尺寸标记的检测更加困难。
例如,如果您的应用程序只需要10个标记,那么使用仅由这10个标记组成的字典比使用由1000个标记组成的字典更好。原因是10个标记组成的字典将具有更高的标记间距离,因此对错误更具鲁棒性。
因此,aruco模块提供了几种选择标记字典的方式,以便您可以提高系统的鲁棒性
这是选择字典最简单的方法。aruco模块包含一组预定义字典,其标记大小和标记数量各不相同。例如
cv::aruco::DICT_6X6_250是一个预定义标记字典的示例,包含6x6位和总共250个标记。
在所有提供的字典中,建议选择适合您应用程序的最小字典。例如,如果您需要200个6x6位的标记,最好使用cv::aruco::DICT_6X6_250而不是cv::aruco::DICT_6X6_1000。字典越小,标记间距离越大。
可用预定义字典的列表可以在PredefinedDictionaryType枚举的文档中找到。
字典可以自动生成,以调整所需的标记数量和位数,从而优化标记间距离
这将生成一个由36个5x5位标记组成的自定义字典。该过程可能需要几秒钟,具体取决于参数(对于较大的字典和更多的位数,速度会较慢)。
您还可以使用`opencv/samples/cpp`中的`aruco_dict_utils.cpp`示例。此示例计算生成字典的最小汉明距离,并允许您创建抗反射标记。
最后,字典可以手动配置,以便可以使用任何编码。为此,需要手动分配cv::aruco::Dictionary对象参数。必须指出的是,除非您有特殊原因手动执行此操作,否则最好使用前面提到的替代方案之一。
bytesList是包含所有标记代码信息的数组。markerSize是每个标记维度的尺寸(例如,对于5x5位的标记,为5)。最后,maxCorrectionBits是标记检测过程中可以纠正的最大错误位数。如果此值过高,可能会导致大量的误报。
bytesList中的每一行都代表字典中的一个标记。然而,标记并非以其二进制形式存储,而是以一种特殊格式存储,以简化其检测。
幸运的是,可以使用静态方法Dictionary::getByteListFromBits()轻松地将标记转换为这种形式。
例如
cv::aruco::ArucoDetector 的参数之一是 cv::aruco::DetectorParameters 对象。此对象包含在标记检测过程中可自定义的所有选项。
本节描述了每个检测器参数。这些参数可以根据它们所涉及的过程进行分类
标记检测过程中的第一步是对输入图像进行自适应阈值处理。
例如,上面使用的示例图像的阈值图像是
此阈值处理可以使用以下参数进行自定义
adaptiveThreshWinSizeMin和adaptiveThreshWinSizeMax参数表示自适应阈值处理中阈值窗口大小(以像素为单位)的选择区间(更多详情请参阅OpenCV threshold()和adaptiveThreshold()函数)。
参数adaptiveThreshWinSizeStep表示窗口大小从adaptiveThreshWinSizeMin到adaptiveThreshWinSizeMax的增量。
例如,对于值adaptiveThreshWinSizeMin = 5,adaptiveThreshWinSizeMax = 21,adaptiveThreshWinSizeStep = 4,将有5个阈值处理步骤,窗口大小分别为5、9、13、17和21。在每个阈值图像上,都将提取标记候选。
如果标记尺寸过大,窗口尺寸过低可能会“破坏”标记边框,导致无法检测到,如下图所示
另一方面,如果标记太小,过大的值也会产生相同的影响,并可能降低性能。此外,该过程将趋于全局阈值处理,导致自适应优势的丧失。
最简单的情况是adaptiveThreshWinSizeMin和adaptiveThreshWinSizeMax使用相同的值,这会产生一个单一的阈值处理步骤。然而,通常最好使用一系列值作为窗口大小,尽管许多阈值处理步骤也会显著降低性能。
adaptiveThreshConstant参数表示阈值操作中添加的常数值(更多详情请参阅OpenCV threshold()和adaptiveThreshold()函数)。其默认值在大多数情况下是一个不错的选择。
阈值处理后,会检测到轮廓。然而,并非所有轮廓都被视为标记候选。它们在不同步骤中被过滤掉,以便丢弃那些极不可能成为标记的轮廓。本节中的参数自定义此过滤过程。
必须指出的是,在大多数情况下,这是一个在检测能力和性能之间取得平衡的问题。所有被考虑的轮廓都将在后续阶段进行处理,而这些阶段通常计算成本更高。因此,在此阶段丢弃无效候选比在后期阶段更可取。
另一方面,如果过滤条件过于严格,真实的标记轮廓可能会被丢弃,从而无法检测到。
这些参数确定标记的最小和最大尺寸,具体来说是最小和最大标记周长。它们不是以绝对像素值指定,而是相对于输入图像的最大维度指定。
例如,一个尺寸为640x480的图像,最小相对标记周长为0.05,将导致最小标记周长为640x0.05 = 32像素,因为640是图像的最大维度。maxMarkerPerimeterRate参数也适用相同规则。
如果minMarkerPerimeterRate过低,检测性能会显著下降,因为会有更多的轮廓被考虑用于后续阶段。对于maxMarkerPerimeterRate参数来说,这种惩罚不那么明显,因为通常小轮廓比大轮廓多得多。minMarkerPerimeterRate设为0,maxMarkerPerimeterRate设为4(或更大)将等同于考虑图像中的所有轮廓,然而出于性能原因,不推荐这样做。
对每个候选应用多边形近似,并且只接受那些近似于方形的。此值确定多边形近似可能产生的最大误差(有关更多信息,请参阅approxPolyDP()函数)。
此参数相对于候选的长度(以像素为单位)。因此,如果候选的周长为100像素,而polygonalApproxAccuracyRate的值为0.04,则最大误差将为100x0.04=5.4像素。
在大多数情况下,默认值工作良好,但对于高度扭曲的图像可能需要更高的误差值。
同一标记中任意一对角点之间的最小距离。以标记周长为相对值表示。像素中的最小距离为 周长 * minCornerDistanceRate。
两个不同标记的任何一对角点之间的最小距离。它以两个标记中最小标记周长为相对值表示。如果两个候选标记太近,则忽略较小的那个。
标记角点到图像边框的最小距离(以像素为单位)。如果遮挡很小,则部分被图像边框遮挡的标记可以正确检测。然而,如果一个角点被遮挡,返回的角点通常会放置在图像边框附近的错误位置。
如果标记角点的位置很重要,例如如果您想进行姿态估计,最好丢弃任何角点离图像边框太近的标记。否则,则没有必要。
候选检测后,对每个候选的位进行分析,以确定它们是否是标记。
在分析二进制代码本身之前,需要提取位。为此,校正透视畸变,并使用Otsu阈值对结果图像进行阈值处理,以分离黑色和白色像素。
这是去除标记透视畸变后得到的图像示例
然后,图像被分割成一个网格,其单元格数量与标记中的位数相同。在每个单元格中,计算黑色和白色像素的数量以确定分配给该单元格的位值(根据多数值)
有几个参数可以自定义此过程
此参数表示标记边框的宽度。它相对于每个位的尺寸。因此,值为2表示边框的宽度为两个内部位。
此参数需要与您使用的标记的边框大小一致。边框大小可以在标记绘制函数(如generateImageMarker())中配置。
此值决定了执行Otsu阈值处理的像素值的最小标准差。如果标准差较低,则可能意味着整个方块都是黑色(或白色),应用Otsu就没有意义。如果是这种情况,所有位都设置为0(或1),具体取决于平均值是高于还是低于128。
此参数确定校正透视畸变后所获得的图像中每单元格的像素数(包括边框)。这是上图中红色方块的大小。
例如,假设我们正在处理5x5位且边框大小为1位的标记(参见markerBorderBits)。那么,每维度的总单元格/位数是5 + 2*1 = 7(边框必须计算两次)。总单元格数是7x7。
如果perspectiveRemovePixelPerCell的值为10,那么获得的图像大小将是10*7 = 70 -> 70x70像素。
此参数的较高值可以改善位提取过程(在一定程度上),但可能会影响性能。
提取每个单元格的位时,会计算黑白像素的数量。通常,不建议考虑所有单元格像素。相反,最好忽略单元格边缘的一些像素。
原因是,在消除透视畸变后,单元格的颜色通常不会完美分离,白色单元格可能会侵入黑色单元格的一些像素(反之亦然)。因此,最好忽略一些像素,以避免计算错误的像素。
例如,在下图所示
只考虑绿色方块内的像素。从右图可以看出,结果像素包含的来自相邻单元格的噪声量较低。perspectiveRemoveIgnoredMarginPerCell参数表示红色和绿色方块之间的差异。
此参数相对于单元格的总大小。例如,如果单元格大小为40像素,并且此参数的值为0.1,则在单元格中忽略40*0.1=4像素的边距。这意味着在每个单元格中实际分析的总像素数将是32x32,而不是40x40。
提取位后,下一步是检查提取的代码是否属于标记字典,并在必要时执行错误校正。
标记边框的位应该是黑色的。此参数指定边框中允许的错误位数,即边框中白色位的最大数量。它相对于标记中的总位数表示。
每个标记字典都有一个理论上可纠正的最大位数(Dictionary.maxCorrectionBits)。但是,此值可以通过errorCorrectionRate参数进行修改。
例如,如果允许纠正的位数(对于所使用的字典)为6,并且errorCorrectionRate的值为0.5,则实际可以纠正的最大位数是6*0.5=3位。
此值对于降低错误校正能力以避免误报非常有用。
检测并识别标记后,最后一步是对角点位置执行亚像素精细化(参见OpenCV cornerSubPix()和cv::aruco::CornerRefineMethod)。
请注意,此步骤是可选的,仅当标记角点的位置必须精确时(例如用于姿态估计)才有意义。这通常是一个耗时的步骤,因此默认情况下是禁用的。
此参数决定是否执行角点亚像素处理,以及如果执行则使用哪种方法。如果不需要精确的角点,可以禁用此功能。可能的值包括CORNER_REFINE_NONE、CORNER_REFINE_SUBPIX、CORNER_REFINE_CONTOUR和CORNER_REFINE_APRILTAG。
此参数决定了角点精细化过程的最大窗口大小。
高值可能导致图像的近角包含在窗口区域中,从而在过程中标记的角移动到不同且不正确的位置。此外,它可能会影响性能。如果ArUco标记太小,窗口大小可能会减小,请检查cv::aruco::DetectorParameters::relativeCornerRefinmentWinSize。最终窗口大小计算为:min(cornerRefinementWinSize, averageArucoModuleSize*relativeCornerRefinmentWinSize),其中averageArucoModuleSize是ArUco标记的平均模块大小(像素)。
角点精细化的动态窗口大小,相对于ArUco模块大小(默认0.3)。
最终的窗口大小计算为:min(cornerRefinementWinSize, averageArucoModuleSize*relativeCornerRefinmentWinSize),其中averageArucoModuleSize是ArUco标记的平均模块大小(像素)。对于彼此相距较远的标记,将参数值增加到0.4-0.5可能很有用。对于彼此相距较近的标记,将参数值减小到0.1-0.2可能很有用。
这两个参数决定了亚像素精细化过程的停止标准。cornerRefinementMaxIterations表示最大迭代次数,而cornerRefinementMinAccuracy表示停止过程前的最小误差值。
如果迭代次数过高,可能会影响性能。另一方面,如果过低,可能导致亚像素精细化效果不佳。