OpenCV Subdiv2D 平面细分

2024-09-04 09:50:04 浏览数 (3)

平面细分(Subdiv2D)是OpenCV中一个强大的类,用于在平面上进行细分操作,并提供了一系列函数来管理和操作这些三角形。在本文中,我们将详细介绍Subdiv2D类的使用方法,并提供相关的源代码。

简介

Subdiv2D 类用于对一组 2D 点(表示为 Point2f 向量)执行各种平面细分。OpenCV 使用 Delaunay 算法对平面进行三角剖分,该算法对应于 Voronoi 图的偶图。在下图中,Delaunay 三角剖分用黑线标记,Voronoi 图用红线标记。

Delaunay 三角剖分(黑色)和 Voronoi(红色)

使用方法

实例化对象 - Subdiv2D
代码语言:txt复制
import cv2
subdiv = cv2.Subdiv2D (Rect rect)

这里的 rect = [x_min, y_min, x_max, y_max]

表示这个 subdiv 对象仅在这个矩形范围内有效。

  • 示例代码: 创建一个 500x500 的平面空间对象
代码语言:txt复制
rect = (0, 0, 500, 500)
# 创建Subdiv2D 实例
subdiv = cv2.Subdiv2D(rect)
插入数据 - insert

将单个点插入到Delaunay三角剖分中。

代码语言:txt复制
subdiv.insert(Point2f pt)
  • 示例代码:
代码语言:txt复制
subdiv = cv2.Subdiv2D(rect)
points = []

points.append((100, 100))
points.append((100, 200))
points.append((200, 100))
points.append((200, 200))
points.append((200, 300))
points.append((400, 400))
points.append((300, 400))

# 将点依次插入subdiv中
for p in points :
    subdiv.insert(p)

也可以插入多个点到 Delaunay 三角剖分中。

代码语言:txt复制
subdiv.insert([
    (100, 100),
    (100, 200),
    (200, 100),
    (200, 200),
    (200, 300),
    (400, 400)
])
查找目标最近点 - findNearest

该函数是另一个用于在子划分中定位输入点的函数。它找到与输入点最近的一个子划分顶点。

代码语言:txt复制
cv2.Subdiv2D.findNearest(pt) ->	retval, nearestPt

返回变量

含义

pt

query 点

retval

返回点 ID

nearestPt

最近点

  • 示例代码:
代码语言:txt复制
query_point = (180, 210)
retval, nearestPt = subdiv.findNearest(query_point)

注意:我在使用这个函数的时候出现了返回点 nearestPt 坐标均为 0 的情况,个人怀疑是这个函数的 bug (opencv 4.9.0.80) ,所以碰到需要使用 nearestPt 信息的时候建议不要直接用这个函数返回的 nearestPt,而是结合 getVertex() 函数共同使用。

代码语言:txt复制
query_point = (180, 210)
retval, _ = subdiv.findNearest(query_point)
nearestPt, _ = subdiv.getVertex(retval)

这套代码我在使用时没出过问题。

返回顶点位置 - getVertex

根据顶点 ID 返回顶点位置。

代码语言:txt复制
cv2.Subdiv2D.getVertex(	vertex	) ->	nearestPt, firstEdge
  • 参数含义:

返回变量

含义

vertex

顶点 ID。

nearestPt

最近点

firstEdge

可选。连接到顶点的第一个边ID。

  • 示例代码:
代码语言:txt复制
query_point = (180, 210)
retval, _ = subdiv.findNearest(query_point)
nearestPt, edge_index = subdiv.getVertex(retval)
边的终点 - edgeDst
代码语言:txt复制
cv2.Subdiv2D.edgeDst(	edge	) ->	retval, dstpt
  • 参数含义:

变量

含义

edge

分划边 ID。

retval

点 ID

dstpt

边终点位置。

  • 示例代码:
代码语言:txt复制
query_point = (180, 210)
retval, _ = subdiv.findNearest(query_point)
nearestPt, edge_index = subdiv.getVertex(retval)

dst_index, dst_point = subdiv.edgeDst(edge_index)
边的起点 - edgeOrg

返回边的起点。

代码语言:txt复制
cv2.Subdiv2D.edgeOrg(	edge	) ->	retval, orgpt
  • 参数含义:

变量

含义

edge

分划边 ID。

retval

点 ID

dstpt

边起点位置。

  • 示例代码:
代码语言:txt复制
query_point = (180, 210)
retval, _ = subdiv.findNearest(query_point)
nearestPt, edge_index = subdiv.getVertex(retval)

org_index, org_point = subdiv.edgeOrg(edge_index)
获取边 - getEdge

返回与给定边相关的边之一。

代码语言:txt复制
cv2.Subdiv2D.getEdge(edge, nextEdgeType) ->	retval
  • nextEdgeType
代码语言:txt复制
-  NEXT_AROUND_ORG 线原点周围(如果e是输入边,则图片下的 eOnext)
代码语言:txt复制
-  NEXT_AROUND_DST 边顶点周围(eDnext)
代码语言:txt复制
-  PREV_AROUND_ORG 线原点周围(eRnext的反转)
代码语言:txt复制
-  PREV_AROUND_DST 边终点周围(eLnext的反转)
代码语言:txt复制
-  NEXT_AROUND_LEFT 左面周围(eLnext)
代码语言:txt复制
-  NEXT_AROUND_RIGHT 右面周围(eRnext)
代码语言:txt复制
-  PREV_AROUND_LEFT 左面周围(eOnext的反转)
代码语言:txt复制
-  PREV_AROUND_RIGHT 右面周围(eDnext的反转)
  • 示例代码:
代码语言:txt复制
another_edge_id = subdiv.getEdge(edge_index, cv2.SUBDIV2D_NEXT_AROUND_DST)
返回边列表 - getEdgeList

返回所有边的列表。

代码语言:txt复制
cv2.Subdiv2D.getEdgeList() -> edgeList
  • 示例代码:
代码语言:txt复制
edge_list = subdiv.getEdgeList()
代码语言:txt复制
-->

[[  200.   100. -1500. -1500.]
 [-1500. -1500.   100.   200.]
 [-1500. -1500.   100.   100.]
 [  200.   200.   200.   100.]
 [    0.  1500.   100.   200.]
 [  100.   100.   100.   200.]
 [ 1500.     0.   200.   100.]
 [  100.   200.   200.   100.]
 [  100.   100.   200.   100.]
 [  100.   200.   200.   200.]
 [  400.   400.   200.   100.]
 [  100.   200.   200.   300.]
 [    0.  1500.   200.   300.]
 [  200.   200.   200.   300.]
 [  400.   400.   200.   200.]
 [  200.   300.   400.   400.]
 [ 1500.     0.   400.   400.]
 [    0.  1500.   400.   400.]
 [  200.   300.   300.   400.]
 [  400.   400.   300.   400.]
 [    0.  1500.   300.   400.]]
返回所有三角形 - getTriangleList

返回所有三角形的列表。

代码语言:txt复制
cv2.Subdiv2D.getTriangleList() -> triangleList
  • 示例代码:
代码语言:txt复制
tri_list = subdiv.getTriangleList()
代码语言:txt复制
-->
[[200. 200. 200. 100. 400. 400.]
 [200. 100. 200. 200. 100. 200.]
 [100. 200. 100. 100. 200. 100.]
 [100. 200. 200. 200. 200. 300.]
 [200. 300. 200. 200. 400. 400.]
 [200. 300. 400. 400. 300. 400.]]
返回沃罗诺伊图 - getVoronoiFacetList

返回所有沃罗诺伊面元的列表。

代码语言:txt复制
cv2.Subdiv2D.getVoronoiFacetList(idx) -> facetList, facetCenters
  • 参数列表:

变量

含义

idx

要考虑的顶点ID向量。对于所有顶点,您可以通过空向量传递。

facetList

输出向量包含Voronoi面。

facetCenters

输出向量包含Voronoi面的中心点。

  • 示例代码:
代码语言:txt复制
facetList, facetCenters = subdiv.getVoronoiFacetList([])
代码语言:txt复制
facetList -->
(array([[  150., -1550.],
       [  150.,   150.],
       [-1550.,   150.]], dtype=float32), array([[  150.    ,   250.    ],
       [ -414.2857,   814.2857],
       [-2116.6667,   683.3333],
       [-1550.    ,   150.    ],
       [  150.    ,   150.    ],
       [  150.    ,   150.    ]], dtype=float32), array([[  837.8049 ,  -108.53658],
       [  450.     ,   150.     ],
       [  150.     ,   150.     ],
       [  150.     ,   150.     ],
       [  150.     , -1550.     ],
       [  683.3333 , -2116.6667 ]], dtype=float32), array([[450., 150.],
       [350., 250.],
       [150., 250.],
       [150., 150.]], dtype=float32), array([[ 350.     ,  250.     ],
       [-242.85715,  842.8571 ],
       [-414.2857 ,  814.2857 ],
       [ 150.     ,  250.     ],
       [ 350.     ,  250.     ]], dtype=float32), array([[ 350.     , 1004.5455 ],
       [ 350.     ,  250.     ],
       [ 350.     ,  250.     ],
       [ 450.     ,  150.     ],
       [ 837.8049 , -108.53658],
       [1378.5714 , 1378.5714 ]], dtype=float32), array([[ 350.     , 1004.5455 ],
       [-242.85715,  842.8571 ],
       [ 350.     ,  250.     ]], dtype=float32))

facetCenters -->
[[100. 100.]
 [100. 200.]
 [200. 100.]
 [200. 200.]
 [200. 300.]
 [400. 400.]
 [300. 400.]]
初始化 - Delaunay

创建一个新的空Delaunay细分。

代码语言:txt复制
cv2.Subdiv2D.initDelaunay(rect) -> None
  • 参数说明: 变量 含义 rect 包含所有要添加到分划中的二维点的矩形。
  • 示例代码:
代码语言:txt复制
subdiv.initDelaunay(rect)
定位 - locate

返回点在Delaunay三角剖分中的位置。

该函数在细分中定位输入点,并给出一个三角形边或顶点。

代码语言:txt复制
cv2.Subdiv2D.locate(pt) -> retval, edge, vertex
  • 参数说明: 变量 含义 pt 要定位的点。 edge 输出点所属的边或位于其右侧的边。 vertex 可选输出顶点,如果输入点与该顶点重合。
  • 示例代码:
代码语言:txt复制
cv2.Subdiv2D.locate(pt) -> retval, edge, vertex
  • 返回: 一个整数,指定以下五种位置情况之一点位置
代码语言:txt复制
- 点落在某个面片内。此函数返回`PTLOC_INSIDE`,且edge将包含面片的边之一。
- 点落在边上。此函数返回`PTLOC_ON_EDGE`,且edge将包含此边。
- 点与细分的一个顶点重合。此函数返回`PTLOC_VERTEX`,且vertex将包含顶点的指针。
- 点位于细分参考矩形外部。此函数返回`PTLOC_OUTSIDE_RECT`,不会填充任何指针。
- 一个输入参数无效。将引发运行时错误,或者如果选择静默或“父”错误处理模式,则返回 `PTLOC_ERROR`。
获取下一个边 - nextEdge

返回以边缘为起点的下一边缘。

代码语言:txt复制
cv2.Subdiv2D.nextEdge(edge) -> retval

参数说明:

变量

含义

retval

下一个边缘的 ID。

edge

输出点所属的边或位于其右侧的边。

示例代码:

代码语言:txt复制
next_edge = subdiv.nextEdge(10)
旋转边缘 - rotateEdge

返回同一四边形的另一个边缘。

代码语言:txt复制
cv2.Subdiv2D.rotateEdge(edge, rotate) -> retval

参数说明:

变量

含义

edge

分划边 ID。

rotate

指定返回与输入quad-edge相同的边的参数。

  • 0 - 输入边(如果 e 是输入边,则下图中的 e)
  • 1 - 旋转边( eRot )
  • 2 - 反转边(绿色显示的反转e)
  • 3 - 反转旋转边(绿色显示的反转eRot)

示例代码:

代码语言:txt复制
subdiv.rotateEdge(2, 0)

Vornoni 示例

代码语言:txt复制
import cv2
import numpy as np
import random
import vvdutils as vv


# 检查一个点是否在矩形内
def rect_contains(rect, point) :
    if point[0] < rect[0] :
        return False
    elif point[1] < rect[1] :
        return False
    elif point[0] > rect[2] :
        return False
    elif point[1] > rect[3] :
        return False
    return True

# 绘制一个点
def draw_point(img, p, color ) :
    cv2.circle( img, p, 2, color, 2)

# 绘制 delaunay 三角剖分
def draw_delaunay(img, subdiv, delaunay_color ) :
    triangleList = subdiv.getTriangleList()
    size = img.shape
    r = (0, 0, size[1], size[0])
    for t in triangleList :
        pt1 = (int(t[0]), int(t[1]))
        pt2 = (int(t[2]), int(t[3]))
        pt3 = (int(t[4]), int(t[5]))
        if rect_contains(r, pt1) and rect_contains(r, pt2) and rect_contains(r, pt3) :
            cv2.line(img, pt1, pt2, delaunay_color, 1)
            cv2.line(img, pt2, pt3, delaunay_color, 1)
            cv2.line(img, pt3, pt1, delaunay_color, 1)

# 绘制 voronoi 图
def draw_voronoi(img, subdiv) :
    ( facets, centers) = subdiv.getVoronoiFacetList([])
    for i in range(0,len(facets)) :
        ifacet_arr = facets[i]
        center = centers[i].astype('int32')
        ifacet = np.array(ifacet_arr)
        color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
        cv2.fillConvexPoly(img, ifacet.astype('int32'), color)
        ifacets = np.array([ifacet])
        cv2.polylines(img, ifacets.astype('int32'), True, (0, 0, 0), 1)
        cv2.circle(img, (center[0], center[1]), 3, (0, 0, 0), 1)


if __name__ == '__main__':
    # 定义绘制颜色
    delaunay_color = (255,255,255)
    points_color = (0, 0, 255)

    img = np.zeros([500,500,3],dtype=np.uint8)

    # 创建用于Subdiv2D 的矩形
    size = img.shape
    rect = (0, 0, size[1], size[0])

    # 创建Subdiv2D 实例
    subdiv = cv2.Subdiv2D(rect)
    points = []

    points.append((100, 100))
    points.append((100, 200))
    points.append((200, 100))
    points.append((200, 200))
    points.append((200, 300))
    points.append((400, 400))
    points.append((300, 400))

    # 将点依次插入subdiv中
    for p in points :
        subdiv.insert(p)
        # 展示动画画板

    img_copy = img.copy()
    draw_delaunay(img_copy, subdiv, (255, 255, 255))
    vv.PIS(img_copy)

    # 绘制delaunay 三角剖分
    draw_delaunay( img, subdiv, (255, 255, 255) )
    for p in points :
        draw_point(img, p, (0,0,255))
    # 为Voronoi 图分配空间
    img_voronoi = np.zeros(img.shape, dtype = img.dtype)
    # 绘制 Voronoi 图
    draw_voronoi(img_voronoi, subdiv)
    vv.PIS(img_voronoi)
    pass

参考资料

  • https://docs.opencv.org/4.x/df/dbf/classcv_1_1Subdiv2D.html#a3ec256af000e129e08eb5f269ccdeb0f
  • https://docs.opencv.ac.cn/4.10.0/df/dbf/classcv_1_1Subdiv2D.html#a3ec256af000e129e08eb5f269ccdeb0f

文章链接:

https://cloud.tencent.com/developer/article/2449128

0 人点赞