# 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 3
。4/5
是3a
的子轮廓,其构成层级4hierarchy 4
。 属于每个层的轮廓都有其自己的信息,如它的子轮廓是什么,父轮廓是什么,OpenCV
通过一个四个元素的数组来表示每个轮廓与其他轮廓的关系,这个四维数组中的值分别表示**[Next, Previous, First_Child, Parent]
, Next
表示属于同一个层级hierarchy
的下一个轮廓,以上图轮廓0
为例,0,1,2
属于同个层级hierarchy-0
的轮廓,因此0
的Next
是1
,1
的Next
是2
。同一个层级中轮廓2
已经是最后一个了,因此其Next
是-1
。同样对于上图的轮廓4
,与其同属于层级4hierarchy-4
的轮廓是5
,因此4
的Next=5
,而5
的Next=-1
。Previous
表示同一层级中之前的的那个轮廓**,如上图,1
的Previous=0
, 2
的Previous=1
,0
的Previous=-1
。First_Child
表示当前轮廓的第一个子轮廓,如上图,0
的First_Child=-1
,2
的First_Child=2a
,3a
的First_Child=4
。Parent
表示当前轮廓的父轮廓,如上图,4
和5
的父轮廓都是3a
,3a
的父轮廓是3
,3
的父轮廓是2a
,2
的Parent=-1
。
findContours
方法中的mode
参数会返回不同的hierarchy
信息,因为有些算法会找出轮廓间的嵌套和邻近关系,有些则只是把轮廓找出来而不会解析轮廓之间的信息。
# 2.1 RETR_LIST
RETR_LIST
算法只会返回轮廓信息,而没有轮廓间的嵌套信息。因此,所有的轮廓都属于同个层级hierarchy
没有父子关系, hierarchy
返回值中只有Next
和Previous
,Parent
和First_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
个轮廓之间的邻近信息,Parent
和First_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]
值得注意的是对于轮廓2a
和3a
,其hierarchy
分别是[-1, -1, -1, 2]
和[-1, -1, -1, 4]
。这是因为2a
和3a
虽然属于hierarchy-1
,但中间还隔着轮廓3
,因此2a
和3a
之间没有邻近关系。
再来看个例子,如下图:【来自于OpenCV Doc】
轮廓0
是外轮廓,1
和2
分别是轮廓0
所围成物体的内轮廓,4
属于内轮廓,3
属于外轮廓,6
属于内轮廓,5
属于外轮廓,7
和8
也都属于外轮廓。对于轮廓0
其属于hierarchy-1
,两个内轮廓1
和2
属于hierarchy-2
。故对于轮廓0
,其Next=3
,同个层级hierarchy level
的下一个,previous=-1
,‵First-Child=1,故轮廓
0的
hierarchy=[3,-1, 1, -1]`。
轮廓1
属于层级2
,hierarchy-2
,其同一层级的下一个(与1
在同个父外轮廓中)轮廓是2
,其他均为-1
,因此轮廓1
的hierarchy=[2, -1, -1, 0]
。
轮廓2
属于层级2
,hierarchy-2
,其前一个轮廓是同个父外轮廓下的1
,其余为-1
,因此轮廓2
的hierarchy=[-1, 1, -1, 0]
。
轮廓3
在hierarchy-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