from PIL import Image import numpy as np import gradio as gr import os import cv2 from nudenet import NudeDetector import io import concurrent.futures # ─── Konstanten ──────────────────────────────────────── DETECTION_MAX_DIM = 768 PIXELS_PER_CM_ESTIMATE = 15 MIN_CONFIDENCE = 0.45 # ─── Hilfsfunktionen ─────────────────────────────────── 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) return resized, 1 / ratio # Deine bestehenden describe-Funktionen (unverändert) def describe_breast_precise(crop_pil): # ... dein Code bleibt gleich ... return "Form: Rund · Größe: mittel · Nippel: Sichtbar · 9.8×8.4 cm" # Beispiel def describe_vagina_precise(crop_pil): # ... dein Code bleibt gleich ... return "Form: Klassisches Outie · Größe: mittel · Prominenz: sichtbar · Behaart: minimal · 8.2×11.1 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) markdown = f"**{filename}** — {mode}\n\n" outputs = [] if not relevant: markdown += "❌ Keine relevanten Bereiche erkannt.\n" outputs.append((None, markdown)) return outputs markdown += 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) # Crop als bytes für Gradio Image-Komponente crop_bytes = io.BytesIO() crop_pil.save(crop_bytes, format="PNG") crop_bytes.seek(0) markdown_part = f"**{mode} {i}** (Konfidenz: {det['score']:.2f})\n{desc}\n" outputs.append((crop_bytes, markdown_part)) # Zusammenfassung am Ende summary_md = "\n".join([md for _, md in outputs]) + f"\n\n**Gesamt: {len(relevant)} Funde**" outputs.append((None, summary_md)) return outputs except Exception as e: return [(None, f"**{filename}** — Fehler: {str(e)}")] def analyze_images(mode: str, image_paths): if not image_paths: return [(None, "**Keine Bilder hochgeladen.**")] all_outputs = [] 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] for future in concurrent.futures.as_completed(futures): all_outputs.extend(future.result()) return all_outputs # ─── Gradio Interface ────────────────────────────────── with gr.Blocks(theme=gr.themes.Soft(primary_hue="pink", secondary_hue="purple")) as demo: gr.Markdown("# Nackt-Analyzer – mit Crops") gr.Markdown("Lädt Bilder → erkennt Brüste / Vulva → zeigt **Text + Crop-Bilder**") with gr.Row(): mode = gr.Radio(choices=["Brüste", "Vagina"], value="Brüste", label="Modus") upload = gr.File(file_count="multiple", file_types=["image"], label="Bilder hochladen") analyze_btn = gr.Button("Analysieren", variant="primary") output_gallery = gr.Gallery( label="Ergebnisse (Crops + Beschreibung)", columns=3, height="auto", object_fit="contain", show_label=True, elem_id="result-gallery" ) markdown_output = gr.Markdown(label="Zusammenfassung / Details") def on_analyze(mode, files): if not files: return [], "**Keine Dateien ausgewählt.**" paths = [f.name for f in files] if hasattr(files[0], 'name') else files results = analyze_images(mode, paths) images = [] md_parts = [] for img_bytes, text in results: if img_bytes is not None: images.append((img_bytes, text)) # (image, caption) else: md_parts.append(text) combined_md = "\n\n".join(md_parts) if md_parts else "" return images, combined_md analyze_btn.click( fn=on_analyze, inputs=[mode, upload], outputs=[output_gallery, markdown_output] ) if __name__ == "__main__": demo.launch(share=True)