OpenCV 图像轮廓检测

OpenCV 图像轮廓检测 教程(中文)重点讲解 OpenCV 中用于轮廓检测的核心功能,主要基于 imgproc 模块的 findContoursdrawContours 函数。轮廓检测用于提取图像中对象的边界,广泛应用于形状分析、对象识别和图像分割。本教程涵盖轮廓检测、绘制、属性计算(如面积、周长)、层次结构分析,以及实际应用示例,提供清晰的 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 添加形状标签,便于可视化。

五、注意事项

  1. 输入图像
  • 轮廓检测需要二值图像,建议使用阈值处理或 Canny 边缘检测生成。
  • 预处理(如高斯模糊)可提高轮廓质量。
  1. 参数调整
  • epsilon 控制近似精度,需根据轮廓复杂度调整。
  • 过滤小轮廓(通过 contourArea)避免噪声干扰。
  1. 性能优化
  • 使用 CHAIN_APPROX_SIMPLE 减少内存使用。
  • 对大图像应用 ROI 降低计算量。
  1. 错误处理
  • 检查 imread 返回值,防止图像加载失败。
  • 确保二值图像格式正确(uint8)。
  1. 层次结构
  • 选择合适的检索模式(RETR_EXTERNALRETR_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++ 实现代码,请告诉我,我可以提供详细的解决方案或针对特定任务的优化!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注