简体   繁体   English

将 Voronoi 图渲染为 numpy 数组

[英]render Voronoi diagram to numpy array

I'd like to generate Voronoi regions, based on a list of centers and an image size.我想根据中心列表和图像大小生成 Voronoi 区域。

I'm tryed the next code, based on https://rosettacode.org/wiki/Voronoi_diagram我尝试了下一个代码,基于https://rosettacode.org/wiki/Voronoi_diagram

def generate_voronoi_diagram(width, height, centers_x, centers_y):
    image = Image.new("RGB", (width, height))
    putpixel = image.putpixel
    imgx, imgy = image.size
    num_cells=len(centers_x)
    nx = centers_x
    ny = centers_y
    nr,ng,nb=[],[],[]
    for i in range (num_cells):
        nr.append(randint(0, 255));ng.append(randint(0, 255));nb.append(randint(0, 255));

    for y in range(imgy):
        for x in range(imgx):
            dmin = math.hypot(imgx-1, imgy-1)
            j = -1
            for i in range(num_cells):
                d = math.hypot(nx[i]-x, ny[i]-y)
                if d < dmin:
                    dmin = d
                    j = i
            putpixel((x, y), (nr[j], ng[j], nb[j]))
    image.save("VoronoiDiagram.png", "PNG")
    image.show()

I have the desired output:我有所需的输出:

在此处输入图片说明

But it takes too much to generate the output.但是生成输出需要太多时间。

I also tried https://stackoverflow.com/a/20678647 It is fast, but I didn't find the way to translate it to numpy array of img_width X img_height.我也试过https://stackoverflow.com/a/20678647它很快,但我没有找到将其转换为 img_width X img_height 的 numpy 数组的方法。 Mostly, because I don't know how to give image size parameter to scipy Voronoi class .大多数情况下,因为我不知道如何为 scipy Voronoi class提供图像大小参数。

Is there any faster way to have this output?有没有更快的方法来获得这个输出? No centers or polygon edges are needed不需要中心或多边形边

Thanks in advance提前致谢

Edited 2018-12-11: Using @tel "Fast Solution" 2018-12-11 编辑:使用@tel “快速解决方案”

在此处输入图片说明

The code execution is faster, it seems that the centers have been transformed.代码执行速度更快,似乎中心已经转换。 Probably this method is adding a margin to the image可能这种方法是为图像添加边距

Fast solution快速解决方案

Here's how you can convert the output of the fast solution based on scipy.spatial.Voronoi that you linked to into a Numpy array of arbitrary width and height.以下是如何将基于scipy.spatial.Voronoi快速解决方案的输出转换为任意宽度和高度的 Numpy 数组。 Given the set of regions, vertices that you get as output from the voronoi_finite_polygons_2d function in the linked code, here's a helper function that will convert that output to an array:给定一组regions, vertices您从链接代码中的voronoi_finite_polygons_2d函数获得的regions, vertices ,这里有一个辅助函数,可以将该输出转换为数组:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas

def vorarr(regions, vertices, width, height, dpi=100):
    fig = plt.Figure(figsize=(width/dpi, height/dpi), dpi=dpi)
    canvas = FigureCanvas(fig)
    ax = fig.add_axes([0,0,1,1])

    # colorize
    for region in regions:
        polygon = vertices[region]
        ax.fill(*zip(*polygon), alpha=0.4)

    ax.plot(points[:,0], points[:,1], 'ko')
    ax.set_xlim(vor.min_bound[0] - 0.1, vor.max_bound[0] + 0.1)
    ax.set_ylim(vor.min_bound[1] - 0.1, vor.max_bound[1] + 0.1)

    canvas.draw()
    return np.frombuffer(canvas.tostring_rgb(), dtype='uint8').reshape(height, width, 3)

Testing it out测试一下

Here's a complete example of vorarr in action:这是一个完整的vorarr示例:

from scipy.spatial import Voronoi

# get random points
np.random.seed(1234)
points = np.random.rand(15, 2)

# compute Voronoi tesselation
vor = Voronoi(points)

# voronoi_finite_polygons_2d function from https://stackoverflow.com/a/20678647/425458
regions, vertices = voronoi_finite_polygons_2d(vor)

# convert plotting data to numpy array
arr = vorarr(regions, vertices, width=1000, height=1000)

# plot the numpy array
plt.imshow(arr)

Output:输出:

在此处输入图片说明

As you can see, the resulting Numpy array does indeed have a shape of (1000, 1000) , as specified in the call to vorarr .如您所见,生成的 Numpy 数组确实具有(1000, 1000)的形状,如对vorarr的调用中所指定。

If you want to fix up your existing code如果您想修复现有代码

Here's how you could alter your current code to work with/return a Numpy array:以下是如何更改当前代码以使用/返回 Numpy 数组:

import math
import matplotlib.pyplot as plt
import numpy as np

def generate_voronoi_diagram(width, height, centers_x, centers_y):
    arr = np.zeros((width, height, 3), dtype=int)
    imgx,imgy = width, height
    num_cells=len(centers_x)

    nx = centers_x
    ny = centers_y

    randcolors = np.random.randint(0, 255, size=(num_cells, 3))

    for y in range(imgy):
        for x in range(imgx):
            dmin = math.hypot(imgx-1, imgy-1)
            j = -1
            for i in range(num_cells):
                d = math.hypot(nx[i]-x, ny[i]-y)
                if d < dmin:
                    dmin = d
                    j = i
            arr[x, y, :] = randcolors[j]

    plt.imshow(arr.transpose(1, 0, 2))
    plt.scatter(cx, cy, c='w', edgecolors='k')
    plt.show()
    return arr

Example usage:用法示例:

np.random.seed(1234)

width = 500
cx = np.random.rand(15)*width

height = 300
cy = np.random.rand(15)*height

arr = generate_voronoi_diagram(width, height, cx, cy)

Example output:示例输出:

在此处输入图片说明

A fast solution without using matplotlib is also possible.不使用 matplotlib 的快速解决方案也是可能的。 Your solution is slow because you're iterating over all pixels, which incurs a lot of overhead in Python.您的解决方案很慢,因为您要遍历所有像素,这会在 Python 中产生大量开销。 A simple solution to this is to compute all distances in a single numpy operation and assigning all colors in another single operation.对此的一个简单解决方案是在单个 numpy 操作中计算所有距离,并在另一个单个操作中分配所有颜色。

def generate_voronoi_diagram_fast(width, height, centers_x, centers_y):
    # Create grid containing all pixel locations in image
    x, y = np.meshgrid(np.arange(width), np.arange(height))

    # Find squared distance of each pixel location from each center: the (i, j, k)th
    # entry in this array is the squared distance from pixel (i, j) to the kth center.
    squared_dist = (x[:, :, np.newaxis] - centers_x[np.newaxis, np.newaxis, :]) ** 2 + \
                   (y[:, :, np.newaxis] - centers_y[np.newaxis, np.newaxis, :]) ** 2
    
    # Find closest center to each pixel location
    indices = np.argmin(squared_dist, axis=2)  # Array containing index of closest center

    # Convert the previous 2D array to a 3D array where the extra dimension is a one-hot
    # encoding of the index
    one_hot_indices = indices[:, :, np.newaxis, np.newaxis] == np.arange(centers_x.size)[np.newaxis, np.newaxis, :, np.newaxis]

    # Create a random color for each center
    colors = np.random.randint(0, 255, (centers_x.size, 3))

    # Return an image where each pixel has a color chosen from `colors` by its
    # closest center
    return (one_hot_indices * colors[np.newaxis, np.newaxis, :, :]).sum(axis=2)

Running this function on my machine obtains a ~10x speedup relative to the original iterative solution (not taking plotting and saving the result to disk into account).在我的机器上运行此函数可以获得相对于原始迭代解决方案的约 10 倍加速(不考虑绘图并将结果保存到磁盘)。 I'm sure there are still a lot of other tweaks which could further accelerate my solution.我确信还有很多其他的调整可以进一步加速我的解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM