Монохромные дисплеи обычно используются для отображения какой-нибудь примитивной графики: текста, графиков, диаграмм и пр. Если вывести на тот же OLED-дисплей с разрешением 128×64 пикселя фотографию, картинка будет не ахти какой, но всё же вполне интерпретируемой.
А что, если вообще транслировать на такой индикатор потоковое изображение с камеры? В этой статье проведем именно такой эксперимент. Практической пользы он не несет, но выглядеть такое устройство будет весьма необычно.
Список компонентов
Для эксперимента нам понадобится Raspberry Pi 4, разумеется, с блоком питания и SD-картой памяти. Модуль OLED-дисплея и провода.
Подготовка
Прежде чем разбираться с трансляцией, подключим OLED дисплей к микрокомпьютеру по схеме из одноимённого урока Raspberry Pi: работа с OLED дисплеем. Там же смотрим какие библиотеки нужно установить для работы дисплея.
Для организации потока используем подход, который часто применяется для трансляции видео с веб-камеры в интернет. Подход заключается в том, чтобы постоянно записывать изображение с веб-камеры во временный файл, который затем используется для просмотра в браузере. Мы же будем считывать содержимое этого файла и выводить его на OLED-дисплей.
И начнём мы с того, что должным образом подготовим место для сохраняемого изображения. Можно этот шаг пропустить, но важно помнить, что при хранении этого файла на SD-карте мы получим сразу две проблемы:
- при постоянной перезаписи файла (даже 1 раз в секунду) мы будем тратить ресурс SD-карты;
- скорость записи/чтения на флеш-память не такая высокая, как в случае памяти ОЗУ.
Собственно, решение очевидно — будем хранить файл в оперативной памяти. Обозначим в настройках накопителей папку tmp, как хранимую в ОЗУ. Для этого откроем в редакторе файл настроек:
sudo nano /etc/fstab
и добавим в него строку:
tmpfs /tmp tmpfs defaults,noatime,nosuid 0 0
Должно получиться так:
Сохраняем файл Ctrl+O, выходим Ctrl+X и перезагружаем Raspberry Pi.
Утилита для чтения веб-камеры
Чем считывать картинку с веб-камеры? На самом деле решений несколько:
- утилита fswebcam;
- утилита mjpg-stream;
- пакет motion;
- opencv;
- для камеры с CSI интерфейсом подойдет raspistill.
Используем, на мой взгляд, самую простую fswebcam. Устанавливаем её через консоль:
sudo apt-get install fswebcam
Проверим работу утилиты командой:
fswebcam /tmp/pic.jpg
После завершения работы утилиты, в папке tmp появится изображение с камеры. Следующий шаг — сохранение изображение в этот файл автоматически, каждую секунду.
Для удобства, а может и для будущей демонизации, создаём shell-скрипт для запуска fswebcam с нужными параметрами:
cd /home/pi
touch webcam.sh
открываем созданный файл в редакторе nano:
nano webcam.sh
и пишем такой код:
#!/bin/bash fswebcam -q -r 160x120 --loop 1 --no-banner --exec 'mv /tmp/stream/pic.jpg /tmp/last.jpg' /tmp/pic.jpg
Сохраняем Ctrl+O, выходим Ctrl+X.
Поясню параметры.
- q — не отображать служебную информацию в процессе;
- r — разрешение изображения; у OLED-дисплея разрешение 128×64, так что можно поставить ближайшее, поддерживаемое камерой;
- loop 1 — считывать изображение каждую секунду;
- exec ‘mv /tmp/stream/pic.jpg /tmp/last.jpg’ — перед записью нового файла, прежний копировать в файл last.jpg; это поможет избавиться от попыток OLED-скрипта открыть файл, в который fswebcam еще пишет данные;
- последний параметр — целевой файл.
Теперь сделаем shell-скрипт исполняемым:
chmod +x webcam.sh
И запустим:
webcam.sh
Проверяем папку tmp. Теперь там есть два файла: last.jpg и pic.jpg, которые постоянно обновляются.
Python-скрипт для вывода на OLED-дисплей
Чтобы не нарушать процесс записи изображения с камеры в файл, откроем новую консоль. Создадим файл с расширением .py в домашней папке и откроем его в редакторе:
touch oledcam.py
nano oledcam.py
Напишем программу, которая будет в бесконечном цикле считывать изображение из файла /tmp/pic.jpg и выводить его на OLED.
import board
import adafruit_ssd1306
import time
from PIL import Image
width = 128
height = 64
# инициализация дисплея
i2c = board.I2C()
oled = adafruit_ssd1306.SSD1306_I2C(width, height, i2c, addr=0x3D)
time.sleep(1)
while True:
oled.fill(0) # чистим экран
image = Image.open('/tmp/stream/last.jpg') # открываем файл
image_r = image.resize((width,height), Image.BICUBIC) # масштабируем изображение в размер дисплея
image_bw = image_r.convert("1") # преобразуем в монохромную матрицу точек
for x in range(width):
for y in range(height):
oled.pixel(x,y,bool(int(image_bw.getpixel((x,y))))) # точка за точкой выводим в буфер дисплея
oled.show() # выводим изображение на дисплей
time.sleep(1) # пауза между кадрам 1 секунда
Сохраняем файл и запускаем его:
python3 oledcam.py
Готово. Теперь у нас параллельно запущены две программы: одна сохраняет кадры в файл, а вторая считывает их и выводит на дисплей.
Дальнейшие размышления
К сожалению, мне не удалось добиться от fswebcam скорости большей, чем 1 кадр в 2-3 секунды. Если у кого то получится, пишите в комментариях.
Если вы используете родную камеру с CSI интерфейсом, можно попробовать сохранять изображения с помощью утилиты raspistill. Строка запуска утилиты может быть такой:
raspistill --nopreview -w 160 -h 120 -q 75 -o /tmp/pic.jpg -tl 500 -t 9999999
Здесь параметр tl — задает период получения снимка в миллисекундах, t — общее время работы скрипта, q — степень сжатия jpeg.
Строку:
oled.fill(0) # чистим экран
оставьте перед WHILE, чтобы очистка происходила только при первом запуске программы, а не при каждом цикле прорисовки изображения.
Тогда удастся выводить больше кадров (если убрать строку time.sleep(1))
Скрипт по отрисовке кадра достаточно быстрый, даже с учетом очистки (хотя fill лишнее). Пауза 1сек стоит уже исходя из скорости формирования файлов, они всё равно быстрее не генерируются. То есть узкое место, всё-таки, утилита fswebcam.