Person Detailer
Detail each face in a crowd with its own LoRA — without burning out your workflow
- images
- person_data
- model
- clip
- vae
- positive_base
- negative
- dd_options
- inpaint_options
- images
- refined
- refined_references
- refined_generic
If you've ever tried to put two or three different characters in one image and watched the model blend them into one mush-faced average, you know the pain this node exists for. Person Detailer is the workhorse of the FVMtools pack: it takes an image, a set of detected faces, and - here's the part FaceDetailer from Impact Pack can't do - applies a different LoRA and prompt to each person. Person one gets your character LoRA and a "short brown hair" prompt; person two gets the other LoRA and "long black hair"; everyone else can be swept up by a generic slot. It's the multi-character problem from character-consistency land, solved with a bunch of per-slot inpaint passes instead of hoping regional prompting cooperates.
What it actually does
The mechanism is the old detect-crop-refine loop - the same idea Impact Pack's FaceDetailer popularized in ComfyUI - but re-engineered for multiple identities. Feed it images plus person_data from Person Selector Multi (the node that matched each face to a reference and generated its masks), and it iterates over your reference slots one at a time. For each enabled slot it crops the matched region, applies that slot's LoRA as a temporary clone on your base model, encodes the slot's prompt, and runs a masked inpaint pass at target_width × target_height - 800×1200 by default, which is the whole point: a face that's 80×80 in the full frame gets a proper resolution budget. Then it feathers the edges (mask_blend_pixels, default 32) and stitches it back. Slots process sequentially, each inpainting into the result of the previous one, so the image builds up progressively.
There's real engineering under the hood. LoRA files live in a 6-slot LRU cache so the same LoRA isn't re-read from disk for every face. And before the batch loop it pre-computes every unique (LoRA, strength, prompt) combination once - five people sharing one LoRA means one load and one conditioning encode, not five. If you're running Z-Image Turbo (Lumina2), it auto-detects it and converts diffusers-style LoRAs to the fused QKV format on the fly. That last bit is why the defaults look weird: steps defaults to 4 and denoise to 0.52, tuned for distilled turbo models. On a regular SDXL or Flux checkpoint, raise steps before you conclude it's broken.
The inputs that matter
reference_1_enabled/reference_1_lora/reference_1_prompt- slot 1 is on by default; slots 2–5 start disabled. Each slot's prompt, if empty, falls back to thepositive_baseconditioning. This is where you wire per-character identity.denoise- 0.52 default. Lower keeps more original detail; too high and you're re-drawing the face, not refining it.generic_enabled+generic_prompt- the catch-all. It starts off; flip it on and, withgeneric_catch_unprocessed(on by default) doing the sweeping, it details any face no reference slot claimed, so nobody gets left looking mushy.
Outputs: images (the final stitched result), plus refined, refined_references, and refined_generic - grids of the inpainted crops, handy for checking what each slot actually did. Wire images straight to a SaveImage or into an upscaler.
Installing it
Grab the whole pack once - every node in this family ships together. Via ComfyUI Manager, search comfyui-fvmtools, or:
cd ComfyUI/custom_nodes
git clone https://github.com/ping1979ping/comfyui-FVMtools
pip install insightface>=0.7.3 onnxruntime-gpu>=1.17.0 opencv-python>=4.8.0 numpy>=1.24.0
Restart ComfyUI. InsightFace's buffalo_l models auto-download on first use, but the BiSeNet segmentation model does not - drop parsing_bisenet.pth (53 MB, from xinntao/facexlib releases) into ComfyUI/models/gfpgan/ or face/head masks silently won't exist.
Where people get burned
The classic gotcha: an empty output. If you see a 64×64 black placeholder image come out, that's not a crash - it means no face matched any enabled slot, so nothing got detailed. Check your reference images, the threshold on Person Selector Multi, and whether the right slots are enabled. Also worth knowing: this node is LOUD - it loads a face detector, a face parser, potentially SAM, and runs a full inpaint per person. First run on a big batch can feel like it hung while models initialize. It's not hung, it's downloading buffalo_l.
Inputs (46)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | Input image batch [B, H, W, C] | |
| person_data | PERSON_DATA | Person data from Person Selector Multi node | |
| model | MODEL | Base model (LoRAs are applied as temporary clones per slot) | |
| clip | CLIP | CLIP model for prompt encoding | |
| vae | VAE | VAE for encode/decode in the inpaint pipeline | |
| seed | INT | 00–18446744073709550000 | Global seed, fixed across all slots and batch items |
| steps | INT | 41–100 | Number of sampling steps per inpaint |
| denoise | FLOAT | 0.520–1 | Denoise strength for inpainting (lower = more original detail preserved) |
| sampler_name | COMBO | Sampler algorithm | |
| scheduler | COMBO | Noise schedule | |
| detail_daemon_enabled | BOOLEAN | true | Enable Detail Daemon sigma manipulation for enhanced detail preservation |
| detail_amount | FLOAT | 0.20-5–5 | Detail Daemon strength. Positive = more detail, negative = smoother. 0 = off. |
| dd_smooth | BOOLEAN | true | Smooth the Detail Daemon sigma curve to avoid artifacts |
| mask_blend_pixels | INT | 320–128 | Gaussian feather radius at mask edges for seamless blending |
| mask_expand_pixels | INT | 00–64 | Dilate/expand mask by this many pixels before inpainting |
| target_width | INT | 80064–4096 | Width to resize face crops to before sampling |
| target_height | INT | 120064–4096 | Height to resize face crops to before sampling |
| reference_1_enabled | BOOLEAN | true | Enable reference slot 1 for detailing |
| reference_1_lora | COMBO | LoRA to apply when detailing reference 1 | |
| reference_1_lora_strength | FLOAT | 1.000–2 | LoRA strength for reference 1 |
| reference_1_prompt | STRING | Positive prompt for reference 1. If empty, uses base conditioning. | |
| reference_2_enabled | BOOLEAN | false | Enable reference slot 2 for detailing |
| reference_2_lora | COMBO | LoRA to apply when detailing reference 2 | |
| reference_2_lora_strength | FLOAT | 1.000–2 | LoRA strength for reference 2 |
| reference_2_prompt | STRING | Positive prompt for reference 2. If empty, uses base conditioning. | |
| reference_3_enabled | BOOLEAN | false | Enable reference slot 3 for detailing |
| reference_3_lora | COMBO | LoRA to apply when detailing reference 3 | |
| reference_3_lora_strength | FLOAT | 1.000–2 | LoRA strength for reference 3 |
| reference_3_prompt | STRING | Positive prompt for reference 3. If empty, uses base conditioning. | |
| reference_4_enabled | BOOLEAN | false | Enable reference slot 4 for detailing |
| reference_4_lora | COMBO | LoRA to apply when detailing reference 4 | |
| reference_4_lora_strength | FLOAT | 1.000–2 | LoRA strength for reference 4 |
| reference_4_prompt | STRING | Positive prompt for reference 4. If empty, uses base conditioning. | |
| reference_5_enabled | BOOLEAN | false | Enable reference slot 5 for detailing |
| reference_5_lora | COMBO | LoRA to apply when detailing reference 5 | |
| reference_5_lora_strength | FLOAT | 1.000–2 | LoRA strength for reference 5 |
| reference_5_prompt | STRING | Positive prompt for reference 5. If empty, uses base conditioning. | |
| generic_enabled | BOOLEAN | false | Enable detailing for unmatched faces (not assigned to any reference) |
| generic_catch_unprocessed | BOOLEAN | true | ON: detail all faces not processed by active slots (including matched but disabled slots). OFF: only truly unmatched faces from Person Selector Multi. |
| generic_lora | COMBO | LoRA to apply for unmatched faces | |
| generic_lora_strength | FLOAT | 1.000–2 | LoRA strength for unmatched faces |
| generic_prompt | STRING | Positive prompt for unmatched faces. If empty, uses base conditioning. | |
| positive_baseopt | CONDITIONING | Base positive conditioning, used as fallback when a slot's prompt is empty | |
| negativeopt | CONDITIONING | Negative conditioning. If not connected, an empty negative is used. | |
| dd_optionsopt | DD_OPTIONS | Advanced Detail Daemon parameters from Detail Daemon Options node | |
| inpaint_optionsopt | INPAINT_OPTIONS | Advanced inpaint settings and per-slot overrides from Inpaint Options node |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| images | IMAGE | — |
| refined | IMAGE | — |
| refined_references | IMAGE | — |
| refined_generic | IMAGE | — |