Raspberry Pi: веб-камера и OLED-дисплей
Монохромные дисплеи обычно используются для отображения какой-нибудь примитивной графики: текста, графиков, диаграмм и пр. Если вывести на тот же OLED-дисплей с разрешением 128×64 пикселя фотографию, картинка будет не ахти какой, но всё же вполне интерпретируемой.
А что, если вообще транслировать на такой индикатор потоковое изображение с камеры? В этой статье проведём именно такой эксперимент. Практической пользы он не несёт, но выглядеть такое устройство будет весьма необычно.
Список компонентов
Для эксперимента нам понадобится Raspberry Pi 4, разумеется, с блоком питания и SD-картой памяти. Модуль OLED-дисплея и провода.
Примечание
Блок питания с USB-разъёмом и Type-C кабелем, 5 В, 3 А
Карта памяти micro-SD 16 ГБ
Подготовка
Прежде чем разбираться с трансляцией, подключим OLED-дисплей к микрокомпьютеру по схеме из соответствующего урока по работе с OLED-дисплеем на Raspberry Pi. Там же смотрим, какие библиотеки нужно установить для работы дисплея.
Для организации потока используем подход, который часто применяется для трансляции видео с веб-камеры в интернет. Подход заключается в том, чтобы постоянно записывать изображение с веб-камеры во временный файл, который затем используется для просмотра в браузере. Мы же будем считывать содержимое этого файла и выводить его на 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.