将Voronoi图单元格区域转换为像素坐标列表

问题描述

我正在使用Voronoi图进行图像处理(procedurally generated stippling)。 为此,我需要创建一个元组(x,y像素位置)列表(coords_within_cell)的列表(单元)。

我已经开发了几种蛮力算法来完成此任务(请参阅下文),但是它们太慢了,无法处理超过10分。 scipy空间实用程序的效率似乎高出1000倍以上。因此,我想使用scipy生成Voronoi图: https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.Voronoi.html

使用scipy生成Voronoi图非常简单,但是不幸的是,我无法弄清楚如何将像元区域转换为像素坐标。最好的方法是什么?

我找到了一个相关的问题,但是没有答案,因此被删除https://web.archive.org/web/20200120151304/https://stackoverflow.com/questions/57703129/converting-a-voronoi-diagram-into-bitmap

蛮力算法1(太慢)

import math
import random
from PIL import Image 

def distance(x1,y1,x2,y2):
    return math.hypot(x2 - x1,y2 - y1)

# define the size of the x and y bounds
screen_width = 1260
screen_height = 1260

# define the number of points that should be used
number_of_points = 16

# randomly generate a list of n points within the given x and y bounds
point_x_coordinates = random.sample(range(0,screen_width),number_of_points)
point_y_coordinates = random.sample(range(0,screen_height),number_of_points)
points = list(zip(point_x_coordinates,point_y_coordinates))

# each point needs to have a corresponding list of pixels
point_pixels = []
for i in range(len(points)):
    point_pixels.append([]) 

# for each pixel within bounds,determine which point it is closest to and add it to the corresponding list in point_pixels
for pixel_y_coordinate in range(screen_height):
    for pixel_x_coordinate in  range(screen_width):
        distance_to_closest_point = float('inf')
        closest_point_index = 1

        for point_index,point in enumerate(points):
            distance_to_point = distance(pixel_x_coordinate,pixel_y_coordinate,point[0],point[1])
            if(distance_to_point < distance_to_closest_point):
                closest_point_index = point_index
                distance_to_closest_point = distance_to_point
        
        point_pixels[closest_point_index].append((pixel_x_coordinate,pixel_y_coordinate))

# each point needs to have a corresponding centroid
point_pixels_centroid = []

for pixel_group in point_pixels:
    x_sum = 0
    y_sum = 0
    for pixel in pixel_group:
        x_sum += pixel[0]
        y_sum += pixel[1]
    
    x_average = x_sum / len(pixel_group)
    y_average = y_sum / len(pixel_group)

    point_pixels_centroid.append((round(x_average),round(y_average)))



# display the resulting voronoi diagram
display_voronoi = Image.new("RGB",(screen_width,"white")
for pixel_group in point_pixels:
    rgb = random.sample(range(0,255),3)
    for pixel in pixel_group:
        display_voronoi.putpixel( pixel,(rgb[0],rgb[1],rgb[2],255) )

for centroid in point_pixels_centroid:
    print(centroid)
    display_voronoi.putpixel( centroid,(1,1,255) )

display_voronoi.show()

蛮力算法2(也太慢了): Based on this concept.

import math
import random
from PIL import Image 

def distance(x1,y2 - y1)

# define the size of the x and y bounds
screen_width = 500
screen_height = 500

# define the number of points that should be used
number_of_points = 4

# randomly generate a list of n points within the given x and y bounds
point_x_coordinates = random.sample(range(0,determine which point it is closest to and add it to the corresponding list in point_pixels
# do this by continuously growing circles outwards from the points
# if circles overlap then whoever was their first claims the location

# keep track of whether pixels have been used or not
# this is done via a 2D list of booleans
is_drawn_on = []
for i in range(screen_width):
    is_drawn_on.append([]) 
    for j in range(screen_height):
        is_drawn_on[i].append(False)

circles_are_growing = True
radius = 1
while(circles_are_growing):
    circles_are_growing = False
    for point_index,point in enumerate(points):
        for i in range(point[0] - radius,point[0] + radius):
            for j in range(point[1] - radius,point[1] + radius):
                # print(str(i)+" vs "+str(len(is_drawn_on)))
                if(i >= 0 and i < len(is_drawn_on)):
                    if(j >= 0 and j < len(is_drawn_on[i])):
                        if(not is_drawn_on[i][j] and distance(i,j,point[1]) <= radius):
                            point_pixels[point_index].append((i,j))
                            circles_are_growing = True
                            is_drawn_on[i][j] = True
    radius += 1

# each point needs to have a corresponding centroid
point_pixels_centroid = []

for pixel_group in point_pixels:
    x_sum = 0
    y_sum = 0
    for pixel in pixel_group:
        x_sum += pixel[0]
        y_sum += pixel[1]
    
    x_average = x_sum / len(pixel_group)
    y_average = y_sum / len(pixel_group)

    point_pixels_centroid.append((round(x_average),255) )

display_voronoi.show()

解决方法

与其直接构建和询问Voronoi图,不如构建和查询标准搜索树。以下是我使用scipy.spatial.KDTree对代码进行的修改,以确定每个像素位置的最接近点,然后是结果图像(一个500x500图像,具有500个Voronoi点)。

代码仍然有些慢,但是现在可以在Voronoi点数上很好地扩展。如果您避免为每个Voronoi单元构建像素位置列表,而直接在图像中设置数据,则可能会更快。

最快的解决方案可能包括构建Voronoi diagam,一次遍历一个像素,并关联最接近的Voronoi单元,在需要时查看相邻的Voronoi单元(因为前一个像素为找到Voronoi单元提供了很好的猜测对于下一个像素)。但这将涉及编写更多这样的天真的使用KDTree的代码,并且可能不会产生巨大的收益:这时代码的慢部分是构建所有可以独立清理的按像素排列的数组/数据。 / p>

import math
import random
from PIL import Image 
from scipy import spatial
import numpy as np

# define the size of the x and y bounds
screen_width = 500
screen_height = 500

# define the number of points that should be used
number_of_points = 500

# randomly generate a list of n points within the given x and y bounds
point_x_coordinates = random.sample(range(0,screen_width),number_of_points)
point_y_coordinates = random.sample(range(0,screen_height),number_of_points)
points = list(zip(point_x_coordinates,point_y_coordinates))

# each point needs to have a corresponding list of pixels
point_pixels = []
for i in range(len(points)):
    point_pixels.append([]) 

# build a search tree
tree = spatial.KDTree(points)

# build a list of pixed coordinates to query
pixel_coordinates = np.zeros((screen_height*screen_width,2));
i = 0
for pixel_y_coordinate in range(screen_height):
    for pixel_x_coordinate in  range(screen_width):
        pixel_coordinates[i] = np.array([pixel_x_coordinate,pixel_y_coordinate])
        i = i+1

# for each pixel within bounds,determine which point it is closest to and add it to the corresponding list in point_pixels
[distances,indices] = tree.query(pixel_coordinates)

i = 0
for pixel_y_coordinate in range(screen_height):
    for pixel_x_coordinate in  range(screen_width):
        point_pixels[indices[i]].append((pixel_x_coordinate,pixel_y_coordinate))
        i = i+1

# each point needs to have a corresponding centroid
point_pixels_centroid = []

for pixel_group in point_pixels:
    x_sum = 0
    y_sum = 0
    for pixel in pixel_group:
        x_sum += pixel[0]
        y_sum += pixel[1]

    x_average = x_sum / max(len(pixel_group),1)
    y_average = y_sum / max(len(pixel_group),1)

    point_pixels_centroid.append((round(x_average),round(y_average)))


# display the resulting voronoi diagram
display_voronoi = Image.new("RGB",(screen_width,"white")
for pixel_group in point_pixels:
    rgb = random.sample(range(0,255),3)
    for pixel in pixel_group:
        display_voronoi.putpixel( pixel,(rgb[0],rgb[1],rgb[2],255) )

for centroid in point_pixels_centroid:
    #print(centroid)
    display_voronoi.putpixel( centroid,(1,1,255) )

#display_voronoi.show()
display_voronoi.save("test.png")

voronoi image