【问题标题】:Getting a bounded polygon coordinates from Voronoi cells从 Voronoi 单元获取有界多边形坐标
【发布时间】:2015-04-24 06:53:32
【问题描述】:

我有点(例如,lat、lon 对蜂窝塔位置),我需要获取它们形成的 Voronoi 单元的多边形。

from scipy.spatial import Voronoi

tower = [[ 24.686 ,  46.7081],
       [ 24.686 ,  46.7081],
       [ 24.686 ,  46.7081]]

c = Voronoi(towers)

现在,我需要获取每个单元格的纬度、经度坐标中的多边形边界(以及该多边形包围的质心是什么)。我也需要这个 Voronoi 有界。这意味着边界不会无限远,而是在边界框内。

【问题讨论】:

    标签: python computational-geometry polygons voronoi


    【解决方案1】:

    给定一个矩形边界框,我的第一个想法是在这个边界框和scipy.spatial.Voronoi 生成的 Voronoï 图之间定义一种相交操作。这个想法不一定很好,因为这需要编写大量计算几何的基本函数。

    但是,这是我想到的第二个想法(hack?):计算平面中一组n 点的 Voronoï 图的算法具有O(n ln(n)) 的时间复杂度。添加点以约束初始点的 Voronoï 单元位于边界框内怎么样?

    有界 Voronoï 图的解

    一张照片胜过一场精彩的演讲:

    我在这里做了什么?这很简单!初始点(蓝色)位于[0.0, 1.0] x [0.0, 1.0]。然后我根据x = 0.0(边界框的左边缘)通过反射对称得到左侧的点(蓝色)(即[-1.0, 0.0] x [0.0, 1.0])。根据x = 1.0、y = 0.0 和y = 1.0(边界框的其他边缘)的反射对称性,我得到了完成这项工作所需的所有点(蓝色)。

    然后我运行scipy.spatial.Voronoi。上图描绘了生成的 Voronoï 图(我使用 scipy.spatial.voronoi_plot_2d)。

    接下来要做什么?只需根据边界框过滤点、边或面。并根据众所周知的公式得到每个人脸的质心计算centroid of polygon。这是结果的图像(质心为红色):

    展示代码之前的一些乐趣

    太棒了!它似乎工作。如果在一次迭代后我尝试在质心(红色)而不是初始点(蓝色)上重新运行算法怎么办?如果我一次又一次地尝试呢?

    第 2 步

    第 10 步

    第 25 步

    酷! Voronoï 细胞倾向于最小化它们的能量...

    这里是代码

    import matplotlib.pyplot as pl
    import numpy as np
    import scipy as sp
    import scipy.spatial
    import sys
    
    eps = sys.float_info.epsilon
    
    n_towers = 100
    towers = np.random.rand(n_towers, 2)
    bounding_box = np.array([0., 1., 0., 1.]) # [x_min, x_max, y_min, y_max]
    
    def in_box(towers, bounding_box):
        return np.logical_and(np.logical_and(bounding_box[0] <= towers[:, 0],
                                             towers[:, 0] <= bounding_box[1]),
                              np.logical_and(bounding_box[2] <= towers[:, 1],
                                             towers[:, 1] <= bounding_box[3]))
    
    
    def voronoi(towers, bounding_box):
        # Select towers inside the bounding box
        i = in_box(towers, bounding_box)
        # Mirror points
        points_center = towers[i, :]
        points_left = np.copy(points_center)
        points_left[:, 0] = bounding_box[0] - (points_left[:, 0] - bounding_box[0])
        points_right = np.copy(points_center)
        points_right[:, 0] = bounding_box[1] + (bounding_box[1] - points_right[:, 0])
        points_down = np.copy(points_center)
        points_down[:, 1] = bounding_box[2] - (points_down[:, 1] - bounding_box[2])
        points_up = np.copy(points_center)
        points_up[:, 1] = bounding_box[3] + (bounding_box[3] - points_up[:, 1])
        points = np.append(points_center,
                           np.append(np.append(points_left,
                                               points_right,
                                               axis=0),
                                     np.append(points_down,
                                               points_up,
                                               axis=0),
                                     axis=0),
                           axis=0)
        # Compute Voronoi
        vor = sp.spatial.Voronoi(points)
        # Filter regions
        regions = []
        for region in vor.regions:
            flag = True
            for index in region:
                if index == -1:
                    flag = False
                    break
                else:
                    x = vor.vertices[index, 0]
                    y = vor.vertices[index, 1]
                    if not(bounding_box[0] - eps <= x and x <= bounding_box[1] + eps and
                           bounding_box[2] - eps <= y and y <= bounding_box[3] + eps):
                        flag = False
                        break
            if region != [] and flag:
                regions.append(region)
        vor.filtered_points = points_center
        vor.filtered_regions = regions
        return vor
    
    def centroid_region(vertices):
        # Polygon's signed area
        A = 0
        # Centroid's x
        C_x = 0
        # Centroid's y
        C_y = 0
        for i in range(0, len(vertices) - 1):
            s = (vertices[i, 0] * vertices[i + 1, 1] - vertices[i + 1, 0] * vertices[i, 1])
            A = A + s
            C_x = C_x + (vertices[i, 0] + vertices[i + 1, 0]) * s
            C_y = C_y + (vertices[i, 1] + vertices[i + 1, 1]) * s
        A = 0.5 * A
        C_x = (1.0 / (6.0 * A)) * C_x
        C_y = (1.0 / (6.0 * A)) * C_y
        return np.array([[C_x, C_y]])
    
    vor = voronoi(towers, bounding_box)
    
    fig = pl.figure()
    ax = fig.gca()
    # Plot initial points
    ax.plot(vor.filtered_points[:, 0], vor.filtered_points[:, 1], 'b.')
    # Plot ridges points
    for region in vor.filtered_regions:
        vertices = vor.vertices[region, :]
        ax.plot(vertices[:, 0], vertices[:, 1], 'go')
    # Plot ridges
    for region in vor.filtered_regions:
        vertices = vor.vertices[region + [region[0]], :]
        ax.plot(vertices[:, 0], vertices[:, 1], 'k-')
    # Compute and plot centroids
    centroids = []
    for region in vor.filtered_regions:
        vertices = vor.vertices[region + [region[0]], :]
        centroid = centroid_region(vertices)
        centroids.append(list(centroid[0, :]))
        ax.plot(centroid[:, 0], centroid[:, 1], 'r.')
    
    ax.set_xlim([-0.1, 1.1])
    ax.set_ylim([-0.1, 1.1])
    pl.savefig("bounded_voronoi.png")
    
    sp.spatial.voronoi_plot_2d(vor)
    pl.savefig("voronoi.png")
    

    【讨论】:

    • 为什么在检查点是否在边界框内时减去 epsilon?确保浮点舍入错误不会使点出现在边界框之外,而实际上它在边界框内?
    • 另外,为什么在获取质心坐标时将 A 乘以 6.0?非常感谢您在这些问题上提供的任何帮助!
    • 啊,这回答了我的第二个问题:en.wikipedia.org/wiki/Centroid#Of_a_polygon
    • 回答您的第一个问题:是的,此减法仅用于处理浮点舍入错误。如果我没记错的话,我已经开始没有但无法获得预期的结果。如果您尝试没有减法的代码,请告诉我它是否正常工作。
    • 非常感谢这段代码和洞察力!不过,我对您的程序进行了改进:通过构造,您要保留的区域属于您给scipy.spatial.Voronoi() 的点的前 1/5。这意味着您可以使用为每个点提供的Voronoi.point_region 属性,它所属的区域的索引:vor.filtered_regions = np.array(vor.regions)[vor.point_region[:vor.npoints//5]]。这 a) 节省了一堆手动检查,并且 b) 保证区域以与其匹配点相同的顺序返回(这对我的用例至关重要 :))
    【解决方案2】:

    我在使用 scipy 的 voronoi 函数和创建 CVD 时遇到了很多麻烦,所以这些精彩的帖子和 cmets 帮助很大。作为一个编程新手,我试图理解来自 Flabetvvibes 答案的代码,我将分享我对它如何与 Energya 和我自己的修改一起工作的解释。我还在此答案的底部完整发布了我的代码版本

    import matplotlib.pyplot as pl
    import numpy as np
    import scipy as sp
    import scipy.spatial
    import sys
    import copy
    
    eps = sys.float_info.epsilon
    
    # Returns a new np.array of towers that within the bounding_box
    def in_box(towers, bounding_box):
        return np.logical_and(np.logical_and(bounding_box[0] <= towers[:, 0],
                                             towers[:, 0] <= bounding_box[1]),
                              np.logical_and(bounding_box[2] <= towers[:, 1],
                                             towers[:, 1] <= bounding_box[3]))
    

    in_box 函数使用 numpy 的logical_and 方法返回一个布尔数组,该数组表示来自塔的哪些坐标在边界框中。

    # Generates a bounded vornoi diagram with finite regions in the bounding box
    def bounded_voronoi(towers, bounding_box):
        # Select towers inside the bounding box
        i = in_box(towers, bounding_box)
    
        # Mirror points left, right, above, and under to provide finite regions for the
        # edge regions of the bounding box
        points_center = towers[i, :]
    
        points_left = np.copy(points_center)
        points_left[:, 0] = bounding_box[0] - (points_left[:, 0] - bounding_box[0])
    
        points_right = np.copy(points_center)
        points_right[:, 0] = bounding_box[1] + (bounding_box[1] - points_right[:, 0])
    
        points_down = np.copy(points_center)
        points_down[:, 1] = bounding_box[2] - (points_down[:, 1] - bounding_box[2])
    
        points_up = np.copy(points_center)
        points_up[:, 1] = bounding_box[3] + (bounding_box[3] - points_up[:, 1])
    
        points = np.append(points_center,
                           np.append(np.append(points_left,
                                               points_right,
                                               axis=0),
                                     np.append(points_down,
                                               points_up,
                                               axis=0),
                                     axis=0),
                           axis=0)
    

    Flabetvvibes 镜像点以允许沿边界框内边缘的区域是有限的。 Scipy 的 voronoi 方法对于未定义的顶点返回 -1,因此镜像点允许边界框内的所有区域都是有限的,并且所有无限区域都在边界框外的镜像区域中,稍后将被丢弃。

    # Compute Voronoi
    vor = sp.spatial.Voronoi(points)
    
    # creates a new attibute for points that form the diagram within the region
    vor.filtered_points = points_center 
    # grabs the first fifth of the regions, which are the original regions
    vor.filtered_regions = np.array(vor.regions)[vor.point_region[:vor.npoints//5]]
    
    return vor
    

    bounded_voronoi 方法的最后一位调用 scipy 的 voronoi 函数并为边界框内的过滤点和区域添加新属性。 Energya 建议删除 Flabetvvibe 的代码,该代码手动找到边界框内的所有有限区域,并使用一条线获得前五分之一的区域,这些区域是原始输入以及构成边界框的点。

    def generate_CVD(points, iterations, bounding_box):
        p = copy.copy(points)
    
        for i in range(iterations):
            vor = bounded_voronoi(p, bounding_box)
            centroids = []
    
            for region in vor.filtered_regions:
                # grabs vertices for the region and adds a duplicate
                # of the first one to the end
                vertices = vor.vertices[region + [region[0]], :] 
                centroid = centroid_region(vertices)
                centroids.append(list(centroid[0, :]))
    
            p = np.array(centroids)
    
        return bounded_voronoi(p, bounding_box)
    

    我采用了 Flabetvvibe 的代码,该代码执行了 loyd 算法的迭代,并将其形成为一种易于使用的方法。对于每次迭代,调用先前的 bounded_voronoi 函数,然后为每个单元找到质心,它们成为下一次迭代的新点集。 vertices = vor.vertices[region + [region[0]], :] 简单地抓取当前区域的所有顶点并将第一个顶点复制到末尾,这样第一个和最后一个顶点相同用于计算质心。

    感谢 Flabetvvibes 和 Energya。您的帖子/答案教会了我如何比其文档更好地使用 scipy 的 voronoi 方法。我还将代码作为一个单独的主体发布给任何其他寻找复制/粘贴的人。

    import matplotlib.pyplot as pl
    import numpy as np
    import scipy as sp
    import scipy.spatial
    import sys
    import copy
    
    eps = sys.float_info.epsilon
    
    # Returns a new np.array of towers that within the bounding_box
    def in_box(towers, bounding_box):
        return np.logical_and(np.logical_and(bounding_box[0] <= towers[:, 0],
                                             towers[:, 0] <= bounding_box[1]),
                              np.logical_and(bounding_box[2] <= towers[:, 1],
                                             towers[:, 1] <= bounding_box[3]))
    
    
    # Generates a bounded vornoi diagram with finite regions
    def bounded_voronoi(towers, bounding_box):
        # Select towers inside the bounding box
        i = in_box(towers, bounding_box)
    
        # Mirror points left, right, above, and under to provide finite regions for the edge regions of the bounding box
        points_center = towers[i, :]
    
        points_left = np.copy(points_center)
        points_left[:, 0] = bounding_box[0] - (points_left[:, 0] - bounding_box[0])
    
        points_right = np.copy(points_center)
        points_right[:, 0] = bounding_box[1] + (bounding_box[1] - points_right[:, 0])
    
        points_down = np.copy(points_center)
        points_down[:, 1] = bounding_box[2] - (points_down[:, 1] - bounding_box[2])
    
        points_up = np.copy(points_center)
        points_up[:, 1] = bounding_box[3] + (bounding_box[3] - points_up[:, 1])
    
        points = np.append(points_center,
                           np.append(np.append(points_left,
                                               points_right,
                                               axis=0),
                                     np.append(points_down,
                                               points_up,
                                               axis=0),
                                     axis=0),
                           axis=0)
    
        # Compute Voronoi
        vor = sp.spatial.Voronoi(points)
    
        vor.filtered_points = points_center # creates a new attibute for points that form the diagram within the region
        vor.filtered_regions = np.array(vor.regions)[vor.point_region[:vor.npoints//5]] # grabs the first fifth of the regions, which are the original regions
    
        return vor
    
    
    # Finds the centroid of a region. First and last point should be the same.
    def centroid_region(vertices):
        # Polygon's signed area
        A = 0
        # Centroid's x
        C_x = 0
        # Centroid's y
        C_y = 0
        for i in range(0, len(vertices) - 1):
            s = (vertices[i, 0] * vertices[i + 1, 1] - vertices[i + 1, 0] * vertices[i, 1])
            A = A + s
            C_x = C_x + (vertices[i, 0] + vertices[i + 1, 0]) * s
            C_y = C_y + (vertices[i, 1] + vertices[i + 1, 1]) * s
        A = 0.5 * A
        C_x = (1.0 / (6.0 * A)) * C_x
        C_y = (1.0 / (6.0 * A)) * C_y
        return np.array([[C_x, C_y]])
    
    
    # Performs x iterations of loyd's algorithm to calculate a centroidal vornoi diagram
    def generate_CVD(points, iterations, bounding_box):
        p = copy.copy(points)
    
        for i in range(iterations):
            vor = bounded_voronoi(p, bounding_box)
            centroids = []
    
            for region in vor.filtered_regions:
                vertices = vor.vertices[region + [region[0]], :] # grabs vertices for the region and adds a duplicate of the first one to the end
                centroid = centroid_region(vertices)
                centroids.append(list(centroid[0, :]))
    
            p = np.array(centroids)
    
        return bounded_voronoi(p, bounding_box)
    
    
    # returns a pyplot of given voronoi data
    def plot_vornoi_diagram(vor, bounding_box, show_figure):
        # Initializes pyplot stuff
        fig = pl.figure()
        ax = fig.gca()
    
        # Plot initial points
        ax.plot(vor.filtered_points[:, 0], vor.filtered_points[:, 1], 'b.')
    
        # Plot ridges points
        for region in vor.filtered_regions:
            vertices = vor.vertices[region, :]
            ax.plot(vertices[:, 0], vertices[:, 1], 'go')
    
        # Plot ridges
        for region in vor.filtered_regions:
            vertices = vor.vertices[region + [region[0]], :]
            ax.plot(vertices[:, 0], vertices[:, 1], 'k-')
    
        # stores references to numbers for setting axes limits
        margin_percent = .1
        width = bounding_box[1]-bounding_box[0]
        height = bounding_box[3]-bounding_box[2]
    
        ax.set_xlim([bounding_box[0]-width*margin_percent, bounding_box[1]+width*margin_percent])
        ax.set_ylim([bounding_box[2]-height*margin_percent, bounding_box[3]+height*margin_percent])
    
        if show_figure:
            pl.show()
        return fig
    

    【讨论】:

      猜你喜欢
      • 2015-08-08
      • 1970-01-01
      • 2016-04-30
      • 1970-01-01
      • 2021-02-19
      • 1970-01-01
      • 2021-12-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多