Код как кисть: как Python превращается в художника с помощью генеративных алгоритмов

от автора

Можно ли научить Python рисовать? Эта статья — не сухой туториал, а настоящая история экспериментов с генеративным искусством. В ней рассказывается о создании цифровых картин с помощью случайностей, математики, шума Перлина и любви к визуальному абсурду. Много кода, немного философии и никакой нейросети — только чистый Python и жажда выразительности.

Порой программисту хочется отдохнуть от API, CRUD и фреймворков, и попробовать что-то бессмысленное, но красивое. Вот как программисты становятся художниками. Кто-то берет Blender, кто-то — нейросети. А кто-то — открывает matplotlib и начинает играться со случайными числами.

Сначала всё выглядит как чёртов бардак. А потом вдруг — бах! — появляется текстура, от которой не отвести глаз. Именно так начался этот эксперимент. Ни плана, ни цели. Только желание рисовать с помощью кода.

Идея проста: сгенерировать изображение с нуля, используя только Python и немного хаоса. Никаких pre-trained моделей, никакого ML. Только математические функции и воображение. Это — код как кисть.


Что понадобится

  • Python 3.10+

  • Библиотеки: numpy, matplotlib, Pillow, noise

pip install numpy matplotlib Pillow noise

Первый мазок: случайные цвета и шум

Цифровое полотно — это просто массив. Каждый пиксель — это точка с координатами и цветом. Начнем с создания цветной карты с использованием шума Перлина.

import numpy as np from PIL import Image from noise import pnoise2  width, height = 800, 800 scale = 100.0 image = Image.new("RGB", (width, height)) pixels = image.load()  for x in range(width):     for y in range(height):         nx = x / scale         ny = y / scale         value = pnoise2(nx, ny, octaves=6)         r = int((value + 0.5) * 255) % 256         g = int((0.5 - value) * 255) % 256         b = (x * y) % 256         pixels[x, y] = (r, g, b)  image.save("perlin_art.png")

Шум Перлина позволяет избежать классической зернистости случайных чисел и создаёт «органичную» текстуру.


Углубляемся: цветовые поля с контролем хаоса

Иногда картине не хватает композиции. Добавим цветовые поля, ограниченные по форме, но заполненные шумом. Это похоже на эксперимент в стиле Марка Ротко, только для интровертов с клавиатурой.

def draw_colored_field(image, center, radius, base_color, scale=30.0):     pixels = image.load()     cx, cy = center     for x in range(image.width):         for y in range(image.height):             dx, dy = x - cx, y - cy             if dx * dx + dy * dy <= radius * radius:                 nx, ny = x / scale, y / scale                 noise_val = pnoise2(nx, ny, octaves=4)                 r = int((base_color[0] + noise_val * 50) % 256)                 g = int((base_color[1] + noise_val * 50) % 256)                 b = int((base_color[2] + noise_val * 50) % 256)                 pixels[x, y] = (r, g, b)  image = Image.new("RGB", (800, 800), color="white") draw_colored_field(image, (400, 400), 250, (100, 50, 200)) image.save("field_art.png")

Можно нарисовать несколько полей с разными цветами — и получить сюрреалистичный пейзаж.


Штрихи — линии и повторения

Настоящий художник знает цену повтору. Программист может зациклить его. Добавим абстрактные линии, создающие ощущение ритма.

import matplotlib.pyplot as plt  fig, ax = plt.subplots(figsize=(8, 8)) ax.set_xlim(0, 100) ax.set_ylim(0, 100)  for i in range(50):     x = np.linspace(0, 100, 1000)     y = 50 + 20 * np.sin(x * 0.1 + i)     ax.plot(x, y, alpha=0.1, color='black')  ax.axis('off') plt.savefig("lines_art.png", bbox_inches='tight', pad_inches=0)

Дополнительно: фрактальные формы

Иногда простая рекурсия может нарисовать нечто удивительное. Пример — дерево, где каждая ветка порождает новые ветки:

from PIL import Image, ImageDraw import math  def draw_branch(draw, x, y, angle, depth):     if depth == 0:         return     x2 = x + int(math.cos(angle) * depth * 10.0)     y2 = y + int(math.sin(angle) * depth * 10.0)     draw.line((x, y, x2, y2), fill=(0, 100 + depth * 15, 0), width=depth)     draw_branch(draw, x2, y2, angle - 0.3, depth - 1)     draw_branch(draw, x2, y2, angle + 0.3, depth - 1)  img = Image.new("RGB", (800, 600), (255, 255, 255)) draw = ImageDraw.Draw(img) draw_branch(draw, 400, 500, -math.pi / 2, 10) img.save("tree_fractal.png")

Дополнительно: генерация лабиринта

А что если рисовать нечто строгое, но всё ещё случайное? Например, лабиринт по алгоритму depth-first search.

import random from PIL import Image, ImageDraw  width, height = 40, 40 cell_size = 20 maze = [[0] * width for _ in range(height)]  stack = [(0, 0)] visited = set(stack)  def neighbors(x, y):     for dx, dy in [(0, -1), (1, 0), (0, 1), (-1, 0)]:         nx, ny = x + dx, y + dy         if 0 <= nx < width and 0 <= ny < height and (nx, ny) not in visited:             yield nx, ny  while stack:     x, y = stack[-1]     next_cells = list(neighbors(x, y))     if next_cells:         nx, ny = random.choice(next_cells)         visited.add((nx, ny))         stack.append((nx, ny))         maze[ny][nx] = 1     else:         stack.pop()  img = Image.new("RGB", (width * cell_size, height * cell_size), "white") draw = ImageDraw.Draw(img) for y in range(height):     for x in range(width):         if maze[y][x] == 0:             draw.rectangle(                 [x * cell_size, y * cell_size, (x+1) * cell_size, (y+1) * cell_size], fill="black")  img.save("maze.png")

Дополнительно: пиксельные узоры по формулам

Иногда достаточно просто поиграть с уравнениями. Например, построим картину из уравнений:

from PIL import Image import math  w, h = 600, 600 img = Image.new("RGB", (w, h)) pixels = img.load()  for x in range(w):     for y in range(h):         r = int((math.sin(x * 0.01) + 1) * 127)         g = int((math.cos(y * 0.01) + 1) * 127)         b = int((math.sin((x + y) * 0.01) + 1) * 127)         pixels[x, y] = (r, g, b)  img.save("formula_art.png")

Финальный аккорд: собрать всё вместе

Создание генеративного искусства — это как варка борща: важно не столько следовать рецепту, сколько пробовать на вкус. Хочется больше красного? Убавь шум. Не хватает контраста? Прогони через градиент.

Вот пример итогового подхода:

  1. Генерируем текстуру шума — фон

  2. Добавляем несколько цветовых пятен

  3. Поверх — линии или фигуры

  4. Всё — сохраняем и радуемся


Личное: а зачем вообще всё это?

Иногда очень полезно отвлечься от задач, которые «имеют смысл». Генеративное искусство — это способ побыть в потоке, поиграть с хаосом и логикой одновременно. Это момент, когда математика перестаёт быть скучной, а код — просто инструментом.

Кто-то скажет: «А зачем? Это же бесполезно». Но в том-то и дело. Это красиво. А значит — полезно.


Заключение

Программирование не обязано быть только про производительность и бенчмарки. Оно может быть про чувство. Про визуальный кайф. Про баг, который стал картиной.

Python — это не просто язык, это кисть. И как её использовать — зависит только от фантазии.

Если после этой статьи ты открыл PIL, попробовал сгенерировать хоть что-то, и на секунду почувствовал себя художником — значит, всё не зря.


P.S. Можно добавить генерацию GIF, анимацию, или даже использовать tkinter для интерактива. Но это уже совсем другая история.


ссылка на оригинал статьи https://habr.com/ru/articles/904672/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *