Aruco检测Marker原理及代码详解(c++)

Aruco检测Marker原理及代码详解(c++)

detectmarker主要流程

这个函数写在aruco.cpp里。

detectMarkers()

  • _convertToGrey
  • _detectCandidates //检测候选框
  • _identifyCandidates //识别二维码
  • MarkerSubpixelParallel //标记角亚像素细化
  • MarkerContourParallel //标记角轮廓细化

PART1:检测正方形候选框

_detectCandidates()

  • 将彩色图片转换成灰度图片
  • 候选框抓取,_detectInitialCandidates()
    • 这一部分类似卷积的滑动窗口,设置步长和窗口大小,然后调用DetectInitialCandidatesParallel对每一个窗口进行二值化和边框检测
  • 角点排序,_reorderCandidatesCorners() :确保候选角的顺序为顺时针方向
  • 去除相似框,_filterTooCloseCandidates():把距离特别近的4边形扔掉,对于两个4边形,其4个角点之间的最短平均距离如果小于两个矩形的最小周长*minMarkerDistanceRate,那么就认为这两个矩形是相似的。矩形的周长就是轮廓像素个数,两个相似四边形,周长小的会被扔掉。

相关函数:

_detectInitialCandidates() //预处理,利用目标的凸性检测出四边形集合

_detectCandidates() //检测正方形候选框

_reorderCandidatesCorners() //确保候选角的顺序为顺时针方向

_filterTooCloseCandidates() //检查那些离得太近的候选框,保留那些潜在的候选框

_extractBits() //提取角点(输入侯选角)

_findMarkerContours() // L.131 ,轮廓检测

detectMarkers() // L.1120

  • 创建多个并行检测管道:

    • DetectInitialCandidatesParallel类,该类声明operator()函数,此处调用threshold()findMarkerContours()分别进行二值化操作轮廓检测,在_detectInitialCandidates()被调用。
  • 二值化操作调用opencv:adaptiveThreshold()函数

    • adaptiveThreshold(_in, _out, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, winSize, constant);
    • 该局部自适应二值化操作使用的阈值为ADAPTIVE_THRESH_MEAN_C,平均值-C(Aruco中C=7)
    • THRESH_BINARY_INV,将大于阈值像素的值设置为0,将其他像素值设置为255
  • 在得到二值化窗口之后,利用findContours(contoursImg, contours, RETR_LIST, CHAIN_APPROX_NONE);查找这个二值化图的所有轮廓,每个轮廓存在contours里面。下面对每一个轮廓进行如下的一个过程:

    • 特别大特别小的轮廓一般都不会是目标,最小周长阈值为minPerimeterPixels = minPerimeterRate *max(rows, cols),最大周长阈值为maxPerimeterPixels = maxPerimeterRate *max(rows, cols)
    • 对这个轮廓进行多边形逼近approxPolyDP(contours[i], approxCurve, double(contours[i].size()) * polygonalApproxAccuracyRate, true);。approxCurve是这个轮廓的逼近点,double(contours[i].size()) * polygonalApproxAccuracyRate是对应的逼近精度,简单来说,越大的轮廓具有更大的逼近阈值,主要还是防止算法收到噪声影响。
    • 轮廓逼近点必须为4个点(4边形嘛),且这个4边形一定是凸的,否则这个轮廓就一定不是目标轮廓。
    • 4边形的4个角点之间的最小距离minDistSq必须大于轮廓周长minCornerDistanceRate,否则就不是候选。
    • 判断4边形是否存在一个角点离图像的边界非常近,打个比方,角点的坐标为(1,1),与图像边界非常近,如果有那么这个就扔掉,边界距离阈值为minDistanceToBorder。
    • 到这,就说明找到一个候选,存储对应的4个角点和对应的轮廓像素集。

PART2:二维码识别

_identifyCandidates()

  • 将输入图片转换成灰度图_convertToGrey
  • 确认候选框有效性,IdentifyCandidatesParallel
  • 将角的位置转换为正确的旋转位置,correctCornerPosition

相关函数

_identifyOneCandidate() //L.567,确认候选框是否是有效

class IdentifyCandidatesParallell

correctCornerPosition() // L.695,将角的位置转换为正确的旋转位置

_extractBits() //给定一幅输入图像和候选角,提取候选角的位,包括边界位

getPerspectiveTransform()

_getBorderErrors()

  • _identifyOneCandidate() //L.567,确认候选框是否是有效,此处返回一个typ:

  • typ:

    • 0 if the candidate is not valid,

    • 1 if the candidate is a black candidate (default candidate)

    • 2 if the candidate is a white candidate

    • 调用_extractBits():

      • 一个黑色方块或白色方块就叫做一个比特,码标周围用了一圈黑色框围着,框的宽度为markerBorderBits,一个6*6的码标套上一个宽度为1的框,所以实际上比特区域是8*8
      • 对每个四边形利用getPerspectiveTransformwarpPerspective进行透视变换,透视变换的目标为方形,其边长为perspectiveRemovePixelPerCell * 码标的比特边长
      • 判断四边形内部是否为全黑或全白。先移除1/2边框,然后利用meanStdDev计算区域的均值和方差,如果方差小于minOtsuStdDev,说明是全黑或全白,如果均值大于127,则认为是全白,否则全黑
      • 调用threshold(resultImg, resultImg, 125, 255, THRESH_BINARY | THRESH_OTSU),二值化阈值使用大津法,二值化方式THRESH_BINARY 与上述THRESH_BINARY_INV作用相反
      • 已经知道码标的边长的比特数,那么就直接对二值化图像提取出对应的图像块。图像块的四周靠近边界部分肯定有噪声,所以四周边界部分扔掉,那么这个边界宽度为cellMarginPixels = perspectiveRemoveIgnoredMarginPerCell * perspectiveRemovePixelPerCell
      • 利用countNonZero统计比特块的非0个数,如果非0个数超过图像一般,则认为这个块是白色,否则是黑色,最后将这个码标用一个8*8的bits矩阵存储
    • 调用_getBorderErrors():

      • 码标边界都是黑色,因此利用int _getBorderErrors(const Mat &bits, int markerSize, int borderSize)统计边界黑色的个数,如果错误块个数大于编码块总数*maxErroneousBitsInBorderRate,则认为边界错误,导致编码错误
      • 返回边界中错误位的数目,即边界中白色位的数目
    • dictionary->identify:

      • 剔除边界信息,提取码标比特信息存进onlyBits输入dictionary->identify(onlyBits, idx, rotation, params->errorCorrectionRate)进行解码,解码失败就说明这个不是目标,解码成功,并返回一个值,来确定哪个角点是左上第一个角点。
        • 关于identify:函数在dictionary.cpp中,主要思路是:将位矩阵变换为4次旋转的字节列表(因为二维码角点有顺序),然后与字典中存储的二维码信息进行比较(比较方法是汉明距离<此前的比特块检测黑白块用o、1表示的>)
  • IdentifyCandidatesParallell类,对每一个candidate进行一次_identifyOneCandidate()操作

PART3:角点修正

前面步骤检测出的角点都是像素级的,用于后续位姿估计时候可能会有误差,因此检测出角点之后,需要对其进行细化,得到亚像素角点,细化方法有两种,分别为:角点细化(CORNER_REFINE_SUBPIX)和拟合直线细化(CORNER_REFINE_CONTOUR)。细化方法的选择,指定参数cornerRefinementMethod即可,算法默认是不细化的。

角点细化:

opencv自带函数cv::cornerSubPix()可将像素级角点细化为亚像素,其中涉及到三个参数,cornerRefinementWinSize细化窗口大小,cornerRefinementMaxIterations细化最大迭代数,cornerRefinementMinAccuracy细化误差。

直线拟合细化:

对4边形的每个边进行拟合,然后利用拟合直线的交点作为最终细化的角点。

  • 如果相机有畸变,利用undistortPoints对四边形像素点进行校正。

  • 提取两个角点之间的像素集,也就是4边形的每个边

  • 对每个边进行直线拟合

  • 计算交点

最终的交点就作为亚像素角点。

部分内容参考:OpenCV Aruco 参数源码完整解析理解!(转载)

猜你喜欢

转载自blog.csdn.net/qq_43842886/article/details/126410278
今日推荐