您的位置:首页 > 运维架构

OpenCV霍夫变换识别圆

2015-09-17 11:58 459 查看

想用openCV的 cvHoughCircles去识别实心的黑圆。但是cvHoughCircles的效果并不好,会检测出很多并不存在的拟合圆。因此还需在霍夫变换的基础上限定一些条件,对识别出的圆进行check。

因为我想要识别的是实心圆,因此对霍夫变换后的结果进行判断,圆心周围是否都是黑色的,是否是实心圆。

还加入了圆度公式对霍夫变换后的结果进行check:圆度D = 4πS/(L*L)。当对象越接近于圆时,圆度D越接近于1。

加上了这两个check条件后,cvHoughCircles的识别结果,达到了我想要的结果~

首先选定ROI,再对选定区域进行Canny算子的边缘检测。

[cpp]
view plaincopyprint?

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

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。满足条件的则为要找的圆。

[cpp]
view plaincopyprint?

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);
}

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);
}


[cpp]
view plaincopyprint?

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;
}
}

}

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;
}
}

}


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

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

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

以后有空再进行完善~

转载于:/article/1465576.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: