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

Коротко об идее

Основными активными элементами являются веб-камера, OpenCV, компьютер с запущенным генератором изображений Stable Diffusion и монитор, скрытый за двусторонним (зеркальным с одной стороны) стеклом.

На управляющим экраном компьютере запущен python-скрипт, который контролирует кадры с веб-камеры и ищет лица. Когда лицо обнаружено (это значит, что кто‑то посмотрел в зеркало), кадр передаётся Stable Diffusion с использованием режима 'img2img' и соответствующим промптом (текстовой подсказкой, задающей тон позы, костюма и общего фона). После генерации реальное отражение зрителя заменяется его «монстр‑версией».

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

Софт и электроника

Вы можете посмотреть мой код на GitHub. Он не слишком аккуратный, улучшения приветствуются. Есть две части: python-скрипт для компьютера и схема для ардуинки, запускающей дополнительный световой эффект.

Сборка создана под AUTOMATIC1111 Stable Diffusion, которую нужно загрузить отдельно. Дальше по тексту я для простоты буду называть её SD. Пожалуй, это самая ценная часть сборки, требующая несколько гигабайт дискового пространства и умеренно мощной видеокарты, способной работать с ним (NVidia, AMD, Intel; ~4 ГБ+ VRAM) и на разумной скорости. Озадачьтесь этим вопросом заранее.

Что нам ещё понадобится:

  • ЖК-экран (монитор телевизора/компьютера)

  • Веб-камера

  • Компьютер с более-менее мощной видеокартой

  • 2-стороннее зеркало

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

Если вы хотя бы немного знакомы с Python, возьмите код по ссылкам на GitHub (если вы новичок, попробуйте воспользоваться опцией Code → Download ZIP), а инструкции по настройке (зависимости и т. д.) смотрите в README для каждого из них. Опять же, я настоятельно рекомендую сначала заставить работать Stable Diffusion Web UI и сгенерировать несколько тестовых изображений. Убедитесь, что у вас установлен последний Python 3. Хотя обычно с этим интерфейсом работают через браузер, мы будем использовать встроенный API для управления им из другого скрипта. Обязательно добавьте параметры ‑api и ‑listen в конфигурацию командной строки (см. README).

Скрипт hauntedmirror.py занимается отображением картинки и может (это не обязательно) работать на том же компьютере, что и SD. В отличие от Stable Diffusion, эта часть программы легковесна и может быть запущена на любом оборудовании, которое у вас есть, а взаимодействовать с бэкендом SD по Wi‑Fi. По умолчанию вся обработка изображений происходит локально, никакие фотографии чужих детей не отправляются на случайные сторонние серверы. Для распознавания лиц и отображения изображений используется OpenCV, а собственно для распознавания — каскадный классификатор Хаара. Это не совсем современное решение, но лёгкое и позволяющее справиться с поставленной задачей. Не обязательно, чтобы все было идеально: распознавание лиц нужно только для запуска захвата кадра и не используется при генерации замещающего изображения.

Перед запуском скрипта hauntedmirror.py в самом начале необходимо настроить несколько параметров (см. README), включая IP‑адрес и порт экземпляра Stable Diffusion (если они отличаются), COM‑порт для связи с дополнительным эффектом освещения, некоторые параметры размера изображения и, конечно, сами параметры SD.

Параметры по умолчанию являются хорошей отправной точкой, но там есть одна вещь, которую вы определённо захотите подправить: параметр 'steps', который, по сути, является компромиссом между качеством изображения и временем обработки и зависит от вашего GPU. Чем видеокарта мощнее, тем быстрее выдаются результаты. В моих тестах значение 8 — это нижний предел для получения хороших результатов, а время генерации изображения сократилось до ~3-5 секунд на видеокарте NVIDIA GeForce GTX 1080. Такие приёмы, как эффект загадочного освещения или приклеенная к зеркалу бумажка с текстом, могут помочь компенсировать задержку, но вы же понимаете, что люди не будут долго ждать, пока что-то произойдёт.

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

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

Итак, давайте перейдём к настройкам Diffusion. Для получения достойных результатов полезно знать, как работает SD, но не обязательно. Этот эффект использует режим 'img2img', в котором к выбранному исходному изображению добавляется контролируемое количество шума. Количество исходного шума может быть настроено с помощью параметра denoising_strength в диапазоне от 0.0 (немодифицированное исходное изображение) до 1.0 (чистый случайный шум). В моих экспериментах значение около 0,45 оказалось оптимальным, при котором хорошо добавлялись жуткие элементы, но сохранялось общее исходное изображение. По крайней мере, для меня большая часть привлекательности заключалась в том, что жертвы видели в зеркале именно себя, а не случайное пугающее изображение.

Поиск нужного баланса в вашей собственной установке может потребовать некоторых экспериментов. Аналогично, вес текстовой подсказки настраивается (cfg_scale) и может варьироваться от 0 (игнорирование) до 30 (гиперфокус). Для текстовой подсказки хорошо подходят значения в диапазоне 5–15, высокие значения приводят к карикатурному виду. Что касается собственно текстовой подсказки, то чрезвычайно общие подсказки типа «призрачный», «жуткий», «страшный» и т. п., похоже, хорошо работают на всех входных изображениях и в основном затрагивают лица, в то время как более конкретные подсказки дают более неоднозначные результаты. К сожалению, подсказки типа «лицо покрыто паутиной» не сработали, а ведь это было бы просто замечательно.

Менять модели на лету не советую, на это уходит слишком много времени. Обратите внимание, при запуске SD будет автоматически перезагружаться последняя использованная модель. И если вы уже использовали SD и баловались с какими‑нибудь, к примеру, NSFW‑моделями, то рекомендую дважды или трижды убедиться в том, что загружается безопасная модель, чтобы не шокировать незадачливых любителей посмотреться в зеркало.

Для своей системы я пробовал модели dreamlikeDiffusion и Dreamshaper, и обе дали довольно хорошие результаты. Последняя больше изменяет фон при одинаковом значении denoising_strength. Как ни странно, обе эти модели оказались лучше, чем специально разработанная для данной темы модель creepy‑diffusion, которая давала небольшой набор однотипных монстро‑лиц.

Пример работы Dreamshaper
Пример работы Dreamshaper

Как создать световые эффекты (это необязательно, но рекомендуется):

  • Arduino-совместимая (или любая другая) плата с последовательным портом

  • Макетная плата

  • Белые светодиодные модули 5V. Я взял модули Star

  • Алюминиевый L-образный кронштейн (теплораспределитель для светодиодов большей мощности)

  • Мячики для настольного тенниса (в качестве рассеивателей света), если вам хочется изменить внешний вид светодиодной подсветки

  • NPN-транзистор средней мощности (чтобы мог управлять светодиодами)

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

Для простого крепления осветительных приборов на болтах, которые также служат радиатором, я покрасил алюминиевый L‑образный кронштейн в чёрный цвет и приклеил к нему светодиодные Star-модули. Заодно сделал выемку посередине для веб‑камеры.

Ещё мне очень хотелось, чтобы в зеркале было каноническое освещение с маленькими круглыми лампочками, а в продаже таких низковольтных светодиодов не нашлось, поэтому я купил несколько модулей Star мощностью 3 Вт, обрезал несколько шариков для пинг-понга и приклеил по одному на каждый светодиод.

Для проекта я использовал запасную Arduino-совместимую плату с источником питания 5 В. Я использовал «тёплые белые» 3-ваттные светодиоды, которые позиционируются как "5V" и включают соответствующий токоограничивающий резистор для работы от 5 В прямо на модуле. Схема драйвера, которую я использовал, представляет собой один силовой транзистор NPN с некритичным базовым резистором (300 ~ 1 кОм). Схема показана ниже, серыми рамками обозначены светодиодные модули с внутренними компонентами.

Эти светодиоды были доставлены из знакомых многим зарубежных источников, где в описаниях часто указываются «оптимистичные» значения мощности. Но благодаря транзистору (на котором падает напряжение ~0,7 В) и мощному алюминиевому кронштейну, на который я их приклеил, я не столкнулся с чрезмерным нагревом или другими проблемами, и они по‑прежнему светили достаточно ярко.

Скрипт Arduino очень прост, он принимает по последовательному порту один из трёх ASCII-символов для установки режима освещения (Lit, Flicker или Dark) и модулирует яркость светодиодов с помощью ШИМ‑вывода. В режиме Flicker яркость линейно затухает между произвольно выбранными уровнями. Это создаёт ощущение плавного «мерцания лампочек» из старых фильмов ужасов, когда кто‑то за кадром буквально щелкал выключателями, а мощным лампам накаливания той эпохи требовалось некоторое время, чтобы отреагировать.

Зеркало и рама:

  • Рама - купить или сделать самому

  • Двустороннее зеркало (или акриловый лист и зеркальная плёнка)

  • Крепёж для фиксации зеркала

Мне нужна была рама, которая выглядела бы старой и потрёпанной. В магазинах нашёл несколько вариантов, но они были дороговаты для разового проекта (> 80 долл.) и требовали доработки под мой монитор, поэтому в итоге я просто купил в хозяйственном магазине молдинг с антикварным узором, пару баллончиков краски, посмотрел несколько видеороликов по созданию «старинных рам» и начал творить. Честно говоря, денег это, скорее всего, не сэкономило, зато я получил больше удовольствия.

Рама изготовлена с помощью ручной пилы и дешёвого торцовочного станка, что, вероятно, заметно. Края не совсем совпадают, но и ладно. Чтобы сделать её более похожей на старинную бронзу, я нанёс на белый виниловый молдинг слой черной аэрозольной краски, а затем сразу же начал оттирать её бумажным полотенцем, смоченным в спирте. Результат казался именно таким, как хотелось. Но краска высохла очень быстро, и вскоре этот метод стал неэффективным, поэтому я слегка отшлифовал поверхность мелкозернистой наждачной бумагой (320 грит), чтобы осветлить края и высокие участки узора. Рама уже выглядела ветхой и можно было бы остановиться. Но я увлёкся процессом и не стал этого делать.

Как создавалось жуткое зеркало
Белый виниловый молдинг, покрытый черной аэрозольной краской, частично стертый для контраста.
Белый виниловый молдинг, покрытый черной аэрозольной краской, частично стертый для контраста.
После покрытия бронзовой аэрозольной краской
После покрытия бронзовой аэрозольной краской
Так собирал уголки
Так собирал уголки
Добавляем свет
Добавляем свет
Та самая чёрная бумага по краям
Та самая чёрная бумага по краям
Готово!
Готово!
Тестовые запуски с участием фокус-группы
Тестовые запуски с участием фокус-группы
Монтаж на ЖК-экран и проверка освещения
Монтаж на ЖК-экран и проверка освещения
Свет погас и вы видите...себя?
Свет погас и вы видите...себя?

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

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

Для сборки рамы и крепления «стекла» я использовал несколько плоских прямоугольных скоб. Просто скрепил их вместе и прижал стекло к раме. Поскольку рама получилась большой и тяжёлой, для надёжности добавил по внешним краям набор L‑образных угловых скоб. А для крепления экрана использовал широкий L‑образный деревянный кронштейн, который свисает над верхним краем экрана. Монитор, который я использую, неплохой, но имеет какую‑то странную систему крепления Dell, а не стандартные крепления VESA.

Кстати, вместо моего варианта можно использовать облачные GPU и SD-модели. Здесь есть простор для творчества, оставлю всё на усмотрение читателя. ︎А ещё рекомендую подумать о том, как реализовать функцию быстрого редактирования промптов.

Вот и всё, ребята! Спасибо за внимание.


Что ещё интересного есть в блоге Cloud4Y

→ Симпсоны-ТВ: руководство по сборке

→ NAS за шапку сухарей

→ Взлом Hyundai Tucson, часть 1часть 2

→ Собираем машину для стринг-арта

→ 50 самых интересных клавиатур из частной коллекции

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


  1. savostin
    19.10.2023 19:31
    +1

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