# findContours函数解析

# 1.基本用法

获取对象的轮廓,一般最好先对图像进行灰度化再进行阈值处理,然后用来检测轮廓。


void cv::findContours(
     InputOutputArray image,
     OutputArrayOfArrays contours,
     OutputArray hierarchy,
     int mode,
     int method,
     Point offset = Point() 
)
  • image: 8位单通道图像
  • contours: 检测到的轮廓,vector<vector<cv::Point>>
  • hierarchy: 检测到的轮廓之间的嵌套和邻近关系,vector<cv::Vec4i>
  • mode: 不同的轮廓检测算法,常用的有RETR_EXTERNAL/RETR_LIST/RETR_CCOMP/RETR_TREE四种 (opens new window)
  • method: 轮廓逼近方法, (opens new window),可使用更少的点表示轮廓,减少内存占用。
  • offset: 轮廓点应该偏移的量,当在roi上提取轮廓后想还原到原图上时比较有用。

# 2.轮廓提取模式

第四个参数mode,可以选择不同的轮廓提取算法,常用的有RETR_EXTERNAL/RETR_LIST/RETR_CCOMP/RETR_TREE四种 (opens new window)。下面分别进行介绍。在findContours函数中,其第3个参数hierarchy表示的轮廓之间的层级关系,对于不同mode的轮廓提取算法,其返回的值是不同的。如下图【来自于OpenCV Doc】:

图中不同的轮廓有层级嵌入关系,我们将外部的轮廓称为,内部的轮廓称为hierarchy就是表示轮廓的父子和邻近关系的矩阵。上图中有0/1/2/3/4/5/2a/3a 8 个轮廓,其中0,1,2是最外侧的轮廓,可记为它们在层级0hierarchy-0。而2a是轮廓2的子轮廓,记为其在层级1hierarchy 1。同样轮廓3是轮廓2a的子轮廓,记为其在层级2hierarchy 2。同样轮廓3a是轮廓3的子轮廓,记为其在层级3hierarchy 34/53a的子轮廓,其构成层级4hierarchy 4。 属于每个层的轮廓都有其自己的信息,如它的子轮廓是什么,父轮廓是什么,OpenCV通过一个四个元素的数组来表示每个轮廓与其他轮廓的关系,这个四维数组中的值分别表示**[Next, Previous, First_Child, Parent], Next表示属于同一个层级hierarchy的下一个轮廓,以上图轮廓0为例,0,1,2属于同个层级hierarchy-0的轮廓,因此0Next1,1Next2。同一个层级中轮廓2已经是最后一个了,因此其Next-1。同样对于上图的轮廓4,与其同属于层级4hierarchy-4的轮廓是5,因此4Next=5,而5Next=-1Previous表示同一层级中之前的的那个轮廓**,如上图,1Previous=0, 2Previous=1,0Previous=-1First_Child表示当前轮廓的第一个子轮廓,如上图,0First_Child=-1,2First_Child=2a,3aFirst_Child=4Parent表示当前轮廓的父轮廓,如上图,45的父轮廓都是3a3a的父轮廓是3,3的父轮廓是2a,2Parent=-1

findContours方法中的mode参数会返回不同的hierarchy信息,因为有些算法会找出轮廓间的嵌套和邻近关系,有些则只是把轮廓找出来而不会解析轮廓之间的信息。

# 2.1 RETR_LIST

RETR_LIST算法只会返回轮廓信息,而没有轮廓间的嵌套信息。因此,所有的轮廓都属于同个层级hierarchy没有父子关系, hierarchy返回值中只有NextPreviousParentFirst_Child都为-1。四维数组的第3和第4个元素都是-1。如上图运行findContours后的输出:

findContours(image, contours, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
>>> hierarchy
[1, -1, -1, -1]
[2, 0, -1, -1]
[3, 1, -1, -1]
[4, 2, -1, -1]
[5, 3, -1, -1]
[6, 4, -1, -1]
[7, 5, -1, -1]
[-1, 6, -1, -1]

这里的0/1/2/3/4/5/6/7对应的是轮廓在contours中的下标。

# 2.2 RETR_EXTERNAL

RETR_EXTERNAL算法,只会返回最外侧的轮廓信息,所有的子轮廓都不会返回,如上图,使用RETR_EXTERNAL算法将只会返回hierarchy-0层级0的3个轮廓。当然hierarchy中也只有3个轮廓之间的邻近信息,ParentFirst_Child依然都为-1

findContours(image, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
[1, -1, -1, -1]
[2, 0, -1, -1]
[-1, 1, -1, -1]

# 2.3 RETR_CCOMP

RETR_CCOMP算法会找到图中所有的轮廓,但只会将轮廓组织成两层hierarchy=2。物体的外轮廓属于hierarchy-0,内轮廓属于hierarchy-1,如上图0/1/2/3/4/5都属于hierarchy-0,而2a/3a属于hierarchy-1

findContours(image, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);
[1, -1, -1, -1]
[2, 0, -1, -1]
[4, 1, 3, -1]
[-1, -1, -1, 2]
[6, 2, 5, -1]
[-1, -1, -1, 4]
[7, 4, -1, -1]
[-1, 6, -1, -1]

值得注意的是对于轮廓2a3a,其hierarchy分别是[-1, -1, -1, 2][-1, -1, -1, 4]。这是因为2a3a虽然属于hierarchy-1,但中间还隔着轮廓3,因此2a3a之间没有邻近关系。

再来看个例子,如下图:【来自于OpenCV Doc】

轮廓0是外轮廓,12分别是轮廓0所围成物体的内轮廓,4属于内轮廓,3属于外轮廓,6属于内轮廓,5属于外轮廓,78也都属于外轮廓。对于轮廓0其属于hierarchy-1,两个内轮廓12属于hierarchy-2。故对于轮廓0,其Next=3,同个层级hierarchy level的下一个,previous=-1,‵First-Child=1,故轮廓0hierarchy=[3,-1, 1, -1]`。

轮廓1属于层级2hierarchy-2,其同一层级的下一个(与1在同个父外轮廓中)轮廓是2,其他均为-1,因此轮廓1hierarchy=[2, -1, -1, 0]

轮廓2属于层级2hierarchy-2,其前一个轮廓是同个父外轮廓下的1,其余为-1,因此轮廓2hierarchy=[-1, 1, -1, 0]

轮廓3hierarchy-1中的Next=5,Previous=0,First-Child=4,Parent=-1

>>> hierarchy
array([[[ 3, -1,  1, -1],
        [ 2, -1, -1,  0],
        [-1,  1, -1,  0],
        [ 5,  0,  4, -1],
        [-1, -1, -1,  3],
        [ 7,  3,  6, -1],
        [-1, -1, -1,  5],
        [ 8,  5, -1, -1],
        [-1,  7, -1, -1]]])

# 2.4 RETR_TREE

RETR_TREE算法提取所有的轮廓,并返回所有轮廓之间的嵌套关系。如上图,使用RETR_TREE得到的轮廓hierarchy之间的关系为:【来自于OpenCV Doc】括号中的绿色字表示轮廓所属的层级hierarchy

以轮廓0为例,其属于hierarchy-0,‵Next=7, Previous=-1, First_Child=1, Parent=-1`。

>>> hierarchy
array([[[ 7, -1,  1, -1],
        [-1, -1,  2,  0],
        [-1, -1,  3,  1],
        [-1, -1,  4,  2],
        [-1, -1,  5,  3],
        [ 6, -1, -1,  4],
        [-1,  5, -1,  4],
        [ 8,  0, -1, -1],
        [-1,  7, -1, -1]]])

findContours(image, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);
[3, -1, 1, -1]
[2, -1, -1, 0]
[-1, 1, -1, 0]
[5, 0, 4, -1]
[-1, -1, -1, 3]
[6, 3, -1, -1]
[-1, 5, -1, -1]

# 3.测试代码

#include <opencv2/opencv.hpp>
#include <common.h>

using namespace std;

int main(int argc, char **argv) 
{
    cv::RNG rng(12345);
    cout << "Usage: " << argv[0] << "\n";
    cv::String img_dir = "/images/OpenCV/2findContours/hole-hierarchy.png";
    cv::Mat image = cv::imread(img_dir, cv::IMREAD_GRAYSCALE);

    vector<cv::Vec4i> hierarchy;
    vector<vector<cv::Point> > contours;

    findContours(image, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE);
    cv::cvtColor(image, image, cv::COLOR_GRAY2BGR);
    std::cout << "Contours Size: " << contours.size() << std::endl;
    for(size_t i=0; i<contours.size(); i++)
    {
        cv::Scalar clr = cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
        cv::drawContours(image, contours, i, clr, 3);
    }

    for(auto &v : hierarchy)
    {
        std::cout << v << std::endl;
    }
    std::cout << image.channels() << std::endl;
    cv::imwrite("contours.png", image);
    cv::imshow("Img", image);
    cv::waitKey(0);
    return 0;
}s
(adsbygoogle = window.adsbygoogle || []).push({});

# 参考资料