New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

NudeNetUpdated

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

NudeNetUpdated - pypi Package Compare versions

Comparing version
2.2.2
to
2.2.3
+46
pyproject.toml
[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:
[![DOI](https://zenodo.org/badge/173154449.svg)](https://zenodo.org/badge/latestdoi/173154449) ![Upload Python package](https://github.com/notAI-tech/NudeNet/actions/workflows/python-publish.yml/badge.svg)
- 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.
![](https://i.imgur.com/0KPJbl9.jpg)
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
[![DOI](https://zenodo.org/badge/173154449.svg)](https://zenodo.org/badge/latestdoi/173154449) ![Upload Python package](https://github.com/notAI-tech/NudeNet/actions/workflows/python-publish.yml/badge.svg)
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
```
![](https://i.imgur.com/0KPJbl9.jpg)
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)
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
[![DOI](https://zenodo.org/badge/173154449.svg)](https://zenodo.org/badge/latestdoi/173154449) ![Upload Python package](https://github.com/notAI-tech/NudeNet/actions/workflows/python-publish.yml/badge.svg)
## 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)
![](https://i.imgur.com/0KPJbl9.jpg)
**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
#!/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")
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])