在计算机视觉领域,OpenCV 是一款功能强大且应用广泛的开源库,它提供了丰富的 API,支持图像读取、预处理、特征检测等多种操作。本文将结合实际代码案例,详细讲解如何使用 OpenCV 实现轮廓检测、轮廓近似、模板匹配等常用功能,同时介绍如何通过argparse
模块配置命令行参数,让程序更具灵活性和可扩展性。
一、OpenCV 图像处理核心功能实战
(一)图像轮廓检测与筛选
轮廓是图像中物体边界的重要表示,通过检测轮廓,我们可以获取物体的形状、大小等关键信息。以下代码实现了从图像读取、预处理到轮廓检测、筛选与绘制的完整流程。
import cv2
# 1. 读取图像并进行预处理
phone = cv2.imread('lunkuo.png') # 替换为你的图片路径
# 转为灰度图,减少计算量并为后续二值化做准备
gray = cv2.cvtColor(phone, cv2.COLOR_BGR2GRAY)
# 二值化处理:将灰度图转为黑白二值图,突出物体轮廓
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 2. 检测轮廓(兼容OpenCV 3.x和4.x版本)
img, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 3. 计算轮廓的面积与周长
# 计算前两个轮廓的面积
area_0 = cv2.contourArea(contours[0])
print(f"第一个轮廓的面积:{area_0}")
area_1 = cv2.contourArea(contours[1])
print(f"第二个轮廓的面积:{area_1}")
# 计算第一个轮廓的周长(closed=True表示轮廓是闭合的)
length = cv2.arcLength(contours[0], closed=True)
print(f"第一个轮廓的周长:{length}")
# 4. 筛选面积大于10000的轮廓(去除小噪声轮廓)
filtered_contours = []
for contour in contours:
if cv2.contourArea(contour) > 10000:
filtered_contours.append(contour)
# 绘制筛选后的轮廓(绿色,线宽3)
image_copy = phone.copy()
cv2.drawContours(image=image_copy, contours=filtered_contours,
contourIdx=-1, color=(0, 255, 0), thickness=3)
cv2.imshow('Contours_show_10000', image_copy)
cv2.waitKey(0) # 等待按键,按下任意键关闭窗口
# 5. 找到面积最大的轮廓并绘制(红色,线宽3)
# 按面积降序排序轮廓
sorted_contours = sorted(contours, key=cv2.contourArea, reverse=True)
largest_contour = sorted_contours[0]
# 绘制最大轮廓
image_copy = phone.copy()
cv2.drawContours(image=image_copy, contours=[largest_contour],
contourIdx=-1, color=(0, 0, 255), thickness=3)
cv2.imshow('largest_contour', image_copy)
cv2.waitKey(0)
# 6. 为指定轮廓绘制外接圆和外接矩形
cnt = contours[2] # 选择第三个轮廓(可根据实际需求调整索引)
# 绘制外接圆(绿色,线宽2)
(x, y), radius = cv2.minEnclosingCircle(cnt)
center = (int(x), int(y)) # 圆心坐标(需转为整数,图像像素坐标为整数)
phone_circle = cv2.circle(phone, center, int(radius), (0, 255, 0), 2)
cv2.imshow('phone_circle', phone_circle)
cv2.waitKey(0)
# 绘制外接矩形(绿色,线宽2)
x, y, w, h = cv2.boundingRect(cnt) # x,y为矩形左上角坐标,w为宽,h为高
phone_rectangle = cv2.rectangle(phone, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.imshow('phone_rectangle', phone_rectangle)
cv2.waitKey(0)
cv2.destroyAllWindows() # 关闭所有OpenCV窗口
关键知识点解析:
- 图像预处理:
cvtColor
将 BGR 格式(OpenCV 默认读取格式)转为灰度图,threshold
通过设定阈值(此处为 127)将灰度图转为二值图,让轮廓更清晰。 - 轮廓检测:
findContours
函数中,RETR_TREE
表示获取轮廓的层级关系,CHAIN_APPROX_SIMPLE
会简化轮廓,去除冗余点,减少内存占用。 - 轮廓筛选与排序:通过
contourArea
计算轮廓面积,结合条件判断筛选出目标轮廓;sorted
函数配合cv2.contourArea
可按面积对轮廓排序,轻松找到最大轮廓。
(二)轮廓近似:简化复杂轮廓
当轮廓边缘过于复杂(如包含大量细小锯齿)时,我们可以通过轮廓近似算法,用更少的点表示轮廓,同时保留其整体形状。以下代码演示了轮廓近似的实现过程:
import cv2
# 1. 读取图像并预处理(步骤与上一部分类似)
phone = cv2.imread('lunkuo.png')
gray = cv2.cvtColor(phone, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 2. 检测轮廓(兼容不同OpenCV版本,取返回值的后两个元素)
contours = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2]
# 3. 轮廓近似
# epsilon为近似精度,是轮廓周长的百分比(此处为1%),值越小,近似轮廓越接近原轮廓
epsilon = 0.01 * cv2.arcLength(contours[0], True)
# approxPolyDP函数实现轮廓近似,True表示轮廓闭合
approx = cv2.approxPolyDP(contours[0], epsilon, True)
# 4. 对比原轮廓与近似轮廓的形状(输出点的数量)
print(f"原轮廓的点数量:{contours[0].shape}") # 格式为(N, 1, 2),N为点的数量
print(f"近似轮廓的点数量:{approx.shape}")
# 5. 绘制近似轮廓并显示
phone_new = phone.copy()
image_contours = cv2.drawContours(phone_new, [approx], contourIdx=-1, color=(0, 255, 0), thickness=3)
cv2.imshow('原始图像', phone)
cv2.waitKey(0)
cv2.imshow('近似轮廓', image_contours)
cv2.waitKey(0)
cv2.destroyAllWindows()
核心原理:轮廓近似基于Douglas-Peucker 算法,通过设定epsilon
值控制近似程度。epsilon
越小,近似轮廓与原轮廓的差异越小,但点的数量越多;epsilon
越大,轮廓越简化,但可能丢失细节。实际应用中需根据需求调整该参数。
(三)模板匹配:在图像中查找目标物体
模板匹配是通过滑动模板图像在待检测图像上移动,计算模板与待检测图像各区域的相似度,从而找到目标物体位置的技术。以下代码实现了在 “可乐” 图像中查找 “瓶盖” 模板的功能:
import cv2
# 1. 读取待检测图像(kele.png)和模板图像(keke.png)
kele = cv2.imread('kele.png')
keke = cv2.imread('keke.png')
# 2. 显示原始图像,确认图像读取成功
cv2.imshow('可乐图像', kele)
cv2.imshow('瓶盖模板', keke)
cv2.waitKey(0)
# 3. 获取模板图像的高和宽(用于后续绘制矩形)
h, w = keke.shape[:2] # shape返回(高, 宽, 通道数),取前两个元素
# 4. 执行模板匹配
# 匹配方法:cv2.TM_CCORR_NORMED(归一化相关匹配),返回相似度矩阵
res = cv2.matchTemplate(kele, keke, cv2.TM_CCORR_NORMED)
# 5. 找到相似度最高的位置(归一化匹配中,最大值对应最相似区域)
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) # 匹配区域的右下角坐标
# 6. 在待检测图像上绘制矩形框,标记目标位置(绿色,线宽2)
kele_keke = cv2.rectangle(kele, top_left, bottom_right, (0, 255, 0), 2)
# 7. 显示匹配结果
cv2.imshow('模板匹配结果', kele_keke)
cv2.waitKey(0)
cv2.destroyAllWindows()
注意事项:
- 模板匹配对尺度和旋转敏感,如果目标物体在待检测图像中发生缩放或旋转,匹配效果会显著下降,此时需结合尺度不变特征变换(SIFT)、旋转不变特征变换(SURF)等算法。
- 匹配方法的选择:除
TM_CCORR_NORMED
外,OpenCV 还提供TM_SQDIFF
(平方差匹配,最小值为最佳匹配)、TM_CCOEFF
(相关系数匹配)等方法,需根据实际场景选择。
二、用 argparse 配置命令行参数,提升程序灵活性
在实际项目中,我们常需要调整程序参数(如阈值、端口号等),如果每次修改都直接改动代码,效率较低。argparse
是 Python 标准库中的模块,可实现命令行参数的解析,让参数配置更便捷。以下代码演示了其基本用法:
import argparse
# 1. 创建参数解析器对象
parser = argparse.ArgumentParser(description='命令行参数配置示例') # description为程序描述
# 2. 添加命令行参数
# --SERIAL_PORT1:第一个报警器的串口号,字符串类型,默认值为COM5,help为参数说明
parser.add_argument('--SERIAL_PORT1', type=str, default='COM5', help='第一个报警器的串口号')
# --area_thred:物体面积阈值,整数类型,默认值1600
parser.add_argument('--area_thred', type=int, default=1600, help='物体面积的阈值')
# --confid_level:识别置信度,浮点类型,默认值0.8
parser.add_argument('--confid_level', type=float, default=0.8, help='识别的置信度')
# --aaa:自定义整数参数,默认值100(无短选项)
parser.add_argument('--aaa', type=int, default=100)
# -b/--bbb:自定义整数参数,支持短选项(-b)和长选项(--bbb),默认值10
parser.add_argument('-b', '--bbb', type=int, default=10)
# 3. 解析命令行参数
opt = parser.parse_args()
# 4. 使用解析后的参数
a = opt.aaa
b = opt.bbb
print(f"参数aaa与bbb的和为:{a + b}")
使用方法:
- 将代码保存为
argparse_demo.py
。 - 在命令行中运行,可直接使用默认参数:
bash
python argparse_demo.py
输出:参数aaa与bbb的和为:110
- 也可在命令行中指定参数值,覆盖默认值:
bash
python argparse_demo.py --aaa 200 -b 50 --SERIAL_PORT1 COM3 --area_thred 2000
输出:参数aaa与bbb的和为:250
优势:通过argparse
,我们无需修改代码,即可在命令行中灵活调整参数,尤其适合批量运行程序或在服务器环境中使用。
三、总结与拓展
本文通过实际代码案例,讲解了 OpenCV 中轮廓检测、轮廓近似、模板匹配等核心功能的实现,同时介绍了argparse
模块的使用,帮助提升程序的灵活性。这些技术在目标检测、图像分割、物体识别等场景中应用广泛,例如:
- 工业质检:通过轮廓检测判断产品是否存在缺陷。
- 智能监控:通过模板匹配查找特定目标(如可疑物品)。
- 机器人视觉:通过轮廓近似简化物体形状,便于机器人抓取。