opencv中关于轮廓检测识别Contours及相关函数的介绍

发布时间:2023-01-16 14:30

最近在用vs和opencv库在做图像处理的项目,关于轮廓识别部分,我查阅了一些资料, 现结合自己的理解整理出来,希望能对你有用。

1.contours概述
在利用openCV对图像进行处理时,我们可能会需要提取出图片上物体的轮廓,进而判断其形状、获取轮廓上的点等等。为了便捷地获得图像轮廓,我们可以使用opencv库中的Contours相关API接口。
Contours可以简单地被理解为一条由其上全部连续点组成的曲线,这些点通常颜色相同、像素值接近。一张图片上可能有多条轮廓,而每条轮廓由多个连续的像素点组成。Contours主要用于形状分析、物体检测识别和轮廓周长面积计算等场合[1]。

2.常用函数
识别轮廓时,opencv中最常用的两个API就是 findContours() 和 drawContours() ,下面对这两者进行介绍。

findContours()
该函数用于提取图像中的轮廓,并将轮廓信息存储起来。

findContours( InputOutputArray image,//输入图像,一般为二值图
 OutputArrayOfArrays contours,//用于保存轮廓信息的数组
 OutputArray hierarchy,//轮廓层级关系
 int mode,//轮廓的检索模式
 int method,//定义轮廓的近似方法
 Point offset=Point());//偏移量

以上六个参数的详细解释如下。
(1)为了提高轮廓识别准确度,输入的image一般为二值图像,即经过灰度化,滤波,canny、sobel或laplace边缘检测后的图像。
(2)我们需要定义一个容量可变的容器,来保存图像的轮廓信息。那么,第二个参数contours便来源于此。contours可以用 std::vector> contours;语句进行定义。在此语句中,std::vectorC++中的一个类,表示容量可变的数组容器。该数组由多个contours组成,数组中的每一个元素即为一个contours,每一个contours由多个Point组成。那么,为什么要求数组的大小可以改变呢?这是因为在检测轮廓之前,计算机并不知道图像上的独立轮廓到底有多少个,如果提前设定数组大小,可能会出现数据溢出的情况。为了避免数据丢失,用std::vector定义容器,随着更多的轮廓被检测出,数组容量也不断被扩大。这里可能不太容易理解,但下面的代码一看你就懂了。
(3)hierarchy表示轮廓的层级关系。用语句vector hierarchy;来定义,一个hierarchy包含4个整型数据,分别表示:后一个轮廓的序号、前一个轮廓的序号、子轮廓的序号、父轮廓的序号,用于体现图像不同轮廓之间的层级关系[2]。hierarchy参数项与下面的mode参数相关联,选择不同的检索模式,hierarchy的值不同。
(4)mode为轮廓的检索模式,有4个选项,不同选项最终生成出来的轮廓样式有所不同[3]。你可以根据自己的需求进行选择,如下表所示。

模式 特点
CV_RETR_LIST 检测所有的轮廓,包括内围、外围轮廓,轮廓之间不建立层级关系
CV_RETR_CCOMP 检测所有轮廓,轮廓之间只建立两个层级关系,不存在父轮廓或子轮廓关系
CV_RETR_TREE 检测所有轮廓,轮廓之间建立完整的层级关系,全部四个层级关系
CV_RETR_EXTERNAL 只检测最外围轮廓 ,忽略内围轮廓,轮廓之间不存在层级关系

(5)method,用来定义轮廓近似方法。根据前面的内容,我们知道轮廓是由物体外形的边界点组成的曲线,包含着边界上的点。但是,contours中是否一定存储着所有边界点呢?答案是不一定,这由轮廓近似方法method决定。
如果设置选项为CV_CHAIN_APPROX_NONE,所有边界点都被保存。然而,它们并不一定都能派上用场。比如说,识别到一条直线轮廓,为了显示它,我们只须存储它的两个端点就够了,可以舍弃掉其余的点。要做到这点,将选项改为CV_CHAIN_APPROX_SIMPLE即可,舍弃冗余的点,从而节省存储空间。结合下面的图片进行理解,左侧矩形轮廓通过CV_CHAIN_APPROX_NONE得到,共734个点;右侧矩形轮廓通过CV_CHAIN_APPROX_SIMPLE得到,只有4个点。可见,后者节省了很多空间。
opencv中关于轮廓检测识别Contours及相关函数的介绍_第1张图片
不同的模式所提取出来的轮廓点有所不同,如下表所示。

模式 特性
CV_CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点
CV_CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息,拐点与拐点之间直线段上的信息点不予保留
CV_CHAIN_APPROX_TC89_L1 或 CV_CHAIN_APPROX_TC89_KCOS 使用teh-Chinl chain 近似法中的一个

(6)Point(a,b)可以用作图像中轮廓位置的修改。例如Point(20,30)表示每个轮廓点向右偏移20个像素点、向下偏移30个像素点。(默认右为正、下为正)

drawContours()
该函数将提取出来的轮廓信息绘制出来,显示在图片上。

void drawContours(InputOutputArray image, //输出的目标图像
InputArrayOfArrays contours, //输入前面findContours提取的轮廓数组
int contourIdx, //数组contours中的元素序号,0、1、2...
const Scalar& color, //轮廓的颜色
int thickness=1, //轮廓线宽度
int lineType=8, //轮廓线型
InputArray hierarchy=noArray(), //轮廓层级关系
int maxLevel=INT_MAX, //轮廓绘制最高层次
Point offset=Point() );//偏移量

以上为drawContours函数及其参数的解释,其中大部分很容易理解或者在上文中已解释过。这里详细介绍第8个参数int maxLevel=INT_MAX,此参数表示绘制轮廓线的最大级别。如果为0,则只绘制指定的轮廓。如果值为1,函数将绘制该轮廓线和所有内嵌轮廓线。如果值为2,则该函数绘制轮廓线、所有内嵌轮廓线,和比内嵌轮廓线再低一级的内嵌轮廓线,以此类推。仅当存在可用的层次结构时才考虑此参数,就是说hierarchy有效时这个参数才有用。

3.代码实现
下面结合实例对图像轮廓识别地过程进行解释。

#include
#include
#include
using namespace cv;
using namespace std;

Mat src, src_gray, src_blur, src_canny, src_cont;

int main(int argc, char** argv){
	src = imread("C:\\Users\\ASUS\\Pictures\\beauties\\lindaiyu.jpg");
	if(!src.data){
		printf("can not open the mark_image!!\n");
		system("pause");
		return -1;
	}
	namedWindow("tupian", CV_WINDOW_AUTOSIZE);
	imshow("tupian", src);

	cvtColor(src, src_gray, CV_BGR2GRAY);  //将图像灰度化
	blur(src_gray, src_blur, Size(3, 3));  //对图像进行均值滤波
	Canny(src_blur, src_canny, 40, 120);

	src_cont = Mat::zeros(src.size(), CV_8UC3);  //创建一个与src尺寸相同的纯色图片
	vector<vector<Point>> contours;  //定义一个存储轮廓数组的容器
	vector<Vec4i> hierarchy;  // 每一个hierarchy由4个整形变量组成
	findContours(src_canny, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));   //寻找轮廓
	RNG rng(0);  //定义随机数,初始值为0
	for(int i = 0; i < contours.size(); i++){
		Scalar color = Scalar(rng.uniform(0,255), rng.uniform(0,255), rng.uniform(0,255));  //定义一个随机颜色
		drawContours(src_cont, contours, i, color, 1, 8, hierarchy, 0, Point(0,0));  //绘制第i个轮廓
	}
	namedWindow("contours", CV_WINDOW_AUTOSIZE);
	imshow("contours", src_cont);
	waitKey(0);
	return 0;
}

首先,对图像进行灰度化处理,再调用blur()函数去除噪声,然后使用canny算子获得图像边缘,之后找到图像的每一个轮廓,最后依次绘制出来。至此,轮廓识别的整个过程便搞定了。原图与轮廓图的对比如下所示。
opencv中关于轮廓检测识别Contours及相关函数的介绍_第2张图片

4.查找指定轮廓上的点
识别出轮廓并在图像中显示出来之后,我们或许还有进一步的需求,例如判断轮廓形状、获得每条轮廓上像素点的信息、计算轮廓周长面积等,下面我们介绍查找轮廓点的方法。
在上面的程序中加入以下代码即可。

for(int i = 0; i < contours.size(); i++){
	for(int j = 0; j< contours[i].size(); j++){
		cout<<"第 "<<i<<" 个轮廓上第 "<<j<<" 个点的坐标为:";
		cout<<contours[i][j].x<<", "<<contours[i][j].y<<endl;
	}
}

contours.size()返回整张图片上轮廓的总数目;contours[i].size()返回第 i 个轮廓上像素点的总数;contours[i][j]表示第 i 个轮廓上的第 j 个像素点,contours[i][j].xcontours[i][j].y分别为该点的x和y坐标值。
输出结果如下图所示。
opencv中关于轮廓检测识别Contours及相关函数的介绍_第3张图片
至此,本文对Contours概念及相关函数做了简要的介绍,并读取出轮廓上所有的像素点,大家如有疑问或其它见解欢迎在评论区讨论。

参考引用
[1].opencv官网上关于contours的介绍(https://docs.opencv.org/3.3.1/d4/d73/tutorial_py_contours_begin.html)
[2].轮廓层级的进一步解释(https://blog.csdn.net/qq_33810188/article/details/81285867)
[3].findContours函数参数详解(https://blog.csdn.net/laobai1015/article/details/76400725)

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号