美文网首页图像处理摄影读书
【图像处理】OpenCV系列二十三 --- 图像轮廓查找(fin

【图像处理】OpenCV系列二十三 --- 图像轮廓查找(fin

作者: 307656af5a04 | 来源:发表于2019-05-06 21:07 被阅读234次

上一节我们学习了用如何用watershed函数对图像使用分水岭算法,相信大家学习之后,对分水岭算法已经有了基本的了解,本节呢,我们处理上节遗留的问题,即图像轮廓的查找(findContours)函数!本节针对这个问题,我们进行详解的理解!

一、findContours()函数详解

1、函数原型

void findContours(InputArray image, 
    OutputArrayOfArrays contours,
    OutputArray hierarchy, 
    int mode,
    int method, 
    Point offset = Point());

void findContours(InputArray image, 
    OutputArrayOfArrays contours,
    int mode, 
    int method, 
    Point offset = Point());

2、函数功能
在二值化图像中查找轮廓;
该函数使用@CITE Suzuki 85算法从二进制图像中检索轮廓;
轮廓是形状分析和目标检测与识别的有效工具;

Note
自从OpenCV3.2版本以后,原图像不会被此函数修改。

3、参数详解

  • 第一个参数,InputArray image,原图像,一般为8位单通道的图像;非零像素为1,0像素为0,即输入图像是一个二值化的图像;你可以用compare, inRange, threshold , adaptiveThreshold, Canny,以及其他的方法将一个灰度图像或者彩色图像转换为二值化的图像;如果函数的参数mode是RETR_CCOMP 或者 RETR_FLOODFILL,输入图像可以是32位整型图像(CV_32SC1);

  • 第二个参数,OutputArrayOfArrays contours,检测得到的轮廓,每一个轮廓存储在一个vector类型的点集中;

  • 第三个参数,OutputArray hierarchy,可选的输出矢量,例如std::vector<cv::Vec4i>,包含图像的拓扑信息,它的数量与轮廓的个数一样,每个轮廓contours[i]对应4个hierarchy元素hierarchy[i][0] ~ hierarchy[i][3],分别表示后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号,如果没有对应项,该值设置为-1;

  • 第四个参数,int mode,轮廓的检索模式;

(1)RETR_EXTERNAL表示只检测外轮廓,对于所有的轮廓,设置hierarchy[i][2]和hierarchy[i][3]为-1,即不存在父轮廓和内嵌轮廓的索引编号,只存在后一个轮廓和前一个轮廓;
(2)RETR_LIST 检测的轮廓不建立等级关系
(3)RETR_CCOMP 建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息,如果内孔内还有一个连通物体,这个物体的边界也在顶层;
(4)RETR_TREE 建立一个等级树结构的轮廓;
(5)RETR_FLOODFILL,输入图像可以是32位的整型图像;

  • 第五个参数,int method,轮廓的近似方法;

(1)CHAIN_APPROX_NONE存储所有的轮廓点,相邻的两个点的像素位置差不超过1;
(2)CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息 ;
(3)CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chin chain 近似算法

  • 第六个参数,Point offset = Point(),一个可选的变量,每个轮廓被偏移的偏移量;如果从图像ROI中提取轮廓,然后在整个图像上下文中分析它们,这是非常有用的。

二、drawContours()函数详解

**1、函数原型

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());

2、函数功能

3、参数详解

  • 第一个参数,InputOutputArray image,目标图像;

  • 第二个参数,InputArrayOfArrays contours,输入的所有轮廓,每个轮廓以点集存储;

  • 第三个参数,int contourIdx,参数指示那个轮廓需要去绘制,如果此参数是负数,表示所有的轮廓都要绘制;

  • 第四个参数,const Scalar& color,绘制轮廓的颜色

  • 第五个参数,int thickness = 1,绘制轮廓线的粗细,如果此参数是负数(例如,thickness=FILLED),会对轮廓进行填充;

  • 第六个参数,int lineType = LINE_8,要绘制的线段的类型;

线段的类型有以下几种类型:
(1)FILLED,直接将需要绘制的区域进行填充;
(2)LINE_4 ,4连通的线段;
(3)LINE_8 ,8连通的线段;
(4)LINE_AA ,抗锯齿类型的线段;

  • 第七个参数,InputArray hierarchy = noArray(),层次结构信息,与函数findcontours()的hierarchy有关;

  • 第八个参数,int maxLevel = INT_MAX,绘制轮廓的最高级别。若为0,则绘制指定轮廓;若为1,则绘制该轮廓和所有嵌套轮廓;若为2,则绘制该轮廓、嵌套轮廓/子轮廓和嵌套-嵌套轮廓/孙轮廓,该参数只有在层级结构时才用到;

  • 第九个参数,Point offset = Point(),按照偏移量偏移所有的轮廓,即偏移所有的点坐标;

三、综合实例

1、实验案例
查找一幅图像中的矩形,只要矩形,其他的不要

#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>

using namespace cv;
using namespace std;

static void help(const char* programName)
{
    cout <<
        "\n利用金字塔、Canny算子、轮廓和轮廓简化来查找图像中的矩形\n"
        "返回所有检测的矩形图像\n"
        "调用:\n"
        "./" << programName << " [file_name (optional)]\n"
        "当前使用的OpenCV版本为 " << CV_VERSION << "\n" << endl;
}

// 阈值
int thresh = 50, N = 11;

const char* wndname = "矩形检测实例";

// 辅助函数
// 找到向量之间的余弦
// 从 pt0->pt1 和从 pt0->pt2
static double angle(Point pt1, Point pt2, Point pt0)
{
    double dx1 = pt1.x - pt0.x;
    double dy1 = pt1.y - pt0.y;
    double dx2 = pt2.x - pt0.x;
    double dy2 = pt2.y - pt0.y;

    return (dx1*dx2 + dy1*dy2) / 
        sqrt((dx1*dx1 + dy1*dy1)*
        (dx2*dx2 + dy2*dy2) + 1e-10);
}

// 返回检测到矩形的图像
static void findSquares(const Mat& image, 
    vector<vector<Point> >& squares)
{
    squares.clear();

    Mat pyr, timg, gray0(image.size(), CV_8U), gray;

    // 图像金字塔过滤图像中的噪音
    pyrDown(image, pyr, Size(image.cols / 2, 
        image.rows / 2));

    pyrUp(pyr, timg, image.size());

    // 存放轮廓的点集
    vector<vector<Point> > contours;

    // 在图像的每一个颜色同中查找图像的矩形
    for (int c = 0; c < 3; c++)
    {
        int ch[] = { c, 0 };

        // 输入的矩阵的某些通道拆分复制给对应的输出矩阵
        mixChannels(&timg, 1, &gray0, 1, ch, 1);

        // 尝试使用不同的阈值
        for (int l = 0; l < N; l++)
        {
            // 当阈值为0时,用canny算子
            if (l == 0)
            {
                // 使用canny算子
                Canny(gray0, gray, 0, thresh, 5);
                
                // 使用腐蚀可以处理canny算子结果
                // 中的处在边缘之间潜在的孔 
                dilate(gray, gray, Mat(), Point(-1, -1));
            }
            else
            {
                
                // tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
                gray = gray0 >= (l + 1) * 255 / N;
            }

            // 查找轮廓,并将所有的轮廓存储在list中
            findContours(gray, contours, 
                RETR_LIST, CHAIN_APPROX_SIMPLE);

            vector<Point> approx;

            // 测试每一个轮廓
            for (size_t i = 0; i < contours.size(); i++)
            {
                // 进行多边形拟合
                approxPolyDP(contours[i], approx, 
                    arcLength(contours[i], true)*0.02, true);

                // 对找到的轮廓进行过滤,只要矩形
                if (approx.size() == 4 &&
                    fabs(contourArea(approx)) > 1000 &&
                    isContourConvex(approx))
                {
                    double maxCosine = 0;
                    for (int j = 2; j < 5; j++)
                    {
                        // 寻找相交边的最大角度的cos值
                        double cosine = fabs(angle(approx[j % 4],
                            approx[j - 2], approx[j - 1]));

                        maxCosine = MAX(maxCosine, cosine);
                    }

                    // 如果cosθ < 0.3 ,则认为找到矩形 
                    if (maxCosine <  0.3)
                        squares.push_back(approx);
                }
            }
        }
    }
}

// 将检测到的矩形绘制到图像上
static void drawSquares(Mat& image, 
    const vector<vector<Point> >& squares)
{
    for (size_t i = 0; i < squares.size(); i++)
    {
        const Point* p = &squares[i][0];
        int n = (int)squares[i].size();

        // 拟合多边形
        polylines(image, &p, &n, 1, 
            true, Scalar(0, 255, 0), 
            3, LINE_AA);
    }
    // 显示图像
    imshow(wndname, image);
}

int main(int argc, char** argv)
{
    help(argv[0]);
    
    // 存放轮廓的点集
    vector<vector<Point> > squares;

    // 读取图像
    Mat image = imread("pic1.png", IMREAD_COLOR);

    // 判断图像是否为孔
    if (image.empty())
    {
        cout << "Couldn't load image!" << endl;
        return -1;
    }

    // 查找轮廓
    findSquares(image, squares);

    // 绘制轮廓
    drawSquares(image, squares);

    waitKey(0);
        
    return 0;
}

2、实验结果

原图 实验结果

我是奕双,现在已经毕业将近两年了,从大学开始学编程,期间学习了C语言编程,C++语言编程,Win32编程,MFC编程,毕业之后进入一家图像处理相关领域的公司,掌握了用OpenCV对图像进行处理,如果大家对相关领域感兴趣的话,可以关注我,我这边会为大家进行解答哦!如果大家需要相关学习资料的话,可以私聊我哦!

相关文章

网友评论

    本文标题:【图像处理】OpenCV系列二十三 --- 图像轮廓查找(fin

    本文链接:https://www.haomeiwen.com/subject/aautoqtx.html