Artfat Face Consistency (Batch)
Turn a folder of generations into a scored contact sheet in one run
- reference_image
- contact_sheet
- csv_path
- mean_score
- report
The sibling node to Artfat Face Consistency (Sort) is for the other half of the job. Sort gates frames live, mid-generation. Batch takes the pile of images you already generated - a few hundred PNGs of your persona in every pose you could think of - and turns it into a labelled contact sheet, a CSV of scores, and optionally sorted keep/reject copies. If your character-consistency workflow ends with you manually culling renders, this is the node that retires that chore.
It runs the exact same scoring core as Sort: OpenCV YuNet detects the face, SFace embeds it, and cosine similarity against a reference says how much it looks like your person. Pure cv2 + numpy on CPU - no VRAM, no API, no key. One neat trick: give it no reference at all and it auto-selects the medoid of your input set as the anchor - the most central image - so even a folder of a single character's renders produces a useful ranking. Better still to bring a real reference, because judging a dataset against its own middle is a weaker bar than judging it against who it's supposed to be.
The inputs that matter
input_folder- the folder of images to analyse. That's the whole job in one field.keep_threshold- cosine at/above this means keep. The default0.363is SFace's own same-identity baseline.reject_threshold- below this means reject; anything between the two is borderline.reference_image/reference_folder- 1..N reference shots, merged into a centroid.sort_copies- also copy images intokeep/borderline/reject/_nofacesubfolders, if you want the actual culling done on disk rather than just scored.contact_cols- columns on the contact sheet (1–12).
What comes out
contact_sheet is an IMAGE you can wire into a Preview - every thumbnail stamped with its verdict and score, so you can scroll a few hundred faces in one glance. There's also csv_path (where the CSV was written), mean_score, and a report string. The CSV logs each image's face_frac and the threshold that was actually applied, which is the part you'll care about later.
The same size-aware thinking from Sort shows up here, scaled to both bars: flip adaptive_threshold on and close-ups get judged strictly (keep_large / reject_large) while small, far faces get the lenient end (keep_small / reject_small), with frac_large / frac_small defining the bands. A full-body shot of the right person stops being failed for having a small face. It's off by default.
Install
Same as the Sort node - one pack, two nodes. ComfyUI Manager (search "Artfat Face Consistency"), or:
cd ComfyUI/custom_nodes
git clone https://github.com/artfat-creator/ComfyUI-Artfat-FaceConsistency
Restart ComfyUI; they land under artfat. Dependencies are opencv-python + numpy (both ship with ComfyUI) and the detector models are bundled, so there's no download step. The optional YOLO detector needs ultralytics and a face .pt in models/ultralytics/bbox/.
Troubleshooting
- "No images in '…'" - the folder path is wrong or empty; the node raises rather than silently doing nothing.
- "No faces found anywhere" - no detector found a face in the whole folder, so there's nothing to anchor to. If your images genuinely have faces, that's a detector issue - try the YOLO option.
- Near-zero scores skewing things - the
mean_scoredrops embeddings below ~0.1, because those are SFace failing to embed a crop (a detector glitch), not real identity mismatches. Don't set thresholds against them. - Weird paths on Windows - the code decodes images via
numpyinstead ofcv2.imread, so non-ASCII and spaced paths work where stock OpenCV chokes. A real, if niche, pain point handled for you.
The genuinely useful move is to run Batch once with a csv_path, read where your persona's real frames land per face size, and carry those numbers into the Sort node for live gating. Batch is the audit, Sort is the bouncer - they share one scoring core, so numbers transfer cleanly. And like its sibling, it's fully MIT with no InsightFace models in the mix, which puts it well ahead of most face tooling on licensing.
Inputs (19)
| Name | Type | Default | Description |
|---|---|---|---|
| input_folder | STRING | Folder of images to analyse. | |
| keep_threshold | FLOAT | 0.36-1–1 | Cosine >= this -> keep (SFace same-id = 0.363). |
| reject_threshold | FLOAT | 0.28-1–1 | Cosine < this -> reject; between = borderline. |
| reference_imageopt | IMAGE | Reference identity 1..N (batch). Empty = auto-medoid of the input set. | |
| reference_folderopt | STRING | Optional folder of references, merged with reference_image. | |
| sort_copiesopt | BOOLEAN | false | Also copy images into keep/borderline/reject/_noface subfolders. |
| contact_colsopt | INT | 61–12 | — |
| adaptive_thresholdopt | BOOLEAN | false | ON = keep/reject thresholds scale with face size (close-ups strict, small/far faces lenient). OFF = flat keep_threshold/reject_threshold. |
| keep_largeopt | FLOAT | 0.60-1–1 | Adaptive: keep threshold for LARGE/close faces (face_frac >= frac_large). |
| keep_smallopt | FLOAT | 0.50-1–1 | Adaptive: keep threshold for SMALL/far faces (face_frac <= frac_small). |
| reject_largeopt | FLOAT | 0.40-1–1 | Adaptive: reject threshold for LARGE/close faces. |
| reject_smallopt | FLOAT | 0.30-1–1 | Adaptive: reject threshold for SMALL/far faces. |
| frac_largeopt | FLOAT | 0.150–1 | Adaptive: face-area fraction at/above which the LARGE thresholds apply. |
| frac_smallopt | FLOAT | 0.030–1 | Adaptive: face-area fraction at/below which the SMALL thresholds apply. |
| detectoropt | COMBO | YuNet (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_modelopt | COMBO | Face .pt model used when detector = YOLO. | |
| detect_confopt | FLOAT | 0.500.05–0.95 | YOLO confidence. LOWER = catches harder/smaller/angled faces. |
| crop_paddingopt | FLOAT | 0.300–1 | Expand the detected face box by this fraction before scoring (more context). |
| min_face_fracopt | FLOAT | 0.000–0.5 | Ignore faces smaller than this fraction of the frame (0 = off). Skips background faces. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| contact_sheet | IMAGE | — |
| csv_path | STRING | — |
| mean_score | FLOAT | — |
| report | STRING | — |