赞
踩
本项目是机器视觉课程的大作业,写的并不是很明晰,如有问题欢迎提出。
本项目为基于OpenCV的火花塞间隙尺寸测量,主要是运用模板匹配技术定位火花塞间隙尺寸,然后寻找火花塞间隙并测量其尺寸。
程序主要分成四个部分:
在此部分中,将完成模板的创建和待处理图片的路径读取。程序将从文件夹中读取model.png图片作为模板。如果文件夹中没有该图片,则将从第一张待处理图片中截取所需区域作为模板。
此函数主要调用OpenCV库中的matchTemplate函数以及相关函数寻找各种角度的图片中与模板的最佳匹配点,并且根据该点找寻区域的中心位置。模板匹配算法根据需要决定,本项目使用平均方差。
为了匹配旋转的图像,所以在匹配时要将图像进行旋转。
旋转使用的是仿射变换,仿射矩阵可用getRotationMatrix2D()得到M1。
由于在旋转图像后,图像的大小也会改变,所以不能直接使用M1进行变换,而是要用getAffineTransform()函数再获得一个大小变换矩阵M2
最后将M1的旋转部分和M2的大小变换部分相结合变成一个新的变换矩阵M,使用warpAffine()函数就能完成图像的仿射变换。
由于代码需要,还需要写坐标变换的函数,公式如下
M就是一个3*2的矩阵
如果单纯使用模板匹配,时间复杂度将爆炸,但我们可以使用图像金字塔来进行优化
建立n层图像金字塔,对于每一层金字塔,将图像向下采样,每 2 n 2^n 2n行 2 n 2^n 2n列取一个像素。由此获得 1 2 n \frac{1}{2^n} 2n1倍的图像和模板。采样完后的图像可用高斯滤波器滤波(本项目没有)
从高层开始将该层模板对该层图像进行模板匹配,所有匹配度高于某一阈值的点都将用一个矩形框住,并记录下旋转角度变换范围。
下一层在上一层截取的区域和角度中进行模板匹配,依次类推逐渐获得一个比较小的匹配区域和角度范围。
对n-1层图像金字塔依次进行匹配后,在源图像中找到得到的区域,并在相应角度范围中进行最终的模板匹配,获得中心点的位置。
当找到匹配区域的中心点位置后,垂直向下寻找火花塞间隙。将图片变换成灰度图之后,就可以发现向下直线上的两处灰度值突变的点便是间隙边缘上的点,计算两点间距离就是火花塞间隙的开度。
测量处相关函数之后就可以在图片上绘制测量区域,并且显示开度的数值
#include <opencv2/opencv.hpp> #include <iostream> #include <vector> using namespace cv; using namespace std; 其他函数/// void Min(int &a,int b) { if(a>b)a = b; } void Max(int &a,int b) { if(a<b)a = b; } Point GetCenter(int x,int y,int w,int h) { return Point(x+w/2,y+h/2); } /* 获取仿射矩阵 */ Mat GetMatrix(int cols,int rows,int newCols,int newRows,int degree) { Point2f center(cols / 2, rows/2);//中心 Mat M1 = getRotationMatrix2D(center, degree, 1);//计算旋转的仿射变换矩阵 Point2f srcPoints1[3]; Point2f dstPoints1[3]; srcPoints1[0] = Point2i(0, 0); srcPoints1[1] = Point2i(0, rows); srcPoints1[2] = Point2i(cols, 0); dstPoints1[0] = Point2i((newCols - cols)/2 , (newRows - rows)/2); dstPoints1[1] = Point2i((newCols - cols)/2 , (newRows + rows)/2); dstPoints1[2] = Point2i((newCols + cols)/2, (newRows - rows)/2); Mat M2 = getAffineTransform(srcPoints1, dstPoints1); M1.at<double>(0, 2) = M1.at<double>(0, 2) + M2.at<double>(0, 2); M1.at<double>(1, 2) = M1.at<double>(1, 2) + M2.at<double>(1, 2); return M1; } //旋转图像内容不变,尺寸相应变大 Mat Rotate(Mat src, int degree) { double angle = degree * CV_PI / 180.; double a = sin(angle), b = cos(angle); int rows=src.rows; int cols=src.cols; //旋转后的新图尺寸 int width_rotate= int(rows * fabs(a) + cols * fabs(b)); int height_rotate=int(cols * fabs(a) + rows * fabs(b)); Mat M = GetMatrix(cols,rows,width_rotate,height_rotate,degree); Mat dst= Mat::zeros(width_rotate, height_rotate, src.type()); warpAffine(src, dst, M, Size(width_rotate, height_rotate));//仿射变换 return dst; } /* 坐标变换 */ void TransCoor(Point &point,int degree,int rows,int cols,bool dir = 1) { double angle = degree * CV_PI / 180.; double a = sin(angle), b = cos(angle); //旋转后的新图尺寸 int width_rotate= ceil(rows * fabs(a) + cols * fabs(b)); int height_rotate=ceil(cols * fabs(a) + rows * fabs(b)); Mat M; if(dir == 0)//顺时针 { M = GetMatrix(cols,rows,width_rotate,height_rotate,degree); } else //逆时针 { M = GetMatrix(width_rotate,height_rotate,cols,rows,-degree); } point = Point(point.x*M.at<double>(0, 0)+point.y*M.at<double>(0,1)+M.at<double>(0,2), point.x*M.at<double>(1, 0)+point.y*M.at<double>(1, 1)+M.at<double>(1,2)); } int level = 3;//金字塔层级 int degreeMin,degreeMax;//旋转范围 int degree;//最终的旋转角度 /* 在ROI图像中匹配模板model */ Point Match(Mat src,Mat model) { Point temLoc;//最佳匹配点 double Min = 1; for(int d = degreeMin;d<=degreeMax;d++)//旋转角度 { Mat img = Rotate(src,d); int row = img.rows; int col = img.cols; Mat result(col, row, CV_32FC1); if(row < model.rows || col < model.cols)continue; matchTemplate(img, model, result, TM_SQDIFF_NORMED);//匹配模板 //normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());//归一化 Point minLoc; Point maxLoc; double min, max; //找到最匹配点(函数说明:在一个数组中找到全局最小值和全局最大值) minMaxLoc(result, &min, &max, &minLoc, &maxLoc, Mat()); if(min<Min) { temLoc = minLoc;//TM_SQDIFF_NORMED是最小值 degree = d;//最佳匹配角度 Min = min; } } return GetCenter(temLoc.x, temLoc.y, model.cols, model.rows); } /* 获得第level层金字塔图像 */ Mat GetPyramid(Mat src,int level) { int sample = 1<<level;//采样率为2^level Mat dst = Mat::zeros(Size(src.cols/sample,src.rows/sample),src.type()); for(int i=0;i<dst.rows;i++) for(int j=0;j<dst.cols;j++) { dst.at<Vec3b>(i,j) = src.at<Vec3b>(i*sample,j*sample); } return dst; } /* 缩小寻找范围 */ void FindROI(Mat src,Rect &ROI,int level,Mat model) { Mat pyramidSrc = GetPyramid(src,level); Mat pyramidModel = GetPyramid(model,level); Point topLeftP = Point(pyramidSrc.cols,pyramidSrc.rows); Point ButtonRightP = Point(0,0); bool isMatch = false; int dmin = 359,dmax = 0; for(int d = degreeMin;d<=degreeMax;d+=10*level)//旋转角度 { Mat img = Rotate(pyramidSrc,d); int row = img.rows; int col = img.cols; if(row < pyramidModel.rows || col < pyramidModel.cols)continue; Mat result(col, row, CV_32FC1); matchTemplate(img, pyramidModel, result, TM_SQDIFF_NORMED);//匹配模板 Point minLoc; Point maxLoc; double min, max; //找到最匹配点(函数说明:在一个数组中找到全局最小值和全局最大值) minMaxLoc(result, &min, &max, &minLoc, &maxLoc, Mat()); if(min<0.1*level) { isMatch = true; //匹配到的四个点 Point point[4] = { minLoc, Point(minLoc.x,minLoc.y+pyramidModel.rows), Point(minLoc.x+pyramidModel.cols,minLoc.y), Point(minLoc.x+pyramidModel.cols,minLoc.y+pyramidModel.rows) }; //寻找左上角和右下角 for(int i = 0;i<4;i++) { TransCoor(point[i],d,pyramidSrc.rows,pyramidSrc.cols); Min(topLeftP.x,point[i].x); Min(topLeftP.y,point[i].y); Max(ButtonRightP.x,point[i].x); Max(ButtonRightP.y,point[i].y); } Min(dmin,d); Max(dmax,d); } } if(isMatch == false)return ;//如果没匹配到则返回 //更新感兴趣区域 topLeftP*=1<<level; ButtonRightP*=1<<level; Max(ROI.x,topLeftP.x+ROI.x); Max(ROI.y,topLeftP.y+ROI.y); Min(ROI.width,ButtonRightP.x-topLeftP.x); Min(ROI.height,ButtonRightP.y-topLeftP.y); Max(ROI.x,0); Max(ROI.y,0); Min(ROI.width,src.cols); Min(ROI.height,src.rows); degreeMax = dmax; degreeMin = dmin; } Point FindTemplate(Mat src,Mat model) { Rect ROI = Rect(0,0,src.cols,src.rows); degreeMin = 0,degreeMax = 359; for(int i=level;i>=1;i--) { FindROI(src(ROI),ROI,i,model); } cout<<degreeMax<<" "<<degreeMin<<endl; //原始图像中匹配 Point center = Match(src(ROI),model); //还原中心坐标 TransCoor(center,degree,ROI.height,ROI.width); center.x+=ROI.x; center.y+=ROI.y; TransCoor(center,degree,src.rows,src.cols,false); return center; } /* 寻找直线端点 */ void FindLinePoint(Mat src,Point startP,Point &firstP,Point &secondP,int &dis) { int y = startP.y; Mat gray; cvtColor(src,gray,COLOR_RGB2GRAY); while(y<src.rows) { int deri = (int)gray.at<uchar>(y,startP.x)- (int)gray.at<uchar>(y+1,startP.x); if( deri < -65)//像素变大且跨度大于阈值则为第一个点 { firstP = Point(startP.x,y+1); } if( deri > 65)//像素变小且跨度大于阈值则为第二个点 { secondP = Point(startP.x,y+1); break; } y++; } dis = abs(firstP.y-secondP.y);//计算尺寸 return ; } void Deal(Mat src,Mat model) { //寻找匹配到的区域的中心点 Point center = FindTemplate(src,model); //寻找火花塞间隙的两个端点,并计算尺寸 Point firstP,secondP; int dis; FindLinePoint(Rotate(src,degree),center,firstP,secondP,dis); // //标记并输出图像 Mat dst; src.copyTo(dst); // //将坐标变换为原图 TransCoor(center,degree,src.rows,src.cols); TransCoor(firstP,degree,src.rows,src.cols); TransCoor(secondP,degree,src.rows,src.cols); circle(dst,center,3,Scalar(0,0,255)); line(dst,firstP,secondP,Scalar(0,0,255)); putText(dst,"d:"+to_string(dis),(secondP+firstP)/2+Point(10,0), FONT_HERSHEY_COMPLEX,0.5,Scalar(0,0,255)); imshow("dst",dst); } int main() { string pattern_jpg; vector<String> image_files; pattern_jpg = ".\\img\\*.png"; glob(pattern_jpg, image_files);//读取图片路径 //创建模板 string mode_path = ".\\img\\model.png"; Mat model = imread(mode_path);//模板 if(model.empty())//读取不到图片 { Mat img=imread(image_files[0]); model = img(Rect(250,150,120,80)); } //处理图片 for(int i=0;i<image_files.size();i++) { cout << image_files[i] << endl; if(image_files[i]==".\\img\\model.png")continue; Mat img=imread(image_files[i]); Deal(img,model); waitKey(); } return 0; }
在这个项目中我们学到了如何基于opencv库中的模板匹配实现待识别器件的定位,以及根据位置信息测量所需的参数。该技术可用于简单环境下的零件尺寸测量。由于需要旋转角度匹配时可以匹配旋转的图片,所以速度非常的慢。但使用图像金字塔进行优化后,速度提升非常之大。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。