Face Fixer
The Impact-Pack-style detailer that fixes every face, then blends it back cleanly
- image
- model
- positive
- negative
- vae
- mask
- upscale_model
- image
- face_mask
- info_json
Small faces are the thing that breaks in every generation. A face occupying 70 pixels of a 1024px frame comes out as a smear, because the latent has no budget for it. Face Fixer (MEC) runs the whole automatic-detailing loop in one node: detect every face with YOLO11, crop and optionally upscale each one, re-render it with its own sampling pass, then blend the results back with color matching. It's a behavioural clone of Forbidden Vision's "Fixer" (a real pack by luxdelux7 that people use exactly for this) built on Impact Pack's wildcard syntax, and if you've read the KB's detailing essay you know the loop: detect → crop/upscale → re-render → paste back.
How it works
Pipeline: YOLO11 face detection → per-face crop with crop_padding → optional AI pre-upscale (upscale_model) for small faces → a KSampler on each crop → smart blend (color match + lightness rescue + differential diffusion) → wildcard prompt routing.
Key inputs:
face_model- a YOLO11.pt/.onnxinComfyUI/models/ultralytics/bbox/. This is your responsibility: the pack doesn't bundle weights (a deliberate choice - see below). Choosenoneto skip detection and use themaskinput instead.crop_resolution(default 768) - each face gets resized to this longer side before sampling.denoise(default 0.4) - per-face strength; 0.3 subtle cleanup, 0.7 aggressive reshape. Start low.steps,cfg,sampler_name,scheduler,seed- per-face sampling. Seed is base seed + face index, which quietly sidesteps the classic detailer clone problem.blend_softness,mask_dilate,color_match,lightness_rescue,differential_diffusion- the blend stage, all defaulting on. This is where the seam dies.
The wildcard prompts are the fun part. face_positive_prompt/face_negative_prompt use Impact Pack tokens: [SEP] separates per-face prompts, [ASC]/[DSC] order by score, [ASC-SIZE]/[DSC-SIZE] by face size, [SKIP] leaves a face untouched. Outputs: image, face_mask (combined detection mask), and info_json (per-face bbox, score, prompt, denoise).
The honest caveats
From the KB's detailing essay, the failure modes are real: over-processing an already-good face (you can't see the difference but paid render time), and identity drift on crowds. The seed+i scheme helps the clone problem. The Ultralytics note matters too - the YOLO path is AGPL-3.0, and the Dec-2024 Ultralytics supply-chain incident is why some people prefer MediaPipe. This node uses the standard ultralytics/onnxruntime path, so be aware of the licensing if you're shipping a product.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/Code2Collapse/ComfyUI-CustomNodePacks.git
pip install opencv-python>=4.7.0 scipy>=1.10.0
Then get a face model: face_yolov8n.pt or face_yolov8s.pt (the standard community weights) into ComfyUI/models/ultralytics/bbox/. If neither ultralytics nor onnxruntime is installed, the node falls back to the mask input so your workflow doesn't hard-fail - that's by design. Restart and confirm [MEC] Loaded .... And the cardinal rule from the KB: detail at native resolution first, upscale after - upscaling before the detail pass bakes the mangled face into more pixels.
Inputs (25)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | Source image (single frame or batch; processed independently per frame). | |
| model | MODEL | Diffusion model to sample with. | |
| positive | CONDITIONING | Base positive conditioning. Wildcards in face_positive_prompt override per face. | |
| negative | CONDITIONING | Base negative conditioning. Wildcards in face_negative_prompt override per face. | |
| vae | VAE | VAE used to encode/decode the per-face crops. | |
| face_model | COMBO | none | YOLO11 face-detection .pt/.onnx in ComfyUI/models/ultralytics/bbox/. Choose 'none' to use the optional mask input instead. |
| confidence | FLOAT | 0.500.05–0.95 | Minimum detection confidence. |
| max_faces | INT | 80–32 | Maximum number of faces to process per frame (0 = all). |
| crop_padding | FLOAT | 1.401–3 | Bbox padding multiplier so the sampler sees context around each face. |
| crop_resolution | INT | 768256–2048 | Resize each face crop to this longer-side resolution before sampling. |
| denoise | FLOAT | 0.400–1 | Per-face denoise strength (0.3 = subtle, 0.7 = aggressive reshape). |
| steps | INT | 201–100 | Sampling steps per face. |
| cfg | FLOAT | 6.00–30 | CFG scale for face sampling. |
| sampler_name | COMBO | euler | Sampler algorithm. |
| scheduler | COMBO | normal | Sigma schedule. |
| seed | INT | 00–18446744073709550000 | Base seed; each face gets seed+i. |
| blend_softness | FLOAT | 6.00–64 | Feather radius (px) on the per-face blend mask. |
| mask_dilate | INT | 4-32–32 | Dilate (>0) / erode (<0) of the per-face blend mask. |
| color_match | BOOLEAN | true | Reinhard mean/std colour match per face. |
| lightness_rescue | BOOLEAN | true | Lift the per-face L channel if the sample comes back darker than the original. |
| differential_diffusion | BOOLEAN | true | Weight the blend by |orig - sampled| so unchanged pixels stay sharp. |
| maskopt | MASK | Optional manual face mask. Used directly when face_model='none' or detection finds nothing. | |
| upscale_modelopt | UPSCALE_MODEL | Optional UPSCALE_MODEL applied to faces below crop_resolution before sampling. | |
| face_positive_promptopt | STRING | Per-face positive prompt with wildcards: [SEP] separates faces; [ASC]/[DSC]/[ASC-SIZE]/[DSC-SIZE] order; [SKIP] leaves a face untouched. | |
| face_negative_promptopt | STRING | Same syntax as face_positive_prompt for negatives. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | Image with detected faces detailed and blended back over the original. |
| face_mask | MASK | Combined face-detection mask covering all processed faces. |
| info_json | STRING | JSON metadata: per-face bbox, score, prompt, denoise. |