7 / app.py
drdudddd's picture
Update app.py
4e27788 verified
import gradio as gr
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import cv2
import os
import tempfile
from nudenet import NudeDetector
# --- Konstanten ---
DETECTION_MAX_DIM = 768
PIXELS_PER_CM_ESTIMATE = 15
MIN_CONFIDENCE = 0.45
detector = NudeDetector(inference_resolution=640)
def resize_for_detection(img_pil, max_dim):
if max(img_pil.width, img_pil.height) <= max_dim:
return img_pil, 1.0
ratio = max_dim / max(img_pil.width, img_pil.height)
new_size = (int(img_pil.width * ratio), int(img_pil.height * ratio))
resized = img_pil.resize(new_size, Image.Resampling.LANCZOS)
scale = 1 / ratio
return resized, scale
def describe_breast_precise(crop_pil):
w, h = crop_pil.size
if w * h == 0:
return "Fehler: leeres Crop"
gray = cv2.cvtColor(np.array(crop_pil), cv2.COLOR_RGB2GRAY)
_, thresh = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
nipple_detected = any(
40 < cv2.contourArea(c) < (w * h / 4)
and (p := cv2.arcLength(c, True)) > 0
and (4 * np.pi * cv2.contourArea(c) / (p * p)) > 0.55
for c in contours
)
ratio = w / h
shape = "Breit" if ratio > 1.15 else "Hoch" if ratio < 0.85 else "Rund"
size = "klein" if w * h < 28000 else "mittel" if w * h < 75000 else "groß" if w * h < 140000 else "sehr groß"
w_cm = round(w / PIXELS_PER_CM_ESTIMATE, 1)
h_cm = round(h / PIXELS_PER_CM_ESTIMATE, 1)
return f"Brust: {shape}, {size}, Nippel: {'Ja' if nipple_detected else 'Nein'}, {w_cm}x{h_cm}cm"
def describe_vagina_precise(crop_pil):
w, h = crop_pil.size
if w * h == 0:
return "Fehler: leeres Crop"
gray = cv2.cvtColor(np.array(crop_pil), cv2.COLOR_RGB2GRAY)
hair_ratio = np.sum(cv2.inRange(gray, 35, 145) > 0) / (w * h)
shaved = "rasiert" if hair_ratio < 0.04 else "minimal" if hair_ratio < 0.13 else "Brazilian" if hair_ratio < 0.36 else "behaart"
ratio = w / h
area = w * h
if area < 18000:
form_desc = "Innie"
elif area > 65000 and ratio > 1.45:
form_desc = "Outie (Puff)"
elif ratio > 1.45:
form_desc = "Outie"
else:
form_desc = "Innie/Outie"
size = "winzig" if area < 18000 else "klein" if area < 38000 else "mittel" if area < 65000 else "groß"
w_cm = round(w / PIXELS_PER_CM_ESTIMATE, 1)
h_cm = round(h / PIXELS_PER_CM_ESTIMATE, 1)
return f"Vagina: {form_desc}, {size}, {shaved}, {w_cm}x{h_cm}cm"
def process_image(image_path):
try:
original_pil = Image.open(image_path).convert("RGB")
detection_pil, scale = resize_for_detection(original_pil, DETECTION_MAX_DIM)
detections = detector.detect(np.array(detection_pil))
draw = ImageDraw.Draw(original_pil)
try:
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20)
except Exception:
font = ImageFont.load_default()
results_text = []
for det in detections:
label = det["class"]
score = det.get("score", 0)
if score < MIN_CONFIDENCE:
continue
if label not in ["FEMALE_BREAST_EXPOSED", "FEMALE_GENITALIA_EXPOSED"]:
continue
x, y, w, h = [int(v * scale) for v in det["box"]]
crop_pil = original_pil.crop((x, y, x + w, y + h))
if label == "FEMALE_BREAST_EXPOSED":
desc = describe_breast_precise(crop_pil)
color = (255, 46, 130)
else:
desc = describe_vagina_precise(crop_pil)
color = (138, 43, 226)
draw.rectangle([x, y, x + w, y + h], outline=color, width=4)
text_pos = (x, y - 25 if y > 25 else y + h)
draw.text(text_pos, desc, fill=color, font=font)
results_text.append(desc)
if not results_text:
draw.text((10, 10), "Keine relevanten Bereiche erkannt.", fill=(255, 0, 0), font=font)
return original_pil
except Exception as e:
print(f"Fehler: {e}")
return None
def analyze_all(files):
if not files:
return [], []
processed_images = []
output_files = []
output_dir = os.path.join(tempfile.gettempdir(), "gradio_analyzer_outputs")
os.makedirs(output_dir, exist_ok=True)
for f in files:
res = process_image(f.name)
if res is None:
continue
processed_images.append(res)
base_name = os.path.splitext(os.path.basename(f.name))[0]
out_path = os.path.join(output_dir, f"{base_name}_analyzed.png")
res.save(out_path)
output_files.append(out_path)
return processed_images, output_files
custom_css = """
body { background: #0f0f1a; color: #e0e0ff; }
.gradio-container { max-width: 1000px !important; margin: auto; }
h1 { color: #ff2e82; text-align: center; }
"""
with gr.Blocks() as demo:
gr.Markdown("# 👙 Automatischer Nackt-Analyzer")
gr.Markdown("Lade Bilder hoch für eine automatische Analyse. Die Ergebnisse werden im Bild angezeigt und zusätzlich als Dateien bereitgestellt.")
with gr.Row():
input_files = gr.File(file_count="multiple", label="Bilder hochladen")
with gr.Row():
output_gallery = gr.Gallery(
label="Analyse-Ergebnisse",
columns=2,
height="auto"
)
with gr.Row():
output_downloads = gr.File(
label="Analysierte Bilder herunterladen",
file_count="multiple"
)
input_files.change(
fn=analyze_all,
inputs=input_files,
outputs=[output_gallery, output_downloads]
)
if __name__ == "__main__":
demo.launch(
css=custom_css,
theme=gr.themes.Soft(primary_hue="pink")
)