创建具有圆形区域边缘的Voronoi艺术

发布于 2025-01-25 10:50:56 字数 2574 浏览 0 评论 0原文

我正在尝试创建一些艺术“图”,如以下内容: ”红色Voronoi

区域的颜色并不重要,我要实现的目标是沿Voronoi地区边缘的可变“厚度”(特别是它们看起来像它们看起来像一个更大的圆形斑点在拐角处相遇,在他们的中间更薄)。

我尝试了每个像素的“手动绘画”基于到每个质心的最小距离(每种与颜色相关联):

n_centroids = 10
centroids = [(random.randint(0, h), random.randint(0, w)) for _ in range(n_centroids)]
colors = np.array([np.random.choice(range(256), size=3) for _ in range(n_centroids)]) / 255

for x, y in it.product(range(h), range(w)):
    distances = np.sqrt([(x - c[0])**2 + (y - c[1])**2 for c in centroids])
    centroid_i = np.argmin(distances)
    img[x, y] = colors[centroid_i]
    
plt.imshow(img, cmap='gray')

“

或通过scipy.spatial.voronoi我的顶点点,尽管我仍然看不到如何用所需的可变厚度通过它们绘制一条线。

from scipy.spatial import Voronoi, voronoi_plot_2d

# make up data points
points = [(random.randint(0, 10), random.randint(0, 10)) for _ in range(10)]

# add 4 distant dummy points
points = np.append(points, [[999,999], [-999,999], [999,-999], [-999,-999]], axis = 0)

# compute Voronoi tesselation
vor = Voronoi(points)

# plot
voronoi_plot_2d(vor)

# colorize
for region in vor.regions:
    if not -1 in region:
        polygon = [vor.vertices[i] for i in region]
        plt.fill(*zip(*polygon))

# fix the range of axes
plt.xlim([-2,12]), plt.ylim([-2,12])
plt.show()

编辑:

我设法通过侵蚀 +转角平滑(通过评论中建议的中间过滤器)在每个区域上获得了一个令人满意的结果,然后将其绘制到黑色背景中。

res = np.zeros((h,w,3))
for color in colors:
    region = (img == color)[:,:,0]
    region = region.astype(np.uint8) * 255
    region = sg.medfilt2d(region, 15) # smooth corners
    # make edges from eroding regions
    region = cv2.erode(region, np.ones((3, 3), np.uint8))
    region = region.astype(bool)
    res[region] = color
    
plt.imshow(res)

但是,如您所见,沿该区域的边界/边缘的“拉伸”线不完全存在。还有其他建议吗?

I'm trying to create some artistic "plots" like the ones below:Red Voronoi art
enter image description here

The color of the regions do not really matter, what I'm trying to achieve is the variable "thickness" of the edges along the Voronoi regions (espescially, how they look like a bigger rounded blob where they meet in corners, and thinner at their middle point).

I've tried by "painting manually" each pixel based on the minimum distance to each centroid (each associated with a color):

n_centroids = 10
centroids = [(random.randint(0, h), random.randint(0, w)) for _ in range(n_centroids)]
colors = np.array([np.random.choice(range(256), size=3) for _ in range(n_centroids)]) / 255

for x, y in it.product(range(h), range(w)):
    distances = np.sqrt([(x - c[0])**2 + (y - c[1])**2 for c in centroids])
    centroid_i = np.argmin(distances)
    img[x, y] = colors[centroid_i]
    
plt.imshow(img, cmap='gray')

voronoi diagram

Or by scipy.spatial.Voronoi, that also gives me the vertices points, although I still can't see how I can draw a line through them with the desired variable thickness.

from scipy.spatial import Voronoi, voronoi_plot_2d

# make up data points
points = [(random.randint(0, 10), random.randint(0, 10)) for _ in range(10)]

# add 4 distant dummy points
points = np.append(points, [[999,999], [-999,999], [999,-999], [-999,-999]], axis = 0)

# compute Voronoi tesselation
vor = Voronoi(points)

# plot
voronoi_plot_2d(vor)

# colorize
for region in vor.regions:
    if not -1 in region:
        polygon = [vor.vertices[i] for i in region]
        plt.fill(*zip(*polygon))

# fix the range of axes
plt.xlim([-2,12]), plt.ylim([-2,12])
plt.show()

voronoi region plot

Edit:

I've managed to get a somewhat satisfying result via erosion + corner smoothing (via median filter as suggested in the comments) on each individual region, then drawing it into a black background.

res = np.zeros((h,w,3))
for color in colors:
    region = (img == color)[:,:,0]
    region = region.astype(np.uint8) * 255
    region = sg.medfilt2d(region, 15) # smooth corners
    # make edges from eroding regions
    region = cv2.erode(region, np.ones((3, 3), np.uint8))
    region = region.astype(bool)
    res[region] = color
    
plt.imshow(res)

voronoi art
But as you can see the "stretched" line along the boundaries/edges of the regions is not quite there. Any other suggestions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

橙幽之幻 2025-02-01 10:50:56

这就是@Johanc的建议。 IMO,它看起来比我在Bezziele曲线的尝试要好得多。 但是,圆形Polygon类似乎存在一个小问题,因为有时在拐角处存在很小的缺陷(例如,下图中的蓝色和紫色之间的缺陷)。

编辑。 :我修复了圆形的polygon类。

#!/usr/bin/env python
# coding: utf-8
"""
https://stackoverflow.com/questions/72061965/create-voronoi-art-with-rounded-region-edges
"""

import numpy as np
import matplotlib.pyplot as plt

from matplotlib import patches, path
from scipy.spatial import Voronoi, voronoi_plot_2d


def shrink(polygon, pad):
    center = np.mean(polygon, axis=0)
    resized = np.zeros_like(polygon)
    for ii, point in enumerate(polygon):
        vector = point - center
        unit_vector = vector / np.linalg.norm(vector)
        resized[ii] = point - pad * unit_vector
    return resized


class RoundedPolygon(patches.PathPatch):
    # https://stackoverflow.com/a/66279687/2912349
    def __init__(self, xy, pad, **kwargs):
        p = path.Path(*self.__round(xy=xy, pad=pad))
        super().__init__(path=p, **kwargs)

    def __round(self, xy, pad):
        n = len(xy)

        for i in range(0, n):

            x0, x1, x2 = np.atleast_1d(xy[i - 1], xy[i], xy[(i + 1) % n])

            d01, d12 = x1 - x0, x2 - x1
            l01, l12 = np.linalg.norm(d01), np.linalg.norm(d12)
            u01, u12 = d01 / l01, d12 / l12

            x00 = x0 + min(pad, 0.5 * l01) * u01
            x01 = x1 - min(pad, 0.5 * l01) * u01
            x10 = x1 + min(pad, 0.5 * l12) * u12
            x11 = x2 - min(pad, 0.5 * l12) * u12

            if i == 0:
                verts = [x00, x01, x1, x10]
            else:
                verts += [x01, x1, x10]

        codes = [path.Path.MOVETO] + n*[path.Path.LINETO, path.Path.CURVE3, path.Path.CURVE3]

        verts[0] = verts[-1]

        return np.atleast_1d(verts, codes)


if __name__ == '__main__':

    # make up data points
    n = 100
    max_x = 20
    max_y = 10
    points = np.c_[np.random.uniform(0, max_x, size=n),
                   np.random.uniform(0, max_y, size=n)]

    # add 4 distant dummy points
    points = np.append(points, [[2 * max_x, 2 * max_y],
                                [   -max_x, 2 * max_y],
                                [2 * max_x,    -max_y],
                                [   -max_x,    -max_y]], axis = 0)

    # compute Voronoi tesselation
    vor = Voronoi(points)

    fig, ax = plt.subplots(figsize=(max_x, max_y))
    for region in vor.regions:
        if region and (not -1 in region):
            polygon = np.array([vor.vertices[i] for i in region])
            resized = shrink(polygon, 0.15)
            ax.add_patch(RoundedPolygon(resized, 0.2, color=plt.cm.Reds(0.5 + 0.5*np.random.rand())))

    ax.axis([0, max_x, 0, max_y])
    ax.axis('off')
    ax.set_facecolor('black')
    ax.add_artist(ax.patch)
    ax.patch.set_zorder(-1)
    plt.show()

This is what @JohanC suggestion looks like. IMO, it looks much better than my attempt with Bezier curves. However, there appears to be a small problem with the RoundedPolygon class, as there are sometimes small defects at the corners (e.g. between blue and purple in the image below).

Edit: I fixed the RoundedPolygon class.

enter image description here

#!/usr/bin/env python
# coding: utf-8
"""
https://stackoverflow.com/questions/72061965/create-voronoi-art-with-rounded-region-edges
"""

import numpy as np
import matplotlib.pyplot as plt

from matplotlib import patches, path
from scipy.spatial import Voronoi, voronoi_plot_2d


def shrink(polygon, pad):
    center = np.mean(polygon, axis=0)
    resized = np.zeros_like(polygon)
    for ii, point in enumerate(polygon):
        vector = point - center
        unit_vector = vector / np.linalg.norm(vector)
        resized[ii] = point - pad * unit_vector
    return resized


class RoundedPolygon(patches.PathPatch):
    # https://stackoverflow.com/a/66279687/2912349
    def __init__(self, xy, pad, **kwargs):
        p = path.Path(*self.__round(xy=xy, pad=pad))
        super().__init__(path=p, **kwargs)

    def __round(self, xy, pad):
        n = len(xy)

        for i in range(0, n):

            x0, x1, x2 = np.atleast_1d(xy[i - 1], xy[i], xy[(i + 1) % n])

            d01, d12 = x1 - x0, x2 - x1
            l01, l12 = np.linalg.norm(d01), np.linalg.norm(d12)
            u01, u12 = d01 / l01, d12 / l12

            x00 = x0 + min(pad, 0.5 * l01) * u01
            x01 = x1 - min(pad, 0.5 * l01) * u01
            x10 = x1 + min(pad, 0.5 * l12) * u12
            x11 = x2 - min(pad, 0.5 * l12) * u12

            if i == 0:
                verts = [x00, x01, x1, x10]
            else:
                verts += [x01, x1, x10]

        codes = [path.Path.MOVETO] + n*[path.Path.LINETO, path.Path.CURVE3, path.Path.CURVE3]

        verts[0] = verts[-1]

        return np.atleast_1d(verts, codes)


if __name__ == '__main__':

    # make up data points
    n = 100
    max_x = 20
    max_y = 10
    points = np.c_[np.random.uniform(0, max_x, size=n),
                   np.random.uniform(0, max_y, size=n)]

    # add 4 distant dummy points
    points = np.append(points, [[2 * max_x, 2 * max_y],
                                [   -max_x, 2 * max_y],
                                [2 * max_x,    -max_y],
                                [   -max_x,    -max_y]], axis = 0)

    # compute Voronoi tesselation
    vor = Voronoi(points)

    fig, ax = plt.subplots(figsize=(max_x, max_y))
    for region in vor.regions:
        if region and (not -1 in region):
            polygon = np.array([vor.vertices[i] for i in region])
            resized = shrink(polygon, 0.15)
            ax.add_patch(RoundedPolygon(resized, 0.2, color=plt.cm.Reds(0.5 + 0.5*np.random.rand())))

    ax.axis([0, max_x, 0, max_y])
    ax.axis('off')
    ax.set_facecolor('black')
    ax.add_artist(ax.patch)
    ax.patch.set_zorder(-1)
    plt.show()
粉红×色少女 2025-02-01 10:50:56

诸如bezier多边形“近似”之类的东西可以帮助我吗?

尝试使用bezier曲线:

”在此处输入图像描述”

#!/usr/bin/env python
# coding: utf-8
"""
https://stackoverflow.com/questions/72061965/create-voronoi-art-with-rounded-region-edges
"""

import numpy as np
import matplotlib.pyplot as plt

from scipy.spatial import Voronoi, voronoi_plot_2d
from bezier.curve import Curve # https://bezier.readthedocs.io/en/stable/python/index.html


def get_bezier(polygon, n=10):
    closed_polygon = np.concatenate([polygon, [polygon[0]]])
    # Insert additional points lying along the edges of the polygon;
    # this allows us to use higher order bezier curves.
    augmented_polygon = np.array(augment(closed_polygon, n))
    # The bezier package does not seem to support closed bezier curves;
    # to simulate a closed bezier curve, we triplicate the polygon,
    # and only evaluate the curve on the inner third.
    triplicated_polygon = np.vstack([augmented_polygon, augmented_polygon, augmented_polygon])
    bezier_curve = Curve(triplicated_polygon.T, degree=len(triplicated_polygon)-1)
    return bezier_curve.evaluate_multi(np.linspace(1./3, 2./3, 100)).T


def augment(polygon, n=10):
    new_points = []
    for ii, (x0, y0) in enumerate(polygon[:-1]):
        x1, y1 = polygon[ii+1]
        x = np.linspace(x0, x1, n)
        y = np.linspace(y0, y1, n)
        new_points.extend(list(zip(x[:-1], y[:-1])))
    new_points.append((x1, y1))
    return new_points


if __name__ == '__main__':

    # make up data points
    points = np.random.randint(0, 11, size=(50, 2))

    # add 4 distant dummy points
    points = np.append(points, [[999,999], [-999,999], [999,-999], [-999,-999]], axis = 0)

    # compute Voronoi tesselation
    vor = Voronoi(points)
    # voronoi_plot_2d(vor)

    fig, ax = plt.subplots()
    for region in vor.regions:
        if region and (not -1 in region):
            polygon = np.array([vor.vertices[i] for i in region])
            bezier_curve_points = get_bezier(polygon, 40)
            ax.fill(*zip(*bezier_curve_points))

    ax.axis([1, 9, 1, 9])
    ax.axis('off')
    plt.show()

Could something like bezier polygon "approximations" help me with this?

An attempt using Bezier curves:

enter image description here

#!/usr/bin/env python
# coding: utf-8
"""
https://stackoverflow.com/questions/72061965/create-voronoi-art-with-rounded-region-edges
"""

import numpy as np
import matplotlib.pyplot as plt

from scipy.spatial import Voronoi, voronoi_plot_2d
from bezier.curve import Curve # https://bezier.readthedocs.io/en/stable/python/index.html


def get_bezier(polygon, n=10):
    closed_polygon = np.concatenate([polygon, [polygon[0]]])
    # Insert additional points lying along the edges of the polygon;
    # this allows us to use higher order bezier curves.
    augmented_polygon = np.array(augment(closed_polygon, n))
    # The bezier package does not seem to support closed bezier curves;
    # to simulate a closed bezier curve, we triplicate the polygon,
    # and only evaluate the curve on the inner third.
    triplicated_polygon = np.vstack([augmented_polygon, augmented_polygon, augmented_polygon])
    bezier_curve = Curve(triplicated_polygon.T, degree=len(triplicated_polygon)-1)
    return bezier_curve.evaluate_multi(np.linspace(1./3, 2./3, 100)).T


def augment(polygon, n=10):
    new_points = []
    for ii, (x0, y0) in enumerate(polygon[:-1]):
        x1, y1 = polygon[ii+1]
        x = np.linspace(x0, x1, n)
        y = np.linspace(y0, y1, n)
        new_points.extend(list(zip(x[:-1], y[:-1])))
    new_points.append((x1, y1))
    return new_points


if __name__ == '__main__':

    # make up data points
    points = np.random.randint(0, 11, size=(50, 2))

    # add 4 distant dummy points
    points = np.append(points, [[999,999], [-999,999], [999,-999], [-999,-999]], axis = 0)

    # compute Voronoi tesselation
    vor = Voronoi(points)
    # voronoi_plot_2d(vor)

    fig, ax = plt.subplots()
    for region in vor.regions:
        if region and (not -1 in region):
            polygon = np.array([vor.vertices[i] for i in region])
            bezier_curve_points = get_bezier(polygon, 40)
            ax.fill(*zip(*bezier_curve_points))

    ax.axis([1, 9, 1, 9])
    ax.axis('off')
    plt.show()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文