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的大小选取都有影响。
Canny 和 Blurring 算法结果图像。
红色显示的是 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
首先,我们确认一下结果:
这次的结果里,只检测出了最外围轮廓。很明显
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 条件下的结果:
和预想的一样,它只标出了没有“孩子”的轮廓,即此轮廓内没有更小的轮廓。
对了,我要强调一下,这个例子里我在使用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”。
你问我怎么算的9800? 估算的, 但是我想说的不是面积这部分,利用面积也只是方法之一。 想要更精确确定的方法有很多。比如是高和宽的比例。
今天主要是为了介绍hierarchy的魅力,其实利用好真的能排除很多没用的信息。hierarchy 只是OpenCV Contours 这部分的魅力之一, 仔细翻阅官方文件你会发现,opencv 这部分还真的准备了很多宝贝。