Schools NL, Ghost post utils, nude + age detection
This commit is contained in:
157
app_cv/Demo.ipynb
Normal file
157
app_cv/Demo.ipynb
Normal file
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import base64\n",
|
||||
"import json\n",
|
||||
"import requests\n",
|
||||
"import io\n",
|
||||
"import numpy as np\n",
|
||||
"import PIL.Image\n",
|
||||
"import cv2\n",
|
||||
"from pprint import pprint\n",
|
||||
"\n",
|
||||
"def process_image(path_img):\n",
|
||||
" with open(path_img, \"rb\") as image_file:\n",
|
||||
" encoded_string = base64.b64encode(image_file.read()).decode('utf-8')\n",
|
||||
" response = requests.post(\n",
|
||||
" 'http://localhost:5000/process',\n",
|
||||
" headers={'Content-Type': 'application/json'},\n",
|
||||
" data=json.dumps({'image': encoded_string})\n",
|
||||
" )\n",
|
||||
" response_dict = response.json()\n",
|
||||
" pprint(response_dict)\n",
|
||||
" # Decode\n",
|
||||
" image_bytes = base64.b64decode(response_dict.get(\"image_b64\"))\n",
|
||||
" img_array = np.frombuffer(io.BytesIO(image_bytes).getvalue(), dtype=np.uint8)\n",
|
||||
" img_bgr = cv2.imdecode(img_array, cv2.IMREAD_COLOR)\n",
|
||||
" img_rgb = img_bgr[:, :, ::-1]\n",
|
||||
" return img_rgb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"path_img = \"imgs/img_1p.jpg\"\n",
|
||||
"PIL.Image.fromarray( process_image(path_img) )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"path_img = \"imgs/img_nude.jpg\"\n",
|
||||
"PIL.Image.fromarray( process_image(path_img) )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"'''\n",
|
||||
"# !git clone https://github.com/wildchlamydia/mivolo\n",
|
||||
"# !pip install ultralytics yt_dlp pandas scipy timm==0.8.13.dev0\n",
|
||||
"# !pip install ./mivolo\n",
|
||||
"\n",
|
||||
"!python mivolo/demo.py \\\n",
|
||||
" --input \"face_data/sample_image.jpg\" \\\n",
|
||||
" --output \"output\" \\\n",
|
||||
" --detector-weights \"mivolo/pretrained/yolov8x_person_face.pt\" \\\n",
|
||||
" --checkpoint \"mivolo/pretrained/model_imdb_cross_person_4.22_99.46.pth.tar\" \\\n",
|
||||
" --device \"cpu\" \\\n",
|
||||
" --draw\n",
|
||||
"'''\n",
|
||||
"\n",
|
||||
"'''\n",
|
||||
"# !git clone https://github.com/Kartik-3004/facexformer.git\n",
|
||||
"# !pip install huggingface_hub torch torchvision torchaudio opencv-python facenet_pytorch\n",
|
||||
"from huggingface_hub import hf_hub_download\n",
|
||||
"hf_hub_download(repo_id=\"kartiknarayan/facexformer\", filename=\"ckpts/model.pt\", local_dir=\"./facexformer\")\n",
|
||||
"\n",
|
||||
"!python facexformer/inference.py \\\n",
|
||||
" --model_path facexformer/ckpts/model.pt \\\n",
|
||||
" --image_path face_data/sample_image.jpg \\\n",
|
||||
" --results_path face_data \\\n",
|
||||
" --task parsing\n",
|
||||
" x\n",
|
||||
"!python facexformer/inference.py \\\n",
|
||||
" --model_path facexformer/ckpts/model.pt \\\n",
|
||||
" --image_path face_data/face.png \\\n",
|
||||
" --results_path face_data \\\n",
|
||||
" --task landmarks\n",
|
||||
"\n",
|
||||
"!python facexformer/inference.py \\\n",
|
||||
" --model_path facexformer/ckpts/model.pt \\\n",
|
||||
" --image_path face_data/face.png \\\n",
|
||||
" --results_path face_data \\\n",
|
||||
" --task headpose\n",
|
||||
"\n",
|
||||
"!python facexformer/inference.py \\\n",
|
||||
" --model_path facexformer/ckpts/model.pt \\\n",
|
||||
" --image_path face_data/face.png \\\n",
|
||||
" --results_path face_data \\\n",
|
||||
" --task attributes\n",
|
||||
"\n",
|
||||
"!python facexformer/inference.py \\\n",
|
||||
" --model_path facexformer/ckpts/model.pt \\\n",
|
||||
" --image_path face_data/face.png \\\n",
|
||||
" --results_path face_data \\\n",
|
||||
" --task age_gender_race\n",
|
||||
"'''"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "matitos_cv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
17
app_cv/README.md
Normal file
17
app_cv/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Requirements
|
||||
|
||||
```
|
||||
pip install git+https://github.com/wildchlamydia/mivolo.git "nudenet>=3.4.2"
|
||||
```
|
||||
|
||||
- Download checkpoints
|
||||
- https://github.com/wildchlamydia/mivolo
|
||||
- models/mivolo/model_imdb_cross_person_4.22_99.46.pth.tar
|
||||
- models/mivolo/yolov8x_person_face.pt
|
||||
- https://github.com/notAI-tech/NudeNet?tab=readme-ov-file#available-models
|
||||
- models/nude_detector/640m.onnx
|
||||
|
||||
|
||||
# TODO
|
||||
|
||||
- Client side inference: https://github.com/notAI-tech/NudeNet/tree/v3/in_browser
|
||||
35
app_cv/Server.ipynb
Normal file
35
app_cv/Server.ipynb
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# !pip install flask\n",
|
||||
"!python app.py"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "matitos_cv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
48
app_cv/app.py
Normal file
48
app_cv/app.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from flask import Flask, request, jsonify
|
||||
import base64
|
||||
import io
|
||||
import cv2
|
||||
import traceback
|
||||
import os
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s [%(levelname)s] %(message)s', handlers=[logging.StreamHandler()])
|
||||
from cv_processor import process
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/process', methods=['POST'])
|
||||
def process_image():
|
||||
logging.info("POST /process")
|
||||
# Json
|
||||
data = request.get_json()
|
||||
# Valid data?
|
||||
if not data or 'image' not in data:
|
||||
return jsonify({"error": "No image data provided"}), 400
|
||||
|
||||
try:
|
||||
image_data = data['image']
|
||||
|
||||
# Decode base64 string
|
||||
image_bytes = base64.b64decode(image_data)
|
||||
image_stream = io.BytesIO(image_bytes)
|
||||
|
||||
# Process the image
|
||||
results = process(image_stream)
|
||||
|
||||
# Encode processed image to base64
|
||||
_, buffer = cv2.imencode('.jpg', results.get("image"), [cv2.IMWRITE_JPEG_QUALITY, 100])
|
||||
processed_image_base64 = base64.b64encode(buffer).decode('utf-8')
|
||||
|
||||
# Update image with base64 encoded
|
||||
results["image_b64"] = processed_image_base64
|
||||
# Pop image (not serializable)
|
||||
results.pop("image")
|
||||
# Jsonify
|
||||
return jsonify(results)
|
||||
|
||||
except Exception as e:
|
||||
logging.warning("Exception: {}".format(traceback.format_exc()))
|
||||
return jsonify({"error": traceback.format_exc()}), 400
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=os.getenv("DEBUG_MODE", False))
|
||||
132
app_cv/cv_processor.py
Normal file
132
app_cv/cv_processor.py
Normal file
@@ -0,0 +1,132 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s [%(levelname)s] %(message)s', handlers=[logging.StreamHandler()])
|
||||
|
||||
# Age
|
||||
from mivolo.predictor import Predictor
|
||||
import argparse
|
||||
# Nudity
|
||||
from nudenet import NudeDetector
|
||||
|
||||
|
||||
class CV():
|
||||
def __init__(self):
|
||||
args = argparse.ArgumentParser()
|
||||
args.add_argument("--device", type=str, default="cpu")
|
||||
args.add_argument("--checkpoint", default="models/mivolo/model_imdb_cross_person_4.22_99.46.pth.tar")
|
||||
args.add_argument("--detector_weights", default="models/mivolo/yolov8x_person_face.pt")
|
||||
args.add_argument("--with-persons", action="store_true", default=False, help="If set model will run with persons, if available")
|
||||
args.add_argument("--disable-faces", action="store_true", default=False, help="If set model will use only persons if available")
|
||||
args.add_argument("--draw", action="store_true", default=False, help="If set, resulted images will be drawn")
|
||||
args = args.parse_args([])
|
||||
# Initialize
|
||||
self.predictor_age = Predictor(args)
|
||||
|
||||
# Initialize
|
||||
self.nude_detector = NudeDetector(model_path="models/nude_detector/640m.onnx", inference_resolution=640)
|
||||
# detector = NudeDetector(model_path="downloaded_640m.onnx path", inference_resolution=640)
|
||||
# https://github.com/notAI-tech/NudeNet?tab=readme-ov-file#available-models
|
||||
|
||||
# All labels list
|
||||
self.nudity_all_labels = [
|
||||
"FEMALE_GENITALIA_COVERED",
|
||||
"FACE_FEMALE",
|
||||
"BUTTOCKS_EXPOSED",
|
||||
"FEMALE_BREAST_EXPOSED",
|
||||
"FEMALE_GENITALIA_EXPOSED",
|
||||
"MALE_BREAST_EXPOSED",
|
||||
"ANUS_EXPOSED",
|
||||
"FEET_EXPOSED",
|
||||
"BELLY_COVERED",
|
||||
"FEET_COVERED",
|
||||
"ARMPITS_COVERED",
|
||||
"ARMPITS_EXPOSED",
|
||||
"FACE_MALE",
|
||||
"BELLY_EXPOSED",
|
||||
"MALE_GENITALIA_EXPOSED",
|
||||
"ANUS_COVERED",
|
||||
"FEMALE_BREAST_COVERED",
|
||||
"BUTTOCKS_COVERED",
|
||||
]
|
||||
# Classes of interest
|
||||
self.nudity_classes_of_interest = ["BUTTOCKS_EXPOSED", "FEMALE_BREAST_EXPOSED", "FEMALE_GENITALIA_EXPOSED", "ANUS_EXPOSED", "MALE_GENITALIA_EXPOSED"]
|
||||
|
||||
def _censor(self, image_bgr, detections):
|
||||
# Copy original image
|
||||
image_bgr_censored = image_bgr.copy()
|
||||
|
||||
for detection in detections:
|
||||
box = detection["box"]
|
||||
x, y, w, h = box[0], box[1], box[2], box[3]
|
||||
# Change these pixels to pure black
|
||||
image_bgr_censored[y : y + h, x : x + w] = (0, 0, 0)
|
||||
|
||||
return image_bgr_censored
|
||||
|
||||
def process_image(self, image_bgr):
|
||||
###################################################################
|
||||
# Predict
|
||||
detected_objects, out_img = self.predictor_age.recognize(image_bgr)
|
||||
logging.debug("#persons: {}, #faces: {}".format(detected_objects.n_persons, detected_objects.n_faces))
|
||||
|
||||
# Num faces and persons detected
|
||||
detected_objects.n_faces, detected_objects.n_persons
|
||||
# Association
|
||||
detected_objects.associate_faces_with_persons()
|
||||
|
||||
# detected_objects.face_to_person_map
|
||||
# {2: 1, 3: 0}
|
||||
# detected_objects.ages
|
||||
# [None, None, 27.18, 23.77]
|
||||
age_predictions = [e for e in detected_objects.ages if e is not None]
|
||||
|
||||
# Crops of faces & persons
|
||||
# crops = detected_objects.collect_crops(img)
|
||||
any_minor_present = any([ a < 18 for a in detected_objects.ages if a is not None ])
|
||||
###################################################################
|
||||
|
||||
###################################################################
|
||||
# Predict
|
||||
nude_detections = self.nude_detector.detect(image_bgr)
|
||||
logging.debug("Nude detections: {}".format(nude_detections))
|
||||
# Filter by classes of interest
|
||||
nude_detections = [ detection for detection in nude_detections if detection["class"] in self.nudity_classes_of_interest ]
|
||||
# Nude detections present?
|
||||
any_nude_detection = len(nude_detections) > 0
|
||||
###################################################################
|
||||
|
||||
###################################################################
|
||||
# Censor image
|
||||
censored_img_bgr = self._censor(image_bgr, nude_detections)
|
||||
# Plot age predictions on censored image
|
||||
output_image = detected_objects.plot(img=censored_img_bgr)
|
||||
###################################################################
|
||||
|
||||
results = {
|
||||
"any_minor_present": any_minor_present,
|
||||
"any_nude_detection": any_nude_detection,
|
||||
"nudity_detections": nude_detections,
|
||||
"age_predictions": age_predictions,
|
||||
"image": output_image,
|
||||
}
|
||||
return results
|
||||
|
||||
def process(image_bytes):
|
||||
try:
|
||||
logging.info("Processing image")
|
||||
# Convert bytes to NumPy array
|
||||
img_array = np.frombuffer(image_bytes.getvalue(), dtype=np.uint8)
|
||||
# Decode image using OpenCV
|
||||
img_bgr = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
|
||||
|
||||
if img_bgr is None:
|
||||
return {}
|
||||
# Process
|
||||
results = CV().process_image(img_bgr)
|
||||
logging.info("Returning results")
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
logging.warning("Error processing image: {}".format(str(e)))
|
||||
return {}
|
||||
BIN
app_cv/imgs/img_1p.jpg
Normal file
BIN
app_cv/imgs/img_1p.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
BIN
app_cv/imgs/img_nude.jpg
Normal file
BIN
app_cv/imgs/img_nude.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
Reference in New Issue
Block a user