轮廓发现

前言:

        当我们通过阈值分割提取到图像中的目标物体后,我们就需要通过边缘检测来提取目标物体的轮廓,使用这两种方法基本能够确定物体的边缘或者前景。接下来,我们通常需要做的是拟合这些边缘的前景,如拟合出包含前景或者边缘像素点的最小外包矩形、圆、凸包等几何形状,为计算它们的面积或者模板匹配等操作打下坚实的基础。

一、查找、绘制轮廓

      首先了解一下轮廓的定义。一个轮廓代表一系列的点(像素),这一系列的点构成一个有序的点集,所以可以把一个轮廓理解为一个有序的点集。

    1.1 findContour()函数

        在OpenCV中,提供了一个函数返回或者输出一个有序的点集或者有序的点集的集合(指多个有序的点集),函数findContour是从二值图像中来计算轮廓的,它可以使用Canny()函数处理的图像,因为这样的图像含有边缘像素;也可以使用threshold()或者adaptiveThreshold()处理后的图像,其边缘隐含在正负区域的交界处。这个函数的声明如下:

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

其参数解释如下:

     (1)image:单通道图像矩阵,可以是灰度图,但更常用的是二值图像,一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像;

    (2)contours:vector<vector<Point>>类型,是一个向量,并且是一个双重向量,向量内每个元素保存了一组由连续的Point点构成的点的集合的向量,每一组Point点集就是一个轮廓。有多少轮廓,向量contours就有多少元素。

    (3)hierarchy:vector<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。

    (4)mode:int类型的,定义轮廓的检索模式:

           取值一:CV_RETR_EXTERNAL只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略;

           取值二:CV_RETR_LIST   检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1,具体下文会讲到;

           取值三:CV_RETR_CCOMP  检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层;

           取值四:CV_RETR_TREE, 检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。

     (5) method:int类型,定义轮廓的近似方法:

           取值一:CV_CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点到contours向量内;

           取值二:CV_CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留;

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

    (6) Point:偏移量,所有的轮廓信息相对于原始图像对应点的偏移量,相当于在每一个检测出的轮廓点上加上该偏移量,并且Point还可以是负值。

 注意事项:

        显然,从函数名可以看出“寻找轮廓”的意思。我们可以通过边缘检测算法得到边缘二值图或者前景二值图,二值图的边缘像素或者前景像素就可以被看出是由多个轮廓(点集)组成的。函数findContours的作用就是将二值图的边缘像素或者前景像素拆分成多个轮廓,便于分开讨论每一个轮廓,其中参数image代表一张二值图,contours代表输出的多个轮廓。对于该函数的C++API,对一个轮廓的描述用vector<Point>,那么多个轮廓(多个点集)如何表示呢?即参数contours是什么数据结构呢?在C++API中,用vector<vector<Point>>描述多个轮廓,即将多个轮廓存在一个vector中。

 

1.2 drawContours()函数

       OpenCV中也提供了一个函数来绘制findContours所找到的多个轮廓,其函数声明如下:

void drawContours(InputOutputArray image,

InputArrayOfArrays contours,

int contourIdx,

const Scalar& color,

int thickness = 1,

int lineType = 8,

InputArray hierarchy = noArray(),

int maxLevel = INT_MAX,

Point offset = Point())

其参数解释如下:

    (1)image: 代表输入的图像矩阵,将轮廓画在该图上;
    (2)contours:是得到的一系列点的集合,代表多个轮廓;
    (3)contourIdx:是一个索引,代表绘制contours中的第几个轮廓;
    (4) color:被填充的颜色,单色可以设置为Scalar(255)等;
    (5)thickness: 所画Contour的线条粗细,如果该参数值小于0,则表示填充整个轮廓内的区域;
    (6)lineType: 线的连通性;
    (7)hierarchy:可选层次信息结构,这里面是findContours所的到的基于Contours的层级信息;
    (8)maxLevel: 绘制轮廓的最大等级。如果等级为0,绘制单独的轮廓。如果为1,绘制轮廓及在其后的相同的级别下轮廓。如果值为2,所有的轮廓。如果等级为2,绘制所有同级轮廓及所有低一级轮廓,诸此种种。如果值为负数,函数不绘制同级轮廓,但会升序绘制直到级别为abs(max_level)-1的子轮廓

    (9)offset:照给出的偏移量移动每一个轮廓点坐标.当轮廓是从某些感兴趣区域(ROI)中提取的然后需要在运算中考虑ROI偏移量时,将会用到这个参数。

 

二、 演示代码

实例步骤

 

  • 转灰度
  • canny得到二值图像
  • findcontours寻找轮廓
  • drawcontours画轮廓

 

我们编写演示代码如下:

#include<opencv2\opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
Mat src1, src2,gray_img, dst;
int value = 100;
int max_value = 255;
void demo(int, void*);
int main()
{
	//发现轮廓-->	cvtcolor-->canny得到二值图像-->findcontours(寻找轮廓)-->drawcontous()
	src1 = imread("C:\\Users\\马迎伟\\Desktop\\yuan1.jpg");
	//src2 = imread("C:\\Users\\马迎伟\\Desktop\\heibao1.png");
	if (src1.empty())
	{
		printf("cannot load!!\n");
		system("pause");
		return -1;
	}
	namedWindow("input", CV_WINDOW_AUTOSIZE);
	imshow("input",src1);
	namedWindow("output", CV_WINDOW_AUTOSIZE);
	cvtColor(src1,gray_img,CV_BGR2GRAY);
	createTrackbar("creattrackbar","output",&value,max_value,demo);
	demo(0, 0);
	waitKey(0);
	return 0;
}
void demo(int, void*)
{
	//将图像表现在src3上
	Mat src3 = Mat::zeros(src1.size(),CV_8UC3);
	vector<vector<Point>>contours;
	vector<Vec4i>hierarchy;
	Canny(gray_img,src2,value,value*2,3,false );
	findContours(src2,contours,RETR_TREE,CHAIN_APPROX_SIMPLE,Point(0,0));
	RNG rng(12345);
	for (size_t i = 0; i < contours.size(); i++)
	{
		Scalar color = Scalar(rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255));
		drawContours(src3, contours, i,color,1,LINE_AA,hierarchy,0,Point(0,0));
	}
	imshow("output",src3);
}