图像预处理-绘制图像轮廓与凸包
一.绘制图像轮廓
1.1 轮廓
相对于边缘,轮廓是连续闭合的,边缘不一定连续。
边缘检测主要用于判断图像中的物件类型,轮廓则获取物件形态(比如矩形宽高)
1.2 寻找轮廓
寻找轮廓需要将图像做一个二值化处理,要绘制轮廓的部分置为白色,其余部分置为黑色。
一个白色像素相邻(上下左右及两条对角线)位置有黑色像素存在或者一个黑色像素相邻(上下左右及两条对角线)位置有白色像素存在时,那么该像素点就会被认定为边界像素点,轮廓就是有无数个这样的边界点组成的。
contours,hierarchy = cv2.findContours(image,mode,method)
- 返回值:[ 轮廓点坐标 ] 和 [ 层级关系 ]。
- contours:表示获取到的轮廓点的列表,是一个元组。检测到有多少个轮廓,该列表就有多少子列表,每一个子列表都代表了一个轮廓中所有点的坐标。
- hierarchy:表示轮廓之间的关系。对于第i条轮廓,hierarchy[i][0], hierarchy[i][1] , hierarchy[i][2] , hierarchy[i][3]分别表示其后一条轮廓、前一条轮廓、(同层次的第一个)子轮廓、父轮廓的索引(如果没有相应的轮廓,则对应位置为-1)。该参数的使用情况会比较少。
- image:表示输入的二值化图像。
- mode:表示轮廓的检索模式。
- method:轮廓的表示方法。
1.2.1 mode参数
- RETR_EXTERNAL
只查找最外层的轮廓。并且在hierarchy里的轮廓关系中,每一个轮廓只有前一条轮廓与后一条轮廓的索引,而没有父轮廓与子轮廓的索引。
- RETR_LIST
查找所有轮廓,每一个轮廓只有前一条轮廓与后一条轮廓的索引,而没有父轮廓与子轮廓的索引。
- RETR_CCOMP
查找所有轮廓,列出所有的轮廓。并且在hierarchy里的轮廓关系中,会像下图一样显示六条数据:
- RETR_TREE
表示列出所有的轮廓。并且在hierarchy里的轮廓关系中,轮廓会按照树的方式显示,其中最外层的轮廓作为树根,其子轮廓是一个个的树枝。(这个方法作了解)
1.2.2 method参数
轮廓存储方法:
- CHAIN_APPROX_NONE 表示将所有的轮廓点都进行存储
- CHAIN_APPROX_SIMPLE 表示只存储有用的点,比如直线只存储起点和终点,四边形只存储四个顶点,默认使用这个方法
1.2.3 绘制轮廓
cv2.drawContours(image, contours, contourIdx, color, thickness)
- image:原始图像,一般为单通道或三通道的 numpy 数组。
- contours:一个元组包含多个轮廓的列表(有几个轮廓元组就有几个元素),每个轮廓本身也是一个由点坐标构成的二维数组(numpy数组)。
- contourIdx:要绘制的轮廓索引。如果设为 -1,则会绘制所有轮廓。根据索引找到轮廓点绘制出来。默认是-1。
- color:绘制轮廓的颜色,可以是 BGR 值或者是灰度值(对于灰度图像)。
- thickness:轮廓线的宽度,如果是正数,则画实线;如果是负数,则填充轮廓内的区域。
import cv2 as cvimg = cv.imread('../images/pig.png')
img1 = cv.imread('../images/pig.png')# 灰度化
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)# 二值化
ret, binary = cv.threshold(gray, 127, 255, cv.THRESH_OTSU)# 只绘制外部轮廓
contours, hierarchy = cv.findContours(binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
cv.drawContours(img, contours, -1, (0, 255, 0), 3)# 绘制内外
contours1, hierarchy1 = cv.findContours(binary, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE)
cv.drawContours(img1, contours1, -1, (0, 255, 0), 3)cv.imshow('img', img)
cv.imshow('img1', img1)
cv.imshow('binary', binary)
cv.waitKey(0)
cv.destroyAllWindows()
二.凸包特征检测
凸包其实就是将一张图片中物体的最外层的点连接起来构成的凸多边形,它能包含物体中所有的内容。存在凸包,那么这个点集里面的所有点要么在凸包上,要么在凸包内。凸包点则是最外层的点。
2.1 获取凸包点的方法
- 穷举法
将集中的点进行随机两两配对,并进行连线,对于每条直线,检查其余所有的点是否处于该直线的同一侧,如果是,那么说明构成该直线的两个点就是凸包点,其余的线依次进行计算,从而获取所有的凸包点。
- QuickHull法
所有点放在二维坐标系中,找到横坐标最小和最大的两个点并连线。此时整个点集被分为两部分,直线上为上包,直线下为下包。
找到上包中的点距离该直线最远的点,连线并寻找之前连的左直线左侧的点和右直线右侧的点,然后重复本步骤,直到找不到为止。对下包也是这样操作。
上图就是 QuickHull 的寻找过程,顺序已经在图中标出。
2.2 获取凸包点函数
cv2.convexHull(points)
- points:输入参数,图像的轮廓(需要先获取,若有多个轮廓要调用for循环获取contours元组里的所有坐标)
2.3 绘制凸包
cv2.polylines(image, pts, isClosed, color, thickness=1)
- pts:一个二维 numpy 数组,每个元素是一维数组,代表一个多边形的一系列顶点坐标(凸包点)。
- isClosed:布尔值,表示是否闭合多边形,如果为 True,会在最后一个顶点和第一个顶点间自动添加一条线段,形成封闭的多边形。
- color:线条颜色,可以是一个三元组或四元组,分别对应BGR或BGRA通道的颜色值,或者是灰度图像的一个整数值。
- thickness(可选):线条宽度,默认值为1。
import cv2 as cvimg = cv.imread('../images/tu.png')
img1 = cv.imread('../images/tu.png')# 灰度化
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)# 二值化
ret, binary = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)# 轮廓发现
contours, hierarchy = cv.findContours(binary, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)# 获取凸包
# 这里是用一个列表包装了所有坐标,所以后面的hull传参不需要加[]
hull = [cv.convexHull(c) for c in contours]
# 两种方式都可以,这种需要传hull套上[],毕竟只能传一个参数进去
hull1 = cv.convexHull(contours[0])# 绘制轮廓
cv.drawContours(img, hull, -1, (0, 255, 0), 2)
cv.drawContours(img1, [hull1], -1, (0, 0, 255), 2)cv.imshow('img', img)
cv.imshow('img1', img1)
cv.waitKey(0)
cv.destroyAllWindows()