oioio / app.py
Galaxydude2's picture
Update app.py
49b3295 verified
from PIL import Image
import numpy as np
import gradio as gr
import os
import cv2
from nudenet import NudeDetector
import concurrent.futures
# --- Konstanten ---
DETECTION_MAX_DIM = 768
PIXELS_PER_CM_ESTIMATE = 15
MIN_CONFIDENCE = 0.45
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/Horizontal" if ratio > 1.15 else "Hoch/Vertikal" if ratio < 0.85 else "Rund/Ausgewogen"
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"Form: {shape} · Größe: {size} · Nippel: {'Sichtbar' if nipple_detected else 'Flach'} · {w_cm}×{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 = "komplett rasiert" if hair_ratio < 0.04 else "minimal/Strip" if hair_ratio < 0.13 else "Brazilian" if hair_ratio < 0.36 else "voll behaart"
ratio = w / h
area = w * h
if area < 18000:
form_desc = "Winzige Innie" if 0.75 <= ratio <= 1.25 else "Vertikal" if ratio < 0.75 else "Horizontal"
prom = "sehr unauffällig"
elif area > 65000 and ratio > 1.45:
form_desc = "Extrem Outie (Puff)"
prom = "sehr stark"
elif ratio > 1.45:
form_desc = "Breites Outie"
prom = "stark"
elif ratio < 0.75:
form_desc = "Lange Innie"
prom = "mittel"
elif ratio > 1.1:
form_desc = "Klassisches Outie"
prom = "sichtbar"
else:
form_desc = "Ausgewogene Innie/Outie"
prom = "normal"
visibility = "Innere Labien sichtbar" if "Outie" in form_desc or prom in ["stark", "sehr stark", "sichtbar"] else "Innere Labien verdeckt"
size = "winzig" if area < 18000 else "klein" if area < 38000 else "mittel" if area < 65000 else "groß & voll"
w_cm = round(w / PIXELS_PER_CM_ESTIMATE, 1)
h_cm = round(h / PIXELS_PER_CM_ESTIMATE, 1)
return f"Form: {form_desc} · Größe: {size} · Prominenz: {prom} · Sichtbarkeit: {visibility} · Behaart: {shaved} · {w_cm}×{h_cm} cm"
detector = NudeDetector(inference_resolution=640)
def process_single_image(mode: str, path: str):
try:
original_pil = Image.open(path).convert("RGB")
detection_pil, scale = resize_for_detection(original_pil, DETECTION_MAX_DIM)
detections = detector.detect(np.array(detection_pil))
target_class = "FEMALE_BREAST_EXPOSED" if mode == "Brüste" else "FEMALE_GENITALIA_EXPOSED"
relevant = [d for d in detections if d["class"] == target_class and d.get("score", 0) >= MIN_CONFIDENCE]
filename = os.path.basename(path)
result = f"**{filename}** — {mode}\n\n"
if not relevant:
result += "❌ Keine relevanten Bereiche erkannt.\n\n"
return result
result += f"✅ **{len(relevant)}** {mode.lower()} gefunden\n\n"
for i, det in enumerate(relevant, 1):
x, y, w, h = [int(v * scale) for v in det["box"]]
crop_pil = original_pil.crop((x, y, x + w, y + h))
desc = describe_breast_precise(crop_pil) if mode == "Brüste" else describe_vagina_precise(crop_pil)
result += f"**{mode} {i}**\n{desc}\n\n"
return result
except Exception as e:
return f"**{os.path.basename(path)}** — Fehler: {str(e)}\n\n"
def analyze_text(mode: str, image_paths):
if not image_paths:
return "**Keine Bilder hochgeladen.**"
max_workers = min(6, len(image_paths), os.cpu_count() or 4)
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = [executor.submit(process_single_image, mode, p) for p in image_paths]
results = [f.result() for f in concurrent.futures.as_completed(futures)]
return "\n---\n\n".join(results)
custom_css = """
body { background: #0f0f1a; color: #e0e0ff; font-family: system-ui, sans-serif; }
.gradio-container { max-width: 1100px !important; margin: auto; }
h1 { color: #ff2e82; text-shadow: 0 0 25px #ff2e8260; }
button { background: linear-gradient(45deg, #ff2e82, #c71585) !important; border: none !important; font-weight: 600; }
.markdown { font-size: 15.5px; line-height: 1.65; }
"""
interface = gr.Interface(
fn=analyze_text,
inputs=[
gr.Radio(choices=["Brüste", "Vagina"], value="Brüste", label="Suchmodus"),
gr.File(file_count="multiple", type="filepath", label="Bilder hochladen")
],
outputs=gr.Markdown(label="Text-Ergebnisse"),
title="Nackt-Analyzer – Brüste / Vagina",
description="Schnelle textbasierte Analyse (optimiert für Gradio 6+)"
)
if __name__ == "__main__":
interface.launch(
share=True,
theme=gr.themes.Soft(primary_hue="pink", secondary_hue="purple"),
css=custom_css
)