Аппаратный ЭЛТ-фильтр для картинок

Всю мою жизнь мне нравятся средства отображения информации — в виде электронно-лучевых трубок. В них есть определённый романтизм и шарм. Недаром их часто используют в иллюстрациях к фильмам антиутопиям.
В конце 2000-х набирали популярности социальные сети с фотографиями. И мне очень не хватало в те годы настоящего живого фильтра, который делает эффект телевизора. В один из прекрасных дней мне встретился проект, где из видоискателя от видеокамеры и фотоаппарата, сделали аппаратный фильтр для фотографий. Проект, когда я его встретил, уже не работал, а значит поле было не занято. Понял, вот это оно! И с тех пор идея поселилась в моей голове.
Как вы понимаете, этот проект — самый эпичный долгострой в моей жизни, раз я задумал его ещё в конце 2000-х. Где-то примерно в году 2015 он работал для друзей, и даже в 2016 году он ездил на Chaos Construction. Но это всё было не то, интерфейсы не те. И вот, наконец проект завершён, и может быть показан широкому зрителю.
А прежде, чем вы залезете под кат, можно сразу попробовать отфильтровать картинку — достаточно просто зайти в наш телеграмм бот.
Поехали, ниже много аппаратной жести.
Концепция
Идея достаточно проста, родилась она в то время, когда автоматическая обработка фотографий только набирала обороты, и всякие аппаратные решения могли бы быть достаточно забавными.
На чёрно-белый ЭЛТ-монитор выводится картинка, затем фотографируется камерой, а дальше уже сводятся цвета.
Вообще, те, кто давно следят за моим творчеством, могут сопоставить несколько статей по данной теме. Например, когда я искал монитор, то в качестве него хотел использовать видоискатель от старой видеокамеры. Так получилась статья «Мини ЭЛТ монитор». Но, к сожалению, экран от видеокамеры оказался слишком мал для таких целей. Поэтому, в дальнейшем, я приобрёл небольшой чёрно-белый охранный монитор, который был больше по размерам и оказался намного более удобным для моей задачи.

Охранный монитор «Topica TP-098»
В процессе экспериментов я подбирал различные камеры. Вообще, хотел использовать зеркальный фотоаппарат и даже пробовал его подключать. Это вы читали в статье «Старый фотик + bash = таймлапс». К сожалению, из-за малого ресурса, а также всяких аппаратных косяков — решил отказаться от этого варианта. Остановился на Raspberry Pi и малиновой камере.
От идеи до готового прототипа

Внешний вид первого работающего прототипа (2016 г.)
Сразу стало понятно, что для реализации всей задумки, мне понадобится готовый стенд. Он должен удовлетворять следующим параметрам:
- Быть закрытым, чтобы не было бликов на мониторе.
- Быть носимым, чтобы можно было перемещать по квартире.
- В нём должна обеспечиваться достаточная вентиляция, чтобы монитор не перегревался.
- Необходимо иметь возможность в широких пределах менять месторасположение камеры и жёстко её фиксировать.
▍ Фанерный ящик
Первое, что было изготовлено — это фанерный ящик. Тут никаких хитростей нет, разве что съёмная боковая и верхняя крышка. Для уменьшения бликов изнутри был окрашен чёрной тушью. Это было сделано по неопытности, идеальная краска, с хорошим светопоглащением, доступная в любом магазине — это акриловая газовая сажа.
Ящик не так прост, как кажется на первый взгляд. Как я уже сказал, необходимо обеспечить охлаждение, поэтому ящик располагается на ножках, которые просто вырезаны из той же фанеры, с набойкой из войлока. Это нужно, чтобы снизу было пространство для движения воздуха. В месте установки монитора — сделаны отверстия воздухозабора (там же, где они у самого монитора) и отверстия под его ножки. Для удобства подключения — сзади установлена обычная розетка, куда подключается монитор и блок питания одноплатника.

Отверстия воздухозабора и под ножки

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

Рельсу изготовил из соснового щита. Не самый лучший материал для таких целей, слишком мягкий. Но для разовой установки подойдёт.

Готовые рельсы из соснового щита
Когда стало понятно, что это крепление наиболее оптимально, я выпилил все необходимые детали на лобзиковом станке для установки болтов. Они нужны для регулировки высоты и наклона камеры. Прорезь нужна, чтобы можно было смещать камеру поперёк оси. Не знаю, как корректно именовать этот элемент, назову суппортом.


Готовый суппорт
В результате всех примерок и перестановок получился следующий готовый прототип.

На фотографии не видно, но суппорт прижимается к рельсам с помощью барашка. Это оказалось не очень удобно, и дальше я решил сделать фанерный диск с запрессованной гайкой.
▍ Окончательная сборка
Поскольку проект растянулся на много лет, и описать все события с ним, в рамках одной статьи, невозможно, основной акцент я дам только на последних изменениях. Аппаратные решения минувшего прошлого я просто не помню, всё это было утрачено в веках.
Мозгами всего устройства выступает Raspberry Pi 4, для камеры взял устаревший модуль V2. Поскольку всё работает в закрытом корпусе, для лучшего охлаждения одноплатника приобрёл дополнительный корпус-радиатор с вентиляторами.

Исходники в коробочках
После чего всё примеряю по месту и думаю об оптимальном расположении элементов.

Примерка
Самое сложное было сделать крепление камеры. Необходимо было обеспечить жёсткость крепления так, чтобы камера не болталась и не дрожала. Перепробовал несколько вариантов, самым оптимальным оказалось крепление на винтах М2, между гаек.

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

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

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

Готовый ящик

Ящик с закрытой крышкой
Настройка и разработка софта
Самая сложная часть — это не собрать ящик, хоть эта часть выглядит круто и понятна каждому. Самая сложная и скрытная часть — это настроить всё, и разработать ПО.
▍ Корректная установка камеры
Для меня это самая муторная задача, потому что надо в течение нескольких часов двигать суппорт и крутить винты. И да, я до сих пор недоволен идеальностью выставленной камеры.
В первый раз я выставлял камеру достаточно хитрым способом: скачал на DVD-диск специальные настроечные таблицы и выводил с помощью проигрывателя эти таблицы на монитор, а на телевизор выводил результат того, что снимает камера.

Настройка положения камеры в далёком 2016 году
Сейчас такими глупостями заниматься я не стал, потому что ни телевизора, ни плеера у меня уже нет. Поступил сильно проще, я открыл документацию на камеру для малины. И просто сделал трансляцию того, что она выводит в VLC. Главное поставить разрешение поменьше, а с помощью fbi (Linux framebuffer imageviewer) выводил на экран обычную настроечную таблицу для чёрно-белых телевизоров и старался, чтобы она хоть как-то совпадала с тем, что я должен увидеть.

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

Успешный вывод настроечной таблицы
▍ Позвольте, а откуда цвет?
Внимательный читатель заметил, что в своём проекте я использую чёрно-белый монитор. Можно даже потрудиться, найти на него документацию и убедиться, что цвет он выводить не умеет. Но как же можно получить цветное изображение?
Всё просто, берётся цветная картинка, например:

И разделяется на три канала по цветам (делается это программно).

Три отдельных картинки с каждым цветом
Далее, каждый цвет отдельно выводится на монитор и фотографируется:

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

Изображение, полученное с монитора
Виден засвет тёмной области, это именно та проблема, которая возникает в абсолютно тёмном ящике. Поэтому и нужен контр-свет.
▍ Разработка софта. Серверная часть
В первоначальном прототипе у меня был вебсайт, где всё работало через CGI. Но это было не самый удобный вариант и по многим параметрам он мне не нравиося. Поэтому было принято волевое решение шагать в ногу со временем и сделать telegram-бота. Сам бот — это для меня запредельная магия (шучу), я потыкал в него палочкой и не смог реализовать. Поэтому слёзно просил помочь мне его сделать man_of_letters. О подобном боте у нас есть отличная статья «Проект — электрический помощник для редакции». С небольшими изменениями из него получился бот для получения и отдачи фотографий пользователю. Более подробно рассказать не смогу, потому что сильно код бота не ковырял.
API работы устроено достаточно просто:
- Бот принимает картинку от пользователя, шинкует её на три канала цвета RGB и сохраняет картинки в png с соответствующими именами.
- После успешной шинковки создаёт просто пустой файл, с именем трёх файлов. Создание этого файла и имя файла является ключём для того, чтобы начать обработку.
- После того как моё устройство заберёт эти файлы, перемолет их, а далее складывает обратно — все три обработанных файла в другую папку. Бот их сводит и отдаёт клиенту.
Итого, у меня три папки, которые и являются API для взаимодействия с ботом. Для работы с ними решили использовать протокол ssh, потому что это удобно, надёжно и просто. Как же я ошибался…
▍ Клиентская часть
Главной проблемой работы по ssh — это держать постоянное подключение. И оказалось, что при длительном соединении постоянно «лопалась труба» (ошибка broken pipe).

Лопнувшие трубы ssh соединения сильно портили малину, и порой даже бились данные. Решила эти проблемы следующая статья. Но, изначальный сервер у нас был на Debian, любезно предоставленный компанией RuVDS, а там этот фокус вообще чуть не погасил весь ssh. Поэтому в срочном порядке пришлось переехать на сервер с Ubuntu 20.04. Для наших задач хватит самой простецкой конфигурации.
Далее там создаём файл:
sudo vim /etc/ssh/sshd_config.d/alive.conf
И добавляем туда следующие строки:
ClientAliveInterval 30
ClientAliveCountMax 30
TCPKeepAlive no
И не забываем перезапустить sshd-демон:
sudo systemctl restart ssh
Теперь возникает другой вопрос: как на стороне клиента aka Raspberry Pi мониторить создание файла на удалённом сервере?
Изначально для этих целей я использовал приложение inotifywait и отслеживал создание новых файлов, вот такой совершенно страшной конструкцией и далее в теле while уже делал все свои грязные дела:
ssh teleuser@$server inotifywait -e create /****/workaround --format "%f" -q -m| while read file; do
...
Но длительные тесты показали, что такой подход неэффективен: теряется часть файлов, и в случае потери соединения, мы уже не будем знать, что там произошло. Поэтому сделал на обычном ls, сортируя по дате добавления в обратном порядке, и по очереди пробегаясь по каждому файлу. И всё это запихал в большой цикл while. Не элегантно, но работает.
ssh teleuser@$server inotifywait -e create /****/workaround --format "%f" -q -m| while read file; do
Вывод на экран делаю в консольном режиме, используя стандартную программу вывода во фреймбуффер:
sudo fbi -T 2 --nocomments --noverbose -a /***/hipcrt/${file%.*}$num.png
Самое сложное было разобраться с тем, как это фотографировать. Когда-то, давным-давно, я использовал приложение raspistill, но с тех пор много воды утекло и появилось другое приложение.
Необходимо было подобрать оптимальное разрешение, чтобы было всё чётко видно, и не занимало много места, и настроить выдержку с таким параметром, чтобы картинка успела отрисоваться вся, и при этом не было пересвета и баланс белого.

Пример неудачно подобранной выдержки, когда идут разноцветные полосы из-за того, что кадр не успел отрисоваться весь.
В результате после небольшого НИР получилась следующая команда:
libcamera-still -n --width 800 --height 600 -o /****/hipcrt/result/${file%.*}$num.jpg -e jpg --shutter 125000 -t 500 --awb fluorescent
Итоговый скрипт получился простым и лаконичным. В этом скрипте происходит удаление всех временных файлов, то есть пользовательские файлы на сервере не хранятся, для экономии места, и мы их увидеть, увы, не можем.
#!/bin/bash
server=***.***.***.***
while true
do
sleep 0.1
ssh teleuser@$server ls -1tr /*****/workaround | while read file; do
scp teleuser@$server:/*****/pic_from_user_split/${file%.*}* /*****/hipcrt/
ssh teleuser@$server rm -f /*****/workaround/$file
ssh teleuser@$server rm -f /*****/pic_from_user/${file%.*}*
ssh teleuser@$server rm -f /*****/pic_from_user_split/${file%.*}*
for num in "_r" "_b" "_g"
do
sudo fbi -T 2 --nocomments --noverbose -a /*****/hipcrt/${file%.*}$num.png
sleep 0.5
sudo libcamera-still -n --width 800 --height 600 -o /*****/hipcrt/result/${file%.*}$num.jpg -e jpg --shutter 125000 -t 500 --awb fluorescent
done
sudo killall fbi
scp /*****/hipcrt/result/${file%.*}* teleuser@$server:/*****/pic_from_crt/
rm -f /*****/hipcrt/*.png /*****/hipcrt/result/*
done
done
exit 0
На самом деле, я как-то так легко и просто всё расписал, но в реальности — вся эта куча экспериментов и опытов вылилась в недели работы, потому что было неясно, где и что отваливается.
Так, и где же я могу всё это попробовать?

Рекурсивная фотография аппаратного ЭЛТ-фильтра, обработанная на аппаратном ЭЛТ-фильтре
Хочется уже попробовать, правда? Такая краткая инструкция для начинающих:
- Бот обитает по следующему адресу.
- Заходим в него и жмём «запустить».
- После этого у вас появится меню.
- Выбираем «Обработать новое изображение» и скармливаем любой графический файл со сжатием.
После этого ваше изображение добавляется в очередь. Обработка каждого изображения занимает 15 секунд, поэтому из-за хабраэффекта, пожалуйста, соблюдайте терпение, все получат свои картинки рано или поздно. С телефона всё выглядит вот так.


Чтобы канал не забил один крепкий кликер фоток — там стоит антиспам, ограничивающий количество фотографий от одного человека. Так что выбирайте фотографии лучше.
Заключение

В моей жизни — этот проект самый эпичный долгострой, который всё же был реализован и доведён до конца. Проект меня многому научил. Когда я делал сайт, то впервые вообще столкнулся с js, php и вообще web-программированием, CGI вообще был для меня пустым звуком, а ещё разработка своих скриптов… Всё это стало для меня замечательным опытом и дало весьма неплохой навык, который пригодился в моей дальнейшей работе.
Да, сам проект может быть глупым и абсурдным, но лично меня он научил очень многому, и я благодарен, что он состоялся.
Главное я благодарен всем моим друзьям, которые мне помогали с этим проектом. Не буду перечислять всех по именам, вы все молодцы. Громадное спасибо man_of_letters за помощь с сайтом в начале пути, и за помощь с ботом в завершении проекта. Компании RuVDS за помощь в подготовке этого проекта.
А главное, вам мои дорогие читатели, за то, что будете его использовать и понимать что это просто прикольно!
HIPCRT_BOT
И напоследок, вот видео, которое изготовлено на этом аппарате. Понадобился день, чтобы обработать все кадры этого короткого видеоролика.
З.Ы. Если всё будет хорошо, постараюсь чтобы аппаратная часть проработала не меньше месяца!
RUVDS | Community в telegram и уютный чат