1 使用教程

Prometheus二维码检测使用教程如下图所示:

tutorial_aruco_1.jpg

具体请参考相应链接:

2 算法原理与参数说明

Aruco二维码检测的主要步骤如下:

  • 检测候选框
  • 四边形识别
  • 对检测到的靶标进行过滤、角点修正
  • 位姿估计

2.1 候选框检测

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

image-20210427175010713.png

OpenCV中检测目标候选框的代码为:

_detectCandidates(grey, candidates, contours, params);

其中,grey为输入的灰度图,candidates为候选框的4个顶点,是一个vector变量,每个vector元素就存了4个点。contours为候选框对应的轮廓像素集合,也是个vector变量,每个元素存储的是对应矩形框的像素集。params为算法的对应参数集合。contourscandidates个数一致,表示检测出的候选四边形个数。

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

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

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

2.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个角点之间的最小距离minDistSq必须大于轮廓周长*minCornerDistanceRate,否则就不是候选。
  • 判断4边形是否存在一个角点离图像的边界非常近,打个比方,角点的坐标为(1, 1),与图像边界非常近,如果有那么这个就扔掉,边界距离阈值为minDistanceToBorder
  • 到这,就说明找到一个候选,存储对应的4个角点和对应的轮廓像素集。

2.1.2 角点排序

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

2.1.3 去除相似四边形

距离特别近的4边形应该被扔掉,在

_filterTooCloseCandidates(candidates, candidatesOut, contours, contoursOut, _params->minMarkerDistanceRate);

实现这个功能。

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

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

2.2 二维码识别

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

image-20210427190810389.png

每个检测出的四边形,算法利用多线程对其进行解码,解码成功这说明这个是个靶标,解码流程如下所示啦。

  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个数超过图像一般,则认为这个块是白色,否则是黑色,最后将这个码标用一个8x8的bits矩阵存储。
  6. 码标边界都是黑色,因此利用int _getBorderErrors(const Mat &bits, int markerSize, int borderSize)统计边界黑色的个数,如果错误块个数大于编码块总数*maxErroneousBitsInBorderRate,则认为边界错误,导致编码错误。
  7. 剔除边界信息,提取码标比特信息存进onlyBits输入dictionary->identify(onlyBits, idx, rotation, params->errorCorrectionRate)进行解码,解码失败就说明这个不是目标,解码成功,并返回一个值,来确定哪个角点是左上第一个角点。

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

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

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

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

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

角点修正

前面步骤检测出的角点都是像素级的,用于后续位姿估计时候可能会有误差,因此检测出角点之后,需要对其进行细化,得到亚像素角点,细化方法有两种,分别为:

  • 角点细化(CORNER_REFINE_SUBPIX)
  • 拟合直线细化(CORNER_REFINE_CONTOUR)

细化方法的选择,指定参数cornerRefinementMethod即可,算法默认是不细化的。

角点细化方法

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

拟合直线细化方法

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

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

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

详细的参数定义如下图所示:

image-20210428103802528.png

2.4 位姿估计

PnP(Perspective-n-Point)是求解3D到2D点对运动的方法。它描述了已知n个3D空间点及其图像上的投影位置时,如何估计相机的位姿。

PnP问题有很多种求解方法,例如,用3对点估计位姿的P3P、直接线性变换(DLT)、EPnP、UPnP,等等。此外,还能用非线性优化的方法,构建最小二乘问题并迭代求解,也就是万金油的Bundle Adjustment。

Bundle Adjustment是一个最小化重投影误差(Reprojection error)问题。