Главная » Хабрахабр » Три картинки за одно воскресенье: о создании бюджетных стереоизображений на пальцах (стереограмма, анаглиф, стереоскоп)

Три картинки за одно воскресенье: о создании бюджетных стереоизображений на пальцах (стереограмма, анаглиф, стереоскоп)

Пришли очередные выходные, надо написать пару десятков строк кода и нарисовать картинку, да лучше не одну. Итак, на прошлых и позапрошлых выходных я показал, как делать трассировку лучей и даже взрывать всякое. Это многих удивляет, но комьпютерная графика — очень простая вещь, пары сотен строк голого C++ вполне хватает на создание интересных картинок.

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

Для тех, кто не застал, эта игра позволяла делать 3Д рендер и в анаглиф, и в стереограммы в основных настройках, просто доступных в меню! Мозг это взрывало просто конкретно. Безумие разработчиков Magic Carpet не даёт мне покоя.

Итак, приступим. Для начала, благодаря чему вообще наш зрительный аппарат позволяет воспринимать глубину? Есть такое умное слово «параллакс». Если на пальцах, то давайте сфокусируем взгляд на экране. Всё, что находится в плоскости экрана, для нашего мозга существует в единичном экземпляре. А вот если вдруг муха пролетит перед экраном, то (если мы не меняем взгляда!) наш мозг её зарегистрирует в двух экземплярах. А заодно и паук на стене за экраном тоже раздваивается, причём направление раздвоения зависит от того, находится объект перед точкой фокуса или позади:

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

Давайте преставим, что наш экран — это окно в виртуальный мир 🙂

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

Допустим, положение камеры мы можем сдвинуть на межглазное расстояние, что же ещё? Ну, положим, направление взляда не меняется, это вектор (0,0,-1). А наш рейтрейсер умеет рендерить только симметричный конус взгляда: Есть одна маленькая тонкость: конус взгляда через наше «окно» несимметричен.

Читить 🙂
На самом деле, мы можем отрендерить картинки шире, нежели нам нужно, и просто обрезать лишнее: Что же делать?

С общим механизмом рендеринга должно быть понятно, теперь самое время задаться вопросом доставки изображения до нашего мозга. Один из самых простых вариантов это красно-синие очки:

Получится вот такая картинка: Мы просто сделаем два пре-рендера не цветными, а чёрно-белыми, левую картинку запишем в красный канал, а правую — в синий.

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

У них много недостатков, например, плохая цветопередача (кстати, попробуйте в зелёный канал финальной картинки записать зелёный канал правого глаза). Анаглифные рендеры — один из самых древних способов просмотра (компьютерных!) стереокартинок. Одна польза — такие очки легко сделать из подручных материалов.

С массовым распространением смартфонов мы вспомнили, что такое стереоскопы (которые, на секундочку, были изобретены в 19м веке)! Несколько лет назад гугл предложил использовать две копеечные линзы (к сожалению, на коленке не делаются), немного картона (валяется повсюду) и смартфон (лежит в кармане) для получения вполне сносных очков виртуальной реальности:

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

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

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

Важно то, что стереограмма построенная для одного способа, должна этим же способом и просматриваться, иначе получается инвертированная карта глубин (см. Разным людям эти приёмы даются по-разному, я, например, совсем не умею глаза сдвигать, зато с лёгкостью развожу. Проблема такого способа просмотра стерео в том, что очень сложно сильно сдвинуть глаза относительно нормального состояния, поэтому приходится довольствоваться маленькими картинками. отрицательный и положительный параллакс). Давайте полностью пожертвуем цветом, и захотим получить только восприятие глубины. А что делать, если хочется больших? Забегая вперёд, вот картинка, которую мы получим в конце этой части:

Если вы не привыкли к стереограммам, попробуйте разные условия: картинка на полный экран, маленькая картинка, яркий свет, темнота. Эта стереогрмма создана для «разведения» глаз (wall-eyed stereogram). Проще всего фокусироваться на левой верхней части картинки, т.к. Задача развести глаза так, чтобы две соседние вретикальные полоски совпали. Мне, например, мешает окружение хабра, я открываю картинку на полный экран. она плоская. Не забудьте с неё убрать мышку!

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

в их статье «Displaying 3D Images: Algorithms for Single Image Random Dot Stereograms». Эта стереограмма нарисована по методу, предложенному четверть века назад Thimbleby и др.

Отправная точка

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

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

Основной принцип

Пусть у нас глаза находятся на расстоянии d от экрана. Поместим (воображаемую) дальнюю плоскость (z=0) на том же расстоянии позади экрана. Выберем постоянную μ, которая определит положение ближней плоскости (z=0): она будет на расстоянии μd от дальней. Я в своём коде выбрал μ=1/3. Итого, весь наш мир живёт на расстоянии от d-μd до d за экраном. Пусть у нас определено расстояние e между глазами (в пикселях, в моём коде я выбрал 400 пикселей).

Как найти расстояние между этими пикселями? Если мы смотрим на точку нашего объекта, отмеченную на схеме красным, то два пикселя, отмеченных зелёным, должны иметь одинаковый цвет в стереограмме. Если текущая проецируемая точка имеет глубину z, то отношение параллакса к расстоянию между глазами равно отношениям соответствующих глубин: p/e = (d-dμz)/(2d-dμz). Очень просто. То есть, p/e = (1-μz)/(2-μz), а это означает, что параллакс равняется p=e*(1-μz)/(2-μz) пикселей. Кстати, обратите внимание, что d сокращается и более нигде не участвует!

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

Подготавливаем исходную картинку

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

Эти волны сделаны с расстоянием в 200 пикселей, это (при выбранных μ=1/3 и e=400) максимальное значение параллакса в нашем мире, оно же дальняя плоскость. Обратите внимание, что в целом цвета просто случайные, за исключением того, что я в красном канале положил rand()*sin, чтобы обеспечить периодические волны. Эти волны необязательны, но они облегчат нужную фокусировку зрения.

Рендерим стереограмму

Собственно, полный код, относящийся к стереограмме, выглядит вот так:

int parallax(const float z) { const float eye_separation = 400.; // interpupillary distance in pixels const float mu = .33; // if the far plane is a distance D behind the screen, then the near plane is a distance mu*D in front of the far plane return static_cast<int>(eye_separation*((1.-z*mu)/(2.-z*mu))+.5);
} size_t uf_find(std::vector<size_t> &same, size_t x) { return same[x]==x ? x : uf_find(same, same[x]);
} void uf_union(std::vector<size_t> &same, size_t x, size_t y) { if ((x=uf_find(same, x)) != (y=uf_find(same, y))) same[x] = y;
} int main() {
[...] for (size_t j=0; j<height; j++) for (size_t i=0; i<width; i++) { // resolve the constraints size_t root = uf_find(same, i); for (size_t c=0; c<3; c++) framebuffer[(i+j*width)*3+c] = framebuffer[(root+j*width)*3+c]; } }
[...]

Функция int parallax(const float z) даёт расстояние между пикселями одинакового цвета для текущего значения глубины. Если что, то коммит брать тут. Поэтому основной цикл просто пробегает все строчки; для каждой из них мы начинаем с полного неограниченного набора пикселей, на который затем будем накладывать попарные ограничения равенства, и в итоге у нас окажется некое количество кластеров (несвязных) пикселей одного цвета. Мы рендерим стереограмму построчно, так как строчки независимы между собой (у нас нет вертикального параллакса). Например, пиксель с индеком left и пиксель с индексом right должны в итоге оказаться одинаковыми.

Самый простой ответ — union–find data structure. Как хранить этот набор ограничений? Основная мысль в том, что для каждого кластера у нас окажется некий «ответственный» за него, он же коренной, пиксель, его мы оставим того цвета, какого он был в исходной картинке, а все остальные пиксели кластера перекрасим: Её я описывать не буду, это и так только три строчки кода, можно прочитать в википедии.

for (size_t i=0; i<width; i++) { // resolve the constraints size_t root = uf_find(same, i); for (size_t c=0; c<3; c++) framebuffer[(i+j*width)*3+c] = framebuffer[(root+j*width)*3+c]; }

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

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


Оставить комментарий

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

*

x

Ещё Hi-Tech Интересное!

Магнитная лента в 21 веке — как её используют

Магнитная лента не исчезла насовсем и до сих пор находит применение в дата-центрах. Фото — Don DeBold — CC BY Высокая ёмкость Это — одно из главных преимуществ магнитных лент. Когда в середине девяностых на прилавках магазинов появились коммерческие винчестеры ...

Обман автоматизированных камер наблюдения

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