OpenCV(三十六):多边形逼近与凸包

在图像处理与计算机视觉中,轮廓形状的简化与包围区域的计算是常用操作。OpenCV 提供了两个核心函数帮助我们实现这些功能:

  • 多边形逼近(Polygon Approximation, cv2.approxPolyDP
  • 凸包(Convex Hull, cv2.convexHull

二者点集输入通常来自 cv2.findContours 提取的轮廓,但使用目的和几何意义不同:多边形逼近侧重"简化形状",凸包则是"计算最小凸多边形包围区域"。

多边形逼近(approxPolyDP)

多边形逼近用于在保持轮廓形状基本特征的前提下用更少的顶点表示轮廓 。其核心算法为 Ramer--Douglas--Peucker(RDP)算法,目标是减少不必要的点,从而得到更加紧凑的多边形表示。

RDP 算法核心思想

给定一条由若干离散点组成的折线:

  1. 连接首尾两点形成一条直线作为基准;
  2. 找出所有点中 偏离该直线距离最大的点
  3. 若最大距离 > 给定阈值 ε(epsilon),则说明折线在该点处发生显著弯曲:
    • 将折线分成两段,递归处理;
  4. 若最大距离 ≤ ε,则说明所有点几乎在同一条直线上,可以直接用首尾点替代中间点。

最终即可得到一个近似该轮廓的较低复杂度折线(多边形)。

ε(epsilon)参数的意义

OpenCV 中的 epsilon逼近精度(容差),常设为周长的某个比例:

python 复制代码
epsilon = 0.01 * cv2.arcLength(cnt, True)

epsilon 越大:

顶点更少,形状更粗略

epsilon 越小:

保留更多细节,顶点更多

多边形逼近的应用场景

  • 判断几何形状:三角形、四边形、五边形等;
  • 简化物体外轮廓,用于目标识别;
  • 动态检测手势(手部轮廓简化);
  • 降低计算量(减少点集规模);
  • 针对复杂边缘进行平滑处理。

示例

python 复制代码
import cv2
import numpy as np

img = cv2.imread("shape.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

for cnt in contours:
    epsilon = 0.02 * cv2.arcLength(cnt, True)
    approx = cv2.approxPolyDP(cnt, epsilon, True)
    
    # 绘制逼近多边形
    cv2.drawContours(img, [approx], -1, (0, 0, 255), 2)

cv2.imshow("Polygon Approx", img)
cv2.waitKey(0)

执行效果:

凸包(Convex Hull)

凸包(Convex Hull)是指 用最少的点构成一个凸多边形,使所有点都在该多边形内或边界上

常见比喻:

把钉子钉在木板上,然后用橡皮筋套住所有钉子,最终橡皮筋形成的轮廓就是凸包。

凸包的数学定义

给定一个点集 P,凸包 H§ 是一个满足:

  • H§ 是凸集;
  • P ⊆ H§;
  • H§ 是包含 P 的所有凸集中的最小一个。

OpenCV 默认使用 Graham Scan 或 Andrew's Monotone Chain 算法

凸包算法一般复杂度 O(n log n),核心思想:

  1. 按 x 坐标(次按 y 坐标)排序;
  2. 构建上包络(upper hull)和下包络(lower hull);
  3. 使用几何叉积判断"左转/右转"决定保留或舍弃某些点;
  4. 合并上下包络形成最终凸包。

凸包的常用用途

  • 检测物体的外接凸边界;
  • 手势识别(检测手指数量、指尖位置);
  • 作为形状特征,计算形状紧致度/凸度;
  • 碰撞检测、物理模拟中的包围体。

示例

python 复制代码
import cv2
import numpy as np

img = cv2.imread("shape.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

for cnt in contours:
    hull = cv2.convexHull(cnt)
    
    # 绘制凸包
    cv2.drawContours(img, [hull], -1, (255, 0, 0), 2)

cv2.imshow("Convex Hull", img)
cv2.waitKey(0)

执行效果:

综合示例

python 复制代码
import cv2
import numpy as np

# -------------------------
# Step 1: 图像读取与预处理
# -------------------------
img = cv2.imread("hand.png")                         # 输入图像
orig = img.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (7, 7), 0)
_, thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

# 查找轮廓
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnt = max(contours, key=cv2.contourArea)             # 取最大轮廓(手势或主要目标)


# -------------------------
# Step 2: 多边形逼近 approxPolyDP
# -------------------------
epsilon = 0.01 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)


# -------------------------
# Step 3: 凸包 convexHull
# -------------------------
# returnPoints=True 返回点坐标(用于绘图)
hull_pts = cv2.convexHull(cnt)

# returnPoints=False 返回索引(用于凸缺陷)
hull_idx = cv2.convexHull(cnt, returnPoints=False)


# -------------------------
# Step 4: 凸缺陷 convexityDefects
# -------------------------
defects = cv2.convexityDefects(cnt, hull_idx)


# -------------------------
# Step 5: 绘制结果
# -------------------------

# ① 绘制原轮廓
cv2.drawContours(orig, [cnt], -1, (0, 255, 0), 2)

# ② 绘制多边形逼近(红色)
cv2.drawContours(orig, [approx], -1, (0, 0, 255), 2)

# ③ 绘制凸包(蓝色)
cv2.drawContours(orig, [hull_pts], -1, (255, 0, 0), 2)

# ④ 绘制凸缺陷点(绿色)
if defects is not None:
    for i in range(defects.shape[0]):
        s, e, f, depth = defects[i, 0]
        start = tuple(cnt[s][0])
        end   = tuple(cnt[e][0])
        far   = tuple(cnt[f][0])      # 凸缺陷最深点(凹陷点)
        
        cv2.circle(orig, start, 6, (0, 255, 255), -1)  # 起点(指节)
        cv2.circle(orig, end, 6, (0, 255, 255), -1)    # 终点
        cv2.circle(orig, far, 6, (0, 255, 0), -1)      # 凹陷处(指缝)


# -------------------------
# Step 6: 显示结果
# -------------------------
cv2.imshow("Threshold", thresh)
cv2.imshow("Contour Analysis", orig)
cv2.waitKey(0)
cv2.destroyAllWindows()

执行效果:

总结

对比项 多边形逼近(approxPolyDP) 凸包(convexHull)
核心功能 轮廓简化 计算最小凸包围区域
输出 近似原轮廓的多边形 覆盖所有点的最小凸多边形
是否保持原形状凹陷 否(会"撑平"所有凹陷)
是否保持点顺序 是(可返回索引)
顶点数量 取决于 epsilon 通常较少
常用场景 边缘简化、检测三角/矩形等形状 手势分析、碰撞检测、几何包围
相关推荐
_Twink1e43 分钟前
【HCIA-AIV4.0】2025题库+解析(三)
人工智能·笔记·华为·开源
北堂飘霜1 小时前
AI 求职工具评测:简小派 vs Jobscan、Teal 与国内同类
人工智能
糖果罐子♡1 小时前
在 openEuler 上快速体验 PyTorch 深度学习
人工智能·pytorch·深度学习
周杰伦_Jay1 小时前
【Conda 完全指南】环境管理+包管理从入门到精通(含实操示例+表格对比)
开发语言·人工智能·微服务·架构·conda
暗碳1 小时前
ai分析aweme-app.xml,default_config .xml文件
xml·人工智能
凌晨一点的秃头猪1 小时前
构建视觉词典(visual vocabulary / codebook)
人工智能
PS1232321 小时前
城市安全建设中的风环境监测解决方案
大数据·人工智能
梯度下降不了班1 小时前
【mmodel/xDiT】多模态^_^从入门到放弃的学习路径
人工智能·学习·stable diffusion