| import gradio as gr |
| import numpy as np |
| from PIL import Image, ImageDraw, ImageFont |
| import cv2 |
| from nudenet import NudeDetector |
|
|
| |
| 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): |
| try: |
| original_pil = Image.fromarray(image).convert("RGB") if isinstance(image,np.ndarray) else image.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) |
| 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-15 if y>15 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 np.array(original_pil) |
|
|
| except Exception as e: |
| print(f"Fehler: {e}") |
| return None |
|
|
| |
| css = """ |
| body { background: #0f0f1a; color: #e0e0ff; } |
| .gradio-container { max-width: 900px !important; margin: auto; } |
| h1 { color: #ff2e82; text-align: center; } |
| """ |
|
|
| with gr.Blocks(css=css) as demo: |
| gr.Markdown("# 👙 Automatischer Nackt-Analyzer") |
| gr.Markdown("Lade ein Bild hoch und erhalte direkt das analysierte Bild mit Annotationen.") |
| input_image = gr.Image(type="numpy", label="Bild hochladen") |
| output_image = gr.Image(label="Analyse-Ergebnis") |
| input_image.change(fn=process_image, inputs=input_image, outputs=output_image) |
|
|
| demo.launch() |