引言
在图像处理中,轮廓检测是一项基础且重要的技术。通过轮廓,我们可以获取物体的边界信息,进而进行形状分析、物体识别等操作。本文将基于一个具体的例子------绘制花朵图片的外部轮廓及其近似轮廓,来详细介绍OpenCV中轮廓检测、筛选、近似和绘制的完整流程。代码将逐步解释每个环节,并重点剖析 max(contours, key=cv2.contourArea) 这一关键用法。
环境与依赖
-
Python 3.x
-
OpenCV(
cv2) -
NumPy
任务描述
给定一张花朵图片 hua.png,要求在同一窗口内完成:
-
用红色画出花朵的外部轮廓;
-
用绿色画出其近似轮廓(ε设置为轮廓周长的0.005)。
效果图应同时显示红色精确轮廓和绿色简化轮廓。
原图如下:

最终效果如下:

实现步骤详解
1. 读取图像
import cv2
import numpy as np
img = cv2.imread('hua.png')
使用 cv2.imread 读取图片,返回一个BGR格式的NumPy数组。
2. 图像预处理
轮廓检测通常需要二值图像,因此我们将原图转换为灰度图,再进行二值化。
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
灰度化是为了简化后续处理。
ret, binary = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY_INV)
这里采用固定阈值二值化,像素值大于240的设为255(背景),其余设为0(花朵)。由于花朵颜色较深,背景较亮,我们使用 THRESH_BINARY_INV 得到白色背景、黑色花朵的图像(便于轮廓检测)。
注意:阈值240可根据实际图片调整,确保花朵区域被正确分割。
3. 轮廓检测
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2:]
-
cv2.findContours从二值图像中提取轮廓。 -
参数
cv2.RETR_TREE建立完整的轮廓层级关系。 -
cv2.CHAIN_APPROX_NONE保存轮廓上的所有点。 -
OpenCV版本差异:旧版返回2个值,新版返回3个值(第一个是修改后的图像)。使用
[-2:]可以兼容两种版本,只取后两个返回值(轮廓列表和层级结构)。
4. 筛选最大轮廓
检测到的轮廓可能包含噪声或小区域,我们只关心最大的轮廓(即花朵主体)。
max_contour = max(contours, key=cv2.contourArea)
这是本文的核心:
-
contours是一个列表,每个元素是一个轮廓(NumPy数组)。 -
max()是Python内置函数,用于返回可迭代对象中的最大值。 -
key=cv2.contourArea指定了一个"键函数":在比较每个轮廓时,先用cv2.contourArea计算该轮廓的面积,然后根据面积大小决定哪个轮廓最大。 -
最终
max_contour就是面积最大的那个轮廓。
为什么这样写? 直接比较轮廓对象本身没有意义,我们需要按面积排序,因此通过
key指定一个转换函数,max会依据该函数的返回值进行比较。
5. 计算近似轮廓
使用 Douglas-Peucker 算法对轮廓进行多边形近似,减少轮廓点数。
perimeter = cv2.arcLength(max_contour, True) # 计算轮廓周长
epsilon = 0.005 * perimeter # 近似精度
approx_contour = cv2.approxPolyDP(max_contour, epsilon, True)
-
cv2.arcLength计算轮廓的周长,第二个参数True表示轮廓是闭合的。 -
epsilon是近似精度,这里设为周长的0.5%。 -
cv2.approxPolyDP返回近似后的轮廓点集。
6. 绘制结果
在同一图像上绘制两种轮廓,并显示。
img_result = img.copy()
# 红色绘制外部轮廓(BGR: (0,0,255)),线宽3
cv2.drawContours(img_result, [max_contour], -1, (0, 0, 255), 3)
# 绿色绘制近似轮廓(BGR: (0,255,0)),线宽3
cv2.drawContours(img_result, [approx_contour], -1, (0, 255, 0), 3)
cv2.imshow('Result', img_result)
cv2.waitKey(0)
cv2.destroyAllWindows()
完整代码
整合以上步骤,得到完整脚本:
import cv2
import numpy as np
# 1. 读取图像
img = cv2.imread('hua.png')
# 2. 预处理:灰度 + 二值化(白底黑花)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY_INV)
# 3. 轮廓检测(兼容不同OpenCV版本)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2:]
# 4. 筛选面积最大的轮廓(花朵主体)
max_contour = max(contours, key=cv2.contourArea)
# 5. 计算近似轮廓
perimeter = cv2.arcLength(max_contour, True)
epsilon = 0.005 * perimeter
approx_contour = cv2.approxPolyDP(max_contour, epsilon, True)
# 6. 绘制结果
img_result = img.copy()
cv2.drawContours(img_result, [max_contour], -1, (0, 0, 255), 3) # 红色外部轮廓
cv2.drawContours(img_result, [approx_contour], -1, (0, 255, 0), 3) # 绿色近似轮廓
# 显示
cv2.imshow('Result', img_result)
cv2.waitKey(0)
cv2.destroyAllWindows()
关键点解析
1. 二值化阈值的选择
本例采用固定阈值240,这是因为背景较亮。实际应用中,可能需要自适应阈值或Otsu方法。可根据花朵与背景的对比度灵活调整。
2. max(contours, key=cv2.contourArea)
-
cv2.contourArea:OpenCV提供的函数,计算轮廓面积。 -
max函数的key参数 :它接受一个函数,该函数作用于每个元素,返回一个可比较的值。max根据这些值确定最大值对应的元素。 -
等价于以下代码:
max_area = 0 max_contour = None for c in contours: area = cv2.contourArea(c) if area > max_area: max_area = area max_contour = c但使用
max更加简洁高效。
3. 轮廓近似算法
cv2.approxPolyDP 基于 Douglas-Peucker 算法,其核心思想是用较少的点逼近原始轮廓,且最大距离不超过 epsilon。epsilon 越小,近似轮廓越精确;epsilon 越大,轮廓越简化(点数越少)。
4. 绘制轮廓
cv2.drawContours 可在图像上绘制轮廓列表。这里我们只绘制单个轮廓,所以将其放入列表 [max_contour] 和 [approx_contour] 中。参数 -1 表示绘制所有索引的轮廓(这里只有一个)。颜色使用BGR格式,红色为 (0,0,255),绿色为 (0,255,0)。
5. 窗口显示管理
cv2.waitKey(0) 等待用户按键,cv2.destroyAllWindows() 关闭所有窗口。注意不要在显示前调用 destroyAllWindows,否则窗口会立即关闭。
运行结果与讨论
运行代码后,将弹出一个窗口,显示原图上叠加了红色和绿色轮廓。红色轮廓精确地勾勒出花朵的每个细节,而绿色轮廓则用较少的线段近似表达花朵形状,整体上保留了主要特征但更加平滑。
通过调整 epsilon 的系数(例如0.01或0.001),可以观察到近似轮廓的不同简化程度。这在实际应用中可用于形状匹配、特征提取等场景。
总结
本文通过一个简单的花朵图像轮廓绘制示例,展示了OpenCV轮廓处理的基本流程:
-
图像预处理(灰度、二值化)
-
轮廓检测与筛选(
max+cv2.contourArea) -
轮廓近似(
approxPolyDP) -
轮廓绘制(
drawContours)
重点剖析了 max(contours, key=cv2.contourArea) 的原理和用法,帮助读者理解如何利用Python内置函数和OpenCV函数实现高效筛选。
希望这篇文章对你在图像轮廓处理的学习中有所帮助!如果有任何问题或建议,欢迎留言讨论。
注意:实际运行代码时,请确保图片路径正确,并根据图像调整二值化阈值。