7
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")
    )