Opencv查找图形形状的重要API讲解

发布于:2025-06-08 ⋅ 阅读:(22) ⋅ 点赞:(0)

一:介绍

图像形状查找在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;
}

关键参数说明

  1. 目标图像:绘制的目标画布(示例中为result
  2. 轮廓列表findContours检测到的轮廓集合
  3. 轮廓索引
    • -1:绘制所有轮廓
    • n:绘制第n个轮廓(0为第一个)
  4. 颜色:BGR格式,例如Scalar(0,255,0)表示绿色
  5. 厚度
    • 正数:轮廓线宽度
    • -1:填充轮廓内部

四.findContours与drawContours使用关系图

五.Canny函数的简介以及定义

1.相关介绍

Canny函数主要用在OPENCV的边缘检测计算,边缘检测是OPENCV图像中非常重要的功能,它的功能如(上图一)。它能够高效地提取图像中的边缘信息,而Canny边缘检测是OPENCV里面最优秀和最精准的边缘检测方法

Canny的工作原理可以分为以下比较重要的步骤进行处理,分别是:

1.高斯滤波(将图像转换为灰度图像,高斯滤波作用是平滑图像,让Canny检测的时候准确率更高)

2.梯度强度和方向的计算(计算图像中每个像素的强度和方向、强度表示像素点的边缘强度、梯度表示的是边缘方向这里的梯度需要用到sobel因子)

3.非极大抑制(经过NMS操作后,会除去一些不是边缘的像素点)

4.双阈值处理(给出一个阈值,若超过这个阈值的边缘则会被保留)

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

参数调试threshold1threshold2是关键。经验比值约为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,强阈值75
    Mat 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;
}


网站公告

今日签到

点亮在社区的每一天
去签到