1. 轮廓的概念
轮廓是目标物体或者区域在图像外部的边界线,通常由一系列像素点相连组成,这些像素点共同构成了一个封闭的形状,这样形状就是轮廓。
轮廓与边缘不同:
轮廓是连续的,边缘可以连续也可以离散
轮廓是完整的,边缘可以是完成的也可以不完整
轮廓主要分析物体的形态,比如计算物体的周长与面积;边缘作为图像特征使用,比如区分脸和手。

代码运行步骤:
图片输入→灰度化→二值化→寻找轮廓→绘制轮廓→图片输出
1.1 寻找轮廓
# 4. 寻找轮廓
# 返回值1:所有轮廓的点坐标,是一个list列表
# 返回值2:轮廓的层级关系
contours, hierarchy = cv2.findContours(
image = image_np_thresh, # 二值化之后的图像
mode = cv2.RETR_EXTERNAL, # 默认的轮廓查找方式
method = cv2.CHAIN_APPROX_SIMPLE # 默认的轮廓近似办法
)
●contours 轮廓s
tuple类型,所有轮廓的点(每个元素是Numpy数组),可以通过[n-1]取出第n个轮廓的数据
●hierarchy 轮廓关系
如果没有检测到轮廓,则返回的数组为空,通常不会出现。
对于第n个轮廓:
○hierarchy[n][0]表示当前轮廓的后一条轮廓
○hierarchy[n][1]表示当前轮廓的前一条轮廓
○hierarchy[n][2]表示当前轮廓的第一个子轮廓
○hierarchy[n][3]表示当前轮廓的父轮廓
如果出现-1表示没有。
此参数较少使用,通常用于处理复杂画面效果。
●image 输入图像
要求8位的二值化图像,前景色白色,背景色黑色
●mode 轮廓的查找方式
○RETR_EXTERNAL 查找最外层轮廓(不查找内层轮廓)
在hierarchy层级结构中,每一个轮廓只有前后轮廓的索引,没有父轮廓与子轮 廓的索引。
○RETR_LIST 查找所有轮廓(包括外层和内层轮廓)
在hierarchy层级结构中,每一个轮廓只有前后轮廓的索引,没有父轮廓与子轮 廓的索引。
○RETR_CCOMP 查找所有轮廓(包括外层和内层轮廓)
在hierarchy层级结构中,每一个轮廓只有前后轮廓的索引,有父轮廓与子轮 廓的索引,轮廓为两层结构。
○RETR_TREE (包括外层和内层轮廓)
在hierarchy层级结构中,每一个轮廓只有前后轮廓的索引,有父轮廓与子轮 廓的索引,轮廓为树状结构。
●method 轮廓的近似办法
○CHAIN_APPROX_NONE 存储所有轮廓点
○CHAIN_APPROX_SIMPLE 只存储有用的点
比如轮廓中的直线,则只存储起点和终点;比如轮廓是四边形,则只存储四个顶点。
○CHAIN_APPROX_TC89_L1 使用Teh_Chin链进行轮廓逼近
精度高(优化后的点更少),使用少。

1.2 绘制轮廓
使用绘制轮廓的方式把上一步的寻找的轮廓点连接在一起进行绘制,这一步绘制的是前景轮廓。

- contourIdx 轮廓编号
-1表示绘制所有轮廓
- color 轮廓颜色
- thickness 绘制的线宽
1.3 轮廓检测算法 Suzuki
内容复杂,了解即可
2 代码实践
原图3.jpg

python
import cv2
if __name__ == '__main__':
# 1. 图片输入
path = '3.jpg'
image_np = cv2.imread(path)
# 2. 灰度化
image_np_gray = cv2.cvtColor(
image_np,
cv2.COLOR_BGR2GRAY
)
# 3. 二值化
ret, image_np_thresh = cv2.threshold(
image_np_gray,
127,
255,
cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
print(ret)
# 4. 寻找轮廓
contours, hierarchy = cv2.findContours(
image_np_thresh, # 二值化之后的图像
cv2.RETR_EXTERNAL, # 查找方式
cv2.CHAIN_APPROX_SIMPLE # 近似办法
)
print(len(contours))
for i in contours:
print(i.shape)
"""
cv2.findContours(): 查找图像中的轮廓
参数详解:
image_np_thresh: 二值化图像,必须是8位单通道图像
cv2.RETR_EXTERNAL: 轮廓检索模式,只检测最外层轮廓
cv2.CHAIN_APPROX_SIMPLE: 轮廓近似方法,压缩水平、垂直和对角线段,只保留它们的端点
返回值:
contours: 找到的轮廓列表,每个轮廓是一个点集
hierarchy: 轮廓的层次信息(此代码中未使用)
print(len(contours)): 打印找到的轮廓数量
for i in contours: print(i.shape): 遍历所有轮廓并打印每个轮廓的形状(即包含多少个点)
"""
# 5. 绘制轮廓
image_np = cv2.drawContours(
image_np, # 在哪个图上绘制
contours, # 轮廓数据列表
contourIdx=-1, # 绘制轮廓的id,-1表示全绘制
color=(0, 0, 255), # 绘制的颜色
thickness=2 # 线宽
)
"""
cv2.drawContours(): 在图像上绘制轮廓
参数详解:
image_np: 要绘制轮廓的目标图像(会在原图上直接修改)
contours: 要绘制的轮廓列表
contourIdx=-1: 指定要绘制哪个轮廓,-1表示绘制所有轮廓
color=(0, 0, 255): 轮廓颜色,BGR格式(这里是红色)
thickness=2: 轮廓线宽度
"""
# 6. 图片输出
cv2.imshow('image_np', image_np)
cv2.waitKey(0)
cv2.imwrite('image.png', image_np)
# 读取图像 → 2. 转换为灰度图 → 3. 二值化处理 → 4. 查找轮廓 → 5. 绘制轮廓 → 6. 显示结果
运行后图片:image.png

练习:绘制图像的所有轮廓,并使用不同的颜色标识。
图片:3.jpg

python
"""绘制图像的所有轮廓,并使用不同的颜色标识。"""
import cv2
import numpy as np
if __name__ == '__main__':
# 1.图片输入
path = '3.jpg' # 图像文件路径
image_np = cv2.imread(path) # 读取图像为NumPy数组(BGR格式)
# # 2. 灰度化
# image_np_gray = cv2.cvtColor(
# image_np,
# cv2.COLOR_BGR2GRAY
# )
# cv2.imshow('image_np_gray', image_np_gray)
# # 3. 二值化
# ret, image_np_thresh = cv2.threshold(
# image_np_gray,
# 192, # 191/192为青色和粉色的临界值
# 255,
# cv2.THRESH_BINARY_INV
# )
# 使用灰度化、二值化后,颜色浅的 无法选中
# 2.HSV空间转换
hsv_image_np = cv2.cvtColor(image_np, cv2.COLOR_BGR2HSV) # BGR转HSV
# 3. 制作掩膜
# 定义绿色范围/背景图掩膜
green_low = np.array([35, 43, 46]) # 红色下限(Hmin, Smin, Vmin)
green_high = np.array([77, 255, 255]) # 红色上限(Hmax, Smax, Vmax)
mask1 = cv2.inRange( # 创建掩膜
hsv_image_np, # 基于哪个图像, 输入HSV图像
green_low, # 颜色下限
green_high # 颜色上限
)
# 合并掩膜
mask_image_np = cv2.bitwise_or(mask1, mask1) # 红
cv2.imshow('mask_image_np4', mask_image_np)
# 4.反阈值二值化取到前景图片
ret, image_np_thresh = cv2.threshold(
mask_image_np, # 输入的灰度图像
127, # 阈值(127),但使用OTSU方法时会自动计算最佳阈值
255, # 最大值(255),当像素值超过阈值时赋予的值
cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU
)
cv2.imshow('image_np_thresh', image_np_thresh)
# 5. 寻找轮廓
contours, hierarchy = cv2.findContours(
image_np_thresh, # 二值化之后的图像
cv2.RETR_LIST, # 查找方式:查找所有轮廓(包括外层和内层轮廓)
cv2.CHAIN_APPROX_SIMPLE # 近似办法
)
print(len(contours))
for i in contours:
print(i.shape)
# 定义不同颜色用于绘制不同轮廓
colors = [
(0, 0, 0), # 黑色
(255, 255, 0), # 青色
(255, 0, 0), # 蓝色
(255, 0, 255), # 粉色
]
# 遍历所有找到的轮廓
for i, cnt in enumerate(contours):
# enumerate()函数返回的是(index, value)元组对,所以不能直接将循环变量用于索引
# 计算当前轮廓的面积,过滤掉太小的轮廓(可能是噪声)
area = cv2.contourArea(cnt)
if area < 197: # 忽略面积小于100像素的轮廓
continue
# 选择颜色(循环使用预定义的颜色)
# color = colors[i] # 当i≥4时会报IndexError
color = colors[i % len(colors)] # 0%4=0...5%4=1(循环)
# 6. 绘制轮廓
image_np = cv2.drawContours(
image_np, # 在哪个图上绘制
contours, # 轮廓数据列表
contourIdx=i, # 绘制轮廓的id,-1表示全绘制,从0开始代表下标
color=color, # 使用选择的颜色
thickness=2 # 线宽
)
# 6. 图片输出
cv2.imshow('image_np', image_np)
cv2.waitKey(0)
cv2.imwrite('123.png', image_np)
运行结果:123.png
