Перейти к содержанию

FALPRS

Этот проект является заменой старого. Основные отличия:

  • В качестве СУБД используется PostgreSQL.
  • Проект использует userver — асинхронный фреймворк с открытым исходным кодом.
  • Строгое соблюдение типов данных в API запросах. Например, если ожидается числовое поле, то его нельзя заключать в кавычки.
  • Добавлена система распознавания автомобильных номеров (license plate recognition system - LPRS).

Содержание

LPRS

Система предназначена для распознавания автомобильных номеров и обнаружения специального транспорта с мигалками: скорая помощь, МЧС, полиция и т.п. На данный момент поддерживаются российские регистрационные знаки типа 1 (ГОСТ Р 50577-93) и 1А (ГОСТ Р 50577-2018):

Используемые модели нейронных сетей

Сервис работает с четырмя нейронными сетями: VDNet, VCNet, LPDNet и LPRNet. VDNet предназначена для поиска транспортных средств по полученному снимку с видео камеры. VCNet определяет, является ли каждое найденное транспортное средство специальным. LPDNet предназначена для поиска автомобильных номеров. LPRNet предназначена для распознавания номеров из полученных LPDNet данных. Модели VDNet, LPDNet и LPRNet обучены с помощью Ultralytics. Модель VCNet получена путем "дообучения" (transfer learning with fine-tuning). За основу взята эта модель. Для инференса нейронных сетей используется NVIDIA Triton Inference Server.

Общая схема взаимодействия с LPRS

Для взаимодействия с сервисом используется API, подробное описание которого находится в репозитории в файле docs/openapi.yaml. Сначала нужно зарегистрировать видео потоки, с которыми предстоит работать системе. Для этого используется API метод addStream. Основные параметры:

  • streamId - внутренний для вашего бэкенда (внешний для LPRS) идентификатор видео потока;
  • config - конфигурационные параметры. Пример тела запроса:
{
  "streamId": "1234",
  "config": {
    "screenshot-url": "https://my.video.stream/capture",
    "callback-url": "https://my.host/callback?streamId=1"
  }
}

Теперь LPRS знает, что захват кадров надо делать с помощью указанного параметра screenshot-url, а при обнаружении специального транспорта или распознавании автомобильных номеров отправлять краткую информацию HTTP методом POST на callback-url. Чтобы LPRS стала обрабатывать кадры, нужно вызвать API метод startWorkflow. Пример тела запроса:

{
  "streamId": "1234"
}

Система начнёт делать цикличный процесс: получение кадра, его обработка с помощью нейронных сетей, отправление, если требуется, информации на callback-url. Пауза на некоторое время и вновь получение кадра, обработка и т.д. Для остановки процесса нужно вызвать API метод stopWorkflow. Пример тела запроса:

{
  "streamId": "1234"
}

Чтобы снизить нагрузку на систему, мы рекомендуем вызывать startWorkflow и stopWorkflow, например, в соответствии с видеоаналитикой камеры: детектор движения, пересечение линий, обнаружение вторжений и т.п. При получении краткой информации о распознанных номерах, ваш бэкенд должен её обработать. Например, cверить полученные номера со списком разрешённых и открыть ворота или просто проигнорировать. События распознавания номеров некоторое время хранятся в LPRS. Если требуется полная информация о событии, то нужно использовать API метод getEventData. Для удаления видео потока используется API метод removeStream. С помощью listStreams можно получить информацию о зарегистрированных в LPRS видео потоках.

Автоматический бан номеров

Для предотвращения спама запросов на callback-url используется двухэтапный бан номеров. Если система видит номер впервые, то после обработки он окажется на первом этапе бана: на некоторое время (парметр конфигурации ban-duration) номер игнорируется вне зависимости от его расположения в кадре. Если система снова увидит этот номер на первой стадии, то время бана продлевается. По окончании первого этапа номер попадёт в следующий. На втором этапе бана (параметр конфигурации ban-duration-area) номер игнорируется до тех пор, пока не поменяет свое местоположение в кадре. При смене местоположения (параметр конфигурации ban-iou-threshold) номер будет обработан и снова попадёт на первый этап. По окончании второго этапа номер удаляется из бана. Если вы не хотите, чтобы применялся автоматический бан номеров, то необходимо установить значение конфигурационного параметра ban-duration или ban-duration-area в нулевое значение, например 0s (0 секунд).

FRS

Система предназначена для распознавания лиц.

Используемые модели нейронных сетей

На данный момент FRS использует в работе три модели:

  • scrfd - предназначена для поиска лиц на изображении. Ссылка на проект.
  • genet - предназначена для определения наличия на лице маски или тёмных очков. За основу взят этот проект. Модель получена путем "дообучения" (transfer learning with fine-tuning) на трех классах: открытое лицо, лицо в маске, лицо в тёмных очках.
  • arcface - предназначена для вычисления биометрического шаблона лица. Ссылка на проект.

Общая схема взаимодействия с FRS

Для взаимодействия с сервисом используется API, подробное описание которого находится в репозитории в файле docs/openapi.yaml. Первым делом, ваш бэкенд с помощью вызова API метода addStream регистрирует видео потоки. Основными параметрами метода являются:

  • streamId - внутренний для бэкенда (внешний для FRS) идентификатор видео потока;
  • url - это URL для захвата кадра с видео потока. FRS не декодирует видео, а работает с отдельными кадрами (скриншотами). Например, URL может выглядеть как http://<имя хоста>/cgi-bin/images_cgi?channel=0&user=admin&pwd=<пароль>
  • callback - это URL, который FRS будет использовать, когда распознает зарегистрированное лицо. Например, http://<адрес-бэкенда>/face_recognized?stream_id=1

Для регистрации лиц используется метод registerFace. Основные параметры метода:

  • streamId - идентификатор видео потока;
  • url - это URL с изображением лица, которое нужно зарегистрировать. Например, http://<адрес-бэкенда>/image_to_register.jpg В случае успешной регистрации возвращается внутренний для FRS (внешний для бэкенда) уникальный идентификатор зарегистрированного лица - faceId.

Для старта и окончания обработки кадров используется метод motionDetection. Основная идея заключается в том, чтобы FRS обрабатывала кадры только тогда, когда зафиксировано движение перед видео камерой. Параметры метода:

  • streamId - идентификатор видео потока;
  • start - признак начала или окончания движения. Если start=true, то FRS начинает через каждую секунду (задаётся параметром delay-between-frames) обрабатывать кадр с видео потока. Если start=false, то FRS прекращает обработку.

Под обработкой кадра подразумевается следующая цепочка действий:

  1. Поиск лиц с помощью нейронной сети scrfd.
  2. Если лица найдены, то каждое проверяется на "размытость" и "фронтальность".
  3. Если лицо не размыто (чёткое изображение) и фронтально, то с помощью нейронной сети genet определяется наличие маски и тёмных очков.
  4. Для каждого лица без маски и без тёмных очков, с помощью нейронной сети arcface, вычисляется биометрический шаблон лица, он же дескриптор. Математически, дескриптор представляет собой 512-ти мерный вектор.
  5. Далее каждый такой дескриптор попарно сравнивается с зарегистрированными в системе. Сравнение делается путём вычисления косинуса угла между векторами-дескрипторами: чем ближе значение к единице, тем больше похоже лицо на зарегистрированное. Если самое максимальное значение косинуса больше порогового значения (параметр tolerance), то лицо считается распознанным и FRS вызывает callback (событие распознавания лица) и в качестве параметров указывает идентификатор дескриптора (faceId) и внутренний для FRS (внешний для бэкенда) идентификатор события (eventId). Если в одном кадре окажется несколько распознанных лиц, то callback будет вызван для самого "качественного" ("лучшего") лица (параметр blur).
  6. Каждое найденное неразмытое, фронтальное, без маски и без тёмных очков, лучшее лицо временно хранится в журнале FRS.

С помощью метода bestQuality у FRS можно запрашивать "лучший" кадр из журнала. Например, незнакомый системе человек подошёл к домофону и открыл дверь ключом. Бэкенд знает время открытия ключом (date) и запрашивает лучший кадр у FRS. FRS ищет у себя в журнале кадр с максимальным blur из диапазона времени [date - best-quality-interval-before; date + best-quality-interval-after] и выдаёт его в качестве результата. Такой кадр - хороший кандидат для регистрации лица с помощью метода registerFace. Как правило, для хорошего распознавания необходимо зарегистрировать несколько лиц одного человека, в том числе с кадров, сделанных в тёмное время суток, когда камера переходит в инфракрасный режим работы.

Сборка и настройка проекта

Системные требования

  • CPU с AVX-инструкциями.
  • NVIDIA GPU с параметром Compute Capability большим или равным 6.0 и памятью 4 Гб или больше. Подробности можно посмотреть, например, здесь.
  • СУБД PostgreSQL 14 или выше.

Для получения исходного кода нужен git. Если не установлен, то выполнить команду:

sudo apt-get install -y git

Получение исходного кода проекта:

cd ~
git clone --recurse-submodules https://github.com/rosteleset/falprs.git

Установка драйверов NVIDIA

Если в системе уже установлены свежие драйвера NVIDIA, то пропустите этот пункт. Для установки можно использовать скрипт scripts/setup_nvidia_drivers.sh. Основные команды взяты отсюда.

sudo ~/falprs/scripts/setup_nvidia_drivers.sh

После установки необходимо перезагрузить операционную систему:

sudo reboot

Установка Docker Engine

Если в системе уже установлен Docker Engine, то пропустите этот пункт. Для установки можно использовать скрипт scripts/setup_docker.sh. Основные команды взяты отсюда.

sudo ~/falprs/scripts/setup_docker.sh

Установка NVIDIA Container Toolkit

Если в системе уже установлен NVIDIA Container Toolkit, то пропустите этот пункт. Для установки можно использовать скрипт scripts/setup_nvidia_container_toolkit.sh. Основные команды взяты отсюда.

sudo ~/falprs/scripts/setup_nvidia_container_toolkit.sh

После установки предлагается перезапустить docker:

sudo systemctl restart docker

Установка PostgreSQL

Если PostgreSQL не установлен, то запустите команду:

sudo apt-get install -y postgresql

Запустите psql:

sudo -u postgres psql

Выполните SQL команды, указав ваш пароль вместо "123":

drop user if exists falprs;
create user falprs with encrypted password '123';
create database frs;
grant all on database frs to falprs;
alter database frs owner to falprs;
create database lprs;
grant all on database lprs to falprs;
alter database lprs owner to falprs;
\q

Сборка проекта

Для сборки проекта можно использовать скрипт scripts/build_falprs.sh. Мажорная версия PostgreSQL задаётся переменной PG_VERSION. Установленную версию PostgreSQL можно узнать командой:

psql --version

Рабочая директория проекта задаётся переменной FALPRS_WORKDIR (значение по-умолчанию /opt/falprs), версия контейнера с Triton Inference Server задаётся переменной TRITON_VERSION.

Таблица поддержки Compute Capability и последней версии контейнера
Compute Capability Архитектура GPU Версия контейнера TensorRT
6.x Pascal 24.04 8.6.3
7.0 Volta 24.09 10.4.0.26

Посмотреть Compute Capability:

nvidia-smi --query-gpu=compute_cap --format=csv

Если у вас GPU с Compute Capability 7.5 или выше, то вы можете использовать самую последнюю версию контейнера. Например, сборка для Ubuntu 24.04 и GPU с архитектурой Volta:

sudo PG_VERSION=16 TRITON_VERSION=24.09 ~/falprs/scripts/build_falprs.sh

Создание TensorRT планов моделей нейронных сетей

Для инференса используются TensorRT планы, которые могут быть получены из моделей нейронных сетей в формате ONNX (Open Neural Network Exchange). Для создания планов в рабочей директории можно использовать скрипт scripts/tensorrt_plans.py. Рабочая директория проекта задаётся переменной FALPRS_WORKDIR (значение по-умолчанию /opt/falprs), версия Triton Inference Server задаётся переменной TRITON_VERSION. Важно: если вы делаете миграцию со старого проекта, то ещё необходимо с помощью утилиты sha1sum указать хеш-сумму ONNX файла, который вы использовали для создания TensorRT плана модели arcface. Это значение указывается в переменной ARCFACE_SHA1. Пример выполнения без миграции:

sudo TRITON_VERSION=24.09 python3 ~/falprs/scripts/tensorrt_plans.py

Примеры выполнения с миграцией для старого файла glint_r50.onnx. Подсчёт хеш-суммы:

sha1sum glint_r50.onnx
4fd7dce20b6987ba89910eda8614a33eb3593216  glint_r50.onnx

Создание TensorRT планов:

sudo TRITON_VERSION=24.09 ARCFACE_SHA1=4fd7dce20b6987ba89910eda8614a33eb3593216 python3 ~/falprs/scripts/tensorrt_plans.py

Настройка проекта

Для первоначального заполнения баз данных выполните команды, указав значения переменных с префиксом pg_ (пароль "123" замените на указанный вами при создании пользователя PostgreSQL):

pg_user=falprs pg_passwd=123 pg_host=localhost pg_port=5432 pg_db=frs ~/falprs/scripts/sql_frs.sh
pg_user=falprs pg_passwd=123 pg_host=localhost pg_port=5432 pg_db=lprs ~/falprs/scripts/sql_lprs.sh

Конфигурация проекта находится в файле /opt/falprs/config.yaml У основных параметров есть описание в комментариях. Некоторые значения нужно заменить.

  • В секциях components_manager -> components -> lprs-postgresql-database и components_manager -> components -> frs-postgresql-database в значениях атрибутов dbconnection замените пароль "123" на указанный вами при создании пользователя PostgreSQL , а также другие реквизиты доступа, если они отличаются.
  • В секции components_manager -> task_processors -> main-task-processor замените значение worker_threads на количество ядер CPU вашего сервера. Количество можно посмотреть, например, с помощью команды:
cat /proc/cpuinfo | grep processor | wc -l
  • В секции components_manager -> task_processors -> fs-task-processor замените значение worker_threads на количество CPU вашего сервера, умноженное на 4.

Для запуска контейнера с Triton Inference Server выполните команду:

sudo TRITON_VERSION=24.09 ~/falprs/scripts/triton_service.sh

Для создания сервиса falprs и ротации логов выполните команду:

sudo ~/falprs/scripts/falprs_service.sh

Для запуска сервиса falprs выполните команду:

sudo systemctl start falprs.service

Управление группами видео потоков

Каждый видео поток принадлежит какой-то одной группе. При заполнении первоначальными данными автоматически создаётся группа с названием default. При вызове API методов для этой группы можно не указывать токен авторизации. Для просмотра, добавления и удаления групп можно использовать скрипт utils/vstream_groups.py Устанавливаем зависимости:

sudo apt-get install -y python3-psycopg2 python3-prettytable

Показать список команд:

python3 ~/falprs/utils/vstream_groups.py -h

Пример добавления новой группы в FRS:

python3 ~/falprs/utils/vstream_groups.py -t frs -a "My new group"

Показать список групп в LPRS:

python3 ~/falprs/utils/vstream_groups.py -t lprs -l

Пример вывода:

+----------+--------------+--------------------------------------+
| id_group |  group_name  |              auth_token              |
+----------+--------------+--------------------------------------+
|    1     |   default    | 4b05cce8-d29e-4e7f-a1fa-247a91f3fd46 |
|    2     | My new group | 74c0e0f0-ea70-47fb-b715-8932baf7e049 |
+----------+--------------+--------------------------------------+

При вызове API методов для новых групп необходимо указывать токен авторизации. Если вы хотите, чтобы токен авторизации стал обязательным для группы default, то установите значение 0 для параметров allow-group-id-without-auth в соответствующих секциях конфигурационного файла проекта (/opt/falprs/config.yaml) и перезапустите сервис.

Примеры

Устанавливаем зависимости:

sudo apt-get install -y nodejs npm libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev

LPRS

Заходим в директорию с примерами:

cd ~/falprs/examples/lprs

Устанавливаем зависимости:

npm i

Запускаем тестовый сервис для обработки колбэков:

node lprs_backend.js

Добавляем "тестовый" видео поток. В новой консоли:

cd ~/falprs/examples/lprs
./test_add_stream.sh

Проверяем, что поток попал в базу данных:

./test_list_streams.sh | jq

В ответе должен быть json-файл. Ждём минуту, чтобы новые данные попали в кэш FALPRS, выполняем команду:

./test_workflow.sh

В результате выполнения этой команды в консоли с тестовым сервисом lprs_backend.js мы должны увидеть строки вида:

[2024-10-16 12:37:16.108] Callback: {"streamId":"test001","date":"2024-10-16T09:37:16.103891056+00:00","eventId":1,"plates":["O588OA68"],"hasSpecial":false}
[2024-10-16 12:37:16.108] Matched numbers: O588OA68
[2024-10-16 12:37:16.162] Save image to file: /tmp/lprs_backend/screenshots/49b507ac-9b7d-43a6-94f0-18f647fc1f5c.jpg

А в директории /tmp/lprs_backend/screenshots должен появиться файл с изображением:

Удаляем "тестовый" видео поток:

./test_remove_stream.sh

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

FRS

Заходим в директорию с примерами:

cd ~/falprs/examples/frs

Устанавливаем зависимости:

npm i

Запускаем тестовый сервис для обработки колбэков:

node frs_backend.js

Добавляем "тестовый" видео поток. В новой консоли:

cd ~/falprs/examples/frs
./test_add_stream.sh

Проверяем, что поток попал в базу данных:

./test_list_streams.sh | jq

В ответе должен быть json-файл. Ждём 10 секунд, чтобы новые данные попали в кэш FALPRS, выполняем команду регистрации лица:

./test_register_face.sh

Проверяем, что лицо попало в базу данных:

./test_list_all_faces.sh | jq

В ответе должен быть json-файл. Ждём 10 секунд, чтобы новые данные попали в кэш FALPRS, выполняем команду:

./test_workflow.sh

В результате выполнения этой команды в консоли с тестовым сервисом frs_backend.js мы должны увидеть строки вида:

[2024-10-21 15:34:29.180] Callback: {"faceId":1,"eventId":1}
[2024-10-21 15:34:29.180] This is Albert Einstein
[2024-10-21 15:34:29.220] Save image to file: /tmp/frs_backend/screenshots/492d7722d038434d9e2676648991e65e.jpg

А в директории /tmp/frs_backend/screenshots должен появиться файл с изображением:

Удаляем "тестовый" видео поток:

./test_remove_stream.sh

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

Тесты

Устанавливаем зависимости:

sudo apt-get install -y python3-requests python3-pytest python3-pytest-order

Создаём директории и тестовые базы данных:

cd ~/falprs/tests
./test_prepare.sh

LPRS

pytest -v -s test_api_lprs.py

FRS

pytest -v -s test_api_frs.py

После окончания тестов удаляем директории и тестовые базы данных:

./test_clean.sh

Синхронизация данных со старым проектом FRS

Скрипт sync_data.py из директории utils данного репозитория делает полную синхронизацию: в новой базе удаляет лишние записи и добавляет отсутствующие. Перед запуском скрипта убедитесь, что у вас остановлен старый сервис, а новый правильно настроен и тоже остановлен. Устанавливаем зависимости:

sudo apt-get install -y pip python3-virtualenv

Заходим в директорию со скриптом:

cd ~/falprs/utils

Копируем файл:

cp config.sample.py config.py

В файле config.py замените значения переменных в соответствии с вашей конфигурцией старого и нового сервиса. Переменные вида mysql_ и *old относятся к старому проекту, pg и *_new - к новому. Выполняем команды:

virtualenv venv
source venv/bin/activate
pip install -r requirements.txt
python3 sync_data.py

После работы скрипта должно появиться сообщение об успешном завершении. Удаляем виртуальную среду:

deactivate
rm -rf ./__pycache__/
rm -rf venv