1. 程式人生 > >輪廓提取findContours和繪製drawContours

輪廓提取findContours和繪製drawContours

輪廓提取findContours和繪製drawContours
首先感謝以下兩位的博文幫助我的理解:

(1)-牧野- http://blog.csdn.net/dcrmg/article/details/51987348

(2)塬萊 http://blog.csdn.net/maomao1011120756/article/details/49794997

void findContours//提取輪廓,用於提取影象的輪廓
(
InputOutputArray image,//輸入影象,必須是8位單通道影象,並且應該轉化成二值的
OutputArrayOfArrays contours,//檢測到的輪廓,每個輪廓被表示成一個point向量
OutputArray hierarchy,//可選的輸出向量,包含影象的拓撲資訊。其中元素的個數和檢測到的輪廓的數量相等
int mode,//說明需要的輪廓型別和希望的返回值方式
int method,//輪廓近似方法
Point offset = Point()
)

void drawContours//繪製輪廓,用於繪製找到的影象輪廓
(
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()
)

Opencv中通過使用findContours函式,簡單幾個的步驟就可以檢測出物體的輪廓,很方便。這些準備繼續探討一下findContours方法中各引數的含義及用法,比如要求只檢測最外層輪廓該怎麼辦?contours裡邊的資料結構是怎樣的?hierarchy到底是什麼鬼?Point()有什麼用?

先從findContours函式原型看起:

[cpp] view plain copy

findContours( InputOutputArray image, OutputArrayOfArrays contours,
OutputArray hierarchy, int mode,
int method, Point offset=Point());

第一個引數:image,單通道影象矩陣,可以是灰度圖,但更常用的是二值影象,一般是經過Canny、拉普拉斯等邊緣檢測運算元處理過的二值影象;

第二個引數:contours,定義為“vector<vector> contours”,是一個向量,並且是一個雙重向量,向量內每個元素儲存了一組由連續的Point點構成的點的集合的向量,每一組Point點集就是一個輪廓。有多少輪廓,向量contours就有多少元素。

第三個引數:hierarchy,定義為“vector hierarchy”,先來看一下Vec4i的定義:
typedef Vec<int, 4> Vec4i;

Vec4i是Vec<int,4>的別名,定義了一個“向量內每一個元素包含了4個int型變數”的向量。

所以從定義上看,hierarchy也是一個向量,向量內每個元素儲存了一個包含4個int整型的陣列。

向量hiararchy內的元素和輪廓向量contours內的元素是一一對應的,向量的容量相同。

hierarchy向量內每一個元素的4個int型變數——hierarchy[i][0] ~hierarchy[i][3],分別表示第i個輪廓的後一個輪廓、前一個輪廓、父輪廓、內嵌輪廓的索引編號。如果當前輪廓沒有對應的後一個輪廓、前一個輪廓、父輪廓或內嵌輪廓的話,則hierarchy[i][0] ~hierarchy[i][3]的相應位被設定為預設值-1。

第四個引數:int型的mode,定義輪廓的檢索模式:

       取值一:CV_RETR_EXTERNAL只檢測最外圍輪廓,包含在外圍輪廓內的內圍輪廓被忽略



       取值二:CV_RETR_LIST   檢測所有的輪廓,包括內圍、外圍輪廓,但是檢測到的輪廓不建立等級關係,彼此之間獨立,沒有等級關係,這就意味著這個檢索模式下不存在父輪廓或內嵌輪廓,所以hierarchy向量內所有元素的第3、第4個分量都會被置為-1,具體下文會講到



       取值三:CV_RETR_CCOMP  檢測所有的輪廓,但所有輪廓只建立兩個等級關係,外圍為頂層,若外圍內的內圍輪廓還包含了其他的輪廓資訊,則內圍內的所有輪廓均歸屬於頂層。兩個等級關係:頂層為連通域的外圍邊界,次層為孔的內層邊界(比如後文的h00、h01、h0000、h0100)。



       取值四:CV_RETR_TREE, 檢測所有輪廓,所有輪廓建立一個等級樹結構。外層輪廓包含內層輪廓,內層輪廓還可以繼續包含內嵌輪廓。






             

兩種輪廓:外部輪廓(橙色虛線)或者孔(藍色點線)。

mode的值決定把找到的輪廓如何掛到輪廓樹節點變數(h_prev, h_next, v_prev, v_next)上。圖2展示了四種可能的mode值所得到的結果的拓撲結構。

圖2 輪廓連線方法

第五個引數:int型的method,定義輪廓的近似方法:

       取值一:CV_CHAIN_APPROX_NONE 儲存物體邊界上所有連續的輪廓點到contours向量內



       取值二:CV_CHAIN_APPROX_SIMPLE 僅儲存輪廓的拐點資訊,把所有輪廓拐點處的點儲存入contours向量內,拐點與拐點之間直線段上的資訊點不予保留



       取值三和四:CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似演算法

第六個引數:Point偏移量,所有的輪廓資訊相對於原始影象對應點的偏移量,相當於在每一個檢測出的輪廓點上加上該偏移量,並且Point還可以是負值!

下邊用效果圖對比一下findContours函式中各引數取不同值時,向量contours和hierarchy的內容如何變化,有何異同。

主體程式如下:
[cpp] view plain copy

#include "core/core.hpp"    
#include "highgui/highgui.hpp"    
#include "imgproc/imgproc.hpp"    
#include "iostream"  
  
using namespace std;   
using namespace cv;    
  
int main(int argc,char *argv[])    
{  
    Mat imageSource=imread(argv[1],0);  
    imshow("Source Image",imageSource);  
    Mat image;  
    GaussianBlur(imageSource,image,Size(3,3),0);  
    Canny(image,image,100,250);  
    vector<vector<Point>> contours;  
    vector<Vec4i> hierarchy;  
    findContours(image,contours,hierarchy,RETR_TREE,CHAIN_APPROX_SIMPLE,Point());  
    Mat imageContours=Mat::zeros(image.size(),CV_8UC1);  
    Mat Contours=Mat::zeros(image.size(),CV_8UC1);  //繪製  
    for(int i=0;i<contours.size();i++)  
    {  
        //contours[i]代表的是第i個輪廓,contours[i].size()代表的是第i個輪廓上所有的畫素點數  
        for(int j=0;j<contours[i].size();j++)   
        {  
            //繪製出contours向量內所有的畫素點  
            Point P=Point(contours[i][j].x,contours[i][j].y);  
            Contours.at<uchar>(P)=255;  
        }  
  
        //輸出hierarchy向量內容  
        char ch[256];  
        sprintf(ch,"%d",i);  
        string str=ch;  
        cout<<"向量hierarchy的第" <<str<<" 個元素內容為:"<<endl<<hierarchy[i]<<endl<<endl;  
  
        //繪製輪廓  
        drawContours(imageContours,contours,i,Scalar(255),1,8,hierarchy);  
    }  
    imshow("Contours Image",imageContours); //輪廓  
    imshow("Point of Contours",Contours);   //向量contours內儲存的所有輪廓點集  
    waitKey(0);  
    return 0;  
}  

程式中所用原始影象如下:

通過調整第四個引數mode——輪廓的檢索模式、第五個引數method——輪廓的近似方式和不同的偏移量Point(),就可以得到以下效果。

一、mode取值“CV_RETR_EXTERNAL”,method取值“CV_CHAIN_APPROX_NONE”,即只檢測最外層輪廓,並且儲存輪廓上所有點:

輪廓:

只有最外層的輪廓被檢測到,內層的輪廓被忽略

contours向量內所有點集:

儲存了所有輪廓上的所有點,影象表現跟輪廓一致

hierarchy向量:

重溫一下hierarchy向量————向量中每個元素的4個整形分別對應當前輪廓的後一個輪廓、前一個輪廓、父輪廓、內嵌輪廓的索引編號。

本次引數配置下,hierarchy向量內有3個元素,分別對應於3個輪廓。以第2個輪廓(對應向量內第1個元素)為例,內容為[2,0,-1,-1], “2”表示當前輪廓的後一個輪廓的編號為2,“0”表示當前輪廓的前一個輪廓編號為0,其後2個“-1”表示為空,因為只有最外層輪廓這一個等級,所以不存在父輪廓和內嵌輪廓。

二、 mode取值“CV_RETR_LIST”,method取值“CV_CHAIN_APPROX_SIMPLE”,即檢測所有輪廓,但各輪廓之間彼此獨立,不建立等級關係,並且僅儲存輪廓上拐點資訊:

檢測到的輪廓跟上文“一”中是一致的,不再顯示。

contours向量內所有點集:

contours向量中所有的拐點資訊得到了保留,但是拐點與拐點之間直線段的部分省略掉了。

hierarchy向量(擷取一部分):

本次引數配置下,檢測出了較多輪廓。第1、第2個整形值分別指向上一個和下一個輪廓編號,由於本次配置mode取值“RETR_LIST”,各輪廓間各自獨立,不建立等級關係,所以第3、第4個整形引數為空,設為值-1。

三、mode取值“CV_RETR_TREE”,method取值“CV_CHAIN_APPROX_NONE”,即檢測所有輪廓,輪廓間建立外層、內層的等級關係,並且儲存輪廓上所有點。

contours向量內所有點集:

所有內外層輪廓都被檢測到,contours點集組成的圖形跟輪廓表現一致。

hierarchy向量(擷取一部分)

本次引數配置要求檢測所有輪廓,每個輪廓都被劃分等級,最外圍、第一內圍、第二內圍等等,所以除第1個最後一個輪廓外,其他輪廓都具有不為-1的第3、第4個整形引數,分別指向當前輪廓的父輪廓、內嵌輪廓索引編號。

四、Point()偏移量設定

使用三中的引數配置,設定偏移量Point為Point(45,30)。

此時輪廓影象為:

可以看到輪廓影象整體向右下角有一個偏轉,偏轉量就是設定的(45,30)。

這個偏移量的設定不能過大或過小(負方向上的過小),若影象上任一點加上該偏移量後超出影象邊界,程式會記憶體溢位報錯。

findContours函式的各引數就探討到此,其他引數配置的情況大同小異。值得關注一下的是繪製輪廓的函式

drawContours中最後一個引數是一個Point型別的offset,這個offset跟findContours函式中的offset含義一致,設定之後所繪製的輪廓是原始輪廓上所有畫素點加上該偏移量offset後的效果。

當所分析影象是另外一個影象的ROI的時候,這個offset偏移量就可以大顯身手了。通過加減這個偏移量,就可以把ROI影象的檢測結果投影到原始影象對應位置上。