您好,登錄后才能下訂單哦!
標準霍夫變換本質上是把圖像映射到它的參數空間上,它需要計算所有的M個邊緣點,這樣它的運算量和所需內存空間都會很大。如果在輸入圖像中只是處理m(m<M)個邊緣點,則這m個邊緣點的選取是具有一定概率性的,因此該方法被稱為概率霍夫變換(Probabilistic Hough Transform)。該方法還有一個重要的特點就是能夠檢測出線端,即能夠檢測出圖像中直線的兩個端點,確切地定位圖像中的直線。
HoughLinesP函數就是利用概率霍夫變換來檢測直線的。它的一般步驟為:
1、隨機抽取圖像中的一個特征點,即邊緣點,如果該點已經被標定為是某一條直線上的點,則繼續在剩下的邊緣點中隨機抽取一個邊緣點,直到所有邊緣點都抽取完了為止;
2、對該點進行霍夫變換,并進行累加和計算;
3、選取在霍夫空間內值最大的點,如果該點大于閾值的,則進行步驟4,否則回到步驟1;
4、根據霍夫變換得到的最大值,從該點出發,沿著直線的方向位移,從而找到直線的兩個端點;
5、計算直線的長度,如果大于某個閾值,則被認為是好的直線輸出,回到步驟1。
HoughLinesP函數的原型為:
void HoughLinesP(InputArray image,OutputArray lines, double rho, double theta, int threshold, double minLineLength=0,double maxLineGap=0 )
image為輸入圖像,要求是8位單通道圖像
lines為輸出的直線向量,每條線用4個元素表示,即直線的兩個端點的4個坐標值
rho和theta分別為距離和角度的分辨率
threshold為閾值,即步驟3中的閾值
minLineLength為最小直線長度,在步驟5中要用到,即如果小于該值,則不被認為是一條直線
maxLineGap為最大直線間隙,在步驟4中要用到,即如果有兩條線段是在一條直線上,但它們之間因為有間隙,所以被認為是兩個線段,如果這個間隙大于該值,則被認為是兩條線段,否則是一條。
HoughLinesP函數是在sources/modules/imgproc/src/hough.cpp文件中被定義的:
void cv::HoughLinesP( InputArray _image, OutputArray _lines, double rho, double theta, int threshold, double minLineLength, double maxGap ) { Ptr<CvMemStorage> storage = cvCreateMemStorage(STORAGE_SIZE); Mat image = _image.getMat(); CvMat c_image = image; CvSeq* seq = cvHoughLines2( &c_image, storage, CV_HOUGH_PROBABILISTIC, rho, theta, threshold, minLineLength, maxGap ); seqToMat(seq, _lines); }
從HoughLinesP函數可以看出,該函數會調用cvHoughLines2函數。它通過參數CV_HOUGH_PROBABILISTIC,最終調用了icvHoughLinesProbabilistic函數:
static void icvHoughLinesProbabilistic( CvMat* image, float rho, float theta, int threshold, int lineLength, int lineGap, CvSeq *lines, int linesMax ) { //accum為累加器矩陣,mask為掩碼矩陣 cv::Mat accum, mask; cv::vector<float> trigtab; //用于存儲事先計算好的正弦和余弦值 //開辟一段內存空間 cv::MemStorage storage(cvCreateMemStorage(0)); //用于存儲特征點坐標,即邊緣像素的位置 CvSeq* seq; CvSeqWriter writer; int width, height; //圖像的寬和高 int numangle, numrho; //角度和距離的離散數量 float ang; int r, n, count; CvPoint pt; float irho = 1 / rho; //距離分辨率的倒數 CvRNG rng = cvRNG(-1); //隨機數 const float* ttab; //向量trigtab的地址指針 uchar* mdata0; //矩陣mask的地址指針 //確保輸入圖像的正確性 CV_Assert( CV_IS_MAT(image) && CV_MAT_TYPE(image->type) == CV_8UC1 ); width = image->cols; //提取出輸入圖像的寬 height = image->rows; //提取出輸入圖像的高 //由角度和距離分辨率,得到角度和距離的離散數量 numangle = cvRound(CV_PI / theta); numrho = cvRound(((width + height) * 2 + 1) / rho); //創建累加器矩陣,即霍夫空間 accum.create( numangle, numrho, CV_32SC1 ); //創建掩碼矩陣,大小與輸入圖像相同 mask.create( height, width, CV_8UC1 ); //定義trigtab的大小,因為要存儲正弦和余弦值,所以長度為角度離散數的2倍 trigtab.resize(numangle*2); //累加器矩陣清零 accum = cv::Scalar(0); //避免重復計算,事先計算好所需的所有正弦和余弦值 for( ang = 0, n = 0; n < numangle; ang += theta, n++ ) { trigtab[n*2] = (float)(cos(ang) * irho); trigtab[n*2+1] = (float)(sin(ang) * irho); } //賦值首地址 ttab = &trigtab[0]; mdata0 = mask.data; //開始寫入序列 cvStartWriteSeq( CV_32SC2, sizeof(CvSeq), sizeof(CvPoint), storage, &writer ); // stage 1. collect non-zero image points //收集圖像中的所有非零點,因為輸入圖像是邊緣圖像,所以非零點就是邊緣點 for( pt.y = 0, count = 0; pt.y < height; pt.y++ ) { //提取出輸入圖像和掩碼矩陣的每行地址指針 const uchar* data = image->data.ptr + pt.y*image->step; uchar* mdata = mdata0 + pt.y*width; for( pt.x = 0; pt.x < width; pt.x++ ) { if( data[pt.x] ) //是邊緣點 { mdata[pt.x] = (uchar)1; //掩碼的相應位置置1 CV_WRITE_SEQ_ELEM( pt, writer ); 把該坐標位置寫入序列 } else //不是邊緣點 mdata[pt.x] = 0; //掩碼的相應位置清0 } } //終止寫序列,seq為所有邊緣點坐標位置的序列 seq = cvEndWriteSeq( &writer ); count = seq->total; //得到邊緣點的數量 // stage 2. process all the points in random order //隨機處理所有的邊緣點 for( ; count > 0; count-- ) { // choose random point out of the remaining ones //步驟1,在剩下的邊緣點中隨機選擇一個點,idx為不大于count的隨機數 int idx = cvRandInt(&rng) % count; //max_val為累加器的最大值,max_n為最大值所對應的角度 int max_val = threshold-1, max_n = 0; //由隨機數idx在序列中提取出所對應的坐標點 CvPoint* point = (CvPoint*)cvGetSeqElem( seq, idx ); //定義直線的兩個端點 CvPoint line_end[2] = {{0,0}, {0,0}}; float a, b; //累加器的地址指針,也就是霍夫空間的地址指針 int* adata = (int*)accum.data; int i, j, k, x0, y0, dx0, dy0, xflag; int good_line; const int shift = 16; //提取出坐標點的橫、縱坐標 i = point->y; j = point->x; // "remove" it by overriding it with the last element //用序列中的最后一個元素覆蓋掉剛才提取出來的隨機坐標點 *point = *(CvPoint*)cvGetSeqElem( seq, count-1 ); // check if it has been excluded already (i.e. belongs to some other line) //檢測這個坐標點是否已經計算過,也就是它已經屬于其他直線 //因為計算過的坐標點會在掩碼矩陣mask的相對應位置清零 if( !mdata0[i*width + j] ) //該坐標點被處理過 continue; //不做任何處理,繼續主循環 // update accumulator, find the most probable line //步驟2,更新累加器矩陣,找到最有可能的直線 for( n = 0; n < numangle; n++, adata += numrho ) { //由角度計算距離 r = cvRound( j * ttab[n*2] + i * ttab[n*2+1] ); r += (numrho - 1) / 2; //在累加器矩陣的相應位置上數值加1,并賦值給val int val = ++adata[r]; //更新最大值,并得到它的角度 if( max_val < val ) { max_val = val; max_n = n; } } // if it is too "weak" candidate, continue with another point //步驟3,如果上面得到的最大值小于閾值,則放棄該點,繼續下一個點的計算 if( max_val < threshold ) continue; // from the current point walk in each direction // along the found line and extract the line segment //步驟4,從當前點出發,沿著它所在直線的方向前進,直到達到端點為止 a = -ttab[max_n*2+1]; //a=-sinθ b = ttab[max_n*2]; //b=cosθ //當前點的橫、縱坐標值 x0 = j; y0 = i; //確定當前點所在直線的角度是在45度~135度之間,還是在0~45或135度~180度之間 if( fabs(a) > fabs(b) ) //在45度~135度之間 { xflag = 1; //置標識位,標識直線的粗略方向 //確定橫、縱坐標的位移量 dx0 = a > 0 ? 1 : -1; dy0 = cvRound( b*(1 << shift)/fabs(a) ); //確定縱坐標 y0 = (y0 << shift) + (1 << (shift-1)); } else //在0~45或135度~180度之間 { xflag = 0; //清標識位 //確定橫、縱坐標的位移量 dy0 = b > 0 ? 1 : -1; dx0 = cvRound( a*(1 << shift)/fabs(b) ); //確定橫坐標 x0 = (x0 << shift) + (1 << (shift-1)); } //搜索直線的兩個端點 for( k = 0; k < 2; k++ ) { //gap表示兩條直線的間隙,x和y為搜索位置,dx和dy為位移量 int gap = 0, x = x0, y = y0, dx = dx0, dy = dy0; //搜索第二個端點的時候,反方向位移 if( k > 0 ) dx = -dx, dy = -dy; // walk along the line using fixed-point arithmetics, // stop at the image border or in case of too big gap //沿著直線的方向位移,直到到達圖像的邊界或大的間隙為止 for( ;; x += dx, y += dy ) { uchar* mdata; int i1, j1; //確定新的位移后的坐標位置 if( xflag ) { j1 = x; i1 = y >> shift; } else { j1 = x >> shift; i1 = y; } //如果到達了圖像的邊界,停止位移,退出循環 if( j1 < 0 || j1 >= width || i1 < 0 || i1 >= height ) break; //定位位移后掩碼矩陣位置 mdata = mdata0 + i1*width + j1; // for each non-zero point: // update line end, // clear the mask element // reset the gap //該掩碼不為0,說明該點可能是在直線上 if( *mdata ) { gap = 0; //設置間隙為0 //更新直線的端點位置 line_end[k].y = i1; line_end[k].x = j1; } //掩碼為0,說明不是直線,但仍繼續位移,直到間隙大于所設置的閾值為止 else if( ++gap > lineGap ) //間隙加1 break; } } //步驟5,由檢測到的直線的兩個端點粗略計算直線的長度 //當直線長度大于所設置的閾值時,good_line為1,否則為0 good_line = abs(line_end[1].x - line_end[0].x) >= lineLength || abs(line_end[1].y - line_end[0].y) >= lineLength; //再次搜索端點,目的是更新累加器矩陣和更新掩碼矩陣,以備下一次循環使用 for( k = 0; k < 2; k++ ) { int x = x0, y = y0, dx = dx0, dy = dy0; if( k > 0 ) dx = -dx, dy = -dy; // walk along the line using fixed-point arithmetics, // stop at the image border or in case of too big gap for( ;; x += dx, y += dy ) { uchar* mdata; int i1, j1; if( xflag ) { j1 = x; i1 = y >> shift; } else { j1 = x >> shift; i1 = y; } mdata = mdata0 + i1*width + j1; // for each non-zero point: // update line end, // clear the mask element // reset the gap if( *mdata ) { //if語句的作用是清除那些已經判定是好的直線上的點對應的累加器的值,避免再次利用這些累加值 if( good_line ) //在第一次搜索中已經確定是好的直線 { //得到累加器矩陣地址指針 adata = (int*)accum.data; for( n = 0; n < numangle; n++, adata += numrho ) { r = cvRound( j1 * ttab[n*2] + i1 * ttab[n*2+1] ); r += (numrho - 1) / 2; adata[r]--; //相應的累加器減1 } } //搜索過的位置,不管是好的直線,還是壞的直線,掩碼相應位置都清0,這樣下次就不會再重復搜索這些位置了,從而達到減小計算邊緣點的目的 *mdata = 0; } //如果已經到達了直線的端點,則退出循環 if( i1 == line_end[k].y && j1 == line_end[k].x ) break; } } //如果是好的直線 if( good_line ) { CvRect lr = { line_end[0].x, line_end[0].y, line_end[1].x, line_end[1].y }; //把兩個端點壓入序列中 cvSeqPush( lines, &lr ); //如果檢測到的直線數量大于閾值,則退出該函數 if( lines->total >= linesMax ) return; } } }
下面就給出應用HoughLinesP函數檢測直線段的應用程序:
#include "opencv2/core/core.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> using namespace cv; using namespace std; int main( int argc, char** argv ) { Mat src, edge,color_edge; src=imread("building.jpg"); if( !src.data ) return -1; Canny(src,edge,50,200,3); cvtColor( edge, color_edge, CV_GRAY2BGR ); vector<Vec4i> lines; HoughLinesP(edge, lines, 1, CV_PI/180, 80, 30, 10 ); for( size_t i = 0; i < lines.size(); i++ ) { Vec4i l = lines[i]; line( color_edge, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 2); } namedWindow( "lines", CV_WINDOW_AUTOSIZE ); imshow( "lines", color_edge ); waitKey(0); return 0; }
下圖為輸出的圖像:
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。