Skip to main content

yahoo-open-nsfw-onnx

Yahoo's Open-NSFW image classifier, packaged as an ONNX Runtime model with no TensorFlow, Keras or PyTorch at runtime.

The model scores an image between 0 and 1 for pornographic content. It is small (5.9M parameters, 24 MB) and fast enough on CPU that a GPU is hard to justify: 7 ms per image on four cores, in a process that holds around 130 MB of memory.

Latency, memory and cold start compared against running the same model under Keras and TensorFlow

Install

pip install yahoo-open-nsfw-onnx

The wheel bundles the .onnx file. There is no download step at first use and no network access at runtime.

Use

from PIL import Image
from yahoo_open_nsfw_onnx import Classifier

classifier = Classifier()

score = classifier.score(Image.open("photo.jpg"))
print(score)  # 0.0041

# Batching is roughly three times faster per image than one call each.
scores = classifier.score_paths(["a.jpg", "b.png", "c.webp"])

A Classifier holds one ONNX Runtime session. Sessions are safe to call from multiple threads, so build one and share it rather than creating one per request.

From the shell:

yahoo-open-nsfw-onnx photos/ --recursive --format csv > scores.csv
yahoo-open-nsfw-onnx a.jpg b.jpg
  0.004132  sfw        a.jpg
  0.981204  nsfw       b.jpg

Reading the score

The score is P(NSFW). Yahoo published no fixed cutoff and suggested tuning it per application. As a starting point, treat 0.2 and 0.8 as the edges of a band where the model is not committing to an answer:

score reading
below 0.2 safe
0.2 to 0.8 undecided, needs a second opinion
above 0.8 pornographic

Two things the model does not do, both of which catch people out:

It only detects pornography. It was trained on pornographic versus non-pornographic images. Violence, gore, weapons, drugs and hate symbols all score low. If you need those, you need a different model.

Large areas of skin-toned texture drive the score up. Running 3,594 AI generated illustrations through it, 20 crossed 0.8. Six of those were Spider-Man and Hulk pictures. A form-fitting suit on a human silhouette hits the same features as bare skin, and the model has no concept of clothing. Line art is not a defence either: pencil sketches of nudes scored above 0.99.

Treating this as a prefilter in front of a slower and more capable model works well. In that run, 3.7% of images landed at 0.2 or above, so the expensive check ran on a thirtieth of the traffic.

Performance

Measured on an Apple M4 (4 performance cores, 6 efficiency cores), macOS 26.5, ONNX Runtime 1.28. Reproduce with python benchmarks/bench.py.

batch size latency per image throughput
1 6.7 ms 6.73 ms 149 img/s
8 67 ms 8.39 ms 119 img/s
32 277 ms 8.65 ms 116 img/s
64 595 ms 9.30 ms 108 img/s

Memory, resident set size of the whole process:

stage RSS
after importing onnxruntime 47 MB
after loading the model 108 MB
serving single images 134 MB

Cold start, from importing the package to the first score, is under 0.1 s.

Thread count is worth setting explicitly. On this chip throughput peaks at the four performance cores and does not improve past them, because every thread waits at the layer barrier for the slowest one and work scheduled onto an efficiency core finishes late:

intra-op threads throughput at batch 64
2 74 img/s
4 117 img/s
6 110 img/s
8 87 img/s
10 106 img/s
Classifier(intra_op_threads=4)

Numbers above 4 threads vary run to run, which is itself the point: leaving the count unpinned makes latency unpredictable. Machines with uniform cores do not behave this way, so measure on your own hardware before tuning.

Decoding and preprocessing costs a further 1.3 ms per image on one core, around 5% of the total, and is not usually worth optimising.

Preprocessing

The model is sensitive to how images reach it, and getting this wrong shifts scores by several percent. yahoo_open_nsfw_onnx.preprocessing reproduces the original Caffe pipeline exactly:

  1. Resize to 256x256, bilinear.
  2. Encode to JPEG and decode again.
  3. Centre crop to 224x224.
  4. Convert RGB to BGR.
  5. Subtract the per-channel training means, [104, 117, 123].

Step 2 looks pointless and is not. The original model was fitted on JPEG data, and the compression artifacts are part of the distribution it learnt. Removing the round trip changes the output.

tests/test_reference_parity.py asserts that this produces byte identical arrays to the reference implementation in bhky/opennsfw2, and that the resulting scores agree to within 1e-4 with no image changing side of a threshold. It is skipped unless opennsfw2 is installed, since that pulls in Keras and TensorFlow. During development the same check was run over 3,594 photographs and illustrations, where the largest disagreement was 3e-06.

Reproducing the model file

The bundled open_nsfw.onnx is not a binary you have to take on trust:

pip install "yahoo-open-nsfw-onnx[export]"
python scripts/export_onnx.py

This loads the upstream Keras weights and re-exports them. The export runs under the Torch backend of Keras, because the TensorFlow backend routes ONNX export through tf2onnx, which pins protobuf~=3.20 and will not install alongside a current TensorFlow. The graph itself is backend independent.

tests/test_model_parity.py locks the outputs to known values so an accidental re-export cannot silently change behaviour.

Relation to other projects

The weights are Yahoo's, originally released for Caffe at yahoo/open_nsfw and unchanged since 2016. This project adds no training and claims no accuracy improvement.

bhky/opennsfw2 is the maintained Keras port and is where the weights here come from. Use it if you want to fine tune, work with video, or stay inside a Keras pipeline. This package exists for the case where you only want to run the model and would rather not install a training framework to do it.

opennsfw-onnx covers much the same ground and is the one to look at first. It was published in July 2026, is Apache-2.0, and does several things this package does not: it selects CUDA, TensorRT, DirectML or CoreML execution providers automatically, accepts file-like objects and arrays as well as paths, and lets you skip the JPEG round trip for a speed gain. It requires Python 3.11 or newer.

SectorLabs/opennsfw-standalone was the original ONNX packaging, in 2021. Its scores agree with this one to four decimal places. It has had no release since July 2021, its repository now returns 404, it pins Pillow<9.0.0, and it raises on NumPy 2 because of a copy=False argument whose meaning changed.

What is left that is particular to this package: the bundled model is re-exported from the current upstream Keras weights by a script in the repository rather than inherited from the 2021 conversion, the preprocessing is asserted byte identical to the reference implementation rather than matched approximately, and it supports Python 3.9. If none of those matter to you, use opennsfw-onnx.

Licence

The code is MIT. The model weights in src/yahoo_open_nsfw_onnx/open_nsfw.onnx are Yahoo's and carry a BSD 2-Clause licence. Both are reproduced in NOTICE.

Metadata

Release files for yahoo-open-nsfw-onnx 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for yahoo-open-nsfw-onnx 1.0.0
File Size Uploaded Provenance Actions
yahoo_open_nsfw_onnx-1.0.0.tar.gz 22.1 MB With provenance Details for yahoo_open_nsfw_onnx-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for yahoo-open-nsfw-onnx 1.0.0
File Interpreter ABI Platform Actions
yahoo_open_nsfw_onnx-1.0.0-py3-none-any.whl Python 3 none any Details for yahoo_open_nsfw_onnx-1.0.0-py3-none-any.whl

Total release size: 44.2 MB

Release files / yahoo_open_nsfw_onnx-1.0.0.tar.gz

Download URL yahoo_open_nsfw_onnx-1.0.0.tar.gz
Size 22.1 MB
Tags Source
SHA-256 checksum
How to use checksums
5f2c3001f32afbb73469773bfcf14fdef6110de6123e80d473ee374dec89891f
BLAKE2b-256 checksum
How to use checksums
660c2cb62f108410c55a93ff590f861a7f4c7dc9fe57904b05094c0906887077
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.

Transparency log

Release files / yahoo_open_nsfw_onnx-1.0.0-py3-none-any.whl

Download URL yahoo_open_nsfw_onnx-1.0.0-py3-none-any.whl
Size 22.1 MB
Tags Python 3
SHA-256 checksum
How to use checksums
581cea9f295ef059861f4917e5403d436ded8b038ec17287fa6d68fa2e632081
BLAKE2b-256 checksum
How to use checksums
f39ce58d8f39db530e4f08170678dc482bbfcf99434fba2755aba9edf1127636
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.

Transparency log

Security for yahoo-open-nsfw-onnx 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for yahoo_open_nsfw_onnx-1.0.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page