Nodes/comfyui-onnxtagger/ONNX Tagger (Censor Detector)
ComfyUI Node

ONNX Tagger (Censor Detector)

A WD14 tagger that also tells you the image is mosaic-censored

By sugarkwork·Created a day ago·Updated a day ago· 1
ONNX Tagger (Censor Detector)
  • image
  • tags
  • is_uncensored
  • is_censored
  • is_no_explicit
  • is_unknown
threshold0.35
character_threshold0.85
uncensored_threshold0.10
censored_threshold0.50
genital_threshold0.20
replace_underscorefalse
trailing_commafalse
exclude_tags

WD14 is the boring, reliable half of captioning: comma-separated booru tags matching how Illustrious, Pony and NoobAI were trained, which is why the standing advice is a tagger and a describer, not one or the other. What the vanilla tagger won't tell you is whether an image has a mosaic block over the interesting parts. That's the pitch of ONNX Tagger (Censor Detector) - same tag string, plus four booleans describing its censorship state.

How it works

The model is SmilingWolf's wd-eva02-large-tagger-v3 - the current EVA02-Large v3 tagger, not the older SwinV2 wd-v1-4 line - run through ONNX Runtime instead of transformers. The node downloads the FP32 ONNX, converts it to FP16 with onnxconverter_common, and keeps the ~600 MB result. Images are letterboxed onto a white square and run one at a time, RGB→BGR. No tokenizer, no CLIP, no second torch model in VRAM.

From the probability vector it then reads three scores: the uncensored tag, the max of a hard-coded list of twenty Danbooru censoring tags (mosaic_censoring, bar_censor, heart_censor and seventeen more), and max(pussy, penis). Verdict order is fixed: uncensored beats censored, censored beats no_explicit, everything left over is unknown.

Read that vocabulary as Danbooru's, not yours. censored means "contains a censorship artifact," not "this is a naughty image," and uncensored tags a work released in its uncensored version - so on fresh AI output you'll mostly land in no_explicit or unknown. Treat that last bucket as "a human should look at this," never as "safe."

The inputs worth touching

image is the only required input, and it takes a batch. Everything else has a usable default.

  • threshold (0.35) and character_threshold (0.85) govern the tag string only - general tags above the first, character tags above the second; they don't touch the four flags. Tag soup? Lower the first. Random waifu name? Raise the second.
  • censored_threshold (0.50) and genital_threshold (0.20) are the ones you'll actually tune. Drop the first to catch fainter mosaics at the cost of false positives; raise the second and more images fall into no_explicit. The defaults come from 249 images validated in the author's parent project - his, not yours.
  • replace_underscore and trailing_comma are prompt compatibility: booru tags use underscores, prompts usually don't, and the WD14 plugins emit a trailing comma. Turn both on when the string feeds a CLIP Text Encode.
  • exclude_tags is comma-separated and exact-match, checked against the final spelling - with replace_underscore on you'd write censored nipples, not censored_nipples, and censored won't sweep away its twenty siblings. It edits the string only - the flags are computed first.

Outputs

tags is a STRING in ComfyUI-WD14-Tagger's format - character tags first, then general, parentheses escaped as \( so nothing downstream reads them as emphasis - so it drops into any text node.

The four booleans (is_uncensored, is_censored, is_no_explicit, is_unknown) are for routing: sort outputs into two folders, skip the upscale on censored frames, gate a preview batch. It's an output node too, and that per-image preview prints the raw scores - the fastest way to pick thresholds. One gotcha: all five outputs are lists, so a batch of four gives you four strings and four sets of booleans.

Installing it

ComfyUI Manager, search onnxtagger (pack title comfyui-onnxtagger, publisher sugarknight), or:

cd ComfyUI/custom_nodes
git clone https://github.com/sugarkwork/comfyui-onnxtagger
pip install -r requirements.txt

Run that pip install with the Python that launches ComfyUI. Requirements: onnxruntime-gpu>=1.20, pip CUDA wheels (nvidia-cudnn-cu12 and friends) and onnx/onnxconverter_common; macOS gets CPU onnxruntime. tagger_core.py preloads the pip-wheel CUDA libraries at import, so LD_LIBRARY_PATH stays untouched.

The model sorts itself out on first run: it fetches selected_tags.csv and the ~1.2 GB FP32 ONNX from HuggingFace, converts to FP16, deletes the FP32, and drops both files in ComfyUI's models/wd14_tagger/ (the WD14 Tagger plugins register that folder; otherwise it uses its own models/). It reuses an existing FP16 model only when names match exactly - wd-eva02-large-tagger-v3-fp16.onnx and the matching .csv. Your wd-v1-4-* files won't be picked up.

Where people get burned

The first queue looks frozen. It's downloading 1.2 GB and converting it - watch the console, not the progress bar, since the node prints download percentages there. It needs internet once, and killing it mid-download leaves a .part file: delete it and requeue.

The node is missing from the menu entirely. tagger_core.py imports onnxruntime at module level, so a failed import kills the whole pack silently. Check the console traceback and re-run the pip install against the right interpreter - a second venv is the usual culprit. Clone without requirements and the FP16 conversion errors out instead.

It's slower than expected. Providers are chosen from what ONNX Runtime reports, so with no working CUDA EP it silently drops to CPUExecutionProvider - on AMD, or a mismatched CUDA stack, that's the fallback. It works, just not quickly on a 500-image folder.

Categoryimage/tagging

Inputs (9)

NameTypeDefaultDescription
imageIMAGE
thresholdFLOAT0.350–1
character_thresholdFLOAT0.850–1
uncensored_thresholdFLOAT0.100–1uncensored タグがこの値以上で is_uncensored=True
censored_thresholdFLOAT0.500–1検閲タグ群の最大値がこの値以上で is_censored=True (uncensored でない場合)
genital_thresholdFLOAT0.200–1max(pussy, penis) がこの値未満で is_no_explicit=True (上記いずれでもない場合)
replace_underscoreBOOLEANfalse
trailing_commaBOOLEANfalse
exclude_tagsSTRING

Outputs (5)

NameTypeDescription
tagsSTRING
is_uncensoredBOOLEAN
is_censoredBOOLEAN
is_no_explicitBOOLEAN
is_unknownBOOLEAN