Adding AI object detection to my cameras with Frigate
I already had a Hikvision NVR recording my cameras. It records well and understands nothing. If I wanted to know whether a person walked up to the door at 2am, my only option was to scrub footage. What I wanted was for the system to tell me “a person was in the driveway” and skip the rest. That is what Frigate does: real-time object detection on top of the cameras you already have. This is the story of deploying it, including the first attempt that was bad enough to shelve.
What Frigate adds to an NVR that already works
Frigate is a self-hosted NVR built around object detection. It pulls RTSP streams from your cameras, runs each frame through a detector to find people, cars, animals, and so on, and records based on what it sees rather than just on motion. It also integrates with Home Assistant, which opens the door to automations (that is its own follow-up). I did not rip out the Hikvision NVR. Frigate pulls the NVR’s camera streams and adds detection on top, so the NVR keeps doing what it is good at and Frigate does the part it could not.
The first attempt, and why I shelved it
The deploy itself was routine: a Proxmox LXC container, a Docker Compose stack, driven by Ansible, the same as every other service here. I pointed nine cameras at it and let the default CPU detector run.
It was unusable. With nine cameras the CPU sat at 80 to 115 percent, and that was after I had already dropped detection to 2 frames per second and tuned the thread count. A homelab service that pins a node’s CPU at all hours defeats the purpose. So I did something I do not do often enough: I stopped, moved the issue back to the backlog, and walked away from it. A bad deploy you keep limping along is worse than one you shelve until you know how to do it right.
Intel iGPU to the rescue: OpenVINO
What I had been missing was hardware acceleration. Running object detection on a general-purpose CPU is the slow, hot way to do it; the work belongs on silicon built for it. The host had an Intel integrated GPU sitting idle, so I came back with a plan to use it.
Two changes did the work. First, pass the iGPU into the container. In a Proxmox LXC that means handing the render device through to the container:
/dev/dri/renderD128
Second, point Frigate at it. The detector switched from CPU to OpenVINO running on the GPU, and I turned on VAAPI so FFmpeg also decodes the camera streams on the GPU instead of the CPU:
detectors:
ov:
type: openvino
device: GPU
ffmpeg:
hwaccel_args: preset-vaapi
The result was the whole point of the exercise. CPU usage across all nine cameras dropped from 80-115 percent to 25-40 percent, and with that headroom I pushed detection back up from 2 to 5 frames per second. The other common accelerator is a Coral TPU USB stick, and it is a fine option, but if you already have an Intel iGPU doing nothing, passing it through costs nothing and gets you there.
The green-border live view, and go2rtc
With detection working, the live view was broken in a way that is apparently a rite of passage with Frigate: every camera showed a small video feed crammed into the top-left corner over a green background. The recordings were fine, the detection was fine, but the live page looked like a glitch.
The green border is a stream-format problem. The browser cannot play the camera’s native stream cleanly, so it falls back and renders garbage around the edges. Two changes fixed it. First, route the live view through go2rtc, which Frigate bundles, and have it transcode the stream with FFmpeg to browser-friendly H.264 video and AAC audio at a 2 Mbit bitrate. Second, set the detection resolution to a 16:9 shape (640x360) instead of a square, so the aspect ratio the live view uses matches the camera. The trap along the way: go2rtc stream names have to match the camera names exactly, and an invalid live role I had added first just silently did nothing.
Two streams per camera, and where recordings live
Each camera is wired into Frigate twice, and this is the detail that makes the whole thing affordable. The Hikvision cameras expose two RTSP streams: a low-resolution sub-stream and a full-resolution main stream. Frigate uses the sub-stream for detection and the main stream for recording:
inputs:
- path: rtsp://<user>:<pass>@<nvr>:554/Streaming/Channels/202
roles: [detect]
- path: rtsp://<user>:<pass>@<nvr>:554/Streaming/Channels/201
roles: [record]
Running detection on the small sub-stream is far cheaper than running it on a 4K main stream, and recordings still get full quality. It is the single biggest lever on resource usage after hardware acceleration.
Storage splits the same way the work does. Recordings go to the NAS, with 30-day retention that works out to a couple of terabytes across nine cameras, on my QNAP over an SMB mount. The database, though, stays on the container’s local SSD. I had it on the NAS at first and moved it off, because a database doing constant small writes over a network mount is slow. Bulk recordings on the NAS, hot database on local disk.
Three of the nine cameras are Wyze units over WiFi, and they were flaky enough, dropped streams and timestamp errors, that they became their own separate troubleshooting story. The six wired Hikvision cameras through the NVR were rock solid.
Lessons
- Object detection without hardware acceleration does not scale. CPU-only was 80-115 percent for nine cameras; the Intel iGPU with OpenVINO took it to 25-40 percent. If you have an iGPU, pass it through before you buy a Coral.
- A green-border live view is a transcoding problem. The camera is fine; route the live view through go2rtc with FFmpeg transcoding and match the aspect ratio.
- Detect on the sub-stream, record on the main. Cheap detection, full-quality recordings, from the same camera.
- Recordings belong on the NAS; the database does not. Keep the database on local disk so its small constant writes are not crossing a network mount.
- Be willing to shelve a bad deploy. The first attempt pinned a CPU and I moved it back to the backlog rather than live with it. That was the right call, and it came back better.
Related reading
Researching update automation for the homelab
Twenty-three self-hosted services and no update process beyond "when I remember". I compared Watchtower, Diun, Renovate, and WUD, looked at unattended-upgrades for system packages, and landed on a hybrid plan.
Self-hosting file sync with Syncthing, kept local-only
I wanted Dropbox-style file sync across my own devices without putting the files on anyone else's servers, and without announcing my devices to the internet. Here is how I deployed Syncthing on Docker-in-LXC and ran it entirely on my LAN.
Deploying RomM, a self-hosted ROM manager, in the homelab
I wanted a web-based, self-hosted way to organize a pile of game backups with real metadata. RomM was the only option that fit. Here is the Docker-in-LXC deployment, the two gotchas that cost me time, and the NAS mount mistake that bit me later.
Ready to Transform Your Career?
Let's work together to unlock your potential and achieve your professional goals.