NudeNetUpdated
Advanced tools
| [tool.poetry] | ||
| name = "NudeNetUpdated" | ||
| version = "2.2.3" | ||
| description = "" | ||
| authors = ["ELoiselle <emile.loiselle@hotmail.com>"] | ||
| readme = "README.md" | ||
| [tool.isort] | ||
| profile = "black" | ||
| [tool.mypy] | ||
| files = "src/" | ||
| strict = true | ||
| ignore_missing_imports = true | ||
| # issue with the mypy daemon (dmypy) and VSCode resolved by this undocumented configuration option and might not be officially supported in the future. | ||
| ignore_missing_imports_per_module = true | ||
| [tool.pytest.ini_options] | ||
| testpaths = [ | ||
| "tests", | ||
| ] | ||
| [tool.poetry.dependencies] | ||
| python = "^3.11" | ||
| numpy = "^1.25.2" | ||
| pydload = "^1.0.9" | ||
| onnxruntime = "^1.15.1" | ||
| pillow = "^10.0.0" | ||
| opencv-python-headless = ">=4.5.1.48" | ||
| scikit-image = "^0.21.0" | ||
| progressbar = "^2.5" | ||
| [tool.poetry.group.dev.dependencies] | ||
| hupper = "^1.12" | ||
| flake8 = "^6.0.0" | ||
| black = "^23.3.0" | ||
| isort = "^5.12.0" | ||
| mypy = "^1.3.0" | ||
| pytest = "^7.3.1" | ||
| pytest-cov = "^4.0.0" | ||
| pytest-xdist = "^3.3.1" | ||
| [build-system] | ||
| requires = ["poetry-core"] | ||
| build-backend = "poetry.core.masonry.api" |
| import numpy as np | ||
| from utils.download import load_classifier_model | ||
| from utils.image import load_images | ||
| class Classifier: | ||
| """ | ||
| Class for loading model and running predictions. | ||
| For example on how to use take a look the if __name__ == '__main__' part. | ||
| """ | ||
| def classify( | ||
| self, | ||
| image_paths=[], | ||
| batch_size=4, | ||
| image_size=(256, 256), | ||
| categories=["unsafe", "safe"], | ||
| ): | ||
| """ | ||
| inputs: | ||
| image_paths: list of image paths or can be a string too (for single image) | ||
| batch_size: batch_size for running predictions | ||
| image_size: size to which the image needs to be resized | ||
| categories: since the model predicts numbers, categories is the list of actual names of categories | ||
| """ | ||
| nsfw_model = load_classifier_model() | ||
| if not isinstance(image_paths, list): | ||
| image_paths = [image_paths] | ||
| loaded_images, loaded_image_paths = load_images( | ||
| image_paths, image_size, image_names=image_paths | ||
| ) | ||
| if not loaded_image_paths: | ||
| return {} | ||
| preds = [] | ||
| model_preds = [] | ||
| while len(loaded_images): | ||
| _model_preds = nsfw_model.run( | ||
| [nsfw_model.get_outputs()[0].name], | ||
| {nsfw_model.get_inputs()[0].name: loaded_images[:batch_size]}, | ||
| )[0] | ||
| model_preds.append(_model_preds) | ||
| preds += np.argsort(_model_preds, axis=1).tolist() | ||
| loaded_images = loaded_images[batch_size:] | ||
| probs = [] | ||
| for i, single_preds in enumerate(preds): | ||
| single_probs = [] | ||
| for j, pred in enumerate(single_preds): | ||
| single_probs.append( | ||
| model_preds[int(i / batch_size)][int(i % batch_size)][pred] | ||
| ) | ||
| preds[i][j] = categories[pred] | ||
| probs.append(single_probs) | ||
| images_preds = {} | ||
| for i, loaded_image_path in enumerate(loaded_image_paths): | ||
| if not isinstance(loaded_image_path, str): | ||
| loaded_image_path = i | ||
| images_preds[loaded_image_path] = {} | ||
| for _ in range(len(preds[i])): | ||
| images_preds[loaded_image_path][preds[i][_]] = float(probs[i][_]) | ||
| return images_preds | ||
| if __name__ == "__main__": | ||
| m = Classifier() | ||
| while 1: | ||
| print( | ||
| "\n Enter single image path or multiple images separated by || (2 pipes) \n ([Ctrl] + [C] to exit) \n" | ||
| ) | ||
| images = input().split("||") | ||
| images = [image.strip() for image in images] | ||
| print(m.classify(images), "\n") |
| import cv2 | ||
| import numpy as np | ||
| from utils.detector import preprocess_image | ||
| from utils.download import load_detector_classes, load_detector_model | ||
| class Detector: | ||
| def detect_labels(self, img_path, mode="default", min_prob=None): | ||
| detection_model = load_detector_model() | ||
| classes = load_detector_classes() | ||
| if mode == "fast": | ||
| image, scale = preprocess_image(img_path, min_side=480, max_side=800) | ||
| if not min_prob: | ||
| min_prob = 0.5 | ||
| else: | ||
| image, scale = preprocess_image(img_path) | ||
| if not min_prob: | ||
| min_prob = 0.6 | ||
| outputs = detection_model.run( | ||
| [s_i.name for s_i in detection_model.get_outputs()], | ||
| {detection_model.get_inputs()[0].name: np.expand_dims(image, axis=0)}, | ||
| ) | ||
| labels = [op for op in outputs if op.dtype == "int32"][0] | ||
| scores = [op for op in outputs if isinstance(op[0][0], np.float32)][0] # type: ignore | ||
| processed_results = [] | ||
| for score, label in zip(scores[0], labels[0]): | ||
| if score < min_prob: | ||
| continue | ||
| label = classes[label] | ||
| processed_results.append({"label": label, "score": float(score)}) | ||
| return processed_results | ||
| def detect(self, img_path, mode="default", min_prob=None): | ||
| detection_model = load_detector_model() | ||
| classes = load_detector_classes() | ||
| if mode == "fast": | ||
| image, scale = preprocess_image(img_path, min_side=480, max_side=800) | ||
| if not min_prob: | ||
| min_prob = 0.5 | ||
| else: | ||
| image, scale = preprocess_image(img_path) | ||
| if not min_prob: | ||
| min_prob = 0.6 | ||
| outputs = detection_model.run( | ||
| [s_i.name for s_i in detection_model.get_outputs()], | ||
| {detection_model.get_inputs()[0].name: np.expand_dims(image, axis=0)}, | ||
| ) | ||
| labels = [op for op in outputs if op.dtype == "int32"][0] | ||
| scores = [op for op in outputs if isinstance(op[0][0], np.float32)][0] # type: ignore | ||
| boxes = [op for op in outputs if isinstance(op[0][0], np.ndarray)][0] | ||
| boxes /= scale | ||
| processed_boxes = [] | ||
| for box, score, label in zip(boxes[0], scores[0], labels[0]): | ||
| if score < min_prob: | ||
| continue | ||
| box = box.astype(int).tolist() | ||
| label = classes[label] | ||
| processed_boxes.append( | ||
| {"box": [int(c) for c in box], "score": float(score), "label": label} | ||
| ) | ||
| return processed_boxes | ||
| def censor(self, img_path, out_path=None, visualize=False, parts_to_blur=[]): | ||
| if not out_path and not visualize: | ||
| print( | ||
| "No out_path passed and visualize is set to false. There is no point in running this function then." | ||
| ) | ||
| return | ||
| image = cv2.imread(img_path) | ||
| boxes = self.detect(img_path) | ||
| if parts_to_blur: | ||
| boxes = [i["box"] for i in boxes if i["label"] in parts_to_blur] | ||
| else: | ||
| boxes = [i["box"] for i in boxes] | ||
| for box in boxes: | ||
| image = cv2.rectangle( | ||
| image, (box[0], box[1]), (box[2], box[3]), (0, 0, 0), cv2.FILLED | ||
| ) | ||
| if visualize: | ||
| cv2.imshow("Blurred image", image) | ||
| cv2.waitKey(0) | ||
| if out_path: | ||
| cv2.imwrite(out_path, image) | ||
| if __name__ == "__main__": | ||
| m = Detector() | ||
| while 1: | ||
| print("\n Enter single image path ([Ctrl] + [C] to exit) \n") | ||
| print(m.detect_labels(input()), "\n") |
| import cv2 | ||
| import numpy as np | ||
| from PIL import Image | ||
| def read_image_bgr(path): | ||
| """Read an image in BGR format. | ||
| Args | ||
| path: Path to the image. | ||
| """ | ||
| if isinstance(path, str): | ||
| image = np.ascontiguousarray(Image.open(path).convert("RGB")) | ||
| else: | ||
| path = cv2.cvtColor(path, cv2.COLOR_BGR2RGB) | ||
| image = np.ascontiguousarray(Image.fromarray(path)) | ||
| return image[:, :, ::-1] | ||
| def _preprocess_image(x, mode="caffe") -> np.float32: | ||
| x = x.astype(np.float32) | ||
| if mode == "tf": | ||
| x /= 127.5 | ||
| x -= 1.0 | ||
| elif mode == "caffe": | ||
| x -= [103.939, 116.779, 123.68] | ||
| return x | ||
| def compute_resize_scale(image_shape, min_side=800, max_side=1333): | ||
| print(image_shape) | ||
| (rows, cols, _) = image_shape | ||
| if rows > cols: | ||
| largest_side = rows | ||
| smallest_side = cols | ||
| else: | ||
| largest_side = cols | ||
| smallest_side = rows | ||
| scale = min_side / smallest_side | ||
| if largest_side * scale > max_side: | ||
| scale = max_side / largest_side | ||
| return scale | ||
| def resize_image(img, min_side=800, max_side=1333): | ||
| scale = compute_resize_scale(img.shape, min_side=min_side, max_side=max_side) | ||
| img = cv2.resize(img, None, fx=scale, fy=scale) | ||
| return img, scale | ||
| def preprocess_image( | ||
| image_path, | ||
| min_side=800, | ||
| max_side=1333, | ||
| ): | ||
| image = read_image_bgr(image_path) | ||
| image = _preprocess_image(image) | ||
| image, scale = resize_image(image, min_side=min_side, max_side=max_side) | ||
| return image, scale |
| import os | ||
| import onnxruntime | ||
| import pydload | ||
| DATA_DIR = os.path.normpath( | ||
| os.path.join(os.path.dirname(__file__), "..", "..", "models") | ||
| ) | ||
| MAX_DOWNLOAD_ATTEMPTS = 10 | ||
| DETECTOR_MODEL_PATH = os.path.join(DATA_DIR, "detector_model.onnx") | ||
| DETECTOR_MODEL_URL = "https://onedrive.live.com/download?cid=C5669415F4DE9E91&resid=C5669415F4DE9E91%21299479&authkey=ADVoe7wRG0KRcUo" | ||
| DETECTOR_CLASSES_PATH = os.path.join(DATA_DIR, "detector_classes.onnx") | ||
| DETECTOR_CLASSES_URL = "https://onedrive.live.com/download?cid=C5669415F4DE9E91&resid=C5669415F4DE9E91%21299477&authkey=AO1Ekn4UySCrzmk" | ||
| CLASSIFIER_MODEL_PATH = os.path.join(DATA_DIR, "classifier_model.onnx") | ||
| CLASSIFIER_MODEL_URL = "https://onedrive.live.com/download?cid=C5669415F4DE9E91&resid=C5669415F4DE9E91%21299478&authkey=AATSwER87jN2084" | ||
| def load_classifier_model() -> onnxruntime.InferenceSession: | ||
| attempts = MAX_DOWNLOAD_ATTEMPTS | ||
| while not is_file_valid(CLASSIFIER_MODEL_PATH) and attempts > 0: | ||
| attempts -= 1 | ||
| download_data(CLASSIFIER_MODEL_URL, CLASSIFIER_MODEL_PATH) | ||
| return onnxruntime.InferenceSession(CLASSIFIER_MODEL_PATH) | ||
| def load_detector_model() -> onnxruntime.InferenceSession: | ||
| attempts = MAX_DOWNLOAD_ATTEMPTS | ||
| while not is_file_valid(DETECTOR_MODEL_PATH) and attempts > 0: | ||
| attempts -= 1 | ||
| download_data(DETECTOR_MODEL_URL, DETECTOR_MODEL_PATH) | ||
| return onnxruntime.InferenceSession(DETECTOR_MODEL_PATH) | ||
| def load_detector_classes() -> list[str]: | ||
| attempts = MAX_DOWNLOAD_ATTEMPTS | ||
| while not is_file_valid(DETECTOR_CLASSES_PATH) and attempts > 0: | ||
| attempts -= 1 | ||
| download_data(DETECTOR_CLASSES_URL, DETECTOR_CLASSES_PATH) | ||
| return [c.strip() for c in open(DETECTOR_CLASSES_PATH).readlines() if c.strip()] | ||
| def is_file_valid(save_path: str) -> bool: | ||
| return not (not os.path.exists(save_path) or os.path.getsize(save_path) == 0) | ||
| def download_data(url: str, save_path: str) -> None: | ||
| if not os.path.exists(DATA_DIR): | ||
| os.mkdir(DATA_DIR) | ||
| print("Downloading file to " + save_path) | ||
| pydload.dload( | ||
| url, save_to_path=save_path, max_time=1200, verbose=False | ||
| ) # 1200secs = 20mins | ||
| def redownload_data() -> None: | ||
| download_data(CLASSIFIER_MODEL_URL, CLASSIFIER_MODEL_PATH) | ||
| download_data(DETECTOR_MODEL_URL, DETECTOR_MODEL_PATH) | ||
| download_data(DETECTOR_CLASSES_URL, DETECTOR_CLASSES_PATH) |
| import io | ||
| import logging | ||
| import cv2 | ||
| import numpy as np | ||
| from PIL import Image as pil_image | ||
| _PIL_INTERPOLATION_METHODS = { | ||
| "nearest": pil_image.NEAREST, | ||
| "bilinear": pil_image.BILINEAR, | ||
| "bicubic": pil_image.BICUBIC, | ||
| } | ||
| # These methods were only introduced in version 3.4.0 (2016). | ||
| if hasattr(pil_image, "HAMMING"): | ||
| _PIL_INTERPOLATION_METHODS["hamming"] = pil_image.HAMMING | ||
| if hasattr(pil_image, "BOX"): | ||
| _PIL_INTERPOLATION_METHODS["box"] = pil_image.BOX | ||
| # This method is new in version 1.1.3 (2013). | ||
| if hasattr(pil_image, "LANCZOS"): | ||
| _PIL_INTERPOLATION_METHODS["lanczos"] = pil_image.LANCZOS | ||
| def load_img( | ||
| path, grayscale=False, color_mode="rgb", target_size=None, interpolation="nearest" | ||
| ): | ||
| """Loads an image into PIL format. | ||
| :param path: Path to image file. | ||
| :param grayscale: DEPRECATED use `color_mode="grayscale"`. | ||
| :param color_mode: One of "grayscale", "rgb", "rgba". Default: "rgb". | ||
| The desired image format. | ||
| :param target_size: Either `None` (default to original size) | ||
| or tuple of ints `(img_height, img_width)`. | ||
| :param interpolation: Interpolation method used to resample the image if the | ||
| target size is different from that of the loaded image. | ||
| Supported methods are "nearest", "bilinear", and "bicubic". | ||
| If PIL version 1.1.3 or newer is installed, "lanczos" is also | ||
| supported. If PIL version 3.4.0 or newer is installed, "box" and | ||
| "hamming" are also supported. By default, "nearest" is used. | ||
| :return: A PIL Image instance. | ||
| """ | ||
| if grayscale is True: | ||
| logging.warn("grayscale is deprecated. Please use " 'color_mode = "grayscale"') | ||
| color_mode = "grayscale" | ||
| if pil_image is None: | ||
| raise ImportError( | ||
| "Could not import PIL.Image. " "The use of `load_img` requires PIL." | ||
| ) | ||
| if isinstance(path, (str, io.IOBase)): | ||
| img = pil_image.open(path) | ||
| else: | ||
| path = cv2.cvtColor(path, cv2.COLOR_BGR2RGB) | ||
| img = pil_image.fromarray(path) | ||
| if color_mode == "grayscale": | ||
| if img.mode != "L": | ||
| img = img.convert("L") | ||
| elif color_mode == "rgba": | ||
| if img.mode != "RGBA": | ||
| img = img.convert("RGBA") | ||
| elif color_mode == "rgb": | ||
| if img.mode != "RGB": | ||
| img = img.convert("RGB") | ||
| else: | ||
| raise ValueError('color_mode must be "grayscale", "rgb", or "rgba"') | ||
| if target_size is not None: | ||
| width_height_tuple = (target_size[1], target_size[0]) | ||
| if img.size != width_height_tuple: | ||
| if interpolation not in _PIL_INTERPOLATION_METHODS: # type: ignore | ||
| raise ValueError( | ||
| "Invalid interpolation method {} specified. Supported " | ||
| "methods are {}".format( | ||
| interpolation, ", ".join(_PIL_INTERPOLATION_METHODS.keys()) | ||
| ) | ||
| ) | ||
| resample = _PIL_INTERPOLATION_METHODS[interpolation] | ||
| img = img.resize(width_height_tuple, resample) # type: ignore | ||
| return img | ||
| def img_to_array(img, data_format="channels_last", dtype="float32"): | ||
| """Converts a PIL Image instance to a Numpy array. | ||
| # Arguments | ||
| img: PIL Image instance. | ||
| data_format: Image data format, | ||
| either "channels_first" or "channels_last". | ||
| dtype: Dtype to use for the returned array. | ||
| # Returns | ||
| A 3D Numpy array. | ||
| # Raises | ||
| ValueError: if invalid `img` or `data_format` is passed. | ||
| """ | ||
| if data_format not in {"channels_first", "channels_last"}: | ||
| raise ValueError("Unknown data_format: %s" % data_format) | ||
| # Numpy array x has format (height, width, channel) | ||
| # or (channel, height, width) | ||
| # but original PIL image has format (width, height, channel) | ||
| x = np.asarray(img, dtype=dtype) | ||
| if len(x.shape) == 3: | ||
| if data_format == "channels_first": | ||
| x = x.transpose(2, 0, 1) | ||
| elif len(x.shape) == 2: | ||
| if data_format == "channels_first": | ||
| x = x.reshape((1, x.shape[0], x.shape[1])) | ||
| else: | ||
| x = x.reshape((x.shape[0], x.shape[1], 1)) | ||
| else: | ||
| raise ValueError("Unsupported image shape: %s" % (x.shape,)) | ||
| return x | ||
| def load_images(image_paths, image_size, image_names): | ||
| """ | ||
| Function for loading images into numpy arrays for passing to model.predict | ||
| inputs: | ||
| image_paths: list of image paths to load | ||
| image_size: size into which images should be resized | ||
| outputs: | ||
| loaded_images: loaded images on which keras model can run predictions | ||
| loaded_image_indexes: paths of images which the function is able to process | ||
| """ | ||
| loaded_images = [] | ||
| loaded_image_paths = [] | ||
| for i, img_path in enumerate(image_paths): | ||
| try: | ||
| image = load_img(img_path, target_size=image_size) | ||
| image = img_to_array(image) | ||
| image /= 255 | ||
| loaded_images.append(image) | ||
| loaded_image_paths.append(image_names[i]) | ||
| except Exception as ex: | ||
| logging.exception(f"Error reading {img_path} {ex}", exc_info=True) | ||
| return np.asarray(loaded_images), loaded_image_paths |
+54
-90
| Metadata-Version: 2.1 | ||
| Name: NudeNetUpdated | ||
| Version: 2.2.2 | ||
| Summary: A Neural Net for Nudity Detection. Classifier only. | ||
| Home-page: https://github.com/eloiselle/NudeNet | ||
| Author: Emile Loiselle | ||
| Version: 2.2.3 | ||
| Summary: | ||
| Author: ELoiselle | ||
| Author-email: emile.loiselle@hotmail.com | ||
| License: GPLv3 | ||
| Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) | ||
| Classifier: Programming Language :: Python | ||
| Requires-Python: >=3.11,<4.0 | ||
| Classifier: Programming Language :: Python :: 3 | ||
| Classifier: Programming Language :: Python :: 3.6 | ||
| Classifier: Programming Language :: Python :: Implementation :: CPython | ||
| Classifier: Programming Language :: Python :: Implementation :: PyPy | ||
| Requires-Python: >=3.6.0 | ||
| Classifier: Programming Language :: Python :: 3.11 | ||
| Requires-Dist: numpy (>=1.25.2,<2.0.0) | ||
| Requires-Dist: onnxruntime (>=1.15.1,<2.0.0) | ||
| Requires-Dist: opencv-python-headless (>=4.5.1.48) | ||
| Requires-Dist: pillow (>=10.0.0,<11.0.0) | ||
| Requires-Dist: progressbar (>=2.5,<3.0) | ||
| Requires-Dist: pydload (>=1.0.9,<2.0.0) | ||
| Requires-Dist: scikit-image (>=0.21.0,<0.22.0) | ||
| Description-Content-Type: text/markdown | ||
| License-File: LICENSE.md | ||
| ## Python DevTools Boilerplate | ||
| # NudeNet: Neural Nets for Nudity Classification, Detection and selective censoring | ||
| Introducing the Python DevTools Boilerplate, an advanced and feature-rich project template tailored for Python developers. This boilerplate is designed to help you kick-start your Python projects with the best development tools and configurations, giving you a solid foundation for efficient and streamlined development. The boilerplate includes: | ||
| [](https://zenodo.org/badge/latestdoi/173154449)  | ||
| - Poetry for dependency management | ||
| - Flake8 for linting | ||
| - Black for code formatting | ||
| - Mypy for static type checking | ||
| - pytest for testing | ||
| - hupper for monitoring python files | ||
| ### Getting Started | ||
| ## Fork differences: | ||
| To get started, simply clone the repository and install the dependencies: | ||
| - The original detector was added back in | ||
| - The classifier no longer throws the `Initializer block1_conv1_bn/keras_learning_phase:0 appears in graph inputs | ||
| and will not be treated as constant value/weight. etc.` warning. | ||
| - It only works on images. | ||
| - The models are included in the project itself. | ||
| - Only the v2 model is available (the original repo's default). So `v2` from original is `main` here. | ||
| ``` | ||
| git clone https://github.com/shahzayb/python-devtools-boilerplate.git | ||
| cd python-devtools-boilerplate | ||
| poetry install | ||
| ``` | ||
| Uncensored version of the following image can be found at https://i.imgur.com/rga6845.jpg (NSFW) | ||
| Once the dependencies are installed, you can start developing your project. | ||
|  | ||
| To start the project in watch mode, run: | ||
| **Classifier classes:** | ||
| |class name | Description | | ||
| |--------|:--------------: | ||
| |safe | Image is not sexually explicit | | ||
| |unsafe | Image is sexually explicit| | ||
| ``` | ||
| make watch | ||
| ``` | ||
| **Detector classes:** | ||
| |class name | Description | | ||
| |--------|:-------------------------------------: | ||
| |EXPOSED_ANUS | Exposed Anus; Any gender | | ||
| |EXPOSED_ARMPITS | Exposed Armpits; Any gender | | ||
| |COVERED_BELLY | Provocative, but covered Belly; Any gender | | ||
| |EXPOSED_BELLY | Exposed Belly; Any gender | | ||
| |COVERED_BUTTOCKS | Provocative, but covered Buttocks; Any gender | | ||
| |EXPOSED_BUTTOCKS | Exposed Buttocks; Any gender | | ||
| |FACE_F | Female Face| | ||
| |FACE_M | Male Face| | ||
| |COVERED_FEET |Covered Feet; Any gender | | ||
| |EXPOSED_FEET | Exposed Feet; Any gender| | ||
| |COVERED_BREAST_F | Provocative, but covered Breast; Female | | ||
| |EXPOSED_BREAST_F | Exposed Breast; Female | | ||
| |COVERED_GENITALIA_F |Provocative, but covered Genitalia; Female| | ||
| |EXPOSED_GENITALIA_F |Exposed Genitalia; Female | | ||
| |EXPOSED_BREAST_M |Exposed Breast; Male | | ||
| |EXPOSED_GENITALIA_M |Exposed Genitalia; Male | | ||
| To run the tests, run: | ||
| ``` | ||
| make test | ||
| ``` | ||
| # As Python module | ||
| **Installation**: | ||
| ```bash | ||
| pip install -U git+https://github.com/eloiselle/NudeNet | ||
| To lint your code, run: | ||
| ``` | ||
| make lint | ||
| ``` | ||
| **Classifier Usage**: | ||
| ```python | ||
| # Import module | ||
| from nudenet import NudeClassifier | ||
| To format your code, run: | ||
| # initialize classifier (downloads the checkpoint file automatically the first time) | ||
| classifier = NudeClassifier() | ||
| ``` | ||
| make format | ||
| ``` | ||
| # Classify single image | ||
| classifier.classify('path_to_image_1') | ||
| # Returns {'path_to_image_1': {'safe': PROBABILITY, 'unsafe': PROBABILITY}} | ||
| # Classify multiple images (batch prediction) | ||
| # batch_size is optional; defaults to 4 | ||
| classifier.classify(['path_to_image_1', 'path_to_image_2'], batch_size=BATCH_SIZE) | ||
| # Returns {'path_to_image_1': {'safe': PROBABILITY, 'unsafe': PROBABILITY}, | ||
| # 'path_to_image_2': {'safe': PROBABILITY, 'unsafe': PROBABILITY}} | ||
| To run static type checking, run: | ||
| ``` | ||
| make mypy | ||
| ``` | ||
| **Detector Usage**: | ||
| ```python | ||
| # Import module | ||
| from nudenet import NudeDetector | ||
| I hope this boilerplate helps you get started with your Python development projects! | ||
| # initialize detector (downloads the checkpoint file automatically the first time) | ||
| detector = NudeDetector() # detector = NudeDetector('base') for the "base" version of detector. | ||
| ### Contributing | ||
| # Detect single image | ||
| detector.detect('path_to_image') | ||
| # fast mode is ~3x faster compared to default mode with slightly lower accuracy. | ||
| detector.detect('path_to_image', mode='fast') | ||
| # Returns [{'box': LIST_OF_COORDINATES, 'score': PROBABILITY, 'label': LABEL}, ...] | ||
| If you have any suggestions for improvements, please feel free to open an issue or submit a pull request. | ||
| # Detect video | ||
| # batch_size is optional; defaults to 2 | ||
| # show_progress is optional; defaults to True | ||
| detector.detect_video('path_to_video', batch_size=BATCH_SIZE, show_progress=BOOLEAN) | ||
| # fast mode is ~3x faster compared to default mode with slightly lower accuracy. | ||
| detector.detect_video('path_to_video', batch_size=BATCH_SIZE, show_progress=BOOLEAN, mode='fast') | ||
| # Returns {"metadata": {"fps": FPS, "video_length": TOTAL_N_FRAMES, "video_path": 'path_to_video'}, | ||
| # "preds": {frame_i: {'box': LIST_OF_COORDINATES, 'score': PROBABILITY, 'label': LABEL}, ...], ....}} | ||
| ### License | ||
| # Notes: | ||
| - detect_video and classify_video first identify the "unique" frames in a video and run predictions on them for significant performance improvement. | ||
| - The current version of NudeDetector is trained on 160,000 entirely auto-labelled (using classification heat maps and | ||
| various other hybrid techniques) images. | ||
| - The entire data for the classifier is available at https://archive.org/details/NudeNet_classifier_dataset_v1 | ||
| - A part of the auto-labelled data (Images are from the classifier dataset above) used to train the base Detector is available at https://github.com/notAI-tech/NudeNet/releases/download/v0/DETECTOR_AUTO_GENERATED_DATA.zip | ||
| This project is licensed under the MIT License. | ||
+42
-78
@@ -1,98 +0,62 @@ | ||
| # NudeNet: Neural Nets for Nudity Classification, Detection and selective censoring | ||
| ## Python DevTools Boilerplate | ||
| [](https://zenodo.org/badge/latestdoi/173154449)  | ||
| Introducing the Python DevTools Boilerplate, an advanced and feature-rich project template tailored for Python developers. This boilerplate is designed to help you kick-start your Python projects with the best development tools and configurations, giving you a solid foundation for efficient and streamlined development. The boilerplate includes: | ||
| - Poetry for dependency management | ||
| - Flake8 for linting | ||
| - Black for code formatting | ||
| - Mypy for static type checking | ||
| - pytest for testing | ||
| - hupper for monitoring python files | ||
| ## Fork differences: | ||
| ### Getting Started | ||
| - The original detector was added back in | ||
| - The classifier no longer throws the `Initializer block1_conv1_bn/keras_learning_phase:0 appears in graph inputs | ||
| and will not be treated as constant value/weight. etc.` warning. | ||
| - It only works on images. | ||
| - The models are included in the project itself. | ||
| - Only the v2 model is available (the original repo's default). So `v2` from original is `main` here. | ||
| To get started, simply clone the repository and install the dependencies: | ||
| Uncensored version of the following image can be found at https://i.imgur.com/rga6845.jpg (NSFW) | ||
| ``` | ||
| git clone https://github.com/shahzayb/python-devtools-boilerplate.git | ||
| cd python-devtools-boilerplate | ||
| poetry install | ||
| ``` | ||
|  | ||
| Once the dependencies are installed, you can start developing your project. | ||
| **Classifier classes:** | ||
| |class name | Description | | ||
| |--------|:--------------: | ||
| |safe | Image is not sexually explicit | | ||
| |unsafe | Image is sexually explicit| | ||
| To start the project in watch mode, run: | ||
| **Detector classes:** | ||
| |class name | Description | | ||
| |--------|:-------------------------------------: | ||
| |EXPOSED_ANUS | Exposed Anus; Any gender | | ||
| |EXPOSED_ARMPITS | Exposed Armpits; Any gender | | ||
| |COVERED_BELLY | Provocative, but covered Belly; Any gender | | ||
| |EXPOSED_BELLY | Exposed Belly; Any gender | | ||
| |COVERED_BUTTOCKS | Provocative, but covered Buttocks; Any gender | | ||
| |EXPOSED_BUTTOCKS | Exposed Buttocks; Any gender | | ||
| |FACE_F | Female Face| | ||
| |FACE_M | Male Face| | ||
| |COVERED_FEET |Covered Feet; Any gender | | ||
| |EXPOSED_FEET | Exposed Feet; Any gender| | ||
| |COVERED_BREAST_F | Provocative, but covered Breast; Female | | ||
| |EXPOSED_BREAST_F | Exposed Breast; Female | | ||
| |COVERED_GENITALIA_F |Provocative, but covered Genitalia; Female| | ||
| |EXPOSED_GENITALIA_F |Exposed Genitalia; Female | | ||
| |EXPOSED_BREAST_M |Exposed Breast; Male | | ||
| |EXPOSED_GENITALIA_M |Exposed Genitalia; Male | | ||
| ``` | ||
| make watch | ||
| ``` | ||
| To run the tests, run: | ||
| # As Python module | ||
| **Installation**: | ||
| ```bash | ||
| pip install -U git+https://github.com/eloiselle/NudeNet | ||
| ``` | ||
| make test | ||
| ``` | ||
| **Classifier Usage**: | ||
| ```python | ||
| # Import module | ||
| from nudenet import NudeClassifier | ||
| To lint your code, run: | ||
| # initialize classifier (downloads the checkpoint file automatically the first time) | ||
| classifier = NudeClassifier() | ||
| ``` | ||
| make lint | ||
| ``` | ||
| # Classify single image | ||
| classifier.classify('path_to_image_1') | ||
| # Returns {'path_to_image_1': {'safe': PROBABILITY, 'unsafe': PROBABILITY}} | ||
| # Classify multiple images (batch prediction) | ||
| # batch_size is optional; defaults to 4 | ||
| classifier.classify(['path_to_image_1', 'path_to_image_2'], batch_size=BATCH_SIZE) | ||
| # Returns {'path_to_image_1': {'safe': PROBABILITY, 'unsafe': PROBABILITY}, | ||
| # 'path_to_image_2': {'safe': PROBABILITY, 'unsafe': PROBABILITY}} | ||
| To format your code, run: | ||
| ``` | ||
| make format | ||
| ``` | ||
| **Detector Usage**: | ||
| ```python | ||
| # Import module | ||
| from nudenet import NudeDetector | ||
| To run static type checking, run: | ||
| # initialize detector (downloads the checkpoint file automatically the first time) | ||
| detector = NudeDetector() # detector = NudeDetector('base') for the "base" version of detector. | ||
| ``` | ||
| make mypy | ||
| ``` | ||
| # Detect single image | ||
| detector.detect('path_to_image') | ||
| # fast mode is ~3x faster compared to default mode with slightly lower accuracy. | ||
| detector.detect('path_to_image', mode='fast') | ||
| # Returns [{'box': LIST_OF_COORDINATES, 'score': PROBABILITY, 'label': LABEL}, ...] | ||
| I hope this boilerplate helps you get started with your Python development projects! | ||
| # Detect video | ||
| # batch_size is optional; defaults to 2 | ||
| # show_progress is optional; defaults to True | ||
| detector.detect_video('path_to_video', batch_size=BATCH_SIZE, show_progress=BOOLEAN) | ||
| # fast mode is ~3x faster compared to default mode with slightly lower accuracy. | ||
| detector.detect_video('path_to_video', batch_size=BATCH_SIZE, show_progress=BOOLEAN, mode='fast') | ||
| # Returns {"metadata": {"fps": FPS, "video_length": TOTAL_N_FRAMES, "video_path": 'path_to_video'}, | ||
| # "preds": {frame_i: {'box': LIST_OF_COORDINATES, 'score': PROBABILITY, 'label': LABEL}, ...], ....}} | ||
| ### Contributing | ||
| # Notes: | ||
| - detect_video and classify_video first identify the "unique" frames in a video and run predictions on them for significant performance improvement. | ||
| - The current version of NudeDetector is trained on 160,000 entirely auto-labelled (using classification heat maps and | ||
| various other hybrid techniques) images. | ||
| - The entire data for the classifier is available at https://archive.org/details/NudeNet_classifier_dataset_v1 | ||
| - A part of the auto-labelled data (Images are from the classifier dataset above) used to train the base Detector is available at https://github.com/notAI-tech/NudeNet/releases/download/v0/DETECTOR_AUTO_GENERATED_DATA.zip | ||
| If you have any suggestions for improvements, please feel free to open an issue or submit a pull request. | ||
| ### License | ||
| This project is licensed under the MIT License. |
| import base64 | ||
| from nudenet import NudeClassifier | ||
| classifier = NudeClassifier() | ||
| """ | ||
| Your function should take list of items as input | ||
| This makes batching possible | ||
| """ | ||
| def predictor(in_images=[], batch_size=32): | ||
| if not in_images: | ||
| return [] | ||
| preds = classifier.classify(in_images, batch_size=batch_size) | ||
| preds = [preds.get(in_image) for in_image in in_images] | ||
| preds = [{k: float(v) for k, v in pred.items()} if pred else pred for pred in preds] | ||
| return preds | ||
| if __name__ == "__main__": | ||
| import json | ||
| import pickle | ||
| import base64 | ||
| example = ["example.jpg"] | ||
| print(json.dumps(predictor(example))) | ||
| example = { | ||
| file_name: base64.b64encode(open(file_name, "rb").read()).decode("utf-8") | ||
| for file_name in example | ||
| } | ||
| pickle.dump(example, open("example.pkl", "wb"), protocol=2) |
-21
| MIT License | ||
| Copyright (c) 2022 notAI.tech | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
| Metadata-Version: 2.1 | ||
| Name: NudeNetUpdated | ||
| Version: 2.2.2 | ||
| Summary: A Neural Net for Nudity Detection. Classifier only. | ||
| Home-page: https://github.com/eloiselle/NudeNet | ||
| Author: Emile Loiselle | ||
| Author-email: emile.loiselle@hotmail.com | ||
| License: GPLv3 | ||
| Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) | ||
| Classifier: Programming Language :: Python | ||
| Classifier: Programming Language :: Python :: 3 | ||
| Classifier: Programming Language :: Python :: 3.6 | ||
| Classifier: Programming Language :: Python :: Implementation :: CPython | ||
| Classifier: Programming Language :: Python :: Implementation :: PyPy | ||
| Requires-Python: >=3.6.0 | ||
| Description-Content-Type: text/markdown | ||
| License-File: LICENSE.md | ||
| # NudeNet: Neural Nets for Nudity Classification, Detection and selective censoring | ||
| [](https://zenodo.org/badge/latestdoi/173154449)  | ||
| ## Fork differences: | ||
| - The original detector was added back in | ||
| - The classifier no longer throws the `Initializer block1_conv1_bn/keras_learning_phase:0 appears in graph inputs | ||
| and will not be treated as constant value/weight. etc.` warning. | ||
| - It only works on images. | ||
| - The models are included in the project itself. | ||
| - Only the v2 model is available (the original repo's default). So `v2` from original is `main` here. | ||
| Uncensored version of the following image can be found at https://i.imgur.com/rga6845.jpg (NSFW) | ||
|  | ||
| **Classifier classes:** | ||
| |class name | Description | | ||
| |--------|:--------------: | ||
| |safe | Image is not sexually explicit | | ||
| |unsafe | Image is sexually explicit| | ||
| **Detector classes:** | ||
| |class name | Description | | ||
| |--------|:-------------------------------------: | ||
| |EXPOSED_ANUS | Exposed Anus; Any gender | | ||
| |EXPOSED_ARMPITS | Exposed Armpits; Any gender | | ||
| |COVERED_BELLY | Provocative, but covered Belly; Any gender | | ||
| |EXPOSED_BELLY | Exposed Belly; Any gender | | ||
| |COVERED_BUTTOCKS | Provocative, but covered Buttocks; Any gender | | ||
| |EXPOSED_BUTTOCKS | Exposed Buttocks; Any gender | | ||
| |FACE_F | Female Face| | ||
| |FACE_M | Male Face| | ||
| |COVERED_FEET |Covered Feet; Any gender | | ||
| |EXPOSED_FEET | Exposed Feet; Any gender| | ||
| |COVERED_BREAST_F | Provocative, but covered Breast; Female | | ||
| |EXPOSED_BREAST_F | Exposed Breast; Female | | ||
| |COVERED_GENITALIA_F |Provocative, but covered Genitalia; Female| | ||
| |EXPOSED_GENITALIA_F |Exposed Genitalia; Female | | ||
| |EXPOSED_BREAST_M |Exposed Breast; Male | | ||
| |EXPOSED_GENITALIA_M |Exposed Genitalia; Male | | ||
| # As Python module | ||
| **Installation**: | ||
| ```bash | ||
| pip install -U git+https://github.com/eloiselle/NudeNet | ||
| ``` | ||
| **Classifier Usage**: | ||
| ```python | ||
| # Import module | ||
| from nudenet import NudeClassifier | ||
| # initialize classifier (downloads the checkpoint file automatically the first time) | ||
| classifier = NudeClassifier() | ||
| # Classify single image | ||
| classifier.classify('path_to_image_1') | ||
| # Returns {'path_to_image_1': {'safe': PROBABILITY, 'unsafe': PROBABILITY}} | ||
| # Classify multiple images (batch prediction) | ||
| # batch_size is optional; defaults to 4 | ||
| classifier.classify(['path_to_image_1', 'path_to_image_2'], batch_size=BATCH_SIZE) | ||
| # Returns {'path_to_image_1': {'safe': PROBABILITY, 'unsafe': PROBABILITY}, | ||
| # 'path_to_image_2': {'safe': PROBABILITY, 'unsafe': PROBABILITY}} | ||
| ``` | ||
| **Detector Usage**: | ||
| ```python | ||
| # Import module | ||
| from nudenet import NudeDetector | ||
| # initialize detector (downloads the checkpoint file automatically the first time) | ||
| detector = NudeDetector() # detector = NudeDetector('base') for the "base" version of detector. | ||
| # Detect single image | ||
| detector.detect('path_to_image') | ||
| # fast mode is ~3x faster compared to default mode with slightly lower accuracy. | ||
| detector.detect('path_to_image', mode='fast') | ||
| # Returns [{'box': LIST_OF_COORDINATES, 'score': PROBABILITY, 'label': LABEL}, ...] | ||
| # Detect video | ||
| # batch_size is optional; defaults to 2 | ||
| # show_progress is optional; defaults to True | ||
| detector.detect_video('path_to_video', batch_size=BATCH_SIZE, show_progress=BOOLEAN) | ||
| # fast mode is ~3x faster compared to default mode with slightly lower accuracy. | ||
| detector.detect_video('path_to_video', batch_size=BATCH_SIZE, show_progress=BOOLEAN, mode='fast') | ||
| # Returns {"metadata": {"fps": FPS, "video_length": TOTAL_N_FRAMES, "video_path": 'path_to_video'}, | ||
| # "preds": {frame_i: {'box': LIST_OF_COORDINATES, 'score': PROBABILITY, 'label': LABEL}, ...], ....}} | ||
| # Notes: | ||
| - detect_video and classify_video first identify the "unique" frames in a video and run predictions on them for significant performance improvement. | ||
| - The current version of NudeDetector is trained on 160,000 entirely auto-labelled (using classification heat maps and | ||
| various other hybrid techniques) images. | ||
| - The entire data for the classifier is available at https://archive.org/details/NudeNet_classifier_dataset_v1 | ||
| - A part of the auto-labelled data (Images are from the classifier dataset above) used to train the base Detector is available at https://github.com/notAI-tech/NudeNet/releases/download/v0/DETECTOR_AUTO_GENERATED_DATA.zip |
| pillow | ||
| opencv-python-headless>=4.5.1.48 | ||
| scikit-image | ||
| onnxruntime |
| LICENSE.md | ||
| README.md | ||
| setup.py | ||
| NudeNetUpdated.egg-info/PKG-INFO | ||
| NudeNetUpdated.egg-info/SOURCES.txt | ||
| NudeNetUpdated.egg-info/dependency_links.txt | ||
| NudeNetUpdated.egg-info/requires.txt | ||
| NudeNetUpdated.egg-info/top_level.txt | ||
| fastDeploy_recipes/classifier/predictor.py | ||
| src/__init__.py | ||
| src/classifier.py | ||
| src/detector.py | ||
| src/utils/__init__.py | ||
| src/utils/detector.py | ||
| src/utils/download.py | ||
| src/utils/image.py | ||
| src/utils/video.py |
| build | ||
| fastDeploy_recipes | ||
| src |
| [egg_info] | ||
| tag_build = | ||
| tag_date = 0 | ||
-133
| #!/usr/bin/env python | ||
| # -*- coding: utf-8 -*- | ||
| # Note: To use the 'upload' functionality of this file, you must: | ||
| # $ pip install twine | ||
| import io | ||
| import os | ||
| import sys | ||
| from shutil import rmtree | ||
| from setuptools import find_packages, setup, Command, find_namespace_packages | ||
| # Package meta-data. | ||
| NAME = 'NudeNetUpdated' | ||
| DESCRIPTION = 'A Neural Net for Nudity Detection. Classifier only.' | ||
| URL = 'https://github.com/eloiselle/NudeNet' | ||
| EMAIL = 'emile.loiselle@hotmail.com' | ||
| AUTHOR = 'Emile Loiselle' | ||
| REQUIRES_PYTHON = '>=3.6.0' | ||
| VERSION = '2.2.2' | ||
| # What packages are required for this module to be executed? | ||
| REQUIRED = [ | ||
| 'pillow', | ||
| 'opencv-python-headless>=4.5.1.48', | ||
| 'scikit-image', | ||
| 'onnxruntime', | ||
| ] | ||
| # What packages are optional? | ||
| EXTRAS = { | ||
| # 'fancy feature': ['django'], | ||
| } | ||
| # The rest you shouldn't have to touch too much :) | ||
| # ------------------------------------------------ | ||
| # Except, perhaps the License and Trove Classifiers! | ||
| # If you do change the License, remember to change the Trove Classifier for that! | ||
| here = os.path.abspath(os.path.dirname(__file__)) | ||
| # Import the README and use it as the long-description. | ||
| # Note: this will only work if 'README.md' is present in your MANIFEST.in file! | ||
| try: | ||
| with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f: | ||
| long_description = '\n' + f.read() | ||
| except FileNotFoundError: | ||
| long_description = DESCRIPTION | ||
| # Load the package's __version__.py module as a dictionary. | ||
| about = {} | ||
| if not VERSION: | ||
| with open(os.path.join(here, NAME, '__version__.py')) as f: | ||
| exec(f.read(), about) | ||
| else: | ||
| about['__version__'] = VERSION | ||
| class UploadCommand(Command): | ||
| """Support setup.py upload.""" | ||
| description = 'Build and publish the package.' | ||
| user_options = [] | ||
| @staticmethod | ||
| def status(s): | ||
| """Prints things in bold.""" | ||
| print('\033[1m{0}\033[0m'.format(s)) | ||
| def initialize_options(self): | ||
| pass | ||
| def finalize_options(self): | ||
| pass | ||
| def run(self): | ||
| try: | ||
| self.status('Removing previous builds…') | ||
| rmtree(os.path.join(here, 'dist')) | ||
| except OSError: | ||
| pass | ||
| self.status('Building Source and Wheel (universal) distribution…') | ||
| os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable)) | ||
| self.status('Uploading the package to PyPI via Twine…') | ||
| os.system('twine upload dist/*') | ||
| self.status('Pushing git tags…') | ||
| os.system('git tag v{0}'.format(about['__version__'])) | ||
| os.system('git push --tags') | ||
| sys.exit() | ||
| # Where the magic happens: | ||
| setup( | ||
| name=NAME, | ||
| version=about['__version__'], | ||
| description=DESCRIPTION, | ||
| long_description=long_description, | ||
| long_description_content_type='text/markdown', | ||
| author=AUTHOR, | ||
| author_email=EMAIL, | ||
| python_requires=REQUIRES_PYTHON, | ||
| url=URL, | ||
| packages=find_namespace_packages(exclude=('tests',)), | ||
| # If your package is a single module, use this instead of 'packages': | ||
| # py_modules=['mypackage'], | ||
| # entry_points={ | ||
| # 'console_scripts': ['mycli=mymodule:cli'], | ||
| # }, | ||
| install_requires=REQUIRED, | ||
| extras_require=EXTRAS, | ||
| include_package_data=True, | ||
| license='GPLv3', | ||
| classifiers=[ | ||
| # Trove classifiers | ||
| # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers | ||
| 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', | ||
| 'Programming Language :: Python', | ||
| 'Programming Language :: Python :: 3', | ||
| 'Programming Language :: Python :: 3.6', | ||
| 'Programming Language :: Python :: Implementation :: CPython', | ||
| 'Programming Language :: Python :: Implementation :: PyPy' | ||
| ], | ||
| # $ setup.py publish support. | ||
| cmdclass={ | ||
| 'upload': UploadCommand, | ||
| }, | ||
| ) |
| from classifier import Classifier as NudeClassifier | ||
| from detector import Detector as NudeDetector |
| import logging | ||
| import numpy as np | ||
| from utils.video import get_interest_frames_from_video | ||
| from utils.image import load_images | ||
| from utils.download import load_classifier_model | ||
| class Classifier: | ||
| """ | ||
| Class for loading model and running predictions. | ||
| For example on how to use take a look the if __name__ == '__main__' part. | ||
| """ | ||
| nsfw_model = None | ||
| def __init__(self): | ||
| """ | ||
| model = Classifier() | ||
| """ | ||
| self.nsfw_model = load_classifier_model() | ||
| def classify( | ||
| self, | ||
| image_paths=[], | ||
| batch_size=4, | ||
| image_size=(256, 256), | ||
| categories=["unsafe", "safe"], | ||
| ): | ||
| """ | ||
| inputs: | ||
| image_paths: list of image paths or can be a string too (for single image) | ||
| batch_size: batch_size for running predictions | ||
| image_size: size to which the image needs to be resized | ||
| categories: since the model predicts numbers, categories is the list of actual names of categories | ||
| """ | ||
| if not isinstance(image_paths, list): | ||
| image_paths = [image_paths] | ||
| loaded_images, loaded_image_paths = load_images( | ||
| image_paths, image_size, image_names=image_paths | ||
| ) | ||
| if not loaded_image_paths: | ||
| return {} | ||
| preds = [] | ||
| model_preds = [] | ||
| while len(loaded_images): | ||
| _model_preds = self.nsfw_model.run( | ||
| [self.nsfw_model.get_outputs()[0].name], | ||
| {self.nsfw_model.get_inputs()[0].name: loaded_images[:batch_size]}, | ||
| )[0] | ||
| model_preds.append(_model_preds) | ||
| preds += np.argsort(_model_preds, axis=1).tolist() | ||
| loaded_images = loaded_images[batch_size:] | ||
| probs = [] | ||
| for i, single_preds in enumerate(preds): | ||
| single_probs = [] | ||
| for j, pred in enumerate(single_preds): | ||
| single_probs.append( | ||
| model_preds[int(i / batch_size)][int(i % batch_size)][pred] | ||
| ) | ||
| preds[i][j] = categories[pred] | ||
| probs.append(single_probs) | ||
| images_preds = {} | ||
| for i, loaded_image_path in enumerate(loaded_image_paths): | ||
| if not isinstance(loaded_image_path, str): | ||
| loaded_image_path = i | ||
| images_preds[loaded_image_path] = {} | ||
| for _ in range(len(preds[i])): | ||
| images_preds[loaded_image_path][preds[i][_]] = float(probs[i][_]) | ||
| return images_preds | ||
| def classify_video( | ||
| self, | ||
| video_path, | ||
| batch_size=4, | ||
| image_size=(256, 256), | ||
| categories=["unsafe", "safe"], | ||
| ): | ||
| frame_indices = None | ||
| frame_indices, frames, fps, video_length = get_interest_frames_from_video( | ||
| video_path | ||
| ) | ||
| logging.debug( | ||
| f"VIDEO_PATH: {video_path}, FPS: {fps}, Important frame indices: {frame_indices}, Video length: {video_length}" | ||
| ) | ||
| frames, frame_names = load_images(frames, image_size, image_names=frame_indices) | ||
| if not frame_names: | ||
| return {} | ||
| preds = [] | ||
| model_preds = [] | ||
| while len(frames): | ||
| _model_preds = self.nsfw_model.run( | ||
| [self.nsfw_model.get_outputs()[0].name], | ||
| {self.nsfw_model.get_inputs()[0].name: frames[:batch_size]}, | ||
| )[0] | ||
| model_preds.append(_model_preds) | ||
| preds += np.argsort(_model_preds, axis=1).tolist() | ||
| frames = frames[batch_size:] | ||
| probs = [] | ||
| for i, single_preds in enumerate(preds): | ||
| single_probs = [] | ||
| for j, pred in enumerate(single_preds): | ||
| single_probs.append( | ||
| model_preds[int(i / batch_size)][int(i % batch_size)][pred] | ||
| ) | ||
| preds[i][j] = categories[pred] | ||
| probs.append(single_probs) | ||
| return_preds = { | ||
| "metadata": { | ||
| "fps": fps, | ||
| "video_length": video_length, | ||
| "video_path": video_path, | ||
| }, | ||
| "preds": {}, | ||
| } | ||
| for i, frame_name in enumerate(frame_names): | ||
| return_preds["preds"][frame_name] = {} | ||
| for _ in range(len(preds[i])): | ||
| return_preds["preds"][frame_name][preds[i][_]] = probs[i][_] | ||
| return return_preds | ||
| if __name__ == "__main__": | ||
| m = Classifier() | ||
| while 1: | ||
| print( | ||
| "\n Enter single image path or multiple images seperated by || (2 pipes) \n" | ||
| ) | ||
| images = input().split("||") | ||
| images = [image.strip() for image in images] | ||
| print(m.classify(images), "\n") |
-182
| import cv2 | ||
| import logging | ||
| import numpy as np | ||
| from progressbar import progressbar | ||
| from utils.detector import preprocess_image | ||
| from utils.video import get_interest_frames_from_video | ||
| from utils.download import load_detector_classes, load_detector_model | ||
| class Detector: | ||
| detection_model = None | ||
| classes = None | ||
| def __init__(self): | ||
| """ | ||
| model = Detector() | ||
| """ | ||
| self.detection_model = load_detector_model() | ||
| self.classes = load_detector_classes() | ||
| def detect_video( | ||
| self, video_path, mode="default", min_prob=0.6, batch_size=2, show_progress=True | ||
| ): | ||
| frame_indices, frames, fps, video_length = get_interest_frames_from_video( | ||
| video_path | ||
| ) | ||
| logging.debug( | ||
| f"VIDEO_PATH: {video_path}, FPS: {fps}, Important frame indices: {frame_indices}, Video length: {video_length}" | ||
| ) | ||
| if mode == "fast": | ||
| frames = [ | ||
| preprocess_image(frame, min_side=480, max_side=800) for frame in frames | ||
| ] | ||
| else: | ||
| frames = [preprocess_image(frame) for frame in frames] | ||
| scale = frames[0][1] | ||
| frames = [frame[0] for frame in frames] | ||
| all_results = { | ||
| "metadata": { | ||
| "fps": fps, | ||
| "video_length": video_length, | ||
| "video_path": video_path, | ||
| }, | ||
| "preds": {}, | ||
| } | ||
| progress_func = progressbar | ||
| for _ in progress_func(range(int(len(frames) / batch_size) + 1)): | ||
| batch = frames[:batch_size] | ||
| batch_indices = frame_indices[:batch_size] | ||
| frames = frames[batch_size:] | ||
| frame_indices = frame_indices[batch_size:] | ||
| if batch_indices: | ||
| outputs = self.detection_model.run( | ||
| [s_i.name for s_i in self.detection_model.get_outputs()], | ||
| {self.detection_model.get_inputs()[0].name: np.asarray(batch)}, | ||
| ) | ||
| labels = [op for op in outputs if op.dtype == "int32"][0] | ||
| scores = [op for op in outputs if isinstance(op[0][0], np.float32)][0] | ||
| boxes = [op for op in outputs if isinstance(op[0][0], np.ndarray)][0] | ||
| boxes /= scale | ||
| for frame_index, frame_boxes, frame_scores, frame_labels in zip( | ||
| frame_indices, boxes, scores, labels | ||
| ): | ||
| if frame_index not in all_results["preds"]: | ||
| all_results["preds"][frame_index] = [] | ||
| for box, score, label in zip( | ||
| frame_boxes, frame_scores, frame_labels | ||
| ): | ||
| if score < min_prob: | ||
| continue | ||
| box = box.astype(int).tolist() | ||
| label = self.classes[label] | ||
| all_results["preds"][frame_index].append( | ||
| { | ||
| "box": [int(c) for c in box], | ||
| "score": float(score), | ||
| "label": label, | ||
| } | ||
| ) | ||
| return all_results | ||
| def detect_labels(self, img_path, mode="default", min_prob=None): | ||
| if mode == "fast": | ||
| image, scale = preprocess_image(img_path, min_side=480, max_side=800) | ||
| if not min_prob: | ||
| min_prob = 0.5 | ||
| else: | ||
| image, scale = preprocess_image(img_path) | ||
| if not min_prob: | ||
| min_prob = 0.6 | ||
| outputs = self.detection_model.run( | ||
| [s_i.name for s_i in self.detection_model.get_outputs()], | ||
| {self.detection_model.get_inputs()[0].name: np.expand_dims(image, axis=0)}, | ||
| ) | ||
| labels = [op for op in outputs if op.dtype == "int32"][0] | ||
| scores = [op for op in outputs if isinstance(op[0][0], np.float32)][0] | ||
| processed_results = [] | ||
| for score, label in zip(scores[0], labels[0]): | ||
| if score < min_prob: | ||
| continue | ||
| label = self.classes[label] | ||
| processed_results.append( | ||
| {"label": label, "score": float(score)} | ||
| ) | ||
| return processed_results | ||
| def detect(self, img_path, mode="default", min_prob=None): | ||
| if mode == "fast": | ||
| image, scale = preprocess_image(img_path, min_side=480, max_side=800) | ||
| if not min_prob: | ||
| min_prob = 0.5 | ||
| else: | ||
| image, scale = preprocess_image(img_path) | ||
| if not min_prob: | ||
| min_prob = 0.6 | ||
| outputs = self.detection_model.run( | ||
| [s_i.name for s_i in self.detection_model.get_outputs()], | ||
| {self.detection_model.get_inputs()[0].name: np.expand_dims(image, axis=0)}, | ||
| ) | ||
| labels = [op for op in outputs if op.dtype == "int32"][0] | ||
| scores = [op for op in outputs if isinstance(op[0][0], np.float32)][0] | ||
| boxes = [op for op in outputs if isinstance(op[0][0], np.ndarray)][0] | ||
| boxes /= scale | ||
| processed_boxes = [] | ||
| for box, score, label in zip(boxes[0], scores[0], labels[0]): | ||
| if score < min_prob: | ||
| continue | ||
| box = box.astype(int).tolist() | ||
| label = self.classes[label] | ||
| processed_boxes.append( | ||
| {"box": [int(c) for c in box], "score": float(score), "label": label} | ||
| ) | ||
| return processed_boxes | ||
| def censor(self, img_path, out_path=None, visualize=False, parts_to_blur=[]): | ||
| if not out_path and not visualize: | ||
| print( | ||
| "No out_path passed and visualize is set to false. There is no point in running this function then." | ||
| ) | ||
| return | ||
| image = cv2.imread(img_path) | ||
| boxes = self.detect(img_path) | ||
| if parts_to_blur: | ||
| boxes = [i["box"] for i in boxes if i["label"] in parts_to_blur] | ||
| else: | ||
| boxes = [i["box"] for i in boxes] | ||
| for box in boxes: | ||
| part = image[box[1] : box[3], box[0] : box[2]] | ||
| image = cv2.rectangle( | ||
| image, (box[0], box[1]), (box[2], box[3]), (0, 0, 0), cv2.FILLED | ||
| ) | ||
| if visualize: | ||
| cv2.imshow("Blurred image", image) | ||
| cv2.waitKey(0) | ||
| if out_path: | ||
| cv2.imwrite(out_path, image) | ||
| if __name__ == "__main__": | ||
| m = Detector() | ||
| print(m.detect("/Users/bedapudi/Desktop/n2.jpg")) |
| import detector | ||
| import download | ||
| import image | ||
| import video |
| import cv2 | ||
| import numpy as np | ||
| from PIL import Image | ||
| def read_image_bgr(path): | ||
| """ Read an image in BGR format. | ||
| Args | ||
| path: Path to the image. | ||
| """ | ||
| if isinstance(path, str): | ||
| image = np.ascontiguousarray(Image.open(path).convert("RGB")) | ||
| else: | ||
| path = cv2.cvtColor(path, cv2.COLOR_BGR2RGB) | ||
| image = np.ascontiguousarray(Image.fromarray(path)) | ||
| return image[:, :, ::-1] | ||
| def _preprocess_image(x, mode="caffe"): | ||
| x = x.astype(np.float32) | ||
| if mode == "tf": | ||
| x /= 127.5 | ||
| x -= 1.0 | ||
| elif mode == "caffe": | ||
| x -= [103.939, 116.779, 123.68] | ||
| return x | ||
| def compute_resize_scale(image_shape, min_side=800, max_side=1333): | ||
| (rows, cols, _) = image_shape | ||
| smallest_side = min(rows, cols) | ||
| scale = min_side / smallest_side | ||
| largest_side = max(rows, cols) | ||
| if largest_side * scale > max_side: | ||
| scale = max_side / largest_side | ||
| return scale | ||
| def resize_image(img, min_side=800, max_side=1333): | ||
| scale = compute_resize_scale(img.shape, min_side=min_side, max_side=max_side) | ||
| img = cv2.resize(img, None, fx=scale, fy=scale) | ||
| return img, scale | ||
| def preprocess_image( | ||
| image_path, min_side=800, max_side=1333, | ||
| ): | ||
| image = read_image_bgr(image_path) | ||
| image = _preprocess_image(image) | ||
| image, scale = resize_image(image, min_side=min_side, max_side=max_side) | ||
| return image, scale |
| import os | ||
| import pydload | ||
| import onnxruntime | ||
| DATA_DIR=os.path.normpath(os.path.join(os.path.dirname(__file__), '..', "models")) | ||
| DETECTOR_MODEL_PATH=os.path.join(DATA_DIR, 'detector_model.onnx') | ||
| DETECTOR_MODEL_URL='https://onedrive.live.com/download?cid=C5669415F4DE9E91&resid=C5669415F4DE9E91%21299479&authkey=ADVoe7wRG0KRcUo' | ||
| DETECTOR_CLASSES_PATH=os.path.join(DATA_DIR, 'detector_classes.onnx') | ||
| DETECTOR_CLASSES_URL='https://onedrive.live.com/download?cid=C5669415F4DE9E91&resid=C5669415F4DE9E91%21299477&authkey=AO1Ekn4UySCrzmk' | ||
| CLASSIFIER_MODEL_PATH=os.path.join(DATA_DIR, 'classifier_model.onnx') | ||
| CLASSIFIER_MODEL_URL='https://onedrive.live.com/download?cid=C5669415F4DE9E91&resid=C5669415F4DE9E91%21299478&authkey=AATSwER87jN2084' | ||
| def load_classifier_model(): | ||
| if not os.path.exists(CLASSIFIER_MODEL_PATH): | ||
| download_data(CLASSIFIER_MODEL_URL, CLASSIFIER_MODEL_PATH) | ||
| data = onnxruntime.InferenceSession(CLASSIFIER_MODEL_PATH) | ||
| return data | ||
| def load_detector_model(): | ||
| if not os.path.exists(DETECTOR_MODEL_PATH): | ||
| download_data(DETECTOR_MODEL_URL, DETECTOR_MODEL_PATH) | ||
| data = onnxruntime.InferenceSession(DETECTOR_MODEL_PATH) | ||
| return data | ||
| def load_detector_classes(): | ||
| if not os.path.exists(DETECTOR_CLASSES_PATH): | ||
| download_data(DETECTOR_CLASSES_URL, DETECTOR_CLASSES_PATH) | ||
| data = [c.strip() for c in open(DETECTOR_CLASSES_PATH).readlines() if c.strip()] | ||
| return data | ||
| def download_data(url, save_path): | ||
| if not os.path.exists(DATA_DIR): | ||
| os.mkdir(DATA_DIR) | ||
| print("Downloading file to " + save_path) | ||
| try: | ||
| pydload.dload(url, save_to_path=save_path, max_time=None) | ||
| except: | ||
| print("An exception occured during the download.") |
| import io | ||
| import cv2 | ||
| import logging | ||
| import numpy as np | ||
| from PIL import Image as pil_image | ||
| if pil_image is not None: | ||
| _PIL_INTERPOLATION_METHODS = { | ||
| "nearest": pil_image.NEAREST, | ||
| "bilinear": pil_image.BILINEAR, | ||
| "bicubic": pil_image.BICUBIC, | ||
| } | ||
| # These methods were only introduced in version 3.4.0 (2016). | ||
| if hasattr(pil_image, "HAMMING"): | ||
| _PIL_INTERPOLATION_METHODS["hamming"] = pil_image.HAMMING | ||
| if hasattr(pil_image, "BOX"): | ||
| _PIL_INTERPOLATION_METHODS["box"] = pil_image.BOX | ||
| # This method is new in version 1.1.3 (2013). | ||
| if hasattr(pil_image, "LANCZOS"): | ||
| _PIL_INTERPOLATION_METHODS["lanczos"] = pil_image.LANCZOS | ||
| def load_img( | ||
| path, grayscale=False, color_mode="rgb", target_size=None, interpolation="nearest" | ||
| ): | ||
| """Loads an image into PIL format. | ||
| :param path: Path to image file. | ||
| :param grayscale: DEPRECATED use `color_mode="grayscale"`. | ||
| :param color_mode: One of "grayscale", "rgb", "rgba". Default: "rgb". | ||
| The desired image format. | ||
| :param target_size: Either `None` (default to original size) | ||
| or tuple of ints `(img_height, img_width)`. | ||
| :param interpolation: Interpolation method used to resample the image if the | ||
| target size is different from that of the loaded image. | ||
| Supported methods are "nearest", "bilinear", and "bicubic". | ||
| If PIL version 1.1.3 or newer is installed, "lanczos" is also | ||
| supported. If PIL version 3.4.0 or newer is installed, "box" and | ||
| "hamming" are also supported. By default, "nearest" is used. | ||
| :return: A PIL Image instance. | ||
| """ | ||
| if grayscale is True: | ||
| logging.warn("grayscale is deprecated. Please use " 'color_mode = "grayscale"') | ||
| color_mode = "grayscale" | ||
| if pil_image is None: | ||
| raise ImportError( | ||
| "Could not import PIL.Image. " "The use of `load_img` requires PIL." | ||
| ) | ||
| if isinstance(path, (str, io.IOBase)): | ||
| img = pil_image.open(path) | ||
| else: | ||
| path = cv2.cvtColor(path, cv2.COLOR_BGR2RGB) | ||
| img = pil_image.fromarray(path) | ||
| if color_mode == "grayscale": | ||
| if img.mode != "L": | ||
| img = img.convert("L") | ||
| elif color_mode == "rgba": | ||
| if img.mode != "RGBA": | ||
| img = img.convert("RGBA") | ||
| elif color_mode == "rgb": | ||
| if img.mode != "RGB": | ||
| img = img.convert("RGB") | ||
| else: | ||
| raise ValueError('color_mode must be "grayscale", "rgb", or "rgba"') | ||
| if target_size is not None: | ||
| width_height_tuple = (target_size[1], target_size[0]) | ||
| if img.size != width_height_tuple: | ||
| if interpolation not in _PIL_INTERPOLATION_METHODS: | ||
| raise ValueError( | ||
| "Invalid interpolation method {} specified. Supported " | ||
| "methods are {}".format( | ||
| interpolation, ", ".join(_PIL_INTERPOLATION_METHODS.keys()) | ||
| ) | ||
| ) | ||
| resample = _PIL_INTERPOLATION_METHODS[interpolation] | ||
| img = img.resize(width_height_tuple, resample) | ||
| return img | ||
| def img_to_array(img, data_format="channels_last", dtype="float32"): | ||
| """Converts a PIL Image instance to a Numpy array. | ||
| # Arguments | ||
| img: PIL Image instance. | ||
| data_format: Image data format, | ||
| either "channels_first" or "channels_last". | ||
| dtype: Dtype to use for the returned array. | ||
| # Returns | ||
| A 3D Numpy array. | ||
| # Raises | ||
| ValueError: if invalid `img` or `data_format` is passed. | ||
| """ | ||
| if data_format not in {"channels_first", "channels_last"}: | ||
| raise ValueError("Unknown data_format: %s" % data_format) | ||
| # Numpy array x has format (height, width, channel) | ||
| # or (channel, height, width) | ||
| # but original PIL image has format (width, height, channel) | ||
| x = np.asarray(img, dtype=dtype) | ||
| if len(x.shape) == 3: | ||
| if data_format == "channels_first": | ||
| x = x.transpose(2, 0, 1) | ||
| elif len(x.shape) == 2: | ||
| if data_format == "channels_first": | ||
| x = x.reshape((1, x.shape[0], x.shape[1])) | ||
| else: | ||
| x = x.reshape((x.shape[0], x.shape[1], 1)) | ||
| else: | ||
| raise ValueError("Unsupported image shape: %s" % (x.shape,)) | ||
| return x | ||
| def load_images(image_paths, image_size, image_names): | ||
| """ | ||
| Function for loading images into numpy arrays for passing to model.predict | ||
| inputs: | ||
| image_paths: list of image paths to load | ||
| image_size: size into which images should be resized | ||
| outputs: | ||
| loaded_images: loaded images on which keras model can run predictions | ||
| loaded_image_indexes: paths of images which the function is able to process | ||
| """ | ||
| loaded_images = [] | ||
| loaded_image_paths = [] | ||
| for i, img_path in enumerate(image_paths): | ||
| try: | ||
| image = load_img(img_path, target_size=image_size) | ||
| image = img_to_array(image) | ||
| image /= 255 | ||
| loaded_images.append(image) | ||
| loaded_image_paths.append(image_names[i]) | ||
| except Exception as ex: | ||
| logging.exception(f"Error reading {img_path} {ex}", exc_info=True) | ||
| return np.asarray(loaded_images), loaded_image_paths |
| import cv2 | ||
| import os | ||
| import logging | ||
| from skimage import metrics as skimage_metrics | ||
| def is_similar_frame(f1, f2, resize_to=(64, 64), thresh=0.5, return_score=False): | ||
| thresh = float(os.getenv("FRAME_SIMILARITY_THRESH", thresh)) | ||
| if f1 is None or f2 is None: | ||
| return False | ||
| if isinstance(f1, str) and os.path.exists(f1): | ||
| try: | ||
| f1 = cv2.imread(f1) | ||
| except Exception as ex: | ||
| logging.exception(ex, exc_info=True) | ||
| return False | ||
| if isinstance(f2, str) and os.path.exists(f2): | ||
| try: | ||
| f2 = cv2.imread(f2) | ||
| except Exception as ex: | ||
| logging.exception(ex, exc_info=True) | ||
| return False | ||
| if resize_to: | ||
| f1 = cv2.resize(f1, resize_to) | ||
| f2 = cv2.resize(f2, resize_to) | ||
| if len(f1.shape) == 3: | ||
| f1 = f1[:, :, 0] | ||
| if len(f2.shape) == 3: | ||
| f2 = f2[:, :, 0] | ||
| score = skimage_metrics.structural_similarity(f1, f2, multichannel=False) | ||
| if return_score: | ||
| return score | ||
| if score >= thresh: | ||
| return True | ||
| return False | ||
| def get_interest_frames_from_video( | ||
| video_path, | ||
| frame_similarity_threshold=0.5, | ||
| similarity_context_n_frames=3, | ||
| skip_n_frames=0.5, | ||
| output_frames_to_dir=None, | ||
| ): | ||
| skip_n_frames = float(os.getenv("SKIP_N_FRAMES", skip_n_frames)) | ||
| important_frames = [] | ||
| fps = 0 | ||
| video_length = 0 | ||
| try: | ||
| video = cv2.VideoCapture(video_path) | ||
| fps = video.get(cv2.CAP_PROP_FPS) | ||
| length = int(video.get(cv2.CAP_PROP_FRAME_COUNT)) | ||
| if skip_n_frames < 1: | ||
| skip_n_frames = int(skip_n_frames * fps) | ||
| logging.info(f"skip_n_frames: {skip_n_frames}") | ||
| video_length = int(video.get(cv2.CAP_PROP_FRAME_COUNT)) | ||
| for frame_i in range(length + 1): | ||
| read_flag, current_frame = video.read() | ||
| if not read_flag: | ||
| break | ||
| if skip_n_frames > 0: | ||
| if frame_i % skip_n_frames != 0: | ||
| continue | ||
| frame_i += 1 | ||
| found_similar = False | ||
| for context_frame_i, context_frame in reversed( | ||
| important_frames[-1 * similarity_context_n_frames :] | ||
| ): | ||
| if is_similar_frame( | ||
| context_frame, current_frame, thresh=frame_similarity_threshold | ||
| ): | ||
| logging.debug(f"{frame_i} is similar to {context_frame_i}") | ||
| found_similar = True | ||
| break | ||
| if not found_similar: | ||
| logging.debug(f"{frame_i} is added to important frames") | ||
| important_frames.append((frame_i, current_frame)) | ||
| if output_frames_to_dir: | ||
| if not os.path.exists(output_frames_to_dir): | ||
| os.mkdir(output_frames_to_dir) | ||
| output_frames_to_dir = output_frames_to_dir.rstrip("/") | ||
| cv2.imwrite( | ||
| f"{output_frames_to_dir}/{str(frame_i).zfill(10)}.png", | ||
| current_frame, | ||
| ) | ||
| logging.info( | ||
| f"{len(important_frames)} important frames will be processed from {video_path} of length {length}" | ||
| ) | ||
| except Exception as ex: | ||
| logging.exception(ex, exc_info=True) | ||
| return ( | ||
| [i[0] for i in important_frames], | ||
| [i[1] for i in important_frames], | ||
| fps, | ||
| video_length, | ||
| ) | ||
| if __name__ == "__main__": | ||
| import sys | ||
| imp_frames = get_interest_frames_from_video( | ||
| sys.argv[1], output_frames_to_dir="./frames/" | ||
| ) | ||
| print([i[0] for i in imp_frames]) |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
20237
-55.61%10
-47.37%365
-48.59%