Введение
В этом году у меня прошло три персональных выставки портретов и картин, нарисованных роботами по моим алгоритмам. Во‑первых, это очень тяжело, а, во‑вторых, наверно, пора уже позаботиться о приоритете авторов алгоритма. Статья на Хабре, по уверениям нейросетей, на этом пути необходимый этап.
Мне 72 года. Сижу на даче, никого не трогаю. Зимой программирую алгоритмы, летом конструирую станочки. Как‑то после решения быстро сделать очередной станок типа «полярграф» (два моторчика, рисующих на стене), неожиданно задержался на этой теме на полгода. Пришлось конструировать заново ему гондолы (хороших не нашел) и писать для него весь софт (тут было всё ещё хуже), а добившись от него точности 0.3мм(!), обнаружил, что рисовать‑то нечего. Вот так я и вышел на тему рисования на плоттере, которой и занимаюсь уже несколько лет.
Из всего, что я тогда нашел в интернете, меня привлек только подход Волгодонского программиста Алексея Лянгузова (желающие могут ознакомиться с его работами на его сайте). Но и тут никто на блюдечке ничего не принес. Примеры работ были — на описание алгоритма не было даже намека. Неудивительно, что мой алгоритм в итоге (наверно?) очень похож на его алгоритм, ведь по сути я пытался его скопировать.
Итак, что мы имеем? Дана фотография, которую надо как‑то нарисовать шариковой ручкой на плоттере. Лица морщинистых стариков просто идеальны для этой цели. С них обычно начинают и получают быстрый результат. Дальше уже как получится. На первый взгляд задача выглядела довольно простой: что‑то типа сделать векторизацию изображения по оттенкам серого, обвести по контуру, заштриховать. На практике всё оказалось гораздо сложнее. Наверно, не меньше года ушло на то, что я теперь пытаюсь называть волгодонской фотографикой и алгоритмом штриховой векторизации Лянгузова‑Штефана.

Сегодня мои портреты формата А2 содержат от 100 до 160 тысяч штрихов. Робот (рисовальный станок) может рисовать одну работу до двух суток. Во время рисования дополнительно приходится учитывать износ пишущего узла ручки, изменение цвета чернил, изменение толщины линии, перебои питания, температурные эффекты, очистку пишущего узла, если карандаш, то своевременно выдвигать стержень и менять его, и множество других факторов, включая развал станка от много недельной вибрации. Периодически из него выпадают гайки и болтики и растягиваются ремни. И вишенка на торт: отношения с художниками, обвиняющими в отсутствии души у нарисованных роботами картин. Но на эту тему есть хорошая песня: Тыдынч‑тыдынч, Серёга, палатка, ледоруб. Там тоже всё сложно. Душа даже у бардовской песни появляется только при упоминании этих трех волшебных слов. Сарказм.
Самым неожиданным оказалось другое. Через некоторое время я обнаружил, что начиная с некоторого этапа улучшить алгоритм у меня никак не получается. Т.е. придумываешь ночью очередной сложный гениальный алгоритм и в результате пшик. И так раз за разом. Потом бросил. Есть подозрение, что кто бы ни пытался повторить решение этой задачи, получит тот же результат: упрется в предел разрешающей способности пишущего узла и восприятия человеческого глаза и повторит наш с Лянгузовым путь. А это означает, что алгоритм можно описывать как базовый. Лучше не сделают. Или сделают?
Когда же я выжал из фотографики всё, что мог, и начал заниматься математическим изучением и построением спирали Меллана по типу офорта «The Sudarium of Saint Veronica», я понял, что решал почти те же задачи, которые в XVII веке решал Клод Меллан при создании своего знаменитого офорта, и он решал их почти так же. Физика и физиология за четыре века не изменились.

Почему обычная векторизация не работает
Большинство алгоритмов векторизации пытались заменить изображение набором контуров, заполняя их штриховкой. Чем такой подход плох. Представьте прядь женских волос. Если векторизовать по оттенкам даже одну прядь любой женщины, то из‑за игры света алгоритм разобьёт её на несколько контуров по оттенкам. Появятся поперечные векторы‑штрихи, тогда как в жизни их нет. То есть, штрихи должны идти вдоль пряди и часто поперек контуров. Человеческое лицо также состоит не из контуров, а из плавных переходов оттенков по градиенту. При векторизации по оттенкам здесь тоже появляется множество контуров поперек градиентов яркости, но на оригинале-то их нет. Причем, волосы надо штриховать вдоль градиента, а щеки поперек. Но есть ещё и контур лица, особенно на контрастном фоне, и он чёткий. А если смотреть человеческий глаз, то там тоже чёткий контур почти всегда есть, причем почти всегда это окружность. И всё это слегка размыто. Почти неразрешимая задача создать алгоритм, одновременно учитывающий и контуры, и градиенты очень ограниченным набором технических средств. А набор средств очень ограничен. Я оцениваю число оттенков, которые может заметить глаз при штриховой векторизации в 10–12. Это простая задача для нейросети, но на чём её обучать?
Поэтому, как я понимаю, Лянгузов, а потом и я, полностью отказались от рисования длинных контуров и длинных штрихов и перешли на короткие штрихи. Те, кто сталкивался с форматом .dxf, знают, что и там окружности рисуются короткими штрихами. Я иногда пишу штрихи, а иногда векторы. В принципе, различия между ними возникают только при рисовании. Когда рисуешь очень короткие штрихи и пытаешься учесть наличие краски на шарике пишущего узла, то штрихи превращаются в векторы, так как у них появляется направление. В других случаях разницы я не вижу.
Как работает штриховая векторизация
Рассмотрим, как работает алгоритм штриховой векторизации Лянгузова‑Штефана.
1. Подготовка изображения.
Прежде всего изображение надо подготовить. Если это не профессиональная студийная съемка, то обязательна обработка изображения нейросетью, улучшающей качество. Опыт показал, что без сглаживания оттенков нейросетью, мы любым алгоритмом получим «грязный» результат. Побочный и неплохой эффект улучшения лица нейросетью это омоложение, хотя часто оно излишне. Теперь надо подправить динамический диапазон серого. Глазом оттенки серого воспринимаются неравномерно. Очень светлые места и очень темные места почти не различимы. Поэтому, по возможности, нелинейно переносим графическим редактором самые важные места в центр диапазона. При этом, хотя оттенки в светлой и темной части почти неразличимы, но они важны. Они мало различимы глазом, но хорошо различимы при векторизации и будут определять направление штрихов, иначе опять там будет «грязно».
2. Построение векторного поля.
Любой доступной программой векторизации, своим алгоритмом векторизации или даже несколькими для изображения строится векторное поле. Оно может быть как одномерным по оттенкам, так многомерным, учитывающим дополнительные признаки, такие как градиенты, блики и тому подобное. Понятно, что у черного квадрата векторное поле будет квадрат по краю. Это проблема. У рябой картинки будет огромное число пятен и, следовательно, разнонаправленных векторов. Это тоже проблема. Но это скорее исключения.
3. Построение точечного поля.
Самостоятельный этап алгоритма это построение точечного поля. Оно строится по яркости изображения, в зависимости от требуемого размера бумаги, параметров пишущего узла и требуемой отрисовки деталей. К этим точкам в дальнейшем будут привязаны штрихи. В темных местах точек/штрихов будет больше, в светлых местах штрихи располагаются реже.
4. Подбор линейки длин штрихов.
Примерно из тех же соображений, по которым строится точечное поле, строится таблица длин штрихов. Она нелинейна. Это дополнительно расширяет динамический диапазон отображения полутонов. Штрихи короче 0.5мм не позволяет рисовать пишущий узел, длинные штрихи не позволяют прорисовывать детали. Попытки делать штрихи переменной длины даже при одной яркости не дали улучшения в общем случае, но где‑то могут быть и нужны, например, при отрисовке графики с четкими краями рисунка.
5. Вычисление направления штриха для каждой точки точечного поля.
Простор для творчества и экспериментов. Наверно, в этом пункте все алгоритмы будут иметь индивидуальные отличия. Как из моря соседних векторов выбрать подходящий в нужной точке, я не подскажу. Как выбрать угол штриха, если рядом ничего нет? Тоже не подскажу. Кто как. Вариантов не очень много, но они есть. Я так даже не помню на чем в итоге остановился сам.
6. Размещение штрихов.
Теперь уже можно размещать штрихи на пустом бланке или бумаге, ориентируя их по результату обработки векторных полей и выводить их в файл/файлы. Здесь небольшой простор для фантазии, но тоже есть варианты, так как это уже реальные инструменты со своими свойствами.
7. Оптимизация штрихов.
Завершающий этап математической части алгоритма это оптимизация штрихов. Обычно она касается только темных и очень темных мест. Штрихи обязательно будут накладываться друг на друга, а это лишний расход и чернил, и времени. Где‑то, даже на полностью черном поле, останутся просветы, которые при желании тоже можно закрасить. Где‑то штрихи, которые накладываются друг на друга можно удалить. Где‑то они выстроятся в линию, что совсем плохо и лучше их как‑то развести.
8. Тестирование результата.
Необязательный этап, но очень полезный особенно при отсутствии опыта. Тестируем результат от явных ляпов и дефектов, которые обязательно проявятся. В качестве теста я использую моделирование портрета с отрисовкой штрихов с полутонами, затем визуальный анализ и размытие модели по Гауссу.
Мы видим, что упрощенно в алгоритме Лянгузова-Штефана контуры и тональность изображения задаются:
* плотностью расположения штрихов;
* длиной штрихов;
* направлением штрихов;
Также мы видим, что совершенно не учитываются контура после построения векторного поля. Также мы видим, что плотность точек и длина штрихов определяются глобально размером рисунка и параметрами пишущего узла. Чем больше рисунок, тем меньше требуется штрихов. Парадоксально, но это так. Большой рисунок смотрится с большего расстояния. Рисунок, например, размера А3(40×30см) смотрится примерно с полуметра и глаз с этого расстояния различает гораздо больше деталей, чем с метра или дальше. Для рисунка размера А1(84×60см) надо отойти на пару метров, а значит можно обойтись меньшим количеством штрихов и более толстыми линиями. Например, шариковую ручку заменить на капиллярную.
Именно эти параметры определяют восприятие изображения после интеграции глазом множества отдельных штрихов.
Тестирование алгоритма
Достаточно быстро возник вопрос: Как объективно понять, хорошо ли отработал алгоритм или плохо? Ответ оказался неожиданно простым. Если полученное штриховое изображение размыть фильтром Гаусса, результат должен быть максимально похож на исходную фотографию. Светлее, но похож до деталей.
Т.е. разбивая оригинал и векторный рисунок на фрагменты, хороший алгоритм должен давать по каждому фрагменту тот же оттенок, что и на оригинале. Или сразу подбирать параметры настройки алгоритма, учитывая это требование.
Фактически глаз наблюдателя выполняет аналогичную операцию при просмотре изображения с большого расстояния, интегрируя мелкие детали в единое целое. Этот критерий оказался намного полезнее оценки качества полученного результата на мониторе. Уменьшая размер не видно штрихов, а увеличивая масштаб, глаз упирается в часть рисунка, а там ничего не говорящие штрихи.

Черный квадрат, Белый круг и проблема блика.
Если у Малевича черный квадрат это чистый прикол, то для штриховой векторизации это проблема. Контур тут тоже квадрат. Внутри один оттенок, то есть опорные вектора отсутствуют. Интересно будет смотреть как решают эту проблему разные авторы. Белый круг, несмотря на большое число векторов, почти то же самое, что и черный квадрат. А вот светлый бильярдный шар уже интересней, потому что это чистый градиент и отличный тест для алгоритма сам по себе. И на каждом шаре обязательно есть блик. Глаз его хорошо различает, а алгоритм может не увидеть. Динамического диапазона оттенков штрихов для этого просто не хватает. Долго не мог решить, что тут делать. Потом просто для мало различимых, но важных участков дополнительно ввел другой алгоритм, дающий необходимый контраст. Проблема блика как‑то была решена поворотом штрихов и индивидуальным подбором их длины. После этого блик стал восприниматься глазом.

Ограничения зрения
Со временем стало понятно, что большинство улучшений алгоритма не упираются в вычислительные возможности. Они упираются в физиологию зрения. В штриховых техниках невозможно бесконечно увеличивать количество различимых полутонов. Так мы доувеличиваем до принтера. Практический диапазон будет всё‑равно удивительно небольшим: примерно 10–12 оттенков. Конечно, если бы я раньше занимался графикой, всё это я бы знал еще на этапе учебы.
Встреча с Мелланом
Несколько месяцев назад, когда я уже занимался выставками, а не алгоритмом, мне как‑будто намеренно подкинули гравюру Клода Меллана. Это знаменитый Лик Иисуса одной спиралью, которую вот уже 400 лет никто не может повторить. И я на это купился, уверенный, что я то повторить смогу. У меня станков полная мастерская, а у Меллана лишь резец и талант. Я внимательно изучил гравюру, очистил от мусора и перевел ее в векторы. Написал алгоритм более‑менее похожий на алгоритм Меллана для перевода любого изображения в спираль Меллана. Осталось «мелочь» повторить гравюру глубокой печатью, ни разу в жизни этого не делая, но не будем о грустном.
Что общего между фотографикой и спиралью Меллана.
Интересно было анализировать и пробовать понять как Меллан понял, что у него что‑то получится, затевая этот грандиозный проект. Прежде всего размер гравюры это примерно 40×30см, то есть наш А3. Мастера того времени были способны сделать гравюру размером с почтовую марку. Но тут спираль, а это жесткие ограничения: всегда должна быть видима непрерывная темная линия, всегда должны быть видны светлые зазоры между витками. И, вероятно, он просто начал работать на грани возможного: ширина витка на его гравюре от 0.6мм до 1.2мм, тонкие линии и зазоры 0.1–0.2мм. Это же в чистом виде 2-х факторный алгоритм спиральной векторизации. Если в фотографике полутона это плотность и длина штрихов, то здесь это ширина витка и толщина линии. Попытки увеличения скудного динамического диапазона полутонов. Как он додумался в темных местах сужать спираль, остается только гадать. Вот одна из гипотез. Возьмем фрагмент с яркостью 50%. Если принять ширину спирали одинаковой 1.2мм, то ширина линии должна быть 0.6мм при зазоре 0.6мм. А если сузить ширину витка до 0.8мм, то ширина линии 0.4мм при зазоре 0.4мм. И для чисто черного упираемся в соотношение 0.6мм ширина витка, 0.2 светлый зазор и 0.4 черная линия. Т.е. минимальная яркость 33%, максимальная яркость 83%. Это хуже, чем при рисовании штрихами. Возможно сказались и ограничения по инструменту и нужной глубине канавки. Чем шире резец, тем глубже получается канавка, а ее углублять сильно нельзя. А чтобы получить черный цвет канавка должна почти полностью заполнять ширину спирали. Значит ширину надо привязать к резцу. Но при глубокой печати канавка не должна быть ни глубокой, ни широкой. Краску, если ее будет много бумага не впитает и ее размажет при печати. Но сузить виток половина дела, надо еще вернуть его, чтобы спираль постепенно выходила на окружности. Головная боль в полярных координатах.
Века идут, но с каждой новой техникой решаются одни и те же проблемы. С этими же проблемами боролись аналоговые фотографы: зерно негатива и бумаги. Меньше зерно — выше качество. Выдержка, экспозиция и всё это ради точного попадания в динамический диапазон фотопленки и фотобумаги. С этими проблемами боролись изобретатели грампластинок. С этими проблемами боролись разработчики мониторов и принтеров.
Ну и необходимо еще раз отметить гений художника. При размытии офорта Меллана по Гауссу, я получил его исходный рисунок, по которому теперь отлаживаю уже свои алгоритмы векторизации по спирали Меллана. Т.е. прием по своей сути универсальный.
Промежуточные итоги
Начав с задачи рисования портретов роботом, я неожиданно пришел к изучению одной из самых известных гравюр XVII века, а теперь и к попытке повторения древних гравюр/офортов современными техническими средствами. Зигзаги судьбы. Сегодня мне кажется, что штриховая векторизация, дизеринг фотошопа и спираль Меллана являются разными попытками решения одной и той же фундаментальной задачи: Как качественно передать непрерывное изображение при минимуме технических средств. Имеем точки, штрихи, спираль, а в результате человеческий глаз должен как можно более точно восстановить исходный образ, не потеряв оттенков замысла, при его наличии. А если эту задачу решает еще и гений уровня Меллана, то результат на века. Это ведь не просто портрет. Это почти икона. Чуть в сторону и вместо чуда — катастрофа. Кстати, скромно отмечу, что восстановленного Иисуса с офорта Меллана в штриховой технике фотографики, я выставляю на своих выставках. Он и тут смотрится божественно.
Ящик стола
В процессе конструирования и программирования часто приходится что‑то изобретать. Меня обычно не угнетает, что я изобретаю велосипед, — главное мозги занять, но если что‑то из списка вас заинтересует, с удовольствием отвечу на вопросы.
Модульное построение портретов/панно из канцелярских кнопок любого размера.
Решение задач на перекладывание спичек (переложите 2 спички так, чтобы…).
Алгоритм идеального кругового обхода прямоугольной матрицы из центра к краям для фрезерования или рисования.
Простой и быстрый алгоритм нахождения центральной линии для берегов/спирали со сторонами из m и n отрезков. Результат длиной не более m+n отрезков.
