Despeckle Image
Killing stray white dots without killing the eye highlight
- image
- image
- mask
You know the render. Composition is fine, face is fine, and then there's a scatter of bright single-pixel crumbs across the background - salt on a sky, a dot floating on the cheek, three of them between the letters on a book spine. Classic diffusion junk. Despeckle Image is a small node that goes through a finished image and quietly erases exactly those, while leaving hair strands, catchlights in the eyes, and the sheen on a fold alone.
It's a deterministic torch op, not a diffusion pass. No re-gen, no seed lottery, no face quietly changing shape. That's the whole appeal: this layer of work - colour, blur, sharpen, dust-bust - is thirty-year-old image processing that happens to now live inside ComfyUI, and you should spend a millisecond of pixel maths on it instead of a whole sampler run. A filter that does the job is cheaper and more predictable than img2img-ing a fix.
How it decides what counts as a speck
Four things have to be true, and the author is genuinely conservative about all four.
A pixel has to be brighter than its surroundings - that's a white top-hat (the image minus its morphological "opening"), and threshold scales how much brighter it must be. On a near-black patch the required gap shrinks on its own, down to a floor of 0.03, so faint salt on a dark backdrop still goes without you lowering the slider for it.
It has to be small: the speck's blob must fit inside a window twice speck_size. Anything longer - a lit edge, a strand, a carving - is never a candidate, which is what protects detail.
It has to be isolated: the patch around it can't be crowded with other candidates. That's the rule that keeps fabric texture and the tip of a lit fold, because on a bright patch (skin, hair) a strong edge counts as crowd.
A very dark patch also gets an escape hatch for a dominant dot - a highlight on a pupil, say - because the iris next to it is brighter than the black cutoff, so the isolation test doesn't wrongly clear it.
Once a pixel is convicted, it and its dark halo get filled with a weighted average of the surrounding pixels, so a smooth gradient continues through the hole instead of a visible block. The specks that survive are mostly specks it meant to keep.
The two inputs, and the output you'll probably ignore
image- anything IMAGE. Alpha is passed through untouched; the filtering works on RGB.threshold(0–1, default 0.08) - how much brighter than its surroundings a pixel must be. Raise it and only blatant salt goes; lower it and you start eating genuine highlights and grain.speck_size(odd, 3–31, default 5) - the biggest speck to remove, in pixels. Anything that wide or wider survives, which is precisely why an eye highlight survives.
Two outputs: image (the cleaned frame - SaveImage, or back into the chain) and mask, the feather-soft map of what got replaced. Most people never touch the mask, which is fine, but it's the honest way to check your work: pipe it into a mask preview and see whether the node ate what you thought it would.
Order matters: despeckle before you upscale. Upscalers bake defects in and turn a two-pixel crumb into a twenty-pixel blob. Fix at native resolution, then grow the image.
Installing it
Search ComfyUI-Enhance-Pack in ComfyUI Manager, or:
cd ComfyUI/custom_nodes
git clone https://github.com/alchemine/comfyui-enhance-pack
Restart ComfyUI and you'll find it under EnhancePack/Image → Despeckle Image. Nothing else to install: there is no requirements.txt, no model download, no pip step - it runs on the torch and ComfyUI you already have and does the work on your GPU.
One naming note. This pack used to be comfyui-alchemine-pack, and the README says the AdjustImage id carries over from before v8.0.0 so old workflows still load. If you're following an older tutorial, that's the same author, same idea.
Where people get burned
The white dots are usually your VAE, not the model. Before you despeckle anything, check that the right VAE is loaded - mismatched VAEs are a known cause of exactly this salt-and-pepper sprinkle, and it's the first thing the troubleshooting material tells you to check for colour and dotting weirdness. Fixing the cause is one dropdown; fixing it in post is every image, forever. Use this node for the images that already exist and can't be re-rolled.
It's a picker, not a pressure washer. It deliberately won't touch specks in crowded patches, so it won't clean a genuinely noisy frame. For flat noise and banding, use the denoise slider on Adjust Image in the same pack - that's a bilateral filter, the right tool for that job.
speck_size at 31 is slow. The counting windows scale at roughly 4 × speck_size, so maxing it out means hundred-plus-pixel windows doing per-pixel statistics on a whole 2K frame. It's a last-resort setting for small text-sized speckles, not a default.
Batch memory. The whole batch is processed in one GPU pass, so eight 2K images means eight 2K images of intermediates. Chunk big batches if you're tight on VRAM.
Python version. pyproject.toml declares requires-python >= 3.12. The git-clone route doesn't care - nothing gets pip-installed - so if a Manager install acts up on an older Python, clone it instead.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| threshold | FLOAT | 0.080–1 | How much brighter than its surroundings a pixel must be to count as a speck. |
| speck_size | INT | 53–31 | Largest speck to remove, in pixels. Anything this wide or wider (an eye highlight) is kept. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| mask | MASK | — |