UniFace logo

UniFace is a Python library for face analysis. It provides APIs for face detection, recognition, landmarks, face mesh, parsing, portrait matting, tracking, attributes, image quality scoring, gaze estimation, head pose, anti-spoofing, anonymization, and vector search.

The library is built around a common set of conventions. Detectors return Face objects, recognition models use the same landmark format, and higher-level APIs such as FaceAnalyzer combine common steps when you do not need to wire each module manually.

Installation

Install the CPU version for regular CPU inference or Apple Silicon:

pip install "uniface[cpu]"

Install the GPU version for NVIDIA CUDA:

pip install "uniface[gpu]"

The extras are separate because onnxruntime and onnxruntime-gpu should not be installed together. Models are downloaded on first use, verified with SHA-256, and cached locally.

Face Detection

import cv2
from uniface.detection import RetinaFace

image = cv2.imread("photo.jpg")
if image is None:
    raise ValueError("Could not read photo.jpg")

detector = RetinaFace()
faces = detector.detect(image)

for face in faces:
    print("confidence:", round(face.confidence, 3))
    print("bbox:", face.bbox)
    print("landmarks:", face.landmarks)

This is the smallest useful example. It loads an image, runs a detector, and prints the bounding box and 5-point landmarks for each face.

For webcam input, the same detector can be used frame by frame:

import cv2
from uniface.detection import RetinaFace
from uniface.draw import draw_detections

detector = RetinaFace()
cap = cv2.VideoCapture(0)

while True:
    ok, frame = cap.read()
    if not ok:
        break

    faces = detector.detect(frame)
    draw_detections(image=frame, faces=faces, vis_threshold=0.6)

    cv2.imshow("UniFace detection", frame)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

cap.release()
cv2.destroyAllWindows()

For a higher-level pipeline, FaceAnalyzer can run detection, embedding extraction, and optional attributes in one call:

import cv2
from uniface import AgeGender, FaceAnalyzer

image = cv2.imread("photo.jpg")
analyzer = FaceAnalyzer(predictors=[AgeGender()])
faces = analyzer.analyze(image)

for face in faces:
    print(face.bbox, face.sex, face.age)

See the full documentation at yakhyo.github.io/uniface and the source code on GitHub.

What UniFace Includes

Area Models and features
Detection RetinaFace, SCRFD, CenterFace, YOLOv5-Face, YOLOv8-Face, and BlazeFace
Recognition AdaFace, ArcFace, EdgeFace, MobileFace, SphereFace
Landmarks 5-point detector landmarks, 106 / 98 / 68-point models, 468-point 3D Face Mesh, and BlazeFace’s 6 MediaPipe keypoints
Tracking BYTETracker-based persistent IDs for video
Parsing BiSeNet semantic face parsing and XSeg masking
Matting MODNet portrait matting for background removal
Attributes Age, gender, race, emotion, eye openness, glasses, sunglasses, and mask
Quality eDifFIQA image quality scoring in four sizes, from 6.6 MB to 250 MB
Gaze MobileGaze for gaze direction estimation
Head pose Pitch, yaw, and roll estimation
Anti-spoofing MiniFASNet
Privacy Pixelate, gaussian, blackout, elliptical, and median anonymization
Search Optional FAISS-backed vector store

What It Looks Like

Every figure below is rendered from the photographs in the repository by the script that builds the demo set, so the numbers printed on them are measured rather than quoted from a paper. The demo set README records which source feeds which figure and why each model was chosen.

Detection and Landmarks

Small faces are the hard case. This is the 1927 Solvay Conference photograph, and SCRFD-10G finds 29 faces in it at 37 to 46 pixels wide:

Black and white group photograph of 29 physicists on the steps of a building, each face marked with a small green box and five green keypoints. Caption reads: Face Detection, SCRFD-10G, box plus 5 keypoints, 29 faces

Landmark models run on top of a detection. The same portrait at 106, 98, and 68 points:

Three copies of the same smiling woman's portrait side by side, each overlaid with a different landmark set: 106 cyan points from 2d106det, 98 purple points from PIPNet WFLW, and 68 green points from PIPNet 300W

Face Mesh goes further and fits 468 dense 3D points per frame, which holds up under expression changes:

Animated 468-point face mesh tracking a woman's face as her expression changes from neutral to smiling

Parsing, Segmentation, and Matting

Three different ways to cut a face out of a photograph, and they are not interchangeable. Parsing gives you per-region labels, with 13 of BiSeNet’s 19 classes present here:

Face parsing shown as a pair: the original portrait beside a version where skin, hair, eyebrows, eyes, nose, lips, neck and clothing are each shaded a different colour

XSeg returns one binary mask instead, which is what you want when the next step is a cut-out rather than a per-region edit:

Face segmentation in three panels: a woman with curly hair, the same photo with her face region filled solid green as the XSeg mask, and the face cut out onto a transparency checkerboard

Matting returns an alpha matte rather than a mask, so hair keeps its soft edge when you composite:

Portrait matting in three panels: the original photo, the alpha matte as a white silhouette on black with individual hair strands visible, and the subject composited onto a green background

Head Pose and Gaze

Head pose returns pitch, yaw, and roll, drawn here as a projected cube. The model prints pitch and roll only below 60 degrees of yaw, because past that it reports tens of degrees of tilt on a level head:

Three portraits each overlaid with a red, green and blue wireframe cube showing head orientation, labelled yaw minus 77 degrees, yaw plus 8 degrees with pitch minus 9 and roll plus 3, and yaw plus 40 degrees with pitch plus 4 and roll plus 2

Gaze is a separate estimate and it does not follow the head. The middle subject below faces the camera but is still looking 20 degrees to her left:

Three portraits each with a red arrow drawn from between the eyes showing gaze direction, labelled gaze yaw minus 31 degrees, minus 20 degrees, and plus 19 degrees

Attributes

FairFace buckets age rather than predicting a number, and returns sex and race alongside it:

Four portraits in a row, a young boy, a teenage girl, a smiling middle-aged woman and an older man in a flat cap, each labelled with a predicted age bucket of 3 to 9, 20 to 29, 40 to 49 and 60 to 69, and a sex of male, female, female and male

Emotion covers the eight AffectNet classes:

Eight portraits in a two-row grid, each labelled with a predicted emotion and probability: happy 0.99, surprise 0.93, angry 0.92, disgust 0.75, fear 0.93, sad 0.76, contempt 0.98 and neutral 0.76

Face attributes run once per detected face, so a group photo returns an independent result for each person. Only the man on the left reads Glasses True:

Three people photographed together, each face in a white box with its own labels for eyes, mask, glasses and sunglasses, and only the man on the left reading glasses true

Recognition, Quality, and Privacy

AdaFace holds an identity across decades. Einstein matches himself at +0.583 over 26 years and Bohr at +0.689 over 25, while both of the man-against-man negatives land near zero, well under the 0.40 threshold:

Eight archival portraits arranged as four comparison pairs. Einstein in 1921 against 1947 scores plus 0.583 and Bohr in 1910 against 1935 scores plus 0.689, both labelled match; Einstein against Bohr scores plus 0.001 and Einstein against Curie scores minus 0.031, both labelled no match

Quality scoring gives you one number per face, which is what you filter on before enrolling someone. Seven faces in this frame span 0.398 to 0.749, and the low scorers are the ones turned away from the camera:

A group of seven people against a dark wall, each face marked with a corner box coloured amber or green, above a strip of four cropped faces scored 0.398, 0.525, 0.675 and 0.749, with the lowest scores on the faces turned away from the camera

Anti-spoofing judges the presentation, not the face. A live capture reads Real at 1.00, and a print and a screen replay of that same capture read Fake at 0.66 and 0.99:

Three frames of the same woman: a live webcam capture boxed in green and labelled Real 1.00, a printed photograph of her boxed in red and labelled Fake 0.66, and the photo replayed on a tablet screen boxed in red and labelled Fake 0.99

Anonymization ships several methods, so you can match whichever one your compliance requirement actually asks for:

The same photograph of five colleagues repeated four times, with every face obscured by a different method: pixelate, gaussian blur, elliptical blur, and a solid black box

Notebooks and Demo

The examples can be opened directly in Google Colab:

Notebook Colab Focus
Face Detection Open Detection and 5-point landmarks
Face Alignment Open Alignment for recognition
Face Verification Open Similarity-based identity matching
Face Search Open Searching for a person in group photos
Face Analyzer Open Detection, recognition, and attributes
Face Parsing Open Semantic face segmentation
Face Anonymization Open Face blurring and anonymization
Gaze Estimation Open Gaze direction prediction
Face Segmentation Open XSeg-based masking
Face Vector Store Open FAISS-backed embedding search
Head Pose Estimation Open Pitch, yaw, and roll
Face Recognition Open Recognition without FaceAnalyzer
Portrait Matting Open Background removal and compositing
Face Attributes Open Eye, glasses, sunglasses, and mask attributes
Face Mesh Open Dense 468-point face landmarks

There is also a live Hugging Face demo at huggingface.co/spaces/yakhyo/uniface.

Library Design

UniFace keeps the individual modules independent, but makes them work together through shared inputs and outputs. You can use only the detector, build a recognition pipeline yourself, or start from FaceAnalyzer when you want the common detection-plus-recognition path.

The library supports macOS, Linux, and Windows, including CPU inference, Apple Silicon, and NVIDIA CUDA through ONNX Runtime providers.

Several UniFace modules started as standalone projects. These posts go deeper on the individual models the library bundles: