一:介绍
图像形状查找在OPENCV里面是非常常见的功能,它常用于视觉任务、目标检测、图像分割等等。在OPENCV中通常使用Canny函数、findContours函数、drawContours函数结合在一起去做轮廓的形检测。
二.findContours函数的简介以及定义
在OPENCV中通常使用findContours函数去寻找图片的轮廓,也是OPENCV中处理轮廓最重要的函数之一,它常用于找到二值图像中所有物体的轮廓。它的实现原理是通过扫描一张二值图像,然后找到所有的轮廓,并把所有的数据存储在向量里面。下面我们来看看findContours的函数定义
CV_EXPORTS_W void findContours( InputOutputArray image,OutputArrayOfArrays contours,OutputArray hierarchy, int mode,int method, Point offset = Point());
第一个参数:image输入的二值图像,这个图像通常是用在边缘检测、阈值处理等等
第二个参数:contours输出的轮廓集合,每一个轮廓都是由点组成,通常用vector<vector<Point>> contours来表示
第三个参数:hierarchy输出的轮廓层次结构,这通常表示轮廓之间的父子关系,这个是可选参数,通常用vector<Vec4i> hierarchy
来表示。比方说,第i个轮廓,hierarchy[i][0]、hierarchy[i][1]、hierarchy[i][2]、hierarchy[i][3], 依次为第i个轮廓[Next、Pervious、First_Child,Parent], 这表示的是相同等级下种下一轮廓、前一轮廓,第一个子轮廓和父轮廓的索引号。若轮廓i没有下一个,前一个或者父级轮廓,则层次相应的元素是负数。如下图:
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)
三.drawContours函数的简介以及定义
作用:在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
第七个参数:hierarchy ,轮廓层次结构,用于绘制轮廓的父子关系。默认为noArray()
第八个参数:maxLevel ,表示绘制轮廓的最大层级数量。
若maxLevel 为0,则只绘制指定的轮廓;
若maxLevel 为1,则绘制轮廓极其所有嵌套轮廓;
若maxLevel 为2,则绘制轮廓、所有嵌套轮廓、所有嵌套到嵌套的轮廓。
第九个参数:offset轮廓点的偏移量,默认为(0,0)
使用案例
#include <opencv2/opencv.hpp>
using namespace cv;int main() {// 1. 读取图像并预处理Mat image = imread("shape.png");if(image.empty()) return -1;// 2. 转换为灰度图并二值化Mat gray, binary;cvtColor(image, gray, COLOR_BGR2GRAY);threshold(gray, binary, 128, 255, THRESH_BINARY);// 3. 查找轮廓std::vector<std::vector<Point>> contours;findContours(binary, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);// 4. 创建绘制用的空白画布Mat result = Mat::zeros(image.size(), CV_8UC3);// 5. 绘制轮廓(三种方式)// 方式1: 绘制全部轮廓(绿色)drawContours(result, contours, -1, Scalar(0, 255, 0), 2);// 方式2: 绘制特定轮廓(红色)if(contours.size() > 0) {drawContours(result, contours, 0, Scalar(0, 0, 255), 3); // 第一个轮廓加粗}// 方式3: 填充轮廓(蓝色)std::vector<std::vector<Point>> fillContour = {contours[0]};drawContours(result, fillContour, -1, Scalar(255, 0, 0), -1); // 厚度-1表示填充// 6. 显示结果imshow("Original", image);imshow("Contours", result);waitKey(0);return 0;
}
关键参数说明
- 目标图像:绘制的目标画布(示例中为
result
) - 轮廓列表:
findContours
检测到的轮廓集合 - 轮廓索引:
-1
:绘制所有轮廓n
:绘制第n个轮廓(0为第一个)
- 颜色:BGR格式,例如
Scalar(0,255,0)
表示绿色 - 厚度:
- 正数:轮廓线宽度
-1
:填充轮廓内部
四.findContours与drawContours使用关系图
五.Canny函数的简介以及定义
1.相关介绍
Canny函数主要用在OPENCV的边缘检测计算,边缘检测是OPENCV图像中非常重要的功能,它的功能如(上图一)。它能够高效地提取图像中的边缘信息,而Canny边缘检测是OPENCV里面最优秀和最精准的边缘检测方法。
Canny的工作原理可以分为以下比较重要的步骤进行处理,分别是:
1.高斯滤波(将图像转换为灰度图像,高斯滤波作用是平滑图像,让Canny检测的时候准确率更高)
2.梯度强度和方向的计算(计算图像中每个像素的强度和方向、强度表示像素点的边缘强度、梯度表示的是边缘方向这里的梯度需要用到sobel因子)
3.非极大抑制(经过NMS操作后,会除去一些不是边缘的像素点)
4.双阈值处理(给出一个阈值,若超过这个阈值的边缘则会被保留)
下面是双阈值的处理的图解:当梯度值大于maxVal则认为是强边界;当minVal < 梯度值 < maxVal跟边界有连接的部分则保留,否则废弃;梯度值< minVal则废弃。另外需要注意的是高阈值与低阈值的比例最好是2:1到3:1之间。
5.边缘链接(经过双阈值处理过后,强边缘则会留下来,弱边缘则会被抑制,并会把所有的强边缘全部连接起来),步骤如下图
2.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范数进行计算
C++代码使用Canny函数的教程案例
骤包括:加载图像→转灰度→应用Canny→显示结果。
#include <opencv2/opencv.hpp>
using namespace cv;int main() {// 步骤1:加载输入图像(替换"path/to/image.jpg"为实际路径)Mat src = imread("path/to/image.jpg");if (src.empty()) {std::cout << "Error: 无法加载图像" << std::endl;return -1;}// 步骤2:转灰度图(必须预处理为8位灰度格式)Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);// 步骤3:应用Canny函数,设置阈值和参数Mat edges;const double lowThreshold = 50; // 低阈值const double highThreshold = 150; // 高阈值const int apertureSize = 3; // Sobel孔径大小Canny(gray, edges, lowThreshold, highThreshold, apertureSize);// 可选:反转输出(使边缘为黑色,背景为白色)便于显示Mat invertedEdges = ~edges;// 步骤4:显示结果imshow("原始图像", src);imshow("灰度图像", gray);imshow("Canny边缘检测", invertedEdges);waitKey(0); // 等待按键退出return 0;
}
关键注意事项
输入要求:输入图像image
必须是8位灰度图,否则需用cv.cvtColor(src, dst, COLOR_BGR2GRAY)
转换。彩色输入会引发错误3。
参数调试:threshold1
和threshold2
是关键。经验比值约为1:21:2或1:31:3(如50/150)。过高阈值会遗漏边缘,过低会增加噪声。实践中,可用Trackbar交互调整4。
性能优化:apertureSize
增大会捕捉更多细节但也更慢;L2gradient=True
提高精度但牺牲速度,适用于高精度场景。在实时应用中,默认值通常足够2。
输出处理:输出edges
是二值图,可用于后续处理(如轮廓检测或目标识别)。
六.用Opencv查找图形轮廓并画框实战
要实现这个功能我们的代码编写流程如下图步骤
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>using namespace cv;
using namespace std;int main()
{//1.读取图片Mat img = imread("people.jpg"); if(img.empty()){printf("pepole.jpg is empty");return -1;}//2.把彩色图像转换成GRAY灰度图Mat imgGray;cvtColor(img, imgGray, COLOR_RGB2GRAY); //3.Canny对图像进行边缘检测,弱阈值25,强阈值75Mat imgCanny;Canny(imgGray, imgCanny, 25 ,75); // 4.findContours查找灰度图的轮廓vector<vector<Point>> contours;vector<Vec4i> hierarchy;//查询轮廓,MODE是外部轮廓检测RETR_EXTERNAL,method是CHAIN_APPROX_NONE//RETR_EXTERNAL(只检测最外层轮廓)//CHAIN_APPROX_NONE(存储所有顶点,意思就是把图像原封不动的还原出来)findContours(imgCanny, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE); //5.循环轮廓数量后drawcontours画框// 创建绘制用的空白画布Mat drawing = Mat::zeros(imgCanny.size(), CV_8UC3);for (int i = 0; i < contours.size(); i++){Scalar color = Scalar(255,255,0);//对图像轮廓进行画框drawContours(drawing, contours, i, color,1,8,hierarchy); }imwrite("contour.jpg", drawing);return 0;
}