Opencv源码分析 HoughCircles

el/2024/4/20 0:17:24

转者注: 其实霍夫变换理论和opencv中HoughCircles的实现是有根本的不同的,霍夫变换基于像素对自己所属于的直线或曲线参数方程参数进行投票,最终得票高的就是大概率在图像中存在的曲线。而HoughCircles则是根据像素(可能的圆周边缘)的梯度、边缘、边缘方向信息进行圆心定位,然后结合圆周信息和其他约束(如半径大小)进行最终圆的位置的确定。


图形可以用一些参数进行表示,标准霍夫变换的原理就是把图像空间转换成参数空间(即霍夫空间),例如霍夫变换的直线检测就是在距离-角度空间内进行检测。圆可以表示成:

(x-a)2+(y-b)2=r2                  (1)

其中a和b表示圆心坐标,r表示圆半径,因此霍夫变换的圆检测就是在这三个参数组成的三维空间内进行检测。

原则上,霍夫变换可以检测任何形状。但复杂的形状需要的参数就多,霍夫空间的维数就多,因此在程序实现上所需的内存空间以及运行效率上都不利于把标准霍夫变换应用于实际复杂图形的检测中。所以一些改进的霍夫变换就相继提出,它们的基本原理就是尽可能减小霍夫空间的维数。

HoughCircles函数实现了圆形检测,它使用的算法也是改进的霍夫变换——2-1霍夫变换(21HT)。也就是把霍夫变换分为两个阶段,从而减小了霍夫空间的维数。第一阶段用于检测圆心,第二阶段从圆心推导出圆半径。检测圆心的原理是圆心是它所在圆周所有法线的交汇处,因此只要找到这个交点,即可确定圆心,该方法所用的霍夫空间与图像空间的性质相同,因此它仅仅是二维空间。检测圆半径的方法是从圆心到圆周上的任意一点的距离(即半径)是相同,只要确定一个阈值,只要相同距离的数量大于该阈值,我们就认为该距离就是该圆心所对应的圆半径,该方法只需要计算半径直方图,不使用霍夫空间。圆心和圆半径都得到了,那么通过公式1一个圆形就得到了。从上面的分析可以看出,2-1霍夫变换把标准霍夫变换的三维霍夫空间缩小为二维霍夫空间,因此无论在内存的使用上还是在运行效率上,2-1霍夫变换都远远优于标准霍夫变换。但该算法有一个不足之处就是由于圆半径的检测完全取决于圆心的检测,因此如果圆心检测出现偏差,那么圆半径的检测肯定也是错误的。2-1霍夫变换的具体步骤为:

第一阶段:检测圆心

1.1、对输入图像边缘检测;

1.2、计算图形的梯度,并确定圆周线,其中圆周的梯度就是它的法线;

1.3、在二维霍夫空间内,绘出所有图形的梯度直线,某坐标点上累加和的值越大,说明在该点上直线相交的次数越多,也就是越有可能是圆心;

1.4、在霍夫空间的4邻域内进行非最大值抑制;

1.5、设定一个阈值,霍夫空间内累加和大于该阈值的点就对应于圆心。

第二阶段:检测圆半径

2.1、计算某一个圆心到所有圆周线的距离,这些距离中就有该圆心所对应的圆的半径的值,这些半径值当然是相等的,并且这些圆半径的数量要远远大于其他距离值相等的数量;

2.2、设定两个阈值,定义为最大半径和最小半径,保留距离在这两个半径之间的值,这意味着我们检测的圆不能太大,也不能太小;

2.3、对保留下来的距离进行排序;

2.4、找到距离相同的那些值,并计算相同值的数量;

2.5、设定一个阈值,只有相同值的数量大于该阈值,才认为该值是该圆心对应的圆半径;

2.6、对每一个圆心,完成上面的2.1~2.5步骤,得到所有的圆半径。

HoughCircles函数的原型为:

void HoughCircles(InputArray image,OutputArray circles, int method, double dp, double minDist, double param1=100, double param2=100, int minRadius=0,int maxRadius=0 )

image为输入图像,要求是灰度图像

circles为输出圆向量,每个向量包括三个浮点型的元素——圆心横坐标,圆心纵坐标和圆半径

method为使用霍夫变换圆检测的算法,Opencv2.4.9只实现了2-1霍夫变换,它的参数是CV_HOUGH_GRADIENT

dp为第一阶段所使用的霍夫空间的分辨率,dp=1时表示霍夫空间与输入图像空间的大小一致,dp=2时霍夫空间是输入图像空间的一半,以此类推

minDist为圆心之间的最小距离,如果检测到的两个圆心之间距离小于该值,则认为它们是同一个圆心

param1为边缘检测时使用Canny算子的高阈值

param2为步骤1.5和步骤2.5中所共有的阈值

minRadius和maxRadius为所检测到的圆半径的最小值和最大值

HoughCircles函数在sources/modules/imgproc/src/hough.cpp文件内被定义:

[cpp] view plain copy
  1. void cv::HoughCircles( InputArray _image, OutputArray _circles,  
  2.                        int method, double dp, double min_dist,  
  3.                        double param1, double param2,  
  4.                        int minRadius, int maxRadius )  
  5. {  
  6.     //定义一段内存  
  7.     Ptr<CvMemStorage> storage = cvCreateMemStorage(STORAGE_SIZE);  
  8.     Mat image = _image.getMat();    //提取输入图像矩阵  
  9.     CvMat c_image = image;    //矩阵转换  
  10.     //调用cvHoughCircles函数  
  11.     CvSeq* seq = cvHoughCircles( &c_image, storage, method,  
  12.                     dp, min_dist, param1, param2, minRadius, maxRadius );  
  13.     //把序列转换为矩阵  
  14.     seqToMat(seq, _circles);  
  15. }  
cvHoughCircles函数为:
[cpp] view plain copy
  1. CV_IMPL CvSeq*  
  2. cvHoughCircles( CvArr* src_image, void* circle_storage,  
  3.                 int method, double dp, double min_dist,  
  4.                 double param1, double param2,  
  5.                 int min_radius, int max_radius )  
  6. {  
  7.     CvSeq* result = 0;  
  8.   
  9.     CvMat stub, *img = (CvMat*)src_image;  
  10.     CvMat* mat = 0;  
  11.     CvSeq* circles = 0;  
  12.     CvSeq circles_header;  
  13.     CvSeqBlock circles_block;  
  14.     int circles_max = INT_MAX;    //输出最多圆形的数量,设为无穷多  
  15.     //canny边缘检测中双阈值中的高阈值  
  16.     int canny_threshold = cvRound(param1);  
  17.     //累加器阈值  
  18.     int acc_threshold = cvRound(param2);  
  19.   
  20.     img = cvGetMat( img, &stub );  
  21.     //确保输入图像是灰度图像  
  22.     if( !CV_IS_MASK_ARR(img))  
  23.         CV_Error( CV_StsBadArg, "The source image must be 8-bit, single-channel" );  
  24.     //内存空间是否存在  
  25.     if( !circle_storage )  
  26.         CV_Error( CV_StsNullPtr, "NULL destination" );  
  27.     //确保参数的正确性  
  28.     if( dp <= 0 || min_dist <= 0 || canny_threshold <= 0 || acc_threshold <= 0 )  
  29.         CV_Error( CV_StsOutOfRange, "dp, min_dist, canny_threshold and acc_threshold must be all positive numbers" );  
  30.     //圆的最小半径要大于0  
  31.     min_radius = MAX( min_radius, 0 );  
  32.     //圆的最大半径如果小于0,则设最大半径为图像宽和长度的最大值,  
  33.     //如果最大半径小于最小半径,则设最大半径为最小半径加两个像素的宽度  
  34.     if( max_radius <= 0 )  
  35.         max_radius = MAX( img->rows, img->cols );  
  36.     else if( max_radius <= min_radius )  
  37.         max_radius = min_radius + 2;  
  38.   
  39.     if( CV_IS_STORAGE( circle_storage ))  
  40.     {  
  41.         circles = cvCreateSeq( CV_32FC3, sizeof(CvSeq),  
  42.             sizeof(float)*3, (CvMemStorage*)circle_storage );  
  43.     }  
  44.     else if( CV_IS_MAT( circle_storage ))  
  45.     {  
  46.         mat = (CvMat*)circle_storage;  
  47.   
  48.         if( !CV_IS_MAT_CONT( mat->type ) || (mat->rows != 1 && mat->cols != 1) ||  
  49.             CV_MAT_TYPE(mat->type) != CV_32FC3 )  
  50.             CV_Error( CV_StsBadArg,  
  51.             "The destination matrix should be continuous and have a single row or a single column" );  
  52.   
  53.         circles = cvMakeSeqHeaderForArray( CV_32FC3, sizeof(CvSeq), sizeof(float)*3,  
  54.                 mat->data.ptr, mat->rows + mat->cols - 1, &circles_header, &circles_block );  
  55.         circles_max = circles->total;  
  56.         cvClearSeq( circles );  
  57.     }  
  58.     else  
  59.         CV_Error( CV_StsBadArg, "Destination is not CvMemStorage* nor CvMat*" );  
  60.     //选择哪种算法检测圆,目前只有2-1霍夫变换  
  61.     switch( method )  
  62.     {  
  63.     case CV_HOUGH_GRADIENT:  
  64.         //调用icvHoughCirclesGradient函数  
  65.         icvHoughCirclesGradient( img, (float)dp, (float)min_dist,  
  66.                                 min_radius, max_radius, canny_threshold,  
  67.                                 acc_threshold, circles, circles_max );  
  68.           break;  
  69.     default:  
  70.         CV_Error( CV_StsBadArg, "Unrecognized method id" );  
  71.     }  
  72.   
  73.     if( mat )  
  74.     {  
  75.         if( mat->cols > mat->rows )  
  76.             mat->cols = circles->total;  
  77.         else  
  78.             mat->rows = circles->total;  
  79.     }  
  80.     else  
  81.         result = circles;  
  82.     //输出圆  
  83.     return result;  
  84. }  
icvHoughCirclesGradient函数为:
[cpp] view plain copy
  1. static void  
  2. icvHoughCirclesGradient( CvMat* img, float dp, float min_dist,  
  3.                          int min_radius, int max_radius,  
  4.                          int canny_threshold, int acc_threshold,  
  5.                          CvSeq* circles, int circles_max )  
  6. {  
  7.     //为了提高运算精度,定义一个数值的位移量  
  8.     const int SHIFT = 10, ONE = 1 << SHIFT;  
  9.     //定义水平梯度和垂直梯度矩阵的地址指针  
  10.     cv::Ptr<CvMat> dx, dy;  
  11.     //定义边缘图像、累加器矩阵和半径距离矩阵的地址指针  
  12.     cv::Ptr<CvMat> edges, accum, dist_buf;  
  13.     //定义排序向量  
  14.     std::vector<int> sort_buf;  
  15.     cv::Ptr<CvMemStorage> storage;  
  16.   
  17.     int x, y, i, j, k, center_count, nz_count;  
  18.     //事先计算好最小半径和最大半径的平方  
  19.     float min_radius2 = (float)min_radius*min_radius;  
  20.     float max_radius2 = (float)max_radius*max_radius;  
  21.     int rows, cols, arows, acols;  
  22.     int astep, *adata;  
  23.     float* ddata;  
  24.     //nz表示圆周序列,centers表示圆心序列  
  25.     CvSeq *nz, *centers;  
  26.     float idp, dr;  
  27.     CvSeqReader reader;  
  28.     //创建一个边缘图像矩阵  
  29.     edges = cvCreateMat( img->rows, img->cols, CV_8UC1 );  
  30.     //第一阶段  
  31.     //步骤1.1,用canny边缘检测算法得到输入图像的边缘图像  
  32.     cvCanny( img, edges, MAX(canny_threshold/2,1), canny_threshold, 3 );  
  33.     //创建输入图像的水平梯度图像和垂直梯度图像  
  34.     dx = cvCreateMat( img->rows, img->cols, CV_16SC1 );  
  35.     dy = cvCreateMat( img->rows, img->cols, CV_16SC1 );  
  36.     //步骤1.2,用Sobel算子法计算水平梯度和垂直梯度  
  37.     cvSobel( img, dx, 1, 0, 3 );  
  38.     cvSobel( img, dy, 0, 1, 3 );  
  39.     /确保累加器矩阵的分辨率不小于1  
  40.     if( dp < 1.f )  
  41.         dp = 1.f;  
  42.     //分辨率的倒数  
  43.     idp = 1.f/dp;  
  44.     //根据分辨率,创建累加器矩阵  
  45.     accum = cvCreateMat( cvCeil(img->rows*idp)+2, cvCeil(img->cols*idp)+2, CV_32SC1 );  
  46.     //初始化累加器为0  
  47.     cvZero(accum);  
  48.     //创建两个序列,  
  49.     storage = cvCreateMemStorage();  
  50.     nz = cvCreateSeq( CV_32SC2, sizeof(CvSeq), sizeof(CvPoint), storage );  
  51.     centers = cvCreateSeq( CV_32SC1, sizeof(CvSeq), sizeof(int), storage );  
  52.   
  53.     rows = img->rows;    //图像的高  
  54.     cols = img->cols;    //图像的宽  
  55.     arows = accum->rows - 2;    //累加器的高  
  56.     acols = accum->cols - 2;    //累加器的宽  
  57.     adata = accum->data.i;    //累加器的地址指针  
  58.     astep = accum->step/sizeof(adata[0]);    /累加器的步长  
  59.     // Accumulate circle evidence for each edge pixel  
  60.     //步骤1.3,对边缘图像计算累加和  
  61.     for( y = 0; y < rows; y++ )  
  62.     {  
  63.         //提取出边缘图像、水平梯度图像和垂直梯度图像的每行的首地址  
  64.         const uchar* edges_row = edges->data.ptr + y*edges->step;  
  65.         const short* dx_row = (const short*)(dx->data.ptr + y*dx->step);  
  66.         const short* dy_row = (const short*)(dy->data.ptr + y*dy->step);  
  67.   
  68.         for( x = 0; x < cols; x++ )  
  69.         {  
  70.             float vx, vy;  
  71.             int sx, sy, x0, y0, x1, y1, r;  
  72.             CvPoint pt;  
  73.             //当前的水平梯度值和垂直梯度值  
  74.             vx = dx_row[x];  
  75.             vy = dy_row[x];  
  76.             //如果当前的像素不是边缘点,或者水平梯度值和垂直梯度值都为0,则继续循环。因为如果满足上面条件,该点一定不是圆周上的点  
  77.             if( !edges_row[x] || (vx == 0 && vy == 0) )  
  78.                 continue;  
  79.             //计算当前点的梯度值  
  80.             float mag = sqrt(vx*vx+vy*vy);  
  81.             assert( mag >= 1 );  
  82.             //定义水平和垂直的位移量  
  83.             sx = cvRound((vx*idp)*ONE/mag);  
  84.             sy = cvRound((vy*idp)*ONE/mag);  
  85.             //把当前点的坐标定位到累加器的位置上  
  86.             x0 = cvRound((x*idp)*ONE);  
  87.             y0 = cvRound((y*idp)*ONE);  
  88.             // Step from min_radius to max_radius in both directions of the gradient  
  89.             //在梯度的两个方向上进行位移,并对累加器进行投票累计  
  90.             for(int k1 = 0; k1 < 2; k1++ )  
  91.             {  
  92.                 //初始一个位移的启动  
  93.                 //位移量乘以最小半径,从而保证了所检测的圆的半径一定是大于最小半径  
  94.                 x1 = x0 + min_radius * sx;  
  95.                 y1 = y0 + min_radius * sy;  
  96.                 //在梯度的方向上位移  
  97.                 // r <= max_radius保证了所检测的圆的半径一定是小于最大半径  
  98.                 for( r = min_radius; r <= max_radius; x1 += sx, y1 += sy, r++ )  
  99.                 {  
  100.                     int x2 = x1 >> SHIFT, y2 = y1 >> SHIFT;  
  101.                     //如果位移后的点超过了累加器矩阵的范围,则退出  
  102.                     if( (unsigned)x2 >= (unsigned)acols ||  
  103.                         (unsigned)y2 >= (unsigned)arows )  
  104.                         break;  
  105.                     //在累加器的相应位置上加1  
  106.                     adata[y2*astep + x2]++;  
  107.                 }  
  108.                 //把位移量设置为反方向  
  109.                 sx = -sx; sy = -sy;  
  110.             }  
  111.             //把输入图像中的当前点(即圆周上的点)的坐标压入序列圆周序列nz中  
  112.             pt.x = x; pt.y = y;  
  113.             cvSeqPush( nz, &pt );  
  114.         }  
  115.     }  
  116.     //计算圆周点的总数  
  117.     nz_count = nz->total;  
  118.     //如果总数为0,说明没有检测到圆,则退出该函数  
  119.     if( !nz_count )  
  120.         return;  
  121.     //Find possible circle centers  
  122.     //步骤1.4和1.5,遍历整个累加器矩阵,找到可能的圆心  
  123.     for( y = 1; y < arows - 1; y++ )  
  124.     {  
  125.         for( x = 1; x < acols - 1; x++ )  
  126.         {  
  127.             int base = y*(acols+2) + x;  
  128.             //如果当前的值大于阈值,并在4邻域内它是最大值,则该点被认为是圆心  
  129.             if( adata[base] > acc_threshold &&  
  130.                 adata[base] > adata[base-1] && adata[base] > adata[base+1] &&  
  131.                 adata[base] > adata[base-acols-2] && adata[base] > adata[base+acols+2] )  
  132.                 //把当前点的地址压入圆心序列centers中  
  133.                 cvSeqPush(centers, &base);  
  134.         }  
  135.     }  
  136.     //计算圆心的总数  
  137.     center_count = centers->total;  
  138.     //如果总数为0,说明没有检测到圆,则退出该函数  
  139.     if( !center_count )  
  140.         return;  
  141.     //定义排序向量的大小  
  142.     sort_buf.resize( MAX(center_count,nz_count) );  
  143.     //把圆心序列放入排序向量中  
  144.     cvCvtSeqToArray( centers, &sort_buf[0] );  
  145.     //对圆心按照由大到小的顺序进行排序  
  146.     //它的原理是经过icvHoughSortDescent32s函数后,以sort_buf中元素作为adata数组下标,adata中的元素降序排列,即adata[sort_buf[0]]是adata所有元素中最大的,adata[sort_buf[center_count-1]]是所有元素中最小的  
  147.     icvHoughSortDescent32s( &sort_buf[0], center_count, adata );  
  148.     //清空圆心序列  
  149.     cvClearSeq( centers );  
  150.     //把排好序的圆心重新放入圆心序列中  
  151.     cvSeqPushMulti( centers, &sort_buf[0], center_count );  
  152.     //创建半径距离矩阵  
  153.     dist_buf = cvCreateMat( 1, nz_count, CV_32FC1 );  
  154.     //定义地址指针  
  155.     ddata = dist_buf->data.fl;  
  156.   
  157.     dr = dp;    //定义圆半径的距离分辨率  
  158.     //重新定义圆心之间的最小距离  
  159.     min_dist = MAX( min_dist, dp );  
  160.     //最小距离的平方  
  161.     min_dist *= min_dist;  
  162.     // For each found possible center  
  163.     // Estimate radius and check support  
  164.     //按照由大到小的顺序遍历整个圆心序列  
  165.     for( i = 0; i < centers->total; i++ )  
  166.     {  
  167.         //提取出圆心,得到该点在累加器矩阵中的偏移量  
  168.         int ofs = *(int*)cvGetSeqElem( centers, i );  
  169.         //得到圆心在累加器中的坐标位置  
  170.         y = ofs/(acols+2);  
  171.         x = ofs - (y)*(acols+2);  
  172.         //Calculate circle's center in pixels  
  173.         //计算圆心在输入图像中的坐标位置  
  174.         float cx = (float)((x + 0.5f)*dp), cy = (float)(( y + 0.5f )*dp);  
  175.         float start_dist, dist_sum;  
  176.         float r_best = 0;  
  177.         int max_count = 0;  
  178.         // Check distance with previously detected circles  
  179.         //判断当前的圆心与之前确定作为输出的圆心是否为同一个圆心  
  180.         for( j = 0; j < circles->total; j++ )  
  181.         {  
  182.             //从序列中提取出圆心  
  183.             float* c = (float*)cvGetSeqElem( circles, j );  
  184.             //计算当前圆心与提取出的圆心之间的距离,如果两者距离小于所设的阈值,则认为两个圆心是同一个圆心,退出循环  
  185.             if( (c[0] - cx)*(c[0] - cx) + (c[1] - cy)*(c[1] - cy) < min_dist )  
  186.                 break;  
  187.         }  
  188.         //如果j < circles->total,说明当前的圆心已被认为与之前确定作为输出的圆心是同一个圆心,则抛弃该圆心,返回上面的for循环  
  189.         if( j < circles->total )  
  190.             continue;  
  191.         // Estimate best radius  
  192.         //第二阶段  
  193.         //开始读取圆周序列nz  
  194.         cvStartReadSeq( nz, &reader );  
  195.         for( j = k = 0; j < nz_count; j++ )  
  196.         {  
  197.             CvPoint pt;  
  198.             float _dx, _dy, _r2;  
  199.             CV_READ_SEQ_ELEM( pt, reader );  
  200.             _dx = cx - pt.x; _dy = cy - pt.y;  
  201.             //步骤2.1,计算圆周上的点与当前圆心的距离,即半径  
  202.             _r2 = _dx*_dx + _dy*_dy;  
  203.             //步骤2.2,如果半径在所设置的最大半径和最小半径之间  
  204.             if(min_radius2 <= _r2 && _r2 <= max_radius2 )  
  205.             {  
  206.                 //把半径存入dist_buf内  
  207.                 ddata[k] = _r2;  
  208.                 sort_buf[k] = k;  
  209.                 k++;  
  210.             }  
  211.         }  
  212.         //k表示一共有多少个圆周上的点  
  213.         int nz_count1 = k, start_idx = nz_count1 - 1;  
  214.         //nz_count1等于0也就是k等于0,说明当前的圆心没有所对应的圆,意味着当前圆心不是真正的圆心,所以抛弃该圆心,返回上面的for循环  
  215.         if( nz_count1 == 0 )  
  216.             continue;  
  217.         dist_buf->cols = nz_count1;    //得到圆周上点的个数  
  218.         cvPow( dist_buf, dist_buf, 0.5 );    //求平方根,得到真正的圆半径  
  219.         //步骤2.3,对圆半径进行排序  
  220.         icvHoughSortDescent32s( &sort_buf[0], nz_count1, (int*)ddata );  
  221.   
  222.         dist_sum = start_dist = ddata[sort_buf[nz_count1-1]];  
  223.         //步骤2.4  
  224.         for( j = nz_count1 - 2; j >= 0; j-- )  
  225.         {  
  226.             float d = ddata[sort_buf[j]];  
  227.   
  228.             if( d > max_radius )  
  229.                 break;  
  230.             //d表示当前半径值,start_dist表示上一次通过下面if语句更新后的半径值,dr表示半径距离分辨率,如果这两个半径距离之差大于距离分辨率,说明这两个半径一定不属于同一个圆,而两次满足if语句条件之间的那些半径值可以认为是相等的,即是属于同一个圆  
  231.             if( d - start_dist > dr )  
  232.             {  
  233.                 //start_idx表示上一次进入if语句时更新的半径距离排序的序号  
  234.                 // start_idx – j表示当前得到的相同半径距离的数量  
  235.                 //(j + start_idx)/2表示j和start_idx中间的数  
  236.                 //取中间的数所对应的半径值作为当前半径值r_cur,也就是取那些半径值相同的值  
  237.                 float r_cur = ddata[sort_buf[(j + start_idx)/2]];  
  238.                 //如果当前得到的半径相同的数量大于最大值max_count,则进入if语句  
  239.                 if( (start_idx - j)*r_best >= max_count*r_cur ||  
  240.                     (r_best < FLT_EPSILON && start_idx - j >= max_count) )  
  241.                 {  
  242.                     r_best = r_cur;    //把当前半径值作为最佳半径值  
  243.                     max_count = start_idx - j;    //更新最大值  
  244.                 }  
  245.                 //更新半径距离和序号  
  246.                 start_dist = d;  
  247.                 start_idx = j;  
  248.                 dist_sum = 0;  
  249.             }  
  250.             dist_sum += d;  
  251.         }  
  252.         // Check if the circle has enough support  
  253.         //步骤2.5,最终确定输出  
  254.         //如果相同半径的数量大于所设阈值  
  255.         if( max_count > acc_threshold )  
  256.         {  
  257.             float c[3];  
  258.             c[0] = cx;    //圆心的横坐标  
  259.             c[1] = cy;    //圆心的纵坐标  
  260.             c[2] = (float)r_best;    //所对应的圆的半径  
  261.             cvSeqPush( circles, c );    //压入序列circles内  
  262.             //如果得到的圆大于阈值,则退出该函数  
  263.             if( circles->total > circles_max )  
  264.                 return;  
  265.         }  
  266.     }  
  267. }  

 

下面是用HoughCircles函数进行霍夫变换圆检测的实例。由于HoughCircles函数内是调用Canny函数进行边缘检测,opencv的Canny函数是不包括平滑滤波这一步的,因此为了增强抗干扰能力,在使用HoughCircles函数之前,我们先对原图进行滤波处理,我们使用的是高斯模糊方法。
[cpp] view plain copy
  1. #include "opencv2/core/core.hpp"  
  2. #include "opencv2/highgui/highgui.hpp"  
  3. #include "opencv2/imgproc/imgproc.hpp"  
  4.   
  5. #include <iostream>  
  6. using namespace cv;  
  7. using namespace std;  
  8.   
  9. int main( int argc, char** argv )  
  10. {  
  11.     Mat src, gray;  
  12.     src=imread("coins.jpg");  
  13.     if( !src.data )    
  14.         return -1;    
  15.       
  16.     cvtColor( src, gray, CV_BGR2GRAY );  
  17.     //高斯模糊平滑  
  18.     GaussianBlur( gray, gray, Size(9, 9), 2, 2 );  
  19.   
  20.     vector<Vec3f> circles;  
  21.     //霍夫变换  
  22.     HoughCircles( gray, circles, CV_HOUGH_GRADIENT, 1, gray.rows/20, 100, 60, 0, 0 );  
  23.   
  24.     //在原图中画出圆心和圆  
  25.     forsize_t i = 0; i < circles.size(); i++ )  
  26.     {  
  27.         //提取出圆心坐标  
  28.         Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));  
  29.         //提取出圆半径  
  30.         int radius = cvRound(circles[i][2]);  
  31.         //圆心  
  32.         circle( src, center, 3, Scalar(0,255,0), -1, 8, 0 );  
  33.         //圆  
  34.         circle( src, center, radius, Scalar(0,0,255), 3, 8, 0 );  
  35.    }  
  36.   
  37.     namedWindow( "Circle", CV_WINDOW_AUTOSIZE );  
  38.     imshow( "Circle", src );  
  39.   
  40.     waitKey(0);  
  41.     return 0;  
  42. }  
下图为圆检测的结果。

从实际运行的结果来看,我们发现HoughCircles函数不足之处是所需要的参数较多,而且每个参数的改变对结果影响都很大,即漏检和错检的几率很大。

http://www.ngui.cc/el/5181856.html

相关文章

机器学习中的梯度消失、爆炸原因及其解决方法

前言 本文主要深入介绍深度学习中的梯度消失和梯度爆炸的问题以及解决方案。本文分为三部分&#xff0c;第一部分主要直观的介绍深度学习中为什么使用梯度更新&#xff0c;第二部分主要介绍深度学习中梯度消失及爆炸的原因&#xff0c;第三部分对提出梯度消失及爆炸的解决方案。…

opencv3.x的坑 ubuntu

最近有些想法想拿opencv来验证一下&#xff0c;由于之前曾重装过系统&#xff0c;之后用apt-get的方法安装过&#xff0c;但原来的程序里有 #include<opencv2/contrib/contrib.hpp> 这句&#xff0c;升级后的opencv把不稳定模块放在了contrib里&#xff0c;默认并不安…

caffe segnet opencv pgm格式图像操作 之坑

项目并没有结束&#xff0c;但就想吐槽一下软件里的防不胜防的坑&#xff0c;这些坑几乎可以让人浪费掉绝大多数的时间&#xff0c;不得不说&#xff0c;调试这些坑真的很痛苦。。。 caffe segnet 篇&#xff1a; 在github上直接下载的源码包似乎有个bug&#xff0c;至少googl…

relu激活函数解读

Tensorflow学习——ReLu 转载 2017年07月17日 22:39:50 预训练的用处&#xff1a;规则化&#xff0c;防止过拟合&#xff1b;压缩数据&#xff0c;去除冗余&#xff1b;强化特征&#xff0c;减小误差&#xff1b;加快收敛速度。标准的sigmoid输出不具备稀疏性&#xff0c;需要…

TensorFlow基础学习(函数、接口、拓展、实例)

TensorFlow是谷歌基于DistBelief进行研发的第二代人工智能学习系统&#xff0c;其命名来源于本身的运行原理。Tensor(张量)意味着N维数组&#xff0c;Flow(流)意味着基于数据流图的计算&#xff0c;TensorFlow为张量从流图的一端流动到另一端计算过程。TensorFlow是将复杂的数据…

Tensorflow构建卷积神经网络

【方向】 2017-08-24 16:31:56 浏览8502 评论2 云栖社区 函数 http test variables 神经网络 摘要&#xff1a; 本文主要和大家分享如何使用Tensorflow从头开始构建和训练卷积神经网络。这样就可以将这个知识作为一个构建块来创造有趣的深度学习应用程序了。 0. 简介 …

Tensorflow 读入数据

Tensorflow 学习笔记&#xff1a;Input Pipeline - Dataset 原创 2017年11月23日 13:29:47 标签&#xff1a;Tensorflow /dataset /机器学习 /算法 /pipeline Dataset是Tensorflow里面一个比较重要的概念&#xff0c;我们知道机器学习算法需要大概的数据来训练data model. 所…

Tensorflow官方教程(中文译)

https://www.w3cschool.cn/tensorflow_python/tensorflow_python-bm7y28si.html

Tensorflow dataset

相关用法总是会忘&#xff0c;特此转载记下&#xff0c;同时在此向原创作者表示感谢。 Dataset是Tensorflow里面一个比较重要的概念&#xff0c;我们知道机器学习算法需要大概的数据来训练data model. 所以Dataset就是用来做这么一件重要的事情&#xff1a;定义数据pipline&…

SWIG实现Python封装调用C/C++代码

TensorFlow中的SWIG实现Python调用C/C代码 2017年01月05日 18:51:45 阅读数&#xff1a;2832 转自here SWIG是个帮助使用C或者C编写的软件能与其它各种高级编程语言进行嵌入联接的开发工具。SWIG能应用于各种不同类型的语言&#xff0c;包括常用脚本编译语言&#xff0c;例如Pe…