Skip to main content

AlchemyFace

PyPI CI Ruff Python 3.10+

Face recognition built on YuNet and SFacea typed Python library and a desktop application for building face databases.

pip install alchemyface
alchemyface db          # the Face DB Builder

Why

Most Python face-recognition packages pull in dlib, PyTorch or TensorFlow. AlchemyFace uses two small ONNX models through OpenCV's own DNN runtime, so the recognition stack itself is small: the wheel is about 30 KB, and the weights are 37 MB fetched once on first use.

Being straight about the total: the dependencies are not small. OpenCV, NumPy and Pillow come to roughly 160 MB installed — most of it OpenCV. That is well under a PyTorch or TensorFlow install, but it is not "a few megabytes", as this paragraph previously claimed.


The library

import cv2
from alchemyface import Recognizer

r = Recognizer()                       # weights download once, then cached

r.enroll("prashant", cv2.imread("me.jpg"))
r.enroll("alice",    cv2.imread("alice.jpg"))

for recognition in r.identify(cv2.imread("group.jpg")):
    face, match = recognition.face, recognition.match
    if match:
        print(f"{match.label} at {face.bbox} ({match.score:.2f})")
    else:
        print(f"unknown face at {face.bbox}")

identify returns one Recognition per detected face. match is None when nothing clears the threshold — the library never invents a label.

Galleries

Recognizer is a facade over three protocols — Detector, Embedder and FaceStore — so any conforming object can be substituted.

Store
InMemoryStore numpy matrix, unit vectors, .npz save/load. The default.
PickleStore the Unitree G1 robot's list[(id, name, group, vector)] pickle. Stores vectors verbatim.
from alchemyface.store import PickleStore

store = PickleStore()
store.load("face_db.pkl")
print(len(store), store.dim)
for entry in store.entries():
    print(entry.label, entry.group, entry.vector.shape)

Raw versus unit embeddings

SFaceEmbedder returns unit-length vectors by default, because the Embedder protocol promises it and the rest of the library relies on it. Pass normalize=False for SFace's raw output, whose L2 norm is around 10:

from alchemyface.embedding import SFaceEmbedder

SFaceEmbedder().embed(image, face)                    # L2 == 1
SFaceEmbedder(normalize=False).embed(image, face)     # L2 ≈ 10, raw

Cosine similarity is scale-invariant, so matching is identical either way. What differs is what lands on disk: the robot's schema stores raw values, and keeping them means a database written here stays comparable with one written by anything else, and the L2 norm column remains a useful diagnostic rather than reading 1.0000 for every entry.

Live video

from alchemyface import Recognizer
from alchemyface.capture import VideoSource

r = Recognizer()
with VideoSource(0, width=1280, height=720) as camera:
    for frame in camera.frames():
        for recognition in r.identify(frame):
            print(recognition.match.label if recognition.match else "unknown")

The application

alchemyface db

A Tkinter desktop app that turns folders of photos into a .pkl face database.

Build DB

Three panes: image sidebar, the current image with numbered face boxes, and one panel per face.

  1. Choose an input folder and click Open. Every image is detected in the background, the one on screen first, so the sidebar fills in as you work.
  2. Each detected face becomes a numbered box on the canvas and a row on the right — thumbnail, Include, Name, Group. Names default to the filename, or <stem>_faceN when an image holds several.
  3. Untick Include to drop a face; its box turns dashed.
  4. Click a box to select that face. Re-detect runs YuNet again, asking first if you have unsaved edits. The Detection score spinbox (0.10–0.99, default 0.9 — YuNet's own) applies to the live detector immediately; lower finds more faces, higher is stricter.
  5. Save .pkl writes every included, named face, computing any embedding not already cached and renumbering ids from "0".

Sidebar glyphs: · pending · detection failed · no face · ○ (0/N) nothing included · ✓ (k/N) k of N included.

A failure and an empty result are shown differently on purpose. Reporting a broken model or an unreadable file as "no face detected" makes it look like a finding about the photograph — which once put a false claim in this project's own roadmap.

Inspect DB

Read-only viewer for any database: # · ID · Name · Group · Dim · L2 norm · first values, with a summary line of counts, dimension and file size. Reads the four-tuple list form and the back-compatible {name: vector} dict.

Edit DB

Open an existing database and change it.

  • Load any .pkl; the table shows # · Name · Group · Dim · L2 norm · first values. The frame title gains * while there are unsaved changes, and closing the window asks before discarding them.
  • Remove selected — or the Delete key — drops the chosen rows.
  • Double-click a Group cell to edit it inline from a combobox of presets. Anything you type is added to the presets.
  • Add faces from a folder or a single image. Each detected face becomes a candidate card — thumbnail, Include, Name, Group — and nothing changes until you press Add checked. Duplicate names are allowed: the robot resolves by best cosine similarity, so a second photo of someone is an improvement.
  • Save writes over the loaded path; Save as… writes elsewhere.

Resize is planned — see versions.md.

The .pkl schema

[
    ("0", "Alice", "staff",   np.ndarray(shape=(128,), dtype=float32)),
    ("1", "Bob",   "visitor", np.ndarray(shape=(128,), dtype=float32)),
]

Reading is deliberately forgiving. Real databases disagree with this documentation — id is sometimes an int, and the vector sometimes (1, 128) — so both are coerced. A stricter reader would refuse a database that works today.


Requirements

opencv-python-headless, numpy, typer, Pillow. Python 3.10 or newer.

The GUI needs tkinter, but the library does not. Nothing in the library imports it, so import alchemyface works on a server, in Docker, or in CI with no Tk installed — enforced by tests, not hoped for. alchemyface db reports what to install rather than raising:

$ alchemyface db
the desktop application needs tkinter, which is not available: No module named '_tkinter'
tkinter ships with Python but is packaged separately on some systems.
  Debian/Ubuntu:  sudo apt-get install python3-tk
  Fedora:         sudo dnf install python3-tkinter
  macOS/Windows:  reinstall Python from python.org
$ echo $?
3

The presentation helpers are Tk-free too, so you can render a database in a web app or a notebook:

from alchemyface.gui.inspect_data import entry_rows, summarise

OpenCV is the headless build, so there is no libGL requirement either.

Model weights

Resolved in this order, first hit wins:

  1. model_dir= passed to Recognizer
  2. $ALCHEMYFACE_MODEL_DIR
  3. ~/.cache/alchemyface/models/
  4. downloaded from the OpenCV Zoo and SHA256-verified

alchemyface download-models pre-fetches. Set ALCHEMYFACE_MODEL_DIR to work offline.

The recognition threshold

The library defaults to cosine 0.363, SFace's published operating point. The G1 robot matches at 0.32. It is a tunable, not a constant — validate it against your own data.

Development

pyenv install 3.10.6
pyenv virtualenv 3.10.6 alchemyface     # .python-version activates it here
pip install -e ".[dev]"
Command
pytest tests/ -m "not models and not camera and not gui" the fast suite — no display, no models, no network
pytest tests/ -m "gui" needs a display; xvfb-run -a on a headless box
pytest tests/ -m "not camera" everything except the camera
ruff check src tests · ruff format src tests lint and format
mypy src/alchemyface type check
python -m build wheel and sdist

There is deliberately no coverage badge. A hand-written percentage goes stale silently, and generating a real one needs either a third-party service or a bot committing to main — which the repository's commit-identity check refuses. The CI badge already means the gate passed, and that gate includes a coverage floor.

The PyPI badge is static and version-pinned, not the usual dynamic shields.io/pypi/v one. That badge proved unreliable: PyPI's JSON API reports a stale info.version for some time after an upload, shields.io caches whatever it read for three hours (max-age=10800), and GitHub's image proxy then caches shields. The three compound, and the badge showed a version four releases behind. A pinned badge cannot be stale — and CI fails the build if it ever disagrees with pyproject.toml, so it cannot be forgotten either.

Model-backed tests skip unless the weights are present:

export ALCHEMYFACE_MODEL_DIR="$PWD/_local/onnx"

A note on data

_local/ is git-ignored and must stay that way. It holds face embeddings, recordings and photographs of real, identifiable people, carried over from the prototype this grew out of. Under Japan's APPI and GDPR Article 9 those are sensitive personal data. They are development fixtures: excluded from the wheel, the sdist and version control, and a CI step fails the build if any of them ever reach a distribution.

Links

Licence

MIT — see LICENSE.

Model weights are distributed by the OpenCV Zoo under their own terms — YuNet MIT, SFace Apache-2.0 — and are downloaded at runtime rather than redistributed here.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

alchemyface-0.6.0.tar.gz (60.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

alchemyface-0.6.0-py3-none-any.whl (62.6 kB view details)

Uploaded Python 3

File details

Details for the file alchemyface-0.6.0.tar.gz.

File metadata

  • Download URL: alchemyface-0.6.0.tar.gz
  • Upload date:
  • Size: 60.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for alchemyface-0.6.0.tar.gz
Algorithm Hash digest
SHA256 228373034346ffbf700d840c074b817ee04fd7a3e522537cae58115f496f9904
MD5 bf78aa8c0019e8eddcf1bec258e4716c
BLAKE2b-256 d2e61cf73248275654899a4cac69cebd91e40c96b059724750f320e89da1bdbd

See more details on using hashes here.

File details

Details for the file alchemyface-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: alchemyface-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 62.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for alchemyface-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 81d8e800b941060a635b082a27f84e141cf800ea8604620970aeb85607c95db6
MD5 2f44af44bf9c6473167825a0b1d6072d
BLAKE2b-256 08a3f31f1bbcd51bceda408d7dcb9f8bf44a8d23fae561ca0eecf33956868c67

See more details on using hashes here.

Release history Release notifications | RSS feed

1.2.0

2 files

1.1.1

2 files

1.0.0

2 files

This release

0.6.0 This release

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page