Update sam_module.py
Browse files- 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 |
-
#
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
|
| 763 |
-
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
| 767 |
-
|
| 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 |
-
|
| 790 |
-
|
| 791 |
|
| 792 |
-
|
| 793 |
-
|
| 794 |
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
| 801 |
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 842 |
|
| 843 |
-
# ============================================================
|
| 844 |
-
#
|
| 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 |
-
|
| 867 |
-
|
| 868 |
|
| 869 |
-
|
| 870 |
-
|
| 871 |
|
| 872 |
-
|
| 873 |
-
|
| 874 |
|
| 875 |
-
|
| 876 |
|
| 877 |
-
|
| 878 |
-
|
| 879 |
|
| 880 |
-
|
| 881 |
-
|
| 882 |
|
| 883 |
-
|
| 884 |
-
|
| 885 |
-
|
| 886 |
-
|
| 887 |
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
|
| 891 |
-
|
| 892 |
-
|
| 893 |
-
|
| 894 |
-
|
| 895 |
-
|
| 896 |
-
|
| 897 |
-
|
| 898 |
-
|
| 899 |
-
|
| 900 |
-
|
| 901 |
-
|
| 902 |
-
|
| 903 |
-
|
| 904 |
-
|
| 905 |
-
|
| 906 |
-
|
| 907 |
-
|
| 908 |
-
|
| 909 |
-
|
| 910 |
-
|
| 911 |
-
|
| 912 |
-
|
| 913 |
-
|
| 914 |
-
|
| 915 |
-
|
| 916 |
-
|
| 917 |
-
|
| 918 |
-
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
|
| 922 |
-
|
| 923 |
-
|
| 924 |
-
|
| 925 |
-
# ============================================================
|
| 926 |
-
# ABSCHLIESSENDE STATISTIK
|
| 927 |
-
# ============================================================
|
| 928 |
-
|
| 929 |
-
|
| 930 |
-
|
| 931 |
-
|
| 932 |
-
|
| 933 |
-
|
| 934 |
-
|
| 935 |
-
|
| 936 |
-
|
| 937 |
-
|
| 938 |
-
|
| 939 |
-
|
| 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 |
-
|
| 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
|