Low‑Budget Face Verification for Small Projects: Deploying YuNet + SFace End‑to‑End
This article explains how to build a cost‑effective, CPU‑only face verification system for small‑scale projects using the lightweight YuNet detector and SFace recognizer, covering the models’ principles, implementation steps with OpenCV and Gradio, and performance considerations.
Problem
Small projects often lack budget for high‑performance GPUs or commercial face‑recognition APIs and cannot optimise large models such as FaceNet or ArcFace.
YuNet + SFace rationale
YuNet, a millisecond‑level lightweight face detector and landmark localiser developed by Southern University of Science and Technology and included in OpenCV, has ~75 k parameters, runs fast on CPU and provides sufficient accuracy for low‑resource scenarios.
SFace converts a cropped face into a 128‑dimensional unit‑norm vector and compares it with stored vectors using cosine similarity.
Model details
YuNet detection
Input : arbitrary‑size image, recommended 320×320.
Core operation : image is divided into 40×40, 20×20 and 10×10 grids; each grid is classified as face or not, producing a bounding box and five landmarks (two eyes, nose, two mouth corners).
Output : face box coordinates and landmarks used for alignment.
SFace recognition
Input : standardized face image after YuNet alignment.
Feature extraction : face is projected to a 128‑dimensional vector; L2‑normalisation maps vectors onto a unit hypersphere.
Verification : cosine similarity between query vector and each stored vector; similarity above a configurable threshold indicates the same identity.
Implementation
Project structure includes pre‑downloaded YuNet and SFace model files. The core method recognize_image implements the end‑to‑end pipeline.
def recognize_image(self, image: np.ndarray, similarity_threshold: float = None) -> tuple[np.ndarray, dict]:
"""Detect and recognise faces, returning (annotated_image, result_dict)."""
if image is None:
return None, {"face_count": 0, "results": []}
threshold = similarity_threshold or config.RECOGNITION_COSINE_THRESHOLD
faces = self.detector.detect(image)
features_db = self.face_library.get_features_db()
annotated = image.copy()
results = []
for face in faces:
bbox = face["bbox"]
landmarks = face["landmarks"]
feature = self.recognizer.extract_feature(image, face["raw"])
identity, similarity = self._find_best_match(feature, features_db, threshold)
if identity:
color = COLOR_RECOGNIZED
label = f"{identity} ({similarity:.2f})"
status = "recognized"
else:
color = COLOR_UNKNOWN
label = f"Unknown ({similarity:.2f})"
status = "unknown"
self._draw_bbox(annotated, bbox, color, label)
self._draw_landmarks(annotated, landmarks)
results.append({
"bbox": bbox,
"identity": identity or "Unknown",
"similarity": round(similarity, 4),
"status": status,
"confidence": round(face["confidence"], 4),
})
return annotated, {"face_count": len(results), "results": results}Sequence: input validation → threshold selection → YuNet detection → SFace feature extraction → cosine‑similarity search → visual annotation → result packaging.
Deployment and outcome
After uploading face images to the library, the system recognises faces in real‑time on a CPU‑only machine. Detected faces are annotated with bounding boxes (green for recognised, red for unknown) and landmarks (purple). The combination satisfies low‑budget, small‑scale verification tasks such as campus access control, internal attendance, device login and small‑member verification.
Reference: https://link.springer.com/article/10.1007/s11633-023-1423-y
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
xkx's Tech General Store
Code with the left hand, enjoy with the right; a keystroke sweeps away worries.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
