File size: 5,890 Bytes
d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 d477d33 4e27788 | 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 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | 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")
) |