在 opencv 上使用 cv::findContours 查找轮廓,经常遇到轮廓层次、点序等问题,这里对相关问题做一个统一总结
轮廓的层次关系
cv: : findContours 返回轮廓层次关系是 [1, N, 4],其中 N 是轮廓数量,4 是表示层次关系的四元组:[下一个,上一个,First_Child,父],元组内存储的是轮廓 Contours 的序号
- RETR_EXTERNAL: 只寻找最高层级的轮廓
- RETR_LIST: 最简单的一种寻找方式,它不建立轮廓间的子属关系,也就是所有轮廓都属于同一层级, hierarchy 中的后两个值 [First Child, Parent] 都为 - 1
- RETR_CCOMP: 它把所有的轮廓只分为 2 个层级,不是外层的就是里层的
- RETR_TREE: 完整建立轮廓的层级从属关系
1 2 3 4 5 6 7 8 9 10 11
| img1 = np.zeros((300, 450,3), 'uint8') img1 = cv2.line(img1,[30,10],[100,10],(0,100,255),4) img1 = cv2.line(img1,[120,15],[430,15],(0,100,255),4) img1 = cv2.rectangle(img1, [20,60 ,400 ,220],(0,100,255),2) img1 = cv2.rectangle(img1, [50 ,100, 340, 150],(0,100,255),2) img1 = cv2.rectangle(img1, [90 ,130 ,140 ,100],(0,100,255),-1) img1 = cv2.rectangle(img1, [260, 155, 100, 50],(0,100,255),-1) img1 = cv2.rectangle(img1, [105 ,150 ,100 ,20],0,-1); img1 = cv2.rectangle(img1, [105 ,190 ,100 ,30],0,-1); show_images([img1])
|
![png]()
1 2 3
| contours,hierarchy=cv2.findContours(img1[...,-1],cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) contours[0].shape,contours[1].shape,len(contours),hierarchy.shape
|
((8, 1, 2), (8, 1, 2), 10, (1, 10, 4))
hierarchy 表示返回轮廓层次关系是 [1, 10, 4],其中 10 是轮廓数量,4 是表示层次关系的四元组:[下一个,上一个,First_Child,父]
1 2 3 4 5 6 7 8 9 10 11
| import copy
def render_image(contours): img2=copy.deepcopy(img1) for idx,contour in enumerate(contours): if idx==1 or idx==3: cv2.putText(img2,str(idx),(contour[0,0,0],contour[0,0,1]+15),cv2.FONT_HERSHEY_SCRIPT_SIMPLEX, 0.5, (255, 0, 0),2) else: cv2.putText(img2,str(idx),(contour[0,0,0],contour[0,0,1]),cv2.FONT_HERSHEY_SCRIPT_SIMPLEX, 0.5, (255, 0, 0),2) show_images([img2])
|
RETR_EXTERNAL
1 2
| contours,hierarchy=cv2.findContours(img1[...,-1],cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) render_image(contours)
|
![png]()
RETR_LIST
1 2
| contours,hierarchy=cv2.findContours(img1[...,-1],cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE) render_image(contours)
|
![png]()
RETR_CCOMP
1 2 3
| contours,hierarchy=cv2.findContours(img1[...,-1],cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE) render_image(contours) hierarchy
|
![png]()
RETR_TREE
1 2 3
| contours,hierarchy=cv2.findContours(img1[...,-1],cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) render_image(contours) hierarchy
|
![png]()
轮廓点点序
contours 表示轮廓的点,对于每个轮廓来说,其点是按什么顺序返回呢?
首先通过以下代码生成模拟图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import math from PIL import Image, ImageDraw from PIL import ImagePath import numpy as np import cv2 import copy
side = 8 xy = [ tuple([ 132.6923076923077, 72.11538461538458 ]), tuple([ 69.23076923076923, 245.19230769230768 ]), tuple([ 207.69230769230768, 133.65384615384613 ]), tuple([ 50.9230769230769229, 133.65384615384613 ]), tuple([ 200.03846153846155, 248.07692307692304 ]) ] image = ImagePath.Path(xy).getbbox() size = list(map(int, map(math.ceil, (300,300)))) img = Image.new("RGB", size, "#000000") img1 = ImageDraw.Draw(img) img1.polygon(xy, fill ="#eeeeff", outline ="blue")
np_img = np.array(img, dtype=np.uint8) np_img=cv2.cvtColor(np_img,cv2.COLOR_RGB2GRAY) show_images([np_img])
|
![opencv轮廓处理相关问题-20250201201205-1]()
然后使用 cv2.findContours 找到轮廓点,接着使用绘制函数逐渐将点绘制出来,每绘制一个点,输出一次图片,结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| contours,hierarchys = cv2.findContours(np_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) print(len(contours),len(contours[0]))
contour=np.array(contours[0]) contour=contour.reshape((-1,contour.shape[1],contour.shape[-1])) contour=cv2.approxPolyDP(contour,2,True) approx_contours=np.reshape(contour,(contour.shape[0],contour.shape[2]))
img=np.zeros((300,300,3),np.uint8) LABEL_COLORMAP = imgviz.label_colormap(value=200) imgs=[] points_colors=[] for i,point in enumerate(approx_contours): x,y=point getcolor=tuple(LABEL_COLORMAP[(i+1)%len(LABEL_COLORMAP)].tolist()) cv2.circle(img,(x,y),10,getcolor,-1) points_colors.append(getcolor) imgs.append(copy.deepcopy(img))
show_images([np.array(points_colors).reshape(1,len(points_colors),3).astype(np.uint8)]) show_images(imgs,2)
|
![opencv轮廓处理相关问题-20250201201206]()
总结:findContours 以最左边的点为起点,并逆时针返回边缘点
极少像素的边缘点
对于找到边缘为极少数的情况,cv2.findContours 返回轮廓点是怎么样的呢?本节进行研究
1 2 3 4 5 6 7 8
| import matplotlib.pyplot as plt def check_findContours(binary): contours,hierarchy=cv2.findContours(binary, cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE) print('原始非0位置:',np.where(binary>0),'\nfindContours查找位置:',np.array(contours).tolist()) cv2.drawContours(binary,contours,-1,(9),1) show_images([binary],set_locator=True,is_grid=True)
|
单个点
1 2 3 4 5
| import numpy as np binary1=np.zeros((10,10),np.uint8) binary1[5,5]=9 check_findContours(binary1)
|
原始非0位置: (array([5]), array([5]))
findContours查找位置: [[[[5, 5]]]]
![png]()
两个同向点
1 2 3
| binary2=np.zeros((10,10),np.uint8) binary2[5:7,6]=9 check_findContours(binary2)
|
原始非0位置: (array([5, 6]), array([6, 6]))
findContours查找位置: [[[[6, 5]], [[6, 6]]]]
![png]()
两个对角点
1 2 3 4
| binary3=np.zeros((10,10),np.uint8) binary3[5,5]=9 binary3[6,6]=9 check_findContours(binary3)
|
原始非0位置: (array([5, 6]), array([5, 6]))
findContours查找位置: [[[[5, 5]], [[6, 6]]]]
![png]()
三个点
1 2 3 4
| binary4=np.zeros((10,10),np.uint8) binary4[5:7,6]=9 binary4[6,7]=9 check_findContours(binary4)
|
原始非0位置: (array([5, 6, 6]), array([6, 6, 7]))
findContours查找位置: [[[[6, 5]], [[6, 6]], [[7, 6]]]]
![png]()
四个点
1 2 3
| binary5=np.zeros((10,10),np.uint8) binary5[5:7,5:7]=9 check_findContours(binary5)
|
原始非0位置: (array([5, 5, 6, 6]), array([5, 6, 5, 6]))
findContours查找位置: [[[[5, 5]], [[5, 6]], [[6, 6]], [[6, 5]]]]
![png]()
结论:cv2.findContours 返回属于目标内部最边缘的点,如果只有一个点,返回点本身