OpenCV学习笔记(十七):查找并绘制轮廓:findContours()
1、findContours() 函数
该函数使用Suzuki85算法从二值图像中检索轮廓。轮廓线是一种用于形状分析、目标检测和识别的有效工具。
opencv轮廓检测之FindContours函数算法解释
该函数从二值图像中检索轮廓,并返回检索到的轮廓数。函数将填充指针first_contour。它将包含指向第一个最外层轮廓的指针,如果没有检测到轮廓,则为空(如果图像是完全黑色的)。可以使用h_next和v_next链接从first_contour到达其他轮廓。绘制等值线讨论中的示例说明了如何使用等值线进行连接组件检测。轮廓还可以用于形状分析和对象识别-请参阅OpenCV示例目录中的squares.c。
void findContours(
InputOutputArray image, // 要绘制轮廓的图像,8位单通道的图像(256级灰度图)
OutputArrayOfArrays contours, // 所有输入的轮廓,找到的轮廓,其中每个轮廓会被存储为vector<Point>,// 所以contours的类型就是vector<vector<Point>>。
OutputArray hierarchy, // 层次结构,可选的输出向量,包含关于图像的拓扑结构信息。// 其具有跟轮廓数相同的元素个数,类型为vector<Vec4i>// 后一个轮廓、前一个轮廓、第一个子轮廓、父轮廓的索引编号,如果没有对应项,该值设置为-1
int mode, // 检索轮廓的模式分别表示
int method, // 为轮廓的近似办法
Point offset=Point() // 代表轮廓点的偏移量,可以设置为任意值。
);
==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 近似算法
2、drawContours()函数
该函数使用算法从二值图像中检索轮廓。绘制轮廓线或填充轮廓线。如果厚度≥0,该函数在图像中绘制轮廓轮廓;如果厚度<0,则填充轮廓边界区域。
参考博客:
轮廓的层级关系详解
OpenCV中findcontours函数hierarchy轮廓层级详解
void drawContours(
InputOutputArray image, // 要绘制轮廓的图像
InputArrayOfArrays contours, // 所有输入的轮廓,每个轮廓被保存成一个point向量(vector<vector<Point>>)
int contourIdx, // 指定要绘制轮廓的编号,如果是负数,则绘制所有的轮廓
const Scalar& color, // 绘制轮廓所用的颜色
int thickness=1, // 绘制轮廓的线的粗细,如果是负数,则轮廓内部被填充(CV_FILLED 填充内部)
int lineType=8, // 绘制轮廓的线的连通性(LINE_AA 抗锯齿线形)
InputArray hierarchy=noArray(), // 关于层级的可选参数,只有绘制部分轮廓时才会用到(hierarchy=hierarchy,绘制所有轮廓)
int maxLevel=INT_MAX, // 绘制轮廓的最高级别,这个参数只有hierarchy有效的时候才有效
Point offset=Point() // 代表轮廓点的偏移量,可以设置为任意值
)
==maxLevel==
maxLevel=0,绘制与输入轮廓属于同一等级的所有轮廓即输入轮廓和与其相邻的轮廓
maxLevel=1, 绘制与输入轮廓同一等级的所有轮廓与其子节点。
maxLevel=2,绘制与输入轮廓同一等级的所有轮廓与其子节点以及子节点的子节点
3、approxPolyDP()函数
以指定的精度近似生成多边形曲线。
函数逼近一条曲线或另一条曲线/顶点较少的多边形,使它们之间的距离小于或等于指定的精度。它使用Douglas-Peucker算法
void approxPolyDP(
InputArray curve, // 输入的点集(存储在std::vector或Mat中的二维点的输入向量)
OutputArray approxCurve, // 输出的点集,当前点集是能最小包容指定点集的。draw出来即是一个多边形;
double epsilon, // 指定的精度,也即是原始曲线与近似曲线之间的最大距离。
bool closed // 若为true,则说明近似曲线是闭合的,它的首位都是相连,反之,若为false,则断开。
);
4、示例一:
#include <opencv2/opencv.hpp>using namespace cv;
using namespace std;int main()
{QCoreApplication a(argc, argv);// 1、加载源图像//Mat srcImage = imread( "F:/C++/2. OPENCV 3.1.0/TEST/a.jpg", 1 );Mat srcImage = imread( "F:/C++/2. OPENCV 3.1.0/TEST/test3.png", 1 );if(!srcImage.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~! \n"); return false; }cvtColor(srcImage,srcImage,CV_BGR2GRAY);// 2、初始化结果图Mat dstImage = Mat::zeros(srcImage.size(), CV_8UC3);Mat dstImage1 =dstImage.clone();// 3、srcImage取大于阈值119的那部分//threshold(srcImage,srcImage,119,255,THRESH_BINARY);srcImage = srcImage > 119; // 取 阈值大于119imshow( "取阈值后的原始图", srcImage );// 4、定义轮廓和层次结构// 储存所有轮廓(每个轮廓为一个点向量集)// 储存每个轮廓的层次元素,hierarchy[i][0]~hierarchy[i][1] 分别表示后一个、前一个、父轮廓、内嵌轮廓的索引编号vector<vector<Point> > contours;vector<Vec4i> hierarchy;// 5、查找轮廓// RETR_EXTERNAL 表示只检测外轮廓// RETR_LIST 检测的轮廓不建立等级关系// RETR_CCOMP 建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边 界信息。如果内孔内还有一个连通物体,这个物体的边界也在顶层。// RETR_TREE 建立一个等级树结构的轮廓。具体参考contours.c这个demo// 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 近似算法findContours( srcImage, contours, hierarchy,CV_RETR_CCOMP ,CV_CHAIN_APPROX_SIMPLE );cout<<"轮廓数量:"<<contours.size()<<endl;// 6、遍历所有顶层的轮廓, 以随机颜色绘制出每个连接组件颜色// hierarchy[index][0] 表示该某轮廓点集的后一个轮廓点集 索引号// hierarchy[index][1] 表示该某轮廓点集的前一个轮廓点集 索引号// hierarchy[index][2] 表示该某轮廓点集的第一个子轮廓(内嵌)点集 索引号// hierarchy[index][3] 表示该某轮廓点集的父轮廓点集 索引号for( int index = 0; index >= 0; index = hierarchy[index][0] ) // 绘制轮廓//for( int index = 0; index < contours.size(); index ++ ) // 绘制所有内外轮廓{Scalar color( rand()&255, rand()&255, rand()&255 );drawContours( dstImage, contours, index, color, 1, 8,hierarchy ); //CV_FILLED}// // 7、检测到的所有轮廓分别用不同颜色画出来
// for(size_t j=0; j<contours.size(); j++)
// {
// Scalar color( rand()&255, rand()&255, rand()&255 ); // 为每个轮廓新建随机颜色
// for (size_t i = 0; i < contours[j].size(); i++)
// {
// // 绘制边缘
// line(dstImage1, contours[j][i], contours[j][(i + 1) % contours[j].size()], color, 1, 8);
// }
// }// 8、显示最后的轮廓图imshow( "轮廓图", dstImage );//imshow( "边缘图", dstImage1 );waitKey(0);return 0;
}
结果:
分析:
1)“0轮廓"的下一条轮廓是"1轮廓”;“1轮廓"的下一条"3轮廓”;
2)"2轮廓"和"3轮廓"的下一条没有;
3)"0轮廓"和"2轮廓"上一条是-1表示没有;
4)“1轮廓"的子轮廓是"2轮廓”,其他的轮廓没有子轮廓。(1轮廓包含2轮廓)
5)"0、1、3轮廓"没有父轮廓,说明其为最外层轮廓(且为同级轮廓)
PS:轮廓层次关系 看hierarchy[index][2]和hierarchy[index][3]的值
5、示例二:
#include <opencv2/opencv.hpp>using namespace cv;
using namespace std;int levels = 3; // 精度等级
Mat img;
RNG g_rng(12345);
vector<vector<Point> > contours,contours0;
vector<Vec4i> hierarchy;int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 1、 读取图片img = imread( "F:/C++/2. OPENCV 3.1.0/TEST/face.png", 0 );if(!img.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~! \n"); return false; }imshow( "image", img );// // 绘制6个人脸用于测试
// // 创建 500*500 单通道矩阵 灰度图
// Mat img = Mat::zeros(w, w, CV_8UC1);
// 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 );
// }// 显示
// namedWindow( "image", 1 );
// imshow( "image", img );// 2、提取轮廓findContours( img, contours0, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);// 3、对轮廓进行逼近 获得多边形曲线轮廓向量点集contours.resize(contours0.size());for( size_t k = 0; k < contours0.size(); k++ )approxPolyDP(Mat(contours0[k]), contours[k], 0.1, true);// 4、滑动条调用// 设置轨迹条,控制从-3到3的轮廓等级namedWindow( "contours", 1 );createTrackbar( "levels+3", "contours", &levels, 7, on_trackbar );on_trackbar(0,0);// 初次调用waitKey(0);return a.exec();
}
轮廓控制回调函数
static void on_trackbar(int, void*)
{// 1、创建目标图像Mat cnt_img = Mat::zeros(img.rows, img.cols, CV_8UC3);// 2、处理参数int _levels = levels - 3;// 3、绘制轮廓// _levels <= 0 ? 3 : -1Scalar color = Scalar( g_rng.uniform(0, 255), g_rng.uniform(0,255), g_rng.uniform(0,255) );//任意值drawContours( cnt_img, contours,_levels <= 0 ? 3 : -1, Scalar(128,255,255),1, LINE_AA, hierarchy, std::abs(_levels) ); imshow("contours", cnt_img);
}
结果: