Atlas 05 of 07
The face engine
The shared recognition library, and the voting rule that decides when a guess counts as knowing.
- 34
- Tests
- 2
- Consumers
What it does
The face-recognition engine shared by the camera and the mirror — a library both install independently, holding the arithmetic that would be dangerous to have two drifting copies of, and nothing else.
- Detects and embeds
- YuNet for detection, SFace for embeddings, wrapped so neither consumer writes 250 lines of OpenCV nuance twice.
- Matches by cosine similarity
- Against a contiguous reference matrix rather than a loop. Generic over anything with an id, a name and embeddings, checked structurally — so two unrelated types satisfy it without a shared base class.
- Decides when a guess counts as knowing
- A strict majority of *all* observations, with frames that recognised nobody counting against. Ten blanks plus one confident hit is a stranger, and the plurality rule that most implementations reach for answers it with a name.
How it works
A library, not a service
Two modules recognise faces: the camera at a door, and the mirror in a hallway. They share the arithmetic and share nothing else.
What is deliberately not in here
| Not shared | Why |
|---|---|
| Enrollment storage | The camera keys a face to a door permission; the mirror keys it to a screen profile. Same shape, different meaning |
| Thresholds | The camera guards a door at 0.60. The mirror guesses a housemate at 0.45 |
| What a match permits | That is policy, and policy belongs to the service |
The separate databases are the important one. A shared enrollment store sounds obviously correct — one place to add a face — and it means the mirror stops recognising anyone whenever the camera’s Pi is off. Two copies of a small table is a much smaller problem than a hallway display that goes blank because something in a doorway rebooted.
The voting rule
A single frame is a guess. Recognition runs over a window of them and answers only when a strict majority of all observations agree — frames that matched nobody included in the denominator.
- The obvious thing
- Why it fails
- Instead
The alternative is to count only frames that produced a match and take the most common name. It is simpler, it is what a first implementation does, and it converts “one lucky frame out of eleven” into a confident identification. On a door, that is the difference between a security system and a lottery.
The thresholds differ by consumer because the consequences differ. A wrong guess on the mirror shows the wrong to-do list. A wrong guess at the door opens it — which is also why the door lock stays disabled entirely until a separate anti-spoofing decision is made.
Testing
34 passing · ruff and mypy --strict clean
A small library with an unusually high stake per line: the same arithmetic decides whether a door opens and whether a screen shows the right person's calendar. The tests are mostly about the boundaries — the cases where the answer should be "I do not know".
What the tests defend
- The load-bearing test is that ten frames recognising nobody plus one confident hit resolves to *nobody*. That is a stranger who briefly resembled someone, and a plurality rule — the obvious implementation — answers it with a name. The rule is a strict majority of **all** observations, with non-matches counting against.
- Exactly half is tested separately, and separately is the point: four matches against four blanks is not knowledge, and `>=` instead of `>` is a one-character edit that no other test in the suite would notice.
- The matcher is generic over anything with `person_id`, `name`, and `embeddings`, checked structurally. The test that matters there asserts two unrelated types satisfy it without a shared base class — which is what lets the camera and the mirror share the arithmetic while keeping separate enrollment databases.
- Cosine matching is tested against a contiguous reference matrix rather than a loop, because the loop version was correct and slow and the vectorised version is the one that ships. An optimisation with no test is a rewrite with no safety net.
Built with
- Vision
- OpenCVYuNetSFace
- Shape
- PythonProtocol genericsNumPy