web-dev-qa-db-ja.com

matplotlibを使用してランダムな形状/輪郭を作成する

python=を使用してランダムな輪郭の画像を生成しようとしていますが、簡単な方法が見つかりませんでした。

これが私が欲しいものの一種の例です:

enter image description here

最初はmatplotlib関数とガウス関数を使用してそれを行うことを考えましたが、表示した画像に近づけることさえできませんでした。

それを行う簡単な方法はありますか?

助けてくれてありがとう

7
klaus

matplotlibパス

ランダムで非常に滑らかな形状を実現する簡単な方法は、 matplotlib.path モジュールを使用することです。

3次ベジェ曲線を使用すると、ほとんどの線が滑らかになり、シャープエッジの数は調整するパラメーターの1つになります。

手順は次のようになります。最初に、形状のパラメータが定義されます。これらは、鋭いエッジの数nと、単位円のデフォルトの位置rに対する最大の摂動です。この例では、半径を1から1-r1+rの間の乱数に変更する放射状補正を使用して、ポイントを単位円から移動します。

そのため、頂点は、対応する角度と半径係数の積の正弦または余弦として定義され、ドットを円に配置し、ランダムな成分を導入するために半径を変更します。転置するstack.Tおよび[:,None]は、配列をmatplotlibで受け入れられる入力に変換するだけです。

以下は、この種の放射状補正を使用した例です。

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

n=8およびr=0.7の場合、次のような形状が生成されます。

shapes


ガウスフィルター処理されたmatplotlibパス

上記のコードを使用して単一の形状の形状を生成し、scipyを使用して生成された画像の ガウシアンフィルタリング を実行するオプションもあります。

ガウスフィルターを実行し、平滑化された形状を取得する背後にある主なアイデアは、塗りつぶされた形状を作成することです。画像を2D配列として保存します(グレースケール画像になるため、その値は0から1の間になります)。次に、ガウスフィルターを適用します。そして最終的に、フィルターされた配列の0.5の輪郭として平滑化された形状を取得します。

したがって、この2番目のバージョンは次のようになります。

# 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)

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 = 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')

smooth shape

6
OriolAbril

問題は、質問に示されているランダムな形状の種類が真にランダムではないことです。それらはどういうわけか平滑化され、規則正しく、ランダムな形に見えます。真にランダムな形状を作成することはコンピューターで簡単ですが、それらの疑似ランダムな形状を作成することは、ペンと紙を使用することではるかに簡単になります。

したがって、1つのオプションは、そのような形状をインタラクティブに作成することです。これは質問 PythonのインタラクティブBスプラインフィッティング に示されています。

プログラムでランダムな形状を作成したい場合は、ソリューションを それぞれの位置と方向を考慮してポイントを接続する方法 を使用して解決策を適用できます3次ベジェ曲線

アイデアは、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)

これらの機能を使用できます。なので

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()

enter image description here

パラメータが結果にどのように影響するかを確認する場合があります。ここで使用するパラメータは基本的に3つあります。

  • rad、ベジエ曲線の制御点が位置する点の周りの半径。この数値は、隣接するポイント間の距離を基準にしているため、0から1の間である必要があります。半径が大きいほど、曲線の特徴がよりシャープになります。
  • edgy、曲線の滑らかさを決定するパラメーター。 0の場合、各ポイントを通る曲線の角度は、隣接するポイントへの方向の間の平均になります。大きくなるほど、隣接する1点のみで角度が決定されます。したがって、曲線は「鋭く」なります。
  • n使用するランダムポイントの数。もちろん、ポイントの最小数は3です。使用するポイントが多いほど、形状の機能が豊富になります。カーブにオーバーラップまたはループが発生するリスクがあります。

enter image description hereenter image description hereenter image description here

あなたの質問に答えるために、それを行う簡単な方法はありません。自然に見える感じのランダムなものを生成することは、最初に思われるよりもはるかに難しい問題です。そのため、パーリンノイズのようなものが重要なテクニックです。

従来のプログラムによるアプローチ(たとえば、ニューラルネットワークを含まない)は、ランダムなポイントの選択、形状の配置、線の描画など、必要な方法で見栄えがするまで微調整する、複雑な複数ステップのプロセスとなるでしょう。この種のアプローチでは、例のように動的で有機的な外観の形状を確実に生成するものを最初から取得することは非常に困難です。

実装よりも結果に関心がある場合は、説得力のある滑らかなランダムテクスチャを生成し、それらから輪郭線を切り取るライブラリを見つけてみてください。それが、今思い浮かぶ唯一の「簡単な」アプローチです。これはパーリンノイズの例です。グレーレベルから形成された形状に注意してください。 Perlin noise, quantized colors.

3
Mateus Reis