[OpenCV] aruco Markers识别

2023-05-16

reference : http://docs.opencv.org/3.1.0/d5/dae/tutorial_aruco_detection.html

        姿态估计(Pose estimation)在计算机视觉领域扮演着十分重要的角色:机器人导航、增强现实以及其它。这一过程的基础是找到现实世界和图像投影之间的对应点。这通常是很困难的一步,因此我们常常用自己制作的或基本的Marker来让这一切变得更容易。

        最为流行的一个途径是基于二进制平方的标记。这种Marker的主要便利之处在于,一个Marker提供了足够多的对应(四个角)来获取相机的信息。同样的,内部的二进制编码使得算法非常健壮,允许应用错误检测和校正技术的可能性。

       aruco模块基于ArUco库,这是一个检测二进制marker的非常流行的库,是由Rafael Muñoz和Sergio Garrido完成的。

       aruco的函数包含在c++ #include <opencv2/aruco.hpp>

Marker和字典

        一个ArUco marker是一个二进制平方标记,它由一个宽的黑边和一个内部的二进制矩阵组成,内部的矩阵决定了它们的id。黑色的边界有利于快速检测到图像,二进制编码可以验证id,并且允许错误检测和矫正技术的应用。marker的大小决定了内部矩阵的大小。例如,一个4x4的marker由16bits组成。

        一些ArUco markers的例子:

markers.jpg
Example of markers images

        应当注意到,我们需要检测到一个Marker在空间中发生了旋转,但是,检测的过程需要确定它的初始角度,所以每个角落需要是明确的,不能有歧义,保证上述这点也是靠二进制编码完成的。

        markers的字典是在一个特殊应用中使用到的marker的集合。这仅仅是每个marker的二进制编码的链表。

        字典的主要性质是字典的大小和marker的大小:

  • 字典的大小是组成字典的marker的数量
  • marker的大小是这些marker的尺寸(位的个数)

        aruco模块包含了一些预定义的字典,这些字典涵盖了一系列的字典大小和Marker尺寸。

       有些人可能会认为Marker的id是从十进制转成二进制的。但是,考虑到较大的marker会有较多的位数,管理如此多的数据不那么现实,这并不可能。反之,一个marker的id仅需是marker在它所在的字典的下标。例如,一个字典里的五个marker的id是:0,1,2,3和4。

        更多有关字典的信息在“选择字典”部分提及。


创建Marker

        在检测之前,我们需要打印marker,以把它们放到环境中。marker的图像可以使用drawMarker()函数生成。

        例如,让我们分析一下如下的调用:

``` c++ 

    cv::Mat markerImage; cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);

    cv::aruco::drawMarker(dictionary, 23, 200, markerImage, 1); ```


        首先,我们通过选择aruco模块中一个预定义的字典来创建一个字典对象,具体而言,这个字典是由250个marker组成的,每个marker的大小为6x6bits(DICT_6X6_250)

         drawMarker的参数如下:

  • 第一个参数是之前创建的字典对象。
  • 第二个参数是marker的id,在这个例子中选择的是字典DICT_6X6_250第23个marker。注意到每个字典是由不同数目的Marker组成的,在这个例子中,有效的Id数字范围是0到249。不在有效区间的特定id将会产生异常。
  • 第三个参数,200,是输出Marker图像的大小。在这个例子中,输出的图像将是200x200像素大小。注意到这一参数需要满足能够存储特定字典 的所有位。所以,举例而言,你不能为6x6大小的marker生成一个5x5图像(这还没有考虑到Marker的边界)。除此之外,为了避免变形,这一参数最好和位数+边界的大小成正比,至少要比marker的大小大得多(如这个例子中的200),这样变形就不显著了。
  • 第四个参数是输出的图像。
  • 最终,最后一个参数是一个可选的参数,它指定了Marer黑色边界的大小。这一大小与位数数目成正比。例如,值为2意味着边界的宽度将会是2的倍数。默认的值为1。
  • 生成的图像如下:
  marker23.jpg
Generated marker

        详细的例子在模块演示文件夹中的 create_marker.cpp 

检测Marker

给定一个可以看见ArUco marker的图像,检测程序应当返回检测到的marker的列表。每个检测到的marker包括:

  • 图像四个角的位置(按照原始的顺序)
  • marker的Id

marker检测过程由以下两个主要步骤构成:

  1. 检测有哪些marker。在这一阶段我们分析图像,以找到哪些形状可以被识别为Markers。首先要做的是利用自适应性阈值来分割marker,然后从阈值化的图像中提取外形轮廓,并且舍弃那些非凸多边形的,以及那些不是方形的。我们还使用了一些额外的滤波(来剔除那些过小或者过大的轮廓,过于相近的凸多边形,等)
  2. 检测完marker之后,我们有必要分析它的内部编码来确定它们是否确实是marker。此步骤首先提取每个标记的标记位。为了达到这个目的,首先,我们需要对图像进行透视变换,来得到它规范的形态(正视图)。然后,对规范的图像用Ossu阈值化以分离白色和黑色位。这一图像根据marker大小和边界大小被分为不同格子,我们统计落在每个格子中的黑白像素数目来决定这是黑色还是白色的位。最终,我们分析这些位数来决定这个marker是属于哪个特定字典的,如果有必要的话,需要对错误进行检测。
        考虑如下图像:
singlemarkersoriginal.png
Original image with markers

这些是检测出来的marker(用绿色标记)

singlemarkersdetection.png
Image with detected markers

以下是识别阶段被剔除的Marker候选(用粉红色标记):

singlemarkersrejected.png
Image with rejected candidates

        在aruco模块,检测是由detectMarkers() 函数完成的,这一函数是这个模块中最重要的函数,因为剩下的所有函数操作都基于detectMarkers()返回的检测出的markers。

        一个marker检测的例子:

``` c++ 

cv::Mat inputImage;

vector< int > markerIds; 

vector< vector<Point2f> > markerCorners, rejectedCandidates; 

cv::aruco::DetectorParameters parameters; 

cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250); 

cv::aruco::detectMarkers(inputImage, dictionary, markerCorners, markerIds, parameters, rejectedCandidates); ```

detectMarkers 的参数为:

  • 第一个参数是待检测marker的图像。
  • 第二个参数是字典对象,在这一例子中是之前定义的字典 (DICT_6X6_250).
  • 检测出的markers存储在markerCorners  markerIds结构中:
    • markerCorners 是检测出的图像的角的列表。对于每个marker,将返回按照原始顺序排列的四个角(从左上角顺时针开始)。因此,第一个点是左上角的角,紧接着右上角、右下角和左下角。
    • markerIds 是在markerCorners检测出的所有maker的id列表.注意返回的markerCornersmarkerIds 向量具有相同的大小。 
  • 第四个参数是类型的对象 DetectionParameters. 这一对象包含了检测阶段的所有参数。这一参数将在 下一章节详细介绍。
  • 最后的参数, rejectedCandidates, 返回了所有的marker候选, 例如, 那些被检测出来的不是有效编码的方形。每个候选同样由四个角定义, 它的 形式和markerCorners的参数一样。这一参数可以省略,它仅仅用于debug阶段,或是用于“再次寻找”策略(见refineDetectedMarkers())

 detectMarkers()之后,接下来你想要做的事情可能是检查你的marker是否被正确地检测出来了。幸运的是,aruco模块提供了一个函数,它能在输入图像中来绘制检测出来的markers,这个函数就是drawDetectedMarkers() ,例子如下:

``` c++ cv::Mat outputImage cv::aruco::drawDetectedMarkers(image, markerCorners, markerIds); ```

  • image 是输入/输出图像,程序将在这张图上绘制marker。(它通常就是检测出marker的那张图像)
  • markerCorners 和 markerIds 是检测出marker的结构,它们的格式和 detectMarkers()函数提供的一样。
singlemarkersdetection.png
Image with detected markers

       注意到这个函数仅仅用于可视化,而没有别的什么用途。

       使用这两个函数我们完成了基本的marker识别步骤,我们可以从相机中检测出Marker了。

``` c++ 

cv::VideoCapture inputVideo; 

inputVideo.open(0); 

cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250); 

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) 

                cv::aruco::drawDetectedMarkers(imageCopy, corners, ids);

        cv::imshow("out", imageCopy); 

        char key = (char) cv::waitKey(waitTime); 

        if (key == 27) break; 

} ```


       注意到这里忽略了有些可选的参数,比如检测参数对象、以及输出的被剔除的候选对象向量。

       完整的工程代码包含在模块样例文件夹中的detect_markers.cpp 


Pose检测

        接下来你想要做的应当是通过Marker检测来获取相机pose。

        为了展现相机的Pose检测,你需要知道你的相机的校准(Calibration)参数。这是一个相机矩阵和畸变系数。如果你不知道如何校准你的相机,你可以看一看calibrateCamera() 函数,以及OpenCV的校准教程。你同样可以使用aruco模块来校准你的相机,这在使用aruco进行校准的教程中将会介绍。注意这个过程只需要做一次,除非你的相机的光学性质发生了改变(例如调焦)

        最后,在校准之后我们得到的是相机矩阵:这是一个3x3的矩阵,包含了焦距和相机中心坐标(相机的内参),以及畸变系数:一个包含五个以上元素的向量,它描述的是相机产生的畸变。

        当你用ArUco marker来检测相机Pose时,你可以单独地检测每个Marker的pose。如果你想要从一堆Marker里检测出一个pose,你需要的是aruco板。(参见ArUco板教程)

        涉及到marker的相机pose是一个从marker坐标系统到相机坐标系统的三维变换。这是由一个旋转和一个平移向量确定的(参见 solvePnP() 函数)

        aruco模块提供了一个函数,用来检测所有探测到的Marker的pose。


``` c++ 

Mat cameraMatrix, distCoeffs; 

... 

vector< Vec3d > rvecs, tvecs; 

cv::aruco::estimatePoseSingleMarkers(corners, 0.05, cameraMatrix, distCoeffs, rvecs, tvecs); 

```

  • corners 参数是marker的角向量,是由detectMarkers() 函数返回的。
  • 第二个参数是marker的大小(单位是米或者其它)。注意Pose检测的平移矩阵单位都是相同的。
  • cameraMatrix 和 distCoeffs 是需要求解的相机校准参数。
  • rvecs 和 tvecs 分别是每个markers角的旋转和平移向量。

        这一函数获取的marker坐标系统处在marker重心,Z坐标指向纸面外部,如下图所示。坐标的颜色为,X:红色,Y:绿色,Z:蓝色。

singlemarkersaxis.png
Image with axis drawn

        aruco模块提供了一个函数绘制上图中的坐标,所以我们可以检查pose检测的正确性。

``` c++ 

cv::aruco::drawAxis(image, cameraMatrix, distCoeffs, rvec, tvec, 0.1); 

```

  • image 是输入/输出图像,坐标将会在这张图像上绘制(通常就是检测marker的那张图像)。
  • cameraMatrix 和 distCoeffs 是相机校准参数。
  • rvec 和 tvec 是Pose参数,指明了坐标绘制的位置。
  • 最后一个参数是坐标轴的长度,和tvec单位一样(通常是米)。

针对一个marker的pose检测的基本的完整示例:

``` c++ 

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);

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) { 

                cv::aruco::drawDetectedMarkers(imageCopy, corners, ids);

                vector< Mat > rvecs, tvecs; 

                cv::aruco::estimatePoseSingleMarkers(corners, 0.05, cameraMatrix, distCoeffs, rvecs, tvecs); // draw axis for each marker 

                for(int i=0; i<ids.size(); i++) 

                        cv::aruco::drawAxis(imageCopy, cameraMatrix, distCoeffs, rvecs[i], tvecs[i], 0.1); 

        }

        cv::imshow("out", imageCopy); 

        char key = (char) cv::waitKey(waitTime); 

        if (key == 27) break; 

} ```

        样例视频:

        ![ArUco markers detection video](http://img.youtube.com/vi/IsXWrcB_Hvs/0.jpg)

        完整的工程代码包含在模块样例文件夹中的 detect_markers.cpp

选择字典

        aruco模块提供了Dictionary类来描述marker的字典。

        除了marker大小和字典中的marker数目,字典还有一个很重要的参数,就是内部marker的距离。内部marker的距离是marker之间的最小距离,它决定了字典错误检测和纠正能力。

)        一般而言,较小的字典大小和较大的marker大小将会产生更大的内部marker距离,反之亦然。但是,过大的Marker在检测中更加困难,因为我们需要从图像中提取出更多位的信息。

        例如,如果你的应用中仅仅需要10个marker,最好使用只包含10个marker的字典,而不是包含1000个marker的字典。原因在于,由10个marker组成的字典将会有更大的内部Marker距离,因此,它的容错性更强。

        结果,aruco模块包含了很多选择marker字典的途径,所以你可以让你的系统变得更加健壮。

  • 预定义的字典:

        这是选择字典最简单的办法。aruco模块包含了一系列预定义的字典,涵盖了不同的marker大小和marker数量。例如:

``` c++ 

cv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250); ```

        DICT_6X6_250 是一个预定义的字典,它包含6x6位的marker,总共有250个marker。

        在所有提供的字典中,我们推荐使用你选择尽可能小的marker。例如,如果你需要6x6位的200个marker,选择DICT_6X6_250要优于选择DICT_6X6_1000。字典越小,内部距离就越大。

        

  • 自动生成的字典:

        我们可以针对想要的marker数量和位来生成字典,以得到最优的内部Marker距离。

``` c++ 

cv::aruco::Dictionary dictionary = cv::aruco::generateCustomDictionary(36, 5); ```

       这将会产生一个由36个5X5位字典组成的标准字典。这个过程需要几秒钟,具体时间取决于你的参数(更大的字典和更多的位数会耗费更多的时间)


  • 手动生成的字典:

        最终,我们可以手动设置字典,方便做任何修改。为了做到这一点,我们需要手动给 Dictionary 对象参数赋值。必须注意的是,除非你有一些特别的理由需要自己来生成字典,一般情况下我们推荐前面的任一种方案。

       字典参数为:

``` c++ 

class Dictionary { 

public:

        Mat bytesList; 

        int markerSize; 

        int maxCorrectionBits; // maximum number of bits that can be corrected

        ...

}

```

        bytesList 是一个数组,它包含了所有marker代码的信息。markerSize是每个marker的维度(例如,参数为5代表5x5位)。最终, maxCorrectionBits是marker检测中 可校正的最大比特数,如果这个值过大,会得到大量的错误位置。

        bytesList 中的每一行代表字典中的一个marker。但是,这些marker的数据并不以二进制形式存储,而是以一种特殊的方式存储,这样可以简化检测的过程。

        幸运的是,我们可以简单地调用静态方法Dictionary::getByteListFromBits()来转换到这种形式。

        示例:

``` c++ 

        Dictionary dictionary; 

        // markers of 6x6 bits 

        dictionary.markerSize = 6; 

         // maximum number of bit corrections 

        dictionary.maxCorrectionBits = 3;

        // lets create a dictionary of 100 markers 

        for(int i=0; i<100; i++) {// assume generateMarkerBits() generate a new marker in binary format, so that 

                // markerBits is a 6x6 matrix of CV_8UC1 type, only containing 0s and 1s 

                cv::Mat markerBits = generateMarkerBits(); 

                cv::Mat markerCompressed = getByteListFromBits(markerBits); // add the marker as a new row

                dictionary.bytesList.push_back(markerCompressed); 

        }

```

检测参数

        detectMarkers()的一个参数是DetectorParameters对象。这一对象包含了marker检测过程中所有特定的选项。

        在这一章节中,我们将介绍所有的参数。我们可以根据它们涉及的阶段来给这些参数分类。

阈值化

        检测的第一步是输入图像的阈值化。

        例如,上述样例中的图像阈值化的结果如下:

singlemarkersthresh.png
Thresholded image

        这一阈值化过程由以下几个参数决定:

  • int adaptiveThreshWinSizeMinint adaptiveThreshWinSizeMaxint adaptiveThreshWinSizeStep

        adaptiveThreshWinSizeMin 和 adaptiveThreshWinSizeMax 参数代表选择的自适应阈值窗口大小(以像素为单位)间隔(具体参见OpenCV的 threshold()函数)。

        参数adaptiveThreshWinSizeStep表明了窗口从adaptiveThreshWinSizeMinadaptiveThreshWinSizeMax大小的增量。

        例如,对于adaptiveThreshWinSizeMin=5,adaptiveThreshWinSizeMax=21以及adaptiveThreshWinSizeStep=4,那么将会产生5个阈值化步骤,窗口大小分别为5, 9, 13, 17 和 21。在每个阈值化图像中,都会选出一些marker候选。

        如果marker大小太大的话,较小的窗口大小可能会切割marker的边界,所以它将不会被检测到,就像下图一样:

singlemarkersbrokenthresh.png
Broken marker image

        另一方面,如果marker太小的话,较大的窗口大小也会有类似的效果。此外这一过程将会趋向于全局阈值,而失去了自适应的特性。

        最简单的例子是对adaptiveThreshWinSizeMin adaptiveThreshWinSizeMax使用相同的值,这样就只会执行一次阈值化步骤。但是,最好还是使用一个范围的值作为窗口大小,虽然较多的阈值化步骤会在一定程度上降低性能。

        缺省参数:adaptiveThreshWinSizeMin: 3, adaptiveThreshWinSizeMax: 23, adaptiveThreshWinSizeStep: 10

  • double adaptiveThreshConstant

        这一参数表达了阈值状态下的常量(参见Opencv函数)。它的默认值是大多数例子下较好的情况。

        默认值: 7

轮廓滤波

        阈值化之后,我们需要检测轮廓。但是,我们并不会把所有的轮廓都当作是候选。在不同步骤中,我们通过滤波剔除一些不太可能是marker的轮廓。这一章节中的参数可以自定义这一过程。

        需要注意到,大多数例子中我们需要平衡检测的性能和效率。所有考虑到的轮廓都会在接下来的过程中做进一步处理,这通常产生了更高的计算消耗。所以,我们希望能够在这一阶段就丢弃错误的候选,而不是下一阶段继续处理。

        另一方面,如果滤波的条件过于苛刻,事实上的marker轮廓可能会被错误地剔除,因此,没有检测到marker。

  • double minMarkerPerimeterRatedouble maxMarkerPerimeterRate

        这些参数决定了marker的最小值和最大值,具体来说,是最大最小marker的周长。它们并不是以绝对像素值作为单位,而是相对于输入图片的最大尺寸指定的。

        例如,大小为640x480,最小相对marker周长为0.05的图像,将会产生一个最小周长640x0.05 = 32(像素)的marker,因为640是图像的最大尺寸。参数 maxMarkerPerimeterRate 也是类似的。

        如果 minMarkerPerimeterRate太小,检测阶段性能会降低,因为会有更多的轮廓进入到接下来的阶段。这一弊端对于 maxMarkerPerimeterRate参数而言不是那么显著,因为小的轮廓数目通常要多于大的轮廓。选取 minMarkerPerimeterRate值为0以及值为4,就相当于考虑了图像中的所有轮廓,但是出于性能考虑这是不推荐的。

        缺省值:

Default values:minMarkerPerimeterRate : 0.03, maxMarkerPerimeterRate : 4.0


  • double polygonalApproxAccuracyRate

        我们对所有的候选进行多边形近似,只有近似结果为方形的形状才能通过测试。这一值决定了多边形近似产生的最大误差(参见approxPolyDP() 函数)。

        这一参数是相对于候选长度的(像素上)。所以如果候选的周长为100像素,polygonalApproxAccuracyRate的值为0.04,那么最大的误差应当为100x0.04=5.4像素。

        在大多例子中,缺省参数的表现已经很好了,但对高失真的图像,我们需要更大的误差值。

        缺省值:0.05

  • double minCornerDistanceRate

        同一张marker中每一对角的最小距离。这是相对于marker周长的值。像素的最小距离为Perimeter * minCornerDistanceRate.

        缺省值: 0.05

  • double minMarkerDistanceRate

        两张不同的marker之间的任一对角的最小距离。它表示相对于两个marker的最小标记周长。如果两个候选太接近,较小的一个被忽略。

        缺省值:0.05

  • int minDistanceToBorder

        marker角到图像边缘最小距离。部分图像边缘被遮挡的marker也能被正确地检测出来,如果遮挡部分比较小的话。但是,如果其中一个角被挡住了,返回的角通常在图像边界的一个错误的位置。

        如果marker角的位置很重要的话,例如你想要做pose检测,最好舍弃掉那些离图像边缘太接近的角。否则就没有必要。

        缺省值:3

比特位提取

       检测到候选之后,我们需要分析每个候选的比特位,来确定它们是不是marker。

       在分析二进制编码之前,我们需要提取出比特位。为了达到这个目的,将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。

        以下是一个透视变换后的图像:

removeperspective.png
Perspective removing

        接下来,图像被划分为网格,和marker位数相同。在每个单元格里,我们统计黑色和白色的个数,决定这个单元格的比特位。

bitsextraction1.png
Marker cells

以下参数可以自定义这一过程:

  • int markerBorderBits

        这一参数指定了marker边界的宽度。这和每个比特位的大小相关。因此,值为2意味着边界的长度是两个内部比特位的长度。

        这一参数需要和你使用的Marker边界大小一致,边界的大小可以在绘制函数如 drawMarker()中设置。

        缺省值:1

  • double minOtsuStdDev

        这个值决定了进行Otsu的最小标准差的像素值。如果偏差很低,这可能意味着所有方形都是黑色的(或白色的),Ostu将不起作用。如果是这样的话,所有的比特位都根据平均值大于还是小于128被设为0或者1.

        缺省值:5.0


  • int perpectiveRemovePixelPerCell

        这一参数决定了透视变换后图像的像素数目(每个单元格,包含边界)。这是上图中红色正方形的大小。

        例如,让我们假设我们在处理5x5比特位、边界为1比特位的marker(参见markerBorderBit)。然后,每一维的单元格/比特位的个数为:5 + 2* 1 = 7(边界需要被统计2次)。单元格总体大小为:7x7。

        如果perpectiveRemovePixelPerCell的值为10,那么获取到的图像大小为10*7 = 70 -> 70x70 

        这一参数选择更大的值可以提升比特位的提取过程(在某一程度上),但是它同样也降低了性能。

        缺省值:4


  • double perspectiveRemoveIgnoredMarginPerCell

        当提取每个单元格的比特位时,需要统计黑色和白色的像素个数。一般而言,我们不推荐考虑单元格的所有像素。反之,最好忽略单元格的一些像素。

        原因在于,透视变换之后,单元格的颜色不会完全分离,白色的单元格可能会混入一些黑色的单元格(反之亦然)。因此,最好忽略这些像素,以避免错误的像素计数。

        例如,以下图像:

bitsextraction2.png
Marker cell margins

         我们只考虑处在绿色正方形中的像素。我们可以在右边的图像中看到,最终的像素包含了邻域单元格更少的噪声。参数perspectiveRemoveIgnoredMarginPerCell 指明了红色和绿色正方形之间的距离。

        这一参数是相对于单元格整体的大小的。例如,如果单元格的大小为40像素,这一参数的值为0.1,那么大小为40*0.1=4像素的边界将被剔除。这意味着每个单元格实际上要分析的像素大小为32x32,而不是40x40。

         缺省值:0.13


Marker ID

         比特位提取之后,接下来的步骤是检查提取的编码是否属于这个marker字典,有必要的话,还需要做错误检测步骤。

  • double maxErroneousBitsInBorderRate

        marker边界的比特位应当是黑色的。这一参数指明了允许的边界出错比特位的个数。如,边界可以出现的白色比特位的最大值。它的大小相对于marker中的比特位总数。

        缺省值:0.35


  • double errorCorrectionRate

        每个marker字典有一位可以纠正的理论最大值(Dictionary.maxCorrectionBits)。但是,这个值可以由errorCorrectionRate 参数来修改。

        例如,如果允许纠正的比特位(对于使用的字典)数目为6, errorCorrectionRate的值为0.5,那么实际上最大的可以纠正的比特位个数为6*0.5=3

        这一值对减少错误容忍率以避免错误的位置识别很有帮助。

        缺省值:0.6


角落细化(Corner Refinement)

        当我们检测完marker,并且验证了它们的id之后,最后要做的一步是在角落处的亚像素级的细化(参见OpenCV cornerSubPix())

        注意,这一步是可选的,仅在我们对marker角位置的准确性要求很高时才有意义。例如,pose的检测。这一步骤很耗费时间,所以默认下是不做的。

  • bool doCornerRefinement

        这一参数决定了是否要进行角落亚像素级细化过程,如果对角点的准确性要求不高,可以不进行这一过程。

        缺省值:false


  • int cornerRefinementWinSize

        这一参数决定了亚像素级细化过程的窗口大小。

        较大的值可以产生窗口区域内比较靠近的图像角,marker角会移动到一个不同的错误的地方。除此之外这还会影响到性能。

        缺省值:5


  • int cornerRefinementMaxIterationsdouble cornerRefinementMinAccuracy

        这两个值决定了亚像素级细化过程的结束条件。cornerRefinementMaxIterations指明了迭代的最大次数,cornerRefinementMinAccuracy 是结束这一过程前的最小错误值。

        如果迭代次数过高,这会影响到性能。此外,如果太小的话,亚像素级细化就基本没有发挥作用。

        缺省值:

cornerRefinementMaxIterations: 30, cornerRefinementMinAccuracy: 0.1

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

[OpenCV] aruco Markers识别 的相关文章

  • 将 RGB 转换为黑色或白色

    我如何在Python中获取RGB图像并将其转换为黑白图像 不是灰度 我希望每个像素要么是全黑 0 0 0 要么是全白 255 255 255 流行的 Python 图像处理库中是否有任何内置功能可以完成此任务 如果不是 最好的方法是循环遍历
  • 如何在 CMake Makefile 中包含 OpenCV 库

    我希望你可以帮助我 我有一个简单的 CMakeLists txt 以便在 Leopard 10 5 8 上构建我的项目 我正在使用 CMake 2 8 1 目前这是代码 cmake minimum required VERSION 2 8
  • OpenCV Python 删除图像中的某些对象

    我正在使用带有 opencv 和 numpy 的 python 来检测天文中的星星 例如这个1 https i stack imgur com AKwEJ jpg图片 使用模板匹配 我可以用阈值检测星星 单击 2 2 https i sta
  • 针对不同相机(RGB 和红外)的 StereoCalibrate

    我在校准两个摄像头时遇到问题 第一个是 RGB 第二个是红外 它们有不同的分辨率 我调整了大小并裁剪了更大的图像 焦距等等 例子 RGB 1920x1080 Infrared 512x424 如何相互校准它们 我应该在stereoCalib
  • 使用 Azure 机器学习检测图像中的符号

    4年前我发帖这个问题 https stackoverflow com q 6999920 411094不幸的是 得到的一些答案超出了我的技能水平 我刚刚参加了一次构建巡演会议 他们在会上谈论了机器学习 这让我想到了使用 ML 来解决我的问题
  • 从索贝尔确定图像梯度方向?

    我正在尝试使用 openCV 的 Sobel 方法的结果来确定图像梯度方向 我知道这应该是一个非常简单的任务 我从此处复制了许多资源和答案中的方法 但无论我做什么 所得方向始终在 0 57 度之间 我希望范围为 0 360 我相信所有的深度
  • Pyinstaller“无法执行脚本 pyi_rth_pkgres”并且缺少软件包

    这是我第一次在这里发布问题 因为我的大部分问题已经被其他人回答了 我正在 python 中开发 GUI 应用程序 并尝试使用 pyinstaller 将其打包到单个文件夹和 exe 中 以便于移植 目前 我使用 Windows 10 和 a
  • 如何使用 SimpleBlobDetector 获取 blob 的额外信息?

    robot sherrick 回答了我这个问题 https stackoverflow com a 13534094 1705967 这是他回答的后续问题 cv SimpleBlobDetectorOpencv 2 4 中的 看起来非常令人
  • `opencv.android.JavaCameraView` 和 `opencv.android.NativeCameraView` 有什么区别

    正如主题中所述 有什么区别opencv android JavaCameraView and opencv android NativeCameraView 与其他主要优点相比 有哪些优点可以提供更多选择 来自OpenCV 文档 http
  • Opencv - 找不到头文件

    我正在尝试使用 opencv 开始开发 问题是 到目前为止我几乎无法设置 opencv 因为我找不到它的头文件 我对此主题进行了一些研究 但没有一个真正有帮助 下面是一些链接 opencv2 包含文件在哪里 https stackoverf
  • 如何使用Java OpenCV

    我正在使用图像处理开始我的最后一年项目 并希望完成类似的事情this http www youtube com watch v EPai5f2sWaA 它是人体和物体检测的结合 我真的很想用 Java 来做 因为我在 C 方面的经验很少 I
  • OpenCV 完美识别物体

    我有一个应用程序 我想一次跟踪 2 个在图片中相当小的对象 该应用程序应该在 Android 和 iPhone 上运行 因此算法应该是高效的 对于我的客户来说 如果我们提供一些模式以及附加到要跟踪的对象的软件 以获得易于识别的目标 那就完全
  • OpenCV,捕获的视频比原始相机视频运行得更快!

    我正在使用 openCV 从相机捕获视频并将其存储到 avi 文件 问题是当我完成捕获并运行 avi 文件时 视频流看起来速度很快 这是代码 void main CvCapture capture cvCaptureFromCAM 0 in
  • ValueError:当数组不是序列时设置带有序列的数组元素

    您好 此代码旨在存储使用 open cv 绘制的矩形的坐标 并将结果编译为单个图像 import numpy as np import cv2 im cv2 imread 1 jpg im3 im copy gray cv2 cvtColo
  • OpenCV Python RTSP 流

    我想使用 RTSP 从 IP 摄像机流式传输视频 但我有一个问题 我已经安装了先决条件 而且我的 RTSP 链接可以在 VlC 播放器上运行 但是当我在编辑器中尝试并运行它时 它说找不到相机 这是我的代码 import cv2 import
  • VideoCapture 未检测到 uEye 摄像头

    我的 uEye 相机遇到了一个问题 使用我的笔记本电脑摄像头 id 0 或 USB 上的网络摄像头 id 1 此行完美运行 TheVideoCapturer open 1 TheVideoCapturer 属于 VideoCapture 类
  • GrabCut - bgdModel 和 fgdModel 为空 - 断言错误

    我正在尝试使用 OpenCV2 1 C 中的 GrabCut 算法进行图像分割 这是我的代码 Mat rgbWorkImage imread argv 1 Mat mask mask Scalar 0 Mat bgdModel fgdMod
  • Android API人脸检测与OpenCV/JavaCV人脸检测

    我在 Android 设备上使用了本地 Android 人脸检测 但它似乎很慢 而且我不太确定其可靠性 我还使用了 OpenCV 的人脸检测 但仅限于 PC 而不是 Android 设备 对于 Android 我猜我必须使用 JavaCV
  • 将向量 转换为大小为 (n x 3) 的 Mat,反之亦然

    我有 Point3d 向量 向量形式的点云 如果我使用 OpenCV 提供的转换 比如 cv Mat tmpMat cv Mat pts Here pts is vector
  • 如何将 Opencv VideoWriter 与 GStreamer 结合使用?

    我正在尝试使用 Opencv VideoWriter 传输 h264 流 以使用 VideoCapture 将其传输到网络上的另一台电脑上 但是 我被困在 VideoWriter 上 执行此代码会返回错误 并且 out isOpened 始

随机推荐

  • 前端与产品经理配合

    产品经理PM职业介绍 如何构建原型图 axure软件
  • C++ 重载运算符

    C 43 43 重载运算符号 本文针对结构体重载运算符号进行讲解 其实这是一个困扰我蛮久的问题 xff0c 就是结构体如何使用sort函数进行排序 xff0c 去网上找了很多 xff0c 满多都是关于类的 xff0c 虽然类跟结构体只有访问
  • &运算符的用法

    按位与运算符 34 amp 34 是双目运算符是参与运算的两数各对应的二进位相与 按位与 34 amp 34 功能是参与运算的两数各对应的二进位相与 只有对应的两个二进位均为1时 xff0c 结果位才为1 xff0c 否则为0 参与运算的数
  • 火柴棒游戏(暴力枚举)C++

    暴力枚举 P1149 NOIP2008 提高组 火柴棒等式 题目描述 xff1a 给你n根火柴棍 xff0c 你可以拼出多少个形如 A 43 B 61 CA 43 B 61 C 的等式 xff1f 等式中的AA BB CC是用火柴棍拼出的整
  • 2021蓝桥杯B组 G题砝码称重

    题目大意 xff1a 解法一 xff1a 首先想到的是可以用广度优先搜索的方式来进行暴力求解 xff0c 通过使用递归来将每一种方法遍历 xff0c 并且标记 xff0c 不过由于此方法的时间复杂度是O n3 故使用暴力搜索只能完成50 的
  • 2021蓝桥杯B组 第I题杨辉三角形

    第I题 杨辉三角形 题目大意 xff1a 解法一 xff1a xff08 得20 xff09 思路 xff1a 当指考虑小范围的值时 xff0c 我们可以直接根据杨辉三角形的规律 xff1a 第i行第j列的值 61 第i 1行第j列的值 4
  • Docker--安装Docker和简单使用

    1 docker的安装 1 首先先有一台配置高的虚拟机 xff08 至少两核四G xff09 2 按官方文档 Install Docker Engine on CentOS Docker Documentation 删除docker软件包
  • 力扣 1697. 检查边长度限制的路径是否存在

    给你一个 n 个点组成的无向图边集 edgeList xff0c 其中 edgeList i 61 ui vi disi 表示点 ui 和点 vi 之间有一条长度为 disi 的边 请注意 xff0c 两个点之间可能有 超过一条边 给你一个
  • c++stl 学习心得

    一 c 43 43 和c的区别 xff1a 1 函数默认值 在C 43 43 中我们在定义或声明一个函数的时候 xff0c 有时会在形参中给它赋一个初始值作为不传参数时候的缺省值 xff0c 例如 xff1a int is xff08 in
  • LeetCode 189.轮转数组 (双指针)

    题目传送门 xff1a 轮转数组 题目详情 xff1a 给你一个数组 xff0c 将数组中的元素向右轮转 k 个位置 xff0c 其中 k 是非负数 示例 1 输入 nums 61 1 2 3 4 5 6 7 k 61 3 输出 5 6 7
  • P1005 [NOIP2007 提高组] 矩阵取数游戏

    题目描述 帅帅经常跟同学玩一个矩阵取数游戏 xff1a 对于一个给定的 n m 的矩阵 xff0c 矩阵中的每个元素 ai j 均为非负整数 游戏规则如下 xff1a 每次取数时须从每行各取走一个元素 xff0c 共 n 个 经过 m 次后
  • C - The Domino Effect(dfs+回溯)

    作者 xff1a JF 题目描述 一组标准的双六多米诺骨牌包含28块骨牌 xff08 称为骨头 xff09 xff0c 每个骨牌使用类似骰子的点子显示从0 xff08 空白 xff09 到6的两个数字 28块独特的骨骼由以下PIP组合组成
  • 主脑提示( Master-Mind Hints )

    题目 MasterMind is a game for two players One of them Designer selects a secret code The other Breaker tries to break it A
  • poj 1068 parencondings

    题目描述 xff1a 定义 S 为一个合法的括号字符串 S 可以用以下两种方式编码 xff1a 1 用一个整数数组 P 来表示 xff0c 其中元素 p i 是 S 中每个 39 39 前的 39 39 的个数 xff1b 2 用一个整数数
  • Linux-USB Gadget : Part 6: dummy hcd 驱动简介

    Linux USB Gadget Part 6 dummy hcd 驱动简介 作者 xff1a zjujoe 转载请注明出处 Email xff1a zjujoe 64 yahoo com BLOG xff1a http blog csdn
  • MySQL连接命令

    一 MySQL 连接本地数据库 xff0c 用户名为 root xff0c 密码 123 xff08 注意 xff1a p 和 123 之间不能有空格 xff09 C gt mysql h localhost u root p123 二 M
  • Java数值中加下划线的作用

    在看OkHttp源码的时候 xff0c 看到了如下的代码 xff1a int connectTimeout 61 10 000 int readTimeout 61 10 000 int writeTimeout 61 10 000 一时之
  • docke--制作镜像

    镜像库 xff1a http hub docker com 1 为什么要自己制作镜像 xff1f 更加有安全性 xff1b 可以根据自己的需求得到更加合适自己的镜像 2 镜像有什么 xff1f 操作系统 核心代码 工具 库 运行时的环境 d
  • ubuntu虚拟机,可以ping通域名和主机,但是浏览器不能上网

    根据nat配置好网络 xff0c 和dns地址后 xff0c 可以ping通域名和主机 xff0c 但是浏览器不能上网 xff0c 花了好大的时间折腾 后面发现是谷歌浏览器设置了代理 xff0c 并且在使用时默认使用代理出去 xff0c 如
  • [OpenCV] aruco Markers识别

    reference http docs opencv org 3 1 0 d5 dae tutorial aruco detection html 姿态估计 xff08 Pose estimation xff09 在计算机视觉领域扮演着十分