Дорожная карта развития OpenIPC NVR по трём направлениям: мобильное приложение, GPU-ускорение детекции и интеграция с Home Assistant.
Просмотр потоков, push-уведомления о событиях и открытие дверей СКУД с телефона.
| Вариант | Плюсы | Минусы | Решение |
|---|---|---|---|
| Flutter | Один код на iOS и Android, нативный плеер через video_player, хорошая работа с HLS |
Dart в команде | ✅ Основной |
| React Native | Переиспользование знаний React, общая кодовая база с веб-интерфейсом | Проблемы с фоновым видео и push | ⬜ Резерв |
| Нативные (Swift/Kotlin) | Максимальная производительность и доступ к API | Двойная работа | ❌ |
Решение: Flutter. Команда уже знакома с React, но для видеоплеера и push-уведомлений критичны нативные возможности, а Flutter даёт к ним прямой доступ при единой кодовой базе.
lib/
├── main.dart
├── api/
│ ├── client.dart # HTTP-клиент, JWT, автообновление токена
│ └── models/ # Camera, Event, Recording, ACSController
├── screens/
│ ├── login_screen.dart
│ ├── cameras_screen.dart # сетка превью
│ ├── player_screen.dart # полноэкранный плеер + PTZ-жесты
│ ├── events_screen.dart
│ ├── recordings_screen.dart
│ └── acs_screen.dart # открытие дверей
├── widgets/
│ ├── camera_tile.dart
│ ├── ptz_control.dart # свайпы для поворота
│ └── event_card.dart
└── services/
├── push_service.dart # FCM / APNs
└── storage.dart # защищённое хранение токена
Этап 1. Базовая версия (2–3 недели)
- Авторизация, хранение токена в
flutter_secure_storage - Список камер с превью из
/snapshot, обновление раз в 10 секунд - Полноэкранный плеер HLS через
video_player - Выбор основного и дополнительного потока
Этап 2. События и уведомления (2 недели)
- Лента событий с фильтром по камере и классу объекта
- Push-уведомления: требуется завершить серверную часть
(
POST /notifications/register, отправка через FCM) - Просмотр кадра события крупным планом
Этап 3. PTZ и архив (1–2 недели)
- Управление поворотом свайпами по экрану плеера
- Зум щипком
- Пресеты камер
- Просмотр архива: выбор интервала, воспроизведение
Этап 4. СКУД и полировка (1–2 недели)
- Список контроллеров и дверей, открытие по нажатию с подтверждением
- События проходной с фотографиями
- Тёмная тема, оффлайн-режим для списка камер
| Задача | Решение |
|---|---|
| Видео | HLS для совместимости, WebRTC — при потребности в низкой задержке |
| Плеер | video_player (нативный), при проблемах — better_player |
| Push | firebase_messaging, токен через /notifications/register |
| Токен | flutter_secure_storage (Keychain / Keystore) |
| Сеть | dio с перехватчиком для JWT |
| Состояние | Riverpod |
- Эндпоинт регистрации push-токена. Таблица
push_tokensуже есть, нужен обработчик и отправка. - Отдача снапшотов. Готово.
- WebRTC-сигнализация. go2rtc отдаёт WebRTC по WHEP (и MSE для браузера), но нужен удобный эндпоинт для мобильных клиентов.
- Обновление токена. Добавить
POST /auth/refresh, чтобы не требовать повторного ввода пароля.
Детектор работает на CPU через ultralytics:
RTSP → ffmpeg (1 FPS) → NATS → YOLOv8 (CPU) → NATS → PostgreSQL
Производительность на CPU: примерно 5–10 кадров/с для модели yolov8n
при разрешении 640×640. Этого хватает для 5–10 камер при частоте 1 кадр/с.
Обрабатывать 30–50 камер с частотой 5–10 кадров/с на одной GPU.
| Вариант | Плюсы | Минусы | Решение |
|---|---|---|---|
| ONNX Runtime + CUDA | Максимальная скорость, минимальные зависимости, стабильный API | Нужно экспортировать модель и писать пред/постобработку | ✅ Основной |
| TensorRT | Наивысшая производительность | Жёсткая привязка к версии GPU и драйвера, сложная сборка | ⬜ Для максимальной нагрузки |
| PyTorch + CUDA | Минимум изменений в коде | Тяжёлые зависимости, выше потребление памяти | ⬜ Быстрый старт |
| Triton Inference Server | Масштабирование, батчинг | Отдельный сервис, сложнее эксплуатация | ⬜ При росте |
Решение: ONNX Runtime с провайдером CUDA. Даёт 5–10-кратный прирост относительно CPU, остается кроссплатформенным (можно откатиться на CPU без изменения кода) и не требует сборки под конкретную видеокарту.
┌──────────────┐ NATS ┌─────────────────────────────────┐
│ frame │──────────▶│ AI Worker (пул процессов) │
│ publisher │ кадры │ ┌───────────────────────────┐ │
│ (по камере) │ │ │ Батчер: собирает кадры │ │
└──────────────┘ │ │ от разных камер в пакет │ │
│ └─────────────┬─────────────┘ │
│ ▼ │
│ ┌───────────────────────────┐ │
│ │ ONNX Runtime + CUDA │ │
│ │ YOLOv8, батч 8–16 │ │
│ └─────────────┬─────────────┘ │
│ ▼ │
│ ┌───────────────────────────┐ │
│ │ ByteTrack: трекинг │ │
│ └───────────────────────────┘ │
└───────────────┬─────────────────┘
│ NATS
▼
события в PostgreSQL
Ключевая оптимизация — батчинг: кадры от разных камер собираются в пакет и обрабатываются за один проход GPU. Это даёт кратный прирост относительно покадровой обработки.
Этап 1. Экспорт модели и замеры (3–5 дней)
# Экспорт в ONNX
yolo export model=yolov8n.pt format=onnx imgsz=640 half=True dynamic=True
# Проверка провайдеров
python -c "import onnxruntime as ort; print(ort.get_available_providers())"
# Ожидается: ['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']Замерить базовую производительность на CPU и GPU, зафиксировать метрики: кадров/с, задержка, потребление видеопамяти.
Этап 2. GPU-детектор (1–2 недели)
- Подготовка входного тензора: ресайз с сохранением пропорций, нормализация
- Постобработка: фильтрация по уверенности, NMS
- Батчинг кадров от нескольких камер
- Отслеживание объектов (ByteTrack) с сохранением состояния по камерам
- Настройка через переменные окружения:
| Переменная | Значение по умолчанию | Описание |
|---|---|---|
DEVICE |
auto |
auto, cuda, cpu |
BATCH_SIZE |
8 |
Кадров в пакете |
CONFIDENCE |
0.4 |
Порог уверенности |
HALF_PRECISION |
true |
FP16 для ускорения |
MAX_QUEUE |
32 |
Ограничение очереди кадров |
Этап 3. Оптимизация (1 неделя)
- Проверить влияние FP16 на точность (обычно потеря менее 1%)
- Профилировать узкие места: копирование памяти, предобработка
- При необходимости — экспорт в TensorRT
- Бенчмарк на целевом числе камер
Этап 4. Эксплуатация (3–5 дней)
- Docker-образ с поддержкой GPU (
nvidia-container-toolkit) - Метрики: загрузка GPU, очередь кадров, пропущенные кадры
- Поведение при недоступности GPU: автоматический откат на CPU
- Healthcheck
FROM nvidia/cuda:12.4.1-cudnn-runtime-ubuntu22.04
RUN apt-get update && apt-get install -y python3 python3-pip ffmpeg && \
rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir onnxruntime-gpu ultralytics opencv-python-headless nats-py
COPY . /app
WORKDIR /app
ENTRYPOINT ["python3", "main_gpu.py"]Запуск:
ai-detector:
build: ./ai-detector
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
environment:
DEVICE: cuda
BATCH_SIZE: "16"Требуется установленный nvidia-container-toolkit на хосте.
| Конфигурация | Кадров/с | Камер при 1 кадр/с |
|---|---|---|
| CPU, 8 ядер | 8 | 8 |
| RTX 3060, батч 8 | 80–120 | до 100 |
| RTX 4090, батч 16 | 300+ | ограничено сетью |
- Обучение под задачу. Базовая модель YOLOv8 знает 80 классов COCO. Для распознавания номеров, спецодежды и лиц нужно дообучение на собственном датасете.
- Классификация транспорта. Различать легковые, грузовые, автобусы.
- Распознавание номеров — отдельная модель, привязка к треку транспорта.
- Детекция оставленных предметов — анализ статичных объектов в зоне.
Видеть камеры, события и управлять дверями из интерфейса умного дома.
Есть два подхода:
| Подход | Плюсы | Минусы | Решение |
|---|---|---|---|
| Собственная интеграция (HACS) | Полный контроль, нативные сущности, события шины | Нужно сопровождать Python-код | ✅ Основной |
| Generic Camera + REST sensors | Без разработки | Нет событий, ручная настройка каждой камеры | ⬜ Быстрый старт |
| MQTT discovery | Относительно просто | Ограниченный набор сущностей | ⬜ Дополнение |
Решение: собственная интеграция через HACS с публикацией событий в шину Home Assistant. Дополнительно — поддержка MQTT discovery для тех, кто не хочет ставить HACS.
custom_components/openipc_nvr/
├── __init__.py # настройка, координатор
├── manifest.json
├── config_flow.py # добавление через интерфейс
├── const.py
├── api.py # клиент к REST API NVR
├── camera.py # сущность camera → HLS-поток
├── binary_sensor.py # детекция движения
├── sensor.py # число камер, событий
├── button.py # перезапуск стримера, ребует
├── switch.py # открытие двери
├── event.py # события детекции
└── device_trigger.py # триггеры для автоматизаций
| Платформа | Сущность | Описание |
|---|---|---|
camera |
camera.<имя> |
Поток камеры (HLS) |
binary_sensor |
binary_sensor.<имя>_motion |
Движение за последние N секунд |
sensor |
sensor.nvr_online_cameras |
Камер онлайн |
sensor |
sensor.nvr_events_24h |
Событий за 24 часа |
sensor |
sensor.nvr_disk_usage |
Занято на диске, % |
button |
button.<имя>_restart |
Перезапуск стримера |
switch |
switch.<дверь> |
Открытие двери |
event |
event.<имя>_detection |
Событие детекции |
Этап 1. Базовая интеграция (2 недели)
manifest.jsonс зависимостями- Настройка через интерфейс: адрес сервера, логин, пароль
- Координатор с опросом
/statsи/camerasраз в 30 секунд - Сущности
cameraиbinary_sensorдвижения - Датчики статистики
Этап 2. События в шину HA (1–2 недели)
- Опрос
/eventsс отслеживанием последнего полученного события - Публикация в шину:
hass.bus.async_fire("openipc_nvr_detection", {
"camera_id": event["camera_id"],
"camera_name": camera_name,
"object_class": event["object_class"],
"confidence": event["confidence"],
"timestamp": event["timestamp"],
})- Триггеры для автоматизаций через
device_trigger.py:
automation:
- alias: "Свет при обнаружении человека"
trigger:
- platform: device
device_id: <id камеры>
domain: openipc_nvr
type: detection
event_type: person
action:
- service: light.turn_on
target:
entity_id: light.porchЭтап 3. Управление и публикация (1–2 недели)
- Кнопки перезапуска и перезагрузки
- Открытие дверей СКУД
- Публикация в HACS
- README с примерами автоматизаций
Этап 4. MQTT discovery (опционально, 1 неделя)
Для пользователей без HACS: публикация сущностей по протоколу MQTT discovery. Требует добавления MQTT-клиента в бэкенд.
automation:
- alias: "Уведомление о человеке ночью"
trigger:
- platform: event
event_type: openipc_nvr_detection
event_data:
object_class: person
condition:
- condition: time
after: "23:00:00"
before: "06:00:00"
action:
- service: notify.mobile_app
data:
title: "Движение на участке"
message: >-
{{ trigger.event.data.camera_name }}:
обнаружен {{ trigger.event.data.object_class }}
data:
image: >-
/api/v1/cameras/{{ trigger.event.data.camera_id }}/snapshot- Вебхуки или WebSocket. Сейчас события только опрашиваются через REST. Для мгновенной реакции нужен WebSocket-канал или вебхуки.
- Стабильный идентификатор камеры для привязки сущностей — уже есть (
id). - Смена пароля через API. Сейчас реализована только смена через БД.
Предлагаемая последовательность с учётом зависимостей:
| Приоритет | Задача | Срок | Зависимости |
|---|---|---|---|
| 1 | WebSocket или вебхуки для событий | 1 неделя | — |
| 2 | POST /auth/refresh, смена пароля |
3 дня | — |
| 3 | Регистрация push-токенов и отправка FCM | 1 неделя | — |
| 4 | Запись видеоархива (ffmpeg → MinIO) | 1 неделя | — |
| 5 | GPU-детекция (ONNX + CUDA) | 3–4 недели | — |
| 6 | Интеграция с Home Assistant | 4–5 недель | п. 1 |
| 7 | Мобильное приложение | 6–8 недель | п. 2, 3 |
| 8 | WireGuard-управление туннелями | 2 недели | — |
Задачи 1–4 — фундамент: без них мобильное приложение и интеграция с Home Assistant получатся урезанными.
- GPU-детекция не зависит от остальных задач
- WireGuard-управление не зависит ни от чего
- Документация и тесты — постоянно
- Хранение архива. Записи ведёт сам бэкенд (
recorder_service.go: ffmpeg пишет отрезки, они уходят в MinIO), медиасервер в этом не участвует. Открытый вопрос — как связывать отрезки с метаданными в БД и как чистить старое. - Глубина архива. Политика удаления: по возрасту, по занятому месту или по событиям?
- Резервное копирование. Нужна автоматизация выгрузки архива в внешнее хранилище.
- Масштабирование. При 50+ камерах потребуется разделение медиасерверов и балансировка.
- Распознавание лиц и номеров. Требует отдельных моделей и юридической проработки (персональные данные).