LC Skin Upscale
Put the pores back without re-rolling the face
- image
- upscale_model
- mask
- image
- skin_mask
"Too smooth skin" is the oldest complaint in this hobby and still the first one people make about a brand-new architecture - it's why the Z-Image Turbo writeups of 2026 open with the skincare-ad problem. The usual answers all involve sampling again: FaceDetailer, a low-denoise img2img pass, SeedVR2. Every one of those invents a face.
LC Skin Upscale is the other kind of fix. One CNN upscale model, applied where a mask says, composited back over the original pixels. No sampler, no seed, no CFG - and, per the author, not a stack and not Ultimate SD Upscale. It sits between "leave the skin alone" and "run the face through a generative detailer".
How it actually works
Building the matte. mask_source decides where the effect lands. input uses only the MASK you wire in. chroma ignores your mask and derives one from skin-colour heuristics - RGB ranges plus a CIELAB chroma window, box-blurred and thresholded with mask_sensitivity as the dial. input+chroma is the AND of the two. The matte then gets feathered, and the node crops to its bounding box (padded 16px, snapped to a multiple of eight).
Running the model on the crop. It hands that crop to your UPSCALE_MODEL through Comfy's tiled scaler, and retries at half the tile size if it runs out of VRAM. Then:
detail 1xresizes the model's output back down to the crop's size and pastes it in. Nothing gets bigger. This is the mode for 1× skin models like1xSkinContrast-High-SuperUltraCompactor1x-ITF-SkinDiffDetail-Lite-v1.scalekeeps the model's native factor - the canvas genuinely grows, unmasked area bilinear, masked area run through the model. Only for when you wanted pixels.
Final pixels are original × (1 − mask × blend) + patch × (mask × blend) - the strength you get is mask and blend multiplied, not either alone.
It's a preview node underneath (a PreviewImage subclass), which is where the on-node before/after wipe comes from. Outputs are image (composited, batch-preserved) and skin_mask.
The inputs worth touching
upscale_model- load it with the coreUpscaleModelLoaderfromComfyUI/models/upscale_models, and load a 1× skin model, not a generic 4×. The author's daily is1xSkinContrast-High-SuperUltraCompact.blend- 0 is the original image, 1 is a full replacement under the matte. The pack's own recipe is 0.75, dropping to 0.55–0.65 on already-sharp Krea plates.mask_source-inputif you're wiring a proper person mask, which is the recommended path.mask_feather- soft edge on the composite matte, and the one to nudge if you can see where the patch ends (0.45 in the author's recipe).tile/overlap- the author's numbers are 256 / 16 on large plates, on the grounds that 512 is slower for very little gain.tile: 0means "try the whole crop and shrink if it OOMs".
Install
Manager: search ComfyUI_LC123_nodes. Manually:
cd ComfyUI/custom_nodes
git clone https://github.com/lonecatone23/ComfyUI_LC123_nodes
Then restart. __init__.py has to sit directly in that folder - if you unzipped a pack zip inside an existing clone and ended up with ComfyUI_LC123_nodes/ComfyUI_LC123_nodes/, move the inner files up a level. The console should print an LC123 load line when it comes up. No pip dependencies, but the maskers and the models don't ship with it.
Grab the 1× skin CNN from the pack's model table (openmodeldb, or notkenski/upscalers on HuggingFace) and drop it in:
ComfyUI/models/upscale_models/1xSkinContrast-High-SuperUltraCompact.pth
For the mask, the author's example workflow wires a LayerMask: PersonMaskUltra V2 matte with face + body on. That node lives in chflame163's LayerStyle Advance, not here.
Where it goes wrong
Loading a 4× model in detail 1x. You pay 4× the time, then throw three-quarters of the pixels away. The README warns about this first, and it's invisible in the output - it just gets slow.
The empty-matte no-op, and skin-coloured leaks. If the bounding box comes back empty, or blend is effectively zero, the node hands you the original frame unchanged - so "I ran it and nothing happened" usually means the chroma heuristic found no skin (heavy makeup, green screen, cool grade) or mask_source: input with a mask that's black everywhere. Check skin_mask first. The opposite failure is just as common: the auto matte happily swallows tan fabric, wood and warm walls, so lower mask_sensitivity or wire a real person mask - which is the recommended path anyway, since mask_source ships as input+chroma and quietly falls back to auto-skin if you wire nothing. Related trick: input with no MASK wired gives an all-white matte, i.e. a full-frame 1× pass.
It's a detail pass, not a detail generator. A CNN can't invent a nose, and it won't rescue a soft source - that's SeedVR2's or a face detailer's job. Run it at native resolution, before you upscale, on skin that's clean but too smooth.
PersonMaskUltra friction. It wants mediapipe, and "No module named 'mediapipe'" is a long-running annoyance in the wild. If the mask side breaks, that's a separate pack's dependency, not LC123's.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | Source image (batch ok). | |
| upscale_model | UPSCALE_MODEL | One CNN upscaler. 1× SkinContrast for detail. | |
| mode | COMBO | detail 1x | detail 1x = paste back at original size. scale = keep the model's native factor. |
| blend | FLOAT | 0.750–1 | How hard the upscaled patch replaces the original under the mask. |
| mask_source | COMBO | input+chroma | input = wired mask only. chroma = auto skin. input+chroma = AND. |
| mask_sensitivity | FLOAT | 0.550–1 | Auto-skin reach when chroma is used. |
| mask_feather | FLOAT | 0.450–1 | Soft edge on the composite matte. |
| tile | INT | 5120–2048 | CNN tile size. 0 = try full crop, shrink on OOM. |
| overlap | INT | 320–256 | Tile overlap in pixels. |
| maskopt | MASK | Person / skin matte from RMBG or similar. Stay external. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| skin_mask | MASK | — |