OpenCV 图像轮廓检测
OpenCV 图像轮廓检测 教程(中文)重点讲解 OpenCV 中用于轮廓检测的核心功能,主要基于 imgproc 模块的 findContours 和 drawContours 函数。轮廓检测用于提取图像中对象的边界,广泛应用于形状分析、对象识别和图像分割。本教程涵盖轮廓检测、绘制、属性计算(如面积、周长)、层次结构分析,以及实际应用示例,提供清晰的 Python 代码示例、解释和注意事项,适合初学者快速上手。假设你已安装 OpenCV(opencv-python)。
一、图像轮廓检测概述
- 轮廓:图像中具有相同颜色或强度的连续点的边界,通常是二值图像中白色区域(前景)的边界。
- 应用场景:
- 形状分析:识别圆形、矩形等形状。
- 对象分割:分离前景和背景。
- 特征提取:为目标识别或跟踪提供边界信息。
- 关键函数:
cv2.findContours:查找轮廓,返回轮廓点集和层次信息。cv2.drawContours:绘制轮廓到图像。- 属性函数:
cv2.contourArea(面积)、cv2.arcLength(周长)、cv2.boundingRect(边界框)等。 - 输入要求:
- 通常为二值图像(0 和 255,
uint8类型),可通过阈值处理或边缘检测生成。 - 建议预处理(如高斯模糊、Canny 边缘检测)以提高轮廓质量。
二、核心轮廓检测功能与代码示例
以下按功能分类,逐一讲解并提供 Python 示例代码。
2.1 查找和绘制轮廓 (cv2.findContours, cv2.drawContours)
findContours 检测二值图像中的轮廓,drawContours 将轮廓绘制到图像上。
示例:基本轮廓检测
import cv2
import numpy as np
# 读取图像并转换为灰度
img = cv2.imread('shapes.jpg') # 替换为包含简单形状的图像路径
if img is None:
print("错误:无法加载图像")
exit()
# 转换为灰度并二值化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 查找轮廓
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
img_contours = img.copy()
cv2.drawContours(img_contours, contours, -1, (0, 255, 0), 2) # 绿色轮廓,线宽 2
# 显示结果
cv2.imshow('原始图像', img)
cv2.imshow('二值图像', binary)
cv2.imshow('轮廓', img_contours)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 保存结果
cv2.imwrite('contours.jpg', img_contours)
说明:
- 输入:二值图像(白色前景,黑色背景)。
- 参数:
mode(检索模式):cv2.RETR_EXTERNAL:仅外部轮廓。cv2.RETR_LIST:所有轮廓,无层次。cv2.RETR_CCOMP:两级层次(内外轮廓)。cv2.RETR_TREE:完整层次结构。
method(近似方法):cv2.CHAIN_APPROX_SIMPLE:压缩水平/垂直线,节省内存。cv2.CHAIN_APPROX_NONE:存储所有点。
- 输出:
contours:轮廓列表,每个轮廓是点集(numpy数组)。hierarchy:层次结构,格式为[Next, Previous, FirstChild, Parent]。drawContours:-1表示绘制所有轮廓,颜色为 BGR 格式。
2.2 轮廓属性计算
计算轮廓的面积、周长、边界框等属性,用于形状分析。
示例:计算面积、周长和边界框
import cv2
import numpy as np
# 读取图像并转换为灰度
img = cv2.imread('shapes.jpg')
if img is None:
print("错误:无法加载图像")
exit()
# 转换为灰度并二值化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 查找轮廓
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 绘制轮廓并计算属性
img_contours = img.copy()
for i, cnt in enumerate(contours):
# 面积
area = cv2.contourArea(cnt)
# 周长(True 表示闭合轮廓)
perimeter = cv2.arcLength(cnt, True)
# 边界框
x, y, w, h = cv2.boundingRect(cnt)
print(f"轮廓 {i+1}: 面积={area:.2f}, 周长={perimeter:.2f}, 边界框=({x}, {y}, {w}, {h})")
# 绘制轮廓和边界框
cv2.drawContours(img_contours, [cnt], -1, (0, 255, 0), 2)
cv2.rectangle(img_contours, (x, y), (x+w, y+h), (255, 0, 0), 1)
# 显示结果
cv2.imshow('原始图像', img)
cv2.imshow('轮廓与边界框', img_contours)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 保存结果
cv2.imwrite('contours_with_boxes.jpg', img_contours)
输出示例(假设图像包含多个形状):
轮廓 1: 面积=500.00, 周长=100.00, 边界框=(50, 50, 30, 30)
轮廓 2: 面积=1200.50, 周长=150.25, 边界框=(100, 100, 50, 50)
说明:
contourArea:计算轮廓面积(像素单位)。arcLength:计算周长,True表示闭合轮廓。boundingRect:返回边界框(x, y, width, height)。
2.3 轮廓层次结构
使用 hierarchy 分析轮廓的嵌套关系(如内外轮廓)。
示例:层次轮廓检测
import cv2
import numpy as np
# 读取图像并转换为灰度
img = cv2.imread('shapes.jpg')
if img is None:
print("错误:无法加载图像")
exit()
# 转换为灰度并二值化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 查找轮廓(使用 RETR_TREE 模式)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 绘制轮廓并显示层次
img_contours = img.copy()
for i, cnt in enumerate(contours):
h = hierarchy[0][i] # [Next, Previous, FirstChild, Parent]
color = (0, 255, 0) if h[3] == -1 else (0, 0, 255) # 外部绿色,内部红色
cv2.drawContours(img_contours, [cnt], -1, color, 2)
print(f"轮廓 {i}: 父轮廓={h[3]}, 子轮廓={h[2]}")
# 显示结果
cv2.imshow('原始图像', img)
cv2.imshow('层次轮廓', img_contours)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 保存结果
cv2.imwrite('hierarchy_contours.jpg', img_contours)
输出示例:
轮廓 0: 父轮廓=-1, 子轮廓=1
轮廓 1: 父轮廓=0, 子轮廓=-1
说明:
hierarchy:每个轮廓的层次信息,[Next, Previous, FirstChild, Parent]:Next:同一级的下一个轮廓(-1 表示无)。Previous:同一级的前一个轮廓(-1 表示无)。FirstChild:第一个子轮廓(-1 表示无)。Parent:父轮廓(-1 表示外部轮廓)。RETR_TREE适合复杂嵌套结构,RETR_EXTERNAL仅提取最外层轮廓。
2.4 轮廓近似 (cv2.approxPolyDP)
近似轮廓点集,减少点数,简化形状。
示例:轮廓近似
import cv2
import numpy as np
# 读取图像并转换为灰度
img = cv2.imread('shapes.jpg')
if img is None:
print("错误:无法加载图像")
exit()
# 转换为灰度并二值化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 查找轮廓
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 绘制原始和近似轮廓
img_contours = img.copy()
for cnt in contours:
# 原始轮廓
cv2.drawContours(img_contours, [cnt], -1, (0, 255, 0), 2)
# 近似轮廓
epsilon = 0.02 * cv2.arcLength(cnt, True) # 近似精度
approx = cv2.approxPolyDP(cnt, epsilon, True)
cv2.drawContours(img_contours, [approx], -1, (0, 0, 255), 2)
print(f"原始点数: {len(cnt)}, 近似点数: {len(approx)}")
# 显示结果
cv2.imshow('原始图像', img)
cv2.imshow('轮廓与近似', img_contours)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 保存结果
cv2.imwrite('approx_contours.jpg', img_contours)
输出示例:
原始点数: 120, 近似点数: 4
原始点数: 200, 近似点数: 8
说明:
approxPolyDP:用多边形近似轮廓,epsilon控制近似精度。- 适合将复杂轮廓简化为矩形、圆形等简单形状。
三、综合示例:轮廓检测流水线
结合预处理、轮廓检测、属性计算和近似:
import cv2
import numpy as np
def contour_pipeline(image_path):
"""轮廓检测流水线"""
# 读取图像
img = cv2.imread(image_path)
if img is None:
print("错误:无法加载图像")
return
# 转换为灰度并高斯模糊
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# 二值化(Otsu 方法)
_, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 查找轮廓
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 绘制轮廓并计算属性
img_contours = img.copy()
for i, cnt in enumerate(contours):
area = cv2.contourArea(cnt)
perimeter = cv2.arcLength(cnt, True)
x, y, w, h = cv2.boundingRect(cnt)
epsilon = 0.02 * perimeter
approx = cv2.approxPolyDP(cnt, epsilon, True)
# 过滤小轮廓
if area > 100:
print(f"轮廓 {i}: 面积={area:.2f}, 周长={perimeter:.2f}, 点数={len(approx)}")
cv2.drawContours(img_contours, [cnt], -1, (0, 255, 0), 2)
cv2.rectangle(img_contours, (x, y), (x+w, y+h), (255, 0, 0), 1)
# 显示结果
cv2.imshow('原始图像', img)
cv2.imshow('二值图像', binary)
cv2.imshow('轮廓与边界框', img_contours)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 保存结果
cv2.imwrite('contours_pipeline.jpg', img_contours)
# 使用示例
contour_pipeline('shapes.jpg') # 替换为你的图像路径
四、应用示例:形状识别
结合轮廓检测和近似识别简单形状(如三角形、矩形):
import cv2
import numpy as np
# 读取图像并转换为灰度
img = cv2.imread('shapes.jpg')
if img is None:
print("错误:无法加载图像")
exit()
# 转换为灰度并高斯模糊
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# 二值化
_, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 查找轮廓
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 绘制并识别形状
img_shapes = img.copy()
for cnt in contours:
area = cv2.contourArea(cnt)
if area < 100: # 过滤小轮廓
continue
# 近似轮廓
epsilon = 0.02 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
# 判断形状
if len(approx) == 3:
shape = "三角形"
elif len(approx) == 4:
shape = "矩形"
elif len(approx) > 8:
shape = "圆形"
else:
shape = f"{len(approx)}边形"
# 绘制轮廓和标签
cv2.drawContours(img_shapes, [cnt], -1, (0, 255, 0), 2)
x, y = approx[0][0]
cv2.putText(img_shapes, shape, (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
# 显示结果
cv2.imshow('原始图像', img)
cv2.imshow('形状识别', img_shapes)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 保存结果
cv2.imwrite('shapes_recognized.jpg', img_shapes)
说明:
- 通过
approxPolyDP的点数判断形状。 putText添加形状标签,便于可视化。
五、注意事项
- 输入图像:
- 轮廓检测需要二值图像,建议使用阈值处理或 Canny 边缘检测生成。
- 预处理(如高斯模糊)可提高轮廓质量。
- 参数调整:
epsilon控制近似精度,需根据轮廓复杂度调整。- 过滤小轮廓(通过
contourArea)避免噪声干扰。
- 性能优化:
- 使用
CHAIN_APPROX_SIMPLE减少内存使用。 - 对大图像应用 ROI 降低计算量。
- 错误处理:
- 检查
imread返回值,防止图像加载失败。 - 确保二值图像格式正确(
uint8)。
- 层次结构:
- 选择合适的检索模式(
RETR_EXTERNAL或RETR_TREE)根据需求。
六、资源
- 官方文档:https://docs.opencv.org/master/d4/d73/tutorial_py_contours_begin.html
- imgproc 模块:https://docs.opencv.org/master/d7/d1d/tutorial_how_to_use_IPP.html
- 社区:在 X 平台搜索
#opencv获取最新讨论。
如果你需要更深入的轮廓检测示例(如复杂形状分析、轮廓匹配)或 C++ 实现代码,请告诉我,我可以提供详细的解决方案或针对特定任务的优化!