图像处理摄影读书

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

2019-05-06  本文已影响234人  307656af5a04

上一节我们学习了用如何用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、参数详解

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

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

二、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、参数详解

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

三、综合实例

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对图像进行处理,如果大家对相关领域感兴趣的话,可以关注我,我这边会为大家进行解答哦!如果大家需要相关学习资料的话,可以私聊我哦!

上一篇下一篇

猜你喜欢

热点阅读