Skip to content

About

Video analytics: uploads & RTSP streams → YOLO detection → Kafka → Postgres → Streamlit dashboard

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

Vision Analytics Platform

CI

Платформа видеоаналитики: загрузка видео / RTSP-стрим → YOLO-детекция → Kafka → трекинг объектов (ByteTrack) → PostgreSQL → REST API → Streamlit-дашборд.

Демо

Живые записи работы дашборда — страница 📹 Видео (обработанный ролик с двойным плеером) и страница 📡 Стримы (поток в реальном времени):

Страница Видео: двойной плеер с кадрами и детекциями

Страница Стримы: последние кадры и детекции стрима

Страница Аналитика: распределение классов и детекции по времени
📊 Аналитика — распределение детекций по классам и по времени.

Стек

Архитектура платформы: API, Kafka, GPU-консьюмеры, PostgreSQL, MinIO
Архитектура системы — подробности в доках (EN).

  • API: FastAPI, Pydantic, SQLAlchemy
  • CV: YOLOv8 на NVIDIA Triton Inference Server (TensorRT, NMS в графе, JPEG-intake с DALI-декодом на GPU), OpenCV
  • Трекинг: ByteTrack (двухстадийная ассоциация IoU поверх Kalman-предсказания)
  • БД: PostgreSQL, Alembic (миграции)
  • Дашборд: Streamlit, Plotly
  • Очереди/события: Kafka (KRaft), confluent-kafka
  • RTSP: MediaMTX
  • Мониторинг: Prometheus, Grafana, kafka-exporter
  • Инфра: Docker Compose, GPU passthrough, pytest

Быстрый старт

Docker

# 1. Создай .env из шаблона (секреты и хосты сервисов)
cp .env.example .env

# 2. Подними все сервисы
docker-compose up -d

Поднимутся 15 сервисов (20 контейнеров с учётом реплик yolo-consumer ×3, detection-consumer ×3 и video-consumer ×2): db, minio, kafka, kafka-init (разовый — создаёт топики), triton, api, dashboard, stream-manager, video-consumer (x2), yolo-consumer (x3), detection-consumer (x3), mediamtx, prometheus, grafana, kafka-exporter.

Docker Desktop: все сервисы стека подняты
kafka-init в сером статусе — это норма: разовый контейнер создал топики и штатно завершился.

🔐 Пароли/ключи задаются переменными в .env (${VAR:-default} в compose). В репозитории только безопасные дев-дефолты — для прода переопредели их в .env.

🌐 S3_PUBLIC_ENDPOINT (дефолт http://localhost:9000) — адрес MinIO для presigned URL, которые открывает браузер (видео-плеер в дашборде). Браузер не резолвит docker-имена, поэтому ссылка должна быть по адресу с проброшенным портом. Внутренние запросы контейнеров идут на S3_ENDPOINT.

💻 GPU нужен одному сервису — triton. Весь инференс платформы живёт в Triton Inference Server (TensorRT engine YOLOv8, NMS вшит в граф). Консьюмеры обращаются к нему по docker-сети (TRITON_URL=triton:8001) и собираются в лёгкий CPU-образ без torch. Без GPU и собранного engine (models/triton/yolo_detector/1/model.plan, сборка — внешняя репа yolo-trt-nms, см. шапку docker-compose.yml) детекций не будет.

Окружение Команда
Linux / macOS / WSL2 make up
Windows .\scripts\up.cmd
Вручную docker compose up -d

Почему .cmd, а не .ps1: PowerShell по умолчанию блокирует неподписанные скрипты (ошибка «не имеет цифровой подписи»). up.cmd обходит это сам. Если хочешь запускать up.ps1 напрямую — разреши локальные скрипты один раз: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned

Доступ

Сервис URL
API http://localhost:8000
Swagger http://localhost:8000/docs
Dashboard http://localhost:8501
Grafana http://localhost:3000 (дашборды в General, anonymous Viewer)
Prometheus http://localhost:9090

Загрузка видео

Через Dashboard (страница 📹 Видео) или через API:

curl -X POST http://localhost:8000/api/videos/upload \
  -F "file=@video.mp4"

Видео обработается автоматически: Kafka (video_requests) → video-consumer → YOLO → PostgreSQL. Длинные видео API режет на временные чанки — их жуют параллельно несколько реплик video-consumer (статус и прогресс — в БД, доставка at-least-once). Готовое видео доступно в дашборде: синхронный двойной плеер — чистый кадр и кадр с боксами бок-о-бок на одном общем таймлайне.

RTSP-стриминг (симуляция камерой)

Шаг 1. Запустить FFmpeg — пушить видео в MediaMTX как RTSP-стрим:

ffmpeg -stream_loop -1 -re -i data/test_v.mp4 -c:v copy -an -f rtsp -rtsp_transport tcp rtsp://localhost:8554/mystream
  • -stream_loop -1 — зациклить видео
  • -re — реальная скорость (не быстрее оригинала)
  • -c:v copy — копировать видео без перекодирования
  • -an — убрать аудио (YOLO работает с картинками)
  • -rtsp_transport tcp — использовать TCP (стабильнее с MediaMTX)

Шаг 2. Добавить стрим через Dashboard (страница 📡 Стримы):

  • Название: Тестовая камера
  • URL: rtsp://mediamtx:8554/mystream (из Docker-сети)
  • FPS: 5

Шаг 3. Нажать ▶️ Запустить — API запишет намерение (desired_status=active), а сервис stream_manager поднимет Frame Producer subprocess'ом.

Пайплайн: Producer → Kafka (raw_frames) → YOLO Consumer → Kafka (detections, ключ — stream_id) → Detection Consumer → трекинг ByteTrack (стабильный track_id через гэпы и окклюзии) → PostgreSQL (stream_detections + tracks).

📋 Галерея детекций стрима: кропы объектов с классом и уверенностью
📋 Галерея детекций на странице Стримы — кропы объектов с классом и уверенностью.

📡 Публичные камеры без MediaMTX. Producer открывает и HLS-ссылки (https://.../playlist.m3u8) напрямую — вставь такой URL веб-камеры прямо в форму стрима. Браузерный User-Agent отправляется автоматически: часть CDN (включая idnes.cz) отдаёт HLS только «браузерным» клиентам. Сетевой сбой HLS producer переживает реконректом на месте, не дожидаясь reconciler'а.

Стримы не работают — чек-лист

Механика: кнопка ▶️ Запустить пишет намерение (desired_status=active) в БД, а reconciler в контейнере stream_manager поднимает producer subprocess'ом и следит, чтобы он оставался живым. Если источник не открылся — процесс умирает, reconciler переподнимает его; после 3 быстрых падений подряд стрим получит статус error с причиной и остановится (защита от spawn-шторма). Чтобы повторить попытку после error — нажми ⏹ Стоп, затем ▶️ Запустить. Повторный «Запустить» на живом стриме вернёт 200 (API не знает и не должен знать про живость процесса).

1. Смотри логи producer'а — владелец процесса теперь stream-manager, туда и пишет producer:

docker compose logs stream-manager --tail 50

Там будет либо причина падения с трейсбеком, либо прогресс «Отправлено кадров: N».

2. Типовая причина №1 — адрес источника не виден из контейнера. Producer работает внутри stream-manager, и localhost внутри контейнера — это сам контейнер, а не твоя машина:

Откуда источник URL для стрима
Встроенный MediaMTX rtsp://mediamtx:8554/mystream
Программа на Windows-хосте (FFmpeg/OBS/Larix) rtsp://host.docker.internal:8554/mystream
IP-камера в LAN rtsp://admin:pass@192.168.1.100:554/stream

3. Типовая причина №2 — в источник никто не публикует поток. MediaMTX — приёмник, он сам видео не генерирует. Проверка с хоста (нужен ffmpeg):

ffmpeg -stream_loop -1 -re -i data/test_v.mp4 -c:v copy -an -f rtsp -rtsp_transport tcp rtsp://localhost:8554/mystream

Мониторинг

Стек observability поднимается вместе со всеми (monitoring/ — конфиги в репо):

Компонент Адрес Что смотреть
Grafana http://localhost:3000 Дашборды в папке General (provisioning)
Prometheus http://localhost:9090 Targets, raw-запросы
/metrics консьюмеров внутри сети :9091/:9092/:9093 api — на своём :8000 (instrumentator)

Дашборды: Pipeline Overview (здоровье всего тракта), Streams Trace (стримы, есть переменная stream_id), Video Track (полная трасса видео-задачи: queue wait → задача целиком → этапы чанка decode/inference/write), Infra (Kafka-лаги, API, процессы).

Как читать: квантили считаются по скользящему окну 15м — при простое графики затухают, это память о последних задачах, а не активность. На «Полной трассе» серые столбцы (правая ось) — число задач в окне: где их мало, квантилю верить нельзя. Лаг video-workers считается брокером и всегда честный.

📡 video-consumer и detection-consumer скрейпятся per-replica (dns_sd type=A): один таргет на round-robin DNS-имя сервиса склеивал счётчики реплик в одну серию, и rate()/квантили рисовали фантомные задачи. Конфиг — monitoring/prometheus/prometheus.yml.

Локально (без Docker)

# Установить зависимости (все extras: инференс + дашборд + dev-инструменты)
pip install -e ".[gpu,dash,dev]"

# Запустить PostgreSQL, Kafka
# Обновить DATABASE_URL, KAFKA_BOOTSTRAP_SERVERS

# Миграции
alembic upgrade head

# API
uvicorn app.main:app --reload

# Dashboard
streamlit run dashboard/app.py

# Kafka Consumers
python -m app.consumers.video             # видео: video_requests → Triton → БД
python -m app.consumers.yolo_frames       # стримы: raw_frames → Triton → detections (адрес — из TRITON_URL)
python -m app.consumers.detections

Тесты и линтер

# Тесты — полностью герметичны и не требуют Docker: sqlite in-memory,
# сеть не трогается (MinIO/Kafka замоканы в conftest),
# тестовое видео генерируется на месте. Весь сьют — несколько секунд.
pytest tests/ -v

# Только стриминг-тесты
pytest tests/test_streaming.py -v

# Линтер (тот же, что в CI)
ruff check app dashboard tests

Pre-commit — автопроверки перед коммитом

Хуки запускают ruff и гигиену файлов (пробелы, конец файла, валидность YAML/TOML, защита от крупных файлов и приватных ключей) на каждый git commit. Установка — один раз:

pip install ".[dev]"
pre-commit install
  • Первый прогон по всему репозиторию: pre-commit run --all-files
  • Если хук автоисправил файлы — просто git add -u и коммить снова
  • Хуки живут в .pre-commit-config.yaml; версии синхронизированы с CI

API Endpoints

Swagger UI: все эндпоинты API

Видео

Method Path Description
POST /api/videos/upload Загрузить видео и запустить обработку
GET /api/videos/params Параметры разбора (частота выборки кадров, лимит)
GET /api/videos Список всех видео
GET /api/videos/{id} Информация о видео (+ video_url для браузера)
GET /api/videos/{id}/status Статус обработки
GET /api/videos/{id}/frame-image?timestamp=N Кадр по времени как JPEG
GET /api/videos/{id}/detections?closest_ts=N Детекции (диапазон / ближайший кадр)
DELETE /api/videos/{id} Удалить видео

Стримы

Method Path Description
POST /api/streams Создать стрим
GET /api/streams Список стримов
DELETE /api/streams/{id} Удалить стрим
POST /api/streams/{id}/start Запустить обработку
POST /api/streams/{id}/stop Остановить обработку
GET /api/streams/{id}/latest-frame Последний кадр стрима (превью)
GET /api/streams/{id}/detections Детекции стрима
GET /api/streams/{id}/tracks Треки стрима (ByteTrack: кто, когда и сколько пробыл)

Аналитика

Method Path Description
GET /api/analytics/stats Общая статистика
GET /api/analytics/class-distribution?source=video|stream Распределение по классам
GET /api/analytics/timeline?source=video Детекции по времени видео
GET /api/analytics/stream-activity?minutes=10 Активность стримов по реальному времени

Docker Compose — сеть

Сервис Адрес внутри Docker Адрес с хоста
PostgreSQL db:5432 localhost:5432
Kafka kafka:29092 localhost:9092
MediaMTX mediamtx:8554 localhost:8554
FastAPI api:8000 localhost:8000
MinIO (S3 API) minio:9000 localhost:9000
Prometheus prometheus:9090 localhost:9090
Grafana grafana:3000 localhost:3000

Kafka Topics

Топик Retention Описание
raw_frames 1 час Кадры от Producer (бинарный конверт: мета + JPEG), 6 партиций, без ключа — ровная раздача
detections 24 часа YOLO-детекции (JSON), 6 партиций, ключ stream_id — стрим целиком достаётся одному detection-консьюмеру (трекер в памяти)
video_requests 24 часа Задачи на обработку загруженных видео (JSON), 8 партиций

Kafka — транспорт, не хранилище. Все детекции сохраняются в PostgreSQL.

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

├── app/
│   ├── api/routes/          # API endpoints (videos, streams, analytics)
│   ├── core/                # Config, database, storage, Kafka topics, metrics
│   ├── db/models.py         # SQLAlchemy models (Video, Stream, Detection, Track)
│   ├── schemas/             # Pydantic schemas
│   ├── processing/          # Обработка видео: processor, triton_detector, extractor
│   ├── streams/             # Стримы: reconciler (manager) + FrameProducer
│   ├── tracking/            # ByteTrack: byte_tracker, kalman, association, registry
│   ├── consumers/           # Kafka-консьюмеры (модуль = контейнер)
│   │   ├── video.py         # video_requests → processor → БД
│   │   ├── yolo_frames.py   # raw_frames → Triton → detections + MinIO
│   │   └── detections.py    # detections → трекинг ByteTrack → PostgreSQL
│   └── main.py              # FastAPI entry point
├── dashboard/
│   └── app.py               # Streamlit dashboard
├── monitoring/
│   ├── prometheus/          # prometheus.yml: таргеты, dns_sd per-replica
│   └── grafana/             # provisioning: datasource + дашборды как код
├── alembic/                 # Database migrations
├── tests/                   # Test suite
├── docker-compose.yml       # 15 services (kafka-init — разовый, triton — GPU)
├── Dockerfile.cpu           # единый образ: api, консьюмеры, dashboard (без torch)
└── pyproject.toml           # зависимости (PEP 621): база + extras triton/dash/dev

Требования

  • Docker + Docker Compose
  • NVIDIA GPU + Docker GPU support — для сервиса triton: он единственный делает инференс, без него детекций нет ни у видео, ни у стримов
  • Собранный TensorRT engine в models/triton/yolo_detector/1/model.plan (сборка — внешняя репа yolo-trt-nms, команда сборки в шапке compose)
  • FFmpeg (для симуляции RTSP с видео-файла)

Образы контейнеров GPU не требуют: torch в проекте больше нет, консьюмеры ходят в Triton по gRPC. Резервирование устройства описано у сервиса triton прямо в базовом docker-compose.yml — override-файлы не нужны.

About

Video analytics: uploads & RTSP streams → YOLO detection → Kafka → Postgres → Streamlit dashboard

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages