> 技术文档 > OpenCV图像梯度、边缘检测、轮廓绘制、凸包检测大合集

OpenCV图像梯度、边缘检测、轮廓绘制、凸包检测大合集


一、图像梯度

在图像处理中,「梯度(Gradient)」是一个非常基础但又极其重要的概念。它是图像边缘检测、特征提取、纹理分析等众多任务的核心。梯度的本质是在空间上描述像素灰度值变化的快慢和方向。

但我们如何在图像中计算梯度?又该选择什么样的算子?本文将从梯度的数学定义出发,逐步引入经典的 Sobel 与 Laplacian 算子,带你了解图像梯度的计算原理与实践方式。

1.1 什么是图像梯度?

图像梯度反映的是像素值(灰度或强度)在空间中的变化率。可以类比为地形图中的“坡度”:哪里灰度变化剧烈,哪里就是图像的“边缘”。

对于二维灰度图像I(x,y)I(x,y)I(x,y),梯度定义为图像对空间坐标的偏导数组成的向量:
∇I=[∂I∂x,∂I∂y]\\nabla I = \\left[ \\frac{\\partial I}{\\partial x}, \\frac{\\partial I}{\\partial y} \\right]I=[xI,yI]

  • ∂I∂x\\frac{\\partial I}{\\partial x}xI:表示图像在水平方向(x轴)上的变化率;
  • ∂I∂y\\frac{\\partial I}{\\partial y}yI:表示图像在垂直方向(y轴)上的变化率。

该向量的模长表示梯度的强度,方向表示灰度变化最剧烈的方向。

1.2 如何计算梯度

由于图像是离散的,我们不能直接求导,而是通过离散卷积实现近似求导

使用cv2.filter2D自定义卷积核

OpenCV中filter2D可以对图形施加自定义的卷积核,是实现梯度算子的基础方法

语法如下所示:

dst = cv2.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]])

filter2D函数是用于对图像进行二维卷积(滤波)操作。它允许用户自定义卷积核(kernal)来实现各种图像处理效果,如平滑,锐化,边缘检测。

参数解析:

参数名 类型 说明 src ndarray 输入图像,必须是单通道或多通道(如灰度图或彩色图) ddepth int 输出图像的深度(如 cv2.CV_64F, -1 表示与原图相同) kernel ndarray 卷积核(滤波器),必须是浮点型 np.float32np.float64 dst ndarray (可选)输出图像,与 src 同大小 anchor tuple 卷积核锚点,默认 (-1, -1) 表示核中心 delta float 可选偏移值,加到卷积结果上 borderType int 边界填充方式,常见如 cv2.BORDER_DEFAULT(边界反射_101), cv2.BORDER_REPLICATE
import cv2 as cvimport numpy as np# 构造图像:中心有明显亮度突变img = np.array([ [10, 10, 10, 10, 10, 10, 10], [10, 10, 10, 255, 255, 10, 10], [10, 10, 10, 255, 255, 10, 10], [10, 10, 10, 255, 255, 10, 10], [10, 10, 10, 10, 10, 10, 10]], dtype=np.uint8)# 使用 Sobel 水平方向边缘检测核kernel = np.array([[-1, 0, 1],  [-2, 0, 2],  [-1, 0, 1]], dtype=np.float32)# 卷积img2 = cv.filter2D(img, -1, kernel)print(img2)

结果展示:

[[ 0 0 255 255 0 0 0] [ 0 0 255 255 0 0 0] [ 0 0 255 255 0 0 0] [ 0 0 255 255 0 0 0] [ 0 0 255 255 0 0 0]]

OpenCV图像梯度、边缘检测、轮廓绘制、凸包检测大合集

1.3 常见的梯度算子

1️⃣ Sobel 算子(Sobel Operator)

Sobel 是最常见的梯度算子之一,结合了高斯平滑微分运算,对噪声更鲁棒。

  • 水平方向梯度核:

Gx=[−101−202−101]Gx=\\begin{bmatrix} -1 & 0 & 1 \\\\ -2 & 0 & 2 \\\\ -1 & 0 & 1 \\end{bmatrix}Gx=121000121

  • 垂直方向梯度核:
    Gy=[−1−2−1000121]G_y = \\begin{bmatrix} -1 & -2 & -1 \\\\ 0 & 0 & 0 \\\\ 1 & 2 & 1 \\end{bmatrix}Gy=101202101

在 OpenCV 中的实现:

语法说明:

dst = cv2.Sobel(src, ddepth, dx, dy, ksize=3, scale=1, delta=0, borderType=cv2.BORDER_DEFAULT)
参数名 类型 说明 src ndarray 输入图像(通常为灰度图) ddepth int 输出图像的数据深度(OpenCV 中,-1 表示输出图像的深度与输入图像相同。) dx int x 方向求导阶数(1 表示对 x 求一阶导),获取的垂直边缘 dy int y 方向求导阶数,获取的水平边缘 ksize int Sobel 核大小(可为 1, 3, 5, 7,常用 3) scale float 可选缩放因子,对导数结果进行缩放(一般为 1) delta float 可选偏移量,结果加上 delta(一般为 0) borderType int 边界填充方式,默认 cv2.BORDER_DEFAULT

示例代码:Sobel算子的使用

# sobel算子import cv2 as cvshudu = cv.imread(\'../images/shudu.png\', cv.IMREAD_GRAYSCALE)# x方向dst_x = cv.Sobel(shudu, -1, 1, 0, ksize=3)# y方向dst_y = cv.Sobel(shudu, -1, 0, 1, ksize=3)# x和y方向dst_xy = cv.Sobel(shudu, -1, 1, 1, ksize=3)cv.imshow(\'shudu\', shudu)cv.imshow(\'dst_x\', dst_x)cv.imshow(\'dst_y\', dst_y)cv.imshow(\'dst_xy\', dst_xy)cv.waitKey(0)cv.destroyAllWindows()

结果输出:

OpenCV图像梯度、边缘检测、轮廓绘制、凸包检测大合集 OpenCV图像梯度、边缘检测、轮廓绘制、凸包检测大合集 OpenCV图像梯度、边缘检测、轮廓绘制、凸包检测大合集 OpenCV图像梯度、边缘检测、轮廓绘制、凸包检测大合集 灰度图 dx=1,dy=0(获取垂直边缘) dx=0,dy=1(获取水平边缘) dx=1,dy=1(不建议使用),用Laplacian来获取水平垂直边缘。

dxdy可以都为1,获取的垂直和水平方向上的梯度。dxdy不能都为0。

  • grad_x: 图像在 x 方向的梯度(横向变化)
  • grad_y: 图像在 y 方向的梯度(纵向变化)

我们可以将它们组成一个向量:
G⃗=(grad_x, grad_y)\\vec{G} = (grad\\_x, \\, grad\\_y)G=(grad_x,grad_y)
然后,使用勾股定理计算这个向量的长度(也就是梯度强度):
magnitude=grad_x2+grad_y2\\text{magnitude} = \\sqrt{grad\\_x^2 + grad\\_y^2}magnitude=grad_x2+grad_y2


2️⃣ Laplacian 算子(Laplacian Operator)

一、什么是 Laplacian 算子?

Laplacian(拉普拉斯算子)是二阶微分算子,用于度量函数在某点处的“变化率的变化”,即函数曲率。

在图像处理中,它能检测图像中灰度变化最显著的地方——边缘,尤其是亮度快速变化的区域,对噪声也很敏感。

数学定义如下:
Δf=∂2f∂x2+∂2f∂y2\\Delta f = \\frac{\\partial^2 f}{\\partial x^2} + \\frac{\\partial^2 f}{\\partial y^2}Δf=x22f+y22f


🧮 二、从一维差分到二维卷积核
1. 一维差分

一阶差分(梯度近似):
f′(x)≈f(x+1)−f(x)f\'(x) \\approx f(x+1) - f(x)f(x)f(x+1)f(x)
二阶差分(Laplacian 近似):
f′′(x)≈f(x+1)+f(x−1)−2f(x)f\'\'(x) \\approx f(x+1) + f(x-1) - 2f(x)f′′(x)f(x+1)+f(x1)2f(x)
对应的卷积核(差分模板)为:
k=[1,−2,1]k=[1,−2,1]k=[1,2,1]


2. 推导二维 Laplacian 卷积核

对于二维函数 f(x,y)f(x,y)f(x,y)

水平方向二阶导数:
∂2f∂x2≈f(x+1,y)+f(x−1,y)−2f(x,y)\\frac{\\partial^2 f}{\\partial x^2} \\approx f(x+1, y) + f(x-1, y) - 2f(x, y)x22ff(x+1,y)+f(x1,y)2f(x,y)
垂直方向二阶导数:
∂2f∂y2≈f(x,y+1)+f(x,y−1)−2f(x,y)\\frac{\\partial^2 f}{\\partial y^2} \\approx f(x, y+1) + f(x, y-1) - 2f(x, y)y22ff(x,y+1)+f(x,y1)2f(x,y)
将它们相加:
Δf(x,y)≈f(x+1,y)+f(x−1,y)+f(x,y+1)+f(x,y−1)−4f(x,y)\\Delta f(x, y) \\approx f(x+1, y) + f(x-1, y) + f(x, y+1) + f(x, y-1) - 4f(x, y)Δf(x,y)f(x+1,y)+f(x1,y)+f(x,y+1)+f(x,y1)4f(x,y)
这就是最常见的 4 邻域 Laplacian 模板:
k=[0101−41010]k = \\begin{bmatrix} 0 & 1 & 0 \\\\ 1 & -4 & 1 \\\\ 0 & 1 & 0 \\end{bmatrix}k=010141010


3. 加上对角(斜对角)项:8 邻域

如果你想让算子对角方向也敏感,可以扩展为:
k=[1111−81111]k = \\begin{bmatrix} 1 & 1 & 1 \\\\ 1 & -8 & 1 \\\\ 1 & 1 & 1 \\end{bmatrix}k=111181111

这种核能更广泛捕捉到不同方向的边缘,但也更敏感。

OpenCV 使用方式:

cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
参数 含义 src 输入图像,必须是灰度图 ddepth 输出图像的深度,常用 cv2.CV_64F,避免溢出 ksize 卷积核大小,必须是奇数,一般设为 1 表示使用标准核(上面那个) scale 缩放梯度值,默认 1 delta 可选的偏移值,默认 0 borderType 边缘填充方式,默认 cv2.BORDER_DEFAULT

与 Sobel 不同,Laplacian 不区分方向,输出的是一种方向无关的边缘响应

示例代码
# Laplacian算子import cv2 as cvshudu = cv.imread(\'../images/shudu.png\', cv.IMREAD_GRAYSCALE)# Laplacian算子dst = cv.Laplacian(shudu, -1, ksize=1)cv.imshow(\'shudu\', shudu)cv.imshow(\'dst\', dst)cv.waitKey(0)cv.destroyAllWindows()
OpenCV图像梯度、边缘检测、轮廓绘制、凸包检测大合集 OpenCV图像梯度、边缘检测、轮廓绘制、凸包检测大合集 灰度图 Laplacian算子

二、图像边缘检测

2.1. 什么是图像边缘?

从数学角度来看,图像边缘是图像灰度函数的一阶导数(梯度)取得极大值的位置,或二阶导数(Laplacian)为零的地方。

我们把二维图像 f(x,y)f(x,y)f(x,y) 看作一个连续函数,图像的变化速率(即灰度变化)就是它的梯度:
∇f=(∂f∂x,∂f∂y)\\nabla f = \\left( \\frac{\\partial f}{\\partial x}, \\frac{\\partial f}{\\partial y} \\right)f=(xf,yf)
梯度的模长即为边缘强度:
∣∇f∣=(∂f∂x)2+(∂f∂y)2|\\nabla f| = \\sqrt{ \\left( \\frac{\\partial f}{\\partial x} \\right)^2 + \\left( \\frac{\\partial f}{\\partial y} \\right)^2 }∣∇f=(xf)2+(yf)2


2. 2. 边缘检测的整体流程图

#mermaid-svg-K8tLy32DJDprH2ts {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-K8tLy32DJDprH2ts .error-icon{fill:#552222;}#mermaid-svg-K8tLy32DJDprH2ts .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-K8tLy32DJDprH2ts .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-K8tLy32DJDprH2ts .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-K8tLy32DJDprH2ts .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-K8tLy32DJDprH2ts .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-K8tLy32DJDprH2ts .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-K8tLy32DJDprH2ts .marker{fill:#333333;stroke:#333333;}#mermaid-svg-K8tLy32DJDprH2ts .marker.cross{stroke:#333333;}#mermaid-svg-K8tLy32DJDprH2ts svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-K8tLy32DJDprH2ts .label{font-family:\"trebuchet ms\",verdana,arial,sans-serif;color:#333;}#mermaid-svg-K8tLy32DJDprH2ts .cluster-label text{fill:#333;}#mermaid-svg-K8tLy32DJDprH2ts .cluster-label span{color:#333;}#mermaid-svg-K8tLy32DJDprH2ts .label text,#mermaid-svg-K8tLy32DJDprH2ts span{fill:#333;color:#333;}#mermaid-svg-K8tLy32DJDprH2ts .node rect,#mermaid-svg-K8tLy32DJDprH2ts .node circle,#mermaid-svg-K8tLy32DJDprH2ts .node ellipse,#mermaid-svg-K8tLy32DJDprH2ts .node polygon,#mermaid-svg-K8tLy32DJDprH2ts .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-K8tLy32DJDprH2ts .node .label{text-align:center;}#mermaid-svg-K8tLy32DJDprH2ts .node.clickable{cursor:pointer;}#mermaid-svg-K8tLy32DJDprH2ts .arrowheadPath{fill:#333333;}#mermaid-svg-K8tLy32DJDprH2ts .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-K8tLy32DJDprH2ts .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-K8tLy32DJDprH2ts .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-K8tLy32DJDprH2ts .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-K8tLy32DJDprH2ts .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-K8tLy32DJDprH2ts .cluster text{fill:#333;}#mermaid-svg-K8tLy32DJDprH2ts .cluster span{color:#333;}#mermaid-svg-K8tLy32DJDprH2ts div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-K8tLy32DJDprH2ts :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}原始图像高斯滤波\\n去噪Sobel卷积\\n计算梯度与方向非极大值抑制\\n细化边缘双阈值筛选\\n连接边缘输出边缘图像


2. 3. 高斯滤波去噪

边缘检测属于一种“锐化”操作,容易放大噪声。为此,第一步通常使用高斯滤波对图像进行平滑处理,消除小范围内的噪点干扰:

blur = cv2.GaussianBlur(img, (5, 5), 1.4)

高斯核示例(5x5):
1273[1474141626164726412674162616414741]\\frac{1}{273} \\begin{bmatrix} 1 & 4 & 7 & 4 & 1\\\\ 4 & 16 & 26 & 16 & 4\\\\ 7 & 26 & 41 & 26 & 7\\\\ 4 & 16 & 26 & 16 & 4\\\\ 1 & 4 & 7 & 4 & 1 \\end{bmatrix}27311474141626164726412674162616414741


2.4. Sobel算子计算梯度与方向

📌 Sobel 卷积核

用于计算图像在水平与垂直方向上的一阶导数:

  • 水平(x方向)

Gx=[−101−202−101]G_x = \\begin{bmatrix} -1 & 0 & 1\\\\ -2 & 0 & 2\\\\ -1 & 0 & 1 \\end{bmatrix}Gx=121000121

  • 垂直(y方向)

Gy=[−1−2−1000121]G_y = \\begin{bmatrix} -1 & -2 & -1\\\\ 0 & 0 & 0\\\\ 1 & 2 & 1 \\end{bmatrix}Gy=101202101

梯度值与方向

grad_x = cv2.Sobel(blur, cv2.CV_64F, 1, 0)grad_y = cv2.Sobel(blur, cv2.CV_64F, 0, 1)magnitude = cv2.magnitude(grad_x, grad_y)angle = cv2.phase(grad_x, grad_y, angleInDegrees=True)
  • 梯度幅值(强度):

G=Gx2+Gy2G = \\sqrt{G_x^2 + G_y^2}G=Gx2+Gy2

  • 梯度方向:

θ=arctan⁡(GyGx)\\theta = \\arctan\\left( \\frac{G_y}{G_x} \\right)θ=arctan(GxGy)


2.5. 非极大值抑制(NMS)

目的:只保留梯度方向上的局部极大值点,细化边缘线条

步骤如下:

  1. 对于每一个像素,查找其在梯度方向上的邻接像素。
  2. 如果当前像素的梯度值不是三者中最大的,就将其抑制为0。

为了比较非整数方向上的像素值,需要使用线性插值

得到θ\\thetaθ的值之后,就可以对边缘方向进行分类,为了简化计算过程,一般将其归为四个方向:水平方向、垂直方向、45°方向、135°方向。并且:

θ\\thetaθ值为-22.5°~22.5°,或-157.5°~157.5°,则认为边缘为水平边缘;

当法线方向为22.5°~67.5°,或-112.5°~-157.5°,则认为边缘为45°边缘;

当法线方向为67.5°~112.5°,或-67.5°~-112.5°,则认为边缘为垂直边缘;

当法线方向为112.5°~157.5°,或-22.5°~-67.5°,则认为边缘为135°边缘;

OpenCV图像梯度、边缘检测、轮廓绘制、凸包检测大合集


2.6. 双阈值连接(Hysteresis)

非极大值抑制后,图像中仍有很多边缘片段。通过设定高低两个阈值,连接可靠的边缘:

  • 高于高阈值 → 强边缘(保留)
  • 低于低阈值 → 弱边缘(舍弃)
  • 介于之间 → 如果与强边缘连接,则保留;否则丢弃

OpenCV图像梯度、边缘检测、轮廓绘制、凸包检测大合集

推荐设置

edges = cv2.Canny(img, threshold1=50, threshold2=150)

阈值比建议控制在 2:1 到 3:1 之间。


2.7. Canny 算子:全流程封装

OpenCV 内置的 Canny 算子封装了所有步骤:

edges = cv2.Canny(image, 50, 150)

参数说明:

  • image: 输入灰度/二值化图像
  • threshold1: 低阈值,用于决定可能的边缘点。
  • threshold2: 高阈值,用于决定强边缘点。

2.8. 总结

步骤 作用 工具/算子 高斯滤波 平滑图像,去除噪声 cv2.GaussianBlur 梯度计算 提取边缘强度与方向 cv2.Sobel 非极大值抑制 边缘细化 自定义插值 双阈值链接 连接可靠边缘,抑制伪边缘 cv2.Canny

三、图像轮廓提取与绘制

图像轮廓是计算机视觉中一个非常关键的概念,它广泛应用于目标检测、图像分割、形状分析等地方。

3.1 什么是轮廓(Contours)

轮廓是将具有相同灰度值的像素点连接成线的过程。在图像中,轮廓通常用于表示物体的边界或形状。

轮廓与边缘的区别:

  • 边缘是强度变化的位置(如 Canny)
  • 轮廓是封闭的路径,更强调形状和结构
  • 边缘可能是离散点,轮廓是连续曲线

示意图:

OpenCV图像梯度、边缘检测、轮廓绘制、凸包检测大合集


3.2 寻找轮廓的流程

轮廓提取的流程通常如下:

graph TDA[彩色图像] --> B[灰度化]B --> C[二值化]C --> D[查找轮廓 \\n cv2.findContours()]

3.3 OpenCV 提供了非常方便的函数:

contours, hierarchy = cv2.findContours(image, mode, method)
3.3.1 参数说明:
参数 说明 image 输入图像,必须是二值图像 mode 轮廓检索模式(如下表) method 轮廓逼近方法(如下表) contours 返回的轮廓点坐标数组列表 hierarchy 返回轮廓间的层级结构
3.3.2 mode 参数解释(轮廓层次结构)
mode 值 含义 RETR_EXTERNAL 只提取最外层轮廓(最常用) RETR_LIST 提取所有轮廓,但不构建父子关系 RETR_CCOMP 提取所有轮廓,并将外层和内层分层保存 RETR_TREE 提取所有轮廓并构建完整层次树结构

层次结构说明图(RETR_TREE):

hierarchy[i] = [next, previous, child, parent]

3.3.3 method 参数解释(轮廓点存储方式)
method 值 含义 CHAIN_APPROX_NONE 保存所有边界点 CHAIN_APPROX_SIMPLE 压缩冗余点,只保留关键点(如直线只保留端点) CHAIN_APPROX_TC89_L1 使用 Teh-Chin 链码逼近算法,效率更高(较少使用)

3.4 绘制轮廓

查找到轮廓后,可以使用以下函数将轮廓画出来:

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

参数说明

参数名 含义 image 输入/输出图像(会被修改) contours 找到的轮廓点数组 contourIdx 要绘制的轮廓索引(-1 表示绘制所有) color 轮廓线颜色(BGR) thickness 线条粗细,负值表示填充区域

3.5 实战代码示例:

import cv2 as cvfrom socks import PRINTABLE_PROXY_TYPES# 读取图像img = cv.imread(\'../images/num.png\')# 转换为灰度图像img_gray =cv.cvtColor(img,cv.COLOR_BGR2GRAY)#二值化_,img_binary = cv.threshold(img_gray,127,255,cv.THRESH_BINARY_INV)# 寻找轮廓counters,hierarchy = cv.findContours(img_binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)print(counters)print(len(counters))print(\'-------\')print(hierarchy)# 绘制轮廓cv.drawContours(img,counters,-1,(0,255,0),3,cv.LINE_AA)cv.imshow(\'img\',img)cv.waitKey(0)cv.destroyAllWindows()

OpenCV图像梯度、边缘检测、轮廓绘制、凸包检测大合集


3.6 小贴士:轮廓查找注意事项

  • 🔸 输入图像必须为二值图像(黑白),推荐使用 cv2.threshold()
  • 🔸 可以先做边缘检测(如 Canny),再轮廓提取。
  • 🔸 cv2.findContours() 会修改原图像,最好用拷贝版本。
  • 🔸 drawContours() 可以搭配 boundingRect()minAreaRect() 等函数做目标框选。

3.7总结

步骤 内容 1️⃣ 灰度化原图 2️⃣ 二值化处理 3️⃣ 使用 cv2.findContours 提取轮廓 4️⃣ 使用 cv2.drawContours 绘制轮廓 5️⃣ 可结合形状分析、ROI 提取等进一步处理

四、绘制凸包

我们已经知道了如何获取轮廓点(contours)以及如何通过 cv2.convexHull() 得到 凸包点集。接下来,我们通过绘图的方式将凸包显示出来。


4.1 算法特点

在计算几何中,**穷举法(Brute Force)QuickHull是两种常见的凸包(Convex Hull)**构造算法,它们各有优缺点,适用于不同场景。下面为你简要整理两者特点,并通过表格进行对比:

1. 穷举法(Brute Force)

原理
遍历所有点对,判断这条边是否是凸包边:即判断所有其他点是否都在该边的同一侧。若是,则保留该边。

特点

  • 算法思想简单直观
  • 时间复杂度较高O(n3)O(n^3)O(n3)
  • 适合教学/小规模数据集
  • 实现容易理解,但不适合大数据场景。

2. QuickHull 算法

原理
类似快速排序的分治思想。先找出最左和最右的两个点作为“线段”,划分上下两部分递归寻找最外层点,逐步构造出凸包。

特点

  • 平均性能优良,时间复杂度大约为 O(nlog⁡n)O(n \\log n)O(nlogn)
  • 适合中大型数据
  • 实现相对复杂,但效率更高;
  • 对输入数据分布较敏感(最坏 O(n2)O(n^2)O(n2))。

函数一览

函数 功能 cv2.findContours() 获取轮廓点 cv2.convexHull() 根据轮廓点获取凸包点 cv2.polylines() 根据点集绘制折线(或闭合多边形)
# 获取凸包点import cv2 as cv# 读取图像image_tu = cv.imread(\'../images/tu.png\')# 转换为灰度图像image_gray = cv.cvtColor(image_tu, cv.COLOR_BGR2GRAY)# 二值化处理_,image_binary = cv.threshold(image_gray,127,255,cv.THRESH_BINARY)# 寻找轮廓counters,_ = cv.findContours(image_binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)# 获取凸包convex_hull= []for cnt in counters: convex_hull.append(cv.convexHull(cnt))cv.polylines(image_tu,convex_hull,True,(255,0,0),3,cv.LINE_AA)cv.imshow(\'binary\',image_binary)cv.imshow(\'tu\',image_tu)cv.waitKey(0)cv.destroyAllWindows()

4.4 结果效果

假设你的原始图像中有一个不规则物体,该代码会:

  • 提取其轮廓
  • 计算包住这个物体的最小凸多边形(凸包)
  • 用线条将这个凸包标出

如图所示:

OpenCV图像梯度、边缘检测、轮廓绘制、凸包检测大合集


4.5 应用场景总结

应用领域 使用场景 手势识别 识别手指个数:凸包与缺陷分析(defects) 目标检测 将不规则轮廓转为规则包围多边形 图像压缩 简化轮廓特征 安全区域 包围任意散点区域