OpenCV霍夫变换识别圆

想用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);


 
 

OpenCV霍夫变换识别圆_第1张图片OpenCV霍夫变换识别圆_第2张图片

再对边缘检测后的图片用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;
		}
	}

}



圆度公式的那块,算法还有点问题,不完善。

主要问题是,我简单的把边缘检测后的黑点累加和认为是周长,原图片的黑点累加和认为是面积了。

但是如果,所截取的部分并不是完整的时候,周长的计算就会有问题。

以后有空再进行完善~



你可能感兴趣的:(opencv,霍夫圆变换,openCV霍夫圆变换,HoughCircles,圆度公式)