引言
在计算机视觉中,轮廓检测与模板匹配是两项基础的图像分析技术。轮廓检测通过提取目标的形状边界,将图像简化为几何轮廓,是实现物体形状分析、尺寸测量的关键步骤。模板匹配则通过在图像中滑动已知模板进行比对,直接定位特定目标,适用于元件定位、固定图案识别等任务。它们常协同工作,如用轮廓快速筛选区域,再用模板精确匹配,共同为更复杂的视觉任务提供可靠支持。接下来就让我们一起学习这两样技术。
一、轮廓检测:图像的"描边"艺术
1、什么是轮廓检测?
轮廓检测的核心目标是从数字图像中识别并提取出其中物体或区域的边界线。其目的是找出连续且通常闭合的轮廓曲线,从而勾勒出物体的完整外部形状或内部结构边界。这个过程类似于为图像中感兴趣的物体进行"描边"或"勾勒外形",其结果是由一系列连续点构成的、可用于进一步分析的封闭或开放轮廓集合。它使程序能够对图像中的目标进行定位、测量和分离操作。在OpenCV等常用库中,通常通过cv2.findContours()等核心函数来实现这一功能。
image, contours, hierarchy = cv2.findContours(img, mode, method)
参数:
img:输入图像,必须是二值图像(只有0和255两种像素值)。
mode:轮廓检索模式,常用有以下几种:
cv2.RETR_EXTERNAL:只检测最外层轮廓
cv2.RETR_TREE:检测所有轮廓并建立完整的层次结构(最常用)
method:轮廓近似方法:
cv2.CHAIN_APPROX_NONE:保存轮廓上所有点
cv2.CHAIN_APPROX_SIMPLE:只保存轮廓的拐点(如矩形只保存4个顶点)
返回值:
image:处理后的图像
contours:检测到的轮廓列表,每个轮廓由一系列点组成
hierarchy:轮廓的层次关系信息
2、图像预处理
在进行轮廓检测之前,图像必须经过预处理转换为二值图像,这是因为轮廓检测算法需要明确区分前景目标与背景。处理流程包含三个关键步骤:首先,使用cv2.imread()函数读取原始彩色图像,接着,通过cv2.cvtColor()函数并将转换代码设为COLOR_BGR2GRAY,将彩色图像转换为灰度图像,这里消除了颜色信息,将三维的像素矩阵简化为仅包含亮度信息的二维矩阵,最后,用cv2.threshold()函数对灰度图进行二值化操作,通过设定一个阈值(例如例子中的120),将灰度图中所有低于此阈值的像素点置为0(黑色),高于或等于阈值的像素点置为255(白色),从而生成一个黑白分明、只有前景和背景的两值图像为后续准确的轮廓查找奠定基础。
import cv2
# 读取图像
phone = cv2.imread('phone.png')
# 转换为灰度图
phone_gray = cv2.cvtColor(phone, cv2.COLOR_BGR2GRAY)
# 二值化处理
ret, phone_binary = cv2.threshold(phone_gray, 120, 255, cv2.THRESH_BINARY)
3、检测并绘制轮廓
接下来我们要检测并绘制轮廓,这个操作需要两个关键函数。首先,调用cv2.findContours()函数在二值图像上查找轮廓。其中,参数cv2.RETR_TREE表示检索所有轮廓并建立完整的层级关系树,cv2.CHAIN_APPROX_NONE则存储轮廓上所有的点,不进行近似压缩。该函数返回轮廓列表和层级信息。我们通常先创建一个副本以避免修改原图。接着,使用cv2.drawContours()函数进行绘制,将contours列表传入,设定contourIdx=-1以画出所有轮廓,并指定绘制颜色为绿色(0, 255, 0),线条粗细为2像素。最后,通过cv2.imshow()显示绘制结果。
# 检测轮廓
contours, hierarchy = cv2.findContours(phone_binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2:]
# 在原图上绘制所有轮廓
image_copy = phone.copy()
cv2.drawContours(image=image_copy, contours=contours,
contourIdx=-1, color=(0, 255, 0), thickness=2)
cv2.imshow('Contours_show', image_copy)
cv2.waitKey(0)

二、轮廓特征:了解你的"轮廓"
在我们获取轮廓后,我们需要分析它们的特征,比如面积、周长等,以便进一步筛选和处理。
1、轮廓面积
在OpenCV库中,我们可以使用cv2.contourArea()函数计算轮廓的面积。面积是轮廓的一个基础几何特征,用于根据实际尺寸需求从图像中筛选出符合条件的轮廓,例如过滤掉噪点或选中特定大小的目标物体。
area = cv2.contourArea(contour)
2、轮廓周长
我们还可以使用cv2.arcLength()函数可以计算轮廓的周长。其closed参数用于指定轮廓是否为封闭曲线。周长是另一个核心的轮廓特征,常与面积结合用于形状分析,例如计算轮廓的圆度,或在实际应用中估算物体的边界长度。
perimeter = cv2.arcLength(contour, closed=True)
那接下来让我们按面积筛选轮廓:我们只想显示面积大于10000的轮廓
large_contours = []
for cnt in contours:
if cv2.contourArea(cnt) > 10000:
large_contours.append(cnt)
image_copy = phone.copy()
cv2.drawContours(image_copy, large_contours, -1, (0, 255, 0), 3)
cv2.imshow('Large_Contours', image_copy)
cv2.waitKey(0)

三、轮廓近似:简化轮廓形状
1、为什么要近似?
因为从实际图像中检测出的轮廓往往包含过多的冗余点,导致形状描述过于复杂且计算效率低下。这时我们通过用更少的点来逼近原始轮廓,可以在基本保持其核心几何形状的前提下,简化数据。这种简化对于后续的形状识别、匹配和存储至关重要,它能提高算法效率并减少噪声带来的干扰。
2、近似方法
cv2.approxPolyDP() 是实现轮廓近似的核心函数。其关键参数 epsilon 为近似精度,通常设置为轮廓周长的一个百分比。通过调整该值,可以控制近似的程度:epsilon 值越小,得到的多边形顶点越多,形状越接近原始轮廓,值越大,则顶点越少,形状越简化,直至失去关键特征。
epsilon = 0.01 * cv2.arcLength(contour, True) # 近似精度
approx = cv2.approxPolyDP(contour, epsilon, True) # 进行近似
轮廓近似示例:
# 获取最大面积的轮廓
contours_with_area = [(cnt, cv2.contourArea(cnt)) for cnt in contours]
sorted_contours = sorted(contours_with_area, key=lambda x: x[1], reverse=True)
largest_contour = sorted_contours[0][0]
# 对最大轮廓进行近似
epsilon = 0.01 * cv2.arcLength(largest_contour, True)
approx = cv2.approxPolyDP(largest_contour, epsilon, True)
# 绘制近似后的轮廓
phone_new = phone.copy()
cv2.drawContours(phone_new, [approx], -1, (0, 255, 0), 3)
cv2.imshow('Approximated_Contour', phone_new)
cv2.waitKey(0)

四、轮廓的外接形状
1、外接圆
cv2.minEnclosingCircle() 函数用于计算轮廓的最小外接圆,返回圆心坐标和半径。该圆是能完全包围轮廓的最小的圆,常用于定位和测量。我们在代码里获取的浮点数结果需转换为整数,以便使用 cv2.circle() 函数在图像上绘制出该圆形,从而直观地可视化轮廓的外接范围。
# 计算最小外接圆并绘制(使用最大轮廓)
(x, y), radius = cv2.minEnclosingCircle(largest_contour)
circle_image = phone.copy()
cv2.circle(circle_image, (int(x), int(y)), int(radius), (0, 255, 0), 2)
cv2.imshow('Min_Enclosing_Circle', circle_image)

2、外接矩形
cv2.boundingRect()函数用于计算轮廓的直立外接矩形,返回其左上角顶点坐标、宽度和高度。该矩形是能完全包围轮廓的最小正矩形,常用于目标的快速定位和区域提取。随后,用cv2.rectangle()并传入矩形的两个对角点,即可在图像上绘制出该绿色边框,从而直观地标定轮廓的范围。
# 计算外接矩形并绘制
x, y, w, h = cv2.boundingRect(largest_contour)
rect_image = phone.copy()
cv2.rectangle(rect_image, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.imshow('Bounding_Rectangle', rect_image)

五、模板匹配:图像中的"找茬"游戏
1、什么是模板匹配?
模板匹配是在源图像中寻找与给定模板图像最相似的区域。其核心思想是将模板作为滑动窗口,遍历源图像进行比较,通过特定的方法计算相似度。使用的核心函数是cv2.matchTemplate(),它输入源图像、模板图像和匹配方法,输出一个结果矩阵,其中的极值点位置即指示了最佳匹配区域。
res = cv2.matchTemplate(image, templ, method)
匹配方法:
cv2.TM_SQDIFF_NORMED:归一化平方差匹配,值越小匹配越好
cv2.TM_CCORR_NORMED:归一化相关匹配,值越大匹配越好
cv2.TM_CCOEFF_NORMED:归一化相关系数匹配,最常用,值越大匹配越好
模板匹配的工作原理:首先将模板图像放在原图的每个可能位置,接着在每个位置计算相似度分数,找到分数最高的位置,该位置就是最佳匹配区域。
案例:在可乐罐图片中找商标
import cv2
# 读取图像和模板
kele = cv2.imread('kele.png') # 原图
template = cv2.imread('template.png') # 模板
# 获取模板尺寸
h, w = template.shape[:2]
# 进行模板匹配
res = cv2.matchTemplate(kele, template, cv2.TM_CCOEFF_NORMED)
# 找到最佳匹配位置
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, (0, 255, 0), 2)
# 显示结果
cv2.imshow('Template_Matching_Result', kele_template)
cv2.waitKey(0)
cv2.destroyAllWindows()

到这里我们就学完了opencv中的基础内容了,接下来将会在后面的文章里开始学习进阶的更复杂的计算机视觉操作。