-
Notifications
You must be signed in to change notification settings - Fork 427
Prometheus目标检测算法 二维码检测
Prometheus二维码检测使用教程如下图所示:
具体请参考相应链接:
- 确定图像话题输入
- 相机标定
- 配置二维码的实际边长和ID号
- 输出二维码检测结果ROS话题
Aruco二维码检测的主要步骤如下:
- 检测候选框
- 四边形识别
- 对检测到的靶标进行过滤、角点修正
- 位姿估计
Aruco靶标外部由正方形框构成,因此,首先肯定要检测出候选框,因为图像受到透视变换的影响,导致正方形在图像上的投影可以为任意4边形,唯一可以知道的性质就是,这个4变形一定是具有凸性的,代码也是利用这个性质进行筛选的。如下图所示,该阶段检测绿色所示的4边形。
OpenCV中检测目标候选框的代码为:
_detectCandidates(grey, candidates, contours, params);
其中,grey
为输入的灰度图,candidates
为候选框的4个顶点,是一个vector
变量,每个vector
元素就存了4个点。contours
为候选框对应的轮廓像素集合,也是个vector
变量,每个元素存储的是对应矩形框的像素集。params
为算法的对应参数集合。contours
和candidates
个数一致,表示检测出的候选四边形个数。
候选4边形检测算法分为三个阶段:
- 候选检测
- 角点排序
- 去除相似框
下面对每个阶段进行流程说明。
候选检测阶段仅利用目标的凸性检测出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个角点和对应的轮廓像素集。
角点排序使用函数_reorderCandidatesCorners(candidates);
,保证角点的顺序是顺时针方向,仅此而已。
距离特别近的4边形应该被扔掉,在
_filterTooCloseCandidates(candidates, candidatesOut, contours, contoursOut, _params->minMarkerDistanceRate);
实现这个功能。
对于两个4边形,其4个角点之间的最短平均距离如果小于两个矩形的最小周长*minMarkerDistanceRate
,那么就认为这两个矩形是相似的。矩形的周长就是轮廓像素个数,两个相似4边形,周长小的会被扔掉。
到这,将会得到一组候选的四边形集合,然后下一步对其进行精准识别。
下图是一个Aruco码标,一个黑色方块或白色方块就叫做一个比特,码标周围用了一圈黑色框围着,框的宽度为markerBorderBits
,这里值为1。下图是一个6x6
的码标套上一个宽度为1的框,所以实际上比特区域是8x8
。
对每个检测出的四边形,算法利用多线程对其进行解码,解码成功这说明这个是个靶标,解码流程如下所示啦。
- 对每个四边形利用
getPerspectiveTransform
和warpPerspective
进行透视变换,透视变换的目标为方形,其边长为perspectiveRemovePixelPerCell*码标的比特边长
- 判断四边形内部是否为全黑或全白。先移除1/2边框,然后利用
meanStdDev
计算区域的均值和方差,如果方差小于minOtsuStdDev
,说明是全黑或全白,如果均值大于127,则认为是全白,否则全黑。 - 利用大津阈值对透视变换后的图像做个二值化
threshold(resultImg, resultImg, 125, 255, THRESH_BINARY | THRESH_OTSU);
- 因为已经知道码标的边长的比特数,那么就直接对二值化图像提取出对应的图像块。图像块的四周靠近边界部分肯定有噪声,所以四周边界部分扔掉,那么这个边界宽度为
cellMarginPixels = perspectiveRemoveIgnoredMarginPerCell * perspectiveRemovePixelPerCell
- 利用
countNonZero
统计比特块的非0个数,如果非0个数超过图像一般,则认为这个块是白色,否则是黑色,最后将这个码标用一个8x8
的bits矩阵存储。 - 码标边界都是黑色,因此利用
int _getBorderErrors(const Mat &bits, int markerSize, int borderSize)
统计边界黑色的个数,如果错误块个数大于编码块总数*maxErroneousBitsInBorderRate
,则认为边界错误,导致编码错误。 - 剔除边界信息,提取码标比特信息存进onlyBits输入
dictionary->identify(onlyBits, idx, rotation, params->errorCorrectionRate)
进行解码,解码失败就说明这个不是目标,解码成功,并返回一个值,来确定哪个角点是左上第一个角点。
到这码标识别结束,返回码标信息和对应的轮廓及角点。
检测结果中可能会出现两个码标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边形的每个边进行拟合,然后利用拟合直线的交点作为最终细化的角点。
- 如果相机有畸变,利用
undistortPoints
对四边形像素点进行校正。 - 提取两个角点之间的像素集,也就是4边形的每个边
- 对每个边进行直线拟合
- 计算交点
最终的交点就作为亚像素角点。
详细的参数定义如下图所示:
PnP(Perspective-n-Point)是求解3D到2D点对运动的方法。它描述了已知n个3D空间点及其图像上的投影位置时,如何估计相机的位姿。
PnP问题有很多种求解方法,例如,用3对点估计位姿的P3P、直接线性变换(DLT)、EPnP、UPnP,等等。此外,还能用非线性优化的方法,构建最小二乘问题并迭代求解,也就是万金油的Bundle Adjustment。
Bundle Adjustment是一个最小化重投影误差(Reprojection error)问题。
感谢使用Prometheus自主无人机软件平台!