Replacing a face recognition model can leave the Python code almost unchanged while changing the meaning of every stored embedding and similarity score.
UniFace exposes AdaFace, ArcFace, EdgeFace, MobileFace, and SphereFace through a common recognizer interface. That makes it convenient to compare implementations. It does not make their embedding spaces or decision thresholds interchangeable.
This article develops that distinction from my original recognition model comparison. The focus here is what needs to change in a pipeline when the recognizer changes.
The interface stays stable; the representation changes
The recognition path starts with an image and five alignment landmarks. The landmarks define a standardized face crop, and the recognizer maps that crop to an embedding. UniFace can return an L2-normalized vector for cosine similarity.
Two models can both produce 512 values without assigning the same meaning to those coordinates. Comparing an AdaFace vector with an ArcFace vector is not a supported identity comparison. Even switching weights within a model family should be treated as a representation change unless compatibility has been established.
For a stored gallery, replacing the recognizer therefore requires regenerating its embeddings and rebuilding the corresponding search index. A migration that changes only the model constructor can produce plausible-looking scores from incompatible vectors.
Compute a score before assigning a decision
The following example uses an explicit ArcFace variant and requires one face in each input. It prints a similarity score without attaching an uncalibrated match label.
Install the CPU runtime with pip install "uniface[cpu]", then supply two portrait images:
import cv2
from uniface import ArcFace, RetinaFace, compute_similarity
from uniface.constants import ArcFaceWeights
detector = RetinaFace()
recognizer = ArcFace(model_name=ArcFaceWeights.MNET)
def embed(path):
image = cv2.imread(path)
if image is None:
raise FileNotFoundError(f'Could not read {path}')
faces = detector.detect(image)
if len(faces) != 1:
raise ValueError(f'Expected one face in {path}, found {len(faces)}')
return recognizer.get_normalized_embedding(image, faces[0].landmarks)
score = compute_similarity(
embed('portrait_a.jpg'),
embed('portrait_b.jpg'),
normalized=True,
)
print(f'Cosine similarity: {score:.3f}')
The score is a similarity measurement, not the probability that the images show the same person. A decision threshold is a separate part of the application.
Selecting the highest-confidence detection would not resolve an ambiguous multi-person image: detector confidence measures detection confidence, not which subject the caller intended. Requiring exactly one face makes this example's assumption explicit.
What a small comparison can tell you
The original post compared six model configurations on five archival portraits, producing two same-person pairs and eight different-person pairs. That exercise showed how the scores changed between models while the input images stayed fixed.
It did not establish a general accuracy ranking, a false-match rate for deployment, or a recommendation for a particular device. Ten pairs are too few to characterize errors across lighting, pose, age, camera quality, and population differences. The illustration above comes from a separate demo run and should not be treated as the numerical record of the comparison.
A small test can check that a pipeline runs and reveal obvious score changes. Choosing a deployment threshold requires a larger, representative evaluation.
Calibrate the complete pipeline
Collect labeled same-person and different-person examples that reflect the intended inputs. Separate threshold selection from final evaluation so the reported result is not simply a fit to the calibration examples.
For a rule that accepts a pair when its score is at least a threshold, increasing that threshold generally reduces false matches while increasing missed matches. Evaluate both errors and choose an operating point based on the application's requirements. A value copied from a demo or another model has no such justification.
The detector and preprocessing belong in that evaluation. Different landmarks change alignment; crop geometry, color conventions, and image quality can also affect embeddings. Keep the detector, alignment method, recognizer weights, and preprocessing fixed when comparing scores.
For one-to-many search, evaluate the search workflow with a gallery of representative size as well. A pairwise threshold study alone does not describe the chance of an incorrect identification across many candidates.
Plan model changes as data migrations
Record the recognizer variant, weight checksum, and preprocessing configuration alongside the embedding collection. Build the replacement gallery separately, recalibrate its decision rule, and validate it before switching the application to the new index.
Runtime measurements need the same care. Weight-file size describes storage, not latency. Measure preprocessing, alignment, inference, and search on the intended hardware and execution provider. The laptop timings in the original post are observations from that setup, not performance promises for another device.
A shared API reduces the code needed to compare recognizers. The data migration and evaluation remain part of the change.
See UniFace's recognition documentation, threshold calibration guide, and model licence terms before selecting a configuration for deployment.

Top comments (0)