OpenCV中可以方便的在一副图像中检测到轮廓,并把这些轮廓画出来。主要用到两个函数:一个是findContours( img, contours0, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);另一个是drawContours( cnt_img, contours, idx, color, 1, 8, hierarchy );
int main( int argc, char**)
{
Mat img = Mat::zeros(w, w, CV_8UC1);
//画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 )
{
//给第一个卡通画上11根头发
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 );
}
//显示所画的图像
namedWindow( "image", 1 );
imshow( "image", img );
//抽取轮廓
vector<vector<Point> > contours0;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours( img, contours0, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
contours.resize(contours0.size());
//多边形近似,第三个参数是精度,表示近似曲线和原始曲线之间的距离
for( size_t k = 0; k < contours0.size(); k++ )
approxPolyDP(Mat(contours0[k]), contours[k], 3, true);
namedWindow( "contours", 1 );
Mat cnt_img = Mat::zeros(w, w, CV_8UC3);
int idx = 0;
for( ; idx >= 0; idx = hierarchy[idx][0] )
{
Scalar color( rand()&255, rand()&255, rand()&255 );
drawContours( cnt_img, contours, idx, color, 1, 8, hierarchy );
}
imshow("contours", cnt_img);
namedWindow( "contours1", 1 );
Mat cnt_img1 = Mat::zeros(w, w, CV_8UC3);
idx = 0;
//for( ; idx >= 0; idx = hierarchy[idx][0] )
// {
// Scalar color( rand()&255, rand()&255, rand()&255 );
// drawContours( cnt_img1, contours, idx, color,1, 1, hierarchy, 2 );
// }
Scalar color( rand()&255, rand()&255, rand()&255 );
drawContours( cnt_img1, contours, -1, color,1, 1 );
imshow("contours1", cnt_img1);
while(1)
waitKey();
return 0;
}
上面的代码会首先画6个卡通头像,然后检测卡通头像中的轮廓并画出来。
其中抽取轮廓的代码为:
//抽取轮廓
vector<vector<Point> > contours0;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours( img, contours0, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
findContours函数原型为:
void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, intmethod, Point offset=Point())
CV_RETR_EXTERNAL 仅检测外部轮廓,它会设置hierarchy[i][2]=hierarchy[i][3]=-1 。
CV_RETR_LIST 没有任何层级的检测所有轮廓,这样轮廓之间就失去了拓扑语义,全成了平级关系。
CV_CHAIN_APPROX_NONE 连续存储所有的轮廓点,任何两个相邻的点都是水平、垂直或者斜相邻的。也就是说 max(abs(x1-x2),abs(y2-y1))==1.
函数findContours检测轮廓的算法来自于paper:Suzuki, S. and Abe, K., Topological Structural Analysis of Digitized Binary Images by Border Following. CVGIP 30 1, pp 32-46 (1985)
在代码中,我们接着用了一个近似多边形函数,代码如下:
contours.resize(contours0.size());
//多边形近似,第三个参数是精度,表示近似曲线和原始曲线之间的距离
for( size_t k = 0; k < contours0.size(); k++ )
approxPolyDP(Mat(contours0[k]), contours[k], 3, true);
void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed)
curve – 轮廓点集表示的曲线。通常用vector表示。
approxCurve – 输出的近似多边形曲线。
多边形近似用的算法是:http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
如果增大精度,则轮廓曲线越不光滑,比如我们设置epsilon为6,则得到下面的轮廓图:
最后我们要用drawContours把轮廓画出来:
void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar& color, intthickness=1, int lineType=LINE_8, InputArray hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point() )
Parameters:
比如下面的代码:
for( ; idx >= 0; idx = hierarchy[idx][0] )
{
Scalar color( rand()&255, rand()&255, rand()&255 );
drawContours( cnt_img1, contours, idx, color, -1, 1, hierarchy, 2 );
}
则画的轮廓为:
for( ; idx >= 0; idx = hierarchy[idx][0] )
{
Scalar color( rand()&255, rand()&255, rand()&255 );
drawContours( cnt_img1, contours, idx, color, 1, 1, hierarchy, 0 );
}
输出轮廓为:
for( ; idx >= 0; idx = hierarchy[idx][0] )
{
Scalar color( rand()&255, rand()&255, rand()&255 );
drawContours( cnt_img1, contours, idx, color, 1, 1, hierarchy, 1 );
}
输出轮廓为:
for( ; idx >= 0; idx = hierarchy[idx][0] )
{
Scalar color( rand()&255, rand()&255, rand()&255 );
drawContours( cnt_img1, contours, idx, color, 1, 1, hierarchy, 2 );
}
输出轮廓为:
Scalar color( rand()&255, rand()&255, rand()&255 );
drawContours( cnt_img1, contours, -1, color,1, 1 );
输出轮廓为下图,第三个参数为-1,不用循环,就可以画出所有轮廓。
程序代码:FirstOpenCV23