CV SFace Embeddings
Face recognition in ComfyUI without the InsightFace install
- image
- landmarks
- embeddings
- aligned
- count
Every identity tool in this ecosystem - IP-Adapter FaceID, InstantID, PuLID, ReActor - is the same pipeline wearing different front ends: detect a face, align it, turn it into a vector, condition on the vector. The vector step is InsightFace's ArcFace, and that single dependency is why the install is notoriously ugly and why the whole stack comes with non-commercial weights attached.
CV SFace Embeddings is the same idea, from a different source, with none of that. It's OpenCV's own FaceRecognizerSF producing 128-D identity vectors from SFace, an OpenCV Zoo model shipped under Apache-2.0. If your problem is "how do I compare two faces" rather than "how do I generate a picture of this specific person", this is the licence-clean road.
What it is, exactly
The brief's framing is the cleanest one: CV YuNet Face Detect answers where a face is. This node answers whose it is. Recognition, not detection - half the pipeline, and the half the raw cv2 wrappers can't reach, because FaceRecognizerSF is a class and the pack's generator only parses top-level functions.
Three things come out:
- embeddings -
(N, 128)float32, one identity vector per face, in the same order as the landmarks you fed in. Empty(0, 128)when there are no faces, which is a valid result and not an error. - aligned - an IMAGE batch of the 112×112 crops the model actually read. Preview this when a match looks wrong. Per the tooltip, bad alignment is the usual cause, and this output exists so you can see it instead of guessing.
- count - INT, how many faces were embedded.
How the alignment works, and why it matters
You feed it the landmarks output of YuNet - (N*5, 2) float32, five points per face in YuNet's order: right eye, left eye, nose, right mouth corner, left mouth corner. The row count must be a multiple of 5 or it's not input it can use.
Those five points are used for exactly one job: estimating the similarity transform that puts the face on a canonical 112×112 crop. Eyes level, mouth in the right place, same scale. This is what makes the embedding pose-invariant - the model never sees your face at an angle, it sees the normalized version. Which is also why a garbage crop produces a confidently garbage vector rather than an error. The aligned output is your audit.
Feed the vectors into CV Embedding Match to compare them. That's the other half of a recognition pipeline: nearest-vector, not nearest-pixel.
Install and the model
ComfyUI Manager → ComfyUI CV → install → restart. Or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
Python ≥ 3.12 and a recent ComfyUI on the V3 node API. Then the actual work: models aren't bundled. Download face_recognition_sface_2021dec.onnx (37 MB, SFace loss / MobileFaceNet by Yaoyao Zhong, ONNX conversion by Chengrui Wang, Apache-2.0 per the pack's model_sources.txt) into ComfyUI/models/onnx. That file is what the model combo lists. The pack's model_sources.txt at the repo root carries the URLs and the licence record for every model it expects - read it before you redistribute anything.
Common issues
- Embeddings are wrong for one face out of five. Look at
aligned. A bad landmark set (sunglasses, a profile, a face half out of frame) gives a bad crop, and a bad crop still yields 128 numbers. Garbage in, confident garbage out. - Row count error on landmarks. You wired a landmarks list that isn't N*5 - often a face detector whose landmark order or count differs from YuNet's. This node is YuNet-shaped by design.
countis 0 and downstream is confused. Zero faces is not a failure. Branch oncount.- "I just want face swap." Then you want a face-swapping tool, not a recogniser. SFace gives you identity vectors; it draws nothing. Worth saying because "SFace" gets searched for interchangeably with face swap, and the two share a vocabulary and no code.
- Contrib nodes disappeared after a pip install. Plain OpenCV wheel overwriting the contrib one in the shared
site-packages/cv2- and this node's model path ismodels/onnx, so a missing-model error and a missing-submodule error can look identical on the canvas.tools/repair_opencv_contrib.py --checksorts out the second.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| image | NPARRAY,IMAGE | Image the faces were detected in. An IMAGE batch uses its first frame. Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size. | |
| model | COMBO | SFace .onnx model from ComfyUI/models/onnx (face_recognition_sface_2021dec.onnx). | |
| landmarks | NPARRAY | (N*5, 2) float32 landmarks, five per face in YuNet's order (right eye, left eye, nose, right/left mouth corner) - the 'landmarks' output of 'CV YuNet Face Detect'. The row count must be a multiple of 5. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| embeddings | NPARRAY | (N, 128) float32, one identity vector per face, in the same order as the landmarks. Feed 'CV Embedding Match'. Empty (0, 128) when there are no faces. |
| aligned | IMAGE | IMAGE batch of the aligned 112x112 crops the model actually read - preview it when a match looks wrong; a bad alignment is the usual cause. |
| count | INT | How many faces were embedded. |