【opencv源码剖析】霍夫圆hough circle

算法的整体思路:

1.根据设定的阈值canny_threshold,使用canny边缘检测得到可能为圆边缘的点edges。

2.分别计算x,y方向的sobel梯度,用来判断edges点的边缘梯度方向,一个点需计算正、反两个梯度方向(由源码中的k1控制)。

3.遍历所有的edges点,根据设定的圆半径范围[min_radius,max_radius],在累加投票图中统计圆心出现的次数(参数dp控制累加投票图尺寸)。一个edges点需在所有可能的梯度方向、半径上投票,即需2*(max_radius-min_radius)次投票。最终得到累加投票图adata,同时保存edges点到nz。

4.从第3步的adata中筛选出可能的圆心,判断方法为:圆心投票次数大于acc_threshold,且大于上下左右四个点的投票次数。得到圆心索引centers。

5.按投票次数,从高到低对圆心索引centers排序。此时,adata[centers[0]]即为最高的投票次数,centers[0]对应为圆心位置的索引。

6.遍历centers圆心坐标,计算每个圆心的半径,同时对圆心进行第二次筛选。半径计算方法:找出圆上点最多的半径。圆心第二次筛选方法:圆上点的个数需大于acc_threshold。


源码及注释:

static void
icvHoughCirclesGradient( CvMat* img, float dp, float min_dist,
                         int min_radius, int max_radius,
                         int canny_threshold, int acc_threshold,
                         CvSeq* circles, int circles_max )
{
    const int SHIFT = 10, ONE = 1 << SHIFT;//控制圆心坐标计算精度
    cv::Ptr dx, dy;
    cv::Ptr edges, accum, dist_buf;
    std::vector sort_buf;
    cv::Ptr storage;

    int x, y, i, j, k, center_count, nz_count;
    float min_radius2 = (float)min_radius*min_radius;
    float max_radius2 = (float)max_radius*max_radius;
    int rows, cols, arows, acols;
    int astep, *adata;
    float* ddata;
    CvSeq *nz, *centers;
    float idp, dr;
    CvSeqReader reader;

    edges = cvCreateMat( img->rows, img->cols, CV_8UC1 );
    cvCanny( img, edges, MAX(canny_threshold/2,1), canny_threshold, 3 );//canny得到可能的圆边缘点

    dx = cvCreateMat( img->rows, img->cols, CV_16SC1 );
    dy = cvCreateMat( img->rows, img->cols, CV_16SC1 );
    cvSobel( img, dx, 1, 0, 3 );//sobel结果用于计算点所在边缘梯度方向,结合半径来推算圆心的位置
    cvSobel( img, dy, 0, 1, 3 );

    if( dp < 1.f )//控制累加投票图与原图尺寸比例
        dp = 1.f;
    idp = 1.f/dp;
    accum = cvCreateMat( cvCeil(img->rows*idp)+2, cvCeil(img->cols*idp)+2, CV_32SC1 );
    cvZero(accum);

    storage = cvCreateMemStorage();
    nz = cvCreateSeq( CV_32SC2, sizeof(CvSeq), sizeof(CvPoint), storage );//用于存储canny得到的圆边缘点
    centers = cvCreateSeq( CV_32SC1, sizeof(CvSeq), sizeof(int), storage );//用于存储可能的圆心点索引


    rows = img->rows;
    cols = img->cols;
    arows = accum->rows - 2;
    acols = accum->cols - 2;
    adata = accum->data.i;
    astep = accum->step/sizeof(adata[0]);
    // Accumulate circle evidence for each edge pixel
    for( y = 0; y < rows; y++ )
    {
        const uchar* edges_row = edges->data.ptr + y*edges->step;
        const short* dx_row = (const short*)(dx->data.ptr + y*dx->step);
        const short* dy_row = (const short*)(dy->data.ptr + y*dy->step);

        for( x = 0; x < cols; x++ )
        {
            float vx, vy;
            int sx, sy, x0, y0, x1, y1, r;
            CvPoint pt;

            vx = dx_row[x];
            vy = dy_row[x];

            if( !edges_row[x] || (vx == 0 && vy == 0) )
                continue;

            float mag = sqrt(vx*vx+vy*vy);
            assert( mag >= 1 );
            sx = cvRound((vx*idp)*ONE/mag);//梯度方向,乘idp使x1映射投票图坐标
            sy = cvRound((vy*idp)*ONE/mag);

            x0 = cvRound((x*idp)*ONE);//坐标映射后的圆边缘点
            y0 = cvRound((y*idp)*ONE);
            // Step from min_radius to max_radius in both directions of the gradient
            for(int k1 = 0; k1 < 2; k1++ )
            {
                x1 = x0 + min_radius * sx;//圆心坐标,投票图坐标系
                y1 = y0 + min_radius * sy;

                for( r = min_radius; r <= max_radius; x1 += sx, y1 += sy, r++ )//遍历可能的圆半径
                {
                    int x2 = x1 >> SHIFT, y2 = y1 >> SHIFT;
                    if( (unsigned)x2 >= (unsigned)acols ||
                        (unsigned)y2 >= (unsigned)arows )
                        break;
                    adata[y2*astep + x2]++;//累加投票
                }

                sx = -sx; sy = -sy;//沿正负梯度方向计算两个可能的圆心
            }

            pt.x = x; pt.y = y;
            cvSeqPush( nz, &pt );
        }
    }

    nz_count = nz->total;
    if( !nz_count )
        return;
    //Find possible circle centers
    for( y = 1; y < arows - 1; y++ )
    {
        for( x = 1; x < acols - 1; x++ )
        {
            int base = y*(acols+2) + x;//圆心坐标索引
            if( adata[base] > acc_threshold &&//投票数大于上下左右
                adata[base] > adata[base-1] && adata[base] > adata[base+1] &&
                adata[base] > adata[base-acols-2] && adata[base] > adata[base+acols+2] )
                cvSeqPush(centers, &base);
        }
    }

    center_count = centers->total;
    if( !center_count )
        return;

    sort_buf.resize( MAX(center_count,nz_count) );
    cvCvtSeqToArray( centers, &sort_buf[0] );

    icvHoughSortDescent32s( &sort_buf[0], center_count, adata );//按投票次数进行排序
    cvClearSeq( centers );
    cvSeqPushMulti( centers, &sort_buf[0], center_count );

    dist_buf = cvCreateMat( 1, nz_count, CV_32FC1 );
    ddata = dist_buf->data.fl;

    dr = dp;
    min_dist = MAX( min_dist, dp );
    min_dist *= min_dist;
    // For each found possible center
    // Estimate radius and check support
    for( i = 0; i < centers->total; i++ )
    {
        int ofs = *(int*)cvGetSeqElem( centers, i );//遍历圆心索引,并根据索引值计算圆心坐标x,y
        y = ofs/(acols+2);
        x = ofs - (y)*(acols+2);
        //Calculate circle's center in pixels
        float cx = (float)((x + 0.5f)*dp), cy = (float)(( y + 0.5f )*dp);
        float start_dist, dist_sum;
        float r_best = 0;
        int max_count = 0;
        // Check distance with previously detected circles
        for( j = 0; j < circles->total; j++ )//与已经确定输出的圆比较,距离小于min_dist则排除掉
        {
            float* c = (float*)cvGetSeqElem( circles, j );
            if( (c[0] - cx)*(c[0] - cx) + (c[1] - cy)*(c[1] - cy) < min_dist )
                break;
        }

        if( j < circles->total )
            continue;
        // Estimate best radius
        cvStartReadSeq( nz, &reader );
        for( j = k = 0; j < nz_count; j++ )//遍历所有的圆边缘点
        {
            CvPoint pt;
            float _dx, _dy, _r2;
            CV_READ_SEQ_ELEM( pt, reader );
            _dx = cx - pt.x; _dy = cy - pt.y;
            _r2 = _dx*_dx + _dy*_dy;
            if(min_radius2 <= _r2 && _r2 <= max_radius2 )//判断圆边缘点是否在设置的半径范围内
            {
                ddata[k] = _r2;//保存距离值,点到圆心的距离
                sort_buf[k] = k;//保存距离索引,用于后续排序
                k++;
            }
        }

        int nz_count1 = k, start_idx = nz_count1 - 1;		
        if( nz_count1 == 0 )
            continue;
        dist_buf->cols = nz_count1;
        cvPow( dist_buf, dist_buf, 0.5 );
        icvHoughSortDescent32s( &sort_buf[0], nz_count1, (int*)ddata );//按距离值,由大到小排序

        dist_sum = start_dist = ddata[sort_buf[nz_count1-1]];
        for( j = nz_count1 - 2; j >= 0; j-- )//从最后一个最小距离开始,遍历所有距离值,判断正确的半径值
        {
            float d = ddata[sort_buf[j]];

            if( d > max_radius )
                break;

            if( d - start_dist > dr )//距离值发生变化
            {
                float r_cur = ddata[sort_buf[(j + start_idx)/2]];//r_cur=j到start_idx的平均距离值
                if( (start_idx - j)*r_best >= max_count*r_cur ||//start_idx=当前圆边缘点个数,max_cout=历史最大圆边缘点个数,除以各自的半径,减弱半径越大圆边缘点越多的影响
                    (r_best < FLT_EPSILON && start_idx - j >= max_count) )//简单的圆边缘点个数比较
                {
                    r_best = r_cur;//记录当前最佳半径、圆边缘点个数
                    max_count = start_idx - j;
                }
                start_dist = d;//开始下一个段统计
                start_idx = j;
                dist_sum = 0;
            }
            dist_sum += d;
        }
        // Check if the circle has enough support
        if( max_count > acc_threshold )
        {
            float c[3];
            c[0] = cx;
            c[1] = cy;
            c[2] = (float)r_best;
            cvSeqPush( circles, c );
            if( circles->total > circles_max )
                return;
        }
    }
}


你可能感兴趣的:(opencv源码剖析)