cvHoughCircle 解析
【原文:http://blog.csdn.net/mysee1989/article/details/12405949】
在網上看了一篇關於cvHoughCircle函式的解析,轉載過來看看。學習Opencv,不少人只能呼叫函式而不能作出適配或者修改,這是一種遺憾,貌似很少有這方面的部落格,不知道有人推薦一些關於OpenCV原始碼解析的資料沒有?
一、基本原理
opencv中圓識別的基本原理如下:
1、canny運算元求影象的單畫素二值化邊緣
2、假設我們需要找半徑為R的所有圓,則對於邊緣圖中的每一個邊緣點,該邊緣點的切線的法線方向上(正負兩個方向),尋找到該邊緣點距離為R的點,將該點的計數加1(初始化所有點的計數都是0)
3、找到計數值大於門限值的點,即圓心所在的點
二、程式碼分析
程式碼在/modules\imgproc\src\hough.cpp檔案icvHoughCirclesGradient函式中
- 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 )
- {
- //引數:
- //img: 輸入影象
- //dp: 識別精度,1.0表示按照原圖精度
- //min_dist: 圓心點位置識別精度
- //min_radius: 所需要找的圓的最小半徑
- //max_radius:所需要找的圓的最大半徑
- //canny_threshold:canny運算元的高閥值
- //acc_threshold:累加器閥值,計數大於改閥值的點即被認為是可能的圓心
- //circles: 儲存找到的符合條件的所有圓
- //circles_max: 最多需要的找到的圓的個數
- const int SHIFT = 10, ONE = 1 << SHIFT, R_THRESH = 30;
- cv::Ptr<CvMat> dx, dy;
- cv::Ptr<CvMat> edges, accum, dist_buf;
- std::vector<int> sort_buf;
- cv::Ptr<CvMemStorage> 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;
- //canny運算元求單畫素二值化邊緣,儲存在edges變數中
- edges = cvCreateMat( img->rows, img->cols, CV_8UC1 );
- cvCanny( img, edges, MAX(canny_threshold/2,1), canny_threshold, 3 );
- //sobel運算元求水平和垂直方向的邊緣,用於計算邊緣點的法線方向
- dx = cvCreateMat( img->rows, img->cols, CV_16SC1 );
- dy = cvCreateMat( img->rows, img->cols, CV_16SC1 );
- cvSobel( img, dx, 1, 0, 3 );
- cvSobel( img, dy, 0, 1, 3 );
- //dp表示識別精度
- if( dp < 1.f )
- dp = 1.f;
- idp = 1.f/dp;
- //accum用作累加器,包含影象中每一個點的計數。影象中每一個點都有一個計數,點的計數表示每一個canny邊緣點法線方向上,
- //到該點距離為R的邊緣點的個數,初始化為0
- 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 );
- //centers用於儲存可能的圓心點
- 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]);
- //以下這個迴圈用於獲取所有可能的圓邊緣點,儲存在nz中,同時設定
- //累加器中的值
- 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, k;
- 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表示cos, sy表示sin
- sx = cvRound((vx*idp)*ONE/mag);
- sy = cvRound((vy*idp)*ONE/mag);
- x0 = cvRound((x*idp)*ONE);
- y0 = cvRound((y*idp)*ONE);
- //迴圈兩次表示需要計算兩個方向,法線方向和法線的反方向
- for( k = 0; k < 2; k++ )
- {
- //半徑方向的水平增量和垂直增量
- x1 = x0 + min_radius * sx;
- y1 = y0 + min_radius * sy;
- //在法線方向和反方向上,距離邊緣點的距離為輸入的最大半徑和最小半徑範圍內找點
- //每找到一個點,該點的累加器計數就加1
- 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;
- //累加器中,計數大於閥值的點,被認為可能的圓心點。因為計算各點計數過程中,距離有誤差,所以
- //在與閥值比較時,該點計數先要與4鄰域內的各個點的計數比較,最大者才能和閥值比較。可能的圓心
- //點儲存在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) );
- //連結串列結構的certers轉化成連續儲存結構sort_buf
- cvCvtSeqToArray( centers, &sort_buf[0] );
- //經過icvHoughSortDescent32s函式後,以sort_buf中元素作為adata陣列下標,
- //adata中的元素降序排列, 即adata[sort_buf[0]]是adata所有元素中最大的,
- //adata[sort_buf[center_count-1]]是所有元素中最小的
- icvHoughSortDescent32s( &sort_buf[0], center_count, adata );
- cvClearSeq( centers );
- //經過排序後的元素,重新以連結串列形式儲存到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;
- //對於每一個可能的圓心點,計算所有邊緣點到該圓心點的距離。由於centers中的
- //元素已經經過前面排序,所以累加器計數最大的可能圓心點最先進行下面的操作
- for( i = 0; i < centers->total; i++ )
- {
- int ofs = *(int*)cvGetSeqElem( centers, i );
- y = ofs/(acols+2) - 1;
- x = ofs - (y+1)*(acols+2) - 1;
- float cx = (float)(x*dp), cy = (float)(y*dp);
- float start_dist, dist_sum;
- float r_best = 0, c[3];
- int max_count = R_THRESH;
- //如果該可能的圓心點和已經確認的圓心點的距離小於閥值,則表示
- //這個圓心點和已經確認的圓心點是同一個點
- for( j = 0; j < circles->total; j++ )
- {
- 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;
- cvStartReadSeq( nz, &reader );
- //求所有邊緣點到當前圓心點的距離,符合條件的距離值儲存在ddata中
- 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 );
- //經過如下處理後,以sort_buf中元素作為ddata陣列下標,ddata中的元素降序排列,
- //即ddata[sort_buf[0]]是ddata所有元素中最大的, ddata[sort_buf[nz_count1-1]]
- //是所有元素中最小的
- icvHoughSortDescent32s( &sort_buf[0], nz_count1, (int*)ddata );
- //對所有的距離值做處理,求出最可能圓半徑值,max_count為到圓心的距離為最可能半徑值的點的個數
- 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]];
- if( (start_idx - j)*r_best >= max_count*r_cur ||
- (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;
- }
- //max_count大於閥值,表示這幾個邊緣點構成一個圓
- if( max_count > R_THRESH )
- {
- c[0] = cx;
- c[1] = cy;
- c[2] = (float)r_best;
- cvSeqPush( circles, c );
- if( circles->total > circles_max )
- return;
- }
- }
- }
- 頂
- 0
- 踩
相關文章
- 解析-解析器
- ORACLE SQL解析之硬解析和軟解析OracleSQL
- ORACLE 硬解析和軟解析 軟軟解析Oracle
- 解析-HTML 解析器HTML
- DOM解析和SAX解析
- 軟解析和硬解析
- Oracle中的遊標、硬解析、軟解析、軟軟解析、解析失敗Oracle
- 徹底弄懂oracle硬解析、軟解析、軟軟解析Oracle
- Java解析Excel例項解析JavaExcel
- Oracle 硬解析與軟解析Oracle
- 解析
- Nginx(六):配置解析之location解析Nginx
- Oracle的硬解析和軟解析Oracle
- dom解析和sax解析的區別
- 雲解析DNS如何實現智慧解析?DNS
- XML 檔案解析實踐 (DOM 解析)XML
- Oracle SQL的硬解析和軟解析OracleSQL
- 草稿 - 遊標,硬解析,軟解析 等
- TS流解析之PMT表格解析(轉)
- TS流解析之PAT表格解析(轉)
- 使用JAXP進行DOM解析_SAX解析
- soft parse(軟解析),hard parse(硬解析)
- 解析 SQLiteOpenHelperSQLite
- this 全面解析
- OkHttp解析HTTP
- this全面解析
- Semaphore解析
- Xml解析XML
- Go解析Go
- 容器解析
- 解析ExcelExcel
- Handler解析
- HTML解析HTML
- pull解析
- 解析Ruby
- DOM 解析
- HTTP解析HTTP
- Intent 解析Intent