一、图像轮廓

图像轮廓是图像中连通区域的边界线条,它能精准描述物体的形状和结构,是从 “像素” 到 “物体形态” 的重要桥梁。

1、轮廓的核心特性

与边缘的区别:边缘是单个像素级的灰度突变(如边缘检测得到的零散点),而轮廓是连续的、闭合的边界,需要基于连通区域形成。
依赖二值图像:轮廓查找只能在二值图像(只有黑、白两种像素,即 0 和 255)上进行,因为需要明确区分 “前景物体”(通常为白色,像素值 255)和 “背景”(通常为黑色,像素值 0)。
包含形态信息:轮廓自带物体的面积、周长、位置、凹凸性等信息,通过这些信息可进一步分析物体的大小、形状(如圆形、矩形)。

2、轮廓查找的关键参数(以 OpenCV 为例)

在 OpenCV 中,核心函数是 cv2.findContours(),其结果和用法完全依赖两个关键参数,这也是控制轮廓提取效果的核心:

参数类别 关键选项 作用说明
轮廓检索模式 cv2.RETR_EXTERNAL 只提取最外层轮廓,忽略物体内部的空洞轮廓
cv2.RETR_TREE 提取所有轮廓,并建立轮廓间的层级关系(如父子轮廓)
轮廓逼近方法 cv2.CHAIN_APPROX_SIMPLE 压缩轮廓冗余点(如水平 / 垂直线只保留端点),减少内存 占用
cv2.CHAIN_APPROX_NONE 保留轮廓的所有像素点,精度高但内存消耗大

3、轮廓的常用处理操作

找到轮廓后,通过以下操作可进一步挖掘物体形态信息,这也是实际应用中最常用的功能:
1)绘制轮廓:用 cv2.drawContours() 将轮廓画在原图上,方便可视化(如用红色线条标注物体边界)。
2)形态参数计算:
面积:cv2.contourArea(contour),判断物体大小(如过滤小噪点轮廓)。
周长:cv2.arcLength(contour, closed),closed=True 表示轮廓是闭合的(如圆形周长)。
3)形状拟合与逼近:
多边形逼近:cv2.approxPolyDP(),将不规则轮廓简化为近似多边形(如把圆形轮廓简化为正六边形)。
外接矩形:cv2.boundingRect()(轴对齐矩形)、cv2.minAreaRect()(旋转矩形),用于定位物体位置和范围。
4)凸包检测:cv2.convexHull(),找到轮廓的 “最小凸边界”(如把凹陷的轮廓补成凸形),常用于判断物体的凹凸性。

4、轮廓的典型应用场景

1)目标计数:如工业场景中 “统计生产线上的零件数量”,通过计数轮廓数量实现。
2)形状识别:如区分 “圆形纽扣” 和 “方形纽扣”,通过轮廓的多边形逼近结果(圆形逼近后边数多,方形逼近后为 4 条边)判断。
3)目标分割与提取:如从复杂背景中 “抠出单个物体”,通过轮廓确定物体的像素范围,再裁剪出目标区域。
4)尺寸测量:如基于轮廓的面积、外接矩形尺寸,计算实际物体的大小(需提前校准像素与实际尺寸的比例)。

5、常见问题与避坑点

1)轮廓找不到:优先检查二值图像是否正确 —— 确保前景是白色(255)、背景是黑色(0),若颠倒可通过 cv2.THRESH_BINARY_INV 反相二值化。
2)轮廓有噪点:用 “面积过滤”(如忽略面积小于 50 的轮廓)或 “形态学操作”(如先腐蚀再膨胀,消除小噪点)预处理图像。
3)轮廓不闭合:检查二值图像中物体是否有 “断点”(如前景像素未完全连通),可通过 cv2.dilate() 膨胀操作补全断点。

二、查找轮廓,绘制轮廓

1.查找轮廓

查找轮廓是从二值图像中提取物体边界的过程,必须基于二值图像(仅含 0 和 255 两种像素值,前景为 255,背景为 0)。
核心API:

contours, hierarchy = cv2.findContours(image, mode, method)

两个返回值,contours和hierarchy
mode
RETR_EXTERNAL=0,表示只检测外轮廓
RETR_LIST=1,检测的轮廓不建立等级关系
RETR_CCOMP=2,每层最多两级
RETR_TREE=3,按树形存储轮廓
method
CHAIN_APPROX_NONE,保存所有轮廓上的点
CHAIN_APPROX_SIMPLE,只保存角点

2,绘制轮廓

核心API:

cv2.drawContours(image, contours, contourIdx, color, thickness, lineType)

参数 作用 示例值
image 目标图像(在该图像上绘制轮廓,通常为原图的副本) img_copy(原图的副本,避免修改原图)
contours 待绘制的轮廓列表(即cv2.findContours()返回的contours) -
contourIdx 要绘制的轮廓索引(-1表示绘制所有轮廓) 0(绘制第 1 个轮廓)、-1(绘制所有轮廓)
color 轮廓颜色(BGR 格式,而非 RGB) (0,0,255)(红色)、(0,255,0)(绿色)
thickness 轮廓线宽度(-1表示填充轮廓内部) 2(线宽为 2 像素)、-1(填充轮廓)
lineType 线条类型(通常默认即可)

3.实验代码

#1
#轮廓查找
#查找的图像要是二值化的
import cv2
import numpy as np
cv2.namedWindow('cont',cv2.WINDOW_NORMAL)
cv2.namedWindow('binary',cv2.WINDOW_NORMAL)
#读文件


#img = cv2.imread('contours1.png')
# img = cv2.imread('hand2.png')
img = cv2.imread('hello.png')
print(img.shape)
#转变为单通道
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print(gray.shape)

#二值化,
binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)[1]
print(binary.shape)

#轮廓查找
contours,hierarchy = cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
print(contours)

#绘制轮廓
cv2.drawContours(img,contours,-1,(0,0,255),1)

输出结果对比:

(204, 433, 3)
(204, 433)
(204, 433)
(array([[[  0,   0]],

       [[  0, 203]],

       [[432, 203]],

       [[432,   0]]], dtype=int32), array([[[127, 117]],

       [[128, 116]],

       [[130, 118]],

       [[129, 119]],

#等等.......

在这里插入图片描述

三,轮廓的面积与周长

在轮廓分析中,面积和周长是描述轮廓(物体)形态的两个基础几何特征:面积反映物体的大小,周长反映物体边界的总长度。OpenCV 提供了专门的 API 计算这两个参数,下面详细介绍:

1、轮廓的面积(Contour Area)

轮廓的面积是指轮廓所包围的像素点总数(或物理面积,需结合像素比例尺),单位为像素 ²(默认)。
1)核心 API:cv2.contourArea()

area = cv2.contourArea(contour, oriented=False)

2)参数说明:
contour:单个轮廓(即 cv2.findContours() 返回的 contours 列表中的一个元素,格式为 N×1×2 的 numpy 数组)。
oriented:布尔值,默认 False。
False:返回面积的绝对值(恒为正数)。
True:返回带符号的面积(符号由轮廓的顺时针 / 逆时针方向决定,通常用不到)。
3). 返回值:
浮点型(float)数值,表示轮廓的面积(单位:像素 ²)。

2.轮廓的周长(Contour Perimeter)

轮廓的周长是指轮廓边界上所有点连接成的闭合曲线的总长度,单位为像素(默认)。
1). 核心 API:cv2.arcLength()

perimeter = cv2.arcLength(contour, closed)

2). 参数说明:
contour:单个轮廓(同面积计算,为 contours 列表中的一个元素)。
closed:布尔值,指定轮廓是否闭合。
True:认为轮廓是闭合的(首尾点连接),计算闭合曲线的长度。
False:认为轮廓是开放的(首尾点不连接),计算曲线的实际长度。

3.) 返回值:
浮点型(float)数值,表示轮廓的周长(单位:像素)。

3.实验代码

#2
#接上述代码
#计算面积
area = cv2.contourArea(contours[0])
print("area=%d"%(area))

#计算周长
len =cv2.arcLength(contours[0],True)
print("len=%d"%(len))

输出结果:

area=87696
len=1270

四,多边形逼近和凸包

多边形逼近是把不规则轮廓简化成近似多边形,凸包是找到包裹轮廓的最小凸形边界,两者都能帮我们快速提取物体的核心形状特征。

1、多边形逼近(Polygon Approximation)

多边形逼近的核心思想是:用更少的顶点组成的多边形,去 “近似替代” 原始的复杂轮廓,同时尽可能保留轮廓的整体形状,从而减少数据量、简化后续分析。
1). 核心 API:cv2.approxPolyDP()

approx = cv2.approxPolyDP(contour, epsilon, closed)

2). 参数详解
参数名 作用说明 关键注意点
contour 输入的单个轮廓(即cv2.findContours()返回的contours列表中的一个元素) 必须是N×1×2格式的 numpy 数组,存储轮廓的坐标点
epsilon 逼近精度(距离阈值),单位是像素 决定简化程度:值越小,逼近结果越接近原始轮廓;值越大,多边形顶点越少(越简化)
closed 布尔值,指定逼近后的多边形是否闭合

3). 返回值
approx:逼近后的多边形顶点列表,格式与输入contour一致(M×1×2的 numpy 数组,M为多边形顶点数)。
4). 关键逻辑
epsilon的取值是核心,通常不直接写固定值,而是根据轮廓周长的比例计算,公式参考:epsilon = 0.02 * cv2.arcLength(contour, True)(0.02 是经验系数,可根据需求调整,范围一般在 0.01~0.1 之间)

2、凸包(Convex Hull)

凸包的核心思想是:找到一个 “最小的凸多边形”,让这个多边形能完全包裹住原始轮廓,且轮廓上所有点都在这个凸多边形内部或边上。简单说,就是 “把轮廓的凹陷部分补平,得到最紧凑的凸形边界”。
1). 核心 API:cv2.convexHull()

hull = cv2.convexHull(contour, returnPoints=True)

2). 参数详解
参数名 作用说明 常用选项
contour 输入的单个轮廓(同多边形逼近,N×1×2格式的 numpy 数组) -
returnPoints 布尔值,指定返回结果的类型 True(默认):返回凸包的顶点坐标(M×1×2数组)
False:返回凸包顶点在原始轮廓中的索引
clockwise 布尔值,指定凸包顶点的排列顺序(可选参数) True:顺时针排列
False:逆时针排列(默认,通常无需修改)
minPoints 构建凸包所需的最小点数(可选参数,默认 3,即至少 3 个点才会生成凸包)
3). 返回值
当returnPoints=True时,hull是凸包的顶点坐标列表(M×1×2数组),可直接用于绘制;
当returnPoints=False时,hull是索引列表(M×1数组),需通过contour[hull]获取顶点坐标。

3.实验代码

#接1
##多边形逼近
def drawshape(src,points):
    i = 0
    while i < len(points):
        if(i == len(points) - 1):
            x,y=points[i][0]
            x1,y1 = points[0][0]
            cv2.line(src,(x,y),(x1,y1),(255,0, 255), 3)
        else:
            x, y = points[i][0]
            x1, y1 = points[i+1][0]
            cv2.line(src, (x, y), (x1, y1), (255, 0, 255), 3)
        i = i + 1



e = 20
approx = cv2.approxPolyDP(contours[0],e,True)
drawshape(img,approx)


#凸包
hull = cv2.convexHull(contours[0])
drawshape(img,hull)

输出结果对比:
在这里插入图片描述

五, 外接矩形

外接矩形是围绕轮廓的 “最小矩形边界”,能精准定位轮廓的位置和范围,是目标检测中 “定位物体” 的核心工具。OpenCV 提供了两种常用的外接矩形 API,分别对应轴对齐矩形(不考虑旋转)和旋转矩形(贴合轮廓旋转角度),下面详细介绍:

1、外接矩形的两种类型

在轮廓分析中,外接矩形分为两类,适用场景不同,需根据需求选择:
轴对齐外接矩形:矩形的边严格平行于图像的水平 / 垂直轴,计算简单、速度快,但可能包含较多背景区域(当轮廓旋转时)。
最小面积外接矩形:矩形会随轮廓的旋转角度调整,能以最小的面积包裹轮廓,更贴合物体实际形状,但计算稍复杂,且返回结果包含旋转角度。

2、轴对齐外接矩形(cv2.boundingRect())

1). 核心 API:cv2.boundingRect()
该 API 直接计算轮廓的 “水平 - 垂直边界”,返回矩形的左上角坐标和宽高,格式简洁,适合快速定位。

x, y, w, h = cv2.boundingRect(contour)

2). 参数与返回值
类别 细节说明
输入参数 contour:
单个轮廓(即 cv2.findContours() 返回的 contours 列表中的一个元素,格式为 N×1×2 的 numpy 数组)。
返回值
- x:矩形左上角的 x 坐标(水平方向);
- y:矩形左上角的 y 坐标(垂直方向);
- w:矩形的 宽度(水平方向长度);
- h:矩形的 高度(垂直方向长度)。

3.) 绘制方法
通过 cv2.rectangle() 绘制轴对齐矩形,直接使用返回的 x, y, w, h 即可:

# 绘制轴对齐矩形(绿色,线宽2)
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)

3、最小面积外接矩形(cv2.minAreaRect())

1). 核心 API:cv2.minAreaRect()
该 API 计算能包裹轮廓的 “最小面积矩形”,返回结果包含矩形的中心坐标、宽高和旋转角度,需进一步转换为顶点坐标才能绘制。

min_rect = cv2.minAreaRect(contour)

2). 参数与返回值
类别 细节说明
输入参数 :contour:
单个轮廓(同轴对齐矩形,格式为 N×1×2 的 numpy 数组)。
返回值 :
min_rect 是一个元组,包含 3 个元素:

  • (cx, cy):矩形的 中心坐标;
  • (w, h):矩形的 宽和高(注意:w 不一定大于 h,取决于旋转角度);
  • angle:矩形的 旋转角度(单位:度,范围在 [-90, 0) 之间,表示矩形相对于水平轴的逆时针旋转角度)。
    3). 关键步骤:转换为顶点坐标
    minAreaRect() 返回的是 “矩形参数”(中心、宽高、角度),无法直接用于绘制,需通过 cv2.boxPoints() 转换为 4 个顶点的坐标:
# 1. 将矩形参数转换为4个顶点坐标(浮点型)
box = cv2.boxPoints(min_rect)
# 2. 转换为整数坐标(OpenCV 绘制需整数坐标)
box = np.int32(box)  # 或 np.int64(box),兼容所有 numpy 版本

4.) 绘制方法
通过 cv2.drawContours() 绘制旋转矩形(将 4 个顶点视为一个轮廓):

# 绘制最小面积外接矩形(红色,线宽2)
cv2.drawContours(img, [box], -1, (0, 0, 255), 2)

4.实验代码

import cv2
import numpy as np

# 创建窗口
cv2.namedWindow('cont', cv2.WINDOW_NORMAL)
cv2.namedWindow('binary', cv2.WINDOW_NORMAL)

# 读取图像
img_path = 'hello.png'
img = cv2.imread(img_path)
if img is None:
    raise ValueError(f"无法读取图像 {img_path},请检查文件路径是否正确")
print(f"原始图像形状: {img.shape}")

# 复制原图用于绘制(避免修改原图时的潜在问题)
draw_img = img.copy()

# 转为单通道灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print(f"灰度图形状: {gray.shape}")

# 二值化处理(自动阈值可能更适合不同图像)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)  # 尝试反相阈值,解决前景背景颠倒问题
print(f"二值化图像形状: {binary.shape}")

# 轮廓查找(尝试不同的检索模式)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  # 只找最外层轮廓
if not contours:
    # 尝试另一种阈值模式再找一次
    _, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
    contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if not contours:
        raise ValueError("未找到任何轮廓,请检查图像是否为二值化的前景物体")
print(f"找到轮廓数量: {len(contours)}")

# 绘制所有轮廓(红色)
cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 2)

# 遍历所有轮廓,为每个轮廓绘制外接矩形(确保不遗漏)
for i, cnt in enumerate(contours):
    # 计算轮廓面积,过滤过小的噪点轮廓
    area = cv2.contourArea(cnt)
    if area < 50:  # 过滤面积小于50的小轮廓
        continue
    print(f"轮廓 {i} - 面积: {area:.2f}")

    # 1. 最小外接矩形(旋转矩形,蓝色)
    min_rect = cv2.minAreaRect(cnt)
    box = cv2.boxPoints(min_rect)
    box = np.int64(box)  # 转换为整数坐标
    cv2.drawContours(draw_img, [box], -1, (255, 0, 0), 2)  # 蓝色

    # 2. 最大外接矩形(轴对齐,绿色)
    x, y, w, h = cv2.boundingRect(cnt)
    cv2.rectangle(draw_img, (x, y), (x + w, y + h), (0, 255, 0), 2)  # 绿色

    # 在矩形旁标注轮廓索引
    cv2.putText(draw_img, f"cnt{i}", (x, y - 10),
                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 2)

# 显示结果
cv2.imshow('binary', binary)  # 先看二值化结果是否正确
cv2.imshow('cont', draw_img)
print("按任意键关闭窗口...")
cv2.waitKey(0)
cv2.destroyAllWindows()

输出结果:
在这里插入图片描述

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐