当你完成图像分割之后,图像轮廓检测往往可以进一步筛选你要的目标,OpenCV中可以使用cv2.findContours来得到轮廓。
1. 基本使用方法如下:
import cv2
import numpy as np
img = cv2.imread('black_rect1.png', 0)
ret, th = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
contours, hierarchy = cv2.findContours(th,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
color_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
img = cv2.drawContours(color_img, contours, -1, (255, 0, 0), 2)
# bitwise_not对二值图像取反
cv2.imshow('th_img', cv2.bitwise_not(th))
cv2.imshow('contours_img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
补充:
再不少场景中,找轮廓的最小外接矩形是基本需求,opencv中minAreaRect得到的是一个带有旋转角度信息的rect,可以使用cv2.boxPoints(rect)来将其转为矩形的四个顶点坐标(浮点类型).你也可以使用cv2.polylines来绘制这样的轮廓信息
import cv2
import numpy as np
img = cv2.imread('rotate_rect.png', 0)
ret, th = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
contours, hierarchy = cv2.findContours(th,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
# 利用cv2.boxPoints用来计算minAreaRect所得到的最小面积外接圆的四个顶点,不过得到的是浮点型
rect = cv2.minAreaRect(contours[0])
# 浮点类型转换
box = np.int64(cv2.boxPoints(rect))
# 为了绘制彩色,将图像转为三通道
color_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
# 你可以用polylines来绘制点集数据,第三个参数为绘制时是否将轮廓首尾相连
cv2.polylines(color_img, [box], True, (25, 25, 255), 3)
cv2.imshow('rotate_rect_contour_img', color_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2. 轮廓函数的解释
cv2.findContours(img, mode, method[, contours[, hierarchy[, offset]]])
img:支持8bit单通道的图像,对于非0的像素点会当作1来处理,所以基本上你需要先二值化图像;
mode: 提取轮廓的方式,常用的参数为cv2.RETR_TREE, cv2.RETR_EXTERNAL,前者按照外层轮廓和里层孔来建立等级结构,后者则只求取最外层的轮廓;
method:cv2.CHAIN_APPROX_SIMPLE最常用,压缩水平、垂直、倾斜部分,只会保留最后的一个点,某些情况下,会减少大量不必要的点,比如你需要找矩形,这种方法只会保留四个端点,但对你来说,这样的轮廓就满足需求了;其次cv2.CHAIN_APPROX_NONE则会保留所有找到的轮廓点,会增大消耗;
offset:表示你需要的偏移量,当你是在ROI中进行查找轮廓,那在原图中绘制轮廓信息时,则offset会非常有用,tuple(x_offset, y_offset)
对于hierarchy的信息,其结构形式为[next, previous, first_child, parent],分别代表同级的下一条轮廓,同级的上一条轮廓,当前轮廓的第一个条轮廓,当前轮廓的父轮廓。
参考博客:图像轮廓检索方式详解https://www.cnblogs.com/wojianxin/p/12602490.html
注意findContours参数的变化,在opencv4中,返回值只有contours和hierarchy ,这一点与opencv3中不同。对与轮廓的层级结构,比较难用,虽然可以通过轮廓的层级结构来进行索引你需要的轮廓,不过对于大部分机器视觉应用场景,二值化的结果有时候很难预料,单单通过这种层级关系索引,非常容易出错。所以,只找最外部结构的cv2.RETR_EXTERNAL是不是真香呢?
绘制轮廓:cv2.drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]])
contourIdx:可以绘制指定的某条轮廓,为-1代表绘制全部轮廓
thickness:线条的宽度,为-1时代表填充轮廓内部区域(看你的需要)
3. 轮廓的近似
approx = cv2.approxPolyDP(cnt, epsilon, closed)
epsilon:决定了近似的准确性,代表原始的轮廓与拟合轮廓的最大距离,你可以自己尝试不同的精度来拟合轮廓信息
closed:bool, 轮廓是否闭合
# approxPolyDP
import cv2
import numpy as np
img = cv2.imread('approx_star.png', -1)
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, th = cv2.threshold(gray_img, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(th,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
img_temp = img.copy()
for contour in contours:
# 求轮廓的周长,传递轮廓参数和轮廓是否闭合
epsilon = 0.06 * cv2.arcLength(contour, True)
approx_cnt = cv2.approxPolyDP(contour, epsilon, True)
# 绘制轮廓
cv2.polylines(img_temp, [contour], True, (25, 25, 255), 3)
cv2.polylines(img, [approx_cnt], True, (255, 25, 25), 3)
cv2.imshow('contours', img_temp)
cv2.imshow('approx_lines', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
处理cv2.approxPolyDP()外,你也可以使用cv2.convexHull来求轮廓的近似凸包,其中凸形状内部--任意两点连线都在该形状内部。
cv2.convexHull(cnt, clockwise, returnPoints)
clockwise:默认为False,即轮廓为逆时针方向进行排列;
returnPoints:设置为False会返回与凸包上对应的轮廓的点索引值,设置为True,则会返回凸包上的点坐标集,默认为True
对于opencv-python的提取图像轮廓部分有问题欢迎留言, Have Fun With OpenCV-Python, 下期见。