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()被调用。
- DetectInitialCandidatesParallel类,该类声明operator()函数,此处调用
-
二值化操作调用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
- 对每个四边形利用
getPerspectiveTransform
和warpPerspective
进行透视变换,透视变换的目标为方形,其边长为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表示的>)
- 剔除边界信息,提取码标比特信息存进onlyBits输入
-
-
IdentifyCandidatesParallell类,对每一个candidate进行一次_identifyOneCandidate()操作
PART3:角点修正
前面步骤检测出的角点都是像素级的,用于后续位姿估计时候可能会有误差,因此检测出角点之后,需要对其进行细化,得到亚像素角点,细化方法有两种,分别为:角点细化(CORNER_REFINE_SUBPIX)和拟合直线细化(CORNER_REFINE_CONTOUR)。细化方法的选择,指定参数cornerRefinementMethod即可,算法默认是不细化的。
角点细化:
opencv自带函数cv::cornerSubPix()
可将像素级角点细化为亚像素,其中涉及到三个参数,cornerRefinementWinSize细化窗口大小,cornerRefinementMaxIterations细化最大迭代数,cornerRefinementMinAccuracy细化误差。
直线拟合细化:
对4边形的每个边进行拟合,然后利用拟合直线的交点作为最终细化的角点。
-
如果相机有畸变,利用
undistortPoints
对四边形像素点进行校正。 -
提取两个角点之间的像素集,也就是4边形的每个边
-
对每个边进行直线拟合
-
计算交点
最终的交点就作为亚像素角点。
部分内容参考:OpenCV Aruco 参数源码完整解析理解!(转载)