From a8baab288ec09a91bdce3360f0198116f8d57a46 Mon Sep 17 00:00:00 2001 From: narugo1992 Date: Wed, 11 Sep 2024 15:01:14 +0800 Subject: [PATCH 1/8] dev(narugo): add nudenet detection support --- imgutils/detect/__init__.py | 1 + imgutils/detect/nudenet.py | 121 ++++++++++++++++++++++++++++++++++++ imgutils/detect/text.py | 2 +- test/detect/test_nudenet.py | 75 ++++++++++++++++++++++ 4 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 imgutils/detect/nudenet.py create mode 100644 test/detect/test_nudenet.py diff --git a/imgutils/detect/__init__.py b/imgutils/detect/__init__.py index 2252e8641c8..fbe64d360e8 100644 --- a/imgutils/detect/__init__.py +++ b/imgutils/detect/__init__.py @@ -14,6 +14,7 @@ from .halfbody import detect_halfbody from .hand import detect_hands from .head import detect_heads +from .nudenet import detect_with_nudenet from .person import detect_person from .text import detect_text from .visual import detection_visualize diff --git a/imgutils/detect/nudenet.py b/imgutils/detect/nudenet.py new file mode 100644 index 00000000000..ae58ec60643 --- /dev/null +++ b/imgutils/detect/nudenet.py @@ -0,0 +1,121 @@ +from functools import lru_cache +from pprint import pprint +from typing import Tuple, List + +import matplotlib.pyplot as plt +import numpy as np +from PIL import Image +from hbutils.testing.requires.version import VersionInfo +from huggingface_hub import hf_hub_download + +from imgutils.data import ImageTyping +from imgutils.utils import open_onnx_model +from test.testings import get_testfile +from ..data import load_image + + +def _check_compatibility() -> bool: + import onnxruntime + if VersionInfo(onnxruntime.__version__) < '1.18': + raise EnvironmentError(f'Nudenet not supported on onnxruntime {onnxruntime.__version__}, ' + f'please upgrade it to 1.18+ version.\n' + f'If you are running on CPU, use "pip install -U onnxruntime" .\n' + f'If you are running on GPU, use "pip install -U onnxruntime-gpu" .') # pragma: no cover + + +_REPO_ID = 'deepghs/nudenet_onnx' + + +@lru_cache() +def _open_nudenet_yolo(): + return open_onnx_model(hf_hub_download( + repo_id=_REPO_ID, + repo_type='model', + filename='320n.onnx', + )) + + +@lru_cache() +def _open_nudenet_nms(): + return open_onnx_model(hf_hub_download( + repo_id=_REPO_ID, + repo_type='model', + filename='nms-yolov8.onnx', + )) + + +def _nn_preprocessing(image: ImageTyping, model_size: int = 320) \ + -> Tuple[np.ndarray, float]: + image = load_image(image, mode='RGB', force_background='white') + assert image.mode == 'RGB' + mat = np.array(image) + + max_size = max(image.width, image.height) + + mat_pad = np.zeros((max_size, max_size, 3), dtype=np.uint8) + mat_pad[:mat.shape[0], :mat.shape[1], :] = mat + img_resized = Image.fromarray(mat_pad, mode='RGB').resize((model_size, model_size), resample=Image.BILINEAR) + + input_data = np.array(img_resized).transpose(2, 0, 1).astype(np.float32) / 255.0 + input_data = np.expand_dims(input_data, axis=0) + return input_data, max_size / model_size + + +def _make_np_config(topk: int = 100, iou_threshold: float = 0.45, score_threshold: float = 0.25) -> np.ndarray: + return np.array([topk, iou_threshold, score_threshold]).astype(np.float32) + + +def _nn_postprocess(selected, global_ratio: float): + bboxes = [] + num_boxes = selected.shape[0] + for idx in range(num_boxes): + data = selected[idx, :] + + scores = data[4:] + score = np.max(scores) + label = np.argmax(scores) + + box = data[:4] * global_ratio + x = (box[0] - 0.5 * box[2]).item() + y = (box[1] - 0.5 * box[3]).item() + w = box[2].item() + h = box[3].item() + + bboxes.append(((x, y, x + w, y + h), _LABELS[label], score.item())) + + return bboxes + + +_LABELS = [ + "FEMALE_GENITALIA_COVERED", + "FACE_FEMALE", + "BUTTOCKS_EXPOSED", + "FEMALE_BREAST_EXPOSED", + "FEMALE_GENITALIA_EXPOSED", + "MALE_BREAST_EXPOSED", + "ANUS_EXPOSED", + "FEET_EXPOSED", + "BELLY_COVERED", + "FEET_COVERED", + "ARMPITS_COVERED", + "ARMPITS_EXPOSED", + "FACE_MALE", + "BELLY_EXPOSED", + "MALE_GENITALIA_EXPOSED", + "ANUS_COVERED", + "FEMALE_BREAST_COVERED", + "BUTTOCKS_COVERED" +] + + +def detect_with_nudenet(image: ImageTyping, topk: int = 100, + iou_threshold: float = 0.45, score_threshold: float = 0.25) \ + -> List[Tuple[Tuple[int, int, int, int], str, float]]: + _check_compatibility() + input_, global_ratio = _nn_preprocessing(image, model_size=320) + config = _make_np_config(topk, iou_threshold, score_threshold) + output0, = _open_nudenet_yolo().run(['output0'], {'images': input_}) + selected, = _open_nudenet_nms().run(['selected'], {'detection': output0, 'config': config}) + return _nn_postprocess(selected[0], global_ratio=global_ratio) + + diff --git a/imgutils/detect/text.py b/imgutils/detect/text.py index 28208b70c0c..b097c821140 100644 --- a/imgutils/detect/text.py +++ b/imgutils/detect/text.py @@ -118,7 +118,7 @@ def _get_bounding_box_of_text(image: ImageTyping, model: str, threshold: float) return bboxes -@deprecated(deprecated_in="0.2.10", removed_in="0.4", current_version=__VERSION__, +@deprecated(deprecated_in="0.2.10", removed_in="0.5", current_version=__VERSION__, details="Use the new function :func:`imgutils.ocr.detect_text_with_ocr` instead") def detect_text(image: ImageTyping, model: str = _DEFAULT_MODEL, threshold: float = 0.05, max_area_size: Optional[int] = 640): diff --git a/test/detect/test_nudenet.py b/test/detect/test_nudenet.py new file mode 100644 index 00000000000..5bf43bbf2c2 --- /dev/null +++ b/test/detect/test_nudenet.py @@ -0,0 +1,75 @@ +import pytest +from PIL import Image + +from imgutils.detect import detect_with_nudenet +from imgutils.detect.nudenet import _open_nudenet_nms, _open_nudenet_yolo +from ..testings import get_testfile + + +@pytest.fixture(scope='module', autouse=True) +def _release_model_after_run(): + try: + yield + finally: + _open_nudenet_yolo.cache_clear() + _open_nudenet_nms.cache_clear() + + +@pytest.fixture() +def nude_girl_file(): + return get_testfile('nude_girl.png') + + +@pytest.fixture() +def nude_girl_image(nude_girl_file): + return Image.open(nude_girl_file) + + +@pytest.fixture() +def nude_girl_detection(): + return [ + ((321.3878631591797, 242.3542022705078, 429.8410186767578, 345.7248992919922), + 'FEMALE_BREAST_EXPOSED', + 0.832775890827179), + ((207.8404312133789, 243.68451690673828, 307.2947006225586, 336.3175582885742), + 'FEMALE_BREAST_EXPOSED', + 0.8057667016983032), + ((203.23711395263672, + 348.42012786865234, + 351.32117462158203, + 511.34781646728516), + 'BELLY_EXPOSED', + 0.7703637480735779), + ((280.81117248535156, + 678.6565170288086, + 436.11827087402344, + 767.8816909790039), + 'FEET_EXPOSED', + 0.747696578502655), + ((185.25140380859375, 518.0437889099121, 252.96240234375, 625.8465919494629), + 'FEMALE_GENITALIA_EXPOSED', + 0.7381105422973633), + ((287.9706840515137, 124.07051467895508, 392.7693061828613, 225.3848991394043), + 'FACE_FEMALE', + 0.6556487083435059), + ((103.20288848876953, + 564.7838439941406, + 352.05843353271484, + 707.6390075683594), + 'BUTTOCKS_EXPOSED', + 0.44306617975234985), + ((396.1982898712158, 224.24786376953125, 450.53956413269043, 290.279541015625), + 'ARMPITS_EXPOSED', + 0.31386712193489075) + ] + + +@pytest.mark.unittest +class TestDetectNudeNet: + def test_detect_with_nudenet_file(self, nude_girl_file, nude_girl_detection): + detection = detect_with_nudenet(nude_girl_file) + assert detection == pytest.approx(nude_girl_detection) + + def test_detect_with_nudenet_image(self, nude_girl_image, nude_girl_detection): + detection = detect_with_nudenet(nude_girl_image) + assert detection == pytest.approx(nude_girl_detection) From e668027963602bed0367ba35d88757c5327abe91 Mon Sep 17 00:00:00 2001 From: narugo1992 Date: Wed, 11 Sep 2024 15:02:26 +0800 Subject: [PATCH 2/8] dev(narugo): slightly fix the code --- imgutils/detect/nudenet.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/imgutils/detect/nudenet.py b/imgutils/detect/nudenet.py index ae58ec60643..ec12162ffb0 100644 --- a/imgutils/detect/nudenet.py +++ b/imgutils/detect/nudenet.py @@ -1,8 +1,8 @@ +# NudeNet Model, from https://github.com/notAI-tech/NudeNet +# The ONNX models are hosted on https://huggingface.co/deepghs/nudenet_onnx from functools import lru_cache -from pprint import pprint from typing import Tuple, List -import matplotlib.pyplot as plt import numpy as np from PIL import Image from hbutils.testing.requires.version import VersionInfo @@ -10,7 +10,6 @@ from imgutils.data import ImageTyping from imgutils.utils import open_onnx_model -from test.testings import get_testfile from ..data import load_image @@ -117,5 +116,3 @@ def detect_with_nudenet(image: ImageTyping, topk: int = 100, output0, = _open_nudenet_yolo().run(['output0'], {'images': input_}) selected, = _open_nudenet_nms().run(['selected'], {'detection': output0, 'config': config}) return _nn_postprocess(selected[0], global_ratio=global_ratio) - - From cd95a43f830dd4d920834df8abe7cbfb03359c3b Mon Sep 17 00:00:00 2001 From: narugo1992 Date: Wed, 11 Sep 2024 15:13:31 +0800 Subject: [PATCH 3/8] dev(narugo): add docs for nudenet --- docs/source/api_doc/detect/index.rst | 1 + docs/source/api_doc/detect/nudenet.rst | 14 ++++ .../detect/nudenet_detect_benchmark.plot.py | 34 ++++++++ .../detect/nudenet_detect_demo.plot.py | 19 +++++ imgutils/detect/nudenet.py | 82 ++++++++++++++++++- 5 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 docs/source/api_doc/detect/nudenet.rst create mode 100644 docs/source/api_doc/detect/nudenet_detect_benchmark.plot.py create mode 100644 docs/source/api_doc/detect/nudenet_detect_demo.plot.py diff --git a/docs/source/api_doc/detect/index.rst b/docs/source/api_doc/detect/index.rst index 48a1360957d..1d45e9c5ac6 100644 --- a/docs/source/api_doc/detect/index.rst +++ b/docs/source/api_doc/detect/index.rst @@ -15,6 +15,7 @@ imgutils.detect halfbody hand head + nudenet person text visual diff --git a/docs/source/api_doc/detect/nudenet.rst b/docs/source/api_doc/detect/nudenet.rst new file mode 100644 index 00000000000..9b9400a7579 --- /dev/null +++ b/docs/source/api_doc/detect/nudenet.rst @@ -0,0 +1,14 @@ +imgutils.detect.nudenet +========================== + +.. currentmodule:: imgutils.detect.nudenet + +.. automodule:: imgutils.detect.nudenet + + +detect_with_nudenet +------------------------------ + +.. autofunction:: detect_with_nudenet + + diff --git a/docs/source/api_doc/detect/nudenet_detect_benchmark.plot.py b/docs/source/api_doc/detect/nudenet_detect_benchmark.plot.py new file mode 100644 index 00000000000..c79ca3f8c87 --- /dev/null +++ b/docs/source/api_doc/detect/nudenet_detect_benchmark.plot.py @@ -0,0 +1,34 @@ +import random + +from benchmark import BaseBenchmark, create_plot_cli +from imgutils.detect import detect_with_nudenet + + +class NudenetDetectBenchmark(BaseBenchmark): + def __init__(self): + BaseBenchmark.__init__(self) + + def load(self): + from imgutils.detect.nudenet import _open_nudenet_yolo, _open_nudenet_nms + _ = _open_nudenet_yolo() + _ = _open_nudenet_nms() + + def unload(self): + from imgutils.detect.nudenet import _open_nudenet_yolo, _open_nudenet_nms + _open_nudenet_yolo.cache_clear() + _open_nudenet_nms.cache_clear() + + def run(self): + image_file = random.choice(self.all_images) + _ = detect_with_nudenet(image_file) + + +if __name__ == '__main__': + create_plot_cli( + [ + ('Nudenet', NudenetDetectBenchmark()), + ], + title='Benchmark for Anime NudeNet Detections', + run_times=10, + try_times=20, + )() diff --git a/docs/source/api_doc/detect/nudenet_detect_demo.plot.py b/docs/source/api_doc/detect/nudenet_detect_demo.plot.py new file mode 100644 index 00000000000..cac1cb6134b --- /dev/null +++ b/docs/source/api_doc/detect/nudenet_detect_demo.plot.py @@ -0,0 +1,19 @@ +from imgutils.detect.nudenet import _LABELS, detect_with_nudenet +from imgutils.detect.visual import detection_visualize +from plot import image_plot + + +def _detect(img, **kwargs): + return detection_visualize(img, detect_with_nudenet(img, **kwargs), _LABELS) + + +if __name__ == '__main__': + image_plot( + (_detect('nudenet/nude_girl.png'), 'simple nude'), + (_detect('nudenet/simple_sex.jpg'), 'simple sex'), + (_detect('nudenet/complex_pose.jpg'), 'complex pose'), + (_detect('nudenet/complex_sex.jpg'), 'complex sex'), + columns=2, + figsize=(9, 9), + autonudenet=False, + ) diff --git a/imgutils/detect/nudenet.py b/imgutils/detect/nudenet.py index ec12162ffb0..5ab239ea23c 100644 --- a/imgutils/detect/nudenet.py +++ b/imgutils/detect/nudenet.py @@ -1,5 +1,31 @@ -# NudeNet Model, from https://github.com/notAI-tech/NudeNet -# The ONNX models are hosted on https://huggingface.co/deepghs/nudenet_onnx +""" +Overview: + This module provides functionality for detecting nudity in images using the NudeNet model. + + The module includes functions for preprocessing images, running the NudeNet YOLO model, + applying non-maximum suppression (NMS), and postprocessing the results. It utilizes + ONNX models hosted on `deepghs/nudenet_onnx `_ + for efficient inference. The original project is + `notAI-tech/NudeNet `_. + + .. collapse:: Overview of NudeNet Detect (NSFW Warning!!!) + + .. image:: nudenet_detect_demo.plot.py.svg + :align: center + + The main function :func:`detect_with_nudenet` can be used to perform nudity detection on + given images, returning a list of bounding boxes, labels, and confidence scores. + + This is an overall benchmark of all the nudenet models: + + .. image:: nudenet_detect_benchmark.plot.py.svg + :align: center + + .. note:: + + This module requires onnxruntime version 1.18 or higher. +""" + from functools import lru_cache from typing import Tuple, List @@ -14,6 +40,11 @@ def _check_compatibility() -> bool: + """ + Check if the installed onnxruntime version is compatible with NudeNet. + + :raises EnvironmentError: If the onnxruntime version is less than 1.18. + """ import onnxruntime if VersionInfo(onnxruntime.__version__) < '1.18': raise EnvironmentError(f'Nudenet not supported on onnxruntime {onnxruntime.__version__}, ' @@ -27,6 +58,11 @@ def _check_compatibility() -> bool: @lru_cache() def _open_nudenet_yolo(): + """ + Open and cache the NudeNet YOLO ONNX model. + + :return: The loaded ONNX model for YOLO. + """ return open_onnx_model(hf_hub_download( repo_id=_REPO_ID, repo_type='model', @@ -36,6 +72,11 @@ def _open_nudenet_yolo(): @lru_cache() def _open_nudenet_nms(): + """ + Open and cache the NudeNet NMS ONNX model. + + :return: The loaded ONNX model for NMS. + """ return open_onnx_model(hf_hub_download( repo_id=_REPO_ID, repo_type='model', @@ -43,8 +84,14 @@ def _open_nudenet_nms(): )) -def _nn_preprocessing(image: ImageTyping, model_size: int = 320) \ - -> Tuple[np.ndarray, float]: +def _nn_preprocessing(image: ImageTyping, model_size: int = 320) -> Tuple[np.ndarray, float]: + """ + Preprocess the input image for the NudeNet model. + + :param image: The input image. + :param model_size: The size to which the image should be resized (default: 320). + :return: A tuple containing the preprocessed image array and the scaling ratio. + """ image = load_image(image, mode='RGB', force_background='white') assert image.mode == 'RGB' mat = np.array(image) @@ -61,10 +108,25 @@ def _nn_preprocessing(image: ImageTyping, model_size: int = 320) \ def _make_np_config(topk: int = 100, iou_threshold: float = 0.45, score_threshold: float = 0.25) -> np.ndarray: + """ + Create a configuration array for the NMS model. + + :param topk: The maximum number of detections to keep (default: 100). + :param iou_threshold: The IoU threshold for NMS (default: 0.45). + :param score_threshold: The score threshold for detections (default: 0.25). + :return: A numpy array containing the configuration parameters. + """ return np.array([topk, iou_threshold, score_threshold]).astype(np.float32) def _nn_postprocess(selected, global_ratio: float): + """ + Postprocess the model output to generate bounding boxes and labels. + + :param selected: The output from the NMS model. + :param global_ratio: The scaling ratio to apply to the bounding boxes. + :return: A list of tuples, each containing a bounding box, label, and confidence score. + """ bboxes = [] num_boxes = selected.shape[0] for idx in range(num_boxes): @@ -110,6 +172,18 @@ def _nn_postprocess(selected, global_ratio: float): def detect_with_nudenet(image: ImageTyping, topk: int = 100, iou_threshold: float = 0.45, score_threshold: float = 0.25) \ -> List[Tuple[Tuple[int, int, int, int], str, float]]: + """ + Detect nudity in the given image using the NudeNet model. + + :param image: The input image to analyze. + :param topk: The maximum number of detections to keep (default: 100). + :param iou_threshold: The IoU threshold for NMS (default: 0.45). + :param score_threshold: The score threshold for detections (default: 0.25). + :return: A list of tuples, each containing: + - A bounding box as (x1, y1, x2, y2) + - A label string + - A confidence score + """ _check_compatibility() input_, global_ratio = _nn_preprocessing(image, model_size=320) config = _make_np_config(topk, iou_threshold, score_threshold) From 164b78bab00cbcc08502f29289204d972794b691 Mon Sep 17 00:00:00 2001 From: narugo1992 Date: Wed, 11 Sep 2024 15:24:58 +0800 Subject: [PATCH 4/8] dev(narugo): fix bug in autodoc, test skip --- docs/source/api_doc/detect/nudenet_detect_demo.plot.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/source/api_doc/detect/nudenet_detect_demo.plot.py b/docs/source/api_doc/detect/nudenet_detect_demo.plot.py index cac1cb6134b..f5b612f24f1 100644 --- a/docs/source/api_doc/detect/nudenet_detect_demo.plot.py +++ b/docs/source/api_doc/detect/nudenet_detect_demo.plot.py @@ -9,11 +9,11 @@ def _detect(img, **kwargs): if __name__ == '__main__': image_plot( - (_detect('nudenet/nude_girl.png'), 'simple nude'), - (_detect('nudenet/simple_sex.jpg'), 'simple sex'), - (_detect('nudenet/complex_pose.jpg'), 'complex pose'), - (_detect('nudenet/complex_sex.jpg'), 'complex sex'), + (_detect('censor/nude_girl.png'), 'simple nude'), + (_detect('censor/simple_sex.jpg'), 'simple sex'), + (_detect('censor/complex_pose.jpg'), 'complex pose'), + (_detect('censor/complex_sex.jpg'), 'complex sex'), columns=2, figsize=(9, 9), - autonudenet=False, + autocensor=False, ) From 22f25e8965567cb298152fdf968eb309454b7984 Mon Sep 17 00:00:00 2001 From: narugo1992 Date: Wed, 11 Sep 2024 07:33:30 +0000 Subject: [PATCH 5/8] dev(narugo): auto sync Wed, 11 Sep 2024 07:33:30 +0000 --- .../nudenet_detect_benchmark.plot.py.svg | 2237 +++++++++++++++++ .../detect/nudenet_detect_demo.plot.py.svg | 398 +++ 2 files changed, 2635 insertions(+) create mode 100644 docs/source/api_doc/detect/nudenet_detect_benchmark.plot.py.svg create mode 100644 docs/source/api_doc/detect/nudenet_detect_demo.plot.py.svg diff --git a/docs/source/api_doc/detect/nudenet_detect_benchmark.plot.py.svg b/docs/source/api_doc/detect/nudenet_detect_benchmark.plot.py.svg new file mode 100644 index 00000000000..bd785288b2b --- /dev/null +++ b/docs/source/api_doc/detect/nudenet_detect_benchmark.plot.py.svg @@ -0,0 +1,2237 @@ + + + + + + + + 2024-09-11T07:33:06.903535 + image/svg+xml + + + Matplotlib v3.7.5, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/api_doc/detect/nudenet_detect_demo.plot.py.svg b/docs/source/api_doc/detect/nudenet_detect_demo.plot.py.svg new file mode 100644 index 00000000000..06e4689beac --- /dev/null +++ b/docs/source/api_doc/detect/nudenet_detect_demo.plot.py.svg @@ -0,0 +1,398 @@ + + + + + + + + 2024-09-11T07:32:47.163266 + image/svg+xml + + + Matplotlib v3.7.5, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From fe8ccd4c93902275f4bf2b563411a5bf068ea619 Mon Sep 17 00:00:00 2001 From: narugo1992 Date: Wed, 11 Sep 2024 17:16:56 +0800 Subject: [PATCH 6/8] dev(narugo): add label explaination --- imgutils/detect/nudenet.py | 50 +++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/imgutils/detect/nudenet.py b/imgutils/detect/nudenet.py index 5ab239ea23c..993ef372e1d 100644 --- a/imgutils/detect/nudenet.py +++ b/imgutils/detect/nudenet.py @@ -20,7 +20,55 @@ .. image:: nudenet_detect_benchmark.plot.py.svg :align: center - + + .. note:: + + Here is a detailed list of labels from the NudeNet detection model and their respective meanings: + + .. list-table:: + :widths: 25 75 + :header-rows: 1 + + * - Label + - Description + * - FEMALE_GENITALIA_COVERED + - Detects covered female genitalia in the image. + * - FACE_FEMALE + - Detects the face of a female in the image. + * - BUTTOCKS_EXPOSED + - Detects exposed buttocks in the image. + * - FEMALE_BREAST_EXPOSED + - Detects exposed female breasts in the image. + * - FEMALE_GENITALIA_EXPOSED + - Detects exposed female genitalia in the image. + * - MALE_BREAST_EXPOSED + - Detects exposed male breasts in the image. + * - ANUS_EXPOSED + - Detects exposed anus in the image. + * - FEET_EXPOSED + - Detects exposed feet in the image. + * - BELLY_COVERED + - Detects a covered belly in the image. + * - FEET_COVERED + - Detects covered feet in the image. + * - ARMPITS_COVERED + - Detects covered armpits in the image. + * - ARMPITS_EXPOSED + - Detects exposed armpits in the image. + * - FACE_MALE + - Detects the face of a male in the image. + * - BELLY_EXPOSED + - Detects an exposed belly in the image. + * - MALE_GENITALIA_EXPOSED + - Detects exposed male genitalia in the image. + * - ANUS_COVERED + - Detects a covered anus in the image. + * - FEMALE_BREAST_COVERED + - Detects covered female breasts in the image. + * - BUTTOCKS_COVERED + - Detects covered buttocks in the image. + + .. note:: This module requires onnxruntime version 1.18 or higher. From 07f8567f6d5d508a52a1eb94a8dfac2f4150eaa8 Mon Sep 17 00:00:00 2001 From: narugo1992 Date: Wed, 11 Sep 2024 17:28:13 +0800 Subject: [PATCH 7/8] dev(narugo): fix nudenet unittest --- test/detect/test_nudenet.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/detect/test_nudenet.py b/test/detect/test_nudenet.py index 5bf43bbf2c2..1a8ff6ed964 100644 --- a/test/detect/test_nudenet.py +++ b/test/detect/test_nudenet.py @@ -68,8 +68,18 @@ def nude_girl_detection(): class TestDetectNudeNet: def test_detect_with_nudenet_file(self, nude_girl_file, nude_girl_detection): detection = detect_with_nudenet(nude_girl_file) - assert detection == pytest.approx(nude_girl_detection) + assert [label for _, label, _ in detection] == \ + [label for _, label, _ in nude_girl_detection] + for (actual_box, _, _), (expected_box, _, _) in zip(detection, nude_girl_detection): + assert actual_box == pytest.approx(expected_box) + assert [score for _, _, score in detection] == \ + pytest.approx([score for _, _, score in nude_girl_detection], abs=1e-4) def test_detect_with_nudenet_image(self, nude_girl_image, nude_girl_detection): detection = detect_with_nudenet(nude_girl_image) - assert detection == pytest.approx(nude_girl_detection) + assert [label for _, label, _ in detection] == \ + [label for _, label, _ in nude_girl_detection] + for (actual_box, _, _), (expected_box, _, _) in zip(detection, nude_girl_detection): + assert actual_box == pytest.approx(expected_box) + assert [score for _, _, score in detection] == \ + pytest.approx([score for _, _, score in nude_girl_detection], abs=1e-4) From 3fca6cf6d0a0cc81a40c42ee472c754262c70db4 Mon Sep 17 00:00:00 2001 From: narugo1992 Date: Wed, 11 Sep 2024 17:30:15 +0800 Subject: [PATCH 8/8] dev(narugo): fix a problem in docstring, test skip --- imgutils/detect/nudenet.py | 1 + 1 file changed, 1 insertion(+) diff --git a/imgutils/detect/nudenet.py b/imgutils/detect/nudenet.py index 993ef372e1d..e5db39157db 100644 --- a/imgutils/detect/nudenet.py +++ b/imgutils/detect/nudenet.py @@ -228,6 +228,7 @@ def detect_with_nudenet(image: ImageTyping, topk: int = 100, :param iou_threshold: The IoU threshold for NMS (default: 0.45). :param score_threshold: The score threshold for detections (default: 0.25). :return: A list of tuples, each containing: + - A bounding box as (x1, y1, x2, y2) - A label string - A confidence score