Hotdog_NotHotdog is a CLIP-based NSFW detector for images and videos. It uses OpenAI CLIP to score similarity against a fixed set of prompts and then decides whether content is Safe For Work (SFW) or Not Safe For Work (NSFW) using either a rules-based policy or a trained RandomForest classifier.
This README describes v0.1.1 of the script.
- CLIP-powered: Uses OpenAI CLIP via
open_clip_torch, defaulting toViT-L-14 laion2b_s32b_b82k. - GPU-accelerated: Runs CLIP on GPU when available (or on CPU
otherwise), configurable via
--device. - Dual modes:
- Rules mode: Deterministic policy based on CLIP scores and a similarity threshold.
- Learned mode: RandomForest classifier on top of CLIP features (you train it yourself; no pre-trained classifier is shipped).
- Video support: Samples frames from videos at a configurable FPS
(default
1.0), scores each frame, and aggregates with per-prompt max. - Backward compatibility:
--classifier-mode,--auto-tune-thresholds, and--apply-auto-tuneremain as legacy aliases.--legacy-modelrestores the original v0.1.0 CLIP backbone (ViT-B-32 laion2b_s34b_b79k).--legacy-csvemits the original v0.1.0 CSV schema in addition to the newer per-prompt fields.
- Local operation: After the initial CLIP weight download, everything runs locally.
- Python 3.8+
- pip
- Clone or download the repository.
- Create a virtual environment:
python -m venv .venv .\.venv\Scripts\activate - Install dependencies (or use
pip install -r requirements.txt):pip install torch open_clip_torch pillow numpy tqdm scikit-learn joblib opencv-python
- Clone or download the repository.
- Create a virtual environment:
python3 -m venv .venv source .venv/bin/activate - Install dependencies:
pip install torch open_clip_torch pillow numpy tqdm scikit-learn joblib opencv-python
Scan a directory of images or videos with the simple rules policy:
python Hotdog_NotHotdog.py /path/to/images --out results.csvKey flags:
--threshold: similarity threshold for rules mode (default0.20).--device:cudaorcpu(default: auto-detect).--batch-size: batch size for CLIP inference (default32).--fps: video sampling FPS (default1.0).
Learned mode uses a RandomForest classifier on top of CLIP features. You train the classifier yourself on CSVs generated by this script; no pre-trained classifier is included.
Two modes are exposed via --mode:
--mode rules(default)--mode learned
Legacy flag --classifier-mode is still accepted and mapped to
--mode behind the scenes.
-
Prepare a labeled dataset
- Put SFW images in files whose names start with
Faces(for example:Faces_0001.png). - Put NSFW images in files with any other name.
- Put SFW images in files whose names start with
-
Generate a training CSV in rules mode
From your project root:
python Hotdog_NotHotdog.py /path/to/training_images --out training.csv
-
Auto-tune and save the learned classifier
Use the built-in auto-tune path with an explicit training CSV:
python Hotdog_NotHotdog.py /path/to/training_images \ --auto-tune \ --apply-tune \ --training-csv training.csv \ --classifier-model-path nsfw_classifier.pkl
This will:
- Train a
RandomForestClassifieron features derived from the per-prompt CLIP scores. - Search for a probability threshold that heavily penalizes SFW false positives while keeping good NSFW recall.
- Save:
- the classifier to
nsfw_classifier.pkl(or your chosen path), - thresholds/metadata to
nsfw_classifier_thresholds.json.
- the classifier to
Legacy aliases
--auto-tune-thresholdsand--apply-auto-tuneare still accepted and mapped to--auto-tune/--apply-tune. - Train a
-
Run learned mode
Once you have a classifier and thresholds saved:
python Hotdog_NotHotdog.py /path/to/images \ --mode learned \ --classifier-model-path nsfw_classifier.pkl \ --out learned_results.csv
The script will:
- Load CLIP and run it on GPU if available.
- Load your RandomForest from the given path.
- Load the learned threshold from
<classifier_stem>_thresholds.jsonif present. - Emit per-file CSV rows with both CLIP scores and the final decision (rules vs learned).
To validate that your trained classifier is behaving reasonably on a
known set, you can use the built-in sanity check. It expects a mixture
of SFW Faces* files and other NSFW samples.
python Hotdog_NotHotdog.py . \
--sanity-check \
--sanity-path test_images \
--classifier-model-path nsfw_classifier.pklBehavior:
- Uses
--sanity-pathif provided; otherwise falls back totest_images/if that folder exists. - Requires a trained classifier at
--classifier-model-path. - Runs both rules and learned decisions over the sanity set.
- Fails (exit code
1) if:- any
Faces*file is not SFW in both modes, or - no non-
Faces*file is flagged NSFW in learned mode.
- any
- Prints
Sanity check PASSEDand exits0on success.
This is useful for quick smoke tests and CI.
- SFW: files starting with
Faces(for example:Faces_001.png). - NSFW: all other files.
This convention is used both when training the classifier and when running the sanity checker.
By default, results are saved as CSV with one row per file. In v0.1.1 the schema includes explicit per-prompt scores:
filesafe_simfemale_nipples_simmale_nipples_simpenis_simvulva_simanus_simbreast_simchest_simbikini_simlingerie_simcleavage_simdecision(sfw/nsfw)reasonmode(rules/learned)
If you pass --legacy-csv, the script also emits the original
v0.1.0-style aggregate columns (nipples_sim, clothing_sim,
nsfw_youtube_guess) for easier comparison with older runs.
- High SFW precision: The auto-tuner heavily penalizes SFW
false positives when choosing a probability threshold. In practice,
the goal is that
Faces*images remain SFW in both rules and learned modes on your sanity sets. - Reasonable NSFW recall: Within that constraint, the RandomForest and threshold search are configured to recover borderline explicit samples that might be ambiguous in rules mode.
- Stable features: Feature engineering is versioned
(
FEATURE_VERSION = "v2") so that future changes can be introduced without silently invalidating older models. - Legacy compatibility:
--legacy-modeland--legacy-csvlet you compare against the original v0.1.0 behavior (CLIP backbone and CSV layout) while benefiting from the newer tooling around training and sanity checks.
- This tool is a filter, not a guarantee. No automated NSFW detector is perfect; there will always be some false negatives and false positives, especially on edge cases or out-of-distribution content.
- The built-in auto-tuning and sanity checks are designed to keep
Faces*images SFW and catch clearly explicit content, but you remain responsible for reviewing and validating results in your own workflows, products, or moderation pipelines.
Before tagging a new release or shipping a model:
-
Run sanity check
python Hotdog_NotHotdog.py . \ --sanity-check \ --sanity-path test_images \ --classifier-model-path nsfw_classifier.pkl -
Generate a fresh training CSV (optional but recommended)
python Hotdog_NotHotdog.py /path/to/training_images --out training.csv
-
Auto-tune and apply the classifier
python Hotdog_NotHotdog.py /path/to/training_images \ --auto-tune \ --apply-tune \ --training-csv training.csv \ --classifier-model-path nsfw_classifier.pkl
-
Run learned mode on a test set
python Hotdog_NotHotdog.py /path/to/test_images \ --mode learned \ --classifier-model-path nsfw_classifier.pkl \ --out learned_results.csv
-
Spot-check results
- Confirm
Faces*samples are SFW. - Confirm clearly explicit samples are NSFW.
- Optionally compare with earlier CSVs using
--legacy-csv.
- Confirm
Licensed under the Apache License, Version 2.0. See LICENSE
for the full text. Commercial use is permitted.
Copyright 2025 Creative Mayhem UG.
Contributions welcome! Please follow PEP 8 and add tests for new features where practical. By contributing, you agree that your contributions will be licensed under the Apache License, Version 2.0.