轮廓检测
边缘检测虽然能够检测出边缘,但边缘是不连续的,检测到的边缘并不是一个整体。图像
轮廓是指将边缘连接起来形成的一个整体,用于后续的计算。OpenCV 提供了查找图像轮廓的函数 cv2.findContours(),该函数能够查找图像内的轮廓信
息,而函数 cv2.drawContours()能够将轮廓绘制出来。
API
contours, hierarchy = cv2.findContours( image, mode, method)
- 式中的返回值为:
- contours:返回的轮廓
- 该返回值返回的是一组轮廓信息,每个轮廓都是由若干个点所构成的。例如,contours[i]是第 i 个轮廓(下标从 0 开始),contours[i][j]是第 i 个轮廓内的第 j 个点。
- 返回值 contours 的 type 属性是 list 类型,list 的每个元素都是图像的一个轮廓,用 Numpy中的 ndarray 结构表示。
- 轮廓的个数:len(contours)
- 某个轮廓中包含的点数:len(contours[i])
- hierarchy:图像的拓扑信息(轮廓层次)
- 图像内的轮廓可能位于不同的位置。比如,一个轮廓在另一个轮廓的内部。在这种情况下,我们将外部的轮廓称为父轮廓,内部的轮廓称为子轮廓。按照上述关系分类,一幅图像中所有轮廓之间就建立了父子关系。根据轮廓之间的关系,就能够确定一个轮廓与其他轮廓是如何连接的。比如,确定一个轮廓是某个轮廓的子轮廓,或者是某个轮廓的父轮廓。上述关系被称为层次(组织结构),返回值 hierarchy 就包含上述层次关系。每个轮廓 contours[i]对应 4 个元素来说明当前轮廓的层次关系。其形式为:
[Next,Previous,First_Child,Parent]
* Next:后一个轮廓的索引编号
* Previous:前一个轮廓的索引编号
* First_Child:第 1 个子轮廓的索引编号
* Parent:父轮廓的索引编号
如果上述各个参数所对应的关系为空时,也就是没有对应的关系时,则将该参数所对应的值设为“-1”.使用不同的 mode,得到轮廓的编号是不一样的,得到的 hierarchy 也不一样。
- 式中的参数为:
- image:原始图像。必须是 8 位单通道二值图像。一般情况下,都是将图像处理为二值图像后,再将其作为 image 参数使用的。所有非零值被处理为 1,所有零值保持不变。也就是说灰度图像会被自动处理为二值图像。在实际操作时,可以根据需要,预先使用阈值处理等函数将待查找轮廓的图像处理为二值图像。
- mode:轮廓的提取方式
- cv2.RETR_EXTERNAL:只检测外轮廓
- cv2.RETR_LIST:对检测到的轮廓不建立等级关系
- cv2.RETR_CCOMP:检索所有轮廓并将它们组织成两级层次结构。上面的一层为外边界,下面的一层为内孔的边界。如果内孔内还有一个连通物体,那么这个物体的边界仍然位于顶层。
- cv2.RETR_TREE:建立一个等级树结构的轮廓
- method:轮廓的近似方法,即:如何表达轮廓
- cv2.CHAIN_APPROX_NONE:存储所有的轮廓点,相邻两个点的像素位置差不超过 1
- cv2.CHAIN_APPROX_SIMPLE:压缩水平方向、垂直方向、对角线方向的元素,只保留该方向的终点坐标。例如,在极端的情况下,一个矩形只需要用 4 个点来保存轮廓信息
- cv2.CHAIN_APPROX_TC89_L1:使用 teh-Chinl chain 近似算法的一种风格
- cv2.CHAIN_APPROX_TC89_KCOS:使用 teh-Chinl chain 近似算法的一种风格
使用函数 cv2.findContours()查找图像轮廓时,需要注意以下问题:
* 待处理的源图像必须是灰度二值图。因此,在通常情况下,都要预先对图像进行阈值分割或者边缘检测处理,得到满意的二值图像后再将其作为参数使用。
* 在 OpenCV 中,都是从黑色背景中查找白色对象。因此,对象必须是白色的,背景必须是黑色的。
image=cv2.drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]] )
- 函数的返回值为 image,表示目标图像,即绘制了边缘的原始图像
- image:待绘制轮廓的图像。需要注意,函数 cv2.drawContours()会在图像 image 上直接绘制轮廓。也就是说,在函数执行完以后,image 不再是原始图像,而是包含了轮廓的图像。因此,如果图像 image 还有其他用途的话,则需要预先复制一份,将该副本图像传递给函数 cv2.drawContours()使用
- contours:需要绘制的轮廓。该参数的类型与函数 cv2.findContours()的输出 contours 相同,都是 list 类型
- contourIdx:需要绘制的边缘索引,告诉函数 cv2.drawContours()要绘制某一条轮廓还是全部轮廓。如果该参数是一个整数或者为零,则表示绘制对应索引号的轮廓;如果该值为负数(通常为“-1”),则表示绘制全部轮廓
- color:绘制的颜色,用 BGR 格式表示
- thickness:可选参数,表示绘制轮廓时所用画笔的粗细。如将该值设置为“-1”,则表示要绘制实心轮廓
- lineType:可选参数,表示绘制轮廓时所用的线型
- hierarchy:对应函数 cv2.findContours()所输出的层次信息
- maxLevel:控制所绘制的轮廓层次的深度。如果值为 0,表示仅仅绘制第 0 层的轮廓;如果值为其他的非零正数,表示绘制最高层及以下的相同数量层级的轮廓
- offset:偏移参数。该参数使轮廓偏移到不同的位置展示出来
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
def cvshow(name, ndarray):
img = cv2.imshow(name, ndarray)
cv2.waitKey(0)
cv2.destroyAllWindows()
绘制轮廓
src = cv2.imread("src.png")
gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
cvshow("src", src)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
src_copy = src.copy()
one = cv2.drawContours(src_copy, contours[2], -1, (0, 0, 255), 2)
cvshow("one", one)
通过split()方法对通道进行分割后,就得到了每个通道,此时单个通道就被转化为了二维数据,对应的就成了灰度图,所以你单独展示每一个通道时展示不出红色,蓝色,绿色的,展示出的都是灰度图。必须保持三个通道作为一个整体才可以单独显示出某个原色,就比如赋值操作,对其他两个通道赋值为0,就可以显示出剩下的一种原色,因为此时三个通道是作为一个整体的。
轮廓计算
con = contours[1]
area = cv2.contourArea(con)
length = cv2.arcLength(con, True)
print(area, length)
6767.5 395.8061298131943
轮廓拟合
矩形边界
retval = cv2.boundingRect(array)
- 返回值 retval 表示返回的矩形边界的左上角顶点的坐标值及矩形边界的宽度和高度。
- 参数 array 是灰度图像或轮廓。
该函数还可以是具有 4 个返回值的形式:
x,y,w,h = cv2.boundingRect( array )
- 矩形边界左上角顶点的 x 坐标。
- 矩形边界左上角顶点的 y 坐标。
- 矩形边界的 x 方向的长度。
- 矩形边界的 y 方向的长度。
retval = cv2.boundingRect(con)
print(retval)
cv2.rectangle(src_copy, (retval[0], retval[1]), (retval[0]+retval[2], retval[1]+retval[3]), (0, 255, 0), 2)
cvshow("res", src_copy)
(83, 273, 112, 121)