Python 使用matplotlib创建随机形状/轮廓

Python 使用matplotlib创建随机形状/轮廓,python,matplotlib,contour,shapes,bezier,Python,Matplotlib,Contour,Shapes,Bezier,我试图用python生成一个随机轮廓的图像,但我找不到一个简单的方法 下面是我想要的一个例子: 起初我想用matplotlib和高斯函数来做,但我甚至无法接近我展示的图像 有简单的方法吗 非常感谢您的帮助回答您的问题,没有简单的方法。生成看起来和感觉都很自然的随机事物是一个比乍看起来困难得多的问题——这就是为什么柏林噪声这样的技术很重要的原因 任何传统的编程方法(比如说,不涉及神经网络)都可能最终成为一个复杂的多步骤过程,包括选择随机点、放置形状、绘制线条等,并进行微调,直到看起来像你想要的那

我试图用python生成一个随机轮廓的图像,但我找不到一个简单的方法

下面是我想要的一个例子:

起初我想用matplotlib和高斯函数来做,但我甚至无法接近我展示的图像

有简单的方法吗


非常感谢您的帮助

回答您的问题,没有简单的方法。生成看起来和感觉都很自然的随机事物是一个比乍看起来困难得多的问题——这就是为什么柏林噪声这样的技术很重要的原因

任何传统的编程方法(比如说,不涉及神经网络)都可能最终成为一个复杂的多步骤过程,包括选择随机点、放置形状、绘制线条等,并进行微调,直到看起来像你想要的那样。使用这种方法,从零开始获取任何能够可靠地生成动态和有机外观的形状的东西都是非常困难的

如果您对结果比对实现更感兴趣,您可以尝试找到一个库,该库生成令人信服的平滑随机纹理,并从中剪切出轮廓线。这是目前唯一想到的“简单”方法。这里有一个柏林噪声的例子。注意从灰度级形成的形状。

matplotlib路径 一个简单的方法来实现随机和非常平滑的形状是使用模块

使用三次Bézier曲线,大多数直线将被平滑,并且锐边的数量将是需要调整的参数之一

步骤如下。首先定义形状的参数,这些参数是锐边的数量
n
,以及相对于单位圆中默认位置的最大扰动
r
。在此示例中,使用径向校正将点从单位圆移动,该校正将半径从1修改为介于
1-r
1+r
之间的随机数

这就是为什么顶点被定义为相应角度乘以半径因子的正弦或余弦,以将点放置在圆中,然后修改其半径以引入随机分量。用于转置的
堆栈
.T
[:,None]
仅用于将数组转换为matplotlib接受的输入

下面是使用这种径向校正的示例:

import numpy as np

import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches

n = 8 # Number of possibly sharp edges
r = .7 # magnitude of the perturbation from the unit circle, 
# should be between 0 and 1
N = n*3+1 # number of points in the Path
# There is the initial point and 3 points per cubic bezier curve. Thus, the curve will only pass though n points, which will be the sharp edges, the other 2 modify the shape of the bezier curve

angles = np.linspace(0,2*np.pi,N)
codes = np.full(N,Path.CURVE4)
codes[0] = Path.MOVETO

verts = np.stack((np.cos(angles),np.sin(angles))).T*(2*r*np.random.random(N)+1-r)[:,None]
verts[-1,:] = verts[0,:] # Using this instad of Path.CLOSEPOLY avoids an innecessary straight line
path = Path(verts, codes)

fig = plt.figure()
ax = fig.add_subplot(111)
patch = patches.PathPatch(path, facecolor='none', lw=2)
ax.add_patch(patch)

ax.set_xlim(np.min(verts)*1.1, np.max(verts)*1.1)
ax.set_ylim(np.min(verts)*1.1, np.max(verts)*1.1)
ax.axis('off') # removes the axis to leave only the shape

plt.show()
对于
n=8
r=0.7
产生如下形状:


高斯滤波matplotlib路径 还可以选择使用上述代码为单个形状生成形状,然后使用scipy对生成的图像执行渲染

执行高斯滤波器和检索平滑形状背后的主要思想是创建填充形状;将图像另存为二维数组(其值将介于0和1之间,因为它将是灰度图像);然后应用高斯滤波器;最后,得到平滑后的形状作为滤波后阵列的0.5轮廓

因此,第二个版本看起来像:

# additional imports
from skimage import color as skolor # see the docs at scikit-image.org/
from skimage import measure
from scipy.ndimage import gaussian_filter

sigma = 7 # smoothing parameter
# ...
path = Path(verts, codes)

fig = plt.figure()
ax = fig.add_axes([0,0,1,1]) # create the subplot filling the whole figure
patch = patches.PathPatch(path, facecolor='k', lw=2) # Fill the shape in black
# ...
ax.axis('off')

fig.canvas.draw()

##### Smoothing ####
# get the image as an array of values between 0 and 1
data = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
data = data.reshape(fig.canvas.get_width_height()[::-1] + (3,))
gray_image = skolor.rgb2gray(data)

# filter the image
smoothed_image = gaussian_filter(gray_image,sigma)

# Retrive smoothed shape as 0.5 contour
smooth_contour = measure.find_contours(smoothed_image[::-1,:], 0.5)[0] 
# Note, the values of the contour will range from 0 to smoothed_image.shape[0] 
# and likewise for the second dimension, if desired, 
# they should be rescaled to go between 0,1 afterwards

# compare smoothed ans original shape
fig = plt.figure(figsize=(8,4))
ax1 = fig.add_subplot(1,2,1)
patch = patches.PathPatch(path, facecolor='none', lw=2)
ax1.add_patch(patch)
ax1.set_xlim(np.min(verts)*1.1, np.max(verts)*1.1)
ax1.set_ylim(np.min(verts)*1.1, np.max(verts)*1.1)
ax1.axis('off') # removes the axis to leave only the shape
ax2 = fig.add_subplot(1,2,2)
ax2.plot(smooth_contour[:, 1], smooth_contour[:, 0], linewidth=2, c='k')
ax2.axis('off')

问题在于,问题中显示的随机形状并非真正随机的。它们以某种方式平滑、有序、看似随机的形状。虽然用计算机创建真正的随机形状很容易,但用纸和笔创建那些伪随机形状要容易得多

因此,一种选择是以交互方式创建此类形状。问题中显示了这一点

如果您想以编程方式创建随机形状,我们可以调整解决方案以使用三次贝塞尔曲线

其思想是通过
get_random_points
创建一组随机点,并使用这些点调用函数
get_bezier_curve
。这将创建一组贝塞尔曲线,这些曲线在输入点处彼此平滑连接。我们还确保它们是循环的,即起点和终点之间的过渡也是平滑的

import numpy as np
from scipy.special import binom
import matplotlib.pyplot as plt


bernstein = lambda n, k, t: binom(n,k)* t**k * (1.-t)**(n-k)

def bezier(points, num=200):
    N = len(points)
    t = np.linspace(0, 1, num=num)
    curve = np.zeros((num, 2))
    for i in range(N):
        curve += np.outer(bernstein(N - 1, i, t), points[i])
    return curve

class Segment():
    def __init__(self, p1, p2, angle1, angle2, **kw):
        self.p1 = p1; self.p2 = p2
        self.angle1 = angle1; self.angle2 = angle2
        self.numpoints = kw.get("numpoints", 100)
        r = kw.get("r", 0.3)
        d = np.sqrt(np.sum((self.p2-self.p1)**2))
        self.r = r*d
        self.p = np.zeros((4,2))
        self.p[0,:] = self.p1[:]
        self.p[3,:] = self.p2[:]
        self.calc_intermediate_points(self.r)

    def calc_intermediate_points(self,r):
        self.p[1,:] = self.p1 + np.array([self.r*np.cos(self.angle1),
                                    self.r*np.sin(self.angle1)])
        self.p[2,:] = self.p2 + np.array([self.r*np.cos(self.angle2+np.pi),
                                    self.r*np.sin(self.angle2+np.pi)])
        self.curve = bezier(self.p,self.numpoints)


def get_curve(points, **kw):
    segments = []
    for i in range(len(points)-1):
        seg = Segment(points[i,:2], points[i+1,:2], points[i,2],points[i+1,2],**kw)
        segments.append(seg)
    curve = np.concatenate([s.curve for s in segments])
    return segments, curve

def ccw_sort(p):
    d = p-np.mean(p,axis=0)
    s = np.arctan2(d[:,0], d[:,1])
    return p[np.argsort(s),:]

def get_bezier_curve(a, rad=0.2, edgy=0):
    """ given an array of points *a*, create a curve through
    those points. 
    *rad* is a number between 0 and 1 to steer the distance of
          control points.
    *edgy* is a parameter which controls how "edgy" the curve is,
           edgy=0 is smoothest."""
    p = np.arctan(edgy)/np.pi+.5
    a = ccw_sort(a)
    a = np.append(a, np.atleast_2d(a[0,:]), axis=0)
    d = np.diff(a, axis=0)
    ang = np.arctan2(d[:,1],d[:,0])
    f = lambda ang : (ang>=0)*ang + (ang<0)*(ang+2*np.pi)
    ang = f(ang)
    ang1 = ang
    ang2 = np.roll(ang,1)
    ang = p*ang1 + (1-p)*ang2 + (np.abs(ang2-ang1) > np.pi )*np.pi
    ang = np.append(ang, [ang[0]])
    a = np.append(a, np.atleast_2d(ang).T, axis=1)
    s, c = get_curve(a, r=rad, method="var")
    x,y = c.T
    return x,y, a


def get_random_points(n=5, scale=0.8, mindst=None, rec=0):
    """ create n random points in the unit square, which are *mindst*
    apart, then scale them."""
    mindst = mindst or .7/n
    a = np.random.rand(n,2)
    d = np.sqrt(np.sum(np.diff(ccw_sort(a), axis=0), axis=1)**2)
    if np.all(d >= mindst) or rec>=200:
        return a*scale
    else:
        return get_random_points(n=n, scale=scale, mindst=mindst, rec=rec+1)

我们可以检查参数如何影响结果。这里主要使用3个参数:

  • rad
    ,贝塞尔曲线控制点所在点周围的半径。该数字与相邻点之间的距离有关,因此应介于0和1之间。半径越大,曲线的特征越尖锐
  • edgy
    ,用于确定曲线平滑度的参数。如果为0,则曲线通过每个点的角度将是相邻点方向之间的平均值。它越大,仅由一个相邻点确定的角度就越大。因此,曲线变得“更加尖锐”
  • n
    要使用的随机点的数量。当然,最低分数是3分。使用的点越多,形状的功能越丰富;有可能在曲线中创建重叠或循环

您的示例有一些很好的形状。是否可以从该函数中提取单个形状?@klaus如果图像可以存储为数组,则可以。我的答案的一部分实际上是从数组中检索轮廓坐标。你能举一个库的例子吗?基于这个极好的答案,我写了一个小项目,可以用贝塞尔曲线做一些事情(生成随机形状,生成随机形状的数据集,网格形状,形状为孔的网格域,等等)。阅读此页面的人可能会对此感兴趣:我刚刚发布了一个类似的问题,可能会感兴趣(希望不是重复的):fig=plt.figure()name错误:名称“plt”未定义为plt@OriolAbrilimport matplotlib.pyplot
fig, ax = plt.subplots()
ax.set_aspect("equal")

rad = 0.2
edgy = 0.05

for c in np.array([[0,0], [0,1], [1,0], [1,1]]):

    a = get_random_points(n=7, scale=1) + c
    x,y, _ = get_bezier_curve(a,rad=rad, edgy=edgy)
    plt.plot(x,y)

plt.show()