Создайте целую систему домашнего наблюдения
Я недавно переехал в новый дом с охраной и системой видеонаблюдения. Ничего странного в Перу; Здания с таким уровнем безопасности довольно распространены, поскольку в стране исключительно высок уровень краж со взломом. По данным экспертов по безопасности Budget Direct, в Перу самый высокий уровень краж со взломом в мире — 2086 на 100 000 человек в год.
Тогда в голову пришла мысль: а можно ли воспроизвести такую систему самостоятельно, но только для дома? Возможно, вы помните мои предыдущие статьи, где я игрался с камерами в Unity, так что у меня был некоторый опыт работы с видеопотоками, и идея выглядела осуществимой. Кроме того, я не знал, что делать с купленной веб-камерой, чтобы пройти сертификацию AWS, так что это была отличная возможность использовать ее повторно.
Кроме того, я подумал, что было бы здорово интегрировать компонент наблюдения, который предупреждает меня на моем телефоне, когда камеры обнаруживают какое-либо движение. Такие компании, как Xiaomi, предлагают такого рода решения, и я могу принять их за вдохновение.
Требования
Это может быть самой сложной частью. Идея, витающая в воздухе, должна быть преобразована во что-то конкретное, и должен быть определен список разумных требований. Распространенной ошибкой является определение слишком большого количества требований, которые приводят к невыполнимости проекта.
Основываясь на своей идее, я мог бы определить следующие требования:
- Я должен иметь возможность визуализировать все живые видеопотоки на мониторе моего ПК.
- Система должна работать с веб-камерой старой школы, которую мне пришлось купить, чтобы получить сертификат AWS.
- Система должна иметь возможность обрабатывать переменное количество камер.
- Так как я не всегда могу смотреть в монитор, то ответственные моменты (движения) надо фиксировать и хранить в онлайн-хранилище.
- После того, как видео записано и сохранено, я должен получить уведомление на свой телефон.
- Я должен иметь возможность загружать и визуализировать видео на своем мобильном телефоне.
- Каждое записываемое видео должно содержать название камеры, точную дату и время.
Архитектура программного обеспечения
Начнем с архитектуры программного обеспечения. В зависимости от требований мы можем определить четыре основных компонента системы: настольное приложение, клиентское приложение, серверную систему и службу обмена сообщениями.
Для настольного приложения я выбрал Unity3D. Создание такой системы может быть неочевидным выбором, но у меня есть опыт работы с ней, и я давно хотел сделать прямую интеграцию между Unity3D и AWS, так что это отличная возможность!
Unity3D — более очевидный выбор для клиентского приложения, поскольку он может работать на нескольких платформах и ОС (приложения Unity могут работать на настольных компьютерах, iOS, Android, WebGL, tvOS, PS4 и PS5).
Что касается сервера, я решил работать с Amazon Web Services (AWS) в основном из-за надежности его масштабируемых сервисов и моего опыта работы с ними.
Для уведомлений я выбрал Firebase, который предоставляет самую популярную платформу облачных сообщений по скудной цене.
Вот общая архитектура решения:
Примечания:
- Настольное приложение будет обрабатывать видеопотоки устройств камеры и отображать их на экране.
- Оба приложения Unity3D будут напрямую подключаться к AWS с помощью AWS SDK для .NET.
- В этой статье я решил упростить задачу: я буду использовать пользователей IAM для безопасного подключения приложений к AWS. Если вы хотите увидеть отличную интеграцию с пользователями API Gateway и Cognito, прочитайте следующую статью: Как я построил гостиничную платформу с Unity3D и AWS.
- Политики IAM будут ограничивать доступ к определенному сегменту S3.
- Как только видео загружается в корзину, запускается событие S3 и вызывается функция Lambda.
- Токен Firebase мобильного устройства хранится в таблице DynamoDB. Опять же, я сделал все просто: я вручную скопирую токен Firebase в таблицу DynamoDB, но мы также можем написать лямбда-функцию для ее автоматизации.
- Функция Lambda вызывает службу обмена сообщениями Firebase Cloud для отправки push-уведомления на зарегистрированное мобильное устройство.
Реализация Unity3D
Отображение видеопотоков
Помните мою статью об анализе текстур в реальном времени? Мы создадим нечто подобное для настольного приложения. Нам нужны следующие объекты Unity: Camera
, Plane
, и WebCamTexture
.
Для каждого устройства камеры мы получим поток в виде WebCamTexture и отрендерим его на Plane
. Unity Camera
сфокусируется на Plane
, и при обнаружении движения будет записано видео.
Мы создадим объект, содержащий Plane
, Text
(имя камеры и дату) и запись Camera
. Скрипт CamController
прикреплен к родительскому объекту и будет управлять всем, что связано с видеопотоком: открытием потока, анализом текстур и записью. Для каждого устройства камеры мы клонируем (или создаем экземпляр) объект модели.
Итак, сначала мы ищем доступные устройства с веб-камерой и создадим объект для каждого из них:
Примечания:
- Мы используем класс WebCamTexture для получения всех доступных видеоустройств.
- Мы используем функцию Instantiate для клонирования объекта модели.
Затем мы инициализируем кулачковый контроллер:
Затем мы каждую секунду обновляем текстовое поле текущей датой и временем:
Примечания:
- Мы используем функцию InvokeRepeating для вызова функции обновления каждую секунду.
- Мы используем тег
<mark>
TextMeshPro, который предлагает действительно фантастические варианты формата, как упоминалось в документации. - Мы используем функцию .NET DateTime.Now для получения фактической даты и времени и функцию ToString для их форматирования.
Наконец, мы можем показать видеопотоки:
Примечания:
- Создаем новый
WebCamTexture
с точным названием устройства и стандартным разрешением HD. - Мы визуализируем поток на
Plane
, заменяя текстуру самолета на WebCamTexture устройства камеры. - Мы используем Функция воспроизведения
WebCamTexture
для показа видеопотока.
Вот окончательный результат с двумя камерами и моей собакой Oreo; выглядит круто 🐶 ❤️
Обнаружение движения
Теперь мы выполним анализ текстуры в режиме реального времени, чтобы обнаружить любое движение на камере. Это должно включать следующее поведение:
- Объекты, движущиеся относительно быстро: люди, животные или что-то еще 👽 ходьба
- Резкая смена освещения: в комнате включили свет, фонарики и т.п.
- Любое сильное возмущение окружающей среды: землетрясение, пожар, взрыв, молния и т.п.
В следующем примере я сделал два одинаковых снимка подряд и проверил различия с помощью онлайн-инструмента DiffChecker. Отличия выделены розовым цветом на третьей картинке:
Теоретически между обоими снимками не должно быть никакой разницы, но она есть: возможно, камера немного сдвинулась, или это могли быть какие-то тонкие флуктуации света, которые вызвали такой результат. Поэтому при анализе кадра приходится учитывать только существенные изменения и игнорировать слабые.
В следующем примере между двумя изображениями произошла значительная разница, и мы можем сделать вывод, что наушники двигались. Все остальное можно игнорировать.
Чтобы выполнить обнаружение движения, мы получим массив пикселей текущего кадра текстуры и проанализируем его. Это состоит из двух шагов:
- Сравните пиксель каждого кадра с относительным пикселем предыдущего кадра. Если цвет значительно отличается, то пиксель изменился.
- Подсчитайте, сколько пикселей изменилось по сравнению с общим количеством пикселей кадра. Если отношение значимое, кадр отличается от предыдущего и обнаруживается движение.
Это способ выполнить это в Unity3D:
Примечания:
- Мы используем функцию GetPixel для получения массива пикселей текущего кадра.
- Каждый цветовой канал (R, G, B) представляет собой плавающее значение в диапазоне от 0 до 1.
- Я обнаружил, что при изменении цвета менее чем на 5% пиксель можно считать неизменным.
- Я обнаружил, что менее 5% от общего изменения кадра можно считать неизменным.
Захват видео
Итак, как только мы реализовали обнаружение движения на устройствах с камерами, нам нужно захватить поток и сохранить его в виде видеофайла. Я нашел несколько способов сделать это:
- Использование класса VideoCapture: выглядит хорошо, но сейчас я использую Mac. Жаль, нужен другой вариант.
- Работа со сторонним активом: это лучший вариант, но я почти всегда неохотно работаю со сторонними активами. Действительно ли актив настолько хорош? Будет ли он сохраняться в будущем?
- Использование пакета Unity под названием Recorder. Это выглядит намного лучше, чем предыдущие варианты: разрешена мультиплатформенность, она выпущена самой Unity, а самое главное… она бесплатна!
Пакет Recorder можно установить через окно диспетчера пакетов:
Давайте посмотрим на пакет. Пакет Recorder предоставляет графический интерфейс, в котором захват видео из редактора может выполняться вручную. Фантастическая особенность этого пакета заключается в том, что вы можете выбрать конкретную камеру в качестве цели. Хорошо для нас; мы можем записывать видео из разных видеопотоков одновременно.
Это выглядит хорошо, но нам нужно автоматизировать это. Вот как это сделать программно:
Примечание: все объекты и классы видеорегистратора описаны в документации.
Подключение Unity3D к AWS
➡️ Работа с AWS SDK:
Я буду честен; Я немного боролся с этой частью и объясню, почему.
Несколько лет назад AWS предоставляла специальный SDK для Unity3D, который было очень легко установить. Он устарел и теперь включен в AWS SDK для .NET. Звучит неплохо, но в документации AWS нет подробностей об использовании этого SDK в проекте Unity.
Для .NET Microsoft использует механизм NuGet. В Visual Studio вы можете открыть окно NuGet Packages, в котором вы можете визуализировать пакеты .NET, установленные в вашем текущем проекте.
Для текущего проекта нам нужно реализовать интеграцию с S3, поэтому нам нужен базовый пакет AWSSDK.Core и пакет S3 AWSSDK.S3.
Но держись! Окно NuGet в основном предназначено для проектов .NET, поэтому ваш проект Unity не сможет распознать пакеты, если вы установите их через окно NuGet; вам нужно сделать это вручную с веб-сайта NuGet.
На веб-сайте NuGet вы можете загрузить основной пакет AWS и пакет AWS S3. Поскольку функции AWS SDK используют асинхронные задачи, необходимо также загрузить пакет AsyncInterfaces.
После загрузки вы можете разархивировать пакеты и поместить их в папку Plugins вашего проекта Unity:
Новые пакеты теперь распознаются внутри проекта Unity; мы сделали это хорошо 🙌
➡️ Функции:
Загрузить файл:
В настольном приложении нам нужно загрузить записанный видеофайл в нашу онлайн-базу S3. Будем надеяться, что документация по .NET завершена и дает нам отличные примеры реализации. Чтобы загрузить файл, мы будем использовать метод PutObjectAsync.
Примечания:
- Мы создаем S3-клиент благодаря классам AmazonS3Client и AmazonS3Config.
- Строим запрос благодаря классу PutObjectRequest.
- Класс Task — это чистый .NET-механизм для управления асинхронными процессами. Вы можете вызвать его в Unity с помощью оператора await внутри асинхронной функции.
Список сегментов:
В клиентском приложении нам нужно вывести содержимое корзины S3. Благодаря методу ListObjectsV2 мы получим список имен файлов.
Примечания:
- Строим запрос благодаря классу ListObjectV2Request.
- Мы сохраняем ответ в списке имен файлов.
Скачать файл:
Получив список файлов в корзине, мы можем загрузить интересующее нас видео. Для этого воспользуемся методом GetObjectAsync.
Примечания:
- Строим запрос благодаря классу GetObjectRequest.
- Мы используем persistentDataPath для хранения загруженного нами файла на устройстве.
Показать видео
Мы будем использовать VideoPlayer
component для показа видео внутри клиентского приложения и выберем «URL» в качестве источника.
Затем для каждого выбранного видео мы заменяем параметр URL на путь к файлу и воспроизводим видео.
Примечания:
- Сначала мы проверяем, присутствует ли файл уже в устройстве с помощью метода File.Exists. Если нет, то скачиваем.
Firebase
В консоли Firebase создайте новый проект, а затем новое приложение Unity.
Создать новое приложение Firebase очень просто: вы должны зарегистрировать приложение, загрузить файл конфигурации в свой проект Unity, скачать Unity SDK, разархивировать его и установить нужные пакеты Unity. Каждый шаг хорошо объясняется в консоли, и весь процесс занимает всего пару минут.
Для этого проекта мы установим пакет FirebaseAnalytics (как рекомендуется) и пакет FirebaseMessaging.
В клиентском приложении Unity мы можем использовать функцию GetTokenAsync для получения токена устройства Firebase:
Реализация AWS
S3
Создаем приватный репозиторий, где будут храниться все видеофайлы. Я назову свой «мониторинг-аб».
Я
В IAM мы создадим двух пользователей: одного для загрузки видеофайлов на S3, а другого для их загрузки. В консоли IAM создадим двух пользователей с программным доступом:
После создания не забудьте скопировать секретный ключ доступа или загрузить CSV-файл с учетными данными. Их больше не будет видно!
Эти учетные данные будут использоваться в Unity при создании клиента S3:
Затем нам нужно создать политику для каждого пользователя. Политики позволят пользователю выполнять действия с определенной корзиной (в моем случае — с мониторингом). Для этого проекта удобнее создавать встроенные политики, а не управляемые политики, поскольку они будут неотъемлемой частью каждого пользователя и не будут использоваться повторно. Вы можете проверить Документацию IAM для более подробной информации.
Поэтому мы создадим встроенную политику для каждого пользователя. Для этого нажмите кнопку «Добавить встроенную политику» на экране сведений о пользователе.
Для загружаемого пользователя мы создаем политику с разрешениями ListBucket и GetObject для конкретного ведра Monitoring-ab.
Для пользователя загрузки мы создаем политику с разрешением PutObject для конкретного Bucket Monitoring-ab.
ДинамоДБ
В консоли DynamoDB создайте новую таблицу с appId в качестве ключа раздела:
Затем создайте элемент, связанный с именем корзины S3, и массив с токенами устройств Firebase:
лямбда
➡️ Лямбда-слой:
Прежде чем писать функцию Lambda, мы должны загрузить Firebase Python library в слое Lambda. Библиотека работает с Python ›=3.7, поэтому мы будем работать с самой последней версией Python, поддерживаемой Lambda, Python 3.9. Мы могли бы загрузить библиотеку вместе с кодом, но, внедрив ее в слой, мы могли бы повторно использовать библиотеку Firebase в других проектах и сосредоточиться на коде.
Отличный совет по созданию уровня Lambda — сначала установить необходимые библиотеки в новой локальной среде. Я рекомендую использовать Сообщество PyCharm, которое интегрирует локальные виртуальные среды. Единственное, что вам нужно сделать, это создать новый проект вместе с новой связанной виртуальной средой.
Итак, в новой среде Python 3.9 мы сначала обновим PIP, диспетчер пакетов Python, а затем установим библиотеку Firebase благодаря следующим командам в терминале PyCharm:
После установки вы можете визуализировать библиотеку Firebase и все ее зависимости в папке venv вашего проекта:
Выглядит хорошо! Следующий шаг — скопировать папку lib, содержащуюся в папке venv, в новую папку с именем «python» и, наконец, заархивировать ее.
Мы почти закончили! Теперь мы войдем в консоль AWS, создадим новый слой Lambda для Python 3.9 и загрузим zip-файл.
Наш слой готов к использованию!
➡️ Лямбда-функция:
Это может быть самая захватывающая часть! Наш слой Lambda готов, и теперь мы можем отправлять сообщения в Firebase с помощью функции Lambda.
Прежде всего, мы создадим лямбда-функцию для Python 3.9.
В созданной новой функции Lambda не забудьте добавить слой firebase:
Затем мы возвращаемся к консоли S3 и создаем событие S3, чтобы новая функция Lambda запускалась после загрузки каждого файла в корзину. Функция Lambda будет вызываться после обнаружения события «Put» в корзине:
Вернитесь к Лямбде; мы видим, что событие S3 учтено, и слой добавлен:
И вот функция:
Примечания:
- Чтобы получить учетные данные вашего проекта Firebase, вам сначала нужно создать закрытый ключ в формате JSON. Это легко сделать в настройках проекта, как указано в документации firebase-admin. Затем вы можете загрузить учетные данные в виде локального файла в свою функцию Lambda.
- Мы получаем имена камеры и корзины из события S3. Вы можете найти подробную структуру события S3 и отличный пример реализации на Python в документации AWS.
- Мы используем функцию unquote_plus библиотеки urllib для анализа имен камер и корзин.
- Мы создаем новое сообщение Firebase благодаря классу MulticastMessage, и отправляем сообщение благодаря функции send_multicast. Вы можете найти отличные примеры реализации функции firebase-admin в репозитории Firebase GitHub.
Конечный результат
Что ж, теперь мы можем визуализировать окончательный результат! Для этого теста я разместил две камеры на своей террасе, пока мои собаки спали в спальне, и позвонил им. Я активировал детекцию движения только для камеры, фокусирующейся на собаках.
В настольном приложении мы видим, как Орео и Симба идут ко мне из спальни:
Обнаружено движение и создано короткое видео (Вы можете наблюдать красный кружок на первой камере во время записи видео). Как только видео было загружено, я получил уведомление на своем мобильном устройстве:
Если мы проверим консоль Firebase, мы увидим, что сообщение было успешно отправлено:
Теперь я могу открыть мобильное приложение и посмотреть записанное видео:
Расходы
Это обновление! После публикации этой статьи Сет Уэллс сделал отличное замечание в разделе комментариев: как насчет затрат? Давайте проделаем упражнение с самой пессимистичной гипотезой.
Система, которую я разработал, имеет минимальный интервал записи. Большой интервал сделал бы всю систему неэффективной, а слишком маленький интервал сделал бы ее неудобной (получение уведомления каждую минуту может быть надоедливым). Итак, давайте поговорим об интервале в 5 минут.
Исходя из этого значения, предполагая, что на камерах всегда есть движение, у нас будет 24x60/5 = 288 видео в день, около 9000 видео в месяц. Размер видеофайла может варьироваться в зависимости от желаемого разрешения. Давайте поговорим о видео размером 1 МБ в течение 10 секунд. Теперь давайте проверим стоимость с помощью Калькулятора цен AWS.
- IAM: IAM можно использовать бесплатно.
- S3: Это основная служба нашей системы. Имея 9 ГБ данных в месяц, 9000 запросов на размещение, 1000 запросов на списки (гипотетически, когда пользователь открывает клиентское приложение) и 1000 запросов на получение (гипотетически, когда пользователь загружает видео), мы получаем счет в размере 0,26. долларов США в месяц, 3,12 долларов США в год.
- Lambda: 9 000 запросов в месяц, среднее время выполнения запроса – 3 секунды и 128 МБ выделенной памяти – наш счет составляет 0,00 доллара США, хорошие новости!
- DynamoDB: DynamoDB здесь используется недостаточно: мы не пишем ни в одну таблицу, и у нас есть запрос на чтение каждые 5 минут. Стоимость: 0,00 долларов США.
- Firebase: Firebase Cloud Messaging абсолютно бесплатен.
Всего: 0,26 доллара США в месяц, 3,12 доллара США в год при пессимистическом прогнозе. Можно смело сказать, что наша система доступна по цене!
Заключительные мысли
В этой статье у нас была возможность увидеть, как построить целую облачную архитектуру и как вызвать из нее внешнюю службу обмена сообщениями (Firebase). Мы также смогли создать два приложения Unity3D, которые напрямую взаимодействуют с AWS, благодаря .NET SDK. Кроме того, мы могли оценить стоимость всей системы благодаря калькулятору AWS.
Каждый код в этой статье был протестирован с использованием Unity 2021.3.3 и Visual Studio Community 2022 для Mac. Мобильное устройство, которое я использовал для запуска приложения Unity, — это Galaxy Tab A7 Lite с Android 11.
Все идентификаторы и токены, показанные в этой статье, являются поддельными или просроченными; если вы попытаетесь их использовать, вы не сможете установить никаких соединений.
Вы можете скачать пакеты Unity Настольное приложение и клиентское приложение, специально разработанные для этой статьи.
Особая благодарность Gianca Chavest за создание потрясающей иллюстрации.