〇.算法效果展示
0.1要定位的模板一
找到的匹配
在有污损情况下找到的匹配
0.2要定位的模板2
找到的匹配
一. 理论部分
模板匹配的算法包括基于灰度的匹配,基于特征的匹配,基于组件的匹配,基于相关性的匹配以及局部变形匹配。基于灰度的匹配一般应用在没有缩放和旋转,颜色变化不大的场合。基于特征的匹配一般应用在具有缩放和旋转,颜色变化较大的场合。在模板各个组件有相对位移的情况下,使用基于组件的匹配算法。在图像模糊,目标定位不依赖于边缘的情况下一般使用基于相关性的匹配。目标有局部变形的情况使用局部变形匹配算法。基于特征的匹配算法通常使用线特征即轮廓特征,也有使用点特征(ICP算法和RANSAC算法)和面特征(基于区域,基于纹理等)的方法。本文主要介绍基于轮廓特征的模板匹配。
基于轮廓的模板匹配其相似度分布比基于灰度的模板匹配更加有区分度,因而可以进行较高精度的定位。同时,由于只对轮廓进行处理,因而提高了处理速度,因此在处理带有旋转和缩放的情况时具有优势。另外,基于轮廓特征的模板可以不从现场获取,而从CAD数据生成。
二.实验部分:
2.1实验一:边缘模板创建
(Canny算法+非极大值已知+滞后阈值)
在opencv中有函数findcontour可以找到二值图中的边缘,但需要人为控制二值化参数并观察结果,对于灰度区域较多的模板,并不能找到所有边缘,因此使用Canny边缘检测方法,然后进行非极大值抑制和滞后阈值处理得到的边缘。
原图:
找到的边缘点
代码
int myfindcontour(Mat templateArr, double maxContrast, double minContrast) { Mat src = templateArr.clone(); //变量定义 Size Ssize; Ssize.width = src.cols; Ssize.height = src.rows; modelHeight = src.rows; modelWidth = src.cols; noOfCordinates = 0; cordinates = new Point[modelWidth *modelHeight]; edgeMagnitude = new double[modelWidth *modelHeight]; edgeDerivativeX = new double[modelWidth *modelHeight]; edgeDerivativeY = new double[modelWidth *modelHeight]; //使用Sobel算子计算梯度 GaussianBlur(src, src, Size(5,5), 1); Mat gx, gy; gx = Mat(Ssize.height, Ssize.width, CV_16SC1); gy = Mat(Ssize.height, Ssize.width, CV_16SC1); Sobel(src, gx, CV_16S, 1, 0, 3); Sobel(src, gy, CV_16S, 0, 1, 3); Mat nmsEdges = Mat(Ssize.height, Ssize.width, CV_32F); const short* _sdx; const short* _sdy; double fdx, fdy; double MagG, DirG; double MaxGradient = -99999.99; double direction; int *orients = new int[Ssize.height *Ssize.width]; int count = 0, i, j; double **magMat; CreateDoubleMatrix(magMat, Ssize); for (i = 1; i < Ssize.height - 1; i++) { for (j = 1; j < Ssize.width - 1; j++) { fdx = gx.at<short>(i, j); fdy = gy.at<short>(i, j); MagG = sqrt((float)(fdx*fdx) + (float)(fdy*fdy)); direction = fastAtan2((float)fdy, (float)fdx); magMat[i][j] = MagG; if (MagG > MaxGradient) MaxGradient = MagG; if ((direction > 0 && direction < 22.5) || (direction > 157.5 && direction < 202.5) || (direction > 337.5 && direction < 360)) direction = 0; else if ((direction > 22.5 && direction < 67.5) || (direction > 202.5 && direction < 247.5)) direction = 45; else if ((direction > 67.5 && direction < 112.5) || (direction > 247.5 && direction < 292.5)) direction = 90; else if ((direction > 112.5 && direction < 157.5) || (direction > 292.5 && direction < 337.5)) direction = 135; else direction = 0; orients[count] = (int)direction; count++; } } //非极大值抑制 count = 0; double leftPixel, rightPixel; for (i = 1; i < Ssize.height - 1; i++) { for (j = 1; j < Ssize.width - 1; j++) { switch (orients[count]) { case 0: leftPixel = magMat[i][j - 1]; rightPixel = magMat[i][j + 1]; break; case 45: leftPixel = magMat[i - 1][j + 1]; rightPixel = magMat[i + 1][j - 1]; break; case 90: leftPixel = magMat[i - 1][j]; rightPixel = magMat[i + 1][j]; break; case 135: leftPixel = magMat[i - 1][j - 1]; rightPixel = magMat[i + 1][j + 1]; break; } if ((magMat[i][j] < leftPixel) || (magMat[i][j] < rightPixel)) { nmsEdges.at<int>(i,j) = 0; } else { nmsEdges.at<int>(i,j) = (uchar)(magMat[i][j] / MaxGradient * 255); } count++; } } //滞后阈值 int RSum = 0, CSum = 0; int curX, curY; int flag = 1; for (i = 1; i < Ssize.height - 1; i++) { for (j = 1; j < Ssize.width; j++) { fdx = gx.at<short>(i, j); fdy = gy.at<short>(i, j); MagG = sqrt(fdx*fdx + fdy*fdy); DirG = fastAtan2((float)fdy, (float)fdx); flag = 1; if(nmsEdges.at<int>(i, j) < maxContrast) { if(nmsEdges.at<int>(i, j) < minContrast) { nmsEdges.at<int>(i, j) = 0; flag = 0; //移除 } else { //8邻域中任意一点 if (nmsEdges.at<int>(i - 1, j - 1) < maxContrast && nmsEdges.at<int>(i - 1, j) < maxContrast && nmsEdges.at<int>(i - 1, j + 1) < maxContrast && nmsEdges.at<int>(i, j - 1) < maxContrast && nmsEdges.at<int>(i, j + 1) < maxContrast && nmsEdges.at<int>(i + 1, j - 1) < maxContrast && nmsEdges.at<int>(i + 1, j) < maxContrast && nmsEdges.at<int>(i + 1, j + 1) < maxContrast) { nmsEdges.at<int>(i, j) = 0; flag = 0; } } } //保存找到的边缘点 curX = i; curY = j; if (flag != 0) { if (fdx != 0 || fdy != 0) { RSum = RSum + curX; CSum = CSum + curY; //累加准备计算重心 cordinates[noOfCordinates].x = curX; cordinates[noOfCordinates].y = curY; edgeDerivativeX[noOfCordinates] = fdx; edgeDerivativeY[noOfCordinates] = fdy; if (MagG != 0) edgeMagnitude[noOfCordinates] = 1 / MagG; else edgeMagnitude[noOfCordinates] = 0; noOfCordinates++; } } } } //计算重心 centerOfGravity.x = RSum / noOfCordinates; centerOfGravity.y = CSum / noOfCordinates; //偏移重心 for (int m = 0; m < noOfCordinates; m++) { int temp; temp = cordinates[m].x; cordinates[m].x = temp - centerOfGravity.x; temp = cordinates[m].y; cordinates[m].y = temp - centerOfGravity.y; } delete[] orients; modelDefined = true; return 1; }
2.2实验二:使用滑动窗口的边缘模板匹配
接上述实验一,在找到模板的边缘后,使用同样的方法找到待检图片中边缘。然后使模板在待检测图片中滑动,只对模板中找到的边缘点进行计算,进行基于边缘的匹配。
待检测图片:
检测结果:
代码:
for (i = 0; i < Ssize.height; i++) { for (j = 0; j < Ssize.width; j++) { partialSum = 0; for (m = 0; m < noOfCordinates; m++) { curX = i + cordinates[m].x; curY = j + cordinates[m].y; iTx = edgeDerivativeX[m]; iTy = edgeDerivativeY[m]; if (curX<0||curY<0 || curX>Ssize.height - 1 || curY>Ssize.width - 1) continue; iSx = Sdx.at<short>(curX, curY); iSy = Sdy.at<short>(curX, curY); if ((iSx != 0 || iSy != 0) && (iTx != 0 || iTy != 0)) { partialSum = partialSum + ((iSx*iTx) + (iSy*iTy))*(edgeMagnitude[m] * matGradMag[curX][curY]); } sumOfCoords = m + 1; partialScore = partialSum / sumOfCoords; if (partialScore < (MIN((minScore - 1) + normGreediness*sumOfCoords, normMinScore* sumOfCoords))) break; } if (partialScore > resultScore) //更新结果 { resultScore = partialScore; resultPoint->x = i; resultPoint->y = j; } } }
该算法对待检图片有污损的情况下仍能稳定检测:
如上图待检图片右上部分轮廓不可见,在进过处理后得到如下结果:
2.3实验三:基于Hu矩的边缘模板匹配
在模板情况简单,只用外轮廓就可精确定位的前提下,可以使用Hu矩轮廓匹配方法matchShapes,使用匹配到的重心进行定位,对模板轮廓进行缩放和旋转参数搜索,得到目标相对模板旋转缩放参数。
模板:
待搜索图中目标进行的相应的缩放和旋转:
搜索结果:
代码:
Mat searchImage = imread("pic/contour/Search2.jpg"); Mat searchImageResult = searchImage.clone(); Mat graySearchImg; cvtColor(searchImage, graySearchImg, COLOR_RGB2GRAY); Mat templateImage = imread("pic/contour/Template2.jpg"); Mat grayTemplateImg; cvtColor(templateImage, grayTemplateImg, COLOR_RGB2GRAY); //在Template2和Search2中找外轮廓 Mat bin_search; threshold(graySearchImg, bin_search, 128, 255, THRESH_BINARY); vector<vector<Point>> contours_search; vector<Vec4i> hierarchy_search; findContours(bin_search, contours_search, hierarchy_search, RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0)); Mat bin_template; threshold(grayTemplateImg, bin_template, 128, 255, THRESH_BINARY); vector<vector<Point>> contours_template; vector<Vec4i> hierarchy_template; findContours(bin_template, contours_template, hierarchy_template, RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0)); drawContours(searchImage, contours_search, -1, Scalar(255, 0, 0)); drawContours(templateImage, contours_template, -1, Scalar(255, 0, 0)); //对待检图中的每个轮廓进行判断,可以事先筛选再判断 for (int i = 0; i < contours_search.size(); i++) { double score = matchShapes(contours_template[0], contours_search[i], CONTOURS_MATCH_I3,0); cout << "score" << score << endl; if (score < 0.1) //认为匹配成功 { //计算重心偏移,缩放和旋转 double area_template = contourArea(contours_template[0]); double area_search = contourArea(contours_search[i]); float scale = sqrt(area_search/ area_template); cout << "scale:" << scale << endl; //由于先验目标最小包围矩形是长方形 //因此最小包围矩形的中心和重心的向量夹角为旋转 RotatedRect rect_template = minAreaRect(contours_template[0]); RotatedRect rect_search = minAreaRect(contours_search[i]); //两个旋转矩阵是否同向 int sign = (rect_template.size.width - rect_template.size.height) * (rect_search.size.width - rect_search.size.height); float angle; if (sign > 0) //可以直接相减 angle = rect_search.angle - rect_template.angle; else angle = (90 + rect_search.angle) - rect_template.angle; cout << "angle:" << angle << endl; //对contours_template基于重心进行旋转和缩放, //然后偏移contours_search[i],绘制到待检图中 Moments mom_template = moments(contours_template[0]); Moments mom_search = moments(contours_search[i]); Point gravity_template = Point(mom_template.m10 / mom_template.m00, mom_template.m01 / mom_template.m00); Point gravity_search = Point(mom_search.m10 / mom_search.m00, mom_search.m01 / mom_search.m00); //对模板轮廓进行平移,旋转和缩放 for (int j = 0; j < contours_template[0].size(); j++) { //重心平移到原点 int x = contours_template[0][j].x; int y = contours_template[0][j].y; x -= gravity_template.x; y -= gravity_template.y; //缩放 x *= scale; y *= scale; //旋转 float rad = angle / 180.0*3.1416; contours_template[0][j].x = x*cos(rad) - y * sin(rad); contours_template[0][j].y = x*sin(rad) + y * cos(rad); } //画带有偏移的从模板经过缩放和旋转生成的轮廓 drawContours(searchImageResult, contours_template, -1, Scalar(0, 255, 0),4,8,noArray(),LONG_MAX, gravity_search); } } imshow("templateImage", templateImage); imshow("searchImage", searchImage); imshow("searchImageResult", searchImageResult); waitKey();
总结
该方法由于要找到反映目标外轮廓的曲线,因此对背景干扰较为敏感。但由于不使用滑动窗口的方法,算法复杂度降低,处理时间减少
————————————————
版权声明:本文为CSDN博主「仟人斩」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/iamqianrenzhan/article/details/105040587

