RV1126 NO.38:OPENCV查找图形轮廓重要API讲解
在OpenCV中,图像形状识别是一项基础且实用的功能,广泛应用于计算机视觉领域的目标检测和图像分割等任务。通常通过Canny边缘检测、findContours轮廓提取和drawContours轮廓绘制这三个函数的组合使用,可以实现有效的形状检测。
一.重要的函数讲解:
1.1.findContours函数的简介以及定义

OpenCV中常用findContours函数来检测图像轮廓,这是处理轮廓的核心函数之一。该函数主要用于在二值图像中定位所有物体的轮廓。其工作原理是通过扫描二值图像,检测所有轮廓并将数据存储在向量中。以下是findContours函数的定义:
CV_EXPORTS_W void findContours( InputOutputArray image, OutputArrayOfArrays contours,OutputArray hierarchy, int mode,int method, Point offset = Point());参数说明:
image
输入的二值图像,通常用于边缘检测、阈值处理等操作。contours
输出的轮廓集合,每个轮廓由一系列点组成,通常采用vector<vector<Point>>格式存储。hierarchy(可选参数)
输出的轮廓层次结构,用于描述轮廓间的父子关系,通常以vector<Vec4i>格式表示。对于第i个轮廓:
hierarchy[i][0]:同层级的下一个轮廓索引(Next)hierarchy[i][1]:同层级的前一个轮廓索引(Previous)hierarchy[i][2]:第一个子轮廓索引(First_Child)hierarchy[i][3]:父轮廓索引(Parent)
若某轮廓不存在对应关系(如没有下一个轮廓或父轮廓),相应索引值为负数。

Next:表示同一级别的下一个轮廓索引,若我们图片中取出轮廓0,同一水平的下一个是轮廓1。所以说当轮廓 == 0的时候,NEXT就是轮廓1。
Previous:表示同一级别的上一个轮廓索引,如轮廓1的同一级别的上一个是轮廓0。以此类推,轮廓2的上一个轮廓是轮廓1。
First_Child:表示的是当前轮廓的第一个子轮廓的索引。比方说,对于轮廓2,子轮廓是2a,所以轮廓2的First_Child是轮廓2a相对应的索引值。而对于3a来说,它有两个轮廓分别是6,7, 但这里只能取第一个轮廓,所以这里是6。
Parent:表示的是当前轮廓的父轮廓索引,比方说对于轮廓6和轮廓7来说,它们的父轮廓都是3a。
第四个参数:mode轮廓检索模式,通常有以下选项,分别是:RETR_EXTERNAL(只检测最外层轮廓)、RETR_LIST(检测所有轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,都是独立的)、RETR_CCOMP(检测所有轮廓,但是所有的轮廓只建立两个等级关系,也就是外围是顶层,而外围内的内部轮廓都属于顶层)、RETR_TREE(检测所有轮廓并建立轮廓树,这个模式下外围轮廓包含内层轮廓,内层还可以继续嵌套)。
第五个参数:method轮廓近似方法,通常有以下的几种方法,分别是CHAIN_APPROX_NONE(存储所有顶点,意思就是原封不动的把图形还原出来)、CHAIN_APPROX_SIMPLE(仅存储轮廓的拐点信息,并把所有轮廓拐点处的点保存到向量里面)、CHAIN_APPROX_TC89_L1(使用TEH_CHAIN近似算法)。
第六个参数:offset轮廓点偏移量,默认(0,0)
1.2drawContours函数的简介以及定义

在OPENCV中drawContours常用于绘制图像的轮廓,如上图,我们来看看这个函数的API定义:
CV_EXPORTS_W void drawContours( InputOutputArray image, InputArrayOfArrays contours,int contourIdx, const Scalar& color,int thickness = 1, int lineType = LINE_8,InputArray hierarchy = noArray(),int maxLevel = INT_MAX, Point offset = Point() );第一个参数:image输出图像,即绘制轮廓后的图像
第二个参数:contours轮廓的集合,它是由一系列的点组成
第三个参数:contourIdx、轮廓索引数组,指定要绘制哪些轮廓
第四个参数:contourColor,轮廓颜色,使用Scalar类型表示
第五个参数:thickness,轮廓线宽,默认1
第六个参数:lineType ,轮廓线类型,默认为LINE_8
//! type of line
enum LineTypes{
FILLED=-1,
LINE_4=4,//!< 4-connected line
LINE_8,//!< 8-connected line
LINE_AA = 16 //!< antialiased line
};第七个参数:hierarchy ,轮廓层次结构,用于绘制轮廓的父子关系。默认为noArray()
第八个参数:maxLevel ,表示绘制轮廓的最大层级数量。若maxLevel 为0,则只绘制指定的轮廓;若maxLevel 为1,则绘制轮廓极其所有嵌套轮廓;若maxLevel 为2,则绘制轮廓、所有嵌套轮廓、所有嵌套到嵌套的轮廓。
第九个参数:轮廓点的偏移量,默认为(0,0)
1.3.Canny函数的简介以及定义
Canny函数是OpenCV中用于边缘检测的核心算法,其功能如(上图一)所示。作为OpenCV中最精确高效的边缘检测方法,它能有效提取图像边缘特征。
Canny边缘检测主要包含以下关键步骤:
- 高斯滤波:先将图像转为灰度图,通过高斯模糊平滑图像以提高检测准确率
- 梯度计算:利用Sobel算子计算每个像素的梯度强度和方向,强度反映边缘强度,方向指示边缘走向
- 非极大抑制(NMS):过滤非边缘像素点,保留真实边缘
- 双阈值处理:设置高低阈值,高于高阈值的边缘被保留
- 边缘连接:最终连接所有强边缘,抑制弱边缘,形成完整边缘结构
具体流程如下图2所示。

下面是双阈值的处理的图解:当梯度值大于maxVal则认为是强边界;当minVal < 梯度值 < maxVal跟边界有连接的部分则保留,否则废弃;梯度值< minVal则废弃。另外需要注意的是高阈值与低阈值的比例最好是2:1到3:1之间。

下面我们来看看Canny的函数定义:
CV_EXPORTS_W void Canny( InputArray image, OutputArray edges,double threshold1, double threshold2,int apertureSize = 3, bool L2gradient = false );
第一个参数:image输入的图像,这个图像一定要单通道灰度图
第二个参数:edges输出的边缘图像,这个图像也必须是单通道黑白图
第三个参数:threshold1第一个滞后性阈值,低阈值,小于低阈值则认为是弱边缘,就是需要抛弃的边缘。
第四个参数:threshold2第二个滞后性阈值,高阈值,大于高阈值被认为强边缘,需要保留的边缘
第五个参数:apertureSize指的是Sobel算子大小,这个值默认为3,代表的是3*3的矩阵大小。
第六个参数:L2gradient是计算图像梯度幅度值的情况,这个值默认为False;若选择True,则使用更精确的L2范数进行计算
个人疑问点:
vector<vector<Point>> contours; 和 vector<Vec4i> hierarchy; 基于C++标准模板库(STL)的容器定义在这里的具体详述:
vector<vector<Point>> contours;
- 结构解析:
- 外层
vector:动态数组容器,存储多个内层vector。 - 内层
vector<Point>:每个内层vector是一个点的集合,表示单个轮廓的所有边界点。 Point类型:OpenCV中的基础数据类型(cv::Point),表示二维坐标点(x, y),通常为整数类型(如int)。
- 外层
- 作用:
contours存储检测到的所有轮廓,每个轮廓由一组有序的边界点(Point)构成。- 例如:若检测到3个轮廓,则
contours包含3个vector<Point>元素,每个元素对应一个轮廓的点集。
- 模板特性:
vector是STL中的模板类,通过尖括号<>指定存储的数据类型(此处为vector<Point>)。- 嵌套模板
vector<vector<Point>>形成二维动态数组,类似“数组的数组”。
2. vector<Vec4i> hierarchy;
- 结构解析:
Vec4i类型:OpenCV自定义的四元素整型向量(cv::Vec4i),存储4个int类型数据。vector<Vec4i>:动态数组,每个元素是一个Vec4i对象,表示轮廓的层次关系信息。
- 作用:
hierarchy存储轮廓之间的嵌套关系(如父子轮廓、兄弟轮廓等)。- 每个
Vec4i元素包含4个整数,通常表示:[Next, Previous, FirstChild, Parent]:当前轮廓的下一个轮廓索引、上一个轮廓索引、第一个子轮廓索引、父轮廓索引。
- 例如:若轮廓A包含子轮廓B和C,则B和C的
Parent索引指向A,形成层次树结构。
- 模板特性:
vector同样作为模板容器,存储Vec4i类型数据。Vec4i是OpenCV对固定大小向量的模板实现(如cv::Vec<int, 4>)。
3. 为什么这样设计?
- 轮廓检测需求:
- 轮廓(Contour)本质是点集序列,需动态存储不同数量的点(故用
vector<Point>)。 - 多个轮廓需统一管理(故用外层
vector)。 - 层次关系需结构化存储(故用
Vec4i编码索引关系)。
- 轮廓(Contour)本质是点集序列,需动态存储不同数量的点(故用
- STL与OpenCV结合:
- STL的
vector提供动态数组能力,适配轮廓点数不确定的场景。 - OpenCV的
Point和Vec4i是专门为图像处理优化的数据类型,与OpenCV算法(如findContours)无缝衔接。
- STL的
