Все любят генеративное искусство и всё что с ним связано (вот оно слева направо, в конце есть прикольные ссылочки).

вы только посмотрите, какое изображение!!!
вы только посмотрите, какое изображение!!!

(p.s. в описаниях к сгенерированным изображениям фразы, которые я подавал в генератор)

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

Скрипт генерации представлял из себя один файл, который через Tkinter случайным образом создавал картинку. Так как лучший способ полностью понять работы кода - это зарефакторить его, то этим я и занялся, и вот ссылка на перерефакторенную версию скрипта.

А теперь немного к сути работы.

Общее описание

Random Image Generator - это небольшая программа, которая генерирует изображение по заданной фразе. Пример:

случайная фраза
случайная фраза
вторая случайная фраза
вторая случайная фраза

Общая суть генерации заключается в том, что создаётся некий арт, и затем он применяется ко всем пикселям изображения. По сути, арт представляет собой огромную формулу из операторов с большой вложенностью. Оператор - это отдельный математический оператор (логично), например, синус/косинус/среднее, который принимает одни флоатики и выдаёт другие флоатики.

Флоатики, которые преобразуются в цвет, изначально берутся из положения пикселя отностительно центра изображения. Это позволяет получать одинаковые результаты при каждой генерации для одного арта и бесконечно масштабировать картинку. Положение подаётся как position = (size / pixel_number) * 2 - 1 чтобы иметь значение в [-1; 1]. А при установке пикселя на изображение каждый канал из float(-1; 1) переводится в int(0; 255).

Арт

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

Вот его пример, каждое название - отдельный оператор:

Длинное
Mod(
  Tent(
    Product(
      VariableY(),
      Sum(
        Constant(
          value=(0.3244364873390073, 0.7708296465915099, 0.9332060500466999)
        ),
        Constant(
          value=(0.5041825115944022, 0.6632634769751835, 0.1613102126504703)
        )
      )
    )
  ),
  Tent(
    Tent(
      Sin(
        Mix(
          Product(
            Sin(
              Mix(
                Mix(
                  Level(
                    VariableY(),
                    VariableX(),
                    VariableY(),
                    treshold=-0.10683892347003532
                  ),
                  Tent(
                    VariableY()
                  ),
                  Sum(
                    Sum(
                      Level(
                        VariableX(),
                        Constant(
                          value=(0.675473655969658, 0.5954164416187114, 0.449629381492357)
                        ),
                        VariableY(),
                        treshold=-0.5928609645964091
                      ),
                      VariableY()
                    ),
                    VariableY()
                  )
                ),
                Well(
                  Constant(
                    value=(0.5503713883487658, 0.49962352442393165, 0.7688050540403824)
                  )
                ),
                Level(
                  Sin(
                    Constant(
                      value=(0.8225282623320913, 0.3883003304362743, 0.9568771917767398)
                    ),
                    phase=2.0376783801515193,
                    frequency=3.9896008410954966
                  ),
                  Mix(
                    VariableX(),
                    VariableX(),
                    VariableY()
                  ),
                  VariableY(),
                  treshold=-0.621706133226186
                )
              ),
              phase=2.6735684842393255,
              frequency=1.3136102663251883
            ),
            Tent(
              Mix(
                VariableY(),
                Product(
                  Sum(
                    Level(
                      VariableY(),
                      Sin(
                        VariableY(),
                        phase=3.004718155488889,
                        frequency=1.4456333501008813
                      ),
                      Mix(
                        VariableX(),
                        Constant(
                          value=(0.9345878558040448, 0.20889624624509862, 0.6315200232850579)
                        ),
                        Product(
                          Constant(
                            value=(0.5330792534910264, 0.7945688505346382, 0.47051673946382233)
                          ),
                          VariableY()
                        )
                      ),
                      treshold=-0.38212437520625087
                    ),
                    VariableY()
                  ),
                  Well(
                    Mod(
                      Constant(
                        value=(0.400829150439062, 0.3021058233122762, 0.598367884016014)
                      ),
                      Sum(
                        VariableY(),
                        Constant(
                          value=(0.24750821288915192, 0.5625010703560506, 0.21725209844356919)
                        )
                      )
                    )
                  )
                ),
                Level(
                  Constant(  ## (1)
                    value=(0.8052143634939728, 0.8271696932063766, 0.6657108279633096)
                  ),
                  Sum(
                    Mod(
                      Constant(
                        value=(0.9054037881002341, 0.12796220865865926, 0.4943414103445982)
                      ),
                      Mod(
                        Product(
                          VariableX(),
                          Tent(
                            VariableX()
                          )
                        ),
                        VariableX()
                      )
                    ),
                    Product(
                      Constant(
                        value=(0.2665617642620427, 0.14347704782011006, 0.5622638203078165)
                      ),
                      Level(
                        Constant(
                          value=(0.23086906415935038, 0.37527352432134564, 0.6550565938107306)
                        ),
                        Constant(
                          value=(0.9743539853372215, 0.4993488372065832, 0.05428706152991847)
                        ),
                        VariableX(),
                        treshold=0.7809432727354246
                      )
                    )
                  ),
                  Product(
                    VariableX(),
                    Constant(
                      value=(0.35945935485851765, 0.4090601858176649, 0.7061945311933203)
                    )
                  ),
                  treshold=0.5365108582149631
                )
              )
            )
          ),
          Product(
            Sin(
              VariableY(),
              phase=1.3028027533404898,
              frequency=3.4563327126372814
            ),
            Constant(
              value=(0.7204939582620923, 0.11638980673023169, 0.06796149180653843)
            )
          ),
          Well(
            Mod(
              Level(
                Mod(
                  Constant(
                    value=(0.8232688016240477, 0.7483540167019266, 0.17127382751327436)
                  ),
                  VariableX()
                ),
                Constant(
                  value=(0.8002626489803971, 0.2922622157788455, 0.2775479167197744)
                ),
                Product(
                  VariableY(),
                  VariableX()
                ),
                treshold=-0.544640789851953
              ),
              VariableY()
            )
          )
        ),
        phase=1.9838654684998096,  ## (2)
        frequency=4.625078885960704
      )
    )
  )
)

Видно, что он длинный и имеет большую вложенность, хотя сложность (о ней чуть ниже) генерации не сильно большая. Если же поменять какое-то значение, то изображение должно поменяться. Вот примеры изображений, в которых (1) обнулён оператор Constant(value=(0.8052, 0.8272, 0.6657)) на Constant(value=(0.0, 0.0, 0.0)) и в метке (2) phase=1.9839, frequency=4.625 изменено на phase=1.0, frequency=3.0.

какой-то странный мем!
какой-то странный мем!

Можно посмотреть, что будет, если изменить сами операторы. Первую метку заменим Constant -> VariableX, а во второй Sin -> Well. Вот что получается:

а это ещё более странный мем!!!
а это ещё более странный мем!!!

В первом случае, так же как и при изменении значений, изменения на детализации, а во втором изменяется общий узор.

Общее сравнение полученных изображений

Подробнее разберём генерацию артов. Вот пример сгенерированного арта маленькой сложности:

Well(
  Mod(
    Level(
      VariableX(),
      Product(
        Sum(
          VariableY(),
          VariableY()
        ),
        VariableX()
      ),
      VariableY(),
      treshold=-0.36504581083005916
    ),
    Sum(
      Sum(
        Tent(
          Sum(
            Product(
              VariableX(),
              Product(
                VariableX(),
                VariableY()
              )
            ),
            Constant(
              value=(0.8837650122639356, 0.33526228359302135, 0.09636463380778282)
            )
          )
        ),
        Well(
          VariableX()
        )
      ),
      VariableX()
    )
  )
)

Дерево операторов в более наглядном виде:

дерево операторов
дерево операторов

Для генерации арта необходим параметр сложность, и это как раз то, насколько глубока будет генерация дерева. Для маленькой сложности дерево будет не особо большим (в этом примере сложность 12), но чем больше она будет - тем больше арт (в примере выше было 48) и сложнее изображение. Число сложности не имеет под собой каких-то особых вычислений, это просто случайно натыканный инт.

Алгоритм создания арта (дерева) просто рекурсивно создёт операторы, каждый раз уменьшая сложность у ветвей. Весь код генерации арта:

def generate_art(complexity: int) -> Operator:
    if complexity <= 0:
        # operators_flat - операторы с арностью 0
        plain_operator = random.choice(operators_flat)
        return plain_operator()

    # operators_dimensional - операторы с арностью > 0
    operator = random.choice(operators_dimensional)
    sub_complexities = [
        random.randrange(complexity)
        for _ in range(operator.arity - 1)
    ]

    suboperators = []
    last_complexity = 0
    for curr_complexity in sorted(sub_complexities):
        suboperator = generate_art(curr_complexity - last_complexity)
        suboperators.append(suboperator)
        last_complexity = curr_complexity

    suboperators.append(generate_art(complexity - 1 - last_complexity))

    return operator(*suboperators)

В этом коде есть два списка - operators_flat и operators_dimensional. Это два списка операторов, разница между которыми в их арности: в первом она нулевая, во втором ненулевая. Ключевая разница операторов в этих списках состоит в том, что ненулевые операторы имеют дочерних (по количеству арности), а нулевые не имеют. Нулевые просто выбирают значение, по которому будет происходить генерация (координата x/y/const), но не имеют никаких формул внутри, и ими же заканчивается любая ветвь в дереве арта. А ненулевые получают значения из дочерних, а дальше изменяют то, что получили, по описанной в них формуле. Так, единичные меняют одно значение по математической формуле, операторы с арностью 2+ же смешивают значение из нескольких цветов.

Входные параметры

Результат генерации арта зависит от 2-х вещей: состояние рандома и сложность. Состояние рандома может (и так делалось изначально) браться случайным, но его можно и устанавливать вручную перед генерацией, чтобы получать одинаковые результаты. Передо мной стояла задача генерировать изображения из фраз, поэтому за семя я беру фразу и устанавливаю её рандому непосредственно перед генерацией арта.

Сложность тоже нужно откуда-то брать при генерации, но в то же время необходимо иметь возможность задавать её вручную. Поэтому, если сложность не задана, то перед генерацией тем же ядром получаем случайное число из некоторого промежутка, которое затем используется как сложность.

Вот как изменяются картинки с одной фразой, но разной сложностью. Это интересно тем, как постепенно изменяется изображение с увеличением сложности арта. Здесь фраза случайная, а сложность изменяется от 1 до 148.

сложность от 1 до 148
сложность от 1 до 148

Картинки-картинки-картинки

А вот много изображений, не какие-то определённые результаты, а просто генерация по строчкам песни подряд (кстати, получилось удачно, как по мне).

Кто знает песню - молодец
Healing battles, mighty nations
Healing battles, mighty nations
Holy waters, Milky ways.
Holy waters, Milky ways.
The fierce proverbs, the crusades.
The fierce proverbs, the crusades.
Crooked nails, clear eyes
Crooked nails, clear eyes
Honorable herbaria, branchy cathedrals
Honorable herbaria, branchy cathedrals
Mountains of the forest, lousy rears.
Mountains of the forest, lousy rears.
Binding threads, lingering corridors.
Binding threads, lingering corridors.
Living beasts, warm lands.
Living beasts, warm lands.
Only combustible laughter, tin enemy, strawberry front
Only combustible laughter, tin enemy, strawberry front
Only earthy salt, wind-up mouse, blue wagon.
Only earthy salt, wind-up mouse, blue wagon.
Every moment an overdose for all the rest of the time
Every moment an overdose for all the rest of the time
For all the rest of the time.
For all the rest of the time.
Raw nooks, carbonated rivers.
Raw nooks, carbonated rivers.
Steel eyelids, formidable zeros.
Steel eyelids, formidable zeros.
Tired tantrums, widespread successes.
Tired tantrums, widespread successes.
Dry mouths, seventh heaven.
Dry mouths, seventh heaven.
Side-effects, deplorable grimaces.
Side-effects, deplorable grimaces.
Empty masses, loud words.
Empty masses, loud words.
Insistent conclusions, invisible skeletons.
Insistent conclusions, invisible skeletons.
Colored dreams and something else.
Colored dreams and something else.
Only earthly salt, winding pain, blue wagon.
Only earthly salt, winding pain, blue wagon.
Only combustible laughter, strawberry front, tin foe
Only combustible laughter, strawberry front, tin foe

Дальше

Что будет с этим всем дальше? Скорее всего, я немного потыкаю код и забью, это все мы любим делать, но в теории можно добавить:

  • новые операторы, операторы 4+ арности: сейчас операторов не так много, и интересно бы было добавить много новых, чтобы было меньше повторяющихся шаблонов, больше вариантов

  • компиляцию, кэширование вычислений: вычисление арта представляет их себя числодробление флоатов много раз для каждого пикселя. Питон, на котором написан генератор, в этом плох, но компилирование может сильно ускорить работу

  • гуишку: запуск идёт на чёрном фоне из консоли, а можно красиво и приятно для юзеров-непрограммистов запускать генерацию, более удобно передавать арт

Ссылочки

Не знаю, о чём ещё тут писать, поэтому держите красивые ссылочки:

Комментарии (0)