opencv6.5-imgproc影象處理模組之輪廓
這部分的《opencv_tutorial》上都是直接上程式碼,沒有原理部分的解釋的。
十一、輪廓
1、影象中找輪廓
/// 轉成灰度並模糊化降噪 cvtColor( src, src_gray, CV_BGR2GRAY ); blur( src_gray, src_gray, Size(3,3) );
Mat canny_output;//找到輪廓的圖 vector<vector<Point> > contours;//裝載曲線的點 vector<Vec4i> hierarchy; /// 用Canny運算元檢測邊緣 Canny( src_gray查詢輪廓的函式原型:void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point()), canny_output, thresh, thresh*2, 3 ); /// 尋找輪廓 findContours( canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) ); /// 繪出輪廓 Mat drawing = Mat::zeros( canny_output.size(), CV_8UC3 ); for( int i = 0; i< contours.size(); i++ )//通過對contours.size(),就知道有幾個分離的輪廓了 {Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) ); drawContours( drawing, contours, i, color, 2, 8, hierarchy, 0, Point() );//畫出輪廓 } /// 在窗體中顯示結果 namedWindow( "Contours", CV_WINDOW_AUTOSIZE ); imshow( "Contours", drawing );
void findContours(InputOutputArray image, OutputArrayOfArrays contours, int mode, int method, Point offset=Point())
引數列表:image:一個8-bit的單通道原影象。非零的畫素值被當作1來處理(類似bool型別)。0的畫素值仍舊還是0,所以這個image是被當作二值處理的。你可以使用compare(),inRange(),threshold(),adaptiveThreshold(),Canny(),和其他的從一個灰度或者彩色圖中生成一個二值圖。該函式會在提出輪廓的時候修改image。如果mode等於CV_RETR_CCOMP或者CV_RETR_FLOODFILL,那麼這個輸入同樣可以是標籤的32-bit整數圖(CV_32SC1)。
contours:檢測到的輪廓,每個輪廓都是儲存為點向量;
hierarchy:可選的輸出向量,包含影象拓撲資訊。它有著和輪廓數量一樣多的元素。對於第i 個輪廓contours[i],元素hierarchy【i】【0】,hiearchy【i】【1】,hiearchy【i】【2】和hiearchy【i】【3】是在contours中基於0索引的,分別表示在同一個層次級別上的下一個輪廓、上一個輪廓,第一個孩子輪廓和父親輪廓。如果輪廓 i 沒有下一個、上一個、父親或者巢狀的輪廓,對應的hierarchy【i】的元素就是負的。(這裡其實就是個樹結構,來進行索引不同的輪廓)
mode:輪廓索引模型
– CV_RETR_EXTERNAL 只檢索最外面的輪廓。它會對所有的輪廓設定 hierarchy[i][2] = hierarchy[i][3] = -1 .
– CV_RETR_LIST 不建立任何層次關係來進行索引所有的輪廓.
– CV_RETR_CCOMP 索引所有的輪廓並將它們組織成一個two-level的hierarchy(兩個層次的層級關係):在頂層上,有著成分的外部邊界;在第二層是孔洞的邊界。如果有另一個輪廓在一個連線起來的成分內部,那麼就將它放在頂層上。
– CV_RETR_TREE 檢索所有的輪廓並重構一個全層次的巢狀輪廓結構,在contours.c的例子中你更可以看到全層次建立的程式碼。
method :輪廓逼近的方法
– CV_CHAIN_APPROX_NONE 儲存所有輪廓點的絕對值。也就是說任何的輪廓的2子序列點 (x1,y1) 和 (x2,y2) 就是表示水平的,豎直的或者對角線鄰居,也就是: max(abs(x1-x2),abs(y2-y1))==1;
– CV_CHAIN_APPROX_SIMPLE 壓縮水平的,豎直的和對角線線段,只保留他們的終端。例如:對於一個up-right 的矩形輪廓是由4個點進行編碼的。
– CV_CHAIN_APPROX_TC89_L1, CV_CHAIN_APPROX_TC89_KCOS 使用 Teh-Chin 鏈逼近演算法中主流之一的演算法。
offset:可選的偏移量,通過這個值可以平移每一個輪廓。當輪廓是從一個影象的ROI中提取而你需要在整個影象上下文中分析的時候會變得很有用。
該函式使用演算法從二值影象中索引輪廓。輪廓對於形狀分析和物件的檢測識別是很有用的,在squares.c中有例子程式碼。
Notes:image會被該函式所修改,同樣的該函式不考慮影象的1畫素邊界(它會被0填充然後用來作為鄰居分析),因此接觸影象邊界的輪廓會被修剪(clip,夾)。
畫出輪廓的函式原型:void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar& color, int thickness=1, int lineType=LINE_8, InputArray hierarchy= noArray(), int maxLevel=INT_MAX, Point offset=Point() );
引數列表:目標影象、輸入的輪廓、輪廓的索引id、輪廓的顏色、粗度、線型別、可選的層次資訊、最大級別、偏移量;
第一個引數:目標影象,即畫布;
第二個引數:所有的輸入輪廓,每個輪廓儲存成點向量的形式;
第三個引數:用來索引需要畫的輪廓的引數,如果是負的,那麼就畫所有的輪廓;
第四個引數:輪廓的顏色;
第五個引數:畫的輪廓的線的粗細程度,如果是負的(例如:thickness = CV_FILLED),輪廓內部也會畫出來;
第六個引數:線連線型別,line()可以有更詳細的說明。
第七個引數:可選的關於層次的資訊。當你只想畫某些輪廓的時候才是需要的(見maxLevel);
第八個引數:畫輪廓的最大等級。如果為0,那麼只畫指定的輪廓。如果為1,該函式會畫輪廓及所有的巢狀輪廓。如果為2,該函式會畫輪廓、所有的巢狀輪廓、所有的巢狀-to-巢狀的輪廓,以此類推。該引數當hierarchy可用的時候才被考慮;
第九個引數:可選的輪廓平移引數。通過制定的平移量offset = (dx,dy)來平移所有的畫的輪廓。
該函式但thickness >=0的時候會畫輪廓的外部線條,或者當thickness <0的時候會填充輪廓的框起來的區域。下面的程式碼是展示如何從二值影象中索引聯通的成分,並且標記他們:
Mat src;
src = imread(name,0);
Mat dst = Mat::zeros(src.rows, src.cols, CV_8UC3);
src = src > 1;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours( src, contours, hierarchy,RETR_CCOMP, CHAIN_APPROX_SIMPLE );
//通過對top-level的輪廓進行迭代來畫出每個聯通的成分,使用隨機顏色
int idx = 0;
for( ; idx >= 0; idx = hierarchy[idx][0] ){
Scalar color( rand()&255, rand()&255, rand()&255 );
drawContours( dst, contours, idx, color, FILLED, 8, hierarchy );
}
namedWindow( "Components", 1 );
imshow( "Components", dst );
waitKey(0);
2、計算物體的凸包
凸包就是將物件進行外部輪廓的包含,而且是凸圖形的:
/// 轉成灰度圖並進行模糊降噪 cvtColor( src, src_gray, CV_BGR2GRAY ); blur( src_gray, src_gray, Size(3,3) );
Mat src_copy = src.clone(); Mat threshold_output; vector<vector<Point> > contours;//儲存輪廓的點集合 vector<Vec4i> hierarchy;//構建輪廓的層次結構 /// 對影象進行二值化
int thresh = 100; int max_thresh = 255; RNG rng(12345);threshold(src_gray,threshold_output,thresh,255,THRESH_BINARY);/// 尋找輪廓findContours(threshold_output,contours,hierarchy,CV_RETR_TREE,CV_CHAIN_APPROX_SIMPLE,Point(0,0));/// 對每個輪廓計算其凸包vector<vector<Point>>hull(contours.size());for(inti=0;i<contours.size();i++){
convexHull(Mat(contours[i]),hull[i],false);//凸包計算
}/// 繪出輪廓及其凸包Matdrawing=Mat::zeros(threshold_output.size(),CV_8UC3);for(inti=0;i<contours.size();i++){Scalarcolor=Scalar(rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255));drawContours(drawing,contours,i,color,1,8,vector<Vec4i>(),0,Point());//畫輪廓drawContours(drawing,hull,i,color,1,8,vector<Vec4i>(),0,Point());//畫凸包}/// 把結果顯示在窗體namedWindow("Hull demo",CV_WINDOW_AUTOSIZE);imshow("Hull demo",drawing);凸包函式原型:void convexHull(InputArray points, OutputArray hull, bool clockwise=false, bool returnPoints=true);
引數列表:2d點集合、輸出的凸包、定向標識、操作標識;
第一個引數:輸入的2D點集合,儲存在vector或者Mat中。
第二個引數:輸出的凸包,是一個包含索引的整數向量或者是點向量。在前者中,這個hull元素是在原始陣列中的凸包點上基於0索引的;後者中,hull元素自身就是凸包點。(也就是說該引數要不是索引到原始影象找凸包點,或者是直接提取凸包點);
第三個引數:定向標識,如果為true,那麼輸出的凸包就是順時針方向的;不然就是逆時針方向的。假設的座標系的x 軸指向右邊,y 軸指向上面;
第四個引數:操作標識,在矩陣Mat的情況中,當這個引數為true,該函式返回凸包點;否則返回指向凸包點的索引值;當在第二個引數是陣列vector的情況中,該標識被忽略,輸出是依賴vector的型別指定的,vector<int> 暗示returnPoints= true ,vector<Point>暗示 returnPoints= false。
該函式使用Sklansky的演算法來查詢一個2d點集的凸包,在當前的執行情況下的複雜度是O(NlogN),可以參見convexhull.cpp中驗證不同的函式變數的結果。
3、給輪廓加上矩形或者圓形邊界框
這個還是挺有用的,有時候識別一個物體,想先用簡單的數字影象處理方法得到更少的區域然後可以提取從而接著使用模式識別的方法進行訓練和建立分類器。
使用OpenCV函式 boundingRect 來計算包圍輪廓的矩形框.
int thresh = 100; int max_thresh = 255; RNG rng(12345);
/// 轉化成灰度影象並進行平滑 用來減少噪音點 cvtColor( src, src_gray, CV_BGR2GRAY ); blur( src_gray, src_gray, Size(3,3) );
Mat threshold_output; vector<vector<Point> > contours;//儲存輪廓點 vector<Vec4i> hierarchy;//構建不同輪廓的層次結構 /// 使用Threshold檢測邊緣 threshold( src_gray, threshold_output, thresh, 255, THRESH_BINARY ); /// 找到輪廓 findContours( threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) ); /// 多邊形逼近輪廓 + 獲取矩形和圓形邊界框 vector<vector<Point> > contours_poly( contours.size() );//建立contours.size()個空的多邊形 vector<Rect> boundRect( contours.size() );//建立contours.size()個矩形框 vector<Point2f>center( contours.size() );//建立contours.size()個圓心 vector<float>radius( contours.size() );//建立contours.size()個半徑 for( int i = 0; i < contours.size(); i++ ) {
approxPolyDP( Mat(contours[i]), contours_poly[i], 3, true );//多邊形逼近 boundRect[i] = boundingRect( Mat(contours_poly[i]) );//獲取某個輪廓的矩形框 minEnclosingCircle( contours_poly[i], center[i], radius[i] );//生成某個輪廓的最小包含圓的圓心和半徑 } /// 畫多邊形輪廓 + 包圍的矩形框 + 圓形框 Mat drawing = Mat::zeros( threshold_output.size(), CV_8UC3 ); for( int i = 0; i< contours.size(); i++ ) { Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );//隨機顏色 drawContours( drawing, contours_poly, i, color, 1, 8, vector<Vec4i>(), 0, Point() );//畫輪廓-多邊形 rectangle( drawing, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0 );//畫矩形 circle( drawing, center[i], (int)radius[i], color, 2, 8, 0 );//畫圓形 } /// 顯示在一個視窗 namedWindow( "Contours", CV_WINDOW_AUTOSIZE ); imshow( "Contours", drawing );多邊形逼近的函式原型:void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed);
引數列表:輸入陣列、逼近的結果、逼近的精度、是否閉合的標識;
第一個引數:一個2d點的輸入向量可以儲存在:
– std::vector or Mat (C++ interface)
– Nx2 numpy array (Python interface)
– CvSeq or ‘‘ CvMat (C interface)
第二個引數:逼近的結果,該引數的型別應該匹配輸入曲線的型別。(在c介面中,逼近的曲線儲存在記憶體儲存區中,返回的是指向該記憶體的指標,我列出來的都是cpp介面,所以該句可忽略);
第三個引數:指定逼近精度的引數。這是介於原始曲線與它的逼近之間的最大的距離;
第四個引數:如果為真,逼近的曲線是閉合的(即,將首尾連起來);否則就不是閉合的。
該函式使用一個更少頂點的曲線/多邊形來逼近另一個曲線或多邊形,使得介於他們之間的距離小於或者等於指定的精度。該函式使用的是Douglas-Peucker演算法。http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
計算一個點集合的up-right邊界矩形的函式原型:Rect boundingRect(InputArray points);
引數列表:輸入點;
第一個引數:輸入的2D點集合,儲存在vector或者Mat中。
該函式對指定的點集合計算並返回最小的up-right矩形。
計算一個2d點集合的最小區域圓的函式原型:void minEnclosingCircle(InputArray points, Point2f& center, float& radius)
引數列表:輸入的點集合、輸出的圓心座標、輸出的半徑;
第一個引數:2D點的輸入向量,儲存在:
– std::vector<> or Mat (C++ interface)
– CvSeq* or CvMat* (C interface)
– Nx2 numpy array (Python interface)
第二個引數:圓的輸出的圓心;
第三個引數:圓的輸出的半徑
該函式在一個2D點集合中使用一個迭代演算法來查詢最接近的圓,見minarea.cpp中有例子。
4、給輪廓加上傾斜的邊界框和橢圓
該部分與上面部分很相似,不過採用的函式是不同的。
int thresh = 100; int max_thresh = 255; RNG rng(12345);
/// 轉為灰度圖並模糊化 來減小噪音點 cvtColor( src, src_gray, CV_BGR2GRAY ); blur( src_gray, src_gray, Size(3,3) );
Mat threshold_output; vector<vector<Point> > contours;//儲存輪廓的向量 vector<Vec4i> hierarchy;//輪廓的層次結構 /// 閾值化檢測邊界 threshold( src_gray, threshold_output, thresh, 255, THRESH_BINARY ); /// 尋找輪廓 findContours( threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) ); /// 對每個找到的輪廓建立可傾斜的邊界框和橢圓 vector<RotatedRect> minRect( contours.size() );//儲存contours.size()個旋轉的邊界框***************此處是重點,RotateRect vector<RotatedRect> minEllipse( contours.size() );////儲存contours.size()個旋轉的橢圓************* for( int i = 0; i < contours.size(); i++ ) {
minRect[i] = minAreaRect( Mat(contours[i]) );//獲取包含最小區域的矩形 if( contours[i].size() > 5 ) {
minEllipse[i] = fitEllipse( Mat(contours[i]) );//獲取橢圓所需的資訊
} } /// 繪出輪廓及其可傾斜的邊界框和邊界橢圓 Mat drawing = Mat::zeros( threshold_output.size(), CV_8UC3 ); for( int i = 0; i< contours.size(); i++ ) { Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) ); // contour drawContours( drawing, contours, i, color, 1, 8, vector<Vec4i>(), 0, Point() );//畫物件的輪廓 // ellipse ellipse( drawing, minEllipse[i], color, 2, 8 );//畫橢圓 // rotated rectangle Point2f rect_points[4];
minRect[i].points( rect_points );//將旋轉矩形的頂點賦值給實參; for( int j = 0; j < 4; j++ ) line( drawing, rect_points[j], rect_points[(j+1)%4], color, 1, 8 );//畫出傾斜矩形,採用的是畫四條線的形式實現的 } /// 結果在窗體中顯示 namedWindow( "Contours", CV_WINDOW_AUTOSIZE ); imshow( "Contours", drawing );在輸入的2D點集合中找到一個最小區域的旋轉後的矩形的函式原型:RotatedRect minAreaRect(InputArray points) 引數列表:輸入的2D點集合 第一個引數:輸入的2D點的向量,儲存在: – std::vector<> or Mat (C++ interface)
– CvSeq* or CvMat* (C interface)
– Nx2 numpy array (Python interface)
該函式計算並返回一個指定的點集合的最小區域邊界的矩形,在minarea.cpp中有例子,開發者應該注意當資料是接近包含的矩陣元素邊界的時候 返回的旋轉矩形可以包含負指數。
沿著一個2D點集合進行擬合一個橢圓的函式原型:RotatedRect fitEllipse(InputArray points)
第一個引數:輸入的2D點集合,可以儲存在:
– std::vector<> or Mat (C++ interface)
– CvSeq* or CvMat* (C interface)
– Nx2 numpy array (Python interface)
該函式計算在對一個2D點集合擬合的最好的橢圓(最小二乘作為損失函式來判別)。它返回旋轉的矩形中內含的橢圓,使用的演算法是[FItzgibbon95].開發者應該注意當資料點很靠近包含的矩陣元素的邊界的時候,返回的橢圓/旋轉矩形資料包含負指數。例子程式碼在fitellipse.cpp中。
5、計算輪廓的矩
使用OpenCV函式 moments 計算影象所有的矩(最高到3階)
使用OpenCV函式 contourArea