在檢測(cè)物體的輪廓時(shí),我們通常會(huì)使用到opencv中的findcontour和drawcontour,比較常用而且效果不錯(cuò)。那么findcontour是基于什么原理來(lái)實(shí)現(xiàn)輪廓的提取呢?
輪廓的提取與描述
在目標(biāo)識(shí)別中我們首先要把感興趣的目標(biāo)提取出來(lái),而一般常見(jiàn)的步驟都是通過(guò)顏色或紋理提取出目標(biāo)的前景圖(一幅黑白圖像,目標(biāo)以白色顯示在圖像中),接下來(lái)我們要對(duì)前景圖進(jìn)行分析進(jìn)一步地把目標(biāo)提取出來(lái),而這里常常用到的就是提取目標(biāo)的輪廓。
OpenCV里提取目標(biāo)輪廓的函數(shù)是findContours,它的輸入圖像是一幅二值圖像,輸出的是每一個(gè)連通區(qū)域的輪廓點(diǎn)的集合:vector《vector《Point》》。外層vector的size代表了圖像中輪廓的個(gè)數(shù),里面vector的size代表了輪廓上點(diǎn)的個(gè)數(shù)。下面我們通過(guò)實(shí)例來(lái)看函數(shù)的用法。
view plain copy print?
int main()
{
using namespace cv;
Mat image=imread(“。。/shape.png”);
cvtColor(image,image,CV_BGR2GRAY);
vector《vector《Point》》 contours;
// find
findContours(image,contours,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);
// draw
Mat result(image.size(),CV_8U,Scalar(0));
drawContours(result,contours,-1,Scalar(255),2);
namedWindow(“contours”);
imshow(“contours”,result);
waitKey();
return 0;
}
int main()
{
using namespace cv;
Mat image=imread(“。。/shape.png”);
cvtColor(image,image,CV_BGR2GRAY);
vector《vector《Point》》 contours;
// find
findContours(image,contours,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);
// draw
Mat result(image.size(),CV_8U,Scalar(0));
drawContours(result,contours,-1,Scalar(255),2);
namedWindow(“contours”);
imshow(“contours”,result);
waitKey();
return 0;
}
上面程序中包含了2個(gè)函數(shù),第一個(gè)是查找輪廓函數(shù),它的第三個(gè)參數(shù)說(shuō)明查找輪廓的類(lèi)型,這里我們使用的是外輪廓,還可以查找所有輪廓,即
包括一些孔洞的部分,像圖像人物胳膊與腰間形成的輪廓。第4個(gè)參數(shù)說(shuō)明了輪廓表示的方法,程序中的參數(shù)說(shuō)明輪廓包括了所有點(diǎn),也可以用其
他參數(shù)讓有點(diǎn)直線(xiàn)的地方,只保存直線(xiàn)起始與終點(diǎn)的位置點(diǎn),具體參數(shù)用法可以參考手冊(cè)里函數(shù)的介紹。
第二個(gè)函數(shù)drawContours是一個(gè)畫(huà)輪廓的函數(shù),它的第3個(gè)參數(shù)程序里設(shè)置-1表示所有的輪廓都畫(huà),你也可以指定要畫(huà)的輪廓的序號(hào)。
提取到輪廓后,其實(shí)我們更關(guān)心的是如果把這些輪廓轉(zhuǎn)換為可以利用的特征,也就是涉及到輪廓的描述問(wèn)題,這時(shí)就有多種方法可以選擇,比如矢
量化為多邊形、矩形、橢圓等。OpenCV里提供了一些這樣的函數(shù)。
[cpp] view plain copy print?
// 輪廓表示為一個(gè)矩形
Rect r = boundingRect(Mat(contours[0]));
rectangle(result, r, Scalar(255), 2);
// 輪廓表示為一個(gè)圓
float radius;
Point2f center;
minEnclosingCircle(Mat(contours[1]), center, radius);
circle(result, Point(center), static_cast《int》(radius), Scalar(255), 2);
// 輪廓表示為一個(gè)多邊形
vector《Point》 poly;
approxPolyDP(Mat(contours[2]), poly, 5, true);
vector《Point》::const_iterator itp = poly.begin();
while (itp != (poly.end() - 1))
{
line(result, *itp, *(itp + 1), Scalar(255), 2);
++itp;
}
line(result, *itp, *(poly.begin()), Scalar(255), 2);
// 輪廓表示為凸多邊形
vector《Point》 hull;
convexHull(Mat(contours[3]), hull);
vector《Point》::const_iterator ith = hull.begin();
while (ith != (hull.end() - 1))
{
line(result, *ith, *(ith + 1), Scalar(255), 2);
++ith;
}
line(result, *ith, *(hull.begin()), Scalar(255), 2);
// 輪廓表示為一個(gè)矩形
Rect r = boundingRect(Mat(contours[0]));
rectangle(result, r, Scalar(255), 2);
// 輪廓表示為一個(gè)圓
float radius;
Point2f center;
minEnclosingCircle(Mat(contours[1]), center, radius);
circle(result, Point(center), static_cast《int》(radius), Scalar(255), 2);
// 輪廓表示為一個(gè)多邊形
vector《Point》 poly;
approxPolyDP(Mat(contours[2]), poly, 5, true);
vector《Point》::const_iterator itp = poly.begin();
while (itp != (poly.end() - 1))
{
line(result, *itp, *(itp + 1), Scalar(255), 2);
++itp;
}
line(result, *itp, *(poly.begin()), Scalar(255), 2);
// 輪廓表示為凸多邊形
vector《Point》 hull;
convexHull(Mat(contours[3]), hull);
vector《Point》::const_iterator ith = hull.begin();
while (ith != (hull.end() - 1))
{
line(result, *ith, *(ith + 1), Scalar(255), 2);
++ith;
}
line(result, *ith, *(hull.begin()), Scalar(255), 2);
程序中我們依次畫(huà)了矩形、圓、多邊形和凸多邊形。最終效果如下:
對(duì)連通區(qū)域的分析到此遠(yuǎn)遠(yuǎn)沒(méi)有結(jié)束,我們可以進(jìn)一步計(jì)算每一個(gè)連通區(qū)域的其他屬性,比如:重心、中心矩等特征,這些內(nèi)容以后有機(jī)會(huì)展開(kāi)來(lái)寫(xiě)。
以下幾個(gè)函數(shù)可以嘗試:minAreaRect:計(jì)算一個(gè)最小面積的外接矩形,contourArea可以計(jì)算輪廓內(nèi)連通區(qū)域的面積;pointPolygenTest可以
用來(lái)判斷一個(gè)點(diǎn)是否在一個(gè)多邊形內(nèi)。mathShapes可以比較兩個(gè)形狀的相似性,相當(dāng)有用的一個(gè)函數(shù)。
一、大概思想
他主要介紹了兩種算法,用來(lái)對(duì)數(shù)字二值圖像進(jìn)行拓?fù)浞治觥5谝环N算法是在確定二值圖像邊界的圍繞關(guān)系,即確定外邊界、孔邊界以及他們的層次關(guān)系,由于這些邊界和原圖的區(qū)域具有一一對(duì)應(yīng)關(guān)系(外邊界對(duì)應(yīng)像素值為1的連通區(qū)域,孔邊界對(duì)應(yīng)像素值為0的區(qū)域),因此我們就可以用邊界來(lái)表示原圖。第二種算法,是第一種算法的修改版本,本質(zhì)一樣,但是它只找最外面的邊界。
也許你會(huì)問(wèn),這個(gè)算法怎么來(lái)確定外邊界,孔邊界以及他們的層級(jí)關(guān)系?他采用編碼的思想,給不同的邊界賦予不同的整數(shù)值,從而我們就可以確定它是什么邊界以及層次關(guān)系。輸入的二值圖像即為0和1的圖像,用f(i,j)表示圖像的像素值。每次行掃描,遇到以下兩種情況終止:
?。?)f(i,j-1)=0,f(i,j)=1;//f(i,j)是外邊界的起始點(diǎn)
?。?)f(i,j)》=1,f(i,j+1)=0;//f(i,j)是孔邊界的起始點(diǎn)
然后從起始點(diǎn)開(kāi)始,標(biāo)記邊界上的像素。在這里分配一個(gè)唯一的標(biāo)示符給新發(fā)現(xiàn)的邊界,叫做NBD。初始時(shí)NBD=1,每次發(fā)現(xiàn)一個(gè)新邊界加1。在這個(gè)過(guò)程中,遇到f(p,q)=1,f(p,q+1)=0時(shí),將f(p,q)置為-NBD。什么意思呢?就是右邊邊界的終止點(diǎn)。假如一個(gè)外邊界里有孔邊界時(shí),怎么推導(dǎo)呢?限于篇幅,你可以看論文的附錄1。
二、實(shí)例運(yùn)用
下面舉個(gè)例子,對(duì)一幅圖像先進(jìn)行邊沿提取,這里使用canny,然后再用findcontour提取輪廓,最后用drawcontour畫(huà)出輪廓。
#include “StdAfx.h”#include “opencv2/highgui/highgui.hpp”#include “opencv2/imgproc/imgproc.hpp”#include 《iostream》#include 《stdio.h》#include 《stdlib.h》
using namespace cv;using namespace std;
Mat src; Mat src_gray;int thresh = 100;int max_thresh = 255;RNG rng(12345);
/// Function headervoid thresh_callback(int, void* );
/** @function main */int main(){ /// Load source image and convert it to gray src = imread(“l(fā)ena.jpg”, 1 );
/// Convert image to gray and blur it cvtColor( src, src_gray, CV_BGR2GRAY ); blur( src_gray, src_gray, Size(3,3) );
/// Create Window char* source_window = “Source”; namedWindow( source_window, 0); imshow( source_window, src );
createTrackbar( “ Canny thresh:”, “Source”, &thresh, max_thresh, thresh_callback ); thresh_callback( 0, 0 );
waitKey(0); return(0);}
/** @function thresh_callback */void thresh_callback(int, void* ){ Mat canny_output; vector《vector《Point》 》 contours; vector《Vec4i》 hierarchy;
/// Detect edges using canny Canny( src_gray, canny_output, thresh, thresh*2, 3 ); /// Find contours findContours( canny_output, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
/// Draw contours 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, CV_FILLED, 8, hierarchy, 0, Point() ); }
/// Show in a window namedWindow( “Contours”, 0 ); imshow( “Contours”, drawing );
來(lái)自CODE的代碼片contour.cpp
結(jié)果圖如下:
三、代碼注釋
findContours( canny_output, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
來(lái)自CODE的代碼片findcontour.cpp
canny_output:使用canny算子提取邊緣的結(jié)果圖,這里的圖像必須是二值圖像;
contours:提取得到的輪廓圖,每個(gè)輪廓作為一個(gè)點(diǎn)向量來(lái)存儲(chǔ);
hierarchy:關(guān)于輸出圖像的拓?fù)?a target="_blank">信息,例如第i個(gè)輪廓,hierarchy[i][0]和hiearchy[i][1]表示該輪廓的前一個(gè)和后一個(gè)輪廓,同一層關(guān)系;hiearchy[i][2]和hiearchy[i][3]表示父輪廓和子輪廓,不同層的關(guān)系。
CV_RETR_EXTERNAL:輪廓檢索模式,這里采用外輪廓的模式;
CV_CHAIN_APPROX_SIMPLE:輪廓的近似方法,這里采用壓縮方式,只用端點(diǎn)來(lái)表示;
Point(0,0):偏移量,這里無(wú)偏移。
drawContours( drawing, contours, i, color,CV_FILLED, 8, hierarchy, 0, Point() );
來(lái)自CODE的代碼片drawContours.cpp
drawing:目標(biāo)圖像;
contours:所有輸入的輪廓;
i:指定那個(gè)輪廓被畫(huà);
color:輪廓的顏色
CV_FILLED:線(xiàn)的寬度,這里采用填充模式;
8:線(xiàn)的連接度;
hierarchy:只有當(dāng)你只需要畫(huà)一些輪廓時(shí),這個(gè)參數(shù)才需要:
0:所畫(huà)輪廓的最大級(jí)別,為0時(shí)表示只畫(huà)指定的輪廓;
Point():偏移量。
評(píng)論
查看更多