OpenCV3入門(十)影象輪廓
1、影象輪廓
1.1影象輪廓與API函式
輪廓是一系列相連的點組成的曲線,代表了物體的基本外形,相對於邊緣,輪廓是連續的,邊緣並不全部連續。一般地,獲取影象輪廓要經過下面幾個步驟:
1) 讀取圖片。
2) 將彩色影象轉換成灰度影象。
3) 將灰度影象轉換成二值圖形並查詢其二值影象邊緣即可(如canny邊緣檢測)。
4) 顯示輪廓邊緣。
findContours尋找輪廓函式,原型為:
CV_EXPORTS_W void findContours( InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset = Point()); /** @overload */ CV_EXPORTS void findContours( InputOutputArray image, OutputArrayOfArrays contours, int mode, int method, Point offset = Point());
1)image:影象,單通道灰度圖。
2)contours:檢測到的輪廓,包含物件邊界點(x,y)的座標,每個輪廓儲存為一個點向量可用point型別的vector儲存。
3)hierarchy:輪廓的拓撲資訊,每個輪廓contours[i]包含4個hierarchy[i]元素,hierarchy[i][0]-- hierarchy[i][3],分別代表後一個輪廓、前一個輪廓、父輪廓、內嵌輪廓的編號。
4)mode:輪廓檢索模式。
RETR_EXTERNAL :只檢索最外面的輪廓;
RETR_LIST:檢索所有的輪廓,並將其儲存到一條連結串列當中;
RETR_CCOMP:檢索所有的輪廓,並將他們組織為兩層:頂層是各部分的外部邊界,次層是空洞的內層邊界;
RETR_TREE:檢索所有的輪廓,並重構巢狀輪廓的整個層次;
返回值的含義
5)method:輪廓逼近方法。
CHAIN_APPROX_NONE:輸出輪廓的每個畫素。
CHAIN_APPROX_SIMPLE:壓縮水平的、垂直的和斜的部分,函式只保留他們的終點座標。
drawContours繪製輪廓函式,原型為:
CV_EXPORTS_W 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() );
image – 輸入影象,單通道灰度圖。
contours - 所有的輸入輪廓,每個輪廓為點向量(a point vector)/點向量形式,與findcontours中的contours 形式一致。
contourIdx - 指定輪廓列表的索引 ID(將被繪製),若為負數,則所有的輪廓將會被繪製。
color - 繪製輪廓的顏色。
thickness - 繪製輪廓線條的寬度,若為負值或CV.FILLED則將填充輪廓內部區域。
lineType – 線條的型別,8連通型或4連通型。
hierarchy - 層次結構資訊,與函式findcontours()的hierarchy有關
maxLevel - 繪製輪廓的最高級別。若為0,則繪製指定輪廓;若為1,則繪製該輪廓和所有巢狀輪廓(nested contours);若為2,則繪製該輪廓、巢狀輪廓(nested contours)/子輪廓和巢狀-巢狀輪廓(all the nested-to-nested contours)/孫輪廓,等等。該引數只有在層級結構時才用到。
offset - 按照偏移量移動所有的輪廓(點座標)。
1.2影象輪廓例項
測試程式碼如下。
int main() { RNG rng(12345); Mat src_gray; Mat src = imread("D:\\WORK\\5.OpenCV\\LeanOpenCV\\pic_src\\pic9.bmp"); imshow("原圖", src); /// 轉成灰度並模糊化降噪 cvtColor(src, src_gray, CV_BGR2GRAY); blur(src_gray, src_gray, Size(3, 3)); int thresh = 100; int max_thresh = 255; Mat canny_output; vector<vector<Point> > contours; vector<Vec4i> hierarchy; /// 用Canny運算元檢測邊緣 Canny(src_gray, 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++) { Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)); drawContours(drawing, contours, i, color, 1, 8, hierarchy, 0, Point()); } /// 在窗體中顯示結果 namedWindow("Contours", CV_WINDOW_AUTOSIZE); imshow("Contours", drawing); waitKey(0); }
輸出結果為:
測試2,結果如下圖。
2、凸包
2.1凸包與API函式
在一個實數向量空間V中,對於給定集合X,所有包含X的凸集的交集S被稱為X的凸包。X的凸包可以用X內所有點(X1,…Xn)的凸組合來構造。簡單來講,對於一個二維空間的點集,這個點集當中的一些點總可以形成一個凸多邊形,而這個凸多邊形之內恰好可以包括除了組成凸包這個凸多邊以外的所有點,而這個凸多邊形就是凸包。凸包可以想象成一條剛好包住所有點的橡皮圈,對於給定二維平面上的點集,凸包常常就是將最外層的點連線起來構成的凸多邊形,它能包含點選中所有的點。
物體的凸包檢測常應用在物體識別、手勢識別及邊界檢測等領域。理解物體形狀或輪廓的一種方法是計算物體的凸包,然後計算其凸缺陷。下圖人手影象畫圖了凸包線輪廓,然後標出了凸缺陷A—H。黑色的輪廓線為convexity hull, 而convexity hull與手掌之間的部分為convexity defects. 每個convexity defect區域有四個特徵量:起始點(startPoint),結束點(endPoint),距離convexity hull最遠點(farPoint),最遠點到convexity hull的距離(depth)。
OpenCV使用convexHull函式做物體輪廓凸包檢測:
CV_EXPORTS_W void convexHull( InputArray points, OutputArray hull, bool clockwise = false, bool returnPoints = true );
point:輸入的二維點集,可儲存在向量或矩陣Mat中,代表輪廓點
hull:輸出凸包,這是一個整數索引的載體或點的向量,可以是vector<vector<Point>>和vector<vector<int>>
clockwise:方向標誌位,true為順時針,false為逆時針方向。
return Point :操作標準位,預設true,表示返回凸包的各點,否則返回凸包各點的指數。
OpenCV使用convexityDefects函式做輪廓凸包缺陷檢測:
CV_EXPORTS_W void convexityDefects( InputArray contour, InputArray convexhull, OutputArray convexityDefects );
coutour: 輸入引數,檢測到的輪廓,可以呼叫findContours函式得到;
convexhull: 輸入引數,檢測到的凸包,可以呼叫convexHull函式得到。注意,convexHull函式可以得到vector<vector<Point>>和vector<vector<int>>兩種型別結果,這裡的convexhull應該為vector<vector<int>>型別,否則通不過ASSERT檢查;
convexityDefects:輸出引數,檢測到的最終結果,應為vector<vector<Vec4i>>型別,Vec4i儲存了起始點(startPoint),結束點(endPoint),距離convexity hull最遠點(farPoint)以及最遠點到convexity hull的距離(depth)。
2.2影象凸包檢測例項
影象輪廓與凸包例項測試程式碼如下。
int main() { RNG rng(12345); Mat src_gray; Mat src = imread("D:\\WORK\\5.OpenCV\\LeanOpenCV\\pic_src\\pic9.bmp"); imshow("原圖", src); /// 轉成灰度並模糊化降噪 cvtColor(src, src_gray, CV_BGR2GRAY); //blur(src_gray, src_gray, Size(5, 5)); imshow("src_gray", src_gray); int thresh = 100; int max_thresh = 255; Mat canny_output; vector<vector<Point> > contours; vector<Vec4i> hierarchy; /// 用Canny運算元檢測邊緣 Canny(src_gray, canny_output, thresh, thresh * 2, 3); imshow("canny_output", canny_output); /// 尋找輪廓 findContours(canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0)); //通過發現輪廓得到的候選點來繪製凸包 vector<vector<Point>> convexs(contours.size()); for (size_t i = 0; i < contours.size(); i++) { convexHull(contours[i], convexs[i], false, true); } /// 繪出輪廓 Mat drawing = Mat::zeros(canny_output.size(), CV_8UC3); Mat drawing_convex = Mat::zeros(canny_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, i, color, 1, 8, hierarchy, 0, Point()); //白色線畫出凸包 drawContours(drawing_convex, convexs, i, Scalar(255, 255, 255), 1, LINE_8, noArray(), 0, Point()); } imshow("Contours", drawing); imshow("drawing_convex", drawing_convex); waitKey(0); }
輸出結果為:
測試2,輸出結果為:
3、使用多邊形將輪廓包圍
3.1多邊形包圍輪廓和API
當我們得到物件輪廓後,可用boundingRect()得到包覆此輪廓的最小正矩形,minAreaRect()得到包覆輪廓的最小斜矩形,minEnclosingCircle()得到包覆此輪廓的最小圓形。
CV_EXPORTS_W void approxPolyDP( InputArray curve, OutputArray approxCurve, double epsilon, bool closed ); CV_EXPORTS_W Rect boundingRect( InputArray points ); CV_EXPORTS_W void minEnclosingCircle( InputArray points, CV_OUT Point2f& center, CV_OUT float& radius );
3.2多邊形包圍輪廓測試例項
測試程式碼如下:
int main() { RNG rng(12345); Mat src_gray; Mat src = imread("D:\\WORK\\5.OpenCV\\LeanOpenCV\\pic_src\\img3.bmp"); imshow("原圖", src); /// 轉成灰度並模糊化降噪 cvtColor(src, src_gray, CV_BGR2GRAY); //blur(src_gray, src_gray, Size(5, 5)); imshow("src_gray", src_gray); int thresh = 130; int max_thresh = 255; Mat canny_output; vector<vector<Point> > contours; vector<Vec4i> hierarchy; /// 用Canny運算元檢測邊緣 Canny(src_gray, canny_output, thresh, thresh * 2, 3); imshow("canny_output", canny_output); /// 尋找輪廓 findContours(canny_output, contours, hierarchy, RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(0, 0)); vector<vector<Point> > contours_poly(contours.size()); vector<Rect> boundRect(contours.size()); vector<Point2f>center(contours.size()); vector<float>radius(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(canny_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, i, color, 1, 8, hierarchy, 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); } imshow("Contours", drawing); waitKey(0); }
輸出結果如下:
如果需要填充輪廓空心空白,其中drawContours的引數thicknes代表繪製輪廓線條的寬度,若為負值或CV.FILLED,表示填充輪廓內部區域。所以將此引數置-1就出現下面結果。
4、參考文獻
1、《OpenCV3 程式設計入門》,電子工業出版社,毛星雨著
2、《學習OpenCV》,清華大學出版社,Gary Bradski, Adrian kaehler著
3、用opencv檢測convexity defects
https://www.it610.com/article/4474290.htm
4、計算物體的凸包
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/shapedescriptors/hull/hull.html
5、提取輪廓的原理和程式碼例項
https://blog.csdn.net/qq_29796317/article/details/78297920
6、在影象中尋找輪廓
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/shapedescriptors/find_contours/find_contours.html
7、【計算幾何/凸包】安德魯演算法(Andrew's Algorithm)詳解
https://blog.csdn.net/peng0614/article/details/81193484
技術部落格,轉載請註明。
https://www.cnblogs.com/pingwen/p/12374877.html