Crea forma / contorno aleatorio usando matplotlib

Estoy tratando de generar una imagen de un contorno aleatorio usando python pero no pude encontrar una manera fácil de hacerlo.

Aquí hay un ejemplo de lo que quiero:

introduzca la descripción de la imagen aquí

Inicialmente, pensé en hacerlo usando las funciones matplotlib y gaussiana, pero ni siquiera podía acercarme a la imagen que mostraba.

¿Hay una forma sencilla de hacerlo?

Aprecio cualquier ayuda

Matplotlib Path

Una forma sencilla de lograr formas aleatorias y bastante suaves es mediante el módulo matplotlib.path .

Usando una curva de Bézier cúbica, la mayoría de las líneas se suavizarán, y el número de bordes afilados será uno de los parámetros para ajustar.

Los pasos serían los siguientes. Primero se definen los parámetros de la forma, estos son el número de bordes nítidos n y la perturbación máxima con respecto a la posición predeterminada en el círculo r la unidad. En este ejemplo, los puntos se mueven desde el círculo unitario con una corrección radial, que modifica el radio de 1 a un número aleatorio entre 1-r , 1+r .

Es por eso que los vértices se definen como seno o coseno del ángulo correspondiente por el factor del radio, para colocar los puntos en el círculo y luego modificar su radio para introducir el componente aleatorio. La stack , .T para transponer y [:,None] son simplemente para convertir las matrices a la entrada aceptada por matplotlib.

A continuación hay un ejemplo que utiliza este tipo de corrección radial:

 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 

Lo que para n=8 y r=0.7 produce formas como estas:

formas


Gaussian filtrada matplotlib camino

También existe la opción de generar la forma con el código anterior para una sola forma, y ​​luego usar scipy para realizar un filtrado gaussiano de la imagen generada.

La idea principal detrás de realizar un filtro gaussiano y recuperar la forma suavizada es crear una forma rellena; guarde la imagen como una matriz 2d (cuyos valores estarán entre 0 y 1, ya que será una imagen en escala de grises); luego aplicar el filtro gaussiano; y, finalmente, obtener la forma suavizada como el contorno de 0.5 de la matriz filtrada.

Por lo tanto, esta segunda versión se vería así:

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

forma suave

El problema es que el tipo de formas aleatorias que se muestra en la pregunta no es verdaderamente aleatorio. De alguna manera son formas alisadas, ordenadas, aparentemente aleatorias. Mientras que la creación de formas verdaderamente aleatorias es fácil con la computadora, la creación de esas formas seudoaleatorias es mucho más fácil mediante el uso de un lápiz y papel.

Por lo tanto, una opción es crear estas formas de manera interactiva. Esto se muestra en la pregunta Ajuste de BSpline interactivo en Python .

Si desea crear formas aleatorias mediante progtwigción, podemos adaptar la solución a Cómo conectar puntos teniendo en cuenta la posición y la orientación de cada una de ellas utilizando curvas de Bézier cúbicas .

La idea es crear un conjunto de puntos aleatorios a través de get_random_points y llamar a una función get_bezier_curve con esos. Esto crea un conjunto de curvas bezier que se conectan suavemente entre sí en los puntos de entrada. También nos aseguramos de que sean cíclicos, es decir, que la transición entre el punto inicial y el final también sea suave.

 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)**(nk) 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 = cT 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) 

Puede utilizar esas funciones, por ejemplo, como

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

introduzca la descripción de la imagen aquí

Podemos comprobar cómo los parámetros influyen en el resultado. Hay esencialmente 3 parámetros para usar aquí:

  • rad , el radio alrededor de los puntos en los que se encuentran los puntos de control de la curva bezier. Este número es relativo a la distancia entre los puntos adyacentes y, por lo tanto, debería estar entre 0 y 1. Cuanto mayor sea el radio, más nítidas serán las características de la curva.
  • edgy , un parámetro para determinar la suavidad de la curva. Si es 0, el ángulo de la curva a través de cada punto será la media entre la dirección y los puntos adyacentes. Cuanto más grande sea, más se determinará el ángulo solo por un punto adyacente. La curva por lo tanto se pone “más afilada”.
  • n el número de puntos aleatorios a utilizar. Por supuesto, el número mínimo de puntos es 3. Cuantos más puntos uses, más ricas en características pueden llegar a ser las formas; a riesgo de crear solapamientos o bucles en la curva.

introduzca la descripción de la imagen aquí introduzca la descripción de la imagen aquí introduzca la descripción de la imagen aquí

Para responder a tu pregunta, no hay una manera simple de hacerlo. Generar cosas al azar que se vean y se sientan naturales es un problema mucho más difícil de lo que parece al principio, es por eso que las cosas como el ruido perlin son técnicas de importancia.

Cualquier enfoque programático tradicional (que no implique, digamos, redes neuronales) probablemente terminará como un proceso involucrado de varios pasos para elegir puntos aleatorios, colocar formas, dibujar líneas, etc., sintonizarse hasta que se vea como lo desea. Con este tipo de enfoque, será muy difícil obtener cualquier cosa que genere formas de forma tan dinámica y con un aspecto orgánico como sus ejemplos desde cero.

Si está más interesado en el resultado que en la implementación, puede intentar encontrar una biblioteca que genere texturas aleatorias suaves de apariencia convincente y recortar las líneas de contorno de ellas. Ese es el único enfoque “fácil” que me viene a la mente en este momento. Aquí hay un ejemplo de ruido perlin. Tenga en cuenta las formas formadas a partir de los niveles de gris. Ruido perlin, colores cuantizados.