Skip to content

Commit 8718322

Browse files
committed
add a standalone container image and document the camera bridge
Home Assistant Container/Core users cannot install apps - asked for in the community thread and unanswered so far. The app Dockerfile is unusable outside the supervisor, so this is a plain image plus a compose file, with HOME pointed into the data volume so the model pack survives updates. docs/camera-bridge.md covers what the README section only summarises: building the bridge, checking beforehand whether it pays off, and why box-less snapshots behave differently.
1 parent 7c71bb5 commit 8718322

4 files changed

Lines changed: 198 additions & 0 deletions

File tree

Dockerfile

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Standalone container — for Home Assistant Container/Core users, who cannot install
2+
# apps, and for anyone who would rather not run this on the host.
3+
#
4+
# docker compose up -d (see docker-compose.yml)
5+
#
6+
# config.yaml and the gallery live in mounted volumes, so both survive image updates.
7+
FROM python:3.12-slim
8+
9+
# InsightFace caches its model pack under $HOME/.insightface — pointing HOME into the
10+
# data volume keeps the ~300 MB out of the image and avoids a re-download after updates.
11+
ENV PYTHONUNBUFFERED=1 \
12+
HOME=/opt/faceid/data/model-cache
13+
14+
RUN apt-get update && apt-get install -y --no-install-recommends \
15+
build-essential \
16+
libglib2.0-0 libgl1 libgomp1 \
17+
&& rm -rf /var/lib/apt/lists/*
18+
19+
WORKDIR /opt/faceid
20+
21+
COPY requirements.txt .
22+
RUN pip install --no-cache-dir --upgrade pip \
23+
&& pip install --no-cache-dir -r requirements.txt \
24+
&& apt-get purge -y build-essential && apt-get autoremove -y
25+
26+
COPY app app
27+
COPY static static
28+
COPY scripts scripts
29+
COPY docs/example-config.yaml docs/example-config.yaml
30+
31+
# Gallery, settings and the downloaded model pack — mount this.
32+
VOLUME ["/opt/faceid/data"]
33+
EXPOSE 8600
34+
35+
HEALTHCHECK --interval=30s --timeout=5s --start-period=180s \
36+
CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8600/api/health',timeout=4).status==200 else 1)"
37+
38+
CMD ["python", "-m", "app.main"]

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,9 @@ through the same pipeline; they carry no bounding box, so their snapshot is the
253253
frame rather than a person crop. Off by default, since it costs one API request per
254254
interval.
255255
256+
Setting up such a bridge, checking beforehand whether it pays off, and the pitfalls of
257+
box-less snapshots: **[docs/camera-bridge.md](docs/camera-bridge.md)**.
258+
256259
257260
## Calibrating the threshold
258261

docker-compose.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# FaceID as a container — for Home Assistant Container/Core, or any Docker host.
2+
#
3+
# 1. cp docs/example-config.yaml config.yaml
4+
# 2. edit config.yaml: Frigate URL, MQTT host + credentials, camera names
5+
# 3. docker compose up -d
6+
#
7+
# First start downloads the InsightFace model pack (~300 MB) into the data volume.
8+
# Then open http://<host>:8600
9+
services:
10+
faceid:
11+
build: .
12+
# Or, once you have built and pushed it yourself:
13+
# image: your-registry/faceid:latest
14+
container_name: faceid
15+
restart: unless-stopped
16+
ports:
17+
- "8600:8600"
18+
volumes:
19+
# Your configuration. Read at startup; the Settings tab writes to data/ instead,
20+
# so this file stays yours.
21+
- ./config.yaml:/opt/faceid/config.yaml:ro
22+
# Gallery, review queue, settings, backups and the model pack. Back this up —
23+
# it is the one part that cannot be recreated.
24+
- faceid-data:/opt/faceid/data
25+
# Recognition is CPU-only and needs AVX; any host CPU from the last decade has it.
26+
# Uncomment to keep FaceID from competing with other services for cores:
27+
# cpus: 2.0
28+
29+
volumes:
30+
faceid-data:

docs/camera-bridge.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Using your camera's own person detection
2+
3+
Most IP cameras run their own person detection on-device. It fires at moments Frigate
4+
sometimes misses — someone approaching from an angle the detector struggles with, or a
5+
brief appearance that never accumulates enough motion. A common trick is to bridge that
6+
signal into Frigate: when the camera reports a person, an automation creates a Frigate
7+
event for it.
8+
9+
FaceID can use those events too, but **not out of the box** — this page explains why,
10+
how to check whether it's worth it for you, and how to set it up.
11+
12+
## Why FaceID ignores them by default
13+
14+
FaceID subscribes to `frigate/events`. Frigate publishes there for **tracked objects**:
15+
things its own detector followed across frames. An event created through
16+
`POST /api/events/<camera>/<label>/create` is not a tracked object — it is an entry
17+
Frigate stores, clips and serves through its API, but never announces over MQTT.
18+
19+
So the bridge works, the events exist, Frigate shows them in Explore — and FaceID never
20+
hears about them. On one installation that was 383 events in two weeks, all at the front
21+
door, every single one unseen.
22+
23+
`poll_interval` closes that gap by additionally asking Frigate's event API what happened.
24+
25+
## Is it worth it for you?
26+
27+
Two things decide that: how many such events you get, and whether they contain faces.
28+
Both are measurable before you change anything.
29+
30+
**How many:**
31+
32+
```bash
33+
curl -s "http://frigate:5000/api/events?limit=500&after=$(($(date +%s) - 604800))" \
34+
| python3 -c "import sys,json,collections; e=json.load(sys.stdin); \
35+
b=[x for x in e if (x.get('data') or {}).get('type')=='api']; \
36+
print(len(b),'bridged events in 7 days'); \
37+
print(collections.Counter(x['camera'] for x in b))"
38+
```
39+
40+
Note the `data.type` — the marker sits inside `data`, not at the top level.
41+
42+
**Whether they hold faces:** they are worth polling only if a face is actually visible.
43+
The events carry a snapshot and usually a clip, so check a sample. On the installation
44+
above, 12 of 20 held a usable face — 10 in the snapshot, 5 via the clip, some in both.
45+
If your camera fires on people walking past at distance, your number will be lower.
46+
47+
## Setting up the bridge (Home Assistant)
48+
49+
Skip this if your bridge already exists. The camera's person sensor is exposed by most
50+
integrations as a `binary_sensor`.
51+
52+
```yaml
53+
# configuration.yaml
54+
rest_command:
55+
frigate_person_event:
56+
url: "http://frigate:5000/api/events/{{ camera }}/person/create"
57+
method: POST
58+
content_type: "application/json"
59+
payload: >-
60+
{"source_type": "api", "sub_label": "Camera detection",
61+
"duration": null, "include_recording": true}
62+
```
63+
64+
`duration: null` leaves the event open so the recording covers the whole approach; end it
65+
explicitly, or set a fixed duration in seconds if you prefer fire-and-forget.
66+
67+
```yaml
68+
# automations.yaml
69+
- alias: "Camera person detection to Frigate"
70+
triggers:
71+
- trigger: state
72+
entity_id: binary_sensor.front_door_person
73+
to: "on"
74+
actions:
75+
- action: rest_command.frigate_person_event
76+
data:
77+
camera: entrance # must match the camera name in Frigate's config
78+
mode: single
79+
```
80+
81+
The `sub_label` is what you will see in Frigate's Explore view. FaceID overwrites it with
82+
the recognised name once it identifies someone.
83+
84+
## Turning on polling
85+
86+
```yaml
87+
faceid:
88+
poll_interval: 30 # seconds; 0 = off (default)
89+
```
90+
91+
In the Home Assistant app the option carries the same name. Restart FaceID; the log then
92+
shows each event it pulled in that MQTT never mentioned:
93+
94+
```
95+
Poll: Ereignis 1785012921.911683-oxfvm1 (entrance) nachgezogen — von MQTT nie gemeldet
96+
```
97+
98+
## What to expect, and what to watch out for
99+
100+
**Only finished events are polled.** Anything still in progress will be announced by MQTT
101+
anyway if it is a tracked object, and a bridged event is worth processing once the
102+
recording exists.
103+
104+
**On start, only the last two minutes are considered.** Polling does not backfill history
105+
— use `python -m app.backfill --days 10` for that, which also covers bridged events.
106+
107+
**Snapshots are full frames, not person crops.** A bridged event has no bounding box, so
108+
Frigate cannot crop to the person. Faces are therefore smaller relative to the image than
109+
you may be used to. If nothing is ever recognised from bridged events, lower
110+
`min_face_px` before blaming the bridge. The clip path (see
111+
[Sharper reference photos](../README.md#sharper-reference-photos)) helps here: on that
112+
same installation, faces went from 51–99 px in the snapshot to 134–231 px in the clip.
113+
114+
**Cost.** One API request per interval, plus normal recognition work per event found.
115+
30 seconds is a good starting point; below 10 you are mostly adding load, since these
116+
events are minutes apart anyway.
117+
118+
**Duplicates are handled.** Events already known from MQTT are skipped, and the poller
119+
remembers the last 500 ids it has seen.
120+
121+
## A note on night-time
122+
123+
Bridged events cluster around the times your camera's own detection outperforms
124+
Frigate's — often dusk and night. If your camera switches to infrared then, those faces
125+
are greyscale, and a gallery built entirely from daylight photos will score them poorly.
126+
`scripts/coverage.py` reports which cameras actually produce greyscale and who lacks a
127+
reference for it. One night-time reference per person is usually enough to fix it.

0 commit comments

Comments
 (0)