這個系列的目的是通過對OpenCV示例,進一步了解OpenCV函數的使用,不涉及具體原理。
示例代碼地址:
http://docs.opencv.org/3.0.0/examples.html(安裝openCV時可框選)
目錄
簡介
Example運行截圖
Example分析
Example代碼
簡介
本文記錄了對OpenCV示例contours2.cpp的分析。
這個示例主要演示了如何使用findContours 對圖像進行輪廓檢測。
示例涉及到findContours ,
approxPolyDP,drawContours,createTrackbar,和on_trackbar等四個函數的使用;
1.findContours函數
輪廓檢測
函數原型:
void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point());
參數說明:
image:輸入圖像必須為一個2值單通道圖像
contours:檢測的輪廓數組,每一個輪廓用一個point類型的vector表示
hiararchy:和輪廓個數相同,每個輪廓contours[ i ]對應4個hierarchy元素hierarchy[ i ]
[0 ] ~hierarchy[ i ][ 3 ],分別表示后一個輪廓、前一個輪廓、父輪廓、內
嵌輪廓的索引編號,如果沒有對應項,該值設置為負數。
mode:表示輪廓的檢索模式
RETR_EXTERNAL表示只檢測外輪廓
RETR_LIST檢測的輪廓不建立等級關系
RETR_CCOMP建立兩個等級的輪廓,上面的一層為外邊界,里面的一層為內孔的邊 界信息。如果內孔內還有一個連通物體,這個物體的邊界也在頂層。
RETR_TREE建立一個等級樹結構的輪廓。具體參考contours.c這個demo
method:為輪廓的近似辦法
CHAIN_APPROX_NONE存儲所有的輪廓點,相鄰的兩個點的像素位置差不超過1,即max(abs(x1-x2),abs(y2-y1))==1
CHAIN_APPROX_SIMPLE壓縮水平方向,垂直方向,對角線方向的元素,只保留該方向的終點坐標,例如一個矩形輪廓只需4個點來保存輪廓信息
CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
offset:代表輪廓點的偏移量,可以設置為任意值。對ROI圖像中找出的輪廓,並要在整個圖像中進行分析時,這個參數還是很有用的。
CHAIN_APPROX_NONE存儲所有的輪廓點,相鄰的兩個點的像素位置差不超過1,即max(abs(x1-x2),abs(y2-y1))==1
CHAIN_APPROX_SIMPLE壓縮水平方向,垂直方向,對角線方向的元素,只保留該方向的終點坐標,例如一個矩形輪廓只需4個點來保存輪廓信息
CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
offset:代表輪廓點的偏移量,可以設置為任意值。對ROI圖像中找出的輪廓,並要在整個圖像中進行分析時,這個參數還是很有用的。
PS:findContours后會對輸入的2值圖像改變,所以如果不想改變該2值圖像,需創建新mat來存放。
以上描述摘至參考資料3
函數原型:
OutputArray approxCurve:輸出的點集,當前點集是能最小包容指定點集的。draw出來即是一個多邊形;
double epsilon:指定的精度,也即是原始曲線與近似曲線之間的最大距離。
bool closed:若為true,則說明近似曲線是閉合的,它的首位都是相連,反之,若為false,則斷開。
2.approxPolyDP
點集逼近
void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed);
參數說明:
InputArray curve:輸入的點集OutputArray approxCurve:輸出的點集,當前點集是能最小包容指定點集的。draw出來即是一個多邊形;
double epsilon:指定的精度,也即是原始曲線與近似曲線之間的最大距離。
bool closed:若為true,則說明近似曲線是閉合的,它的首位都是相連,反之,若為false,則斷開。
PS:findContours后的輪廓信息contours可能過於復雜不平滑,可以用approxPolyDP函數對該多邊形曲線做適當近似。
以上描述摘至參考資料4
3.drawContours
繪制輪廓
函數原型:
void
drawContours
(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar& color, int thickness=1, int lineType=8, InputArray hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point() )
參數說明:
InputOutputArray image:要繪制輪廓的圖像
InputArrayOfArrays contours:所有輸入的輪廓,每個輪廓被保存成一個point向量
int contourIdx:指定要繪制輪廓的編號,如果是負數,則繪制所有的輪廓
const Scalar& color:繪制輪廓所用的顏色
int thickness=1:繪制輪廓的線的粗細,如果是負數,則輪廓內部被填充
int lineType=8:繪制輪廓的線的連通性
InputArray hierarchy=noArray():關於層級的可選參數,只有繪制部分輪廓時才會用到
int maxLevel=INT_MAX:繪制輪廓的最高級別,這個參數只有hierarchy有效的時候才有效
maxLevel=0,繪制與輸入輪廓屬於同一等級的所有輪廓即輸入輪廓和與其相鄰的輪廓
maxLevel=1, 繪制與輸入輪廓同一等級的所有輪廓與其子節點。
maxLevel=2,繪制與輸入輪廓同一等級的所有輪廓與其子節點以及子節點的子節點
Point offset=Point():
PS:findContours()運行的時候,這個圖像會被直接塗改,因此如果是將來還有用的圖像,應該復制之后再傳給findContours()。
以上描述摘至參考資料5
4.createTrackbar
窗口中快速創建一個滑動控件,用於手動調節閾值,具有非常直觀的效果。
函數原型:
int createTrackbar(const string& trackbarname, const string& winname,
int* value, int count,
TrackbarCallback onChange = 0,
void* userdata = 0);
參數說明:
trackbarname:滑動空間的名稱;
winname:滑動空間用於依附的圖像窗口的名稱;
value:初始化閾值;
count:滑動控件的刻度范圍;
TrackbarCallback是回調函數。
5
.TrackbarCallback
createTrackbar調用的回調函數,用於響應滑動條的交互消息。
函數原型:
typedef void (CV_CDECL *TrackbarCallback)(int pos, void* userdata);
參數說明:
pos:滑動條當前位置;
userdata:調用滑動條傳遞的數據。
PS:事實上pos和userdata用得非常少,更多的時候直接使用全局變量完成參數傳遞。
Example運行截圖
原圖

效果圖

取消approxPolyDP調用
Example分析
1.申明需要使用的變量
const int w = 500;
int levels = 3;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
注意:
(1)w:圖像的寬度和高度
(2)levels:繪制輪廓的級別和數量
(3)contours:保存輪廓
(4)hierarchy:層級參數
2.主函數
2.1創建一個灰度圖像
Mat img = Mat::zeros(w, w, CV_8UC1);
2.2.繪制6個人臉用於測試

for( int i = 0; i < 6; i++ ) { int dx = (i%2)*250 - 30; int dy = (i/2)*150; const Scalar white = Scalar(255); const Scalar black = Scalar(0); if( i == 0 ) { for( int j = 0; j <= 10; j++ ) { double angle = (j+5)*CV_PI/21; line(img, Point(cvRound(dx+100+j*10-80*cos(angle)), cvRound(dy+100-90*sin(angle))), Point(cvRound(dx+100+j*10-30*cos(angle)), cvRound(dy+100-30*sin(angle))), white, 1, 8, 0); } } ellipse( img, Point(dx+150, dy+100), Size(100,70), 0, 0, 360, white, -1, 8, 0 ); ellipse( img, Point(dx+115, dy+70), Size(30,20), 0, 0, 360, black, -1, 8, 0 ); ellipse( img, Point(dx+185, dy+70), Size(30,20), 0, 0, 360, black, -1, 8, 0 ); ellipse( img, Point(dx+115, dy+70), Size(15,15), 0, 0, 360, white, -1, 8, 0 ); ellipse( img, Point(dx+185, dy+70), Size(15,15), 0, 0, 360, white, -1, 8, 0 ); ellipse( img, Point(dx+115, dy+70), Size(5,5), 0, 0, 360, black, -1, 8, 0 ); ellipse( img, Point(dx+185, dy+70), Size(5,5), 0, 0, 360, black, -1, 8, 0 ); ellipse( img, Point(dx+150, dy+100), Size(10,5), 0, 0, 360, black, -1, 8, 0 ); ellipse( img, Point(dx+150, dy+150), Size(40,10), 0, 0, 360, black, -1, 8, 0 ); ellipse( img, Point(dx+27, dy+100), Size(20,35), 0, 0, 360, white, -1, 8, 0 ); ellipse( img, Point(dx+273, dy+100), Size(20,35), 0, 0, 360, white, -1, 8, 0 ); }
2.3.創建顯示源圖像的窗口
namedWindow( "image", 1 );
2.4.顯示源圖像
imshow( "image", img );
2.5.查找輪廓
vector<vector<Point> > contours0;
findContours( img, contours0, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
2.6.對輪廓進行逼近
contours.resize(contours0.size());
for( size_t k = 0; k < contours0.size(); k++ )
approxPolyDP(Mat(contours0[k]), contours[k], 3, true);
2.7.創建顯示目標圖像(繪制輪廓圖像)的窗口
namedWindow( "contours", 1 );
2.8.為目標圖像預覽窗口創建滑動條
createTrackbar( "levels+3", "contours", &levels, 7, on_trackbar );
注意:
為窗口添加滑動條,在OpenCV研發中非常普遍和實用,其大致步驟如下:
step1: 參數全局變量的聲明與初始化;
step2:聲明存儲圖像的全局變量;
step3:定義響應滑動條的回調函數,根據參數全局變量,和圖像全局變量,以及圖像算法進行處理,並顯示;
step4:創建顯示圖像的窗口;
step5:在函數中使用createTrackbar創建滑動條;
step6:在函數中手動調用回調函數on_trackbar 一次,用於初始化。
2.9.手動調用
on_trackbar(0,0);
2.10等待鍵盤事件
waitKey();
注意:
在OpenCV中常常使用waitKey函數,因為顯示圖像后,需要駐留代碼。以便於觀察和操作。
3.on_trackbar函數分析
3.1創建目標圖像
Mat cnt_img = Mat::zeros(w, w, CV_8UC3);
3.2處理參數
int _levels = levels - 3;
3.3繪制輪廓
drawContours( cnt_img, contours, _levels <= 0 ? 3 : -1, Scalar(128,255,255),
3, LINE_AA, hierarchy, std::abs(_levels) );
3.4顯示目標圖像
imshow("contours", cnt_img);
Example代碼

1 #include "opencv2/imgproc/imgproc.hpp" 2 #include "opencv2/highgui/highgui.hpp" 3 #include <math.h> 4 #include <iostream> 5 6 using namespace cv; 7 using namespace std; 8 9 static void help() 10 { 11 cout 12 << "\nThis program illustrates the use of findContours and drawContours\n" 13 << "The original image is put up along with the image of drawn contours\n" 14 << "Usage:\n" 15 << "./contours2\n" 16 << "\nA trackbar is put up which controls the contour level from -3 to 3\n" 17 << endl; 18 } 19 20 const int w = 500; 21 int levels = 3; 22 23 vector<vector<Point> > contours; 24 vector<Vec4i> hierarchy; 25 26 static void on_trackbar(int, void*) 27 { 28 Mat cnt_img = Mat::zeros(w, w, CV_8UC3); 29 int _levels = levels - 3; 30 drawContours( cnt_img, contours, _levels <= 0 ? 3 : -1, Scalar(128,255,255), 31 3, LINE_AA, hierarchy, std::abs(_levels) ); 32 33 imshow("contours", cnt_img); 34 } 35 36 int main( int argc, char**) 37 { 38 Mat img = Mat::zeros(w, w, CV_8UC1); 39 if(argc > 1) 40 { 41 help(); 42 return -1; 43 } 44 //Draw 6 faces 45 for( int i = 0; i < 6; i++ ) 46 { 47 int dx = (i%2)*250 - 30; 48 int dy = (i/2)*150; 49 const Scalar white = Scalar(255); 50 const Scalar black = Scalar(0); 51 52 if( i == 0 ) 53 { 54 for( int j = 0; j <= 10; j++ ) 55 { 56 double angle = (j+5)*CV_PI/21; 57 line(img, Point(cvRound(dx+100+j*10-80*cos(angle)), 58 cvRound(dy+100-90*sin(angle))), 59 Point(cvRound(dx+100+j*10-30*cos(angle)), 60 cvRound(dy+100-30*sin(angle))), white, 1, 8, 0); 61 } 62 } 63 64 ellipse( img, Point(dx+150, dy+100), Size(100,70), 0, 0, 360, white, -1, 8, 0 ); 65 ellipse( img, Point(dx+115, dy+70), Size(30,20), 0, 0, 360, black, -1, 8, 0 ); 66 ellipse( img, Point(dx+185, dy+70), Size(30,20), 0, 0, 360, black, -1, 8, 0 ); 67 ellipse( img, Point(dx+115, dy+70), Size(15,15), 0, 0, 360, white, -1, 8, 0 ); 68 ellipse( img, Point(dx+185, dy+70), Size(15,15), 0, 0, 360, white, -1, 8, 0 ); 69 ellipse( img, Point(dx+115, dy+70), Size(5,5), 0, 0, 360, black, -1, 8, 0 ); 70 ellipse( img, Point(dx+185, dy+70), Size(5,5), 0, 0, 360, black, -1, 8, 0 ); 71 ellipse( img, Point(dx+150, dy+100), Size(10,5), 0, 0, 360, black, -1, 8, 0 ); 72 ellipse( img, Point(dx+150, dy+150), Size(40,10), 0, 0, 360, black, -1, 8, 0 ); 73 ellipse( img, Point(dx+27, dy+100), Size(20,35), 0, 0, 360, white, -1, 8, 0 ); 74 ellipse( img, Point(dx+273, dy+100), Size(20,35), 0, 0, 360, white, -1, 8, 0 ); 75 } 76 //show the faces 77 namedWindow( "image", 1 ); 78 imshow( "image", img ); 79 //Extract the contours so that 80 vector<vector<Point> > contours0; 81 findContours( img, contours0, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE); 82 83 contours.resize(contours0.size()); 84 for( size_t k = 0; k < contours0.size(); k++ ) 85 approxPolyDP(Mat(contours0[k]), contours[k], 3, true); 86 87 namedWindow( "contours", 1 ); 88 createTrackbar( "levels+3", "contours", &levels, 7, on_trackbar ); 89 90 on_trackbar(0,0); 91 waitKey(); 92 93 return 0; 94 }
參考資料:
1.《opencv 例程四 尋找輪廓》
http://blog.sina.com.cn/s/blog_662c78590100z0rg.html
2.《實用OpenCV》(六) 圖像中的形狀(1)》
http://www.2cto.com/kf/201401/270283.html
3.《findContours函數參數說明及相關函數》
http://blog.sina.com.cn/s/blog_7155fb1a0101a90h.html