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

Про анаглиф


Предвижу вопросы «Почему анаглиф?» Технология прошлого века с кучей недостатков, а с появлением 3D-экранов/очков и VR-шлемов и вовсе считается давно мертвой. Причина проста: для современного 3D необходимо специальное оборудование и оно, по сравнению с простейшими красно-синими очками, стоит на несколько порядков дороже. А еще очки можно сделать самостоятельно из подручных средств.

Сразу раскрою технологию. Известно, что для бинокулярного зрения нам необходимы два изображения: отдельно для левого и правого глаз. Технически это два ракурса объекта с параллаксом около 6.5 см (расстояние между глазами). И, на первый взгляд, они практически неотличимы друг от друга, особенно когда предмет расположен в полутора метрах от глаз. На основании мизерной разницы в картинках наш мозг легко и весьма точно определяет расстояния до всех обозреваемых в данный момент времени объектов.

То есть, согласно теории, получить стереоизображение (SIRDS, стереопары, анаглиф) из одной картинки никак не получится, из-за того что оба глаза получат идентичные изображения. Однако проблема легко решается при наличии карты высот (или карты глубин — depth map) данной картинки. Мы просто строим воксельную поверхность и окрашиваем каждый воксель цветом соответствующего пикселя исходной фотографии.

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


Схематичное изображение пайплайна для получения анаглифа из единичной картинки

Прежде чем приступим к реализации, определимся с инструментами. Прежде всего это GIMP — кроссплатформенный графический редактор.

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



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

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

Формирование карты высот


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

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

Как уже писал, реализаций много, все они генерируют карту глубин различной степени детализации и с разной скоростью. Лично я использую всего два ресурса:

  • Tiling Zoe Depth — на мой взгляд, более детальная карта, но очень много блюра. Дополнительный плюс решения в том, что можно загружать картинки пачками. Работает на Google Colab, так что для доступа потребуется гугл-аккаунт.
  • Depth Anything V2 — более четкие границы и детали переднего плана. Работает в Hugging Face. Авторизация не требуется, с ограниченями по ресурсам я не столкнулся.

Также вы можете потестировать другие алгоритмы на Hugging Face, забив ключевое слово «depth» в строке поиска.

Генерация карты глубин с помощью Tiling Zoe Depth


Запускаем Сhrome и переходим на GitHub по ссылке BillFSmith/TilingZoeDepth, а оттуда на Google Colab по линку «v3» в README.md.

В Google Colab обязательно подключаем мощности удаленной среды выполнения: в правом верхнем углу нажимаем «Подключиться… GPU» и дожидаемся зеленой галочки. Запускаем код нажатием комбинации клавиш Ctrl + F9 или кликом по картинке с треугольником в кружочке.
В результате получаем кнопку для загрузки изображения, для которого мы хотим сгенерировать карту высот. Можно выбрать несколько изображений за раз. Это удобно, так как формирование вычислительного окружения занимает достаточно много времени.



В процессе установки зависимостей будет предупреждение — просто игнорируем. По ходу генерации будут отображаться карты высот в низком и высоком разрешениях. Скачивать их бессмысленно, так как по завершении вычислений браузер попросит разрешения принять их «оптом». Соглашаемся.



Если необходимо обработать дополнительные изображения, то запускаем повторно и выбираем новую порцию картинок. Процесс будет проходить быстрее, так как все библиотеки уже скачаны и среда выполнения настроена.

Когда закончите с обработкой, обязательно отключите ресурсы в правом верхнем углу. Смутно помню, что Google может забанить за их простаивание. Не проверял, но был момент, когда он достаточно долго уверял меня, что у него нет свободных ускорителей для запуска. Возможно, это и есть этакий «мягкий» бан.

Генерация карты глубин с помощью Depth Anything V2


Заходим по ссылке Depth-Anything-V2. Если пространство запущено, все просто: сразу включаемся в работу — загружаем изображение и нажимаем «Compute Depth».



В противном случае придется его стартануть и подождать, пока оно поднимется.

В конечном итоге скачиваем карту глубин по ссылке «Grayscale depth map» под нашими изображениями. Отсутствие пакетного режима компенсируется постоянной готовностью среды выполнения и скоростью генерации.

Если карта глубин немного не нравится


Возможно, вам покажется, что автоматические алгоритмы не совсем корректно определяют расстояние. Это легко поправить.

Загружаем карту глубин в GIMP и вызываем диалог «Цвет → Кривые» (Colors → Curves — здесь и далее я буду в скобках указывать названия элементов англоязычного интерфейса GIMP). Это нужно для корректировки кривой яркости (изображение монохромное). По оси абсцисс у нас цвет пикселя, по оси ординат — расстояние до зрителя, гистограмма — количество пикселей данного цвета. Ноль подразумевает понятие «далеко» (бесконечность, горизонт), а правый верхний угол — экран. В самом начале зависимость линейная, и ее можно поправить.

Если взять, к примеру, старинные фотографии, как в этой статье, то салонные снимки делались на раскрашенном заднике. Алгоритм ZoeDepth может добавить к ним объем, которого там явно не должно быть. Здесь он распознал дерево, кусты и немного странностей слева, а вот Depth Anything V2 их проигнорировал.



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



Но и злоупотреблять инструментом не стоит, так как при неосторожных манипуляциях могут возникнуть нежелательные артефакты — например, «картонные фигуры».

Также карту глубин можно подкорректировать и стандартными инструментами GIMP.

Делаем анаглиф


Итак, у нас есть изображение и карта глубин, теперь приступаем к формированию двух ракурсов.

Загружаем изображение и карту высот в GIMP как слои. Сразу предлагаю в меню «Изображение → Точность» установить «16 бит, с плавающей запятой» (Image → Precision: 16 bit floating point). Это позволит немного снизить наше негативное влияние на качество изображения. Слой с картой высот сдвигаем в самый низ.

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

Разумеется, GIMP не реализует полноценный воксельный движок, однако фильтр «Смещение» (Displace) выдает требуемый нам результат. На основе карты высот производится сдвиг пикселей исходного изображения в требуемом направлении, а образующиеся пустоты заполняются на базе интерполяции цвета соседних пикселей.

Делаем верхний слой активным через меню «Фильтры → Проекция → Смещение» (Filters → Map → Displace) вызываем диалог с настройками фильтра. По верхней кнопке «Вспомогательный вход» (Aux input) подгружаем карту глубин — там все наглядно, просто потыкайте мышью. Вторую кнопку не трогаем, так как в этом нет необходимости: она позволяет иметь разные карты глубин для сдвигов по вертикали и по горизонтали. В параметрах Sampler укажите Linear. Далее разрываем связь между сдвигом по горизонтали и вертикали (нам нужно только горизонтальное смещение) и задаем его. Для хорошего восприятия объема достаточно 6-10 пикселей. Жмем «Окей».



Артефакты при сдвиге


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



Если делать анаглиф так, как описано в различных источниках в сети — пара из оригинального изображения и изображения со сдвигом, то результат будет некрасивым. Поэтому поступаем проще: нужно двигать не одно изображение на 10 пикселей, а оба изображения в разные стороны на 5 пикселей. Для своих анаглифов я делаю так всегда.

На данном этапе в верхних слоях у нас уже есть два разных ракурса. Сразу можно сделать стереопару для Google Cardboard либо использовать ее для других методов формирования трехмерных изображений.

Ну а мы продолжим делать наш анаглиф. Сразу определимся, что он будет для красно-синих очков. Верхний слой — для левого глаза, то есть красным. Активируем его, и в меню «Цвет ` → Составляющие → Микшер каналов»(Colors → Components → Channel Mixer) обнуляем синий и зеленый каналы. На будущее такую конфигурацию сохраняем посредством кнопки «+» рядом со списком «Профили»(Presets) под именем «Red». Так, в следующий раз достаточно будет выбрать его из выпадающего списка, а не вносить значения вручную в несколько полей.



Второй слой содержит изображение для правого глаза. Так же, как и у левого (красного) изображения, через меню «Цвет → Составляющие → Микшер каналов» корректируем цветовые каналы: на этот раз полностью обнуляем все, что связано с красным. Аналогично сохраним его в профиль под именем «Blue».

Возвращаемся к слоям: верхний слой — красный, средний слой — синий, нижний слой — карта глубин. Активируем верхний (красный) слой и чуть выше выбираем «Режим» (Mode) смешения слоев «Разница» (Difference). Картинка сразу меняется: цветность нормализуется, появляются заметные синие и красные контуры там, где содержимое слоев не совпадает.



Улучшаем восприятие


Есть еще один параметр, который стоит отрегулировать, — сдвиг правой и левой картинок относительно друг друга. Погуглите картинки по строке «анаглиф параллакс», на них все будет наглядно нарисовано. Фактически это отодвигает или приближает трехмерное изображение относительно плоскости монитора.

Я это делаю, так как мне не нравятся красные/синие ореолы вокруг лиц или ключевых деталей изображения. Стараюсь, чтобы незначительные вещи на переднем или заднем планах были с максимальным расхождением, а центр композиции, на котором мы фокусируемся при просмотре, находился именно в плоскости экрана.

Сдвиг делается в свойствах слоя. Правой кнопкой мыши по слою, «изменить атрибуты слоя → смещение по Х» (Edit Layer Attributes → Offset X).

Изображение уже готово для просмотра, осталось только экспортировать его в необходимом формате через меню «Файл → Экспортировать как...», чтобы в дальнейшем просто выводить на экран и рассматривать через очки с красно-синими стеклами.

Ручной метод я описал, можно уже начинать потихоньку переводить свой фотобанк в трехмерный вид. Однако я обещал, что мы напишем плагин для GIMP, чтобы уменьшить пробег и количество кликов мышки.

Плагин


Будем писать на Python с использованием штатного модуля GIMP Python-Fu. Было бы заманчиво реализовать полный цикл преобразования — от формирования карты высот до генерации готового изображения. Однако версия питона в GIMP совсем не свежая и подцепить современные модули не удастся. Кроме того, мы будем ограничены вычислительными ресурсами локальной машины, а ведь где-то простаивают теслы гугла :)

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

Текст плагина привожу, объяснения будут ниже:

#!/usr/bin/env python

from gimpfu import *

def generate_anaglyph(img, drawable, gtype, displacement, shift, depthmap) :

    if pdb.gimp_image_get_precision(img) != PRECISION_DOUBLE_LINEAR:
        pdb.gimp_image_convert_precision(img, PRECISION_DOUBLE_LINEAR)

    layer = pdb.gimp_image_get_active_layer(img)

    layerL = pdb.gimp_layer_copy(layer, False)
    pdb.gimp_layer_set_name(layerL, "Left")

    layerR = pdb.gimp_layer_copy(layer, False)
    pdb.gimp_layer_set_name(layerR, "Right")

    img.add_layer(layerR, 0)
    img.add_layer(layerL, 0)

    displacement /= 2;
    pdb.plug_in_displace(img, layerL, -displacement, 0, True, False, depthmap, depthmap, 2)
    pdb.plug_in_displace(img, layerR, +displacement, 0, True, False, depthmap, depthmap, 2)

    # Color anaglyph
    if gtype == 0: 

        pdb.plug_in_colors_channel_mixer(img, layerL, 0,  1.0, 0.0, 0.0,
                                                          0.0, 0.0, 0.0, 
                                                          0.0, 0.0, 0.0 )

        pdb.plug_in_colors_channel_mixer(img, layerR, 0,  0.0, 0.0, 0.0,
                                                          0.0, 1.0, 0.0,
                                                          0.0, 0.0, 1.0 )

    # Optimized anaglyph
    elif gtype == 1:

        pdb.plug_in_colors_channel_mixer(img, layerL, 0,  0.0, 0.7, 0.3,
                                                          0.0, 0.0, 0.0, 
                                                          0.0, 0.0, 0.0 )

        pdb.plug_in_colors_channel_mixer(img, layerR, 0,  0.0, 0.0, 0.0,
                                                          0.0, 1.0, 0.0,
                                                          0.0, 0.0, 1.0 )

    pdb.gimp_layer_set_mode(layerL, LAYER_MODE_DIFFERENCE)

    shiftL = int(shift/2)
    pdb.gimp_layer_set_offsets(layerL, -shiftL, 0)
    pdb.gimp_layer_set_offsets(layerR, shift-shiftL, 0)

    return

register(
    "generate_anaglyph",
    "Generate anaglyph image",
    "Longer description of doing stuff",
    "Alef13",
    "Alef13",
    "2024",
    "<Image>/Filters/Test/Anaglyph image",
    "RGB, RGB*, GRAY*",
    [
        (PF_OPTION, "gtype", "Glasses type", 1, ("Color anaglyph", "Optimized anaglyph")),
        (PF_SLIDER, "displacement",  "Horizontal displacement", 10, (0, 50, 0.1)),
        (PF_SLIDER, "shift",  "Horizontal shift", 0, (-25, 25, 0.1)),
        (PF_LAYER, "depthmap", "Depth map layer", None),
    ],
    [],
    generate_anaglyph)

main()

Прежде всего мы регистрируем нашу функцию generate_anaglyph в качестве фильтра GIMP. В параметре указан путь, по которому она появится в меню «Фильтры». Также при регистрации мы указали параметры функции, диапазоны значений, значения по умолчанию, списки и т.д. GIMP самостоятельно сформирует форму для вызова плагина, в ней мы можем эти параметры корректировать. Как выглядит форма, увидим далее.

Рассмотрим саму функцию-фильтр. Первым делом устанавливаем точность вычислений в 16 бит с плавающей запятой — на всякий случай, если забыли сделать ранее.

Клонируем исходный слой два раза. Я решил исходное изображение не модифицировать и делать оба ракурса в отдельных слоях.

Двигаем (поворачиваем) каждый слой: левый — направо, правый — налево. Суммарное расстояние равно параметру displacement.

На основе параметра GType, который является индексом выпадающего списка, определяем, какого типа анаглиф нам нужен. Дело в том, что разложение на красный и сине-зеленый каналы дают не самую корректную цветовую картинку анаглифа. Кроме того, существуют очки в цветовых парах, отличных от красно-синей (подробности в английской «Википедии»). И здесь я предусматриваю возможность сделать изображение под конкретные очки. В отношении красно-синих очков, с целью хоть как-то сохранить цветопередачу, можно поиграть матрицами в соответствии с данным ресурсом. В плагине, для примера, реализованы так называемый optimized anaglyph и color anaglyph, остальное нетрудно сделать самостоятельно.

Устанавливаем режим наложения верхнего слоя и производим сдвиг слоев относительно друг друга.

Плагин готов, осталось научиться им пользоваться.

Заглянем в настройки GIMP, раздел «Правка → Параметры» (Edit → Preferences), далее «Каталоги → Плагины» (Folders → Plug-ins). Размещаем плагин в одну из этих папок. После перезагрузки GIMP наш плагин появится в меню «Фильтры → test → Anaglyph image».

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



В окошке с параметрами обязательно указываем в качестве карты глубин слой с картой и корректируем при необходимости остальные параметры. Жмем «Ок».

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

Вы, наверное, обратили внимание, что и в ручном методе, и в плагине в параметрах используются пиксели, поэтому для конкретного изображения приходится определять их подбором. Универсальных цифр я не скажу — они зависят от фокуса изображения, разрешения, размера картинки и т.д. Возможно, если чуток разобраться с математикой построения данного 3D, то можно сделать себе табличку с комфортными цифрами (сразу вспомнилась разметка на оптическом прицеле снайперской винтовки :). Ну а пока просто полагаемся на интуицию и опыт.

А теперь пояснение, почему я не пользуюсь этим плагином.
Если внимательно посмотреть на формат вызова плагина pdb.plug_in_displace и форму из меню «Фильтры → Проекция → Смещение», то становится видно, что форма намного богаче, чем вызов функции. Все параметры нас не интересуют, только «Sampler», имеющий по умолчанию значение «Cubic». Именно с этим значением работает функция pdb.plug_in_displace.

Для демонстрации влияния Sampler на изображение, я нарисовал квадрат 2х2 пикселя и сместил его на 0.5 пикселя с картой глубин из этого же квадрата во всех доступных режимах. Явно видно, что режим Cubic, а, значит, и наш плагин, вносит дополнительные артефакты в изображение. Получается, наиболее подходящие методы — это Linear и NoHalo.



Я честно пытался найти, каким образом можно изменить режим, но это реализовано в плагине через GEGL и передается в Python-Fu не в полном объеме. Возможное решение — подгрузка libgegl-0.4.dll/libgegl-0.4.so и вызов функций GEGL напрямую, но его я еще не реализовал, поэтому пока откатываюсь обратно на ручной метод.

Финал


Итак, от плагина мы отказались, но я предлагаю пойти дальше — отказаться еще и от самого GIMP. Причин несколько: россыпь очков различного вида на моем столе, Google Cardboard, другие средства просмотра. Генерировать контент для каждого устройства становится утомительно, а хранить несколько вариантов 3D-изображений нерационально. Поэтому я планирую держать их в виде исходной фотографии и карты глубин, а изображение под конкретное устройство формировать на этапе просмотра.

Также есть варианты размещения карты глубин в метаданных самого изображения. Небольшое исследование этих методов я уже провел, и, возможно, этого хватит на отдельную статью. А пока предлагаю вам освежить впечатления летнего отдыха, но уже в 3D-формате :)

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


  1. pavel_raskin
    13.09.2024 09:14

    Генерировать контент для каждого устройства становится утомительно, а хранить несколько вариантов 3D-изображений нерационально. Поэтому я планирую держать их в виде исходной фотографии и карты глубин, а изображение под конкретное устройство формировать на этапе просмотра.

    Есть очень простая, но функциональная программа StereoPhoto Maker, умеющая создавать из стереопары контент самых разных форматов, в том числе и анаглиф в различных вариациях.


  1. cryptoscam
    13.09.2024 09:14

    пятница 13-е, не иначе.

    как еще можно обьяснить, что в ленте хабра всплывают какие-то "хронологические артефакты" из глубокой древности?

    то чатЖПТ решил поведать "что такое майненк", когда уже даже школьники забыли что это такое, то вот это...

    я конечно и сам интересовался анаглифом, ровно 25 лет назад, когда в драйверах Нвидии нашлись настройки стерео-3Д и от этого был хоть какой-то практический толк, ввиду недоступности и дороговизны "взрослых решений". Но с тех пор уже выросло одно поколение людей и некоторые уже успели заделать новое поколение людей...

    - от анаглифа болит голова и тошнит

    - ну это только первые несколько часов.

    увидеть обьем - это конечно "современно и прогрессивно", но разве технологии не предоставили людям более доступных и адекватных способов?

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

    автор, пожалей свои глаза, купи себе дешевый 3Д-монитор на вторичке и постигни дзен. Иначе это выгладит как "в эпоху зажигалок зиппо ходить и гордо чиркать двумя булыжниками высекая искру"...