在檢測物體的輪廓時,我們通常會使用到opencv中的findcontour和drawcontour,比較常用而且效果不錯。那么findcontour是基于什么原理來實現輪廓的提取呢?
輪廓的提取與描述
在目標識別中我們首先要把感興趣的目標提取出來,而一般常見的步驟都是通過顏色或紋理提取出目標的前景圖(一幅黑白圖像,目標以白色顯示在圖像中),接下來我們要對前景圖進行分析進一步地把目標提取出來,而這里常常用到的就是提取目標的輪廓。
OpenCV里提取目標輪廓的函數是findContours,它的輸入圖像是一幅二值圖像,輸出的是每一個連通區域的輪廓點的集合:vector《vector《Point》》。外層vector的size代表了圖像中輪廓的個數,里面vector的size代表了輪廓上點的個數。下面我們通過實例來看函數的用法。
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個函數,第一個是查找輪廓函數,它的第三個參數說明查找輪廓的類型,這里我們使用的是外輪廓,還可以查找所有輪廓,即
包括一些孔洞的部分,像圖像人物胳膊與腰間形成的輪廓。第4個參數說明了輪廓表示的方法,程序中的參數說明輪廓包括了所有點,也可以用其
他參數讓有點直線的地方,只保存直線起始與終點的位置點,具體參數用法可以參考手冊里函數的介紹。
第二個函數drawContours是一個畫輪廓的函數,它的第3個參數程序里設置-1表示所有的輪廓都畫,你也可以指定要畫的輪廓的序號。
提取到輪廓后,其實我們更關心的是如果把這些輪廓轉換為可以利用的特征,也就是涉及到輪廓的描述問題,這時就有多種方法可以選擇,比如矢
量化為多邊形、矩形、橢圓等。OpenCV里提供了一些這樣的函數。
[cpp] view plain copy print?
// 輪廓表示為一個矩形
Rect r = boundingRect(Mat(contours[0]));
rectangle(result, r, Scalar(255), 2);
// 輪廓表示為一個圓
float radius;
Point2f center;
minEnclosingCircle(Mat(contours[1]), center, radius);
circle(result, Point(center), static_cast《int》(radius), Scalar(255), 2);
// 輪廓表示為一個多邊形
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);
// 輪廓表示為一個矩形
Rect r = boundingRect(Mat(contours[0]));
rectangle(result, r, Scalar(255), 2);
// 輪廓表示為一個圓
float radius;
Point2f center;
minEnclosingCircle(Mat(contours[1]), center, radius);
circle(result, Point(center), static_cast《int》(radius), Scalar(255), 2);
// 輪廓表示為一個多邊形
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);
程序中我們依次畫了矩形、圓、多邊形和凸多邊形。最終效果如下:
對連通區域的分析到此遠遠沒有結束,我們可以進一步計算每一個連通區域的其他屬性,比如:重心、中心矩等特征,這些內容以后有機會展開來寫。
以下幾個函數可以嘗試:minAreaRect:計算一個最小面積的外接矩形,contourArea可以計算輪廓內連通區域的面積;pointPolygenTest可以
用來判斷一個點是否在一個多邊形內。mathShapes可以比較兩個形狀的相似性,相當有用的一個函數。
一、大概思想
他主要介紹了兩種算法,用來對數字二值圖像進行拓撲分析。第一種算法是在確定二值圖像邊界的圍繞關系,即確定外邊界、孔邊界以及他們的層次關系,由于這些邊界和原圖的區域具有一一對應關系(外邊界對應像素值為1的連通區域,孔邊界對應像素值為0的區域),因此我們就可以用邊界來表示原圖。第二種算法,是第一種算法的修改版本,本質一樣,但是它只找最外面的邊界。
也許你會問,這個算法怎么來確定外邊界,孔邊界以及他們的層級關系?他采用編碼的思想,給不同的邊界賦予不同的整數值,從而我們就可以確定它是什么邊界以及層次關系。輸入的二值圖像即為0和1的圖像,用f(i,j)表示圖像的像素值。每次行掃描,遇到以下兩種情況終止:
(1)f(i,j-1)=0,f(i,j)=1;//f(i,j)是外邊界的起始點
(2)f(i,j)》=1,f(i,j+1)=0;//f(i,j)是孔邊界的起始點
然后從起始點開始,標記邊界上的像素。在這里分配一個唯一的標示符給新發現的邊界,叫做NBD。初始時NBD=1,每次發現一個新邊界加1。在這個過程中,遇到f(p,q)=1,f(p,q+1)=0時,將f(p,q)置為-NBD。什么意思呢?就是右邊邊界的終止點。假如一個外邊界里有孔邊界時,怎么推導呢?限于篇幅,你可以看論文的附錄1。
二、實例運用
下面舉個例子,對一幅圖像先進行邊沿提取,這里使用canny,然后再用findcontour提取輪廓,最后用drawcontour畫出輪廓。
#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(“lena.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 );
來自CODE的代碼片contour.cpp
結果圖如下:
三、代碼注釋
findContours( canny_output, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
來自CODE的代碼片findcontour.cpp
canny_output:使用canny算子提取邊緣的結果圖,這里的圖像必須是二值圖像;
contours:提取得到的輪廓圖,每個輪廓作為一個點向量來存儲;
hierarchy:關于輸出圖像的拓撲信息,例如第i個輪廓,hierarchy[i][0]和hiearchy[i][1]表示該輪廓的前一個和后一個輪廓,同一層關系;hiearchy[i][2]和hiearchy[i][3]表示父輪廓和子輪廓,不同層的關系。
CV_RETR_EXTERNAL:輪廓檢索模式,這里采用外輪廓的模式;
CV_CHAIN_APPROX_SIMPLE:輪廓的近似方法,這里采用壓縮方式,只用端點來表示;
Point(0,0):偏移量,這里無偏移。
drawContours( drawing, contours, i, color,CV_FILLED, 8, hierarchy, 0, Point() );
來自CODE的代碼片drawContours.cpp
drawing:目標圖像;
contours:所有輸入的輪廓;
i:指定那個輪廓被畫;
color:輪廓的顏色
CV_FILLED:線的寬度,這里采用填充模式;
8:線的連接度;
hierarchy:只有當你只需要畫一些輪廓時,這個參數才需要:
0:所畫輪廓的最大級別,為0時表示只畫指定的輪廓;
Point():偏移量。
評論