avmosaic 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- avmosaic-0.1.0/LICENSE +21 -0
- avmosaic-0.1.0/NOTICE +26 -0
- avmosaic-0.1.0/PKG-INFO +331 -0
- avmosaic-0.1.0/README.md +293 -0
- avmosaic-0.1.0/pyproject.toml +64 -0
- avmosaic-0.1.0/setup.cfg +4 -0
- avmosaic-0.1.0/src/avmosaic/__init__.py +25 -0
- avmosaic-0.1.0/src/avmosaic/__main__.py +5 -0
- avmosaic-0.1.0/src/avmosaic/censor.py +208 -0
- avmosaic-0.1.0/src/avmosaic/cli.py +673 -0
- avmosaic-0.1.0/src/avmosaic/detector.py +237 -0
- avmosaic-0.1.0/src/avmosaic/evaluate.py +312 -0
- avmosaic-0.1.0/src/avmosaic/image.py +70 -0
- avmosaic-0.1.0/src/avmosaic/labeler.py +278 -0
- avmosaic-0.1.0/src/avmosaic/labels.py +78 -0
- avmosaic-0.1.0/src/avmosaic/models.py +165 -0
- avmosaic-0.1.0/src/avmosaic/tracking.py +391 -0
- avmosaic-0.1.0/src/avmosaic/video.py +296 -0
- avmosaic-0.1.0/src/avmosaic.egg-info/PKG-INFO +331 -0
- avmosaic-0.1.0/src/avmosaic.egg-info/SOURCES.txt +32 -0
- avmosaic-0.1.0/src/avmosaic.egg-info/dependency_links.txt +1 -0
- avmosaic-0.1.0/src/avmosaic.egg-info/entry_points.txt +2 -0
- avmosaic-0.1.0/src/avmosaic.egg-info/requires.txt +9 -0
- avmosaic-0.1.0/src/avmosaic.egg-info/top_level.txt +1 -0
- avmosaic-0.1.0/tests/test_censor.py +139 -0
- avmosaic-0.1.0/tests/test_cli.py +223 -0
- avmosaic-0.1.0/tests/test_evaluate.py +181 -0
- avmosaic-0.1.0/tests/test_image.py +47 -0
- avmosaic-0.1.0/tests/test_labeler.py +109 -0
- avmosaic-0.1.0/tests/test_labels.py +30 -0
- avmosaic-0.1.0/tests/test_model.py +145 -0
- avmosaic-0.1.0/tests/test_samples.py +60 -0
- avmosaic-0.1.0/tests/test_tracking.py +284 -0
- avmosaic-0.1.0/tests/test_video.py +172 -0
avmosaic-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Donghwan Lee and avmosaic contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
avmosaic-0.1.0/NOTICE
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
avmosaic — third-party components
|
|
2
|
+
===================================
|
|
3
|
+
|
|
4
|
+
This repository contains only avmosaic's own source code (MIT, see LICENSE),
|
|
5
|
+
one public-domain test image, and a demo image derived from it. The components
|
|
6
|
+
below are installed separately as Python packages or downloaded at runtime; they
|
|
7
|
+
are not vendored here and keep their own licenses.
|
|
8
|
+
|
|
9
|
+
NudeNet (detector models: 320n.onnx, 640m.onnx)
|
|
10
|
+
https://github.com/notAI-tech/NudeNet
|
|
11
|
+
IMPORTANT: the PyPI package "nudenet" 3.4.x declares the MIT license, but the
|
|
12
|
+
GitHub repository (which also hosts the 640m weights) is licensed AGPL-3.0.
|
|
13
|
+
If you redistribute avmosaic together with NudeNet or its weights, or offer
|
|
14
|
+
it as a network service, check which license applies to you. A detector of
|
|
15
|
+
your own can be plugged in through the Python API instead.
|
|
16
|
+
|
|
17
|
+
ONNX Runtime — MIT — https://github.com/microsoft/onnxruntime
|
|
18
|
+
OpenCV (opencv-python-headless) — Apache-2.0 — https://github.com/opencv/opencv-python
|
|
19
|
+
NumPy — BSD-3-Clause — https://numpy.org
|
|
20
|
+
imageio-ffmpeg — BSD-2-Clause — https://github.com/imageio/imageio-ffmpeg
|
|
21
|
+
Its wheels include an ffmpeg binary, which is a separate program under the
|
|
22
|
+
(L)GPL and is run as a subprocess.
|
|
23
|
+
|
|
24
|
+
tests/data/astronaut.jpg, docs/demo.jpg
|
|
25
|
+
Portrait of astronaut Eileen Collins, NASA, public domain (the same image as
|
|
26
|
+
scikit-image's data.astronaut()).
|
avmosaic-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: avmosaic
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Detect and mosaic explicit regions in images and videos (CLI + Python API).
|
|
5
|
+
Author: avmosaic contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/fattaillabs/avmosaic
|
|
8
|
+
Project-URL: Repository, https://github.com/fattaillabs/avmosaic
|
|
9
|
+
Project-URL: Issues, https://github.com/fattaillabs/avmosaic/issues
|
|
10
|
+
Project-URL: Korean README, https://github.com/fattaillabs/avmosaic/blob/main/README.ko.md
|
|
11
|
+
Project-URL: Japanese README, https://github.com/fattaillabs/avmosaic/blob/main/README.ja.md
|
|
12
|
+
Keywords: censorship,mosaic,nudity-detection,pixelate,video,onnx
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
19
|
+
Classifier: Topic :: Multimedia :: Video
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
License-File: NOTICE
|
|
29
|
+
Requires-Dist: numpy>=1.23
|
|
30
|
+
Requires-Dist: opencv-python-headless>=4.8
|
|
31
|
+
Requires-Dist: onnxruntime>=1.16
|
|
32
|
+
Requires-Dist: nudenet<4,>=3.4
|
|
33
|
+
Requires-Dist: imageio-ffmpeg>=0.4.9
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
36
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# avmosaic
|
|
40
|
+
|
|
41
|
+
Detect explicit regions in images and videos and cover them with a mosaic — from the
|
|
42
|
+
command line or from Python, on Windows, macOS and Linux.
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
*Demo uses the `faces` preset on a public-domain NASA portrait; the default preset
|
|
47
|
+
censors genitalia and the anus.*
|
|
48
|
+
|
|
49
|
+
**English** · [한국어](https://github.com/fattaillabs/avmosaic/blob/main/README.ko.md) · [日本語](https://github.com/fattaillabs/avmosaic/blob/main/README.ja.md)
|
|
50
|
+
|
|
51
|
+
> **Automated detection is an aid, not a guarantee.** The detector will miss regions
|
|
52
|
+
> (unusual angles, close-ups, occlusion, lighting) and will sometimes censor the wrong
|
|
53
|
+
> thing. Review every output before publishing it. You are responsible for complying
|
|
54
|
+
> with the laws that apply to your content. Only process material you have the right to
|
|
55
|
+
> process.
|
|
56
|
+
|
|
57
|
+
## Features
|
|
58
|
+
|
|
59
|
+
- Detection with the open-source [NudeNet](https://github.com/notAI-tech/NudeNet) v3
|
|
60
|
+
ONNX models (`320n` ships with the package; `640m` is more accurate and downloaded on
|
|
61
|
+
demand). Runs on CPU, or with CUDA / DirectML / CoreML through ONNX Runtime.
|
|
62
|
+
- Mosaic tiles aligned to a fixed grid so they don't shimmer between frames. The default
|
|
63
|
+
tile size follows a commonly cited guideline: at least 1/100 of the image's long side
|
|
64
|
+
and never under 4 px. Blur and solid fill are available too.
|
|
65
|
+
- Video tracking: each region is followed with optical flow, so the mosaic moves with it
|
|
66
|
+
instead of smearing along its path. This covers the frames between detector runs, short
|
|
67
|
+
stretches where the detector loses the region, and a few frames before it is first
|
|
68
|
+
detected. Tracks end at scene cuts, and audio is kept.
|
|
69
|
+
- Every key parameter is a flag: classes, threshold, model, padding, tile size, shape,
|
|
70
|
+
hold, codec and quality.
|
|
71
|
+
- Batch processing of folders. JSON reports of every detection. A `--draw-boxes` mode
|
|
72
|
+
for tuning.
|
|
73
|
+
- Handles Unicode file names (Korean, Japanese, …), alpha channels, odd video sizes, and
|
|
74
|
+
NTSC frame rates.
|
|
75
|
+
|
|
76
|
+
## Install
|
|
77
|
+
|
|
78
|
+
Python 3.10+ is required. ffmpeg is bundled through `imageio-ffmpeg`, so there is
|
|
79
|
+
nothing else to install. On Intel Macs, note that recent ONNX Runtime releases
|
|
80
|
+
ship no Intel-macOS wheels, so `pip` can only use an older ONNX Runtime, and only if
|
|
81
|
+
one exists for your Python version (Python 3.14 on Intel macOS has none).
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
git clone https://github.com/fattaillabs/avmosaic && cd avmosaic
|
|
85
|
+
pip install . # or: pip install -e ".[dev]" for development
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
GPU (optional): replace `onnxruntime` with `onnxruntime-gpu` (NVIDIA CUDA) or
|
|
89
|
+
`onnxruntime-directml` (Windows, any GPU), then pass `--device cuda` / `--device directml`.
|
|
90
|
+
The default `--device auto` picks CUDA, then DirectML, then CoreML (Apple), then CPU.
|
|
91
|
+
|
|
92
|
+
## Quick start
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
avmosaic video.mp4 # -> video_censored.mp4
|
|
96
|
+
avmosaic photo.jpg -o out.jpg
|
|
97
|
+
avmosaic ./scenes -o ./censored # whole folder, structure mirrored
|
|
98
|
+
avmosaic video.mp4 --model 640m --detect-every 2 --hold 8
|
|
99
|
+
avmosaic photo.jpg --draw-boxes # see what was detected and how confident it was
|
|
100
|
+
avmosaic detect video.mp4 -o detections.json # detect only, no output media
|
|
101
|
+
avmosaic classes # list classes and presets
|
|
102
|
+
avmosaic models download 640m # fetch the bigger model ahead of time
|
|
103
|
+
avmosaic frames sample.mp4 -o ./frames --every 2 # video -> images to label
|
|
104
|
+
avmosaic label ./frames # mark where the mosaic must go, in your browser
|
|
105
|
+
avmosaic eval regions ./frames # is the mosaic where you marked it?
|
|
106
|
+
avmosaic eval run ./NPDI -o npdi.jsonl # benchmark on a labelled dataset (see below)
|
|
107
|
+
avmosaic eval summary npdi.jsonl
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`avmosaic FILE …` is shorthand for `avmosaic censor FILE …`.
|
|
111
|
+
|
|
112
|
+
## Parameters
|
|
113
|
+
|
|
114
|
+
| Flag | Default | Meaning |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `--classes` | `genitals` | Presets and/or class names, comma separated. Presets: `genitals` (female/male genitalia, anus), `breasts`, `buttocks`, `nudity`, `faces`, `all`. |
|
|
117
|
+
| `--threshold` | `0.25` | Minimum detection score. Lower it to censor more aggressively. |
|
|
118
|
+
| `--model` | `320n` | `320n`, `640m`, or a path to an `.onnx` file. |
|
|
119
|
+
| `--resolution` | model's own | Model input size (a multiple of 32). |
|
|
120
|
+
| `--device` | `auto` | `auto`, `cpu`, `cuda`, `tensorrt`, `directml`, `coreml`, `openvino`. |
|
|
121
|
+
| `--channels` | `rgb` | Channel order fed to the model (`bgr` reproduces upstream NudeNet exactly). |
|
|
122
|
+
| `--method` | `mosaic` | `mosaic`, `blur` or `fill`. |
|
|
123
|
+
| `--block-size` | `auto` | Mosaic tile: `auto`, a pixel count (`24`), or a share of the long side (`2%`). |
|
|
124
|
+
| `--shape` | `rect` | `rect`, or `ellipse` (still covers the whole box). |
|
|
125
|
+
| `--padding` | `0.15` | Grow each box by this fraction of its size on every side. |
|
|
126
|
+
| `--fill-color` | `black` | For `--method fill`: `black`, `white`, `#rrggbb`, `r,g,b`. |
|
|
127
|
+
| `--detect-every` | `1` | Video: run the detector on every Nth frame (and on every scene cut). Tracking fills the frames in between. |
|
|
128
|
+
| `--tracking` | `on` | Video: follow regions with optical flow. `off` censors the union of boxes found within `--hold` frames instead (simpler, but smears fast motion). |
|
|
129
|
+
| `--hold` | `5` | Tracking on: follow a new region back N frames before its first detection. Off: censor boxes found up to N frames before and after. |
|
|
130
|
+
| `--max-lost` | `15` | Tracking on: keep following a region N frames after the detector lost it. |
|
|
131
|
+
| `--smooth` | `2` | Tracking on: average box positions over ±N frames (the raw box is always covered too). |
|
|
132
|
+
| `--codec` / `--crf` / `--preset` | `libx264` / `18` / `medium` | Video encoder settings. `--ffmpeg-args "…"` passes extra encoder flags. |
|
|
133
|
+
| `--no-audio` | off | Drop the audio track. Audio is otherwise copied, and converted to AAC when the source codec doesn't belong in MP4 (e.g. PCM). |
|
|
134
|
+
| `--report FILE` | — | Write every detection (per frame for video) as JSON. |
|
|
135
|
+
| `--draw-boxes` | off | Draw boxes, classes and scores on the output. |
|
|
136
|
+
| `--progress` | `auto` | Progress: a bar in a terminal (`auto`); `always` also logs a line every 10 s when output goes to a file. |
|
|
137
|
+
| `-o`, `--suffix`, `-y` | — | Output file or folder, suffix used when `-o` is absent, overwrite. |
|
|
138
|
+
|
|
139
|
+
### Tuning
|
|
140
|
+
|
|
141
|
+
1. Run `avmosaic sample.mp4 --draw-boxes --report r.json` and look at the result.
|
|
142
|
+
2. Missed regions → lower `--threshold` (e.g. `0.15`), try `--model 640m`, raise `--hold`
|
|
143
|
+
and `--padding`.
|
|
144
|
+
3. Too much censored → raise `--threshold`, and check which classes the report says
|
|
145
|
+
were found.
|
|
146
|
+
|
|
147
|
+
## Recommended settings
|
|
148
|
+
|
|
149
|
+
These are starting points, not guarantees. Check the result with `--draw-boxes` and adjust.
|
|
150
|
+
|
|
151
|
+
| Goal | Flags | Notes |
|
|
152
|
+
|---|---|---|
|
|
153
|
+
| Balanced (default) | none | `320n`, `--threshold 0.25`, tracking on |
|
|
154
|
+
| Fewer false alarms | `--threshold 0.32` (or `0.35`) | On 7,783 swimwear photos, false alarms fell from 0.8% at 0.25 to 0.4% at 0.35. On a 12-minute test clip, 0.32 cut the mosaic segments from 59 to 33 and still caught every long segment. |
|
|
155
|
+
| Fewer misses (safety first) | `--threshold 0.15 --model 640m --max-lost 30 --hold 8 --padding 0.25` | Lower bar, bigger model, longer tracking, larger boxes. Slower, and censors more. |
|
|
156
|
+
| Faster | `--detect-every 2` (30 fps video) or `--detect-every 3` (60 fps) | Tracking fills the frames in between. A 1080p clip went from 22.0 s to 16.1 s with `--detect-every 2`. |
|
|
157
|
+
| Coarser mosaic on small videos | `--block-size 2%` | `auto` is max(4 px, 1/100 of the long side), so a 640×360 video gets 7 px tiles. |
|
|
158
|
+
| Also cover breasts | `--classes genitals,breasts` | |
|
|
159
|
+
| Anonymize faces | `--classes faces` | |
|
|
160
|
+
|
|
161
|
+
## How tracking works
|
|
162
|
+
|
|
163
|
+
For each region the detector finds, avmosaic keeps a track and follows it frame by frame
|
|
164
|
+
with median-flow optical flow (Lucas–Kanade on a grid of points, with a forward–backward
|
|
165
|
+
check). The tracker is built to cover too much rather than too little:
|
|
166
|
+
|
|
167
|
+
- **Between detector runs** (`--detect-every N`): when the next detection arrives, the
|
|
168
|
+
accumulated flow drift is spread back over the frames in between. Both the flow path and
|
|
169
|
+
the corrected path are covered.
|
|
170
|
+
- **Detector misses**: the track continues on optical flow for up to `--max-lost` frames.
|
|
171
|
+
If flow cannot follow (occlusion, blur, a featureless region), the box covers both "it
|
|
172
|
+
stopped" and "it kept its recent speed". When the detector finds the region again within
|
|
173
|
+
`--max-lost` frames, the frames of the gap are still buffered, so they also get the
|
|
174
|
+
straight path between the two detections. The box also grows 1% per frame without a
|
|
175
|
+
detection, up to +50%.
|
|
176
|
+
- **Before the first detection**: a new track is followed backwards over `--hold` frames.
|
|
177
|
+
- **Scene cuts**: these end all tracks and force a detection. Nothing is carried across
|
|
178
|
+
the cut.
|
|
179
|
+
|
|
180
|
+
Frames are rendered with a look-ahead of `max(--max-lost, --hold, --smooth,
|
|
181
|
+
--detect-every − 1)` frames. At the defaults that is 15 frames, or about 100 MB at 1080p.
|
|
182
|
+
|
|
183
|
+
## Checking that the mosaic is in the right place
|
|
184
|
+
|
|
185
|
+
Whether a file is explicit is the wrong question for a censoring tool. What matters is
|
|
186
|
+
whether the mosaic lands where it should. A person marks that once, on a few samples, and
|
|
187
|
+
then it can be checked automatically every time the code or settings change:
|
|
188
|
+
|
|
189
|
+
1. `avmosaic frames sample.mp4 -o ./frames --every 2` saves a frame every 2 seconds.
|
|
190
|
+
2. `avmosaic label ./frames` opens a local page (served on 127.0.0.1 only). Drag a box
|
|
191
|
+
around every region that must be censored. Press **C** for an image that must stay
|
|
192
|
+
clean. Boxes are saved next to each image as `<name>.json`. The page starts empty;
|
|
193
|
+
`--suggest` pre-fills the detector's boxes, which is faster but biases the labels
|
|
194
|
+
towards what the detector already finds.
|
|
195
|
+
3. `avmosaic eval regions ./frames` prints a text report:
|
|
196
|
+
- how much of each marked region the mosaic covers, with the worst cases listed
|
|
197
|
+
- how much of the mosaic falls outside the marked regions
|
|
198
|
+
- whether any clean image was censored
|
|
199
|
+
|
|
200
|
+
It exits with status 1 if a region is covered less than 90% (`--min-coverage`) or a
|
|
201
|
+
clean image loses more than 2% (`--max-false-area`). It takes the same detection and
|
|
202
|
+
censoring flags as `censor`, so settings can be compared directly.
|
|
203
|
+
`AVMOSAIC_SAMPLES=./frames pytest tests/test_samples.py` runs the same check as tests.
|
|
204
|
+
|
|
205
|
+
## Trying it on real footage
|
|
206
|
+
|
|
207
|
+
avmosaic does not include, host or distribute any explicit material, and its tests use
|
|
208
|
+
none. To try it on real footage, you can use a public research dataset such as
|
|
209
|
+
[The Image and video dataset for adult content detection in image and video](https://figshare.com/articles/dataset/The_Image_and_video_dataset_for_adult_content_detection_in_image_and_video/14495367?file=27771210)
|
|
210
|
+
(figshare).
|
|
211
|
+
|
|
212
|
+
> **18+, at your own risk.** Such datasets contain explicit adult content. Before
|
|
213
|
+
> downloading, read the dataset page, including its license and disclaimer. Use the data
|
|
214
|
+
> at your own risk and only where that is lawful. Never commit files from it to this
|
|
215
|
+
> repository or attach them to issues.
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
avmosaic "some video.mp4" --draw-boxes --report report.json # then watch the result
|
|
219
|
+
avmosaic frames "some video.mp4" -o ./frames --every 2 # label + eval regions
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
More about test data and private samples: [tests/data/README.md](https://github.com/fattaillabs/avmosaic/blob/main/tests/data/README.md).
|
|
223
|
+
|
|
224
|
+
## Benchmarking on NPDI / Pornography-2k
|
|
225
|
+
|
|
226
|
+
These research datasets are not public downloads. They contain copyrighted adult material,
|
|
227
|
+
so their maintainers share them only after a formal request and a signed responsibility
|
|
228
|
+
agreement:
|
|
229
|
+
|
|
230
|
+
- **NPDI** (800 videos, ~80 h; half clean, including 200 "difficult" clips such as beaches
|
|
231
|
+
and wrestling): Prof. Sandra Avila, UNICAMP.
|
|
232
|
+
- **Pornography-2k** (2,000 videos, ~140 h): Prof. Anderson Rocha, UNICAMP.
|
|
233
|
+
|
|
234
|
+
Both have per-video labels only, without region boxes, so `avmosaic eval` measures what
|
|
235
|
+
those labels allow. It also accepts folders of images, where each image counts as one
|
|
236
|
+
sample, e.g. explicit images vs. swimwear photos as hard negatives. A file's label comes
|
|
237
|
+
from its path below (and including) the folder you pass: anything matching
|
|
238
|
+
`no[nt][-_ ]?porn` (`nonporn`, `non_porn`, `not_porn`, …) is clean, then anything matching
|
|
239
|
+
`porn` is explicit. Change this with `--positive` and `--negative`.
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
avmosaic eval run ./NPDI -o npdi-320n.jsonl --sample-fps 1 # resumable
|
|
243
|
+
avmosaic eval run ./NPDI -o npdi-640m.jsonl --model 640m
|
|
244
|
+
avmosaic eval summary npdi-320n.jsonl npdi-640m.jsonl --thresholds 0.15,0.25,0.35
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The summary reports the following, as text only:
|
|
248
|
+
|
|
249
|
+
- the file-level AUC
|
|
250
|
+
- for each threshold, the share of explicit and clean videos that get censored at all,
|
|
251
|
+
and the mean share of sampled frames censored (split into easy and difficult clean
|
|
252
|
+
videos when the path says so)
|
|
253
|
+
- the clean files that scored highest, with timestamps, so you can check them for false
|
|
254
|
+
alarms
|
|
255
|
+
- the explicit files where nothing was found
|
|
256
|
+
|
|
257
|
+
## Speed
|
|
258
|
+
|
|
259
|
+
Measured on an Apple M1 (CoreML) with a 1080p, 30 fps, 20-second clip (600 frames):
|
|
260
|
+
|
|
261
|
+
| Settings | Time | Throughput |
|
|
262
|
+
|---|---|---|
|
|
263
|
+
| default (`320n`) | 22.0 s | 27 fps |
|
|
264
|
+
| `--detect-every 2` | 16.1 s | 37 fps |
|
|
265
|
+
| `--model 640m --detect-every 3` | 75.6 s | 7.9 fps |
|
|
266
|
+
|
|
267
|
+
Detection alone takes 15.7 ms per 1080p frame for `320n` (33.1 ms on CPU) and 346 ms for
|
|
268
|
+
`640m` (469 ms on CPU). Re-encoding the same clip with ffmpeg alone takes 8.3 s.
|
|
269
|
+
|
|
270
|
+
Tracking added about 10% in a separate run on the same clip (31.8 s → 34.8 s). The clip
|
|
271
|
+
contained no regions to follow, so this is the scene-cut check. With tracking,
|
|
272
|
+
`--detect-every 3` keeps boxes on target and took 20.0 s.
|
|
273
|
+
|
|
274
|
+
## Python API
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
from avmosaic import CensorOptions, VideoOptions, censor_image, censor_video, create_detector
|
|
278
|
+
|
|
279
|
+
detector = create_detector("320n", classes=["FEMALE_GENITALIA_EXPOSED", "MALE_GENITALIA_EXPOSED"],
|
|
280
|
+
threshold=0.2)
|
|
281
|
+
censor_image("in.jpg", "out.jpg", detector, CensorOptions(block_size="2%"))
|
|
282
|
+
censor_video("in.mp4", "out.mp4", detector, CensorOptions(), VideoOptions(hold=8))
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Any object with a `detect(bgr_image) -> list[Detection]` method can replace the
|
|
286
|
+
detector, for example a model you trained yourself.
|
|
287
|
+
|
|
288
|
+
## Testing
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
pip install -e ".[dev]"
|
|
292
|
+
pytest # everything (about 10 s)
|
|
293
|
+
pytest -m "not model" # skip the tests that run the real model
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
The tests use no explicit material.
|
|
297
|
+
|
|
298
|
+
- **Pipeline tests** replace the model with a colour detector that treats a synthetic
|
|
299
|
+
red/black checkerboard as the region to censor. They check mosaic geometry, frame count,
|
|
300
|
+
audio and duration, the frame-hold logic (a deliberately missed frame leaks with
|
|
301
|
+
`hold=0` and is covered with `hold=2`), odd sizes, NTSC rates and Unicode paths.
|
|
302
|
+
- **Tracking tests** run the planner on in-memory frames and measure exactly how much of
|
|
303
|
+
the true object box is covered in every frame. The scenarios are fast motion, 15-frame
|
|
304
|
+
detector misses, curved paths with `--detect-every 6`, backfill, scene cuts, two objects
|
|
305
|
+
moving in opposite directions, and forced optical-flow failure. In the fast-motion test,
|
|
306
|
+
a 40 px object stays fully covered either way. Tracking censors 1.03× the object's area
|
|
307
|
+
at 8 px/frame and 1.14× at 25 px/frame. `--tracking off` censors 2.70× and 5.48×.
|
|
308
|
+
- **Model tests** run the real NudeNet model. They check that its output matches upstream
|
|
309
|
+
`nudenet` exactly in BGR mode, and use the model's *face* class on a public-domain photo
|
|
310
|
+
as an end-to-end check.
|
|
311
|
+
- **Your own accuracy check:** put labelled images in a private folder (never commit them)
|
|
312
|
+
and run `AVMOSAIC_SAMPLES=/path/to/samples pytest tests/test_samples.py -v`. See
|
|
313
|
+
[tests/test_samples.py](https://github.com/fattaillabs/avmosaic/blob/main/tests/test_samples.py) for the label format.
|
|
314
|
+
|
|
315
|
+
CI runs the suite on Linux, macOS and Windows with Python 3.10, 3.12 and 3.14.
|
|
316
|
+
|
|
317
|
+
## Limitations
|
|
318
|
+
|
|
319
|
+
- The detector is the main source of error. NudeNet was trained on general nudity
|
|
320
|
+
images. Small regions in large frames, extreme close-ups and partial occlusion are
|
|
321
|
+
hard for it.
|
|
322
|
+
- Variable-frame-rate video is converted to a constant rate (this keeps audio in sync).
|
|
323
|
+
- Subtitle and data streams are not copied. Odd video sizes are padded by one pixel for
|
|
324
|
+
H.264.
|
|
325
|
+
- Image metadata (EXIF) is not copied to the output. The EXIF orientation is applied
|
|
326
|
+
first.
|
|
327
|
+
|
|
328
|
+
## License
|
|
329
|
+
|
|
330
|
+
MIT for this project's code — see [LICENSE](https://github.com/fattaillabs/avmosaic/blob/main/LICENSE). Third-party components, including
|
|
331
|
+
an important note about NudeNet's license, are listed in [NOTICE](https://github.com/fattaillabs/avmosaic/blob/main/NOTICE).
|