鄙人初学opencv,在论坛上有人在做PCB板圆形焊盘检测,自己尝试用三种方法来检测圆形焊盘,下面为检测图像:
1、轮廓和霍夫圆变换
1 void main() 2 { 3 IplImage* src=cvLoadImage("1.jpg",CV_LOAD_IMAGE_UNCHANGED);//三通道的彩色图像 4 IplImage* dst=cvCreateImage(cvSize(src->width,src->height),IPL_DEPTH_8U,1); 5 cvCvtColor(src,dst,CV_RGB2GRAY); 6 for (int i=0;i<dst->height;i++) //反色 7 { 8 uchar* ptr=(uchar*)(dst->imageData+i*dst->widthStep); 9 for (int j=0;j<dst->width;j++) 10 { 11 ptr[j]=255-ptr[j]; 12 } 13 } 14 cvSmooth(dst,dst,CV_MEDIAN,11); 15 cvThreshold(dst,dst,200,255,CV_THRESH_TOZERO); 16 //创建核,结构元素原点非常重要, 17 //以5*5的核为例,中心点在(2,2)位置,改为(1,1)后,画出整个轮廓向左上角平移 18 IplConvKernel* myModel=cvCreateStructuringElementEx(5,5,2,2,CV_SHAPE_ELLIPSE); 19 /*cvErode(dst,dst,myModel,3);*/ 20 /*cvDilate(dst,dst,myModel,2);*/ 21 /*cvMorphologyEx(dst,dst,NULL,NULL,CV_MOP_OPEN,1);*/ 22 cvMorphologyEx(dst,dst,NULL,myModel,CV_MOP_OPEN,4); 23 /*cvMorphologyEx(dst,dst,NULL,NULL,CV_MOP_CLOSE,1);*/ 24 25 ////////////////////////////////////////////////////////////////////////// 26 /* 27 * 从上面的二值图可以看出,对于圆提取影响较大的是矩形 28 * 而矩形和圆分开,可以用圆形度特征分开,实践证明该方法不可行 29 * 另一种思路:比较矩形和圆轮廓的外接矩形,利用长与宽的差值作为特征来实现 30 */ 31 //利用圆形度来检测 32 //for (CvSeq* c=contour;c!=NULL;c=c->h_next) 33 //{ 34 // //实验表明圆形度不可用 35 // //计算轮廓面积 36 // double area=cvContourArea(c,CV_WHOLE_SEQ); 37 // //计算轮廓周长 38 // double length=cvArcLength(c,CV_WHOLE_SEQ,-1); 39 // std::cout<<area<<std::endl; 40 // //计算圆形度 41 // double e=4*CV_PI*abs(area)/(length*length); 42 // 43 // //判断轮廓是否为圆 44 // if (e>=0.885) 45 // { 46 // cvDrawContours(dst,c,cvScalarAll(255),cvScalarAll(255),10,1); 47 // } 48 //} 49 CvMemStorage* storage=cvCreateMemStorage(); 50 CvSeq* results=cvHoughCircles(dst,storage,CV_HOUGH_GRADIENT,2.6,10,10,30); 51 for (int i=0;i<results->total;i++) 52 { 53 float* p=(float*)cvGetSeqElem(results,i); 54 if (p[2]>19||p[2]<12) 55 continue; 56 std::cout<<p[2]<<std::endl; 57 CvPoint2D32f pt=cvPoint2D32f(p[0],p[1]); 58 cvCircle(src,cvPointFrom32f(pt),cvRound(p[2]),CV_RGB(255,0,0));/**/ 59 } 60 cvShowImage("2",src); 61 cvWaitKey(); 62 }
结果如下图所示:
2、采用质心标准圆检测:
1 #include "Stafx.h" 2 void main() 3 { 4 IplImage* src=cvLoadImage("1.jpg",CV_LOAD_IMAGE_UNCHANGED);//三通道的彩色图像 5 IplImage* dst=cvCreateImage(cvSize(src->width,src->height),IPL_DEPTH_8U,1); 6 cvCvtColor(src,dst,CV_RGB2GRAY); 7 for (int i=0;i<dst->height;i++) //反色 8 { 9 uchar* ptr=(uchar*)(dst->imageData+i*dst->widthStep); 10 for (int j=0;j<dst->width;j++) 11 { 12 ptr[j]=255-ptr[j]; 13 } 14 } 15 cvSmooth(dst,dst,CV_MEDIAN,11); 16 cvThreshold(dst,dst,200,255,CV_THRESH_TOZERO); 17 //创建核,结构元素原点非常重要, 18 //以5*5的核为例,中心点在(2,2)位置,改为(1,1)后,画出整个轮廓向左上角平移 19 IplConvKernel* myModel=cvCreateStructuringElementEx(5,5,2,2,CV_SHAPE_ELLIPSE); 20 /*cvErode(dst,dst,myModel,3);*/ 21 /*cvDilate(dst,dst,myModel,2);*/ 22 /*cvMorphologyEx(dst,dst,NULL,NULL,CV_MOP_OPEN,1);*/ 23 cvMorphologyEx(dst,dst,NULL,myModel,CV_MOP_OPEN,4); 24 /*cvMorphologyEx(dst,dst,NULL,NULL,CV_MOP_CLOSE,1);*/ 25 26 ////////////////////////////////////////////////////////////////////////// 27 /* 28 * 从上面的二值图可以看出,对于圆提取影响较大的是矩形 29 * 而矩形和圆分开,可以用圆形度特征分开,实践证明该方法不可行 30 * 另一种思路:比较矩形和圆轮廓的外接矩形,利用长与宽的差值作为特征来实现 31 */ 32 //利用圆形度来检测 33 //for (CvSeq* c=contour;c!=NULL;c=c->h_next) 34 //{ 35 // //实验表明圆形度不可用 36 // //计算轮廓面积 37 // double area=cvContourArea(c,CV_WHOLE_SEQ); 38 // //计算轮廓周长 39 // double length=cvArcLength(c,CV_WHOLE_SEQ,-1); 40 // std::cout<<area<<std::endl; 41 // //计算圆形度 42 // double e=4*CV_PI*abs(area)/(length*length); 43 // 44 // //判断轮廓是否为圆 45 // if (e>=0.885) 46 // { 47 // cvDrawContours(dst,c,cvScalarAll(255),cvScalarAll(255),10,1); 48 // } 49 //} 50 CvMemStorage* storage=cvCreateMemStorage(); 51 //寻找轮廓 52 CvSeq* contour=NULL; 53 CvContourScanner scanner=cvStartFindContours(dst,storage,sizeof(CvContour), 54 CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE,cvPoint(0,0)); 55 CvSeq* sq=NULL; 56 do 57 { 58 sq=cvFindNextContour(scanner); 59 if (sq==NULL) break; 60 //计算外接矩形 61 CvRect rect=cvBoundingRect(sq,0); 62 //只有 63 int cha=rect.width-rect.height; 64 if (cha>5) 65 cvSubstituteContour(scanner,NULL); 66 } while (sq!=0); 67 sq=cvEndFindContours(&scanner); 68 //画轮廓二值图 69 std::vector<CvPoint2D32f> central; //存放质心 70 std::vector<int> label; 71 /************************************************************************/ 72 /* 从图上可以分析,该图中圆的半径大小只有两种形式,两种标准圆 73 可想而知,两种圆的半径是确定的,可以事先确定两种标准圆的半径的大小, 74 通过轮廓的二值图中,每个轮廓周长进行聚类分析,确定阈值为90,周长低于90 75 为小圆,半径为14个像素;周长高于90为小圆,半径为18个像素 76 */ 77 /************************************************************************/ 78 for (sq;sq!=NULL;sq=sq->h_next) 79 { 80 //寻找质心 81 CvMoments moment; 82 cvContourMoments(sq,&moment); 83 double m00=moment.m00; 84 double m10=moment.m10; 85 double m01=moment.m01; 86 CvPoint2D32f point=cvPoint2D32f(m10/m00,m01/m00); 87 central.push_back(point); //将每个轮库所得到质心保存下来,压入容器中 88 //计算轮廓周长 89 double length=cvArcLength(sq,CV_WHOLE_SEQ,-1); 90 if (length>90) 91 label.push_back(1); //大圆标签为1 92 else label.push_back(0); //小圆标签为0 93 cvDrawContours( dst, sq, cvScalarAll(255), cvScalarAll(0), -1, CV_FILLED, 8); 94 } 95 //腐蚀掉残留的轮廓 96 cvMorphologyEx(dst,dst,NULL,NULL,CV_MOP_OPEN,1); 97 printf("质心点的数目:%d",central.size()); 98 99 cvShowImage("1",dst); 100 /*cvWaitKey();*/ 101 //将圆画出来 102 std::vector<CvPoint2D32f>::iterator it; 103 std::vector<int>::iterator flag=label.begin(); //标签开始处 104 for (it=central.begin();it!=central.end(),flag!=label.end();it++,flag++) 105 { 106 if(*flag==1) 107 cvCircle(src,cvPointFrom32f(*it),18,CV_RGB(255,0,0)); //画大圆 108 if(*flag==0) 109 cvCircle(src,cvPointFrom32f(*it),14,CV_RGB(255,255,0)); //画小圆 110 cvCircle(src,cvPointFrom32f(*it),2,CV_RGB(0,255,0)); //画圆心 111 } 112 cvShowImage("2",src); 113 cvWaitKey(); 114 }
结果如下图所示:
3、第三种方法:多目标模板匹配算法
1 //模板匹配法 2 CvPoint cvGetNextMinLoc(IplImage*,IplImage*,CvPoint,double); 3 void main() 4 { 5 IplImage* image=cvLoadImage("1.jpg",CV_LOAD_IMAGE_UNCHANGED); 6 //将已经创建模板图像,图像的宽与高均为30,图像中央为一个半径18的三通道 7 //圆,作为模板 8 IplImage* templ=cvLoadImage("2.jpg",CV_LOAD_IMAGE_UNCHANGED); 9 int rwidth=image->width-templ->width+1; 10 int rheight=image->height-templ->height+1; 11 IplImage* result=cvCreateImage(cvSize(rwidth,rheight),IPL_DEPTH_32F,1); 12 std::vector<std::string> str; //容器用来存放窗口的名称 13 str.push_back("1"); 14 str.push_back("2"); 15 cvMatchTemplate(image,templ,result,CV_TM_CCORR_NORMED); 16 /************************************************************************/ 17 /* 模板匹配函数cvMatchTemplate依次计算模板与待测图片的重叠区域的相似度, 18 并将结果存入映射图像result当中,也就是说result图像中的每一个点的值依次 19 代表了一次相似度比较结果 20 */ 21 /************************************************************************/ 22 double min_val; //相似度最小值 23 double max_val; //相似度最大值 24 CvPoint min_loc; //相似度最小值所对应的坐标 25 CvPoint max_loc; //相似度最大值所对应的坐标 26 cvMinMaxLoc(result,&min_val,&max_val,&min_loc,&max_loc,NULL); 27 std::cout<<"相似度最小值:"<<min_val<<std::endl<<"相似度最大值:"<<max_val<<std::endl; 28 printf("相似度最小值位置坐标:(%d, %d)\n",min_loc.x,min_loc.y); 29 printf("相似度最大值位置坐标:(%d, %d)\n",max_loc.x,max_loc.y); 30 /************************************************************************/ 31 /* 32 这里有个奇怪的问题,CV_TM_CCORR_NORMED该匹配算法,相似度最大值为最好匹配, 33 首先,我通过两种方式进行对比: 34 第一种,通过在输入图像中截屏方式获取模板图像,但是匹配的话,需要将相似度最大值位置坐标 35 作为画矩形框的左上角的坐标; 36 第二种,通过在opencv中直接画出目标图像,保存为模板图像,但是匹配的话,需要将相似度最小值位置坐标 37 作为画矩形框的左上角的坐标 38 而本次用的是第二种方式 39 */ 40 /************************************************************************/ 41 cvRectangle(image,min_loc,cvPoint(min_loc.x+templ->width,min_loc.y+templ->height),CV_RGB(255,0,0)); 42 int count=35; 43 CvPoint newmin_loc=min_loc; 44 //寻找下一个相似度较低的 45 while(count--) 46 { 47 newmin_loc=cvGetNextMinLoc(result,templ,newmin_loc,max_val); 48 //画出目标图像的外接矩形 49 cvRectangle(image,newmin_loc,cvPoint(newmin_loc.x+templ->width,newmin_loc.y+templ->height),CV_RGB(255,0,0)); 50 } 51 cvShowImage(str[0].c_str(),result); 52 cvShowImage(str[1].c_str(),image); 53 cvWaitKey(); 54 } 55 //需找下一个匹配点 56 CvPoint cvGetNextMinLoc(IplImage* result,IplImage* templ,CvPoint min_loc, double max_val) 57 { 58 int startX=min_loc.x; 59 int startY=min_loc.y; 60 int endX=min_loc.x+templ->width; 61 int endY=min_loc.y+templ->height; 62 //防止越界 63 if(endX>result->width) 64 endX=result->width; 65 if(endY>result->height) 66 endY=result->height; 67 //将最小的相似度换成最大值,从而剔除最小的,重新寻找最小的 68 for(int i=startX;i<endX;i++) 69 for(int j=startY;j<endY;j++) 70 { 71 cvSetReal2D(result,j,i,max_val); 72 } 73 CvPoint newmax_loc; 74 CvPoint newmin_loc; 75 double newmax_val; 76 double newmin_val; 77 cvMinMaxLoc(result,&newmin_val,&newmax_val,&newmin_loc,&newmax_loc,NULL); 78 return newmin_loc; 79 }
这里模板图像:
结果:
这三种方法的精度还是不够,需要进一步努力。