目录
简介
今天继续学习opencv,今天学习轮廓检测、轮廓的近似
一、轮廓检测
1.查找轮廓的API
image, contours, hierarchy = cv2.findContours(img, mode, method)
参数:img: 需要实现轮廓检测的原图 mode: 轮廓的检索模式,主要有四种方式: cv2.RETR_EXTERNAL:只检测外轮廓,所有子轮廓被忽略 cv2.RETR_LIST:检测的轮廓不建立等级关系,所有轮廓属于同一等级 cv2.RETR_CCOMP:返回所有的轮廓,只建立两个等级的轮廓,一个对象的外轮廓为第1级组织结构, 而对象内中空的轮廓为第2级组织结构,空间中的任何对象的轮廓又是第 1 级组织结构。 -> cv2.RETR_TREE:返回所有的轮廓,建立一个完整的组织结构的轮廓。
method: 轮廓的近似方法,主要有以下两种:
-> cv2.CHAIN_APPROX_NONE:存储所有的轮廓点。 -> cv2.CHAIN_APPROX_SIMPLE:压缩模式,只保留该方向的终点坐标,例如一个矩形轮廓只画4个点来保存轮廓信息。
返回:image: 返回类型的原图 contours:包含图像中所有轮廓的list对象,其中每一个独立的轮廓信息以边界点坐标(x,y)的形式储存在numpy数组中。 hierarchy:轮廓的层次结构,一个包含4个值的数组:[Next, Previous, First Child, Parent] Next:与当前轮廓处于同一层级的下一条轮廓 Previous:与当前轮廓处于同一层级的上一条轮廓 First Child:当前轮廓的第一条子轮廓 Parent:当前轮廓的父轮廓 注意:做轮廓检测前需要将图片读取为二值数据,即像素值只为0和255。
2.代码分析
图像读取与预处理
import cv2
phone = cv2.imread('phone.png') # 读取原图,默认以BGR格式加载
phone_gray = cv2.cvtColor(phone, cv2.COLOR_BGR2GRAY) # 转换为灰度图
cv2.imshow('phone_0', phone_gray) # 显示灰度图
cv2.waitKey(0) # 等待用户按键,0表示无限等待
- 这部分代码首先读取图像,然后将彩色图像转为灰度图(减少计算量,便于后续处理)
cv2.imshow()
用于显示图像,cv2.waitKey(0)
确保图像窗口不会立即关闭
2.1.图像二值化处理
ret, phone_binary = cv2.threshold(phone_gray, 120, 255, cv2.THRESH_BINARY) # 二值化处理
cv2.imshow('phone_binary', phone_binary)
cv2.waitKey(0)
- 二值化是轮廓检测的前提,将灰度图转换为只有黑白两种颜色的图像
- 参数解释:
- 120:阈值,像素值高于此值会被处理
- 255:最大值,满足条件的像素会被设置为此值
cv2.THRESH_BINARY
:二值化类型,大于阈值的设为 255,否则设为 0
2.2轮廓检测
contours = cv2.findContours(phone_binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2] # 检测轮廓
print(len(contours)) # 打印检测到的轮廓数量
cv2.findContours()
是轮廓检测的核心函数- 参数解释:
cv2.RETR_TREE
:轮廓检索模式,检测所有轮廓并建立完整的层次结构cv2.CHAIN_APPROX_NONE
:轮廓近似方法,存储所有轮廓点[-2]
:兼容不同 OpenCV 版本的写法,确保获取到轮廓列表
2.3轮廓绘制
image_copy = phone.copy() # 复制原图,避免在原图上直接绘制
cv2.drawContours(
image=image_copy,
contours=contours,
contourIdx=-1, # -1表示绘制所有轮廓
color=(0,0,255), # 红色(BGR格式)
thickness=2 # 线条粗细
)
cv2.imshow('Contours_show', image_copy)
cv2.waitKey(0)
cv2.drawContours()
用于在图像上绘制轮廓- 这里创建原图副本是为了保留原图,方便后续其他处
2.4轮廓面积计算
area_0 = cv2.contourArea(contours[0]) # 计算第一个轮廓的面积
print(area_0)
area_1 = cv2.contourArea(contours[1]) # 计算第二个轮廓的面积
print(area_1)
cv2.contourArea()
用于计算轮廓所包围的面积- 面积单位是像素,值越大表示轮廓包围的区域越大
2.5轮廓周长计算
length = cv2.arcLength(contours[0], closed=True) # 计算第一个轮廓的周长
print(length)
cv2.arcLength()
用于计算轮廓的周长closed=True
表示轮廓是封闭的
2.6筛选特定面积的轮廓
a_list = []
for i in contours:
if cv2.contourArea(i) > 10000: # 筛选面积大于10000的轮廓
a_list.append(i)
image_copy = phone.copy()
image_copy = cv2.drawContours(image_copy, a_list, -1, (0, 255, 0), 3) # 用绿色绘制筛选后的轮廓
cv2.imshow('Contours_show_10000', image_copy)
cv2.waitKey(0)
- 这部分实现了根据面积筛选轮廓的功能
- 只保留面积大于 10000 的轮廓,并用绿色绘制,便于观察较大的物体轮廓
2.7查找最大面积的轮廓
# 按面积降序排序,取第一个(面积最大的轮廓)
sortcnt = sorted(contours, key=cv2.contourArea, reverse=True)[0]
image_contours = cv2.drawContours(phone.copy(), [sortcnt], -1, (0, 0, 255), 3)
cv2.imshow('image_contours', image_contours)
cv2.waitKey(0)
- 使用
sorted()
函数和cv2.contourArea
作为排序键,找到面积最大的轮廓 - 这种方法常用于定位图像中最主要的物体
2.8绘制轮廓的外接圆
cnt = contours[6] # 选择第7个轮廓(索引从0开始)
(x, y), r = cv2.minEnclosingCircle(cnt) # 计算最小外接圆
phone_circle = cv2.circle(phone, (int(x), int(y)), int(r), (0, 255, 0), 2) # 绘制外接圆
cv2.imshow('phone_circle', phone_circle)
cv2.waitKey(0)
cv2.minEnclosingCircle()
计算能包围轮廓的最小圆- 返回值包括圆心坐标 (x,y) 和半径 r
cv2.circle()
用于绘制圆形,这里用绿色线条
2.9绘制轮廓的外接矩形
x, y, w, h = cv2.boundingRect(cnt) # 计算最小外接矩形
phone_rectangle = cv2.rectangle(phone, (x, y), (x + w, y + h), (255, 0, 0), 2) # 绘制矩形
cv2.imshow('phone_rectangle', phone_rectangle)
cv2.waitKey(0)
cv2.boundingRect()
计算能包围轮廓的最小矩形(轴对齐矩形)- 返回值包括矩形左上角坐标 (x,y)、宽度 w 和高度 h
cv2.rectangle()
用于绘制矩形,这里用蓝色线条
二、轮廓的近似
approx = cv2.approxPolyDP(curve, epsilon, closed)
参数说明: curve:输入轮廓。 epsilon:近似精度,即两个轮廓之间最大的欧式距离。该参数越小,得到的近似结果越接近实际轮廓;反之,得到的近似结果会更加粗略。 closed:布尔类型的参数,表示是否封闭轮廓。如果是 True,表示输入轮廓是封闭的,近似结果也会是封闭的;否则表示输入轮廓不是封闭的,近似结果也 返回值:approx:近似结果,是一个ndarray数组,为1个近似后的轮廓,包含了被近似出来的轮廓上的点的坐标
图像读取与预处理
phone = cv2.imread('phone.png') # 读取原图
phone_gray = cv2.cvtColor(phone,cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret,phone_thresh = cv2.threshold(phone_gray, 120, 255,cv2.THRESH_BINARY) # 二值化处理
- 将彩色图像转为灰度图,简化图像处理
- 二值化处理:将灰度图转换为只有黑白两种颜色的图像,阈值设为 120,大于 120 的像素变为 255(白色),小于等于 120 的变为 0(黑色)
轮廓检测
contours=cv2.findContours(phone_thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)[-2]
- 使用
cv2.findContours
函数检测轮廓 - 参数
cv2.RETR_TREE
表示检测所有轮廓并建立完整的层次结构 - 参数
cv2.CHAIN_APPROX_NONE
表示存储所有轮廓点 [-2]
是为了兼容不同 OpenCV 版本,确保获取到轮廓列表
轮廓近似处理
# 设置近似精度,epsilon值越小,近似结果越接近原始轮廓
epsilon = 0.002 * cv2.arcLength(contours[0], True)
# 对轮廓进行近似
approx = cv2.approxPolyDP(contours[0], epsilon, True)
cv2.arcLength
计算轮廓周长,True
表示轮廓是封闭的epsilon
是近似精度,这里设置为轮廓周长的 0.002 倍cv2.approxPolyDP
函数实现轮廓近似,将轮廓用更少的点来表示,保留主要形状
结果对比与显示
# 打印原始轮廓和近似轮廓的点数量
print(contours[0].shape)
print(approx.shape)
# 绘制近似后的轮廓
phone_new = phone.copy()
image_contours = cv2.drawContours(phone_new, [approx], contourIdx=-1, color=(0,255,0), thickness=3)
# 显示原图和处理后的图像
cv2.imshow('phone', phone)
cv2.waitKey(0)
cv2.imshow('image_contours', image_contours)
cv2.waitKey(0)
- 通过打印形状可以看到,近似后的轮廓点数量明显减少
- 用绿色线条绘制近似后的轮廓,并与原图进行对比显示
cv2.waitKey(0)
表示等待用户按键后再继续执行
通过调整epsilon
的值,可以控制近似程度,值越大,轮廓简化越明显,保留的点越少;值越小,越接近原始轮廓。
三、模板匹配
模板匹配是一种用于查找与模板图像(补丁)匹配(相似)的图像区域的技术
为了识别匹配区域,我们必须通过滑动来将模板图像与源图像进行比较
一次移动一个像素(从左到右,从上到下)。在每个位置,都会计算一个度量(度量计算公式),以便它表示该位置的匹配“好”或“坏”程度
cv2.matchTemplate(image, templ, method, result=None, mask=None)
image :待搜索图像
templ:模板图像
method:计算匹配程度的方法,可以有:
TM_SQDIFF 平方差匹配法,该方法采用平方差进行匹配;匹配越好,值越小;匹配越差,值越大。
TM_CCORR 相关匹配法,该方法采用乘积操作;数值越大表明匹配程度越好。
TM_CCOEFF 相关系数匹配法,数值越大表明匹配程度越好。
TM_SQDIFF_NORMED 归一化平方差匹配法,匹配越好,值越小;匹配越差,值越大。 TM_CCORR_NORMED 归一化相关匹配法,数值越大表明匹配程度越好。 TM_CCOEFF_NORMED 归一化相关系数匹配法,数值越大表明匹配程度越好。
图像读取与显示
kele = cv2.imread('kele.png')
template = cv2.imread('template.png')
cv2.imshow(winname='kele', mat=kele)
cv2.imshow(winname='template', mat=template)
cv2.waitKey(0)
cv2.imread
:从本地加载图像,kele
存大图像、template
存模板小图像,默认按 BGR 色彩空间 读入(和日常 RGB 有区别,OpenCV 历史原因导致 )。cv2.imshow
:创建窗口显示图像,第一个参数是窗口名称(winname
),第二个是要显示的图像数据(mat
)。cv2.waitKey(0)
:暂停程序,等待用户按下任意键再继续执行,0
表示 “无限等待”,保证图像窗口能停留让我们看到内容。
获取模板尺寸与执行模板匹配
h, w = template.shape[:2]
res = cv2.matchTemplate(kele, template, cv2.TM_CCOEFF_NORMED)
template.shape[:2]
:获取模板图像的 高度(h)、宽度(w) ,shape
返回 (高,宽,通道数) ,[:2]
取前两个值,后续标记匹配区域会用到尺寸。cv2.matchTemplate(...)
:执行模板匹配核心操作!把template
当 “模板”,在kele
里逐像素 / 逐区域滑动比较 。cv2.TM_CCOEFF_NORMED
是选的 归一化相关系数匹配法 ,结果存在res
里:res
是个二维矩阵,每个元素值表示对应位置与模板的匹配程度(越接近 1 ,匹配越好 )。
解析匹配结果,标记目标区域
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
kele_template = cv2.rectangle(kele, top_left, bottom_right, color=(0, 255, 0), thickness=2)
cv2.minMaxLoc(res)
:从res
(匹配结果矩阵 )里找 最小值(min_val
)、最大值(max_val
),以及它们对应的坐标(min_loc
、max_loc
) 。因为用了TM_CCOEFF_NORMED
,值越大匹配越好,所以关注max_loc
(最佳匹配区域的左上角坐标 )。bottom_right = (top_left[0] + w, top_left[1] + h)
:根据模板宽高,算出匹配区域 右下角坐标 ,这样就能确定一个矩形范围。cv2.rectangle(...)
:在kele
图像上画矩形!参数分别是:要画的图像(kele
)、左上角坐标(top_left
)、右下角坐标(bottom_right
)、矩形颜色((0, 255, 0)
,即绿色,BGR 格式 )、线条粗细(thickness=2
),结果存在kele_template
里。
显示最终结果
cv2.imshow(winname='kele_template', mat=kele_template)
cv2.waitKey(0)
- 用
cv2.imshow
显示标记了匹配区域的图像kele_template
,再用cv2.waitKey(0)
等待按键,方便我们查看匹配效果,程序最后才结束。