计算机视觉(九):图像轮廓
在计算机视觉(Computer Vision, CV)中,图像轮廓(Image Contour)是图像中物体边界的重要表现形式。它不仅能描述物体的形状特征,还能为目标识别、目标检测、图像分割、场景理解、三维重建等任务提供重要依据。与像素级的边缘检测相比,轮廓更强调封闭的边界、整体性和几何结构。
基本概念
定义
在图像处理中,轮廓通常指图像中前景目标与背景之间的边界线。它是一个闭合曲线,可以用像素点序列来表示。
- 对于二值图像,轮廓是前景与背景的交界线。
- 对于灰度图像,轮廓常通过边缘检测或阈值分割得到。
轮廓与边缘的区别
- 边缘(Edge):灰度强度发生显著变化的位置,可能是不连续的。
- 轮廓(Contour):物体的完整边界,更强调闭合性和结构性。
数学表示
若一个二值目标区域记为 R,则其轮廓 C 可表示为:
即边界点是区域中与背景相邻的像素点集合。
提取方法
轮廓提取的目标是将图像中物体的边界点序列化,形成闭合曲线。
1. 阈值分割 + 轮廓提取
- 先通过全局阈值(如 Otsu 算法)或自适应阈值将图像转为二值图。
- 然后使用轮廓追踪算法(如 OpenCV
findContours
)提取边界。 - 优点:简单高效,适合对比度清晰的目标。
- 缺点:对噪声和光照敏感。
2. 边缘检测 + 轮廓连接
- 常用边缘算子:Sobel、Canny、Laplacian。
- 检测到边缘后,通过连通域分析、霍夫变换或形态学操作获得闭合轮廓。
- 适合复杂纹理和低对比度图像。
3. 形态学方法
- 利用腐蚀(Erode)、膨胀(Dilate)、开运算、闭运算等形态学操作去噪或填洞。
- 提取骨架(Skeletonization)后再追踪轮廓。
- 常用于噪声较多的工业检测任务。
4. 主动轮廓模型(Snake)
- 通过能量函数(包含图像梯度约束和曲线光滑性约束)使轮廓曲线自动收缩到物体边界。
- 优点:轮廓连续性好,可处理复杂目标。
- 缺点:依赖初始轮廓位置,计算复杂。
5. 基于深度学习的方法
- 使用语义分割(如 U-Net、Mask R-CNN)获得物体区域,再提取轮廓。
- 优点:鲁棒性强,适应复杂场景。
- 缺点:需要大量训练数据。
轮廓的表示与特征
提取到轮廓后,需要对其进行表示和特征化,便于后续处理。
1. 边界链码(Chain Code)
- 以起点为基准,用 4 邻域或 8 邻域的方向序列描述轮廓。
- 优点:存储紧凑。
- 缺点:不平移不变。
2. 多边形逼近
- 用折线逼近复杂轮廓,例如 Douglas-Peucker 算法。
- 常用于简化轮廓,提高计算效率。
3. 矩(Moments)
- 几何矩:描述面积、质心。
- Hu 不变矩:在旋转、缩放、平移下保持不变,适合形状识别。
4. 傅里叶描述子
- 将轮廓点序列看作复数序列,做傅里叶变换。
- 低频分量表示整体形状,高频分量表示细节。
- 可用于形状匹配。
5. 曲率与角点
- 曲率较大的点通常对应物体的特征角点。
- 在目标识别和姿态估计中很重要。
OpenCV中的轮廓查找
核心函数
cv2.findContours()函数
查找轮廓。
contours, hierarchy = cv2.findContours(image, mode, method)
参数解析:
image
: 输入的二值图像。注意: 此函数会修改输入图像,通常建议传入一个副本。mode
: 轮廓检索模式,决定了如何组织轮廓的层级关系。cv2.RETR_EXTERNAL
: 只检索最外层的轮廓。cv2.RETR_LIST
: 检索所有轮廓,但不建立任何层级关系。cv2.RETR_CCOMP
: 检索所有轮廓,并将它们组织成两级层次结构(外部轮廓和孔洞)。cv2.RETR_TREE
: 检索所有轮廓,并建立完整的轮廓层次结构树。
method
: 轮廓近似方法,决定如何存储轮廓点。cv2.CHAIN_APPROX_NONE
: 存储轮廓上所有的点。cv2.CHAIN_APPROX_SIMPLE
: 只存储轮廓的端点,例如,一个矩形只存储四个角点。
返回值:
contours
: 找到的所有轮廓的列表。每个轮廓都是一个 NumPy 数组,包含了该轮廓的所有点。hierarchy
: 一个可选的层级信息数组。它告诉我们每个轮廓的父轮廓、子轮廓、上一个轮廓和下一个轮廓。
cv2.drawContours()函数
在图像上绘制找到的轮廓。
cv2.drawContours(image, contours, contourIdx, color, thickness)
参数解析:
image
: 要在上面绘制轮廓的图像。contours
: 轮廓列表,通常是cv2.findContours()
的返回值。contourIdx
: 轮廓的索引,指定绘制哪个轮廓。如果要绘制所有轮廓,传入-1
。color
: 轮廓的颜色,以(B, G, R)
元组形式表示。thickness
: 轮廓线的粗细。如果传入-1
或cv2.FILLED
,则轮廓内部会被填充。
示例
查找并绘制图像中的轮廓
import cv2
import numpy as np# 1. 读取图像并转换为灰度图
image = cv2.imread('shape.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 2. 图像阈值化
# 将图像转换为二值图像,大于127的像素变为255,否则变为0
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)# 3. 查找轮廓
# 检索所有轮廓,并使用简单的近似方法
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)# 4. 在原图上绘制轮廓
# 绘制所有轮廓,颜色为绿色,粗细为2
output_image = image.copy()
cv2.drawContours(output_image, contours, -1, (0, 255, 0), 2)# 5. 显示结果
cv2.imshow('Original Image', image)
cv2.imshow('Binary Image', thresh)
cv2.imshow('Contours', output_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
执行效果:
形状分析与测量
通过计算轮廓的面积、周长等属性来了解物体的基本几何信息。
import cv2
import numpy as np# 1. 创建一个二值图像
img = np.zeros((300, 300), dtype=np.uint8)
# 画一个圆,填充它
cv2.circle(img, (150, 150), 80, 255, -1)# 2. 查找轮廓
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 获取第一个也是唯一的轮廓
cnt = contours[0]# 3. 计算轮廓属性
area = cv2.contourArea(cnt)
perimeter = cv2.arcLength(cnt, True) # True 表示轮廓是闭合的#print(f"轮廓面积: {area}")
#print(f"轮廓周长: {perimeter}")# 4. 绘制轮廓的边界框和最小外接圆
x, y, w, h = cv2.boundingRect(cnt)
cv2.rectangle(img, (x, y), (x + w, y + h), (100), 2)(center_x, center_y), radius = cv2.minEnclosingCircle(cnt)
center = (int(center_x), int(center_y))
radius = int(radius)
cv2.circle(img, center, radius, (100), 2)cv2.imshow("Shape Analysis", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
执行效果:
形状匹配
形状匹配用于比较两个轮廓的相似度。这在物体识别、缺陷检测等任务中非常有用。cv2.matchShapes
函数是实现这一功能的关键。
import cv2
import numpy as np# 1. 定义两个形状(轮廓)
# 第一个形状:矩形
rect = np.zeros((200, 200), dtype=np.uint8)
cv2.rectangle(rect, (50, 50), (150, 150), 255, -1)
contours1, _ = cv2.findContours(rect, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 第二个形状:旋转的矩形
rotated_rect = np.zeros((200, 200), dtype=np.uint8)
M = cv2.getRotationMatrix2D((100, 100), 45, 1) # 旋转45度
rotated_rect = cv2.warpAffine(rect, M, (200, 200))
contours2, _ = cv2.findContours(rotated_rect, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 2. 匹配两个形状
# cv2.CONTOURS_MATCH_I1 是一个常用的方法
match_score = cv2.matchShapes(contours1[0], contours2[0], cv2.CONTOURS_MATCH_I1, 0)print(f"result: {match_score}")
# 分数越低,两个形状越相似。理想情况下,完全相同的形状得分为0。
# 即使形状经过旋转、缩放或平移,matchShapes函数也能很好地工作。cv2.imshow("Original", rect)
cv2.imshow("Rotated", rotated_rect)
cv2.waitKey(0)
cv2.destroyAllWindows()
执行结果:
基于轮廓的图像分割与对象提取
找到轮廓后,可以用它来创建一个掩模(Mask),以精确地从原始图像中提取出物体。
import cv2
import numpy as np# 1. 读取彩色图像
image = cv2.imread('object.jpg')# 2. 转到 HSV 颜色空间
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)# 3. 定义颜色范围(这里以绿色圆为例)
lower_green = np.array([40, 50, 50])
upper_green = np.array([80, 255, 255])# 4. 生成掩模
mask = cv2.inRange(hsv, lower_green, upper_green)# 5. 提取物体
extracted_object = cv2.bitwise_and(image, image, mask=mask)# 6. 显示
cv2.imshow("Original", image)
cv2.imshow("Mask", mask)
cv2.imshow("Extracted Object", extracted_object)
cv2.waitKey(0)
cv2.destroyAllWindows()
执行效果:
轮廓近似与多边形拟合
轮廓可能包含成千上万个点,为了简化形状并减少计算量,可以使用多边形拟合。
import cv2
import numpy as np# 1. 创建一个形状(这里是一个圆,但可以用于任何复杂的形状)
img = np.zeros((300, 300), dtype=np.uint8)
cv2.circle(img, (150, 150), 100, 255, -1)# 2. 查找轮廓
contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # 使用CHAIN_APPROX_NONE来获取所有点
original_contour = contours[0]# 3. 轮廓近似
# 参数 epsilon 决定了近似的精度,值越小,近似越接近原始轮廓
epsilon = 0.04 * cv2.arcLength(original_contour, True)
approx_contour = cv2.approxPolyDP(original_contour, epsilon, True)# 4. 绘制原始轮廓和近似后的轮廓
# 原始轮廓(红色)
original_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
cv2.drawContours(original_img, [original_contour], -1, (0, 0, 255), 2)# 近似轮廓(绿色)
approx_img = np.zeros_like(original_img)
cv2.drawContours(approx_img, [approx_contour], -1, (0, 255, 0), 2)
cv2.imshow("Original vs. Approximated", np.hstack([original_img, approx_img]))
cv2.waitKey(0)
cv2.destroyAllWindows()
执行效果: