Skip to content
Dimitri Montgomery

Atlas 05 of 07

The face engine

The shared recognition library, and the voting rule that decides when a guess counts as knowing.

Shipped — Finished, or finished enough that the write-up is about what it does. Personal · 2026

34
Tests
2
Consumers
01

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.
02

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 sharedWhy
Enrollment storageThe camera keys a face to a door permission; the mirror keys it to a screen profile. Same shape, different meaning
ThresholdsThe camera guards a door at 0.60. The mirror guesses a housemate at 0.45
What a match permitsThat 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.

03

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
04

Built with

Vision
OpenCVYuNetSFace
Shape
PythonProtocol genericsNumPy