Нейросети, умный дом, видеонаблюдение. Выглядит как набор слов, не так ли?
Оказывается, 4 года назад я начинал рассказывать о первых шагах к умному дому. Произошел небольшой перерыв (да, я ленивая жопа!) и я решил - почему бы не продолжить?
А сегодня я расскажу о том, как имея в распоряжении ПК под домашний сервер и несколько wifi камер сделать действительно "умное" видеонаблюдение.
Хотел не просто «запись по движению», а умный видеорегистратор: чтобы определял человека в кадре, отличал кота от коробки, а в идеале ещё и писал текстом, что происходит.
Frigate 0.15 умеет именно это:
Всё крутится локально: никакой утечки видео на чужие сервера и ноль абонплаты.
А вот и "красная машина" =)
Но не без приколов - по мнению детектора, на видео на 83% кот =)
2. Железо и база
Хост: Proxmox 8, в корпусе — AMD Ryzen 5 4600G, 32 Gb RAM, NVIDIA RTX 3050
VM: Ubuntu 22.04, 6 vCPU / 16 GB RAM
GPU passthrough: настроен по гайду на Хабре
Сети: VM и камеры в одной подсети
Камеры: любые с RTSP-потоком (у меня 6 штут Tapo C500)
3. Зачем нужен GPU passthrough?
Отдельно остановлюсь: почему я заморочился с пробросом GPU в VM, а не запустил всё на CPU или, скажем, на TPU (Coral)? Дело в том, что моя задача – тянуть обнаружение объектов в реальном времени на нескольких камерах и параллельно гонять тяжелую модель для описания.
Даже один поток 1080p с современным детектором (YOLOv7) может загрузить CPU на 100%, не говоря уж про генеративную модель с 13 миллиардами параметров (LLaVA 13B) – её на CPU вообще считать не будешь (за минуту никакого описания не дождешься). GPU же позволяет распараллелить эти задачи и выполняет их на порядки быстрее.
Таким образом, GPU passthrough — обязательный шаг, чтобы внутри VM иметь аппаратное ускорение. Он нужен, чтобы Frigate мог использовать CUDA/TensorRT для инференса нейросети, а ffmpeg – аппаратное декодирование/кодирование видео (NVDEC/NVENC). Без GPU моя затея с локальным AI-прислугой просто не взлетела бы.
Настройка прошла относительно спокойно: включил IOMMU в BIOS, в Proxmox добавил устройство hostpci0 (с указанием ID видеокарты), отключил эмулируемую VGA. Ubuntu внутри пришлось снабдить драйверами NVIDIA – после этого карта стала доступна, как если бы она была в обычном PC.
Примечание: в более простых сценариях можно было бы использовать USB-акселератор типа Google Coral TPU – Frigate тоже умеет, но у меня его нет, а вот 3050 просилась в бой. К тому же Coral даёт только обнаружение, а для LLaVA всё равно нужен бы был GPU или очень мощный CPU. Так что выбор очевиден.
4. Docker Compose: поднимаем Frigate и Ollama
Система в VM готова, теперь разворачиваем два основных сервиса в контейнерах: Frigate NVR и Ollama. Оба будут запускаться через Docker Compose — это удобно, чтобы они стартовали вместе и были связаны в одну сеть.
Я установил Docker Engine и docker-compose в Ubuntu (для опытных пользователей это тривиально: несколько команд, либо скрипт установки Docker от официального репозитория).
Предполагаю, что читатель уже умеет ставить Docker и Compose, поэтому опущу подробности apt-установки.
Перейдем сразу к Compose-файлу. Вот финальная версия моего docker-compose.yml (@SupportTech, когда будут уже нормальные код-сниппеты?!):
version: "3.9"
services:
frigate:
container_name: frigate
privileged: true # ← проще дать контейнеру весь /dev, чем перечислять /dev/nvidia*
restart: unless-stopped
image: blakeblackshear/frigate:0.15.0-tensorrt # ← нужная сборка с TensorRT под NVIDIA
deploy:
resources:
reservations:
devices: # ← “новый” способ подключить GPU (Compose v3 + nvidia-container-runtime)
- driver: nvidia
count: 1 # ← берём единственную RTX 3050
capabilities: [gpu]
shm_size: "2048mb" # ← увеличиваем /dev/shm, иначе Frigate будет ворчать
devices:
- /dev/dri/renderD128:/dev/dri/renderD128 # ← останется, если в хосте ещё есть iGPU; иначе убрать
volumes:
- /etc/localtime:/etc/localtime:ro
- ./config:/config
- ./storage:/media/frigate
- type: tmpfs
target: /tmp/cache # ← храним временные JPEG/embeddings в RAM → экономим SSD
tmpfs:
size: 1000000000 # 1 GB хватит 4–6 камерам 1080p
ports:
- "8971:8971" # ← новый веб-UI Frigate 0.15
- "5000:5000" # ← REST API + старый UI (по желанию)
- "8554:8554" # ← RTSP restream от go2rtc
- "8555:8555/tcp" # ← WebRTC сигнализация
- "8555:8555/udp" # ← WebRTC медиа-канал
environment:
FRIGATE_RTSP_PASSWORD: "PASSWORD" # ← чтобы не писать пароль в каждом URL
YOLO_MODELS: "yolov7-640" # ← Frigate подтянет и закэширует модель .onnx
ollama:
container_name: ollama
restart: unless-stopped
image: ollama/ollama
healthcheck:
test: ollama --version || exit 1 # ← простой чек, что CLI отвечает
entrypoint: /root/entrypoint.sh # ← твой скрипт автоподтяжки моделей
volumes:
- ./ollama/volume:/root/.ollama # ← сюда Ollama кладёт модели (сохранятся между рестартами)
- ./ollama/entrypoint.sh/root/entrypoint.sh
- /etc/localtime:/etc/localtime:ro
ports:
- "11434:11434" # ← API Ollama, Frigate будет стучаться сюда
environment:
OLLAMA_NUM_PARALLEL: "1" # ← не даём LLaVA жрать VRAM параллельными запросами
OLLAMA_MAX_QUEUE: "256"
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
На что тут стоит обратить внимание:
Образ Frigate:
Привилегированный режим:
shm_size: 2048mb:
увеличиваем раздел общей памяти. Frigate хранит там кадры для обнаружения и прочие буферы. По умолчанию Docker даёт 64Мб, но с несколькими камерами в высоком разрешении этого мало. В 0.15 Frigate сам ругается, если /dev/shm маловат. Я поставил 2048 MB – у меня 6 камер, этого достаточно, при необходимости можно больше. Главное, чтобы не было переполнения, иначе Frigate может упасть или терять кадры.
Tmpfs на /tmp/cache:
важный трюк. Frigate использует /tmp/cache для разных временных файлов (например, для отслеживания объектов, кадры для распознавания и т.п.). Монтируя туда tmpfs, мы удерживаем эти операции в RAM, что ускоряет работу и снижает износ диска. Рекомендация из документации, и действительно, нагрузка на диск резко упала после этого (SSD сказал спасибо). Я выделил 1 ГБ; в зависимости от числа камер и их разрешения можно увеличить или уменьшить.
Порты:
8971 – новый веб UI Frigate. Начиная с 0.15, интерфейс доступен только на защищенном (с авторизацией) порту.
8554 – RTSP restream (от встроенного сервиса go2rtc). Через него я могу при желании подключаться VLC/телефоном к камерам через Frigate, не нагружая сами камеры множеством прямых коннектов. Удобно, хотя я пользуюсь в основном веб-интерфейсом.
8555 (tcp и udp) – это порты для WebRTC. Frigate через go2rtc умеет раздавать видеопоток прямо в браузер по WebRTC, минуя RTSP. Это быстрее и экономит трафик, и обходится без плагинов.
Переменные среды:
FRIGATE_RTSP_PASSWORD – я назначил пароль для RTSP, и в конфиге камер могу использовать шаблон ${FRIGATE_RTSP_PASSWORD}. Чтобы случайно не засветить пароли, удобнее вынести их в env. (Frigate поддерживает подстановку env-переменных в config.yml).
YOLO_MODELS: "yolov7-640" – переменная окружения для контейнеров Frigate, предназначенная исключительно для автоматической загрузки (и/или перекомпиляции) моделей YOLO при первом старте.
Ollama:
Использую официальный образ ollama/ollama.
Порт 11434: это дефолтный порт REST API Ollama. Я его открыл наружу, хотя можно было не делать этого, если обращаться к модели только из Frigate. Frigate, работая в той же докер-сети, может достучаться до ollama:11434 без проброса на хост. Но мне для отладки хотелось иногда самому попробовать запросы к Ollama, поэтому порт пробросил.
Volume ollama-data на /root/.ollama: здесь хранятся модели, которые скачивает Ollama. Обязательно делаем volume, иначе при пересоздании контейнера вам придётся опять тянуть десятки гигабайт моделей 😅. С volume скачанные модели останутся. (Модели, кстати, он скачивает из интернета при первом запросе, имейте в виду).
Запустив docker-compose up -d, я проверил:
Frigate успешно стартовал, в логах увидел, что он нашёл GPU (строка [INFO] Nvidia decoder initialized и что-то про TensorRT). Если бы он не увидел GPU, он либо упал бы с ошибкой, либо работал на CPU, что нам не подходит.
Ollama запустился и слушает на 11434 – это можно проверить docker logs ollama или открыв http://<IP_ВМ>:11434 (он должен вернуть приветственное сообщение типа Ollama vX.X API).
Оба сервиса работают, но пока Frigate “пустой” – без конфигурации, а Ollama не знает, какую модель мы от него хотим. Пора заполнить конфиг Frigate и подготовить нейросети.
Пример описания события нейросетью
5. Настройка Frigate (config.yml)
Конфигурационный файл config.yml задаёт параметры Frigate: какие камеры подключать, как выполнять детекцию и пр. Рассмотрим минимальный рабочий конфиг под нашу задачу – одна камера с детекцией на GPU (YOLOv7-640):
model:
path: /config/model_cache/tensorrt/yolov7-640.trt # ← готовый движок TensorRT
input_tensor: nchw
input_pixel_format: rgb
width: 640
height: 640
semantic_search:
enabled: true # ← CLIP-embedding для «поиска по картинке/тексту»
model_size: large # large требует +VRAM; можно small, если упираемся в 8 ГБ
reindex: false # true = прогонит всё старое видео (долго)
genai:
enabled: true
provider: ollama
base_url: http://ollama:11434 # ← имя сервиса внутри docker-сети
model: llava # ← в entrypoint подтяни llava 13b или 7b
prompt: >-
Analyze the {label} in these images from the {camera} security camera...
object_prompts: # ← кастомные подсказки на каждый класс
person: Examine the main person in these images...
car: Observe the primary vehicle in these images...
dog: Describe the animal that you see on these images...
# и т. д. для cat, motorcycle, bicycle
detectors:
tensorrt: # ← имя детектора (ссылаемся на него ниже)
type: tensorrt
device: 0 # ← индекс GPU (у нас единственный)
detect:
enabled: true
width: 1920 # ← фактическое разрешение DETECT-потока!
height: 1080
stationary:
interval: 50
threshold: 50
motion:
threshold: 40
contour_area: 25
improve_contrast: true
lightning_threshold: 0.8
birdseye:
enabled: true
mode: motion # ← режим «мозаика» по событиям движения
ffmpeg:
hwaccel_args: preset-nvidia-h264 # ← NVDEC + zero-copy → быстро и без CPU
output_args:
record: -f segment -segment_time 10 -segment_format mp4 -reset_timestamps 1 -strftime 1 -c:v copy -c:a aac
record:
enabled: true
retain:
days: 30
mode: motion
sync_recordings: true
alerts:
retain:
days: 90
detections:
retain:
days: 90
objects:
track:
- person
- car
- motorcycle
- bird
- cat
- dog
go2rtc:
streams:
Backyard:
- rtsp://login:password@192.168.99.37:554/stream1
- ffmpeg:Backyard#video=h264#hardware
cameras:
Backyard:
enabled: true
ffmpeg:
inputs:
- path: rtsp://tapocamera:tapocamera@192.168.99.37:554/stream1
input_args: preset-rtsp-generic
roles: [detect]
motion:
mask: # ← полигоны, где движение игнорируем
- 0.002,0.042,0.32,0.045,0.321,0.004,0.001,0.002
# … (остальные координаты урезал для краткости)
detectors – указываем, что используем детектор типа tensorrt (Nvidia TensorRT) под именем nvidia. Параметр device: 0 означает использовать первый GPU (если у вас несколько видеокарт, можно указать соответствующий индекс). Frigate автоматически подхватит CUDA/TensorRT, если контейнер запущен правильно.
model – путь и параметры модели. Мы нацелились на YOLOv7-640, поэтому путь указывает на файл yolov7-640.trt (который сгенерируется в директории /config/model_cache/tensorrt внутри контейнера). Параметры width/height должны совпадать с размером сети (640x640). Остальные настройки (input_tensor, pixel_format) стандартны для YOLO моделей (формат входного тензора nchw и rgb). Если вы решите использовать другую модель (например, yolov7-tiny-416), не забудьте здесь поменять пути и размеры.
cameras – секция с описанием камер. В примере добавлена одна камера cam1.
ffmpeg.inputs – список потоков для этой камеры. Указан RTSP URL основного потока (stream1). У большинства IP-камер RTSP-поток формата rtsp://<user>:<password>@<ip>:554/<название_потока>. В некоторых камерах основной поток – H.264, в других – H.265. В нашем примере указан пресет preset-nvidia-h264 для аппаратного декодирования H.264 на GPU Nvidia. Если ваша камера выдает H.265, замените на preset-nvidia-h265 (RTX 3050 аппаратно поддерживает и тот, и другой кодек). Роли detect и record означают, что мы используем один и тот же поток и для детекции объектов, и для записи. (При наличии у камеры второго, субпотока низкого разрешения, можно отправлять его на детектор, а основной – только на запись. Для простоты здесь оба роли на одном потоке.)
detect – параметры детекции для этой камеры. Важно указать реальное разрешение видеопотока (width и height), чтобы Frigate знал, какого размера кадры ожидать. Параметр fps ограничивает скорость анализа – 5 кадров/с обычно достаточно для уверенного определения движения и объектов, не тратя лишние ресурсы. Если поставить слишком высокий fps, будете зря грузить и GPU, и CPU (обработка видео).
record – включает запись видео. В данном случае настроена непрерывная запись только при движении (так работает Frigate по умолчанию при включенном режиме recordings с events): при обнаружении события он сохранит сегмент видео, начиная за pre_capture секунд до события и заканчивая через post_capture секунд после. Мы сохраняем записи 7 дней (параметр retain.days). Длительность и поведение записей можно гибко менять, но выходят за рамки данной статьи.
snapshots – включает сохранение снимков с обнаруженными объектами. Frigate сделает и сохранит кадр с пометкой (рамкой) каждого события. Также храним 7 дней для примера.
После прописывания конфигурации, сохраняем config.yml и перезапускаем контейнер Frigate (docker compose restart frigate). Зайдите в веб-интерфейс (порт 5000) – там должна появиться ваша камера. Если всё сделано правильно, поток видео будет идти плавно, а при появлении в кадре людей, машин или других объектов – они выделятся рамками с подписью класса и процентом уверенности. Также в разделе Events начнут появляться записи с событиями движения.
💡 Примечание: По умолчанию Frigate отслеживает только объект person (человек). Чтобы детектировать другие типы (авто, питомцев и пр.), нужно явно добавить их в настройках. В нашем примере мы сразу использовали модель, обученную на 80 объектах COCO, поэтому рекомендуется дополнительно указать в конфиге список объектов, которые вам интересны, например:
objects:
track:
- person
- car
- cat
- dog
Иначе Frigate может игнорировать всё, кроме людей.
6. Первые грабли: ошибки и уроки
Как ни старайся, а без косяков настройка не обходится. Расскажу о своих “граблях” – возможно, сберегу чьи-то нервы:
Грабли №1:
Забытый драйвер NVIDIA. Я так увлёкся пробросом GPU, что при первом запуске контейнеров увидел в логах Frigate: "no CUDA capable device found". Оказалось, внутри Ubuntu я не доустановил драйвер (думал, в образе Frigate уже всё есть – но образ-то видит только /dev/nvidia*, а драйвер – это модуль ядра!). Пришлось экстренно ставить apt install nvidia-headless-525 и перезапускать VM. После этого nvidia-smi появился, и Frigate успешно подхватил GPU. Вывод: не забываем про драйвер в гостевой ОС.
Грабли №2:
Неправильный образ Frigate. Сначала по привычке потянул blakeblackshear/frigate:stable (обычный). Он, конечно, запустился, но тут же завалил CPU – ведь модель детекции работала на процессоре. Я-то ждал, что GPU поможет, ан нет – нужно же tensorrt версия! Ошибка быстро нашлась, контейнер переключил на stable-tensorrt, но потерял час на скачивание нового образа (~6 ГБ). Вывод: используйте правильный тег образа для вашего ускорителя.
Грабли №3:
Мало памяти. Я сперва дал VM только 4 ГБ RAM, думал “для Linux хватит”. И правда, Frigate себе брал ~500 МБ, ffmpeg чуть, да система ~1Гб – норм. Но стоило включить semantic_search, как память улетела в своп. Модель CLIP (даже small) + база данных + кэш – жрали около 6 ГБ. Добавил до 12 ГБ – стало лучше, но при старте LLaVA всё равно подпёрло. В итоге 16 ГБ — впритык, но хватает (пиково видел 12 ГБ usage при описании сразу нескольких кадров). Вывод: не жалейте RAM, лучше с запасом.
Если у вас 8 ГБ или меньше – или не включайте эти фичи, или готовьтесь к тормозам.
Грабли №4:
Долгая генерация описаний. Первые попытки с LLaVA 13B иногда не давали результата: событие проходит, а описания нет. Оказалось, таймаут 60 сек и конкурентные запросы. Если, например, 3 события одновременно, Frigate кидает 3 запроса, а Ollama (по умолчанию) обрабатывает их последовательно. В результате последний может не уложиться в минуту и Frigate его бросит. Решение простое: я ограничил частоту детекций (FPS 5, плюс задержка между детектами по настройкам Frigate), и в реальной жизни редко более 1 объекта сразу появляется. Так что проблема минимизировалась.
Вывод: не перегружайте GenAI запросами, это не real-time штука.
Конечно, были еще мелочи, но перечислил основные, над которыми повозился. К счастью, сообщество Frigate активное: многие вопросы находил на Github Discussions и Reddit. Стоило погуглить ошибку – почти всегда кто-то уже спрашивал, и либо автор (Blake Blackshear), либо другие пользователи помогали с советом.
Краткий чек-лист по настройке для Лиги Лени
Ниже я свёл основные шаги и настройки в короткий список. Если решитесь повторить подобное у себя, пройдитесь по чек-листу – всё ли учтено:
Proxmox и железо: убедиться, что включен IOMMU/VT-d в BIOS. Прописать в /etc/default/grub нужные опции (intel_iommu=on или amd_iommu=on). Добавить видеокарту в vfio (в Proxmox /etc/modprobe.d/blacklist.conf добавить blacklist nouveau и blacklist nvidia, а в /etc/modules загрузить модули vfio). Перезапустить хост.
Создание VM: UEFI BIOS (OVMF), Machine type q35, vga: none, добавить устройство PCIe (GPU и связанный с ним аудиоконтроллер). Дать достаточное RAM (не меньше 8 ГБ, лучше 16). CPU type host, cores – по потребности. Диск – побольше под видео или подмонтировать сетевой/NAS.
Установка гостевой ОС: Поставить Ubuntu 22.04 (или Debian 12) внутри VM. Обновить систему. Установить NVIDIA драйвер (рекомендуемый проприетарный). Проверить nvidia-smi.
Установка Docker: Поставить Docker engine и docker-compose plugin. Настроить nvidia-container-toolkit (sudo apt install nvidia-container-toolkit && sudo nvidia-ctk runtime configure && sudo systemctl restart docker).
Подготовка Docker Compose: Создать docker-compose.yml с двумя сервисами – frigate и ollama (см. пример выше). Убедиться, что указан правильный image для Frigate (stable-tensorrt) и runtime: nvidia для обоих. Настроить volume и порты. Добавить tmpfs и shm.
Конфиг Frigate: Написать config.yml. Обязательные секции: detectors (указать type: tensorrt), model (путь к файлу .trt модели и размеры), cameras (вставить RTSP ссылки, роли, параметры детектора, объекты, маски и т.д. как нужно). При необходимости – mqtt (если использовать MQTT), detect общие настройки (например, максимальное количество одновременно отслеживаемых объектов, я не менял, по дефолту ок).
Запуск Compose: docker-compose up -d. Проверить логи: docker logs frigate -f – должны появиться сообщения о старте, подключении камер, инициализации детектора без ошибок. docker logs ollama -f – убедиться, что API запущен (может висеть в ожидании запросов).
Первый тест: Зайти в веб-интерфейс Frigate – http://<IP_VM>:8971. Создать пользователя/пароль (первый запуск). Увидеть камеры (должны отображаться превью потоков). Помахать рукой в зону видимости – убедиться, что событие фиксируется, появляется bounding box "person" на видео.
Проверка AI-описаний: Открыть вкладку Explore в UI Frigate. Найти последнее событие с объектом, посмотреть – появляется ли под ним текстовое описание. Если нет – проверить docker logs frigate на наличие ошибок GenAI (возможно, проблема с подключением к Ollama).
Оптимизация: Поправить пороги, маски, fps, если слишком много/мало срабатываний. Оценить загрузку: nvidia-smi – посмотреть, сколько памяти занято, нагрузка (Decoder, Encoder, Compute). docker stats – нет ли контейнеров с зашкаливающим CPU (в идеале CPU низкий, GPU берёт нагрузку).
Оповещения: Настроить желаемый способ уведомлений – MQTT, Home Assistant, Telegram-бот или просто email. Для Telegram: создать бот через BotFather, получить токен, написать скрипт (либо использовать готовые интеграции, например, Home Assistant Notify).
Резерв: Настроить автозапуск Compose при перезагрузке (можно через restart: unless-stopped уже сделано, но сам Docker должен стартовать; на Ubuntu это по умолчанию). Резервное копирование конфигов и, возможно, базы Frigate (файл frigate.db – содержит события и embeddings).
Мониторинг: Желательно настроить мониторинг дискового пространства (чтобы видеоархив не переполнил диск). Frigate умеет удалять по retain настройкам, но на всякий случай.
Обновление: Следить за апдейтами Frigate. Например, подписаться на релизы в GitHub или форум. Обновлять образ и конфиг по необходимости.
Фух, внушительный список. Но когда делаешь по шагам, всё не так страшно.
Выводы
Проект удался: я получил умную систему видеонаблюдения, которая работает полностью локально и не уступает во многом облачным аналогам. Frigate 0.15 приятно удивил функционалом: поиск по описанию действительно находит нужные кадры, и больше не нужно пролистывать часы записи в надежде увидеть, когда приходил почтальон. Достаточно вбить пару слов.
Конечно, за всё платим ресурсами: RTX 3050 не скучает – где-то 50–60% её вычислительной мощи постоянно в деле (детекция + AI). Электричество тоже тратится, но я посчитал: в простой мой сервер потребляет ~50 Вт, под нагрузкой ~120 Вт. За возможности, которые я получил, меня это устраивает.
Зато никакой абонентской платы сервисам и никаких проблем с приватностью. Все видеоданные остаются дома, в зашифрованном хранилище. Да и просто это было весело – настроить кучу технологий воедино: Docker, AI, FFmpeg, MQTT, etc.
Что дальше? Буду играться с более продвинутыми моделями (может попробую YOLO-NAS – хвалят за точность). Также подумываю прикрутить распознавание лиц (есть же локальные модели типа InsightFace), но это уже другая история. Frigate пока распознаёт только тип объектов, а не кто именно. Но с помощью дополнений и Home Assistant можно и это реализовать, если очень надо.
Буду благодарен за ваше участие, и планирую писать ещё =)
Например, в следующей статье могу рассказать об автоматическом управлении котлом отопления через Home Assistant и Node-Red.
Т.к. на пикабу до сих пор не прикрутили нормальных код-сниппетов, прикрепляю репозиторий c конфигами из статьи.
Будут вопросы - задавайте, отвечу по мере возможности =)