OpenCV Aruco 参数源码完整解析理解!

Aruco靶标是无人机导航常用的一种靶标,其可以携带编码信息,用于多台设备,现实增强,相机标定等等。
OpenCV Aruco 参数源码完整解析理解!_第1张图片
下面我会对齐进行细致的算法分析,各位按照这个流程阅读OpenCV源码会非常清晰,我主要是对关键函数进行分析,分析其算法流程。

文章目录

  • 0 参数设置
  • 1 检测候选框
    • 1.1 候选检测
    • 1.2 角点排序
    • 1.3 去除相似4边形
  • 2 四边形识别
  • 3 对检测出的靶标进行过滤
  • 4 角点修正
    • 4.1 角点细化方法
    • 4.2 拟合直线细化方法
  • 5 总结

0 参数设置

算法一共20个参数,非常庞大了,建议不要上来看这节参数含义,很容易看不懂,建议结合流程来看,这样很清晰了。

参数定义 参数含义 参数值
adaptiveThreshWinSizeMin 自适应二值化最小窗口大小 3
adaptiveThreshWinSizeMax 自适应二值化最大窗口大小 23
adaptiveThreshWinSizeStep 二值化窗口大小步长 10
adaptiveThreshConstant 自适应二值化函数所需要的一个常数阈值 7
minMarkerPerimeterRate 轮廓最小周长比率 0.03
maxMarkerPerimeterRate 轮廓最大周长比率 4
polygonalApproxAccuracyRate 多边形逼近精度控制率 0.03
minCornerDistanceRate 4边形4个角点之间的最小距离率值 0.05
minDistanceToBorder 角点到边界的最小距离 3
minMarkerDistanceRate 角点最小距离率值 0.05
cornerRefinementMethod 角点细化方法 CORNER_REFINE_NONE
cornerRefinementWinSize 细化窗口大小(仅用于CORNER_REFINE_SUBPIX) 5
cornerRefinementMaxIterations 细化最大迭代次数(仅用于CORNER_REFINE_SUBPIX) 30
cornerRefinementMinAccuracy 细化最小精度(仅用于CORNER_REFINE_SUBPIX) 0.1
markerBorderBits 编码区域的边框位数 1
perspectiveRemovePixelPerCell 透视变换后编码每个比特位的尺寸 4
perspectiveRemoveIgnoredMarginPerCell 编码每个比特块忽略的宽度 0.13
maxErroneousBitsInBorderRate 边框误差比率 0.35
minOtsuStdDev 判断区域是否为全黑或全白的方差 5
errorCorrectionRate 解码误差率 0.6

1 检测候选框

Aruco靶标外部由正方形框构成,因此,首先肯定要检测出候选框,因为图像受到透视变换的影响,导致正方形在图像上的投影可以为任意4边形,唯一可以知道的性质就是,这个4变形一定是具有凸性的,代码也是利用这个性质进行筛选的。如下图所示,该阶段检测绿色所示的4边形。

OpenCV Aruco 参数源码完整解析理解!_第2张图片

OpenCV中检测目标候选框的代码是_detectCandidates(grey, candidates, contours, _params);,其中,grey为输入的灰度图,candidates为候选框的4个点,是一个vector变量,每个vector元素就存了4个点。contours为候选框对应的轮廓像素集合,也是个vector变量,每个元素存储的是对应矩形框的像素集。_params为算法的对应参数集合了。contours和candidates个数一致,个数表示检测出的候选四边形个数。

候选4边形检测算法分为三个阶段:

  • 候选检测
  • 角点排序
  • 去除相似框

下面对每个阶段进行流程说明。

1.1 候选检测

候选检测阶段仅利用目标的凸性检测出4边形集合,对应函数为_detectInitialCandidates(grey, candidates, contours, _params);

因为靶标的颜色仅有黑白,且与周围有明显的区分。因此,直接使用自适应二值化方法adaptiveThreshold提取候选目标,这个函数时OpenCV自带的函数,需要制定二值化窗口,和一个常数。

在这个阶段,算法使用了多种不同的窗口大小来检测4边形,这个时候adaptiveThreshWinSizeMin控制的是最小窗口,adaptiveThreshWinSizeMax控制的是最大窗口,adaptiveThreshWinSizeStep控制窗口步长,adaptiveThreshConstant是这个函数需要的一个常数。举例来说,算法设置的窗口范围为[3,23],步长为10,那么最后选择的窗口大小就为,3,13,23,记住,窗口最好设置为奇数。

在得到二值化窗口之后呢,利用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个角点和对应的轮廓像素集。

1.2 角点排序

角点排序使用函数_reorderCandidatesCorners(candidates);,保证角点的顺序是顺时针方向,仅此而已。

1.3 去除相似4边形

距离特别近的4边形应该被扔掉,在_filterTooCloseCandidates(candidates, candidatesOut, contours, contoursOut, _params->minMarkerDistanceRate);实现这个功能。

对于两个4边形,其4个角点之间的最短平均距离如果小于两个矩形的最小周长*minMarkerDistanceRate,那么就认为这两个矩形是相似的。矩形的周长就是轮廓像素个数,两个相似4边形,周长小的会被扔掉。

到这,将会得到一组候选的4边形集合,然后下一步对其进行精准识别。

2 四边形识别

下图是一个Aruco码标,一个黑色方块或白色方块就叫做一个比特,码标周围用了一圈黑色框围着,框的宽度为markerBorderBits,这里值为1。下图是一个6*6的码标套上一个宽度为1的框,所以实际上比特区域是8*8.

OpenCV Aruco 参数源码完整解析理解!_第3张图片
每个检测出的4边形,算法利用多线程对其进行解码,解码成功这说明这个是个靶标,解码流程如下所示啦。

  1. 对每个四边形利用getPerspectiveTransformwarpPerspective进行透视变换,透视变换的目标为方形,其边长为perspectiveRemovePixelPerCell*码标的比特边长
  2. 判断四边形内部是否为全黑或全白。先移除1/2边框,然后利用meanStdDev计算区域的均值和方差,如果方差小于minOtsuStdDev,说明是全黑或全白,如果均值大于127,则认为是全白,否则全黑。
  3. 利用大津阈值对透视变换后的图像做个二值化threshold(resultImg, resultImg, 125, 255, THRESH_BINARY | THRESH_OTSU);
  4. 因为已经知道码标的边长的比特数,那么就直接对二值化图像提取出对应的图像块。图像块的四周靠近边界部分肯定有噪声,所以四周边界部分扔掉,那么这个边界宽度为cellMarginPixels = perspectiveRemoveIgnoredMarginPerCell * perspectiveRemovePixelPerCell
  5. 利用countNonZero统计比特块的非0个数,如果非0个数超过图像一般,则认为这个块是白色,否则是黑色,最后将这个码标用一个8*8的bits矩阵存储。
  6. 码标边界都是黑色,因此利用int _getBorderErrors(const Mat &bits, int markerSize, int borderSize)统计边界黑色的个数,如果错误块个数大于编码块总数*maxErroneousBitsInBorderRate,则认为边界错误,导致编码错误。
  7. 剔除边界信息,提取码标比特信息存进onlyBits输入dictionary->identify(onlyBits, idx, rotation, params->errorCorrectionRate)进行解码,解码失败就说明这个不是目标,解码成功,并返回一个值,来确定哪个角点是左上第一个角点。

到这码标识别结束,返回码标信息和对应的轮廓及角点。

3 对检测出的靶标进行过滤

检测结果中可能会出现两个码标ID一样,且其中一个码标在另一个码标里面,属于包含关系(OpenCV源码说双边框情况会出现这个问题,算是算法的一个Bug)。

那么针对这个情况就要先找出具有包含关系的矩形,判断ID是否一样,一样就删掉。函数声明如下所示。

void _filterDetectedMarkers(vector< vector< Point2f > >& _corners, vector< int >& _ids, vector< vector< Point> >& _contours)

4 角点修正

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

4.1 角点细化方法

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

4.2 拟合直线细化方法

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

  1. 如果相机有畸变,利用undistortPoints对四边形像素点进行校正。
  2. 提取两个角点之间的像素集,也就是4边形的每个边
  3. 对每个边进行直线拟合
  4. 计算交点

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

5 总结

Aruco是14年提出的一个算法,使用次数很多,非常经典,同样地,其参数过多,对环境有需求,也导致了其无法用于自动化系统(误差干扰很大),从流程也是可以发现的。

但这也很正常,刚产生的算法不可能通用,这就需要我们针对其约束的部分进行放松,用一个更好的办法消除误差,可以用在更广的方向上。

PS:目前为止我都没找到相关的数据集,数据集的采集也是很关键的。

你可能感兴趣的:(OpenCV,计算机视觉,算法,opencv,计算机视觉)