Nodes/Artfat Face Consistency/Artfat Face Consistency (Sort)
ComfyUI Node

Artfat Face Consistency (Sort)

The face-consistency gate that sorts your good frames from your bad ones

By artfat-creator·Created about a month ago·Updated 18 days ago· 4
Artfat Face Consistency (Sort)
  • image
  • reference_image
  • image
  • similarity
  • passed
  • verdict
  • annotated
min_similarity0.50
pass_dir
fail_dir
reference_folder
filename_prefixblanca
csv_path
save_filestrue
adaptive_thresholdfalse
sim_large0.60
sim_small0.50
frac_large0.15
frac_small0.03
borderline_margin0.00
borderline_dir
detectorYuNet (fast, CPU)
yolo_model
detect_conf0.50
crop_padding0.30
min_face_frac0.00

You've been here. You run your persona LoRA, batch out a dozen frames, and three of them are almost your person - same outfit, same pose, but the model quietly swapped in a stranger's face. So you squint at each one and delete the misses by hand. Artfat Face Consistency (Sort) is that squinting, automated. Drop it after your VAE decode and it scores every frame against a reference face, stamps a verdict on it, and files it into a pass or fail folder while you do something else.

Be clear-eyed about what it is: this is a curation node, not a consistency fixer. It won't make faces more alike - it sorts what you already generated. That still matters, because "generate then manually reject the drift" is exactly how most character-consistency workflows actually end, whatever the marketing says about your adapter.

How it works

The scoring core is OpenCV's YuNet detector plus SFace recognizer, pure cv2 + numpy running on CPU. It never touches VRAM, so it doesn't crowd your sampler - you can gate every frame in a batch without stealing memory from generation. YuNet finds the biggest face in frame, SFace aligns and embeds it into a vector, and the node compares that vector to your reference using cosine similarity. Same person scores around 0.363 on SFace's own scale; the closer to 1.0, the stronger the match.

Feed reference_image as a batch of 1..N shots and they get averaged into a single centroid - a dataset centroid is a steadier anchor than any one frame. You need some reference: without one, the node raises an error rather than guessing.

The inputs that actually matter

  • image - the frame(s) to check, usually the VAE-decoded output.
  • min_similarity - the flat pass bar (default 0.5). Since same-id starts around 0.363, 0.45–0.6 is a sensible gate for a strong persona; crank it if your LoRA lands hard.
  • pass_dir / fail_dir - where frames get filed. Both are created if missing.
  • reference_image (or reference_folder) - the identity anchor. Connect this or the node throws.

On the output side you get image (passthrough), similarity (FLOAT), passed (BOOLEAN), verdict (STRING) and annotated - an IMAGE with a score badge drawn in, handy for eyeballing what it flagged. The score is baked into the filename it writes, so your pass folder reads like blanca_00007_sim0.782_pass.png.

Adaptive thresholds and the borderline band

One flat threshold is unfair across shot sizes: a small face embeds from fewer pixels, so a full-body frame of the right person legitimately scores lower than a close-up of the wrong one. Turn on adaptive_threshold and the bar scales with how big the face is in frame (face_frac) - sim_large/sim_small at frac_large/frac_small, linear between. It's off by default, so existing graphs behave exactly as before. Set borderline_margin above 0 and frames that just miss get routed to borderline_dir for a human to review instead of being condemned.

Install

Grab it in ComfyUI Manager (search "Artfat Face Consistency"), or:

cd ComfyUI/custom_nodes
git clone https://github.com/artfat-creator/ComfyUI-Artfat-FaceConsistency

Restart ComfyUI and the nodes appear under the artfat category. Dependencies are opencv-python and numpy, both of which ship with ComfyUI - the yunet.onnx / sface.onnx models are bundled in the pack's models/, so there's nothing extra to download. The optional YOLO detector (more robust on small/angled faces in full-body shots) wants ultralytics plus a face .pt file in models/ultralytics/bbox/ - if you already run Impact-Pack or ADetailer, you very likely have one.

Troubleshooting

  • Nodes don't show up, or load broken after an update - fully restart ComfyUI, and if that's not enough right-click → Fix node (recreate). The README calls this normal after inputs change.
  • "Connect a reference_image or set reference_folder" - you fed it frames but no anchor. Wire one in.
  • Score near zero (< 0.1) - that's usually SFace failing to embed the crop, a detector glitch, not an identity mismatch. Don't tune thresholds to it.
  • Small faces score low - flip on adaptive_threshold, or switch detector to YOLO so the face gets cropped and re-aligned before scoring.

One more thing worth knowing: this is one of the rare face-identity tools with a clean license. No InsightFace models, no non-commercial checkpoints - the whole pack is MIT. If you've ever hit the InsightFace licensing wall with InstantID or PuLID pipelines, this one's refreshingly uncomplicated. Set a csv_path on your first run and read where your persona's real frames land before you pick a gate.

Categoryartfat

Inputs (21)

NameTypeDefaultDescription
imageIMAGEFrame(s) to check — usually the VAE-decoded output.
min_similarityFLOAT0.50-1–1Flat pass threshold (used when adaptive_threshold is OFF). Cosine >= this -> pass. SFace same-id starts ~0.363; for a strong persona 0.45-0.6 is a sensible gate.
pass_dirSTRINGFolder for images that PASS. Created if missing.
fail_dirSTRINGFolder for images that FAIL (and no-face). Created if missing.
reference_imageoptIMAGEReference identity, 1..N images (batch). Averaged into a centroid.
reference_folderoptSTRINGOptional folder of reference images, merged with reference_image.
filename_prefixoptSTRINGblanca
csv_pathoptSTRINGOptional CSV log path (appended). Logs face_frac + threshold for calibration. Empty = no log.
save_filesoptBOOLEANtrueOff = only score/pass outputs, nothing written to disk.
adaptive_thresholdoptBOOLEANfalseON = pass threshold scales with face size (close-ups judged strictly, small/far faces leniently). OFF = flat min_similarity (old behaviour).
sim_largeoptFLOAT0.60-1–1Adaptive: threshold for LARGE/close faces (face_frac >= frac_large).
sim_smalloptFLOAT0.50-1–1Adaptive: threshold for SMALL/far faces (face_frac <= frac_small).
frac_largeoptFLOAT0.150–1Adaptive: face-area fraction at/above which sim_large applies.
frac_smalloptFLOAT0.030–1Adaptive: face-area fraction at/below which sim_small applies.
borderline_marginoptFLOAT0.000–0.50 = off. Else frames JUST BELOW the threshold (within this margin) go to 'borderline' (borderline_dir) for review. At/above threshold = pass; the band is one-sided.
borderline_diroptSTRINGFolder for borderline frames. Empty -> they go to fail_dir.
detectoroptCOMBOYuNet (fast, CPU)YuNet = fast cv2, great on portraits. YOLO = finds small faces in full-body/complex shots, then crops+re-aligns so the score is fair.
yolo_modeloptCOMBOFace .pt model used when detector = YOLO.
detect_confoptFLOAT0.500.05–0.95YOLO confidence. LOWER = catches harder/smaller/angled faces.
crop_paddingoptFLOAT0.300–1Expand the detected face box by this fraction before scoring (more context).
min_face_fracoptFLOAT0.000–0.5Ignore faces smaller than this fraction of the frame (0 = off). Skips background faces.

Outputs (5)

NameTypeDescription
imageIMAGE
similarityFLOAT
passedBOOLEAN
verdictSTRING
annotatedIMAGE