File size: 5,498 Bytes
27ab04b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | 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) |