contours, hierarchy = cv2.findContours(img, mode=cv2.RETR_EXTERNAL, method=cv2.CHAIN_APPROX_NONE)
输入参数:
- img : 单通道二值图像,白色是前景
- RETR_EXTERNAL : 只返回最外边的轮廓, hierarchy[i][2]=hierarchy[i][3]=-1
- CHAIN_APPROX_NONE : 存储轮廓上的所有点
输出参数:
- contours : 轮廓 M*N M是轮廓个数 N是每个轮廓的点
- hierarchy : 轮廓等级关系 M*4
不同版本的opencv中输出参数个数有3个的,有2个的,我的版本是opencv-python 4.5.3.56
这个函数输出数里的contours很好理解,主要是后面这个hierarchy(等级关系)和后面这个mode与method的关系不是很好理解,其中,method主要是表示存储方式的不同,如下:
CV_CHAIN_APPROX_NONE :存储轮廓的所有点
CV_CHAIN_APPROX_SIMPLE :不保存轮廓中水平、垂直、对角的线段,只保存轮廓的角点
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS : 应用了 Teh-Chin 链近 似算法的一种存储风格,这个我也没搞懂
剩下的就是重点,就是输出参数hierarchy和mode的关系到底是什么样的,hierarchy的四个参数到底表示的是什么意思,这四个值分别表示当前轮廓的“后前子父”对象的序号,下面看验证,首先看mode的四个选项,
- CV_RETR_EXTERNAL :返回最外层轮廓, hierarchy[i][2]=hierarchy[i][3]=-1
- CV_RETR_LIST :返回所有的轮廓,但是没建立等级关系
- CV_RETR_CCOMP :返回所有轮廓,包含两个层级结构
- CV_RETR_TREE :返回所有轮廓,建立完整的层次结构
例子:采用图片如下:
验证1、 contours, hierarchy = cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cv2.RETR_EXTERNAL:返回最外侧轮廓线
得到结果:如下图,找到了所有最外侧的边框
此时,hierarchy的值很好理解
表1
序号 | 后 | 前 | 子 | 父 |
0 | 1 | -1 | -1 | -1 |
1 | 2 | 0 | -1 | -1 |
2 | -1 | 1 | -1 | -1 |
表1中第一列是hierarchy中的轮廓序号,后四列分别是该轮廓的“后前子父”的轮廓序号,这里 mode= cv2.RETR_EXTERNAL,所以只返回最外侧轮廓,那么每一个轮廓都是单独的,也就没有子父轮廓,所以后两行都是-1,表示没有。
表1中第一行[1 -1 -1 -1 ]表示序号为0的轮廓,它的下一个是序号为1 的轮廓,前一个是序号为-1的轮廓,也就是没有前一个,它是第一个。同样可以推出,轮廓顺序为:0 1 2
验证2、contours, hierarchy = cv2.findContours(fgmask, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
cv2.RETR_LIST:返回所有轮廓线,不建立等级关系
得到结果:图4
此时,hierarchy的值为:
序 后 前 子 父
0 [ 1 -1 -1 -1]
1 [ 2 0 -1 -1]
2 [ 3 1 -1 -1]
3 [ 4 2 -1 -1]
4 [ 5 3 -1 -1]
5 [ 6 4 -1 -1]
6 [ 7 5 -1 -1]
7 [ 8 6 -1 -1]
8 [ 9 7 -1 -1]
9 [-1 8 -1 -1]
可以看出,图4中,找到了全部的轮廓线,“后前子父”中,子父都为-1,他们之间没有任何等级关系。
验证3:contours, hierarchy = cv2.findContours(fgmask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
cv2.RETR_CCOMP :返回所有轮廓,包含两个层级结构
得到结果如图:
此时,hierarchy的值为
序 后 前 子 父
0 [ 2 -1 1 -1]
1 [-1 -1 -1 0]
2 [ 4 0 3 -1]
3 [-1 -1 -1 2]
4 [ 6 2 5 -1]
5 [-1 -1 -1 4]
6 [-1 4 7 -1]
7 [ 8 -1 -1 6]
8 [ 9 7 -1 6]
9 [-1 8 -1 6]
mode= cv2.RETR_CCOMP :包含了两个层级结构,如上图所示,两个层级分别是一个图形的两侧,如[0 1],[2 3]表示的是左下侧回字形框的两个口的内外侧轮廓;[4 5]表示左上侧五角星的内外侧轮廓;右侧的葫芦形最外侧轮廓是6,内侧轮廓分别有3个,从下到上分别是7、8、9,所以它的两层级结构是[6 (7\8\9)];
从hierarchy的值中也可以看出,0的后一个是2, 2的后一个是4, 4的后一个是6,即[0 2 4 6]是最外侧轮廓;0的子轮廓是1,1的父轮廓是0,并且轮廓1没有并列的轮廓,所以前后都是-1;轮廓2和轮廓3,轮廓4和轮廓5同理;轮廓6的子轮廓有7、8、9,这里将排序第一的子轮廓7,放在hierarchy的第二位上,轮廓7的后一位是8,前一位没有,轮廓8的后一位是9,前一位是7,轮廓9的后一位没有,前一位是8,他们三个都是6的子轮廓,可以看到,他们hierarchy的最后一位都是6.
验证4:contours, hierarchy = cv2.findContours(fgmask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cv2.RETR_TREE :返回所有轮廓,建立完整的层次结构
得到结果如图
此时,hierarchy的值:
序 后 前 子 父
0 [ 4 -1 1 -1]
1 [-1 -1 2 0]
2 [-1 -1 3 1]
3 [-1 -1 -1 2]
4 [ 6 0 5 -1]
5 [-1 -1 -1 4]
6 [-1 4 7 -1]
7 [ 8 -1 -1 6]
8 [ 9 7 -1 6]
9 [-1 8 -1 6]
从图中可以看出,cv2.RETR_TREE :返回所有轮廓,建立完整的层次结构。从左下角的回字形开始,从最外到最内,分别是0、1、2、3,左上角的五角星轮廓分别是4、5,右侧的葫芦形,从最外6,到内侧并列的7、8、9. 在hierarchy中完整的形成了树结构,比如0的子轮廓是1,1的子轮廓是2,2的子轮廓是3;0的下一个轮廓是4,4的子轮廓是5;4的下一个轮廓是6,6的子轮廓是7,7的后一个轮廓是8, 8的后一个轮廓是9.
最后附上验证程序:验证程序中比较乱七八糟,懒得改了
import numpy as np
import cv2
fgmask = cv2.imread('D:\cycFeng\Data\\contours.jpg') # 绝对路径
fgmask = cv2.cvtColor(fgmask, cv2.COLOR_BGR2GRAY)
ret, fgmask = cv2.threshold(fgmask, 100, 255, cv2.THRESH_BINARY_INV)
# cv2.imwrite('D:\cycFeng\Data\\contour.jpg',fgmask)
contours, hierarchy = cv2.findContours(fgmask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)#查找轮廓,输入的是二值图像
i = 0
# for c in contours:
# perimeter = cv2.arcLength(c, True) #计算这个轮廓的周长
# if perimeter > 2: #轮廓周长大于188才画出来
# m = i
# x, y, w, h = cv2.boundingRect(c) #找到三角形的上右边界
# cv2.rectangle(fgmask, (x, y), (x + w, y + h), (123, 123, 17), 2)
# # cv2.imshow(str(i), fgmask)
# # cv2.imwrite('D:\cycFeng\Data\\'+str(i)+'.jpg', fgmask)
# print(hierarchy[0][i])
# i += 1
fgmask = cv2.cvtColor(fgmask, cv2.COLOR_GRAY2BGR)
for cnt in contours:
fgmask = cv2.drawContours(fgmask, cnt, -1, (0, 0, 255), 2)
cv2.imwrite('D:\cycFeng\Data\\fgmask.jpg', fgmask)
print(hierarchy[0])
cv2.imshow('fgmask', fgmask)
cv2.waitKey()