Nodes/ComfyUI CV/CV SFace Embeddings
ComfyUI Node

CV SFace Embeddings

Face recognition in ComfyUI without the InsightFace install

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
CV SFace Embeddings
  • image
  • landmarks
  • embeddings
  • aligned
  • count
◄model▾►

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.
  • count is 0 and downstream is confused. Zero faces is not a failure. Branch on count.
  • "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 is models/onnx, so a missing-model error and a missing-submodule error can look identical on the canvas. tools/repair_opencv_contrib.py --check sorts out the second.
Categoryimage/CV/dnn

Inputs (3)

NameTypeDefaultDescription
imageNPARRAY,IMAGEImage 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.
modelCOMBOSFace .onnx model from ComfyUI/models/onnx (face_recognition_sface_2021dec.onnx).
landmarksNPARRAY(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)

NameTypeDescription
embeddingsNPARRAY(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.
alignedIMAGEIMAGE batch of the aligned 112x112 crops the model actually read - preview it when a match looks wrong; a bad alignment is the usual cause.
countINTHow many faces were embedded.