drdudddd commited on
Commit
4e27788
·
verified ·
1 Parent(s): 39236b4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +62 -37
app.py CHANGED
@@ -3,15 +3,14 @@ import numpy as np
3
  from PIL import Image, ImageDraw, ImageFont
4
  import cv2
5
  import os
 
6
  from nudenet import NudeDetector
7
- import concurrent.futures
8
 
9
  # --- Konstanten ---
10
  DETECTION_MAX_DIM = 768
11
  PIXELS_PER_CM_ESTIMATE = 15
12
  MIN_CONFIDENCE = 0.45
13
 
14
- # Initialisiere den Detektor
15
  detector = NudeDetector(inference_resolution=640)
16
 
17
  def resize_for_detection(img_pil, max_dim):
@@ -25,26 +24,28 @@ def resize_for_detection(img_pil, max_dim):
25
 
26
  def describe_breast_precise(crop_pil):
27
  w, h = crop_pil.size
28
- if w * h == 0: return "Fehler: leeres Crop"
 
29
  gray = cv2.cvtColor(np.array(crop_pil), cv2.COLOR_RGB2GRAY)
30
  _, thresh = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
31
  contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
32
  nipple_detected = any(
33
- 40 < cv2.contourArea(c) < (w * h / 4) and
34
- (p := cv2.arcLength(c, True)) > 0 and
35
- (4 * np.pi * cv2.contourArea(c) / (p * p)) > 0.55
36
  for c in contours
37
  )
38
  ratio = w / h
39
  shape = "Breit" if ratio > 1.15 else "Hoch" if ratio < 0.85 else "Rund"
40
- size = "klein" if w*h < 28000 else "mittel" if w*h < 75000 else "groß" if w*h < 140000 else "sehr groß"
41
  w_cm = round(w / PIXELS_PER_CM_ESTIMATE, 1)
42
  h_cm = round(h / PIXELS_PER_CM_ESTIMATE, 1)
43
  return f"Brust: {shape}, {size}, Nippel: {'Ja' if nipple_detected else 'Nein'}, {w_cm}x{h_cm}cm"
44
 
45
  def describe_vagina_precise(crop_pil):
46
  w, h = crop_pil.size
47
- if w * h == 0: return "Fehler: leeres Crop"
 
48
  gray = cv2.cvtColor(np.array(crop_pil), cv2.COLOR_RGB2GRAY)
49
  hair_ratio = np.sum(cv2.inRange(gray, 35, 145) > 0) / (w * h)
50
  shaved = "rasiert" if hair_ratio < 0.04 else "minimal" if hair_ratio < 0.13 else "Brazilian" if hair_ratio < 0.36 else "behaart"
@@ -68,63 +69,71 @@ def process_image(image_path):
68
  original_pil = Image.open(image_path).convert("RGB")
69
  detection_pil, scale = resize_for_detection(original_pil, DETECTION_MAX_DIM)
70
  detections = detector.detect(np.array(detection_pil))
71
-
72
  draw = ImageDraw.Draw(original_pil)
73
- # Versuche eine Schriftart zu laden, sonst Standard
74
  try:
75
  font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20)
76
- except:
77
  font = ImageFont.load_default()
78
 
79
  results_text = []
80
-
81
  for det in detections:
82
  label = det["class"]
83
  score = det.get("score", 0)
84
  if score < MIN_CONFIDENCE:
85
  continue
86
-
87
  if label not in ["FEMALE_BREAST_EXPOSED", "FEMALE_GENITALIA_EXPOSED"]:
88
  continue
89
-
90
  x, y, w, h = [int(v * scale) for v in det["box"]]
91
  crop_pil = original_pil.crop((x, y, x + w, y + h))
92
-
93
  if label == "FEMALE_BREAST_EXPOSED":
94
  desc = describe_breast_precise(crop_pil)
95
- color = (255, 46, 130) # Pink
96
  else:
97
  desc = describe_vagina_precise(crop_pil)
98
- color = (138, 43, 226) # Purple
99
-
100
- # Zeichne Box
101
  draw.rectangle([x, y, x + w, y + h], outline=color, width=4)
102
- # Zeichne Text-Hintergrund
103
  text_pos = (x, y - 25 if y > 25 else y + h)
104
  draw.text(text_pos, desc, fill=color, font=font)
105
  results_text.append(desc)
106
 
107
  if not results_text:
108
- # Wenn nichts gefunden wurde, gib das Originalbild mit Hinweis zurück
109
  draw.text((10, 10), "Keine relevanten Bereiche erkannt.", fill=(255, 0, 0), font=font)
110
- return original_pil
111
 
112
  return original_pil
 
113
  except Exception as e:
114
  print(f"Fehler: {e}")
115
  return None
116
 
117
  def analyze_all(files):
118
  if not files:
119
- return None
120
-
121
  processed_images = []
 
 
 
 
 
122
  for f in files:
123
  res = process_image(f.name)
124
- if res:
125
- processed_images.append(res)
126
-
127
- return processed_images
 
 
 
 
 
 
 
128
 
129
  custom_css = """
130
  body { background: #0f0f1a; color: #e0e0ff; }
@@ -132,18 +141,34 @@ body { background: #0f0f1a; color: #e0e0ff; }
132
  h1 { color: #ff2e82; text-align: center; }
133
  """
134
 
135
- with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="pink")) as demo:
136
  gr.Markdown("# 👙 Automatischer Nackt-Analyzer")
137
- gr.Markdown("Lade Bilder hoch für eine automatische Analyse von Brüsten und Vagina. Die Ergebnisse werden direkt im Bild angezeigt.")
138
-
139
  with gr.Row():
140
  input_files = gr.File(file_count="multiple", label="Bilder hochladen")
141
-
 
 
 
 
 
 
 
142
  with gr.Row():
143
- output_gallery = gr.Gallery(label="Analyse-Ergebnisse", columns=2, height="auto", show_download_button=True)
144
-
145
- # Automatischer Trigger bei Upload
146
- input_files.change(fn=analyze_all, inputs=input_files, outputs=output_gallery)
 
 
 
 
 
 
147
 
148
  if __name__ == "__main__":
149
- demo.launch()
 
 
 
 
3
  from PIL import Image, ImageDraw, ImageFont
4
  import cv2
5
  import os
6
+ import tempfile
7
  from nudenet import NudeDetector
 
8
 
9
  # --- Konstanten ---
10
  DETECTION_MAX_DIM = 768
11
  PIXELS_PER_CM_ESTIMATE = 15
12
  MIN_CONFIDENCE = 0.45
13
 
 
14
  detector = NudeDetector(inference_resolution=640)
15
 
16
  def resize_for_detection(img_pil, max_dim):
 
24
 
25
  def describe_breast_precise(crop_pil):
26
  w, h = crop_pil.size
27
+ if w * h == 0:
28
+ return "Fehler: leeres Crop"
29
  gray = cv2.cvtColor(np.array(crop_pil), cv2.COLOR_RGB2GRAY)
30
  _, thresh = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
31
  contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
32
  nipple_detected = any(
33
+ 40 < cv2.contourArea(c) < (w * h / 4)
34
+ and (p := cv2.arcLength(c, True)) > 0
35
+ and (4 * np.pi * cv2.contourArea(c) / (p * p)) > 0.55
36
  for c in contours
37
  )
38
  ratio = w / h
39
  shape = "Breit" if ratio > 1.15 else "Hoch" if ratio < 0.85 else "Rund"
40
+ size = "klein" if w * h < 28000 else "mittel" if w * h < 75000 else "groß" if w * h < 140000 else "sehr groß"
41
  w_cm = round(w / PIXELS_PER_CM_ESTIMATE, 1)
42
  h_cm = round(h / PIXELS_PER_CM_ESTIMATE, 1)
43
  return f"Brust: {shape}, {size}, Nippel: {'Ja' if nipple_detected else 'Nein'}, {w_cm}x{h_cm}cm"
44
 
45
  def describe_vagina_precise(crop_pil):
46
  w, h = crop_pil.size
47
+ if w * h == 0:
48
+ return "Fehler: leeres Crop"
49
  gray = cv2.cvtColor(np.array(crop_pil), cv2.COLOR_RGB2GRAY)
50
  hair_ratio = np.sum(cv2.inRange(gray, 35, 145) > 0) / (w * h)
51
  shaved = "rasiert" if hair_ratio < 0.04 else "minimal" if hair_ratio < 0.13 else "Brazilian" if hair_ratio < 0.36 else "behaart"
 
69
  original_pil = Image.open(image_path).convert("RGB")
70
  detection_pil, scale = resize_for_detection(original_pil, DETECTION_MAX_DIM)
71
  detections = detector.detect(np.array(detection_pil))
72
+
73
  draw = ImageDraw.Draw(original_pil)
 
74
  try:
75
  font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20)
76
+ except Exception:
77
  font = ImageFont.load_default()
78
 
79
  results_text = []
80
+
81
  for det in detections:
82
  label = det["class"]
83
  score = det.get("score", 0)
84
  if score < MIN_CONFIDENCE:
85
  continue
86
+
87
  if label not in ["FEMALE_BREAST_EXPOSED", "FEMALE_GENITALIA_EXPOSED"]:
88
  continue
89
+
90
  x, y, w, h = [int(v * scale) for v in det["box"]]
91
  crop_pil = original_pil.crop((x, y, x + w, y + h))
92
+
93
  if label == "FEMALE_BREAST_EXPOSED":
94
  desc = describe_breast_precise(crop_pil)
95
+ color = (255, 46, 130)
96
  else:
97
  desc = describe_vagina_precise(crop_pil)
98
+ color = (138, 43, 226)
99
+
 
100
  draw.rectangle([x, y, x + w, y + h], outline=color, width=4)
 
101
  text_pos = (x, y - 25 if y > 25 else y + h)
102
  draw.text(text_pos, desc, fill=color, font=font)
103
  results_text.append(desc)
104
 
105
  if not results_text:
 
106
  draw.text((10, 10), "Keine relevanten Bereiche erkannt.", fill=(255, 0, 0), font=font)
 
107
 
108
  return original_pil
109
+
110
  except Exception as e:
111
  print(f"Fehler: {e}")
112
  return None
113
 
114
  def analyze_all(files):
115
  if not files:
116
+ return [], []
117
+
118
  processed_images = []
119
+ output_files = []
120
+
121
+ output_dir = os.path.join(tempfile.gettempdir(), "gradio_analyzer_outputs")
122
+ os.makedirs(output_dir, exist_ok=True)
123
+
124
  for f in files:
125
  res = process_image(f.name)
126
+ if res is None:
127
+ continue
128
+
129
+ processed_images.append(res)
130
+
131
+ base_name = os.path.splitext(os.path.basename(f.name))[0]
132
+ out_path = os.path.join(output_dir, f"{base_name}_analyzed.png")
133
+ res.save(out_path)
134
+ output_files.append(out_path)
135
+
136
+ return processed_images, output_files
137
 
138
  custom_css = """
139
  body { background: #0f0f1a; color: #e0e0ff; }
 
141
  h1 { color: #ff2e82; text-align: center; }
142
  """
143
 
144
+ with gr.Blocks() as demo:
145
  gr.Markdown("# 👙 Automatischer Nackt-Analyzer")
146
+ gr.Markdown("Lade Bilder hoch für eine automatische Analyse. Die Ergebnisse werden im Bild angezeigt und zusätzlich als Dateien bereitgestellt.")
147
+
148
  with gr.Row():
149
  input_files = gr.File(file_count="multiple", label="Bilder hochladen")
150
+
151
+ with gr.Row():
152
+ output_gallery = gr.Gallery(
153
+ label="Analyse-Ergebnisse",
154
+ columns=2,
155
+ height="auto"
156
+ )
157
+
158
  with gr.Row():
159
+ output_downloads = gr.File(
160
+ label="Analysierte Bilder herunterladen",
161
+ file_count="multiple"
162
+ )
163
+
164
+ input_files.change(
165
+ fn=analyze_all,
166
+ inputs=input_files,
167
+ outputs=[output_gallery, output_downloads]
168
+ )
169
 
170
  if __name__ == "__main__":
171
+ demo.launch(
172
+ css=custom_css,
173
+ theme=gr.themes.Soft(primary_hue="pink")
174
+ )