Astridkraft commited on
Commit
4c60fbc
·
verified ·
1 Parent(s): 5eb4d07

Update sam_module.py

Browse files
Files changed (1) hide show
  1. sam_module.py +165 -186
sam_module.py CHANGED
@@ -37,11 +37,9 @@ def create_sam_mask(self, image, bbox_coords, mode):
37
  print("🌳 MODUS: ENVIRONMENT_CHANGE")
38
  print("-" * 60)
39
 
40
-
41
  # Der Prozessor von SAM erwartet ein NumPy-Array kein PIL
42
  image_np = np.array(image.convert("RGB"))
43
 
44
-
45
  # Immer nur eine BBox verwenden (SAM 2 erwartet genau 1)
46
  input_boxes = [[[x1, y1, x2, y2]]]
47
 
@@ -67,7 +65,7 @@ def create_sam_mask(self, image, bbox_coords, mode):
67
  num_masks = outputs.pred_masks.shape[2]
68
  print(f" SAM lieferte {num_masks} verschiedene Masken")
69
 
70
- #Sammlung der Masken
71
  all_masks = []
72
 
73
  for i in range(num_masks):
@@ -154,11 +152,11 @@ def create_sam_mask(self, image, bbox_coords, mode):
154
  confidence_score * 0.1
155
  )
156
 
157
- print(f" 📊 STANDARD-SCORES für Maske {i+1}:")
158
- print(f" • BBox-Überlappung: {bbox_overlap_ratio:.3f}")
159
- print(f" • Zentrums-Distanz: {centroid_distance if 'centroid_distance' in locals() else 'N/A'}")
160
- print(f" • Flächen-Ratio: {area_ratio:.3f}")
161
- print(f" • GESAMTSCORE: {score:.3f}")
162
 
163
  if score > best_score:
164
  best_score = score
@@ -173,7 +171,6 @@ def create_sam_mask(self, image, bbox_coords, mode):
173
  max_val = mask_np.max()
174
  print(f" 🔍 Maximaler SAM-Konfidenzwert der besten Maske: {max_val:.3f}")
175
 
176
-
177
  if max_val < 0.6:
178
  dynamic_threshold = 0.3
179
  print(f" ⚠️ SAM ist unsicher (max_val={max_val:.3f} < 0.6)")
@@ -181,15 +178,13 @@ def create_sam_mask(self, image, bbox_coords, mode):
181
  dynamic_threshold = max_val * 0.8
182
  print(f" ✅ SAM ist sicher (max_val={max_val:.3f} >= 0.6)")
183
 
184
-
185
  # Binärmaske erstellen (256x256)
186
  mask_array = (mask_np > dynamic_threshold).astype(np.uint8) * 255
187
 
188
-
189
  # Fallback bei leerer Maske, der höchste Wert ist 0 also schwarz
190
  if mask_array.max() == 0:
191
  print(" ⚠️ Maske leer, erstelle rechteckige Fallback-Maske")
192
- mask_array = np.zeros((512, 512), dtype=np.uint8) * 255 #weiße 512x512-Maske
193
 
194
  # Skaliere BBox auf 512x512
195
  scale_x = 512 / image.width
@@ -202,10 +197,8 @@ def create_sam_mask(self, image, bbox_coords, mode):
202
  # Schwarzes Rechteck für Person
203
  cv2.rectangle(mask_array, (fb_x1, fb_y1), (fb_x2, fb_y2), 0, -1)
204
 
205
-
206
  # Damit wird die Rohmaske für die UI-Anzeige gespeichert
207
  raw_mask_array = mask_array.copy()
208
-
209
 
210
  print("🌳 ENVIRONMENT-CHANGE POSTPROCESSING")
211
 
@@ -217,19 +210,16 @@ def create_sam_mask(self, image, bbox_coords, mode):
217
  mask_array = np.array(temp_mask)
218
  print(f" ✅ Maske auf Originalgröße skaliert: {mask_array.shape}")
219
 
220
-
221
  # Maske invertieren (Person wird schwarz, Hintergrund weiß)
222
  mask_array = 255 - mask_array
223
  print(" ✅ Maske invertiert (Person schwarz, Hintergrund weiß)")
224
 
225
-
226
  # Weiße Punkte in der Person (schwarz) entfernen
227
  print("🧹 Entferne weiße Punkte in der Person...")
228
  kernel_open = np.ones((3, 3), np.uint8)
229
  mask_array = cv2.morphologyEx(mask_array, cv2.MORPH_OPEN, kernel_open, iterations=3)
230
  print(" ✅ MORPH_OPEN entfernt weiße Punkte in der Person")
231
 
232
-
233
  # DEBUG nach MORPH_OPEN
234
  print(f" Nach MORPH_OPEN - Weiße Pixel: {np.sum(mask_array > 127)}")
235
 
@@ -278,7 +268,6 @@ def create_sam_mask(self, image, bbox_coords, mode):
278
  print(f" Schwarze Pixel (PERSON - Erhaltung): {black_pixels:,} ({black_ratio:.1f}%)")
279
  print(f" Gesamtpixel: {total_pixels:,}")
280
 
281
-
282
  # Warnungen basierend auf Verhältnis
283
  if white_ratio < 30:
284
  print(f" ⚠️ WARNUNG: Sehr wenig Hintergrund ({white_ratio:.1f}%)")
@@ -291,7 +280,6 @@ def create_sam_mask(self, image, bbox_coords, mode):
291
  else:
292
  print(f" ℹ️ Normales Verhältnis ({white_ratio:.1f}%)")
293
 
294
-
295
  # Zurück zu PIL Image
296
  mask = Image.fromarray(mask_array).convert("L")
297
  raw_mask = Image.fromarray(raw_mask_array).convert("L")
@@ -302,7 +290,7 @@ def create_sam_mask(self, image, bbox_coords, mode):
302
  print(f"🎛️ Verwendeter Modus: {mode}")
303
  print("#" * 80)
304
 
305
- return mask, raw_mask #in mask steht die invertierte nachbearbeitete Maske, in raw_mask die Rohmaske
306
 
307
  # ============================================================
308
  # BLOCK 2: FOCUS_CHANGE
@@ -344,8 +332,9 @@ def create_sam_mask(self, image, bbox_coords, mode):
344
  print(f"✅ Vorhersage abgeschlossen")
345
  print(f" Anzahl der Vorhersagemasken: {outputs.pred_masks.shape[2]}")
346
 
 
347
 
348
- #Sammlung der Masken
349
  all_masks = []
350
 
351
  for i in range(num_masks):
@@ -360,7 +349,6 @@ def create_sam_mask(self, image, bbox_coords, mode):
360
  mask_np = resized_mask.sigmoid().cpu().numpy()
361
  all_masks.append(mask_np)
362
 
363
-
364
  # BBox-Information für Heuristik
365
  bbox_center = ((x1 + x2) // 2, (y1 + y2) // 2)
366
  bbox_area = (x2 - x1) * (y2 - y1)
@@ -450,7 +438,6 @@ def create_sam_mask(self, image, bbox_coords, mode):
450
  mask_np = resized_mask.sigmoid().cpu().numpy()
451
  print(f" 🔄 Beste Maske skaliert auf 512×512 für ControlNet")
452
 
453
-
454
  # ============================================================
455
  # DYNAMISCHER THRESHOLD
456
  # SAM gibt nur Wahrscheinlichkeiten aus!
@@ -470,7 +457,6 @@ def create_sam_mask(self, image, bbox_coords, mode):
470
  # Binärmaske erstellen (256x256)
471
  mask_array = (mask_np > dynamic_threshold).astype(np.uint8) * 255
472
 
473
-
474
  # Fallback bei leerer Maske, der höchste Wert ist 0 also schwarz
475
  if mask_array.max() == 0:
476
  print(" ⚠️ Maske leer, erstelle rechteckige Fallback-Maske")
@@ -645,49 +631,50 @@ def create_sam_mask(self, image, bbox_coords, mode):
645
  print(" 🔄 SAM wird auf aufbereitetem Ausschnitt ausgeführt")
646
  print(f" 📊 SAM-Eingabegröße: {image.size}")
647
 
648
- #======Bei allen Modi gleich=====
649
- print("-" * 60)
650
- print(f"📦 BOUNDING BOX DETAILS FÜR SAM:")
651
- print(f" Bild-Größe für SAM: {image.size}")
652
- print(f" BBox Koordinaten: [{x1}, {y1}, {x2}, {y2}]")
653
- print(f" BBox Dimensionen: {x2-x1}px × {y2-y1}px")
 
 
654
 
655
- # Vorbereitung für SAM2 - WICHTIG: NUR EINE BBOX
656
- print("-" * 60)
657
- print("🖼️ BILDAUFBEREITUNG FÜR SAM 2")
658
- #SAM erwartet NumPy-Array, kein PIL
659
- image_np = np.array(image.convert("RGB"))
660
 
661
- # Immer nur eine BBox verwenden (SAM 2 erwartet genau 1)
662
- input_boxes = [[[x1, y1, x2, y2]]]
663
-
664
- # Punkt in der BBox-Mitte (zur Ünterstützung von SAM damit BBox nicht zu dicht um Kopf gezogen werden muß!)
665
- center_x = (x1 + x2) // 2
666
- center_y = (y1 + y2) // 2
667
 
668
- # Punkt im Gesicht (30% höher vom Mittelpunkt)(auch für größere BBox)
669
- bbox_height = y2 - y1
670
- face_offset = int(bbox_height * 0.3)
671
- face_x = center_x
672
- face_y = center_y - face_offset
673
- face_y = max(y1 + 10, min(face_y, y2 - 10)) # In BBox halten
674
 
675
- # BEIDE Punkte kombinieren
676
- input_points = [[[[center_x, center_y], [face_x, face_y]]]] # ZWEI Punkte
677
- input_labels = [[[1, 1]]] # Beide sind positive Prompts
 
 
 
678
 
679
- print(f" 🎯 SAM-Prompt: BBox [{x1},{y1},{x2},{y2}]")
680
- print(f" 👁️ Punkte: Mitte ({center_x},{center_y}), Gesicht ({face_x},{face_y})")
 
681
 
 
 
682
 
683
- # Aufruf des SAM-Prozessors mit den Variablen. Der Processor verpackt diese Rohdaten
684
- # in die für das SAM-Modell erforderlichen Tensoren und speichert sie in inputs.
685
- inputs = self.sam_processor(
686
- image_np,
687
- input_boxes=input_boxes,
688
- input_points=input_points, # ZWEI Punkte
689
- input_labels=input_labels, # Zwei Labels
690
- return_tensors="pt"
691
  ).to(self.device) # Ohne .to(self.device) werden die Tensoren standardmäßig im CPU-RAM erzeugt und gespeichert! Da GPU-Fehler!
692
 
693
  print(f"✅ Processor-Ausgabe: Dictionary mit {len(inputs)} Schlüsseln: {list(inputs.keys())}")
@@ -695,7 +682,6 @@ def create_sam_mask(self, image, bbox_coords, mode):
695
  print(f" - 'input_boxes' Shape: {inputs['input_boxes'].shape}")
696
  if 'input_points' in inputs:
697
  print(f" - 'input_points' Shape: {inputs['input_points'].shape}")
698
-
699
 
700
  # 4. SAM2 Vorhersage
701
  print("-" * 60)
@@ -763,7 +749,6 @@ def create_sam_mask(self, image, bbox_coords, mode):
763
 
764
  mask_area_pixels = np.sum(mask_binary)
765
 
766
-
767
  # ============================================================
768
  # SPEZIALHEURISTIK
769
  # ============================================================
@@ -865,39 +850,38 @@ def create_sam_mask(self, image, bbox_coords, mode):
865
  max_val = mask_np.max()
866
  print(f"🔍 Maximaler SAM-Konfidenzwert der besten Maske: {max_val:.3f}")
867
 
868
-
869
- # ============================================================
870
- # THRESHOLD-BESTIMMUNG
871
- # ============================================================
872
- # Spezieller Threshold für Gesichter
873
- if max_val < 0.5:
874
- dynamic_threshold = 0.25
875
- print(f" ⚠️ SAM ist unsicher für Gesicht (max_val={max_val:.3f} < 0.5)")
876
- elif max_val < 0.8:
877
- dynamic_threshold = max_val * 0.65 # Mittlerer Threshold
878
- print(f" ℹ️ SAM ist mäßig sicher für Gesicht (max_val={max_val:.3f})")
879
- else:
880
- dynamic_threshold = max_val * 0.75 # Hoher Threshold
881
- print(f" ✅ SAM ist sicher für Gesicht (max_val={max_val:.3f} >= 0.8)")
882
 
883
- print(f" 🎯 Gesichts-Threshold: {dynamic_threshold:.3f}")
884
 
885
- # Binärmaske erstellen
886
- print("🐛 DEBUG THRESHOLD:")
887
- print(f" mask_np Min/Max: {mask_np.min():.3f}/{mask_np.max():.3f}")
888
- print(f" dynamic_threshold: {dynamic_threshold:.3f}")
889
 
890
- mask_array = (mask_np > dynamic_threshold).astype(np.uint8) * 255
891
 
892
- print(f"🚨 DEBUG BINÄRMASKE:")
893
- print(f" mask_array Min/Max: {mask_array.min()}/{mask_array.max()}")
894
- print(f" Weiße Pixel in mask_array: {np.sum(mask_array > 0)}")
895
- print(f" Anteil weiße Pixel: {np.sum(mask_array > 0) / mask_array.size:.1%}")
896
 
897
- # Fallback wenn Maske leer
898
- if mask_array.max() == 0:
899
- print("⚠️ KRITISCH: Binärmaske ist leer! Erzwinge Testmaske (BBox).")
900
- print(f" 🚨 BBox für Fallback: x1={x1}, y1={y1}, x2={x2}, y2={y2}")
901
 
902
  test_mask = np.zeros((image.height, image.width), dtype=np.uint8)
903
  cv2.rectangle(test_mask, (x1, y1), (x2, y2), 255, -1)
@@ -905,126 +889,123 @@ def create_sam_mask(self, image, bbox_coords, mode):
905
  mask_array = test_mask
906
  print(f"🐛 DEBUG ERZWUNGENE MASKE: Weiße Pixel: {np.sum(mask_array > 0)}")
907
 
908
- # Rohmaske speichern
909
- raw_mask_array = mask_array.copy()
910
 
911
- # ============================================================
912
- # POSTPROCESSING
913
- # ============================================================
914
 
915
- print("👤 GESICHTS-SPEZIFISCHES POSTPROCESSING")
916
 
917
- # 1. Größte zusammenhängende Komponente finden
918
- labeled_array, num_features = ndimage.label(mask_array)
919
 
920
- if num_features > 0:
921
- print(f" 🔍 Gefundene Komponenten: {num_features}")
922
 
923
- sizes = ndimage.sum(mask_array, labeled_array, range(1, num_features + 1))
924
- largest_component_idx = np.argmax(sizes) + 1
925
 
926
- print(f" 👑 Größte Komponente: Nr. {largest_component_idx} mit {sizes[largest_component_idx-1]:,} Pixel")
927
 
928
- # NUR die größte Komponente behalten (der Kopf)
929
- mask_array = np.where(labeled_array == largest_component_idx, mask_array, 0)
930
 
931
- # MORPHOLOGISCHE OPERATIONEN FÜR SAUBEREN KOPF
932
- print(" ⚙️ Morphologische Operationen für sauberen Kopf")
933
 
934
- # Zuerst CLOSE, um kleine Löcher im Kopf zu füllen
935
- kernel_close = np.ones((7, 7), np.uint8)
936
- mask_array = cv2.morphologyEx(mask_array, cv2.MORPH_CLOSE, kernel_close, iterations=1)
937
- print(" • MORPH_CLOSE (7x7) - Löcher im Kopf füllen")
938
 
939
- # Dann OPEN, um kleine Ausreißer zu entfernen
940
- kernel_open = np.ones((5, 5), np.uint8)
941
- mask_array = cv2.morphologyEx(mask_array, cv2.MORPH_OPEN, kernel_open, iterations=1)
942
- print(" • MORPH_OPEN (5x5) - Rauschen entfernen")
943
-
944
- # ============================================================
945
- # KRITISCH: MASKE IMMER ZURÜCK AUF ORIGINALGRÖSSE (auch bei Fallback!)
946
- # ============================================================
947
- print("-" * 60)
948
- print("🔄 MASKE IMMER ZURÜCK AUF ORIGINALGRÖSSE TRANSFORMIEREN")
949
 
950
- # WICHTIG: Immer die richtigen Crop-Koordinaten verwenden
951
- temp_mask = Image.fromarray(mask_array).convert("L")
952
- print(f" Maskengröße auf Ausschnitt: {temp_mask.size}")
953
 
954
- # Maske auf ORIGINALBILDGRÖSSE bringen
955
- final_mask = Image.new("L", original_image.size, 0)
956
- print(f" Leere Maske in Originalgröße: {final_mask.size}")
957
 
958
- # Immer die gespeicherten Crop-Koordinaten verwenden
959
- if crop_x1 is not None and crop_y1 is not None:
960
- final_mask.paste(temp_mask, (crop_x1, crop_y1))
961
- print(f" Maskenposition im Original: ({crop_x1}, {crop_y1})")
962
- else:
963
- # Fallback: Zentrieren
964
- x_offset = (original_image.width - temp_mask.width) // 2
965
- y_offset = (original_image.height - temp_mask.height) // 2
966
- final_mask.paste(temp_mask, (x_offset, y_offset))
967
- print(f" ⚠️ Keine Crop-Koordinaten, zentriert: ({x_offset}, {y_offset})")
968
 
969
- mask_array = np.array(final_mask)
970
- print(f" ✅ Maske zurück auf Originalgröße skaliert: {mask_array.shape}")
971
 
972
- # Bild-Referenz zurücksetzen
973
- image = original_image
974
- print(f" 🔄 Bild-Referenz wieder auf Original gesetzt: {image.size}")
975
 
976
- # ============================================================
977
- # ABSCHLIESSENDE STATISTIK
978
- # ============================================================
979
-
980
- print("📊 FINALE MASKEN-STATISTIK")
981
-
982
- # Weiße Pixel zählen
983
- white_pixels = np.sum(mask_array > 0)
984
- total_pixels = mask_array.size
985
- white_ratio = white_pixels / total_pixels * 100
986
 
987
- # Original-BBox Fläche (vor Crop)
988
- original_face_area = original_bbox_size[0] * original_bbox_size[1]
989
- coverage_ratio = white_pixels / original_face_area if original_face_area > 0 else 0
990
- print(f" 👤 GESICHTSABDECKUNG: {coverage_ratio:.1%} der ursprünglichen BBox")
991
 
992
- print(f" Weiße Pixel (Veränderungsbereich): {white_pixels:,} ({white_ratio:.1f}%)")
993
- print(f" Schwarze Pixel (Erhaltungsbereich): {total_pixels-white_pixels:,} ({100-white_ratio:.1f}%)")
994
- print(f" Gesamtpixel: {total_pixels:,}")
995
- print(f" ���� GESICHTSABDECKUNG: {coverage_ratio:.1%} der ursprünglichen BBox")
996
 
997
- # Warnungen basierend auf Abdeckung
998
- if coverage_ratio < 0.7:
999
- print(f" ⚠️ WARNUNG: Geringe Gesichtsabdeckung ({coverage_ratio:.1%})")
1000
- elif coverage_ratio > 1.3:
1001
- print(f" ⚠️ WARNUNG: Sehr hohe Gesichtsabdeckung ({coverage_ratio:.1%})")
1002
- elif 0.8 <= coverage_ratio <= 1.2:
1003
- print(f" ✅ OPTIMALE Gesichtsabdeckung ({coverage_ratio:.1%})")
1004
 
1005
- #===============
1006
-
1007
- # Zurück zu PIL Image
1008
- mask = Image.fromarray(mask_array).convert("L")
1009
- raw_mask = Image.fromarray(raw_mask_array).convert("L")
1010
 
 
 
 
 
 
 
 
1011
 
 
 
 
1012
 
1013
- print("#" * 80)
1014
- print(f"✅ SAM 2 SEGMENTIERUNG ABGESCHLOSSEN")
1015
- print(f"📐 Finale Maskengröße: {mask.size}")
1016
- print(f"🎛️ Verwendeter Modus: {mode}")
1017
 
1018
- print(f"👤 Crop={crop_size}×{crop_size}px, Heuristik-Score={best_score:.3f}")
1019
- print(f"👤 Kopfabdeckung: {coverage_ratio:.1%} der BBox")
1020
 
1021
- print(f"🔍 DEBUG FINALE MASKE:")
1022
- print(f" mask_array Min/Max: {mask_array.min()}/{mask_array.max()}, Typ: {mask_array.dtype}")
1023
- print(f" Weiße Pixel final: {np.sum(mask_array > 0)}")
1024
 
1025
- print("#" * 80)
1026
 
1027
- return mask, raw_mask
1028
 
1029
  # ============================================================
1030
  # UNBEKANNTER MODUS
@@ -1047,6 +1028,4 @@ def create_sam_mask(self, image, bbox_coords, mode):
1047
  print(f" ⚠️ Fallback-Maske angepasst: {fallback_mask.size} → {original_image.size}")
1048
  fallback_mask = fallback_mask.resize(original_image.size, Image.Resampling.NEAREST)
1049
 
1050
- return fallback_mask, fallback_mask
1051
-
1052
-
 
37
  print("🌳 MODUS: ENVIRONMENT_CHANGE")
38
  print("-" * 60)
39
 
 
40
  # Der Prozessor von SAM erwartet ein NumPy-Array kein PIL
41
  image_np = np.array(image.convert("RGB"))
42
 
 
43
  # Immer nur eine BBox verwenden (SAM 2 erwartet genau 1)
44
  input_boxes = [[[x1, y1, x2, y2]]]
45
 
 
65
  num_masks = outputs.pred_masks.shape[2]
66
  print(f" SAM lieferte {num_masks} verschiedene Masken")
67
 
68
+ # Sammlung der Masken
69
  all_masks = []
70
 
71
  for i in range(num_masks):
 
152
  confidence_score * 0.1
153
  )
154
 
155
+ print(f" 📊 STANDARD-SCORES für Maske {i+1}:")
156
+ print(f" • BBox-Überlappung: {bbox_overlap_ratio:.3f}")
157
+ print(f" • Zentrums-Distanz: {centroid_distance if 'centroid_distance' in locals() else 'N/A'}")
158
+ print(f" • Flächen-Ratio: {area_ratio:.3f}")
159
+ print(f" • GESAMTSCORE: {score:.3f}")
160
 
161
  if score > best_score:
162
  best_score = score
 
171
  max_val = mask_np.max()
172
  print(f" 🔍 Maximaler SAM-Konfidenzwert der besten Maske: {max_val:.3f}")
173
 
 
174
  if max_val < 0.6:
175
  dynamic_threshold = 0.3
176
  print(f" ⚠️ SAM ist unsicher (max_val={max_val:.3f} < 0.6)")
 
178
  dynamic_threshold = max_val * 0.8
179
  print(f" ✅ SAM ist sicher (max_val={max_val:.3f} >= 0.6)")
180
 
 
181
  # Binärmaske erstellen (256x256)
182
  mask_array = (mask_np > dynamic_threshold).astype(np.uint8) * 255
183
 
 
184
  # Fallback bei leerer Maske, der höchste Wert ist 0 also schwarz
185
  if mask_array.max() == 0:
186
  print(" ⚠️ Maske leer, erstelle rechteckige Fallback-Maske")
187
+ mask_array = np.zeros((512, 512), dtype=np.uint8) * 255 # weiße 512x512-Maske
188
 
189
  # Skaliere BBox auf 512x512
190
  scale_x = 512 / image.width
 
197
  # Schwarzes Rechteck für Person
198
  cv2.rectangle(mask_array, (fb_x1, fb_y1), (fb_x2, fb_y2), 0, -1)
199
 
 
200
  # Damit wird die Rohmaske für die UI-Anzeige gespeichert
201
  raw_mask_array = mask_array.copy()
 
202
 
203
  print("🌳 ENVIRONMENT-CHANGE POSTPROCESSING")
204
 
 
210
  mask_array = np.array(temp_mask)
211
  print(f" ✅ Maske auf Originalgröße skaliert: {mask_array.shape}")
212
 
 
213
  # Maske invertieren (Person wird schwarz, Hintergrund weiß)
214
  mask_array = 255 - mask_array
215
  print(" ✅ Maske invertiert (Person schwarz, Hintergrund weiß)")
216
 
 
217
  # Weiße Punkte in der Person (schwarz) entfernen
218
  print("🧹 Entferne weiße Punkte in der Person...")
219
  kernel_open = np.ones((3, 3), np.uint8)
220
  mask_array = cv2.morphologyEx(mask_array, cv2.MORPH_OPEN, kernel_open, iterations=3)
221
  print(" ✅ MORPH_OPEN entfernt weiße Punkte in der Person")
222
 
 
223
  # DEBUG nach MORPH_OPEN
224
  print(f" Nach MORPH_OPEN - Weiße Pixel: {np.sum(mask_array > 127)}")
225
 
 
268
  print(f" Schwarze Pixel (PERSON - Erhaltung): {black_pixels:,} ({black_ratio:.1f}%)")
269
  print(f" Gesamtpixel: {total_pixels:,}")
270
 
 
271
  # Warnungen basierend auf Verhältnis
272
  if white_ratio < 30:
273
  print(f" ⚠️ WARNUNG: Sehr wenig Hintergrund ({white_ratio:.1f}%)")
 
280
  else:
281
  print(f" ℹ️ Normales Verhältnis ({white_ratio:.1f}%)")
282
 
 
283
  # Zurück zu PIL Image
284
  mask = Image.fromarray(mask_array).convert("L")
285
  raw_mask = Image.fromarray(raw_mask_array).convert("L")
 
290
  print(f"🎛️ Verwendeter Modus: {mode}")
291
  print("#" * 80)
292
 
293
+ return mask, raw_mask # in mask steht die invertierte nachbearbeitete Maske, in raw_mask die Rohmaske
294
 
295
  # ============================================================
296
  # BLOCK 2: FOCUS_CHANGE
 
332
  print(f"✅ Vorhersage abgeschlossen")
333
  print(f" Anzahl der Vorhersagemasken: {outputs.pred_masks.shape[2]}")
334
 
335
+ num_masks = outputs.pred_masks.shape[2]
336
 
337
+ # Sammlung der Masken
338
  all_masks = []
339
 
340
  for i in range(num_masks):
 
349
  mask_np = resized_mask.sigmoid().cpu().numpy()
350
  all_masks.append(mask_np)
351
 
 
352
  # BBox-Information für Heuristik
353
  bbox_center = ((x1 + x2) // 2, (y1 + y2) // 2)
354
  bbox_area = (x2 - x1) * (y2 - y1)
 
438
  mask_np = resized_mask.sigmoid().cpu().numpy()
439
  print(f" 🔄 Beste Maske skaliert auf 512×512 für ControlNet")
440
 
 
441
  # ============================================================
442
  # DYNAMISCHER THRESHOLD
443
  # SAM gibt nur Wahrscheinlichkeiten aus!
 
457
  # Binärmaske erstellen (256x256)
458
  mask_array = (mask_np > dynamic_threshold).astype(np.uint8) * 255
459
 
 
460
  # Fallback bei leerer Maske, der höchste Wert ist 0 also schwarz
461
  if mask_array.max() == 0:
462
  print(" ⚠️ Maske leer, erstelle rechteckige Fallback-Maske")
 
631
  print(" 🔄 SAM wird auf aufbereitetem Ausschnitt ausgeführt")
632
  print(f" 📊 SAM-Eingabegröße: {image.size}")
633
 
634
+ # ============================================================
635
+ # SCHRITT 5: SAM-AUSFÜHRUNG
636
+ # ============================================================
637
+ print("-" * 60)
638
+ print(f"📦 BOUNDING BOX DETAILS FÜR SAM:")
639
+ print(f" Bild-Größe für SAM: {image.size}")
640
+ print(f" BBox Koordinaten: [{x1}, {y1}, {x2}, {y2}]")
641
+ print(f" BBox Dimensionen: {x2-x1}px × {y2-y1}px")
642
 
643
+ # Vorbereitung für SAM2 - WICHTIG: NUR EINE BBOX
644
+ print("-" * 60)
645
+ print("🖼️ BILDAUFBEREITUNG FÜR SAM 2")
646
+ # SAM erwartet NumPy-Array, kein PIL
647
+ image_np = np.array(image.convert("RGB"))
648
 
649
+ # Immer nur eine BBox verwenden (SAM 2 erwartet genau 1)
650
+ input_boxes = [[[x1, y1, x2, y2]]]
 
 
 
 
651
 
652
+ # Punkt in der BBox-Mitte (zur Ünterstützung von SAM damit BBox nicht zu dicht um Kopf gezogen werden muß!)
653
+ center_x = (x1 + x2) // 2
654
+ center_y = (y1 + y2) // 2
 
 
 
655
 
656
+ # Punkt im Gesicht (30% höher vom Mittelpunkt)(auch für größere BBox)
657
+ bbox_height = y2 - y1
658
+ face_offset = int(bbox_height * 0.3)
659
+ face_x = center_x
660
+ face_y = center_y - face_offset
661
+ face_y = max(y1 + 10, min(face_y, y2 - 10)) # In BBox halten
662
 
663
+ # BEIDE Punkte kombinieren
664
+ input_points = [[[[center_x, center_y], [face_x, face_y]]]] # ZWEI Punkte
665
+ input_labels = [[[1, 1]]] # Beide sind positive Prompts
666
 
667
+ print(f" 🎯 SAM-Prompt: BBox [{x1},{y1},{x2},{y2}]")
668
+ print(f" 👁️ Punkte: Mitte ({center_x},{center_y}), Gesicht ({face_x},{face_y})")
669
 
670
+ # Aufruf des SAM-Prozessors mit den Variablen. Der Processor verpackt diese Rohdaten
671
+ # in die für das SAM-Modell erforderlichen Tensoren und speichert sie in inputs.
672
+ inputs = self.sam_processor(
673
+ image_np,
674
+ input_boxes=input_boxes,
675
+ input_points=input_points, # ZWEI Punkte
676
+ input_labels=input_labels, # Zwei Labels
677
+ return_tensors="pt"
678
  ).to(self.device) # Ohne .to(self.device) werden die Tensoren standardmäßig im CPU-RAM erzeugt und gespeichert! Da GPU-Fehler!
679
 
680
  print(f"✅ Processor-Ausgabe: Dictionary mit {len(inputs)} Schlüsseln: {list(inputs.keys())}")
 
682
  print(f" - 'input_boxes' Shape: {inputs['input_boxes'].shape}")
683
  if 'input_points' in inputs:
684
  print(f" - 'input_points' Shape: {inputs['input_points'].shape}")
 
685
 
686
  # 4. SAM2 Vorhersage
687
  print("-" * 60)
 
749
 
750
  mask_area_pixels = np.sum(mask_binary)
751
 
 
752
  # ============================================================
753
  # SPEZIALHEURISTIK
754
  # ============================================================
 
850
  max_val = mask_np.max()
851
  print(f"🔍 Maximaler SAM-Konfidenzwert der besten Maske: {max_val:.3f}")
852
 
853
+ # ============================================================
854
+ # THRESHOLD-BESTIMMUNG
855
+ # ============================================================
856
+ # Spezieller Threshold für Gesichter
857
+ if max_val < 0.5:
858
+ dynamic_threshold = 0.25
859
+ print(f" ⚠️ SAM ist unsicher für Gesicht (max_val={max_val:.3f} < 0.5)")
860
+ elif max_val < 0.8:
861
+ dynamic_threshold = max_val * 0.65 # Mittlerer Threshold
862
+ print(f" ℹ️ SAM ist mäßig sicher für Gesicht (max_val={max_val:.3f})")
863
+ else:
864
+ dynamic_threshold = max_val * 0.75 # Hoher Threshold
865
+ print(f" ✅ SAM ist sicher für Gesicht (max_val={max_val:.3f} >= 0.8)")
 
866
 
867
+ print(f" 🎯 Gesichts-Threshold: {dynamic_threshold:.3f}")
868
 
869
+ # Binärmaske erstellen
870
+ print("🐛 DEBUG THRESHOLD:")
871
+ print(f" mask_np Min/Max: {mask_np.min():.3f}/{mask_np.max():.3f}")
872
+ print(f" dynamic_threshold: {dynamic_threshold:.3f}")
873
 
874
+ mask_array = (mask_np > dynamic_threshold).astype(np.uint8) * 255
875
 
876
+ print(f"🚨 DEBUG BINÄRMASKE:")
877
+ print(f" mask_array Min/Max: {mask_array.min()}/{mask_array.max()}")
878
+ print(f" Weiße Pixel in mask_array: {np.sum(mask_array > 0)}")
879
+ print(f" Anteil weiße Pixel: {np.sum(mask_array > 0) / mask_array.size:.1%}")
880
 
881
+ # Fallback wenn Maske leer
882
+ if mask_array.max() == 0:
883
+ print("⚠️ KRITISCH: Binärmaske ist leer! Erzwinge Testmaske (BBox).")
884
+ print(f" 🚨 BBox für Fallback: x1={x1}, y1={y1}, x2={x2}, y2={y2}")
885
 
886
  test_mask = np.zeros((image.height, image.width), dtype=np.uint8)
887
  cv2.rectangle(test_mask, (x1, y1), (x2, y2), 255, -1)
 
889
  mask_array = test_mask
890
  print(f"🐛 DEBUG ERZWUNGENE MASKE: Weiße Pixel: {np.sum(mask_array > 0)}")
891
 
892
+ # Rohmaske speichern
893
+ raw_mask_array = mask_array.copy()
894
 
895
+ # ============================================================
896
+ # POSTPROCESSING
897
+ # ============================================================
898
 
899
+ print("👤 GESICHTS-SPEZIFISCHES POSTPROCESSING")
900
 
901
+ # 1. Größte zusammenhängende Komponente finden
902
+ labeled_array, num_features = ndimage.label(mask_array)
903
 
904
+ if num_features > 0:
905
+ print(f" 🔍 Gefundene Komponenten: {num_features}")
906
 
907
+ sizes = ndimage.sum(mask_array, labeled_array, range(1, num_features + 1))
908
+ largest_component_idx = np.argmax(sizes) + 1
909
 
910
+ print(f" 👑 Größte Komponente: Nr. {largest_component_idx} mit {sizes[largest_component_idx-1]:,} Pixel")
911
 
912
+ # NUR die größte Komponente behalten (der Kopf)
913
+ mask_array = np.where(labeled_array == largest_component_idx, mask_array, 0)
914
 
915
+ # MORPHOLOGISCHE OPERATIONEN FÜR SAUBEREN KOPF
916
+ print(" ⚙️ Morphologische Operationen für sauberen Kopf")
917
 
918
+ # Zuerst CLOSE, um kleine Löcher im Kopf zu füllen
919
+ kernel_close = np.ones((7, 7), np.uint8)
920
+ mask_array = cv2.morphologyEx(mask_array, cv2.MORPH_CLOSE, kernel_close, iterations=1)
921
+ print(" • MORPH_CLOSE (7x7) - Löcher im Kopf füllen")
922
 
923
+ # Dann OPEN, um kleine Ausreißer zu entfernen
924
+ kernel_open = np.ones((5, 5), np.uint8)
925
+ mask_array = cv2.morphologyEx(mask_array, cv2.MORPH_OPEN, kernel_open, iterations=1)
926
+ print(" • MORPH_OPEN (5x5) - Rauschen entfernen")
927
+
928
+ # ============================================================
929
+ # KRITISCH: MASKE IMMER ZURÜCK AUF ORIGINALGRÖSSE (auch bei Fallback!)
930
+ # ============================================================
931
+ print("-" * 60)
932
+ print("🔄 MASKE IMMER ZURÜCK AUF ORIGINALGRÖSSE TRANSFORMIEREN")
933
 
934
+ # WICHTIG: Immer die richtigen Crop-Koordinaten verwenden
935
+ temp_mask = Image.fromarray(mask_array).convert("L")
936
+ print(f" Maskengröße auf Ausschnitt: {temp_mask.size}")
937
 
938
+ # Maske auf ORIGINALBILDGRÖSSE bringen
939
+ final_mask = Image.new("L", original_image.size, 0)
940
+ print(f" Leere Maske in Originalgröße: {final_mask.size}")
941
 
942
+ # Immer die gespeicherten Crop-Koordinaten verwenden
943
+ if 'crop_x1' in locals() and 'crop_y1' in locals():
944
+ final_mask.paste(temp_mask, (crop_x1, crop_y1))
945
+ print(f" Maskenposition im Original: ({crop_x1}, {crop_y1})")
946
+ else:
947
+ # Fallback: Zentrieren
948
+ x_offset = (original_image.width - temp_mask.width) // 2
949
+ y_offset = (original_image.height - temp_mask.height) // 2
950
+ final_mask.paste(temp_mask, (x_offset, y_offset))
951
+ print(f" ⚠️ Keine Crop-Koordinaten, zentriert: ({x_offset}, {y_offset})")
952
 
953
+ mask_array = np.array(final_mask)
954
+ print(f" ✅ Maske zurück auf Originalgröße skaliert: {mask_array.shape}")
955
 
956
+ # Bild-Referenz zurücksetzen
957
+ image = original_image
958
+ print(f" 🔄 Bild-Referenz wieder auf Original gesetzt: {image.size}")
959
 
960
+ # ============================================================
961
+ # ABSCHLIESSENDE STATISTIK
962
+ # ============================================================
 
 
 
 
 
 
 
963
 
964
+ print("📊 FINALE MASKEN-STATISTIK")
 
 
 
965
 
966
+ # Weiße Pixel zählen
967
+ white_pixels = np.sum(mask_array > 0)
968
+ total_pixels = mask_array.size
969
+ white_ratio = white_pixels / total_pixels * 100
970
 
971
+ # Original-BBox Fläche (vor Crop)
972
+ original_bbox_width = original_bbox[2] - original_bbox[0]
973
+ original_bbox_height = original_bbox[3] - original_bbox[1]
974
+ original_face_area = original_bbox_width * original_bbox_height
975
+ coverage_ratio = white_pixels / original_face_area if original_face_area > 0 else 0
976
+ print(f" 👤 GESICHTSABDECKUNG: {coverage_ratio:.1%} der ursprünglichen BBox")
 
977
 
978
+ print(f" Weiße Pixel (Veränderungsbereich): {white_pixels:,} ({white_ratio:.1f}%)")
979
+ print(f" Schwarze Pixel (Erhaltungsbereich): {total_pixels-white_pixels:,} ({100-white_ratio:.1f}%)")
980
+ print(f" Gesamtpixel: {total_pixels:,}")
 
 
981
 
982
+ # Warnungen basierend auf Abdeckung
983
+ if coverage_ratio < 0.7:
984
+ print(f" ⚠️ WARNUNG: Geringe Gesichtsabdeckung ({coverage_ratio:.1%})")
985
+ elif coverage_ratio > 1.3:
986
+ print(f" ⚠️ WARNUNG: Sehr hohe Gesichtsabdeckung ({coverage_ratio:.1%})")
987
+ elif 0.8 <= coverage_ratio <= 1.2:
988
+ print(f" ✅ OPTIMALE Gesichtsabdeckung ({coverage_ratio:.1%})")
989
 
990
+ # Zurück zu PIL Image
991
+ mask = Image.fromarray(mask_array).convert("L")
992
+ raw_mask = Image.fromarray(raw_mask_array).convert("L")
993
 
994
+ print("#" * 80)
995
+ print(f"✅ SAM 2 SEGMENTIERUNG ABGESCHLOSSEN")
996
+ print(f"📐 Finale Maskengröße: {mask.size}")
997
+ print(f"🎛️ Verwendeter Modus: {mode}")
998
 
999
+ print(f"👤 Crop={crop_size}×{crop_size}px, Heuristik-Score={best_score:.3f}")
1000
+ print(f"👤 Kopfabdeckung: {coverage_ratio:.1%} der BBox")
1001
 
1002
+ print(f"🔍 DEBUG FINALE MASKE:")
1003
+ print(f" mask_array Min/Max: {mask_array.min()}/{mask_array.max()}, Typ: {mask_array.dtype}")
1004
+ print(f" Weiße Pixel final: {np.sum(mask_array > 0)}")
1005
 
1006
+ print("#" * 80)
1007
 
1008
+ return mask, raw_mask
1009
 
1010
  # ============================================================
1011
  # UNBEKANNTER MODUS
 
1028
  print(f" ⚠️ Fallback-Maske angepasst: {fallback_mask.size} → {original_image.size}")
1029
  fallback_mask = fallback_mask.resize(original_image.size, Image.Resampling.NEAREST)
1030
 
1031
+ return fallback_mask, fallback_mask