opencv版本:3.4.7
编译器版本:VS2019
轮廓发现概述(find contour in your image)
- 轮廓发现是基于图像边缘提取的基础寻找对象轮廓的方法。所以边缘提取的阈值选定会影响最终轮廓发现的结果
相关函数API
在二值图像上发现轮廓 findContoursAPI
void findContours(
InputOutputArray image, //输入图像,非0图像看做1,0像素保持不变,8bit
OutputArrayOfArrays contours,//发现全部的轮廓对象
OutputArray hierarchy, //该图的拓扑结构,可选,该轮廓发现算法是基于图像拓扑结构的实现
int mode,//轮廓返回的模式
int method, //发现方法
Point offset = Point()//轮廓像素位移,默认(0,0)没有位移
);
参数介绍
- image:单通道图像矩阵,可以是灰度图,但更常用的是二值图像,一般是经过
Canny
、拉普拉斯等边缘检测算子处理过的二值图像; - contours:
vector<vector<Point>>
类型,是一个向量,并且是一个双重向量,向量内每个元素保存了一组由连续的Point点构成的点的集合的向量,每一组Point
点集就是一个轮廓。有多少轮廓,向量contours
就有多少元素。 - hierarchy:
vector<Vec4i>
类型,Vec4
i是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
。 - mode:int类型的,定义轮廓的检索模式:
- 取值一:CV_RETR_EXTERNAL只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略;
- 取值二:CV_RETR_LIST 检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以
hierarchy
向量内所有元素的第3
、第4
个分量都会被置为-1
,具体下文会讲到; - 取值三:CV_RETR_CCOMP 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层;
- 取值四:CV_RETR_TREE, 检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
- 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 近似算法;
- 取值一:CV_CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点到
- Point:偏移量,所有的轮廓信息相对于原始图像对应点的偏移量,相当于在每一个检测出的轮廓点上加上该偏移量,并且
Point
还可以是负值。
注意事项:
显然,从函数名可以看出“寻找轮廓”的意思。我们可以通过边缘检测算法得到边缘二值图或者前景二值图,二值图的边缘像素或者前景像素就可以被看出是由多个轮廓(点集)组成的。函数findContours的作用就是将二值图的边缘像素或者前景像素拆分成多个轮廓,便于分开讨论每一个轮廓,其中参数image代表一张二值图**,contours代表输出的多个轮廓。对于该函数的C++API**,对一个轮廓的描述用vector,那么多个轮廓(多个点集)如何表示呢?即参数contours是什么数据结构呢?在C++API中,用vector<vector>描述多个轮廓,即将多个轮廓存在一个vector中。
绘制轮廓 drawContours函数API
void drawContours(
InputOutputArray image, //输入图像 轮廓将绘制到此图上
InputArrayOfArrays contours, //全部发现的轮廓对象
int contourIdx,//轮廓索引号
const Scalar& color,//绘制时候的颜色
int thickness = 1,//绘制线宽
int lineType = 8, //线的类型
InputArray hierarchy = noArray(),//拓扑结构图
int maxLevel = INT_MAX, //最大层数,0只绘制当前的,1表示绘制当前及其内嵌的轮廓
Point offset = Point()//轮廓位移 可选
)
参数介绍
- image: 代表输入的图像矩阵,将轮廓画在该图上;
- contours:是得到的一系列点的集合,代表多个轮廓;
- contourIdx:是一个索引,代表绘制
contours
中的第几个轮廓; - color:被填充的颜色,单色可以设置为
Scalar(255)
等; - thickness: 所画
Contour
的线条粗细,如果该参数值小于0
,则表示填充整个轮廓内的区域; - lineType: 线的连通性;
- hierarchy:可选层次信息结构,这里面是
findContours
所的到的基于Contours
的层级信息; - maxLevel: 绘制轮廓的最大等级。如果等级为
0
,绘制单独的轮廓。如果为1
,绘制轮廓及在其后的相同的级别下轮廓。如果值为2
,所有的轮廓。如果等级为2
,绘制所有同级轮廓及所有低一级轮廓,诸此种种。如果值为负数,函数不绘制同级轮廓,但会升序绘制直到级别为abs(max_level)-1
的子轮廓 - offset:照给出的偏移量移动每一个轮廓点坐标.当轮廓是从某些感兴趣区域(
ROI
)中提取的然后需要在运算中考虑ROI
偏移量时,将会用到这个参数。
图像处理流程
- 输入图像转化为灰度图
- 使用
Canny
进行边缘提取,得到二值图 - 使用
findContours
寻找轮廓 - 使用
drawContours
绘制轮廓
代码演示
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;
#define PIC_PATH "C:\\pic\\"
#define PIC_NAME "1.jpg"
Mat src;
int threshold_value = 20;
int threshold_max = 255;
void contours_demo(int, void*);
int main()
{
string pic = string(PIC_PATH) + string(PIC_NAME);
cout << "原始图片为:"<<pic << endl;
src = imread(pic);
if (src.empty()) {
cout << "图片不存在" << endl;
return -1;
}
namedWindow("原始图片", WINDOW_AUTOSIZE);
imshow("原始图片", src);
cvtColor(src, src, COLOR_BGR2GRAY); //转化为灰度图像
namedWindow("灰度图像", WINDOW_AUTOSIZE);
imshow("灰度图像", src);
createTrackbar("边缘调整", "原始图片", &threshold_value, threshold_max, contours_demo);
contours_demo(0, 0);
waitKey(0);
destroyAllWindows();
return 0;
}
void contours_demo(int, void*)
{
Mat canny_output,dst;
vector<vector<Point>> contourspoints; //轮廓集合
vector<Vec4i> hierachy; //对应每一个轮廓所对用的层次索引 后一个轮廓 前一个轮廓 父轮廓 内嵌轮廓的索引编号
Canny(src, canny_output, threshold_value, threshold_value * 2, 3, false); //边缘处理
//轮廓查找
findContours(canny_output, contourspoints, hierachy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
//创建目标图像
dst = Mat::zeros(src.size(), CV_8UC3);
RNG rng(123456); //定义一个随机数变量
for (size_t i = 0; i < contourspoints.size(); i++)
{
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)); //定义画笔颜色
drawContours(dst, contourspoints, i, color, 2, 8, hierachy, 0, Point(0, 0)); //绘制轮廓
}
namedWindow("轮廓图", WINDOW_AUTOSIZE); //显示目标图像
imshow("轮廓图", dst);
}
执行效果
参考链接:https://blog.csdn.net/weixin_41695564/article/details/80085012