OpenCV 使用 findContours 函数 Note1: 利用各个轮廓的相互阶层(hierarchy)关系

OpenCV 使用 findContours 函数 Note1: 利用各个轮廓的相互阶层(hierarchy)关系输入图像

void Detect_Object(Mat img)
{
    Mat gray, binaryIMG;
    Mat correct_IMG;

    cvtColor(img, gray, CV_BGR2GRAY);
    Canny(gray, binaryIMG, 90, 180);
    blur(binaryIMG, binaryIMG, Size(3, 3));

    // Find contours
    vector<vector<Point> > contours;
    vector<Vec4i> hierarchy;
    double TargetArea = 0;
    findContours(binaryIMG, contours, hierarchy, RETR_TREE, CV_CHAIN_APPROX_NONE, Point(0, 0));

    for (int i = 0; i < contours.size(); i++)
    {
        drawContours(img, contours, i, Scalar(0, 255, 0), 2, 8, hierarchy, 0, Point());
    }
}

先上代码, 上述函数是寻找一个图像内的所有轮廓。

执行后,利用

drawContours(img, contours, i, Scalar(0, 255, 0), 2, 8, hierarchy, 0, Point());

 这个函数,会画出所有图像内的轮廓。

执行上述代码后,我的代码上显示 contour.size() ,即轮廓个数是34个。 这个轮廓个数不是绝对的, 结果由你对canny()选取的阈值, blur() mask的大小选取都有影响。

OpenCV 使用 findContours 函数 Note1: 利用各个轮廓的相互阶层(hierarchy)关系

Canny 和 Blurring 算法结果图像。

 OpenCV 使用 findContours 函数 Note1: 利用各个轮廓的相互阶层(hierarchy)关系

红色显示的是 FindContours函数所检测到的轮廓。

 

你或许会觉得,最终显示的结果像一幅捕捉边缘的图像,即Edge Map。也会不解,有了Canny,何须多此一举在寻找轮廓。

接下来我就要介绍本章的主角轮廓 Hierarchy的阶层关系。

先看一段修改后的代码:

    for (int i = 0; i < contours.size(); i++)
    {
        if (hierarchy[i][3] == -1)
            {
                drawContours(img, contours, i, Scalar(0, 0, 255), 1, 8, hierarchy, 0, Point());
            }
    }

 

这段代码中,多了一段条件语句,语句里写了 

hierarchy[i][3] == -1

首先,我们确认一下结果:

OpenCV 使用 findContours 函数 Note1: 利用各个轮廓的相互阶层(hierarchy)关系

这次的结果里,只检测出了最外围轮廓。很明显

hierarchy[i][3] == -1 起了决定性的作用。 

我们先看看OpenCV官方文件是怎么写的。

OpenCV represents it as an array of four values : [Next, Previous, First_Child, Parent].   (Link me: https://docs.opencv.org/3.4.0/d9/d8b/tutorial_py_contours_hierarchy.html)

 

hierarchy[i][3] == -1, 这里的hierarchy[i]指的是第i个轮廓的阶层关系。而hierarchy[i][0],hierarchy[i][1],hierarchy[i][2],hierarchy[i][3]分别指的是Next, Previous, First_child, Parent。

我们设定的hierarchy[i][3]== -1 的意思就是 “轮廓没有父母”, 即“这个轮廓没有上层阶级的轮廓”。

同理, hierarchy[i][2]== -1 的意思就是 “此轮廓没有第一个孩子”,即“此轮廓没有下层阶级的轮廓”。

至于,hierarchy[][0],hierarchy[][1] 指的是此轮廓的后一个轮廓,和前一个轮廓。他们都是同一个阶级的轮廓。这个前后顺序可能是很随意的,至今还没找到规律。所以还没有到怎么利用他们。

所以,
hierarchy[i][3]== -1 条件下的结果, 是选中了没有父母的轮廓,即他的外围没有包围他的轮廓。

再看看
hierarchy[i][2]== -1 条件下的结果:

OpenCV 使用 findContours 函数 Note1: 利用各个轮廓的相互阶层(hierarchy)关系

 

和预想的一样,它只标出了没有“孩子”的轮廓,即此轮廓内没有更小的轮廓。

对了,我要强调一下,这个例子里我在使用FindContours函数的时候,我用了RETR_TREE 模式。这个模式是“万能的”,把图像内各轮廓的亲属关系都联系上了。简而言之,你能知道一个轮廓的”爷爷奶奶“,”孙子孙女“。甚至更深的祖辈关系。

opencv 提供了各种模式,RETR_LIST, RETR_EXTERNAL等。 上面的官方文件有详细说明。
RETR_LIST 就是这个图像的轮廓只可能是两个阶层的其中之一,要么你就是爹, 要么你就是儿子。本章只讲RETR_TREE。

这时候你可能还没领会到Hierarchy的魅力。 他不会马上帮你挑出你最想要的信息,但是他确实个帮你排除“杂质”帮手。

例如, 我们这次测试的Heliport图像, 很明显这个图像里的“H”是叫无人机去识别, 并且在其中心降落的。 那我们怎么去识别他呢? 如果不用Deep Learning。

我们可以在这个图像里找没有孩子的轮廓,但没有孩子的轮廓候补也很多。怎么办?
很简单, 选取轮廓面积最大的,或者大于一定面积以上的,其实方法真的很多。只要你多加一个条件语句。

简单的看下代码:
    for (int i = 0; i < contours.size(); i++)
    {
        if (hierarchy[i][2] == -1 && contourArea(contours[i]) > 9800)
            {
                drawContours(img, contours, i, Scalar(0, 0, 255), 2, 8, hierarchy, 0, Point());

            }
    }

 

我加了一段 面积大于 9800 像素的条件。结果就找到了 “H”。

OpenCV 使用 findContours 函数 Note1: 利用各个轮廓的相互阶层(hierarchy)关系

 

你问我怎么算的9800? 估算的, 但是我想说的不是面积这部分,利用面积也只是方法之一。 想要更精确确定的方法有很多。比如是高和宽的比例。

今天主要是为了介绍hierarchy的魅力,其实利用好真的能排除很多没用的信息。hierarchy 只是OpenCV Contours 这部分的魅力之一, 仔细翻阅官方文件你会发现,opencv 这部分还真的准备了很多宝贝。