想用openCV的 cvHoughCircles去识别实心的黑圆。但是cvHoughCircles的效果并不好,会检测出很多并不存在的拟合圆。因此还需在霍夫变换的基础上限定一些条件,对识别出的圆进行check。
因为我想要识别的是实心圆,因此对霍夫变换后的结果进行判断,圆心周围是否都是黑色的,是否是实心圆。
还加入了圆度公式对霍夫变换后的结果进行check:圆度D = 4πS/(L*L)。当对象越接近于圆时,圆度D越接近于1。
加上了这两个check条件后,cvHoughCircles的识别结果,达到了我想要的结果~
首先选定ROI,再对选定区域进行Canny算子的边缘检测。
IplImage *srcBitmap; srcBitmap = cvLoadImage(fileNameFull, 1); cvCvtColor(srcBitmap, srcBitmap, CV_RGB2GRAY); cvThreshold(srcBitmap,srcBitmap,200,255,THRESH_BINARY); cvSetImageROI(srcBitmap, cvRect(0, 0, 400, 400)); //pImg8uOri为原图,pImg8u为边缘检测后的图片 pImg8uOri = cvCreateImage(cvSize(400, 400), srcBitmap->depth, srcBitmap->nChannels); pImg8u = cvCreateImage(cvSize(400, 400), srcBitmap->depth, srcBitmap->nChannels); cvCopy(srcBitmap, pImg8uOri, NULL);cvResetImageROI(srcBitmap);cvCanny(pImg8uOri, pImg8u, 1, 2, 3);
再对边缘检测后的图片用cvHoughCircles进行找圆,对霍夫变换后的结果进行check。满足条件的则为要找的圆。
CvSeq * circles=NULL; CvMemStorage* storage = cvCreateMemStorage(0); circles=cvHoughCircles(pImg8u,storage,CV_HOUGH_GRADIENT, 1, //最小分辨率,应当>=1 //double类型的dp,用来检测圆心的累加器图像的分辨率于输入图像之比的倒数 //且此参数允许创建一个比输入图像分辨率低的累加器。 //例如,如果dp = 1,累加器和输入图像具有相同的分辨率 //如果dp = 2,累加器便有输入图像一半那么大的宽度和高度 50, //pImg8u->height/4, //该参数是让算法能明显区分的两个不同圆之间的最小距离 40, //用于Canny的边缘阀值上限,下限被置为上限的一半 2, //累加器的阀值 //越小就可以检测到更多根本不存在的圆。 //越大的话,检测到的圆就越接近完美的圆形 10, //最小圆半径 40 //最大圆半径 ); int k; int circleCount = 0; double maxD = 0; int maxPx = 0; int maxPy = 0; int maxR = 0; for (k=0;k<circles->total;k++){ float *p=(float*)cvGetSeqElem(circles,k); if ((cvRound(p[0])-15 < 0) || (cvRound(p[0]) + 15 >399)) continue; if ((cvRound(p[1])-15 < 0) || (cvRound(p[1]) + 15 >399)) continue; //check所找到的圆是否是实心的 if (!(isFilled(pImg8uOri,cvRound(p[0]),cvRound(p[1]),15))) continue; int oriCount = 0; int cannyCount = 0; //圆度D = 4πS/(L*L)。当对象越接近于圆时,圆度D越接近于1 int xLeft = cvRound(p[0]) - 24; if (xLeft < 0){ xLeft = 0; } int xRight = cvRound(p[0]) + 25; if (xRight > 399){ xRight = 399; } int yLeft = cvRound(p[1]) - 24; if (yLeft < 0){ yLeft = 0; } int yRight = cvRound(p[1]) + 25; if (yRight > 399){ yRight = 399; } int xCoordinate = 0; int yCoordinate = 0; int rightOri = 0; int rightCanny = 0; for(int x1 = xLeft; x1 < xRight; x1++){ for(int y1 = yLeft; y1 < yRight; y1++){ CvScalar s1 = cvGet2D(pImg8uOri, y1, x1); if (s1.val[0] < 128){ oriCount ++; xCoordinate = xCoordinate + x1; yCoordinate = yCoordinate + y1; } CvScalar s2 = cvGet2D(pImg8u, y1, x1); if (s2.val[0] > 128) cannyCount ++; if (x1 == xRight - 1){ if (s1.val[0] < 128){ rightOri = rightOri + 1; } if (s2.val[0] > 128){ rightCanny = rightCanny + 1; } } } //圆度公式需要算出周长,周长的算法我用的是求边缘检测后的该区域黑点的个数 //如果实心区域超出了选取的目标区域,则周长没闭合。需补偿 //暂时未考虑上下边超出了目标区域的情况 if (x1 == xLeft){ if (cannyCount < oriCount){ cannyCount = cannyCount + oriCount; } } if (x1 == xRight - 1){ if (rightCanny < rightOri) { cannyCount = cannyCount + rightOri; } } } //圆度越大越接近于圆 double D = 4 * PI * oriCount/(cannyCount * cannyCount); if (D < 0.4 || D >= 1) continue; //根据平均值校准圆心 int rectifyX = (double)xCoordinate/(double)oriCount + 0.5; int rectifyY = (double)yCoordinate/(double)oriCount + 0.5; //校准半径 int rectifyR1 = 0; for (int x1 = xLeft ; x1 < xRight; x1++ ){ CvScalar s = cvGet2D(pImg8uOri, rectifyY, x1); if (s.val[0] < 128) { rectifyR1 = rectifyX - x1; break; } } int rectifyR2 = 0; for (int x1 = xRight ; x1 > xLeft; x1-- ){ CvScalar s = cvGet2D(pImg8uOri, rectifyY, x1); if (s.val[0] < 128) { rectifyR2 = x1 - rectifyX; break; } } int rectifyR3 = 0; for (int y1 = yLeft ; y1 < yRight; y1++ ){ CvScalar s = cvGet2D(pImg8uOri, y1, rectifyX); if (s.val[0] < 128) { rectifyR3 = rectifyY - y1; break; } } int rectifyR4 = 0; for (int y1 = yRight ; y1 > yLeft; y1-- ){ CvScalar s = cvGet2D(pImg8uOri, y1, rectifyX); if (s.val[0] < 128) { rectifyR4 = y1 - rectifyY; break; } } int rectifyR = (double)(rectifyR1 + rectifyR2 + rectifyR3 + rectifyR4)/(double)4 + 0.5; //cvCircle(pImg8uOri,cvPoint(cvRound(p[0]),cvRound(p[1])),cvRound(p[2]),CV_RGB(0,255,0),1,CV_AA,0); //cvCircle(pImg8uOri,cvPoint(cvRound(p[0]),cvRound(p[1])),1,CV_RGB(155,50,255),1,CV_AA,0); int p2 = rectifyR; if(p2 >= 18 || p2 <= 12){ p2 = 15; } circleCount = circleCount + 1; if(maxD == 0){ maxD = D; maxPx = rectifyX; maxPy = rectifyY; maxR = p2; px = maxPx; py = maxPy; pr = maxR; } if(circleCount > 1){ /如果有多个圆符合条件,则选出圆度最大的圆,也就是最圆的圆 if(maxD > D){ //原来识别出的圆更圆 px = maxPx; py = maxPy; pr = maxR; }else{ //新圆更圆 maxPx = rectifyX; maxPy = rectifyY; maxR = p2; px = maxPx; py = maxPy; pr = maxR; } } return cvPoint(px,py); }
bool Card::isFilled(IplImage *pImg8u , int x ,int y , int r) { int xRight = x; int xLeft = x; int yTop = y; int yBottom = y; for (int x1 = x ; x1<=x+r;x1++ ) { CvScalar s = cvGet2D(pImg8u, y, x1); if (s.val[0] < 128) { xRight = x1; } else { break ; } } for (int y1=y ; y1<=y+r;y1++ ) { CvScalar s = cvGet2D(pImg8u, y1, x); if (s.val[0] < 128){ yBottom =y1; } else { break; } } for (int x1 = x ; x1>=x-r;x1-- ) { CvScalar s = cvGet2D(pImg8u, y, x1); if (s.val[0] < 128) { xLeft = x1; } else { break ; } } for (int y1=y ; y1>=y-r;y1-- ) { CvScalar s = cvGet2D(pImg8u, y1, x); if (s.val[0] < 128){ yTop =y1; } else { break; } } //边缘检测后的圆可能并不十分完美,houghCircle可能不能十分准确的找出圆心 //参数被从10调为7 if ((xRight-x)<7 || (x-xLeft)<7 || (yBottom-y)<7 || (y-yTop)<7) { return false; }else{ //圆点周围11*11的范围,点的覆盖率需大于95% if((x - 5)<=0 || (y - 5)<=0 || (x + 5)>=400 || (y + 5)>=400){ return false; } int SqCount = 0; for(int m1=x-5; m1<=x+5; m1++){ for(int m2=y-5; m2<=y+5; m2++){ CvScalar s3 = cvGet2D(pImg8u, m2, m1); if (s3.val[0] < 128){ SqCount = SqCount + 1; } } } if(SqCount >= 115){ return true; }else{ return false; } } }
圆度公式的那块,算法还有点问题,不完善。
主要问题是,我简单的把边缘检测后的黑点累加和认为是周长,原图片的黑点累加和认为是面积了。
但是如果,所截取的部分并不是完整的时候,周长的计算就会有问题。
以后有空再进行完善~