前言:轮廓检测是图像处理和计算机视觉中非常核心的技术,用于识别图像中物体的边界。通过轮廓,我们可以获得物体的形状、大小、位置等信息,进而实现目标识别、图像分割、形状分析等高级任务。OpenCV提供了强大的轮廓检测与处理函数,本文将结合代码实例,详细讲解轮廓检测的完整流程,并展示实际运行结果。
一、轮廓检测基础
1.1 什么是轮廓
轮廓可以简单理解为图像中连接所有连续点(沿着边界)的曲线,这些点具有相同的颜色或灰度。轮廓检测常用于物体识别、形状分析等场景。
1.2 轮廓检测的步骤
轮廓检测通常遵循以下步骤:
读取图像并转换为灰度图。
二值化处理:将灰度图转换为二值图像(像素值仅为0和255),因为轮廓检测算法通常在二值图像上工作。
执行轮廓检测:使用cv2.findContours()查找轮廓。
绘制或分析轮廓:根据需要绘制轮廓或提取轮廓特征。
二、关键函数详解
2.1 转换为灰度图与二值化
import cv2
# 读取图像
phone = cv2.imread('phone.png')
# 转换为灰度图
phone_gray = cv2.cvtColor(phone, cv2.COLOR_BGR2GRAY)
cv2.imshow('Gray', phone_gray)
cv2.waitKey(0)
# 二值化处理(阈值120,最大值255,THRESH_BINARY类型)
ret, phone_binary = cv2.threshold(phone_gray, 120, 255, cv2.THRESH_BINARY)
cv2.imshow('Binary', phone_binary)
cv2.waitKey(0)
运行结果说明:

-
灰度图窗口显示原图的灰度版本。
-
二值图窗口显示黑白图像,其中灰度值大于120的像素变为白色,其余为黑色。
2.2 查找轮廓:cv2.findContours()
函数语法(OpenCV 3/4版本):
contours, hierarchy = cv2.findContours(image, mode, method)
注意:在OpenCV 3和4中,该函数只返回两个值(contours, hierarchy),而OpenCV 2返回三个值(image, contours, hierarchy)。本教程基于OpenCV 3/4。
参数详解:
-
image:输入的二值图像(通常为8位单通道,像素值0或255)。注意:该函数会直接修改输入图像,建议传入图像的副本。
-
mode:轮廓检索模式,决定轮廓的组织方式。
模式描述 cv2.RETR_EXTERNAL 只检测最外层轮廓,忽略内部轮廓。 cv2.RETR_LIST 检测所有轮廓,但不建立等级关系,所有轮廓处于同一层级。 cv2.RETR_CCOMP 检测所有轮廓,并建立两个层级的层次结构:顶层为外边界,次层为内孔边界。 cv2.RETR_TREE 检测所有轮廓,并建立完整的等级树结构,包含父子关系。 -
method:轮廓近似方法,决定如何存储轮廓点。
方法描述 cv2.CHAIN_APPROX_NONE 存储所有轮廓点,相邻两点距离不超过1像素。 cv2.CHAIN_APPROX_SIMPLE 压缩水平、垂直、对角方向,只保留端点。例如一个矩形只需4个点。
返回值:
-
contours:一个Python列表,每个元素是一个轮廓(由NumPy数组存储的边界点坐标,形状为(N,1,2))。
-
hierarchy:轮廓的层次信息,形状为(1,N,4)的数组,每个轮廓对应四个整数:[Next, Previous, First_Child, Parent]。
-
Next:同一层级的下一条轮廓的索引,无则-1。
-
Previous:同一层级的上一条轮廓的索引,无则-1。
-
First_Child:第一个子轮廓的索引,无则-1。
-
Parent:父轮廓的索引,无则-1。
-
代码示例:
# 使用RETR_TREE检索所有轮廓,使用CHAIN_APPROX_NOME存储所有点
contours, hierarchy = cv2.findContours(phone_binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
print(f"检测到 {len(contours)} 个轮廓")
输出示例:
检测到 9 个轮廓
(具体数值取决于图像内容,此处为笔者上传图片的轮廓)
2.3 绘制轮廓:cv2.drawContours()
函数语法:
cv2.drawContours(image, contours, contourIdx, color, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]])
参数详解:
-
image:要在其上绘制轮廓的目标图像(可以是原图或副本)。
-
contours:轮廓列表(即findContours返回的contours)。
-
contourIdx:要绘制的轮廓索引。若为负数(如-1),则绘制所有轮廓。
-
color:轮廓颜色,BGR格式,如(0,255,0)为绿色。
-
thickness:线条粗细,若为负数则填充轮廓内部。
-
lineType:线条类型,如cv2.LINE_8(默认)。
-
hierarchy:层次结构(可选),用于绘制特定层级。
-
maxLevel:绘制轮廓的最大层级(配合hierarchy使用)。
-
offset:轮廓点的偏移量。
代码示例:
# 复制原图,避免修改原图
image_copy = phone.copy()
# 绘制所有轮廓,绿色,线宽3
cv2.drawContours(image_copy, contours, -1, (0, 255, 0), 3)
cv2.imshow('All Contours', image_copy)
cv2.waitKey(0)
运行结果说明:

-
显示窗口“All Contours”,原图上用绿色线条绘制了所有检测到的轮廓。
三、轮廓特征
检测到轮廓后,我们通常需要提取其特征,如面积、周长等。
3.1 轮廓面积:cv2.contourArea()
函数语法:
area = cv2.contourArea(contour[, oriented])
-
contour:单个轮廓(如contours[i])。
-
oriented:若为True,返回带符号的面积(可用于判断轮廓方向),默认False返回绝对值。
示例:
area_0 = cv2.contourArea(contours[0])
area_1 = cv2.contourArea(contours[1])
print(f"轮廓0面积: {area_0}")
print(f"轮廓1面积: {area_1}")
输出示例:

轮廓0面积: 50716.5
轮廓1面积: 255.5
3.2 轮廓周长:cv2.arcLength()
函数语法:
perimeter = cv2.arcLength(curve, closed)
-
curve:轮廓点集。
-
closed:布尔值,指示轮廓是否闭合(True表示闭合,计算闭合周长;False表示不闭合,计算曲线长度)。
示例:
length = cv2.arcLength(contours[0], True)
print(f"轮廓0周长: {length}")
输出示例:

轮廓0周长: 952.437722325325
3.3 根据面积筛选轮廓
通过遍历轮廓,可以根据面积大小筛选出感兴趣的轮廓。
示例:保留面积大于10000的轮廓并绘制
large_contours = []
for contour in contours:
if cv2.contourArea(contour) > 10000:
large_contours.append(contour)
image_large = phone.copy()
cv2.drawContours(image_large, large_contours, -1, (0, 255, 0), 3)
cv2.imshow('Large Contours (>10000)', image_large)
cv2.waitKey(0)
运行结果说明:

-
窗口“Large Contours (>10000)”仅显示面积大于10000的轮廓,过滤掉了小面积噪点或无关轮廓。
四、几何形状拟合
通过轮廓,可以拟合出各种几何形状,如最小外接圆、外接矩形等,从而获得物体的位置和尺寸。
4.1 最小外接圆:cv2.minEnclosingCircle()
函数语法:
(x, y), radius = cv2.minEnclosingCircle(points)
-
points:轮廓点集。
-
返回值:圆心坐标(x, y)和半径radius(均为浮点数)。
示例:
# 选择第6个轮廓(根据实际图像调整索引)
cnt = contours[6]
(x, y), r = cv2.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(r)
# 在原图上绘制外接圆
phone_circle = phone.copy()
cv2.circle(phone_circle, center, radius, (0, 255, 0), 2)
cv2.imshow('Min Enclosing Circle', phone_circle)
cv2.waitKey(0)
运行结果说明:

-
窗口“Min Enclosing Circle”显示用绿色圆圈包围的轮廓。
4.2 外接矩形:cv2.boundingRect()
函数语法:
x, y, w, h = cv2.boundingRect(array)
-
array:轮廓点集。
-
返回值:矩形左上角坐标(x, y),宽度w,高度h。
示例:
x, y, w, h = cv2.boundingRect(cnt)
phone_rect = phone.copy()
cv2.rectangle(phone_rect, (x, y), (x + w, y + h), (0, 0, 255), 2)
cv2.imshow('Bounding Rectangle', phone_rect)
cv2.waitKey(0)
运行结果说明:

-
窗口“Bounding Rectangle”显示用红色矩形框出的轮廓。
五、完整示例代码及运行结果
以下将所有步骤整合,方便读者运行观察。代码中包含必要的注释和输出语句。
import cv2
# 1. 读取图像
phone = cv2.imread('phone.png')
if phone is None:
print("无法读取图像,请检查路径")
exit()
# 2. 转换为灰度图并二值化
gray = cv2.cvtColor(phone, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY)
# 3. 查找轮廓(使用TREE模式,存储所有点)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
print(f"找到 {len(contours)} 个轮廓")
# 4. 绘制所有轮廓
all_contours_img = phone.copy()
cv2.drawContours(all_contours_img, contours, -1, (0, 255, 0), 3)
cv2.imshow('All Contours', all_contours_img)
# 5. 计算并打印第一个轮廓的面积和周长
if len(contours) > 0:
area = cv2.contourArea(contours[0])
perimeter = cv2.arcLength(contours[0], True)
print(f"第一个轮廓面积: {area}, 周长: {perimeter}")
# 6. 根据面积筛选(面积大于10000)
large_contours = [cnt for cnt in contours if cv2.contourArea(cnt) > 10000]
large_img = phone.copy()
cv2.drawContours(large_img, large_contours, -1, (0, 255, 0), 3)
cv2.imshow('Large Contours (>10000)', large_img)
# 7. 对某个轮廓绘制最小外接圆和外接矩形
if len(contours) > 6: # 确保索引存在
cnt = contours[6]
# 最小外接圆
(x, y), r = cv2.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(r)
circle_img = phone.copy()
cv2.circle(circle_img, center, radius, (0, 255, 0), 2)
cv2.imshow('Min Enclosing Circle', circle_img)
# 外接矩形
x, y, w, h = cv2.boundingRect(cnt)
rect_img = phone.copy()
cv2.rectangle(rect_img, (x, y), (x + w, y + h), (0, 0, 255), 2)
cv2.imshow('Bounding Rectangle', rect_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
运行结果示例(基于某张phone.png图像):
检测到 9 个轮廓
轮廓0面积: 50716.5
轮廓1面积: 255.5
轮廓0周长: 952.437722325325
同时依次弹出以下窗口:
-
Gray:灰度图
-
Binary:二值图
-
All Contours:所有轮廓绘制结果(绿色线条)
-
Large Contours (>10000):筛选后的大面积轮廓
-
Min Enclosing Circle:第6个轮廓的最小外接圆
-
Bounding Rectangle:第6个轮廓的外接矩形
读者可根据实际图像调整阈值和轮廓索引,观察不同效果。
六、注意事项
输入图像要求:cv2.findContours()的输入必须是二值图像(0或255),且通常为单通道。如果不是,需要提前二值化。
图像副本:findContours会修改传入的图像,建议传入副本或确保原图不再需要。
OpenCV版本差异:
-
OpenCV 2:返回三个值(image, contours, hierarchy)
-
OpenCV 3/4:返回两个值(contours, hierarchy)
根据使用的版本调整代码。
轮廓近似:CHAIN_APPROX_SIMPLE能大幅减少轮廓点数,节省内存,但可能损失微小细节;CHAIN_APPROX_NONE保留所有点,更精确。
层次结构理解:在复杂图像中,利用hierarchy可以分析轮廓的包含关系(如物体内部有空洞),实现更精细的筛选。
七、总结
本文详细介绍了OpenCV中轮廓检测的完整流程,包括:
-
图像预处理(灰度化、二值化)
-
使用findContours查找轮廓,并理解不同检索模式和近似方法的区别
-
使用drawContours绘制轮廓
-
提取轮廓特征(面积、周长)并据此筛选
-
拟合几何形状(最小外接圆、外接矩形)
通过实际代码运行和输出示例,读者可以直观理解各步骤的效果。轮廓检测是图像分析的基础,掌握这些操作后,可以进一步实现物体计数、形状分类、目标定位等高级功能。建议读者多尝试不同参数,观察结果变化,加深理解。
