Update controlnet_module.py
Browse files- controlnet_module.py +101 -14
controlnet_module.py
CHANGED
|
@@ -104,8 +104,8 @@ class ControlNetProcessor:
|
|
| 104 |
|
| 105 |
def create_sam_mask(self, image, bbox_coords, mode):
|
| 106 |
"""
|
| 107 |
-
Erstellt präzise Maske mit SAM 2
|
| 108 |
-
|
| 109 |
"""
|
| 110 |
try:
|
| 111 |
print("#" * 80)
|
|
@@ -127,10 +127,62 @@ class ControlNetProcessor:
|
|
| 127 |
|
| 128 |
# 2. Validiere BBox
|
| 129 |
x1, y1, x2, y2 = self._validate_bbox(image, bbox_coords)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
print("-" * 60)
|
| 131 |
-
print(f"📦 BOUNDING BOX DETAILS:")
|
| 132 |
-
print(f"
|
| 133 |
-
print(f"
|
| 134 |
print(f" BBox Dimensionen: {x2-x1}px × {y2-y1}px")
|
| 135 |
|
| 136 |
# 3. Vorbereitung für SAM2
|
|
@@ -174,7 +226,8 @@ class ControlNetProcessor:
|
|
| 174 |
print(f" Nach Sigmoid und CPU: {mask_np.shape}, Wertebereich: [{mask_np.min():.3f}, {mask_np.max():.3f}]")
|
| 175 |
|
| 176 |
mask_array = (mask_np > 0.5).astype(np.uint8) * 255
|
| 177 |
-
|
|
|
|
| 178 |
|
| 179 |
# 7. BEIDE MASKEN ERSTELLEN (vor Nachbearbeitung)
|
| 180 |
original_mask_array = mask_array.copy() # Person weiß (255), Hintergrund schwarz (0)
|
|
@@ -253,21 +306,28 @@ class ControlNetProcessor:
|
|
| 253 |
print(" ✅ Focus-Modus: Person verändert, Hintergrund geschützt")
|
| 254 |
|
| 255 |
elif mode == "face_only_change":
|
| 256 |
-
print("👤 MODUS: NUR GESICHT ÄNDERN")
|
| 257 |
# Arbeite auf der ORIGINAL-Maske (Person weiß, Hintergrund schwarz)
|
| 258 |
mask_array = original_mask_array.copy()
|
| 259 |
print(" Arbeite auf originaler Maske (Person weiß, Hintergrund schwarz)")
|
| 260 |
|
| 261 |
# Größte weiße Komponente behalten (Person)
|
| 262 |
labeled_array, num_features = ndimage.label(mask_array)
|
| 263 |
-
print(f" Gefundene weiße Komponenten
|
| 264 |
|
| 265 |
-
if num_features >
|
| 266 |
sizes = ndimage.sum(mask_array, labeled_array, range(1, num_features + 1))
|
| 267 |
-
print(f" Größen der weißen Komponenten: {sizes}")
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
|
| 272 |
# Starke Erosion für präzises Gesicht
|
| 273 |
kernel = np.ones((3,3), np.uint8)
|
|
@@ -282,7 +342,33 @@ class ControlNetProcessor:
|
|
| 282 |
print(f" Wende GaussianBlur an (Kernel 3x3) für glatte Kanten...")
|
| 283 |
mask_array = cv2.GaussianBlur(mask_array, (3, 3), 0)
|
| 284 |
|
| 285 |
-
print(" ✅ Gesichts-Modus:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
|
| 287 |
# 9. Qualitätskontrolle und Statistik
|
| 288 |
white_pixels = np.sum(mask_array > 127)
|
|
@@ -303,6 +389,7 @@ class ControlNetProcessor:
|
|
| 303 |
print("#" * 80)
|
| 304 |
print(f"✅ SAM 2 SEGMENTIERUNG ABGESCHLOSSEN")
|
| 305 |
print(f"📐 Finale Maskengröße: {mask.size}")
|
|
|
|
| 306 |
print("#" * 80)
|
| 307 |
return mask
|
| 308 |
|
|
|
|
| 104 |
|
| 105 |
def create_sam_mask(self, image, bbox_coords, mode):
|
| 106 |
"""
|
| 107 |
+
ERWEITERTE Funktion: Erstellt präzise Maske mit SAM 2
|
| 108 |
+
Sonderbehandlung für face_only_change: Arbeitet auf Bildausschnitt
|
| 109 |
"""
|
| 110 |
try:
|
| 111 |
print("#" * 80)
|
|
|
|
| 127 |
|
| 128 |
# 2. Validiere BBox
|
| 129 |
x1, y1, x2, y2 = self._validate_bbox(image, bbox_coords)
|
| 130 |
+
|
| 131 |
+
# ============================================================
|
| 132 |
+
# SPEZIALBEHANDLUNG NUR FÜR face_only_change
|
| 133 |
+
# ============================================================
|
| 134 |
+
if mode == "face_only_change":
|
| 135 |
+
print("-" * 60)
|
| 136 |
+
print("👤 SPEZIALMODUS: NUR GESICHT - ERSTELLE FOKUSIERTEN AUSSCHNITT")
|
| 137 |
+
print("-" * 60)
|
| 138 |
+
|
| 139 |
+
# Originalbild und Koordinaten sichern
|
| 140 |
+
original_image = image
|
| 141 |
+
original_bbox = (x1, y1, x2, y2)
|
| 142 |
+
|
| 143 |
+
# Puffer um die BBox berechnen (20% der BBox-Größe, mindestens 50px)
|
| 144 |
+
padding_x = max(50, int((x2 - x1) * 0.2))
|
| 145 |
+
padding_y = max(50, int((y2 - y1) * 0.2))
|
| 146 |
+
|
| 147 |
+
# Ausschnitt-Koordinaten berechnen (innerhalb der Bildgrenzen)
|
| 148 |
+
crop_x1 = max(0, x1 - padding_x)
|
| 149 |
+
crop_y1 = max(0, y1 - padding_y)
|
| 150 |
+
crop_x2 = min(image.width, x2 + padding_x)
|
| 151 |
+
crop_y2 = min(image.height, y2 + padding_y)
|
| 152 |
+
|
| 153 |
+
print(f" 📐 Original-BBox: [{x1}, {y1}, {x2}, {y2}]")
|
| 154 |
+
print(f" 📏 Original-BBox Größe: {x2-x1} × {y2-y1} px")
|
| 155 |
+
print(f" 🔲 Ausschnitt-Bereich: [{crop_x1}, {crop_y1}, {crop_x2}, {crop_y2}]")
|
| 156 |
+
print(f" 📏 Ausschnitt-Größe: {crop_x2-crop_x1} × {crop_y2-crop_y1} px")
|
| 157 |
+
print(f" 📊 Puffer: {padding_x} × {padding_y} px")
|
| 158 |
+
|
| 159 |
+
# Bild ausschneiden
|
| 160 |
+
cropped_image = image.crop((crop_x1, crop_y1, crop_x2, crop_y2))
|
| 161 |
+
print(f" ✅ Ausschnitt erstellt: {cropped_image.size}")
|
| 162 |
+
|
| 163 |
+
# BBox-Koordinaten relativ zum Ausschnitt neu berechnen
|
| 164 |
+
rel_x1 = x1 - crop_x1
|
| 165 |
+
rel_y1 = y1 - crop_y1
|
| 166 |
+
rel_x2 = x2 - crop_x1
|
| 167 |
+
rel_y2 = y2 - crop_y1
|
| 168 |
+
|
| 169 |
+
print(f" 🎯 Relative BBox im Ausschnitt: [{rel_x1}, {rel_y1}, {rel_x2}, {rel_y2}]")
|
| 170 |
+
print(f" 📏 Relative BBox Größe: {rel_x2-rel_x1} × {rel_y2-rel_y1} px")
|
| 171 |
+
|
| 172 |
+
# Für SAM: Verwende Ausschnitt und relative Koordinaten
|
| 173 |
+
image = cropped_image
|
| 174 |
+
x1, y1, x2, y2 = rel_x1, rel_y1, rel_x2, rel_y2
|
| 175 |
+
|
| 176 |
+
print(" 🔄 SAM wird auf Ausschnitt (nicht Vollbild) ausgeführt")
|
| 177 |
+
|
| 178 |
+
# ============================================================
|
| 179 |
+
# GEMEINSAME SAM-LOGIK FÜR ALLE MODI
|
| 180 |
+
# (arbeitet auf `image` - bei face_only_change ist das der Ausschnitt)
|
| 181 |
+
# ============================================================
|
| 182 |
print("-" * 60)
|
| 183 |
+
print(f"📦 BOUNDING BOX DETAILS FÜR SAM:")
|
| 184 |
+
print(f" Bild-Größe für SAM: {image.size}")
|
| 185 |
+
print(f" BBox Koordinaten: [{x1}, {y1}, {x2}, {y2}]")
|
| 186 |
print(f" BBox Dimensionen: {x2-x1}px × {y2-y1}px")
|
| 187 |
|
| 188 |
# 3. Vorbereitung für SAM2
|
|
|
|
| 226 |
print(f" Nach Sigmoid und CPU: {mask_np.shape}, Wertebereich: [{mask_np.min():.3f}, {mask_np.max():.3f}]")
|
| 227 |
|
| 228 |
mask_array = (mask_np > 0.5).astype(np.uint8) * 255
|
| 229 |
+
unique_vals = np.unique(mask_array)
|
| 230 |
+
print(f" Nach Threshold (0.5): {mask_array.shape}, Unique Werte: {unique_vals}")
|
| 231 |
|
| 232 |
# 7. BEIDE MASKEN ERSTELLEN (vor Nachbearbeitung)
|
| 233 |
original_mask_array = mask_array.copy() # Person weiß (255), Hintergrund schwarz (0)
|
|
|
|
| 306 |
print(" ✅ Focus-Modus: Person verändert, Hintergrund geschützt")
|
| 307 |
|
| 308 |
elif mode == "face_only_change":
|
| 309 |
+
print("👤 MODUS: NUR GESICHT ÄNDERN (AUF AUSSCHNITT)")
|
| 310 |
# Arbeite auf der ORIGINAL-Maske (Person weiß, Hintergrund schwarz)
|
| 311 |
mask_array = original_mask_array.copy()
|
| 312 |
print(" Arbeite auf originaler Maske (Person weiß, Hintergrund schwarz)")
|
| 313 |
|
| 314 |
# Größte weiße Komponente behalten (Person)
|
| 315 |
labeled_array, num_features = ndimage.label(mask_array)
|
| 316 |
+
print(f" Gefundene weiße Komponenten auf AUSSCHNITT: {num_features}")
|
| 317 |
|
| 318 |
+
if num_features > 0:
|
| 319 |
sizes = ndimage.sum(mask_array, labeled_array, range(1, num_features + 1))
|
| 320 |
+
print(f" Größen der weißen Komponenten auf AUSSCHNITT: {sizes}")
|
| 321 |
+
|
| 322 |
+
if num_features > 1:
|
| 323 |
+
# WICHTIG: Für Gesicht nehmen wir die GRÖSSTE Komponente im AUSSCHNITT
|
| 324 |
+
# (Im Ausschnitt sollte das das Gesicht sein, nicht der Hintergrund)
|
| 325 |
+
largest_component = np.argmax(sizes) + 1
|
| 326 |
+
mask_array = np.where(labeled_array == largest_component, mask_array, 0)
|
| 327 |
+
print(f" ✅ Behalte größte Komponente im Ausschnitt ({num_features} Komponenten)")
|
| 328 |
+
print(f" 📊 Größe der behaltenen Komponente: {sizes[largest_component-1]:,} Pixel")
|
| 329 |
+
else:
|
| 330 |
+
print(f" ℹ️ Nur eine Komponente gefunden, behalte diese")
|
| 331 |
|
| 332 |
# Starke Erosion für präzises Gesicht
|
| 333 |
kernel = np.ones((3,3), np.uint8)
|
|
|
|
| 342 |
print(f" Wende GaussianBlur an (Kernel 3x3) für glatte Kanten...")
|
| 343 |
mask_array = cv2.GaussianBlur(mask_array, (3, 3), 0)
|
| 344 |
|
| 345 |
+
print(" ✅ Gesichts-Modus: Nachbearbeitung auf Ausschnitt abgeschlossen")
|
| 346 |
+
|
| 347 |
+
# ============================================================
|
| 348 |
+
# SPEZIALSCHRITT NUR FÜR face_only_change: MASKE ZURÜCKSKALIEREN
|
| 349 |
+
# ============================================================
|
| 350 |
+
print("-" * 60)
|
| 351 |
+
print("🔄 SPEZIALSCHRITT: MASKE VOM AUSSCHNITT ZURÜCK AUF ORIGINALGRÖSSE")
|
| 352 |
+
|
| 353 |
+
# Temporäre Maske aus dem Array erstellen
|
| 354 |
+
temp_mask = Image.fromarray(mask_array).convert("L")
|
| 355 |
+
print(f" Maskengröße auf Ausschnitt: {temp_mask.size}")
|
| 356 |
+
|
| 357 |
+
# Leere Maske in Originalbild-Größe erstellen
|
| 358 |
+
final_mask = Image.new("L", original_image.size, 0)
|
| 359 |
+
print(f" Leere Maske in Originalgröße: {final_mask.size}")
|
| 360 |
+
|
| 361 |
+
# Die segmentierte Maske an der richtigen Position im Originalbild platzieren
|
| 362 |
+
final_mask.paste(temp_mask, (crop_x1, crop_y1))
|
| 363 |
+
print(f" Maskenposition im Original: ({crop_x1}, {crop_y1})")
|
| 364 |
+
|
| 365 |
+
# Zurück zum mask_array konvertieren
|
| 366 |
+
mask_array = np.array(final_mask)
|
| 367 |
+
print(f" ✅ Maske zurück auf Originalgröße skaliert: {mask_array.shape}")
|
| 368 |
+
|
| 369 |
+
# Originalbild wiederherstellen für eventuelle spätere Verwendung
|
| 370 |
+
image = original_image
|
| 371 |
+
print(f" 🔄 Bild-Referenz wieder auf Original gesetzt: {image.size}")
|
| 372 |
|
| 373 |
# 9. Qualitätskontrolle und Statistik
|
| 374 |
white_pixels = np.sum(mask_array > 127)
|
|
|
|
| 389 |
print("#" * 80)
|
| 390 |
print(f"✅ SAM 2 SEGMENTIERUNG ABGESCHLOSSEN")
|
| 391 |
print(f"📐 Finale Maskengröße: {mask.size}")
|
| 392 |
+
print(f"🎛️ Verwendeter Modus: {mode}")
|
| 393 |
print("#" * 80)
|
| 394 |
return mask
|
| 395 |
|