Sub.1 绘制图像轮廓
It.1 核心概念 🏖️
1)什么是轮廓?
- 定义:由连续像素点组成的闭合曲线,反映物体的基本外形特征
- 本质原理:通过检测像素值突变(边缘)形成的闭合路径
- 与边缘区别:
| 特征 | 轮廓 | 边缘 |
|---|---|---|
| 连续性 | 必须闭合且连续 | 可以是不连续的线段 |
| 数据结构 | 有序点序列(矢量特征) | 离散像素点集合(栅格特征) |
| 应用场景 | 形状分析、目标识别 | 特征提取、图像分割 |
| 关系 | 由边缘连接形成 | 构成轮廓的基础元素 |
2)轮廓的作用?
- 形状分析:计算面积、周长等几何特征
- 目标识别:通过形状匹配识别特定物体(如手势识别)
- 图像分割:提取感兴趣区域(ROI)
- 工业检测:零件尺寸测量、缺陷检测
- 医学影像:器官轮廓提取分析
2)轮廓的作用?
- 形状分析:计算面积、周长等几何特征
- 目标识别:通过形状匹配识别特定物体(如手势识别)
- 图像分割:提取感兴趣区域(ROI)
- 工业检测:零件尺寸测量、缺陷检测
- 医学影像:器官轮廓提取分析
It.2 寻找轮廓 🏖️
contours, hierarchy = cv2.findContours(image,mode,method)
# 查找轮廓
contours, hierarchy = cv2.findContours(binary,
cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
1. 参数:(二值化图像,查找方式,存储方式)
(0)image参数:
- 必须是二值图像
- 目标区域需为白色(255),背景为黑色(0)
(1)mode参数:轮廓查找方式
| 模式 | 特点 |
|---|---|
| RETR_EXTERNAL | 只检测最外层轮廓 → 类似描边,最常用 |
| RETR_LIST | 检测所有轮廓,不提供父子轮廓索引 → 无层级关系 |
| RETR_CCOMP | 所有轮廓,组织为内外层级 → 层级0:外;层级1:内 |
| RETR_TREE | 完整层级树结构(父子关系明确)→ 外root,内树枝 |
(2)method参数:轮廓存储方式
- CHAIN_APPROX_NONE :存储所有轮廓点的坐标。
- CHAIN_APPROX_SIMPLE:默认使用! 只存储有用的点。
- e.g. 直线只存 2 端点,矩形只存 4 角点
- CHAIN_APPROX_TC89_L1:最精确,最复杂,会使用更复杂的算法来存储轮廓点。
2. 返回值:
-
contours:轮廓点列表- 示例:
contours[0]表示第一条轮廓的所有点坐标
- 示例:
-
hierarchy:轮廓间的层级关系,仅用于复杂嵌套结构- 对于第i条轮廓,有四个索引,
hierarchy[i][x] [Next, Previous, First_Child, Parent](不存在则为-1)- 外轮廓:父 → 内轮廓:子
- 对于第i条轮廓,有四个索引,
3. 注意点
- 二值化质量决定成败,建议使用OTSU算法或自适应阈值
- 原理:遍历每个像素,只要邻域有相反像素,就认为是一个边界点像素
- 所以,预处理推荐使用中值滤波
It.3 绘制轮廓 🏖️
cv2.drawContours(image, contours, contourIdx, color, thickness)
# 绘制轮廓
cv2.drawContours(image, contours, -1, (0, 255, 0), 2)
- 参数
contourldx** **:要绘制的索引轮廓,默认-1绘制所有thickness:线宽;正数实线;负数填充
- 返回值:None,在原图上绘制
- 注意点
- 原图会被修改:建议使用
img.copy()保护原始数据 - 彩色绘制:需先将灰度图转BGR格式
- 层级绘制:结合hierarchy参数可实现分层着色
- 原图会被修改:建议使用
四、代码模式 + 可视化图解
Study Example 🍀
1. 根据提取对象的颜色选择阈值法
import cv2 as cv
"""
绘制图像轮廓
"""
# 读取图像
num = cv.imread("images/num_black.png")
graph = cv.imread("images/graph_white.png")
# 二值化
num_gray = cv.cvtColor(num, cv.COLOR_BGR2GRAY)
graph_gray = cv.cvtColor(graph, cv.COLOR_BGR2GRAY)
# num 是黑色目标白色背景,使用反阈值法
_, num_binary = cv.threshold(num_gray, 0, 255, cv.THRESH_OTSU+cv.THRESH_BINARY_INV)
# graph是白色目标黑色背景,使用阈值法
_, graph_binary = cv.threshold(graph_gray, 0, 255, cv.THRESH_OTSU+cv.THRESH_BINARY)
cv.imshow("num_binary", num_binary)
cv.imshow("graph_binary", graph_binary)
# 查找轮廓:cv.findContours(二值化图像,查找方式,存储方式)
num_contours, num_hierarchy = cv.findContours(num_binary, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
graph_contours, graph_hierarchy = cv.findContours(graph_binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# 绘制轮廓:cv.drawContours(图像,轮廓,轮廓索引,轮廓颜色,轮廓线宽)
# 拷贝原图,保护数据
num_copy = num.copy()
graph_copy = graph.copy()
# 绘制轮廓
cv.drawContours(num_copy, num_contours, -1, (0,255,0), -1)
cv.drawContours(graph_copy, graph_contours, -1, (255,0,0), 2)
cv.imshow("org_num", num)
cv.imshow("org_graph",graph)
cv.imshow("draw_num", num_copy)
cv.imshow("draw_graph", graph_copy)
cv.waitKey(0)
cv.destroyAllWindows()
输出

2. 假设错误选择了阈值法
import cv2 as cv
# 读图及预处理
img = cv.imread("images/erode.png")
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
_, binary = cv.threshold(gray, 0, 255, cv.THRESH_OTSU+cv.THRESH_BINARY_INV)
# 查找轮廓
contours, hierarchy = cv.findContours(binary, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
img_copy = img.copy()
cv.drawContours(img_copy, contours, -1, (0, 255, 0), 2)
# 显示结果
cv.imshow("org", img)
"""
阈值法其实没有正确二值化,但因为 mode=cv.RETR_TREE 所以会将所有轮廓都绘制出来,
"""
cv.imshow("draw", img_copy)
cv.waitKey(0)
cv.destroyAllWindows()
输出:
🌟 总结:
- 一般流程:二值化 → 查找轮廓 → 轮廓绘制
- 二值化处理的阈值选择非常重要,直接影响结果
- 绘制前复制原图,避免数据污染
- 彩色绘制需要将灰度图转换为RGB图像
Sub.2 凸包特征检测
It.1 核心概念 🎋
Pt.1 凸包
1. 什么是凸包?
- 定义:包含点集所有点的最小凸多边形
- 数学本质:任意两点连线都在多边形内部
2. 为什么要检测凸包?
- 应用:物体识别(如区分圆形和方形)、手势识别(检测手势的轮廓)、边界检测(提取物体的外部边界)。
- 重要性:简化形状,快速计算。
- 凸包能简化物体形状,去除内部细节和凹痕,保留最关键的外围特征,降低计算复杂度,适用于实时检测。
Pt.2 穷举法
-
遍历所有点对,两两配对连线
-
检查其他点是否全部位于直线同侧
方法:向量叉积;若结果符号一致(全正或全负),则在直线同侧。 -
满足条件:属于凸包边
- 不断for循环,时间复杂度高,实际应用很少用
- 主要用于理解凸包概念
Pt.3 QuickHull法
1. 原理
- 找最远点: 最左和最右的两个点m和n,形成初始边,分割为上下两包
- 分治递归:
- 对上包:找离mn最远的点p,pmn区域内的点排除
- 再找离pm、pn最远的点...
- 合并结果
2. 什么时候用QuickHull来检测凸包?
- 优点:时间复杂度低(O (n log n)),适合处理大量点,效率远高于穷举法。
- 缺点:实现较复杂,需理解分治和距离计算。
- 场景:实际应用中最常用的凸包算法,适用于图像轮廓点较多的场景(如手势识别、物体边界提取)
It.2 实际应用 🎋
Pt.1 获取凸包点
hull = cv2.convexHull(points)
- 参数:
points:必须是二维点集(N×1×2格式,numpy.ndarray)
- 返回值:凸包点的坐标数组
- 注意点:
- 由
cv2.findContours返回的contours,需先提取轮廓点,如contour = contours[0]
- 由
Pt.2 绘制凸包
- 使用绘制多边形的方法
1. 绘制多边形
- cv2.polylines(img, pts, isClosed, color, thickness=1)
pts: 包含多边形顶点坐标的数组-
使用时:
- 参数是包含特征点的数组
- 需要转换为整数,使用** **
.astype(np.int32) - 最后,将其封装到列表里
- 多个多边形,可以依次放入列表
[pts1, pts2, ...]
-
原理
-
像素坐标是整数,需要转换类型来保证
-
封装到列表:src的shape一般是
(n,2)→ 代表有n个顶点,2对应(x,y)坐标。[]封装到列表后,就变成了[array(shape=(n, 2))],OpenCV会将列表中的这个数组当成一个整体,然后去自动解析为 (n,1,2)的数组,进行后续处理。
-
-
isClosed:是否封闭图形 → True 则连接首尾顶color:可以是三元组 / 四元组 / 整数 → BGR / BGRA / 灰度值
import cv2
import numpy as np
# 1. 读取图像并预处理
image = cv2.imread('hand.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 2. 查找轮廓
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contour = contours[0] # 取第一个轮廓(假设只有一个物体)
# 3. 计算凸包
hull = cv2.convexHull(contour)
# 4. 绘制凸包
cv2.polylines(image, [hull.astype(np.int32)], isClosed=True, color=(0, 0, 255), thickness=2)
# 5. 显示结果
cv2.imshow('Convex Hull', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
Sub.3 图像轮廓特征查找
It.1 外接矩形和最小外接矩形 🎍
1. 概念区分
- 外接矩形(Bounding Rect):(AABB)
- 轴对齐矩形:通过轮廓点的
x_min, x_max, y_min, y_max直接生成。 - 特点:矩形边平行于图像坐标轴。
- 轴对齐矩形:通过轮廓点的
- 最小外接矩形(Rotated Rect):(OBB)
- 旋转矩形:包围轮廓的最小面积矩形。
- 特点:围绕轮廓旋转找到,更贴合目标形状,计算复杂度高。
2. 如何找最小外接矩形?
-
算法:旋转卡壳法(Rotating Calipers)
核心思想:
-
1. 计算轮廓的凸包(凸多边形)。
-
2. 遍历凸包每条边作为基准边,找到距离最远的点。
-
3. 计算当前边与最远点形成的矩形面积,记录最小值。
-
3. 实际应用流程 💐
step1 查找轮廓
contours, hierarchy = cv2.findContours(image, mode, method)
step2 计算最小外接矩形
rect = cv2.minAreaRect(cnt)
step3 转换为角点坐标
box = cv2.boxPoints(rect).astype(int)
step4 绘制
cv2.drawContours(image, contours, contourIdx, color, thickness)
Tips - 外接矩形
x, y, w, h = cv2.boundingRect(cnt)
It.2 最小外接圆 🎍
1. 算法原理
-
数学-理论基础:希尔伯特圆定理
对于平面上的任意三个不在同一直线上的点,存在一个唯一的圆同时通过这三个点,且该圆是最小面积的圆(即包含这三个点的圆中半径最小的圆,也称为最小覆盖圆)。
-
Welzl 算法:
- 一种随机增量算法,用于计算给定点集的最小包围圆(可能由 2 个点(直径)或 3 个点确定)。
- 递归选择点,逐步构建最小包围圆。
- 若点不在当前圆内,则将该点作为新圆边界点,重新计算
2. 实际应用流程 💐
step1 获取最小外接圆
cv2.minEnclosingCircle(cnt)
step2 绘制
cv2.circle(img, center, radius, color, thickness)
It.3 代码模式 🎍
Study Example 🍀
import cv2
import numpy as np
# 1. 读取图像并预处理
img = cv2.imread("images/tu.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 2. 查找轮廓
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 3. 遍历每个轮廓
for cnt in contours:
# 外接矩形
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(img, (x,y), (x+w,y+h), (255,0,0), 2)
# 最小外接矩形
# 1. 计算最小外接矩形 -> 得到旋转矩形参数
rect = cv2.minAreaRect(cnt)
# 2. 获取角点数组 -> 得到旋转矩形的四个顶点坐标
box = cv2.boxPoints(rect).astype(int)
# 3. 绘制最小外接矩形
cv2.drawContours(img, [box], 0, (0,255,0), 2)
# 最小外接圆
# 1.计算最小外接圆 -> 得到圆心和半径
(cx,cy), r = cv2.minEnclosingCircle(cnt)
# 2. 绘制最小外接圆
cv2.circle(img, (int(cx),int(cy)), int(r), (0,0,255), 2)
# 显示图像
cv2.imshow('Result', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

392



被折叠的 条评论
为什么被折叠?



