paint-brush
Cómo crear arte generativo digital con Pythonpor@iceorfire
12,475 lecturas
12,475 lecturas

Cómo crear arte generativo digital con Python

por Ice or Fire2022/01/02
Read on Terminal Reader
Read this story w/o Javascript

Demasiado Largo; Para Leer

El arte generativo es una forma de hacer que las imágenes de computadora se vean más orgánicas y menos artificiales. Estamos usando Perlin Noise para que nuestras imágenes se vean mejor. Le mostraremos el código y luego desglosaremos lo que está haciendo. La longitud de línea actual es una variable de seguimiento. El campo de flujo se usa para crear arte único cada vez que se ejecuta. El código se llama "Campos de flujo" y vamos a utilizar cuatro piezas de código: pintor, utils, numpy, Perlin2D, QColor y QPen.

People Mentioned

Mention Thumbnail
featured image - Cómo crear arte generativo digital con Python
Ice or Fire HackerNoon profile picture

¡Tú puedes ser el próximo Jackson Pollock!

Bueno, probablemente no, al menos puedes crear un arte atractivo con el código de Python.

De pie sobre los hombros de los gigantes

Todavía estoy aprendiendo arte generativo y necesitaba un lugar para comenzar, así que tomé el código de Absolute-Tinkerer y lo simplifiqué con fines de aprendizaje. Compruébalo en Github .

Pincel digital

Entonces, ¿qué es el arte generativo de todos modos? Básicamente, está escribiendo código que sigue algunas reglas y usa entradas aleatorias para crear arte único cada vez que se ejecuta. Hay muchos tipos de arte generativo, pero solo nos vamos a centrar en los campos de flujo .

¿Entonces, qué necesitamos?

Vamos a utilizar cuatro piezas de código:

  • pintor (de Absolute-Tinkerer)
  • utils (de Absolute-Tinkerer)
  • numpy (instalación de pip numpy)
  • nuestro código a continuación

Ruido Perlin? ¿Que es eso?

TL; DR En la década de 1980, este tipo realmente inteligente que trabajaba en la película Tron ideó una forma de hacer que las imágenes de computadora se vean más orgánicas y menos artificiales. Puedes leer más sobre esto en Wikipedia . Estamos usando Perlin Noise para que nuestras imágenes se vean mejor.

¡Muéstrame el código ya!

Te mostraré el código y luego desglosaremos lo que está haciendo. Todavía estoy aprendiendo arte generativo, así que si estoy describiendo algo incorrectamente, házmelo saber en los comentarios a continuación.


 import math import random import numpy as np from PyQt5.QtGui import QColor, QPen from PyQt5.QtCore import QPointF import painter from utils import QColor_HSV, save, Perlin2D def draw(width, height, color=200, backgroundColor=(0,0,0), perlinFactorW=2, perlinFactorH=2, step=0.001): seed = random.randint(0, 100000000) # Set the random seed for repeatability np.random.seed(seed) p = painter.Painter(width, height) # Allow smooth drawing p.setRenderHint(p.Antialiasing) # Draw the background color p.fillRect(0, 0, width, height, QColor( *backgroundColor )) # Set the pen color p.setPen(QPen(QColor(150, 150, 225, 5), 2)) print('Creating Noise...') p_noise = Perlin2D(width, height, perlinFactorW, perlinFactorH) print('Noise Generated!') MAX_LENGTH = 2 * width STEP_SIZE = step * max(width, height) NUM = int(width * height / 1000) POINTS = [(random.randint(0, width - 1), random.randint(0, height - 1)) for i in range(NUM)] for k, (x_s, y_s) in enumerate(POINTS): print(f'{100 * (k + 1) / len(POINTS):.1f}'.rjust(5) + '% Complete', end='\r') # The current line length tracking variable c_len = 0 # Actually draw the flow field while c_len < MAX_LENGTH: # Set the pen color for this segment sat = 200 * (MAX_LENGTH - c_len) / MAX_LENGTH hue = (color + 130 * (height - y_s) / height) % 360 p.setPen(QPen(QColor_HSV(hue, sat, 255, 20), 2)) # angle between -pi and pi angle = p_noise[int(x_s), int(y_s)] * math.pi # Compute the new point x_f = x_s + STEP_SIZE * math.cos(angle) y_f = y_s + STEP_SIZE * math.sin(angle) # Draw the line p.drawLine(QPointF(x_s, y_s), QPointF(x_f, y_f)) # Update the line length c_len += math.sqrt((x_f - x_s) ** 2 + (y_f - y_s) ** 2) # Break from the loop if the new point is outside our image bounds # or if we've exceeded the line length; otherwise update the point if x_f < 0 or x_f >= width or y_f < 0 or y_f >= height or c_len > MAX_LENGTH: break else: x_s, y_s = x_f, y_f save(p, fname=f'image_{seed}', folder='.', overwrite=True) draw(3000, 2000, color=63, perlinFactorW=4, perlinFactorH=5, step=0.35)


Importamos nuestros módulos de Python:

 import math import random import numpy as np from PyQt5.QtGui import QColor, QPen from PyQt5.QtCore import QPointF


Luego importamos el código del proyecto de Absolute-Tinkerer (ver arriba):

 import painter from utils import QColor_HSV, save, Perlin2D


Ahora llegamos a la función dibujar que tiene 2 argumentos posicionales y 5 argumentos de palabras clave:

 def draw(width, height, color=200, backgroundColor=(0,0,0), perlinFactorW=2, perlinFactorH=2, step=0.001):

Describiremos los argumentos cuando lleguemos a ellos en el código.

 seed = random.randint(0, 100000000) # Set the random seed for repeatability np.random.seed(seed)


Seleccionamos aleatoriamente un número entero entre 0 y 100000000 y luego usamos ese número para establecer una semilla aleatoria en numpy.

 p = painter.Painter(width, height) # Allow smooth drawing p.setRenderHint(p.Antialiasing) # Draw the background color p.fillRect(0, 0, width, height, QColor( *backgroundColor )) # Set the pen color p.setPen(QPen(QColor(150, 150, 225, 5), 2))


En esta parte del código, definimos nuestro pintor, que es una instancia de QPainter (una clase que nos permite "pintar" píxeles). Usando nuestro pintor, activamos el antialiasing para suavizar los ángulos, rellenamos un rectángulo con nuestro color de fondo provisto y finalmente configuramos nuestro color de pluma para dibujar.

 print('Creating Noise...') p_noise = Perlin2D(width, height, perlinFactorW, perlinFactorH) print('Noise Generated!')


Aquí estamos usando el generador de ruido Perlin de Absolute-Tinkerer para hacer nuestro ruido. Esto permitirá cálculos de ángulos más adelante en el código.

 MAX_LENGTH = 2 * width STEP_SIZE = step * max(width, height) NUM = int(width * height / 1000) POINTS = [(random.randint(0, width - 1), random.randint(0, height - 1)) for i in range(NUM)]


MAX_LENGTH actúa como un protector para que no dibujemos/calculemos valores fuera de los bordes de nuestra imagen y también se usa en algunos cálculos de color.


STEP_SIZE se usa para calcular puntos de dibujo con valores más pequeños que conducen a curvas orgánicas y valores más grandes que generan características "caóticas". NUM dicta cuántos puntos creamos.


Finalmente, PUNTOS calcula puntos (coordenadas X, Y) utilizando la comprensión de listas.

 for k, (x_s, y_s) in enumerate(POINTS): print(f'{100 * (k + 1) / len(POINTS):.1f}'.rjust(5) + '% Complete', end='\r')


Para cada uno de nuestros puntos, obtenga sus coordenadas (X, Y) y muestre nuestro porcentaje completado para dibujar nuestra imagen.

 # The current line length tracking variable c_len = 0


Establezca nuestra variable de seguimiento en 0.

 # Actually draw the flow field while c_len < MAX_LENGTH:


Siga repitiendo hasta que nuestra variable de seguimiento sea menor que nuestro límite de imágenes.

 # Set the pen color for this segment sat = 200 * (MAX_LENGTH - c_len) / MAX_LENGTH hue = (color + 130 * (height - y_s) / height) % 360 p.setPen(QPen(QColor_HSV(hue, sat, 255, 20), 2))


Calcule nuestra variable sat para nuestro valor de saturación y tono para nuestro valor de tono. (Lea una buena explicación sobre el matiz y la saturación ). A continuación, traducimos nuestros valores a un color y lo aplicamos al lápiz de dibujo de nuestro pintor.

 # angle between -pi and pi angle = p_noise[int(x_s), int(y_s)] * math.pi # Compute the new point x_f = x_s + STEP_SIZE * math.cos(angle) y_f = y_s + STEP_SIZE * math.sin(angle)


Usando nuestras coordenadas actuales (X, Y) en x_s y y_s calculamos nuestra variable de ángulo . A continuación, encontramos el coseno del ángulo, lo multiplicamos por STEP_SIZE , lo sumamos a nuestro valor X actual. Siga un proceso similar con el valor Y actual. ¡Ya tenemos las coordenadas de nuestro nuevo punto!

 # Draw the line p.drawLine(QPointF(x_s, y_s), QPointF(x_f, y_f))


Cree QPointF s para nuestros puntos actuales y nuevos y luego dibuje una línea entre ellos.

 # Update the line length c_len += math.sqrt((x_f - x_s) ** 2 + (y_f - y_s) ** 2)


Eleva al cuadrado la diferencia entre los valores X antiguos y nuevos y súmalo a la diferencia cuadrada entre los valores Y antiguos y nuevos. Tome la raíz cuadrada de ese valor y súmela al valor actual de c_len .

 # Break from the loop if the new point is outside our image bounds # or if we've exceeded the line length; otherwise update the point if x_f < 0 or x_f >= width or y_f < 0 or y_f >= height or c_len > MAX_LENGTH: break else: x_s, y_s = x_f, y_f


Si estamos fuera de los límites de la imagen, no haga nada. De lo contrario, guarde las nuevas coordenadas en las antiguas variables de coordenadas. De esa forma, estarán disponibles para la siguiente iteración del bucle.

 save(p, fname=f'image_{seed}', folder='.', overwrite=True)


¡Finalmente un código simple! Tomamos nuestra instancia de pintor y guardamos sus píxeles en nuestra imagen jpg.

 draw(3000, 2000, color=63, perlinFactorW=4, perlinFactorH=5, step=0.35)


¡Llama al código y genera nuestra obra maestra!

¿Qué aprendimos?

Al usar algunas matemáticas algo complicadas, podemos generar imágenes únicas con entradas muy limitadas.

Sección de bonificación AKA Tweak Some Params

Si llamamos a la función dibujar utilizando los parámetros enumerados anteriormente, obtendremos una imagen caótica que nos recuerda al arte de cuerdas . ¿Porqué es eso? Gran pregunta al azar amigo de Internet! Con valores de paso grandes (0,35 frente al valor predeterminado de 0,001), nos separamos más entre los puntos y generamos menos puntos. Eso conduce a líneas en lugar de suaves curvas orgánicas.


¡Intente llamar a la función con diferentes valores para el paso y los factores de Perlin para ver qué imágenes puede hacer!