Astridkraft commited on
Commit
676ec76
·
verified ·
1 Parent(s): bebab88

Update sam_module.py

Browse files
Files changed (1) hide show
  1. sam_module.py +203 -202
sam_module.py CHANGED
@@ -730,213 +730,226 @@ def create_sam_mask(self, image, bbox_coords, mode):
730
  # Basis-Statistiken für jede Maske
731
  mask_binary = (mask_np > 0.5).astype(np.uint8)
732
  mask_area = np.sum(mask_binary)
733
- print(f" Maske {i+1}: Größe={mask_area:,} Pixel, Max-Konfidenz={mask_np.max():.3f}")
734
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
735
 
736
 
 
 
 
737
 
738
-
739
-
740
-
741
-
742
-
743
-
744
-
745
-
746
-
747
-
748
-
749
-
750
-
751
-
752
-
753
-
754
- # ============================================================
755
- # SPEZIALHEURISTIK FÜR GESICHTSMODUS (später im Code)
756
- # ============================================================
757
- if mode == "face_only_change":
758
- print(f" 🔍 Analysiere Maske {i+1} mit GESICHTS-HEURISTIK")
759
-
760
- # 1. FLÄCHENBASIERTE BEWERTUNG (40%)
761
- area_ratio = mask_area_pixels / bbox_area
762
- print(f" 📐 Flächen-Ratio: {area_ratio:.3f} ({mask_area_pixels:,} / {bbox_area:,} Pixel)")
763
-
764
- # Optimale Kopfgröße: 80-120% der BBox
765
- if area_ratio < 0.6:
766
- print(f" ⚠️ Fläche zu klein für Kopf (<60% der BBox)")
767
- area_score = area_ratio * 0.5 # Stark bestrafen
768
- elif area_ratio > 1.5:
769
- print(f" ⚠️ Fläche zu groß für Kopf (>150% der BBox)")
770
- area_score = 2.0 - area_ratio # Linear bestrafen
771
- elif 0.8 <= area_ratio <= 1.2:
772
- area_score = 1.0 # Perfekte Größe
773
- print(f" ✅ Perfekte Kopfgröße (80-120% der BBox)")
774
- else:
775
- # Sanfte Abweichung
776
- area_score = 1.0 - abs(area_ratio - 1.0) * 0.5
777
-
778
- # 2. KOMPAKTHEIT/SOLIDITÄT (30%)
779
- labeled_mask = measure.label(mask_binary)
780
- regions = measure.regionprops(labeled_mask)
781
-
782
- if len(regions) == 0:
783
- compactness_score = 0.1
784
- print(f" ❌ Keine zusammenhängenden Regionen gefunden")
785
- else:
786
- # Größte Region finden (sollte der Kopf sein)
787
- largest_region = max(regions, key=lambda r: r.area)
788
 
789
- # Solidität = Fläche / konvexe Hüllenfläche
790
- solidity = largest_region.solidity if hasattr(largest_region, 'solidity') else 0.7
791
 
792
- # Exzentrizität (wie elliptisch) - Köpfe sind tendenziell elliptisch
793
- eccentricity = largest_region.eccentricity if hasattr(largest_region, 'eccentricity') else 0.5
794
 
795
- # Perfekt runde Formen (Kreis) sind 0, Linie wäre 1
796
- # Köpfe haben typischerweise 0.5-0.8
797
- if 0.4 <= eccentricity <= 0.9:
798
- eccentricity_score = 1.0 - abs(eccentricity - 0.65) * 2
799
- else:
800
- eccentricity_score = 0.2
801
 
802
- compactness_score = (solidity * 0.6 + eccentricity_score * 0.4)
803
- print(f" 🎯 Kompaktheits-Analyse:")
804
- print(f" • Solidität (Fläche/Konvex): {solidity:.3f}")
805
- print(f" • Exzentrizität (Form): {eccentricity:.3f}")
806
- print(f" • Kompaktheits-Score: {compactness_score:.3f}")
807
-
808
- # 3. BBOX-ÜBERLAPPUNG (20%)
809
- bbox_mask = np.zeros((image.height, image.width), dtype=np.uint8)
810
- bbox_mask[y1:y2, x1:x2] = 1
811
- overlap = np.sum(mask_binary & bbox_mask)
812
- bbox_overlap_ratio = overlap / mask_area_pixels if mask_area_pixels > 0 else 0
813
-
814
- # Für Kopf: Sollte großteils in BBox sein (mind. 70%)
815
- if bbox_overlap_ratio >= 0.7:
816
- bbox_score = 1.0
817
- print(f" ✅ Hohe BBox-Überlappung: {bbox_overlap_ratio:.3f} ({overlap:,} Pixel)")
818
- elif bbox_overlap_ratio >= 0.5:
819
- bbox_score = bbox_overlap_ratio * 1.2
820
- print(f" ⚠️ Mittlere BBox-Überlappung: {bbox_overlap_ratio:.3f}")
821
- else:
822
- bbox_score = bbox_overlap_ratio * 0.8
823
- print(f" ❌ Geringe BBox-Überlappung: {bbox_overlap_ratio:.3f}")
824
-
825
- # 4. SAM-KONFIDENZ (10%)
826
- confidence_score = mask_max
827
-
828
- # GESAMTSCORE für Gesicht
829
- score = (
830
- area_score * 0.4 + # 40% Flächenpassung
831
- compactness_score * 0.3 + # 30% Kompaktheit
832
- bbox_score * 0.2 + # 20% BBox-Überlappung
833
- confidence_score * 0.1 # 10% Konfidenz
834
- )
835
-
836
- print(f" 📊 GESICHTS-SCORES für Maske {i+1}:")
837
- print(f" • Flächen-Score: {area_score:.3f}")
838
- print(f" • Kompaktheits-Score: {compactness_score:.3f}")
839
- print(f" • BBox-Überlappungs-Score: {bbox_score:.3f}")
840
- print(f" • Konfidenz-Score: {confidence_score:.3f}")
841
- print(f" • GESAMTSCORE: {score:.3f}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
842
 
843
- # ============================================================
844
- # THRESHOLD-BESTIMMUNG FÜR GESICHTSMODUS
845
- # ============================================================
846
- if mode == "face_only_change":
847
- # Spezieller Threshold für Gesichter
848
- if max_val < 0.5:
849
- dynamic_threshold = 0.25
850
- print(f" ⚠️ SAM ist unsicher für Gesicht (max_val={max_val:.3f} < 0.5)")
851
- elif max_val < 0.8:
852
- dynamic_threshold = max_val * 0.65 # Mittlerer Threshold
853
- print(f" ℹ️ SAM ist mäßig sicher für Gesicht (max_val={max_val:.3f})")
854
- else:
855
- dynamic_threshold = max_val * 0.75 # Hoher Threshold
856
- print(f" ✅ SAM ist sicher für Gesicht (max_val={max_val:.3f} >= 0.8)")
857
-
858
- print(f" 🎯 Gesichts-Threshold: {dynamic_threshold:.3f}")
859
 
860
- # ============================================================
861
- # POSTPROCESSING FÜR GESICHTSMODUS
862
- # ============================================================
863
- if mode == "face_only_change":
864
- print("👤 GESICHTS-SPEZIFISCHES POSTPROCESSING")
865
 
866
- # 1. Größte zusammenhängende Komponente finden
867
- labeled_array, num_features = ndimage.label(mask_array)
868
 
869
- if num_features > 0:
870
- print(f" 🔍 Gefundene Komponenten: {num_features}")
871
 
872
- sizes = ndimage.sum(mask_array, labeled_array, range(1, num_features + 1))
873
- largest_component_idx = np.argmax(sizes) + 1
874
 
875
- print(f" 👑 Größte Komponente: Nr. {largest_component_idx} mit {sizes[largest_component_idx-1]:,} Pixel")
876
 
877
- # NUR die größte Komponente behalten (der Kopf)
878
- mask_array = np.where(labeled_array == largest_component_idx, mask_array, 0)
879
 
880
- # 2. MORPHOLOGISCHE OPERATIONEN FÜR SAUBEREN KOPF
881
- print(" ⚙️ Morphologische Operationen für sauberen Kopf")
882
 
883
- # Zuerst CLOSE, um kleine Löcher im Kopf zu füllen
884
- kernel_close = np.ones((7, 7), np.uint8)
885
- mask_array = cv2.morphologyEx(mask_array, cv2.MORPH_CLOSE, kernel_close, iterations=1)
886
- print(" • MORPH_CLOSE (7x7) - Löcher im Kopf füllen")
887
 
888
- # Dann OPEN, um kleine Ausreißer zu entfernen
889
- kernel_open = np.ones((5, 5), np.uint8)
890
- mask_array = cv2.morphologyEx(mask_array, cv2.MORPH_OPEN, kernel_open, iterations=1)
891
- print(" • MORPH_OPEN (5x5) - Rauschen entfernen")
892
-
893
- # ============================================================
894
- # KRITISCH: MASKE IMMER ZURÜCK AUF ORIGINALGRÖSSE (auch bei Fallback!)
895
- # ============================================================
896
- print("-" * 60)
897
- print("🔄 MASKE IMMER ZURÜCK AUF ORIGINALGRÖSSE TRANSFORMIEREN")
898
-
899
- # WICHTIG: Immer die richtigen Crop-Koordinaten verwenden
900
- temp_mask = Image.fromarray(mask_array).convert("L")
901
- print(f" Maskengröße auf Ausschnitt: {temp_mask.size}")
902
-
903
- # Maske auf ORIGINALBILDGRÖSSE bringen
904
- final_mask = Image.new("L", original_image.size, 0)
905
- print(f" Leere Maske in Originalgröße: {final_mask.size}")
906
-
907
- # Immer die gespeicherten Crop-Koordinaten verwenden
908
- if crop_x1 is not None and crop_y1 is not None:
909
- final_mask.paste(temp_mask, (crop_x1, crop_y1))
910
- print(f" Maskenposition im Original: ({crop_x1}, {crop_y1})")
911
- else:
912
- # Fallback: Zentrieren
913
- x_offset = (original_image.width - temp_mask.width) // 2
914
- y_offset = (original_image.height - temp_mask.height) // 2
915
- final_mask.paste(temp_mask, (x_offset, y_offset))
916
- print(f" ⚠️ Keine Crop-Koordinaten, zentriert: ({x_offset}, {y_offset})")
917
-
918
- mask_array = np.array(final_mask)
919
- print(f" ✅ Maske zurück auf Originalgröße skaliert: {mask_array.shape}")
920
-
921
- # Bild-Referenz zurücksetzen
922
- image = original_image
923
- print(f" 🔄 Bild-Referenz wieder auf Original gesetzt: {image.size}")
924
-
925
- # ============================================================
926
- # ABSCHLIESSENDE STATISTIK FÜR GESICHTSMODUS
927
- # ============================================================
928
- if mode == "face_only_change":
929
- original_face_area = original_bbox_size[0] * original_bbox_size[1]
930
- coverage_ratio = white_pixels / original_face_area if original_face_area > 0 else 0
931
- print(f" 👤 GESICHTSABDECKUNG: {coverage_ratio:.1%} der ursprünglichen BBox")
932
-
933
- # Warnungen basierend auf Abdeckung
934
- if coverage_ratio < 0.7:
935
- print(f" ⚠️ WARNUNG: Geringe Gesichtsabdeckung ({coverage_ratio:.1%})")
936
- elif coverage_ratio > 1.3:
937
- print(f" ⚠️ WARNUNG: Sehr hohe Gesichtsabdeckung ({coverage_ratio:.1%})")
938
- elif 0.8 <= coverage_ratio <= 1.2:
939
- print(f" ✅ OPTIMALE Gesichtsabdeckung ({coverage_ratio:.1%})")
940
 
941
  # ============================================================
942
  # FINALE AUSGABE FÜR GESICHTSMODUS
@@ -950,19 +963,7 @@ if mode == "face_only_change" and crop_size is not None:
950
 
951
 
952
 
953
- elif mode == "face_only_change":
954
- print("-" * 60)
955
- print("👤 MODUS: FACE_ONLY_CHANGE")
956
- print("-" * 60)
957
-
958
- # ... existierende face_only_change Logik hier komplett ...
959
- # (wird aus dem Original übernommen, nicht verändert)
960
-
961
- # WICHTIG: Du musst den face_only_change Code hier einfügen
962
- # von Zeile ~252 bis ~650 aus dem Original
963
-
964
- # Beispiel-Struktur (vereinfacht):
965
- # Crop, Punkte setzen, spezielle Gesichtsheuristik etc.
966
 
967
  # Am Ende:
968
  mask = Image.new("L", (512, 512), 128) # Platzhalter
 
730
  # Basis-Statistiken für jede Maske
731
  mask_binary = (mask_np > 0.5).astype(np.uint8)
732
  mask_area = np.sum(mask_binary)
733
+ print(f" Maske {i+1}: Größe={mask_area:,} Pixel, Max-Konfidenz={mask_np.max():.3f}")
734
 
735
+ # ============================================================
736
+ # HEURISTIK
737
+ # ============================================================
738
+ print("🤔 SCHRITT 6: MASKENAUSWAHL MIT MODUS-SPEZIFISCHER HEURISTIK")
739
+
740
+ bbox_center = ((x1 + x2) // 2, (y1 + y2) // 2)
741
+ bbox_area = (x2 - x1) * (y2 - y1)
742
+ print(f" Erwartetes BBox-Zentrum: {bbox_center}")
743
+ print(f" Erwartete BBox-Fläche: {bbox_area:,} Pixel")
744
+
745
+ best_mask_idx = 0
746
+ best_score = -1
747
+
748
+ for i, mask_np in enumerate(all_masks):
749
+ mask_max = mask_np.max()
750
+
751
+ # Grundlegende Filterung
752
+ if mask_max < 0.3:
753
+ print(f" ❌ Maske {i+1}: Zu niedrige Konfidenz ({mask_max:.3f}), überspringe")
754
+ continue
755
+
756
+ # Adaptiver Threshold
757
+ adaptive_threshold = max(0.3, mask_max * 0.7)
758
+ mask_binary = (mask_np > adaptive_threshold).astype(np.uint8)
759
+
760
+ if np.sum(mask_binary) == 0:
761
+ print(f" ❌ Maske {i+1}: Keine Pixel nach Threshold {adaptive_threshold:.3f}")
762
+ continue
763
+
764
+ mask_area_pixels = np.sum(mask_binary)
765
 
766
 
767
+ # ============================================================
768
+ # SPEZIALHEURISTIK
769
+ # ============================================================
770
 
771
+ print(f" 🔍 Analysiere Maske {i+1} mit GESICHTS-HEURISTIK")
772
+
773
+ # 1. FLÄCHENBASIERTE BEWERTUNG (40%)
774
+ area_ratio = mask_area_pixels / bbox_area
775
+ print(f" 📐 Flächen-Ratio: {area_ratio:.3f} ({mask_area_pixels:,} / {bbox_area:,} Pixel)")
776
+
777
+ # Optimale Kopfgröße: 80-120% der BBox
778
+ if area_ratio < 0.6:
779
+ print(f" ⚠️ Fläche zu klein für Kopf (<60% der BBox)")
780
+ area_score = area_ratio * 0.5 # Stark bestrafen
781
+ elif area_ratio > 1.5:
782
+ print(f" ⚠️ Fläche zu groß für Kopf (>150% der BBox)")
783
+ area_score = 2.0 - area_ratio # Linear bestrafen
784
+ elif 0.8 <= area_ratio <= 1.2:
785
+ area_score = 1.0 # Perfekte Größe
786
+ print(f" ✅ Perfekte Kopfgröße (80-120% der BBox)")
787
+ else:
788
+ # Sanfte Abweichung
789
+ area_score = 1.0 - abs(area_ratio - 1.0) * 0.5
790
+
791
+ # 2. KOMPAKTHEIT/SOLIDITÄT (30%)
792
+ labeled_mask = measure.label(mask_binary)
793
+ regions = measure.regionprops(labeled_mask)
794
+
795
+ if len(regions) == 0:
796
+ compactness_score = 0.1
797
+ print(f" ❌ Keine zusammenhängenden Regionen gefunden")
798
+ else:
799
+ # Größte Region finden (sollte der Kopf sein)
800
+ largest_region = max(regions, key=lambda r: r.area)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
801
 
802
+ # Solidität = Fläche / konvexe Hüllenfläche
803
+ solidity = largest_region.solidity if hasattr(largest_region, 'solidity') else 0.7
804
 
805
+ # Exzentrizität (wie elliptisch) - Köpfe sind tendenziell elliptisch
806
+ eccentricity = largest_region.eccentricity if hasattr(largest_region, 'eccentricity') else 0.5
807
 
808
+ # Perfekt runde Formen (Kreis) sind 0, Linie wäre 1
809
+ # Köpfe haben typischerweise 0.5-0.8
810
+ if 0.4 <= eccentricity <= 0.9:
811
+ eccentricity_score = 1.0 - abs(eccentricity - 0.65) * 2
812
+ else:
813
+ eccentricity_score = 0.2
814
 
815
+ compactness_score = (solidity * 0.6 + eccentricity_score * 0.4)
816
+ print(f" 🎯 Kompaktheits-Analyse:")
817
+ print(f" • Solidität (Fläche/Konvex): {solidity:.3f}")
818
+ print(f" • Exzentrizität (Form): {eccentricity:.3f}")
819
+ print(f" • Kompaktheits-Score: {compactness_score:.3f}")
820
+
821
+ # 3. BBOX-ÜBERLAPPUNG (20%)
822
+ bbox_mask = np.zeros((image.height, image.width), dtype=np.uint8)
823
+ bbox_mask[y1:y2, x1:x2] = 1
824
+ overlap = np.sum(mask_binary & bbox_mask)
825
+ bbox_overlap_ratio = overlap / mask_area_pixels if mask_area_pixels > 0 else 0
826
+
827
+ # Für Kopf: Sollte großteils in BBox sein (mind. 70%)
828
+ if bbox_overlap_ratio >= 0.7:
829
+ bbox_score = 1.0
830
+ print(f" ✅ Hohe BBox-Überlappung: {bbox_overlap_ratio:.3f} ({overlap:,} Pixel)")
831
+ elif bbox_overlap_ratio >= 0.5:
832
+ bbox_score = bbox_overlap_ratio * 1.2
833
+ print(f" ⚠️ Mittlere BBox-Überlappung: {bbox_overlap_ratio:.3f}")
834
+ else:
835
+ bbox_score = bbox_overlap_ratio * 0.8
836
+ print(f" ❌ Geringe BBox-Überlappung: {bbox_overlap_ratio:.3f}")
837
+
838
+ # SAM-KONFIDENZ (10%)
839
+ confidence_score = mask_max
840
+
841
+ # GESAMTSCORE für Gesicht
842
+ score = (
843
+ area_score * 0.4 + # 40% Flächenpassung
844
+ compactness_score * 0.3 + # 30% Kompaktheit
845
+ bbox_score * 0.2 + # 20% BBox-Überlappung
846
+ confidence_score * 0.1 # 10% Konfidenz
847
+ )
848
+
849
+ print(f" 📊 GESICHTS-SCORES für Maske {i+1}:")
850
+ print(f" • Flächen-Score: {area_score:.3f}")
851
+ print(f" • Kompaktheits-Score: {compactness_score:.3f}")
852
+ print(f" • BBox-Überlappungs-Score: {bbox_score:.3f}")
853
+ print(f" • Konfidenz-Score: {confidence_score:.3f}")
854
+ print(f" • GESAMTSCORE: {score:.3f}")
855
+
856
+
857
+ # ============================================================
858
+ # THRESHOLD-BESTIMMUNG
859
+ # ============================================================
860
+ # Spezieller Threshold für Gesichter
861
+ if max_val < 0.5:
862
+ dynamic_threshold = 0.25
863
+ print(f" ⚠️ SAM ist unsicher für Gesicht (max_val={max_val:.3f} < 0.5)")
864
+ elif max_val < 0.8:
865
+ dynamic_threshold = max_val * 0.65 # Mittlerer Threshold
866
+ print(f" ℹ️ SAM ist mäßig sicher für Gesicht (max_val={max_val:.3f})")
867
+ else:
868
+ dynamic_threshold = max_val * 0.75 # Hoher Threshold
869
+ print(f" ✅ SAM ist sicher für Gesicht (max_val={max_val:.3f} >= 0.8)")
870
+
871
+ print(f" 🎯 Gesichts-Threshold: {dynamic_threshold:.3f}")
872
 
873
+ # ============================================================
874
+ # POSTPROCESSING
875
+ # ============================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
876
 
877
+ print("👤 GESICHTS-SPEZIFISCHES POSTPROCESSING")
 
 
 
 
878
 
879
+ # 1. Größte zusammenhängende Komponente finden
880
+ labeled_array, num_features = ndimage.label(mask_array)
881
 
882
+ if num_features > 0:
883
+ print(f" 🔍 Gefundene Komponenten: {num_features}")
884
 
885
+ sizes = ndimage.sum(mask_array, labeled_array, range(1, num_features + 1))
886
+ largest_component_idx = np.argmax(sizes) + 1
887
 
888
+ print(f" 👑 Größte Komponente: Nr. {largest_component_idx} mit {sizes[largest_component_idx-1]:,} Pixel")
889
 
890
+ # NUR die größte Komponente behalten (der Kopf)
891
+ mask_array = np.where(labeled_array == largest_component_idx, mask_array, 0)
892
 
893
+ # MORPHOLOGISCHE OPERATIONEN FÜR SAUBEREN KOPF
894
+ print(" ⚙️ Morphologische Operationen für sauberen Kopf")
895
 
896
+ # Zuerst CLOSE, um kleine Löcher im Kopf zu füllen
897
+ kernel_close = np.ones((7, 7), np.uint8)
898
+ mask_array = cv2.morphologyEx(mask_array, cv2.MORPH_CLOSE, kernel_close, iterations=1)
899
+ print(" • MORPH_CLOSE (7x7) - Löcher im Kopf füllen")
900
 
901
+ # Dann OPEN, um kleine Ausreißer zu entfernen
902
+ kernel_open = np.ones((5, 5), np.uint8)
903
+ mask_array = cv2.morphologyEx(mask_array, cv2.MORPH_OPEN, kernel_open, iterations=1)
904
+ print(" • MORPH_OPEN (5x5) - Rauschen entfernen")
905
+
906
+ # ============================================================
907
+ # KRITISCH: MASKE IMMER ZURÜCK AUF ORIGINALGRÖSSE (auch bei Fallback!)
908
+ # ============================================================
909
+ print("-" * 60)
910
+ print("🔄 MASKE IMMER ZURÜCK AUF ORIGINALGRÖSSE TRANSFORMIEREN")
911
+
912
+ # WICHTIG: Immer die richtigen Crop-Koordinaten verwenden
913
+ temp_mask = Image.fromarray(mask_array).convert("L")
914
+ print(f" Maskengröße auf Ausschnitt: {temp_mask.size}")
915
+
916
+ # Maske auf ORIGINALBILDGRÖSSE bringen
917
+ final_mask = Image.new("L", original_image.size, 0)
918
+ print(f" Leere Maske in Originalgröße: {final_mask.size}")
919
+
920
+ # Immer die gespeicherten Crop-Koordinaten verwenden
921
+ if crop_x1 is not None and crop_y1 is not None:
922
+ final_mask.paste(temp_mask, (crop_x1, crop_y1))
923
+ print(f" Maskenposition im Original: ({crop_x1}, {crop_y1})")
924
+ else:
925
+ # Fallback: Zentrieren
926
+ x_offset = (original_image.width - temp_mask.width) // 2
927
+ y_offset = (original_image.height - temp_mask.height) // 2
928
+ final_mask.paste(temp_mask, (x_offset, y_offset))
929
+ print(f" ⚠️ Keine Crop-Koordinaten, zentriert: ({x_offset}, {y_offset})")
930
+
931
+ mask_array = np.array(final_mask)
932
+ print(f" ✅ Maske zurück auf Originalgröße skaliert: {mask_array.shape}")
933
+
934
+ # Bild-Referenz zurücksetzen
935
+ image = original_image
936
+ print(f" 🔄 Bild-Referenz wieder auf Original gesetzt: {image.size}")
937
+
938
+ # ============================================================
939
+ # ABSCHLIESSENDE STATISTIK
940
+ # ============================================================
941
+
942
+ original_face_area = original_bbox_size[0] * original_bbox_size[1]
943
+ coverage_ratio = white_pixels / original_face_area if original_face_area > 0 else 0
944
+ print(f" 👤 GESICHTSABDECKUNG: {coverage_ratio:.1%} der ursprünglichen BBox")
945
+
946
+ # Warnungen basierend auf Abdeckung
947
+ if coverage_ratio < 0.7:
948
+ print(f" ⚠️ WARNUNG: Geringe Gesichtsabdeckung ({coverage_ratio:.1%})")
949
+ elif coverage_ratio > 1.3:
950
+ print(f" ⚠️ WARNUNG: Sehr hohe Gesichtsabdeckung ({coverage_ratio:.1%})")
951
+ elif 0.8 <= coverage_ratio <= 1.2:
952
+ print(f" ✅ OPTIMALE Gesichtsabdeckung ({coverage_ratio:.1%})")
953
 
954
  # ============================================================
955
  # FINALE AUSGABE FÜR GESICHTSMODUS
 
963
 
964
 
965
 
966
+
 
 
 
 
 
 
 
 
 
 
 
 
967
 
968
  # Am Ende:
969
  mask = Image.new("L", (512, 512), 128) # Platzhalter