轮廓识别

相关概念

二值化图像(Threshold)

灰度图像转换为二值图像(也称作二进制图像,每个像素只有两个可能的取值),将灰度值根据阈值的大小设为0或255(把大于阈值的灰度值设为极大值,小于阈值的灰度值设为极小值),也就是将整个图像呈现出只有黑和白的视觉效果,从而实现二值化

二进制图像中有以下两个定义:

  • 前景:用1表示,表示特征区域
  • 背景:用0表示,对象之外的区域

根据阈值选取的不同,二值化的算法分为固定阈值和自适应阈值

当把一个RGB图像转化为灰度图像时,采用加权平均的方法来计算灰度值,一种常用的方法是亮度权重0.299R,0.587G,0.114B,这些加权数值时基于人眼对不同颜色的敏感度而设计,因为人眼对绿色感知更敏感,所以绿色权重高。在opencv中进行转换时,就是应用上述权重

有向轮廓和无向轮廓

两种描述轮廓的方式

有向轮廓:有起点和终点,用于更详细的形状分析,如计算周长,旋转角度

无向轮廓:无方向的封闭曲线,用于简化轮廓处理,如轮廓匹配,逼近

函数介绍

Canny

用于执行边缘检测,更容易理解单通道图像

1
edges = cv2.Canny(image, threshold1, threshold2, apertureSize, L2gradient)
  • image:单通道灰度图像
  • threshold1和threshold2用于控制边缘检测的阈值函数,1设置低阈值,2高阈值。边缘强度高于2的被视为强边缘,介于1和2的被视为弱边缘,其他的被视为非边缘点。通过调整这两个阈值,可以控制检测边缘的数量和质量
  • L2gradient:布尔值,指定计算梯度幅度时是否使用L2范数(欧氏距离)False使用L1范数计算

findContours

opencv库的一个函数,用于在二进制图像中查找对象(白色)的边界轮廓,四个参数:

  • image:二进制图像
  • mode:轮廓的检索模式
    • cv2.RETR_EXTERNAL仅提取最外部轮廓
    • cv2.RETR_LIST提取所有轮廓,不建立父子关系
    • cv2.RETR_CCOMP提取所有轮廓并组织成两层的层次结构
    • cv2.RETR_TREE提取所有轮廓,并构建完整的层次结构
  • method:表示轮廓的逼近方法,用于压缩轮廓的表示
    • cv2.CHAIN_APPROX_NONE存储所有轮廓点,不做压缩
    • cv2.CHAIN_APPROX_SIMPLE仅存储水平,垂直和对角线方向的端点,其余的舍弃
    • cv2.CHAIN_APPROX_TC89_L1和cv2.CHAIN_APPROX_TC89_KCOS应用Teh-Chin链逼近算法,获得更精确的表示
  • offset:可选参数,通常为(0, 0),表示偏移量

两个返回值:

  • contours:一个列表,包含检测到的轮廓

inRange

创建二进制掩码,可以设定RGB的范围来选择感兴趣的图像,在颜色范围内的像素设置为1,不在的设置为0(黑色)

1
cv2.inRange(src, lower_bound, upper_bound)

bitwise_and

执行位与操作,将原始图像与二进制掩码相乘,以保留蓝色像素。用于图像融合,图像分割等任务

1
2
3
4
5
6
7
8
9
# 定义蓝色的颜色范围(BGR值)
lower_blue = np.array([240, 120, 40]) # 蓝色的下限
upper_blue = np.array([255, 255, 110]) # 蓝色的上限

# 创建蓝色的二进制掩码
blue_mask = cv2.inRange(image, lower_blue, upper_blue)

# 将掩码应用到原始图像上
blue_circle = cv2.bitwise_and(image, image, mask=blue_mask)

threshold

将灰度图像二值化

1
2
3
4
5
6
7
8
9
10
11
import cv2
# 读取灰度化图像
image = cv2.imread('your_image.jpg', cv2.IMREAD_GRAYSCALE)
# 自定义阈值
threshold_value = 128
# 使用阈值将图像进行二值化
_, binary_image = cv2.threshold(image, threshold_value, 255, cv2.THRESH_BINARY)
# 显示二值化图像
cv2.imshow('Binary Image', binary_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

四个参数:原始灰度化图像,阈值,最大像素值(通常为255,表示前景),阈值类型(THRESH_BIANARY表示像素值大于阈值的被设置为255,小于的被设为0)

两个返回值:使用的阈值(不关心 用_表示)和二值化图像

还有其他的阈值类型:

  • cv2.THRESH_BINARY_INV:反向二进制阈值,像素值大于阈值的被设置为0,小于的被设置为225
  • cv2.SHRESH_TRUNC:截断阈值。像素值大于阈值的被设为阈值,小于阈值的保持不变
  • cv2.THRESH_TOZERO:像素值小于阈值被设为0,大于阈值的保持不变
  • cv2.THRESH_TOZERO_INV:小于阈值的保持不变,大于阈值的被设为0

contourArea

用于计算轮廓面积,接受一个轮廓对象(通常是findContours返回的轮廓列表中的一个轮廓)作为参数,返回轮廓面积

1
2
3
contours = np.array([[100, 100], [200, 100], [150, 200]])
print(cv2.contourArea(contours))
# 结果为5000.0

arcLength

计算轮廓的周长(弧长),两个参数:

  • curve:轮廓对象
  • closed:布尔值,指定是否封闭

如果轮廓的第一个点和最后一个点不是相同的点,**True会自动添加一条线段来封闭轮廓,False则不会添加额外的线段 **

getStructuringElement

用于创建形态学操作元素,与形态学操作如腐蚀、膨胀、开运算、闭运算一起使用

  • cv2.MORPH_RECT:矩形结构元素

  • cv2.MORPH_CROSS:十字形结构元素

  • cv2.MORPH_ELLIPSE:椭圆形结构元素

  • ksize:指定结构元素大小,通常为奇数

  • anchor:可选参数,指定锚点位置,默认为结构元素中心

该函数返回一个矩阵

morphologyEx

执行形态学操作,应用于二值图像,以改变对象的形状,大小和结构

op形态学操作类型:

  • cv2.MORPH_ERODE:腐蚀操作
  • cv2.MORPH_DILATE:膨胀操作
  • cv2.MORPH_OPEN:开运算(先腐蚀后膨胀)
  • cv2.MORPH_CLOSE:闭运算(先膨胀后腐蚀)
  • cv2.MORPH_GRADIENT:形态学梯度(膨胀图像减去腐蚀图像)
  • cv2.MORPH_TOPHAT:顶帽运算
  • cv2.MORPH_BLACKHAT:黑帽运算
1
2
3
4
5
6
7
8
kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
kernel2 = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 25))

# 执行开运算
opened_image = cv2.morphologyEx(thresholded_image, cv2.MORPH_OPEN, kernel1)

# 执行闭运算
closed_image = cv2.morphologyEx(floodfilled_image, cv2.MORPH_CLOSE, kernel2)

approxPloyDP

用于多边形逼近,简化和近似复杂轮廓的多边形,减少顶点,用于在轮廓检测后对轮廓进行后处理。三个参数:

  • curve:轮廓对象,一个numpy数组
  • epsilon:逼近精度,是一个最大允许距离的参数(原始轮廓和逼近后的多边形之间的最大距离),较大的epsilon会导致更少的点,较小的会产生更多的点
  • closed:布尔值,指定逼近后的多边形是否封闭,若为True,则逼近后的多边形首尾连接
1
2
3
4
#获取轮廓角点坐标
approx = cv2.approxPolyDP(obj,0.02*perimeter,True)
#轮廓角点的数量
CornerNum = len(approx)

rectangle

绘制矩形,可以是实心或仅包含边框,用于标记对象,参数:

  • img:要在其上绘制矩形的图像
  • pt1:矩形的左上角顶点坐标,两个整数值的元组
  • pt2:右下角
  • color
  • thickness
  • lineType
  • shift:像素坐标的小数位数,通常为0

putText

在图像上绘制文本,用于标注

  • img
  • text
  • org:文本的左下角坐标,元组
  • fontFace:字体类型
  • fontScale:文本大小比例因子
  • color、thickness、lineType
  • bottomLeftOrigin:布尔值,指定坐标原点位置,True左下角,False左上角,默认为False

boundingRect

用于计算包含整个轮廓的最小矩形边框

1
x, y, w, h = cv2.boundingRect(points)
  • (x,y)是矩形左上角坐标(遍历轮廓的所有点,找到最小、最大x,y坐标)
  • w,h是矩形的宽度和高度

drawContours

用于在已有图像上绘制轮廓线,参数:

  • image:要在其上绘制轮廓的图像
  • contours:轮廓列表
  • contoursIdx:要绘制轮廓的索引,如果为负数,则绘制所有轮廓
  • color:颜色
  • thickness:轮廓线的粗细,如果为负数,则轮廓被填充
  • lineType:线条类型
  • hierarchy:层次结构
1
2
3
4
5
6
7
8
9
10
11
12
import cv2
import numpy as np
# 创建一个黑色背景的图像
image = np.zeros((400, 400, 3), dtype=np.uint8)
# 创建一个对象的轮廓
contours = [np.array([[100, 100], [200, 100], [150, 200]])]
# 在图像上绘制轮廓
cv2.drawContours(image, contours, -1, (0, 255, 0), 2)
# 显示图像
cv2.imshow('Contours', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

np.zeros用于创建一个零填充的数组,所有元素(像素值)初始化为0,(400, 400, 3)表示尺寸为400x400像素,三维数组(三个颜色通道)代表了一个彩色图像,这里是一个黑色画布。np.unit8指定数组的数据类型为无符号的8位整数,用于表示像素值在0到255的图像

contours = [np.array([[100, 100], [200, 100], [150, 200]])]解释:contours是一个列表,包含一个元素(NumPy数组),即一个轮廓,一组像素的坐标。该轮廓由三个坐标点组成,若为4个坐标,就是根据顺序首尾相连。而且每个轮廓是个独立的numpy数组

完整代码

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
42
43
44
45
46
47
48
49
50
51
52
import cv2
import numpy as np

image = cv2.imread("kh.jpg")

# 定义蓝色的颜色范围(BGR值)
lower_blue = np.array([200, 90, 0]) # 蓝色的下限
upper_blue = np.array([255, 255, 125]) # 蓝色的上限

# 创建蓝色的二进制掩码
blue_mask = cv2.inRange(image, lower_blue, upper_blue)

# 将掩码应用到原始图像上
blue_circle = cv2.bitwise_and(image, image, mask=blue_mask)
gray_image = cv2.cvtColor(blue_circle, cv2.COLOR_BGR2GRAY)
_, thresholded_image = cv2.threshold(gray_image, 20, 255, cv2.THRESH_BINARY_INV)

kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
kernel2 = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 25))

# 执行开运算
opened_image = cv2.morphologyEx(thresholded_image, cv2.MORPH_OPEN, kernel1)

# 执行漫水法
floodfilled_image = opened_image.copy()
cv2.floodFill(floodfilled_image, None, (0, 0), 0)

# 执行闭运算
closed_image = cv2.morphologyEx(floodfilled_image, cv2.MORPH_CLOSE, kernel2)

# 轮廓
contours, hierarchy = cv2.findContours(closed_image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

area = [0] * len(contours) # 创建一个与轮廓数目相同大小的零数组

for i in range(len(hierarchy)):
area[i] = cv2.contourArea(contours[i]) # 计算轮廓的面积

for i in range(len(hierarchy)):
rect = cv2.minAreaRect(contours[i]) # 计算最小外接矩形
box1 = cv2.boxPoints(rect).astype(int) # 获取最小外接矩形的四个端点
cv2.circle(image, (int(rect[0][0]), int(rect[0][1])), 5, (0, 255, 0), -1) # 绘制最小外接矩形的中心点
for j in range(4):
cv2.line(image, tuple(box1[j]), tuple(box1[(j + 1) % 4]), (0, 255, 0), 2) # 绘制最小外接矩形每条边

# cv2.imshow("thresh", thresholded_image)
# cv2.imshow("open_close", opened_image)
# cv2.imshow("flood_image", floodfilled_image)
cv2.imshow("close_img", closed_image)
cv2.imshow("img", image)
cv2.waitKey(0)
cv2.destroyAllWindows()