EnginDev commited on
Commit
91f7657
·
verified ·
1 Parent(s): 98f1428

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +185 -150
app.py CHANGED
@@ -4,7 +4,7 @@ import numpy as np
4
  from PIL import Image
5
  import cv2
6
 
7
- print("🚀 Starting SAM2 App...")
8
 
9
  device = "cuda" if torch.cuda.is_available() else "cpu"
10
  print(f"📱 Using device: {device}")
@@ -19,31 +19,36 @@ def load_model():
19
  try:
20
  from transformers import SamModel, SamProcessor
21
 
22
- model_name = "facebook/sam-vit-base"
 
 
 
23
  processor = SamProcessor.from_pretrained(model_name)
24
  model = SamModel.from_pretrained(model_name)
25
  model.to(device)
26
- print("✅ Model loaded!")
27
  except Exception as e:
28
  print(f"❌ Model load error: {e}")
29
- raise e
 
 
 
 
 
30
  return model, processor
31
 
32
  def prepare_image(image, max_size=1024):
33
- """Bild vorbereiten und normalisieren"""
34
  if isinstance(image, np.ndarray):
35
  image_pil = Image.fromarray(image)
36
  else:
37
  image_pil = image
38
 
39
- # RGB sicherstellen
40
  if image_pil.mode != 'RGB':
41
  image_pil = image_pil.convert('RGB')
42
 
43
  image_np = np.array(image_pil)
44
  h, w = image_np.shape[:2]
45
 
46
- # Resize
47
  if max(h, w) > max_size:
48
  scale = max_size / max(h, w)
49
  new_h, new_w = int(h * scale), int(w * scale)
@@ -52,133 +57,129 @@ def prepare_image(image, max_size=1024):
52
 
53
  return image_pil, image_np
54
 
55
- def segment_automatic(image):
56
- """Automatische Segmentierung mit robustem Error Handling"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
  if image is None:
59
  return None, {"error": "Kein Bild hochgeladen"}
60
 
61
  try:
62
- print("🔄 Starting segmentation...")
63
  model, processor = load_model()
64
 
65
- # Bild vorbereiten
66
  image_pil, image_np = prepare_image(image)
67
  h, w = image_np.shape[:2]
68
- print(f"📐 Image size: {w}x{h}")
69
 
70
- # Mittelpunkt
71
  center_x, center_y = w // 2, h // 2
72
- input_points = [[[center_x, center_y]]]
73
- input_labels = [[1]] # 1 = Foreground
74
-
75
- print(f"🎯 Using point: ({center_x}, {center_y})")
76
 
77
- # Process
78
  inputs = processor(
79
  image_pil,
80
- input_points=input_points,
81
- input_labels=input_labels,
82
  return_tensors="pt"
83
  ).to(device)
84
 
85
  print("🧠 Running inference...")
86
  with torch.no_grad():
87
- outputs = model(**inputs)
88
-
89
- print(f"📊 Output shapes:")
90
- print(f" Masks: {outputs.pred_masks.shape}")
91
- print(f" Scores: {outputs.iou_scores.shape}")
92
 
93
- # Post-process masks
94
  masks = processor.image_processor.post_process_masks(
95
  outputs.pred_masks.cpu(),
96
  inputs["original_sizes"].cpu(),
97
  inputs["reshaped_input_sizes"].cpu()
98
- )
99
-
100
- print(f"✅ Got {len(masks)} mask batches")
101
-
102
- # Erste Maske nehmen
103
- mask_batch = masks[0]
104
- print(f"📦 Mask batch shape: {mask_batch.shape}")
105
 
106
- # Scores extrahieren
107
  scores = outputs.iou_scores.cpu().numpy()
108
- print(f"📊 Scores shape: {scores.shape}")
109
-
110
- # Beste Maske finden
111
- if scores.ndim == 3:
112
- # Shape: [batch, num_masks, 1]
113
- scores = scores[0, :, 0]
114
- elif scores.ndim == 2:
115
- # Shape: [batch, num_masks]
116
- scores = scores[0]
117
- else:
118
- # Fallback
119
  scores = scores.flatten()
120
 
121
- print(f"📈 Scores: {scores}")
122
 
 
123
  best_idx = np.argmax(scores)
124
  best_score = scores[best_idx]
125
 
126
- # Maske extrahieren
127
- if mask_batch.ndim == 4:
128
- # Shape: [1, num_masks, H, W]
129
- best_mask = mask_batch[0, best_idx].numpy()
130
- elif mask_batch.ndim == 3:
131
- # Shape: [num_masks, H, W]
132
- best_mask = mask_batch[best_idx].numpy()
133
  else:
134
- # Fallback - erste Maske
135
- best_mask = mask_batch.numpy().squeeze()
136
 
137
- print(f"✅ Best mask shape: {best_mask.shape}, Score: {best_score}")
 
 
 
 
138
 
139
- # Overlay erstellen
140
  overlay = image_np.copy()
141
- color = np.array([255, 50, 150]) # Pink
 
 
 
 
 
142
 
143
- # Maske anwenden
144
- mask_bool = best_mask > 0
145
- overlay[mask_bool] = (overlay[mask_bool] * 0.4 + color * 0.6).astype(np.uint8)
 
 
 
146
 
147
- # Umriss zeichnen
148
  contours, _ = cv2.findContours(
149
- (best_mask > 0).astype(np.uint8),
150
  cv2.RETR_EXTERNAL,
151
  cv2.CHAIN_APPROX_SIMPLE
152
  )
153
- cv2.drawContours(overlay, contours, -1, (255, 255, 0), 2)
154
 
155
  metadata = {
156
  "success": True,
157
  "mode": "automatic",
 
158
  "score": float(best_score),
 
159
  "image_size": [w, h],
160
- "mask_area": int(np.sum(mask_bool)),
161
- "mask_percentage": float(np.sum(mask_bool) / (h * w) * 100),
162
- "device": device,
163
- "point_used": [center_x, center_y]
164
  }
165
 
166
  print("✅ Segmentation complete!")
167
- return Image.fromarray(overlay), metadata
168
 
169
  except Exception as e:
170
  import traceback
171
- error_details = traceback.format_exc()
172
- print(f"❌ ERROR:\n{error_details}")
173
-
174
- return image, {
175
- "error": str(e),
176
- "details": error_details,
177
- "suggestion": "Versuche ein anderes Bild oder warte kurz"
178
- }
179
 
180
- def segment_multi(image):
181
- """Multi-Object Segmentierung"""
 
 
 
182
 
183
  if image is None:
184
  return None, {"error": "Kein Bild"}
@@ -188,25 +189,25 @@ def segment_multi(image):
188
  image_pil, image_np = prepare_image(image)
189
  h, w = image_np.shape[:2]
190
 
191
- # Grid von Punkten
192
- points = [
193
- [w//3, h//3],
194
- [2*w//3, h//3],
195
- [w//3, 2*h//3],
196
- [2*w//3, 2*h//3],
197
- [w//2, h//2]
198
- ]
199
 
200
- overlay = image_np.copy()
201
- colors = [
202
- [255, 0, 0], # Rot
203
- [0, 255, 0], # Grün
204
- [0, 0, 255], # Blau
205
- [255, 255, 0], # Gelb
206
- [255, 0, 255] # Magenta
207
- ]
208
 
209
- masks_info = []
 
 
 
210
 
211
  for idx, point in enumerate(points):
212
  inputs = processor(
@@ -217,7 +218,7 @@ def segment_multi(image):
217
  ).to(device)
218
 
219
  with torch.no_grad():
220
- outputs = model(**inputs)
221
 
222
  masks = processor.image_processor.post_process_masks(
223
  outputs.pred_masks.cpu(),
@@ -225,11 +226,7 @@ def segment_multi(image):
225
  inputs["reshaped_input_sizes"].cpu()
226
  )[0]
227
 
228
- scores = outputs.iou_scores.cpu().numpy()
229
-
230
- # Beste Maske
231
- if scores.ndim > 1:
232
- scores = scores.flatten()
233
  best_idx = np.argmax(scores)
234
 
235
  if masks.ndim == 4:
@@ -237,97 +234,135 @@ def segment_multi(image):
237
  else:
238
  mask = masks[best_idx].numpy()
239
 
240
- mask_bool = mask > 0
241
- color = colors[idx % len(colors)]
242
-
243
- overlay[mask_bool] = (overlay[mask_bool] * 0.5 + np.array(color) * 0.5).astype(np.uint8)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
 
245
- masks_info.append({
246
- "id": idx,
247
- "point": point,
248
- "score": float(scores[best_idx]),
249
- "area": int(np.sum(mask_bool))
250
- })
 
251
 
252
  metadata = {
253
  "success": True,
254
- "mode": "multi_object",
255
- "num_regions": len(masks_info),
256
- "masks": masks_info
 
 
 
 
257
  }
258
 
259
  return Image.fromarray(overlay), metadata
260
 
261
  except Exception as e:
262
  import traceback
263
- print(f"❌ Multi Error:\n{traceback.format_exc()}")
264
  return image, {"error": str(e)}
265
 
266
  # Gradio Interface
267
- with gr.Blocks(title="SAM2 Segmentation", theme=gr.themes.Soft()) as demo:
268
 
269
- gr.Markdown("# 🎨 SAM2 Bild Segmentierung")
 
 
 
270
 
271
- with gr.Tab("🤖 Automatisch"):
272
  with gr.Row():
273
  with gr.Column():
274
  input_auto = gr.Image(type="pil", label="📸 Bild hochladen")
 
 
 
 
 
 
275
  btn_auto = gr.Button("🚀 Segmentieren", variant="primary", size="lg")
 
276
  gr.Markdown("""
277
- **Tipp:** Objekt sollte zentral im Bild sein.
278
- Beste Ergebnisse mit klarem Vordergrund/Hintergrund.
 
 
 
279
  """)
280
 
281
  with gr.Column():
282
  output_auto = gr.Image(label="✨ Ergebnis")
283
- json_auto = gr.JSON(label="📊 Info")
284
 
285
  btn_auto.click(
286
  fn=segment_automatic,
287
- inputs=input_auto,
288
  outputs=[output_auto, json_auto]
289
  )
290
-
291
- gr.Examples(
292
- examples=[],
293
- inputs=input_auto,
294
- label="Beispielbilder (Upload deine eigenen!)"
295
- )
296
 
297
- with gr.Tab("🎯 Multi-Region"):
298
  with gr.Row():
299
  with gr.Column():
300
  input_multi = gr.Image(type="pil", label="📸 Bild hochladen")
 
 
 
 
 
 
301
  btn_multi = gr.Button("🚀 Alle Bereiche", variant="primary", size="lg")
 
 
 
 
 
 
 
302
 
303
  with gr.Column():
304
  output_multi = gr.Image(label="✨ Ergebnis")
305
- json_multi = gr.JSON(label="📊 Info")
306
 
307
  btn_multi.click(
308
- fn=segment_multi,
309
- inputs=input_multi,
310
  outputs=[output_multi, json_multi]
311
  )
312
 
313
  gr.Markdown("""
314
  ---
315
- ### 📡 API Nutzung
316
 
317
- **Endpoint:** `https://dein-space.hf.space/api/predict`
 
 
 
 
318
 
319
- ```javascript
320
- const response = await fetch('https://dein-space.hf.space/api/predict', {
321
- method: 'POST',
322
- headers: {'Content-Type': 'application/json'},
323
- body: JSON.stringify({
324
- data: [imageBase64],
325
- fn_index: 0 // 0=Auto, 1=Multi
326
- })
327
- });
328
- ```
329
- """)
330
-
331
- if __name__ == "__main__":
332
- print("🌐 Launching...")
333
- demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)
 
4
  from PIL import Image
5
  import cv2
6
 
7
+ print("🚀 Starting SAM2 App v2.0...")
8
 
9
  device = "cuda" if torch.cuda.is_available() else "cpu"
10
  print(f"📱 Using device: {device}")
 
19
  try:
20
  from transformers import SamModel, SamProcessor
21
 
22
+ # UPGRADE: Large Model für bessere Qualität!
23
+ # Wenn RAM-Error: zurück zu "facebook/sam-vit-base"
24
+ model_name = "facebook/sam-vit-large" # ← BESSER!
25
+
26
  processor = SamProcessor.from_pretrained(model_name)
27
  model = SamModel.from_pretrained(model_name)
28
  model.to(device)
29
+ print(f"✅ Model loaded: {model_name}")
30
  except Exception as e:
31
  print(f"❌ Model load error: {e}")
32
+ print("💡 Falling back to base model...")
33
+ # Fallback
34
+ model_name = "facebook/sam-vit-base"
35
+ processor = SamProcessor.from_pretrained(model_name)
36
+ model = SamModel.from_pretrained(model_name)
37
+ model.to(device)
38
  return model, processor
39
 
40
  def prepare_image(image, max_size=1024):
 
41
  if isinstance(image, np.ndarray):
42
  image_pil = Image.fromarray(image)
43
  else:
44
  image_pil = image
45
 
 
46
  if image_pil.mode != 'RGB':
47
  image_pil = image_pil.convert('RGB')
48
 
49
  image_np = np.array(image_pil)
50
  h, w = image_np.shape[:2]
51
 
 
52
  if max(h, w) > max_size:
53
  scale = max_size / max(h, w)
54
  new_h, new_w = int(h * scale), int(w * scale)
 
57
 
58
  return image_pil, image_np
59
 
60
+ def refine_mask(mask, kernel_size=5):
61
+ """Maske glätten und Löcher füllen"""
62
+ mask_uint8 = (mask > 0).astype(np.uint8) * 255
63
+
64
+ # Morphological Operations für glattere Kanten
65
+ kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (kernel_size, kernel_size))
66
+
67
+ # Closing: Löcher füllen
68
+ mask_closed = cv2.morphologyEx(mask_uint8, cv2.MORPH_CLOSE, kernel)
69
+
70
+ # Opening: Rauschen entfernen
71
+ mask_refined = cv2.morphologyEx(mask_closed, cv2.MORPH_OPEN, kernel)
72
+
73
+ return mask_refined > 0
74
+
75
+ def segment_automatic(image, quality="high"):
76
+ """
77
+ Automatische Segmentierung mit Multi-Mask Output
78
+ quality: "high" (langsamer, präziser) oder "fast" (schneller, gröber)
79
+ """
80
 
81
  if image is None:
82
  return None, {"error": "Kein Bild hochgeladen"}
83
 
84
  try:
85
+ print(f"🔄 Starting segmentation (quality: {quality})...")
86
  model, processor = load_model()
87
 
 
88
  image_pil, image_np = prepare_image(image)
89
  h, w = image_np.shape[:2]
 
90
 
 
91
  center_x, center_y = w // 2, h // 2
 
 
 
 
92
 
93
+ # WICHTIG: multimask_output=True für bessere Qualität!
94
  inputs = processor(
95
  image_pil,
96
+ input_points=[[[center_x, center_y]]],
97
+ input_labels=[[1]],
98
  return_tensors="pt"
99
  ).to(device)
100
 
101
  print("🧠 Running inference...")
102
  with torch.no_grad():
103
+ outputs = model(**inputs, multimask_output=True) # ← Multi-Mask!
 
 
 
 
104
 
105
+ # Post-process
106
  masks = processor.image_processor.post_process_masks(
107
  outputs.pred_masks.cpu(),
108
  inputs["original_sizes"].cpu(),
109
  inputs["reshaped_input_sizes"].cpu()
110
+ )[0]
 
 
 
 
 
 
111
 
 
112
  scores = outputs.iou_scores.cpu().numpy()
113
+ if scores.ndim > 1:
 
 
 
 
 
 
 
 
 
 
114
  scores = scores.flatten()
115
 
116
+ print(f"✅ Got {len(scores)} masks with scores: {scores}")
117
 
118
+ # Beste Maske
119
  best_idx = np.argmax(scores)
120
  best_score = scores[best_idx]
121
 
122
+ if masks.ndim == 4:
123
+ best_mask = masks[0, best_idx].numpy()
 
 
 
 
 
124
  else:
125
+ best_mask = masks[best_idx].numpy()
 
126
 
127
+ # REFINEMENT: Maske glätten
128
+ if quality == "high":
129
+ best_mask_refined = refine_mask(best_mask, kernel_size=7)
130
+ else:
131
+ best_mask_refined = best_mask > 0
132
 
133
+ # Overlay mit Anti-Aliasing
134
  overlay = image_np.copy()
135
+ color = np.array([255, 50, 150])
136
+
137
+ # Weiche Kanten mit Gaussian Blur
138
+ mask_float = best_mask_refined.astype(float)
139
+ if quality == "high":
140
+ mask_float = cv2.GaussianBlur(mask_float, (5, 5), 0)
141
 
142
+ # Alpha Blending
143
+ for c in range(3):
144
+ overlay[:, :, c] = (
145
+ overlay[:, :, c] * (1 - mask_float * 0.6) +
146
+ color[c] * mask_float * 0.6
147
+ )
148
 
149
+ # Kontur zeichnen
150
  contours, _ = cv2.findContours(
151
+ best_mask_refined.astype(np.uint8),
152
  cv2.RETR_EXTERNAL,
153
  cv2.CHAIN_APPROX_SIMPLE
154
  )
155
+ cv2.drawContours(overlay, contours, -1, (255, 255, 0), 3)
156
 
157
  metadata = {
158
  "success": True,
159
  "mode": "automatic",
160
+ "quality": quality,
161
  "score": float(best_score),
162
+ "all_scores": scores.tolist(),
163
  "image_size": [w, h],
164
+ "mask_area": int(np.sum(best_mask_refined)),
165
+ "mask_percentage": float(np.sum(best_mask_refined) / (h * w) * 100),
166
+ "num_contours": len(contours),
167
+ "device": device
168
  }
169
 
170
  print("✅ Segmentation complete!")
171
+ return Image.fromarray(overlay.astype(np.uint8)), metadata
172
 
173
  except Exception as e:
174
  import traceback
175
+ print(f"❌ ERROR:\n{traceback.format_exc()}")
176
+ return image, {"error": str(e)}
 
 
 
 
 
 
177
 
178
+ def segment_multi_dense(image, density="high"):
179
+ """
180
+ Multi-Object mit dichterem Grid
181
+ density: "high" (15x15), "medium" (9x9), "low" (5x5)
182
+ """
183
 
184
  if image is None:
185
  return None, {"error": "Kein Bild"}
 
189
  image_pil, image_np = prepare_image(image)
190
  h, w = image_np.shape[:2]
191
 
192
+ # Dichteres Grid basierend auf Density
193
+ if density == "high":
194
+ grid_size = 5 # 5x5 = 25 Punkte
195
+ elif density == "medium":
196
+ grid_size = 4 # 4x4 = 16 Punkte
197
+ else:
198
+ grid_size = 3 # 3x3 = 9 Punkte
 
199
 
200
+ points = []
201
+ for i in range(1, grid_size + 1):
202
+ for j in range(1, grid_size + 1):
203
+ x = int(w * i / (grid_size + 1))
204
+ y = int(h * j / (grid_size + 1))
205
+ points.append([x, y])
 
 
206
 
207
+ print(f"📍 Using {len(points)} points for segmentation")
208
+
209
+ all_masks = []
210
+ all_scores = []
211
 
212
  for idx, point in enumerate(points):
213
  inputs = processor(
 
218
  ).to(device)
219
 
220
  with torch.no_grad():
221
+ outputs = model(**inputs, multimask_output=True)
222
 
223
  masks = processor.image_processor.post_process_masks(
224
  outputs.pred_masks.cpu(),
 
226
  inputs["reshaped_input_sizes"].cpu()
227
  )[0]
228
 
229
+ scores = outputs.iou_scores.cpu().numpy().flatten()
 
 
 
 
230
  best_idx = np.argmax(scores)
231
 
232
  if masks.ndim == 4:
 
234
  else:
235
  mask = masks[best_idx].numpy()
236
 
237
+ # Nur Masken mit gutem Score behalten
238
+ if scores[best_idx] > 0.7:
239
+ all_masks.append(refine_mask(mask))
240
+ all_scores.append(scores[best_idx])
241
+
242
+ print(f"✅ Got {len(all_masks)} quality masks")
243
+
244
+ # Masken kombinieren mit verschiedenen Farben
245
+ overlay = image_np.copy()
246
+
247
+ # HSV-basierte Farbgenerierung
248
+ colors = []
249
+ for i in range(len(all_masks)):
250
+ hue = int(180 * i / max(len(all_masks), 1))
251
+ color_hsv = np.uint8([[[hue, 255, 200]]])
252
+ color_rgb = cv2.cvtColor(color_hsv, cv2.COLOR_HSV2RGB)[0][0]
253
+ colors.append(color_rgb)
254
+
255
+ # Masken anwenden
256
+ for mask, color, score in zip(all_masks, colors, all_scores):
257
+ alpha = 0.4 + (score - 0.7) * 0.2 # Höherer Score = stärkere Farbe
258
+ overlay[mask] = (
259
+ overlay[mask] * (1 - alpha) +
260
+ np.array(color) * alpha
261
+ ).astype(np.uint8)
262
 
263
+ # Kontur
264
+ contours, _ = cv2.findContours(
265
+ mask.astype(np.uint8),
266
+ cv2.RETR_EXTERNAL,
267
+ cv2.CHAIN_APPROX_SIMPLE
268
+ )
269
+ cv2.drawContours(overlay, contours, -1, color.tolist(), 2)
270
 
271
  metadata = {
272
  "success": True,
273
+ "mode": "multi_object_dense",
274
+ "density": density,
275
+ "grid_size": f"{grid_size}x{grid_size}",
276
+ "total_points": len(points),
277
+ "quality_masks": len(all_masks),
278
+ "avg_score": float(np.mean(all_scores)) if all_scores else 0,
279
+ "scores": [float(s) for s in all_scores]
280
  }
281
 
282
  return Image.fromarray(overlay), metadata
283
 
284
  except Exception as e:
285
  import traceback
286
+ print(f"❌ ERROR:\n{traceback.format_exc()}")
287
  return image, {"error": str(e)}
288
 
289
  # Gradio Interface
290
+ with gr.Blocks(title="SAM2 v2.0", theme=gr.themes.Soft()) as demo:
291
 
292
+ gr.Markdown("""
293
+ # 🎨 SAM2 Segmentierung v2.0
294
+ ### Verbesserte Qualität mit Large Model & Multi-Mask Output
295
+ """)
296
 
297
+ with gr.Tab("🤖 Automatisch (Verbessert)"):
298
  with gr.Row():
299
  with gr.Column():
300
  input_auto = gr.Image(type="pil", label="📸 Bild hochladen")
301
+ quality_radio = gr.Radio(
302
+ choices=["high", "fast"],
303
+ value="high",
304
+ label="Qualität",
305
+ info="High = präziser aber langsamer"
306
+ )
307
  btn_auto = gr.Button("🚀 Segmentieren", variant="primary", size="lg")
308
+
309
  gr.Markdown("""
310
+ **Verbesserungen:**
311
+ - ✨ Large Model (bessere Details)
312
+ - 🎯 Multi-Mask Output
313
+ - 🔄 Kanten-Glättung
314
+ - 🌊 Weiche Übergänge
315
  """)
316
 
317
  with gr.Column():
318
  output_auto = gr.Image(label="✨ Ergebnis")
319
+ json_auto = gr.JSON(label="📊 Details")
320
 
321
  btn_auto.click(
322
  fn=segment_automatic,
323
+ inputs=[input_auto, quality_radio],
324
  outputs=[output_auto, json_auto]
325
  )
 
 
 
 
 
 
326
 
327
+ with gr.Tab("🎯 Multi-Region (Dicht)"):
328
  with gr.Row():
329
  with gr.Column():
330
  input_multi = gr.Image(type="pil", label="📸 Bild hochladen")
331
+ density_radio = gr.Radio(
332
+ choices=["high", "medium", "low"],
333
+ value="medium",
334
+ label="Punkt-Dichte",
335
+ info="High = mehr Details, aber langsamer"
336
+ )
337
  btn_multi = gr.Button("🚀 Alle Bereiche", variant="primary", size="lg")
338
+
339
+ gr.Markdown("""
340
+ **Grid-Größen:**
341
+ - High: 5x5 = 25 Punkte (für Details wie Flossen!)
342
+ - Medium: 4x4 = 16 Punkte
343
+ - Low: 3x3 = 9 Punkte
344
+ """)
345
 
346
  with gr.Column():
347
  output_multi = gr.Image(label="✨ Ergebnis")
348
+ json_multi = gr.JSON(label="📊 Details")
349
 
350
  btn_multi.click(
351
+ fn=segment_multi_dense,
352
+ inputs=[input_multi, density_radio],
353
  outputs=[output_multi, json_multi]
354
  )
355
 
356
  gr.Markdown("""
357
  ---
358
+ ### 📊 Qualitäts-Tipps:
359
 
360
+ **Beste Ergebnisse:**
361
+ - 📸 Gute Beleuchtung
362
+ - 🎯 Objekt zentral & klar erkennbar
363
+ - 🖼️ Kontrast zwischen Objekt & Hintergrund
364
+ - 📏 Nicht zu klein im Bild
365
 
366
+ **Bei fehlenden Details (z.B. Flosse):**
367
+ - Nutze "Multi-Region" mit "High" Density
368
+ - Oder