Онлайн-трансляция (стриминг) видео с веб-камеры через Firebase

В ходе изучения темы машинного обучения (и компьютерного зрения в частности) у меня возникла мысль попробовать осуществить онлайн-трансляцию видео с домашней веб-камеры на своём сайте через Firebase. Идея показалась мне интересной, т.к. в будущем можно было бы демонстрировать работу различных алгоритмов распознавания объектов в реальном времени. При этом использование Firebase в качестве промежуточного звена избавило бы от необходимости решать проблему получения данных с «серого» IP.

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

  • Скрипт на Python, который бы запускался на компьютере с подключённой веб-камерой, получал с неё кадры с заданной периодичностью и сохранял их в некую переменную в Firebase (сервер)
  • Веб-страница с кодом JavaScript, отслеживающим обновление  этой переменной и динамически отображающим её содержимое на странице (клиент)

С Firebase из веб-приложения на Vue я уже работал и опыт был очень позитивный, взаимодействовать с веб-камерой из кода на Python я как раз недавно научился. Серьёзных проблем с реализацией не предвиделось и я с интересом приступил к делу 🙂

Подготовка

Для начала я создал новый проект в Google Cloud Console (GCP) и првязал к нему новый проект в консоли Firebase. Здесь я не буду подробно описывать эти шаги, а инструкции при необходимости можно легко найти в интернете.

Далее в консоли Firebase необходимо добавить новую Realtime Database, в которой создать единственный дочерний элемент frame с пустым значением, в нём будет храниться последний отправленный кадр в формате base64. Этот формат позволяет сохранять изображения в виде строки, которую также можно использовать в качестве атрибута src тега img. Также на время тестирования я установил разрешения на чтение и запись без авторизации на вкладке «Правила»:

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

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

Переходим к написанию кода.

Сервер

Для работы с Firebase я использовал рекомендованную в документации библиотеку Pyrebase. Данные для подключения загружаются из файла конфигурации firebase_config.json. Далее выполняется подключение к Firebase и запускается процесс видеозахвата с камеры. Видеотрансляция отображается в новом окне, при этом каждую секунду в Firebase сохраняется новый кадр видео. Процесс можно остановить нажатием на клавиши «q».

streamer.py

import base64
import json
import time

import cv2
import pyrebase

from imutils.video import VideoStream, FPS


def init_stream():
    print('[INFO] connecting to firebase...')

    with open('firebase_config.json') as file:
        firebase_config = json.load(file)

    firebase = pyrebase.initialize_app(firebase_config)
    db = firebase.database()

    print('[INFO] starting video stream...')

    vs = VideoStream(src=0).start()
    time.sleep(2.0)
    fps = FPS().start()
    start_time = time.time()

    while True:
        frame = vs.read()
        cv2.imshow('Webcam live stream', frame)

        if time.time() - start_time >= 1:
            frame_string = base64.b64encode(cv2.imencode('.jpg', frame)[1]).decode()
            try:
                db.update({'frame': f'data:image/jpeg;base64,{frame_string}'})
            except Exception as e:
                print(e)
            start_time = time.time()

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

        fps.update()

    fps.stop()

    print(f'[INFO] elapsed time: {fps.elapsed():.2f}')
    print(f'[INFO] approx. FPS: {fps.fps():.2f}')

    cv2.destroyAllWindows()
    vs.stop()


if __name__ == '__main__':
    init_stream()

Клиент

Веб-страница с пустым изображением и подключением необходимых библиотек Firebase.

web_client/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Webcam live stream</title>
</head>
<body>
<img src="" id="camView">

<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/7.17.2/firebase-app.js"></script>

<!-- SDKs for Firebase products
     https://firebase.google.com/docs/web/setup#available-libraries -->
<script src="https://www.gstatic.com/firebasejs/7.17.2/firebase-database.js"></script>

<script src="main.js"></script>
</body>
</html>

После загрузки страницы на ней выполняется JavaScript-код, также использующий для подключения к Firebase файл firebase_config.json. После успешного подключения создаётся «слушатель» события изменения содержимого frame в базе, автоматически загружающий его в атрибут src изображения с идентификатором camView.

web_client/main.js

document.addEventListener('DOMContentLoaded', function () {
    fetch('../firebase_config.json')
        .then(response => response.json())
        .then(data => {
            // App's Firebase configuration
            let firebaseConfig = data;
            // Initialize Firebase
            firebase.initializeApp(firebaseConfig);
            // Get a reference to the database service
            let db = firebase.database();
            // Listen for value updates
            let ref = firebase.database().ref('frame');
            ref.on('value', function(snapshot) {
                document.getElementById('camView').src = snapshot.val();
            });
        });
});

Результат

Система работает как и ожидалось! Несмотря на размер кадра около 100 Кб, даже при отправке 5 кадров в секунду изображение на клиенте обновляется практически без задержек. Этого вполне достаточно для демонстрации и на данный момент результат меня полностью устраивает. Единственное, что меня смущает, это объём передаваемых данных. Даже если отправлять по одному кадру в секунду, уложиться в бесплатную квоту в 384 Мб/сутки всё равно не получится. Над этим вопросом надо будет поработать.

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

Репозиторий: https://github.com/RiseLab/webcam_stream_firebase
Демо-клиент: https://riselab.github.io/webcam_stream_firebase