Astridkraft commited on
Commit
eb80db4
·
verified ·
1 Parent(s): fa3db93

Update controlnet_module.py

Browse files
Files changed (1) hide show
  1. controlnet_module.py +53 -78
controlnet_module.py CHANGED
@@ -103,11 +103,10 @@ class ControlNetProcessor:
103
  print(f"⚠️ Fehler beim Glätten der Maske: {e}")
104
  return mask_array
105
 
106
-
107
  def create_sam_mask(self, image, bbox_coords, mode):
108
  """
109
  ERWEITERTE Funktion: Erstellt präzise Maske mit SAM 2
110
- Sonderbehandlung für face_only_change: Arbeitet auf Bildausschnitt
111
  """
112
  try:
113
  print("#" * 80)
@@ -170,7 +169,7 @@ class ControlNetProcessor:
170
  print(f" 📏 BBox Dimensionen: {bbox_width} × {bbox_height} px")
171
  print(f" 📐 Maximale BBox-Dimension: {bbox_max_dim} px")
172
 
173
- # ERHÖHT: Crop-Größe berechnen (BBox × 2.5 für mehr Kontext)
174
  crop_size = int(bbox_max_dim * 2.5)
175
  print(f" 🎯 Ziel-Crop-Größe: {crop_size} × {crop_size} px (BBox × 2.5)")
176
 
@@ -265,41 +264,18 @@ class ControlNetProcessor:
265
  print(f" BBox Koordinaten: [{x1}, {y1}, {x2}, {y2}]")
266
  print(f" BBox Dimensionen: {x2-x1}px × {y2-y1}px")
267
 
268
- # 3. Vorbereitung für SAM2
269
  print("-" * 60)
270
  print("🖼️ BILDAUFBEREITUNG FÜR SAM 2")
271
  image_np = np.array(image.convert("RGB"))
272
 
273
- # ============================================================
274
- # NEU: ERWEITERTE SAM-EINGABE FÜR GESICHTSMODUS
275
- # ============================================================
276
- print("🎯 SCHRITT 4-5: ERWEITERTE SAM-PROMPTING")
277
-
278
- bbox_width = x2 - x1
279
- bbox_height = y2 - y1
280
-
281
- # Für Gesichtsmodus: Verstärkte BBox-Prompts
282
- if mode == "face_only_change":
283
- # 1. Haupt-BBox (ursprüngliche Koordinaten)
284
- input_boxes = [[[x1, y1, x2, y2]]]
285
-
286
- # 2. ERWEITERTE BBox für Gesichtskontext (15% größer)
287
- expand_factor = 0.15
288
- expanded_x1 = max(0, int(x1 - bbox_width * expand_factor))
289
- expanded_y1 = max(0, int(y1 - bbox_height * expand_factor))
290
- expanded_x2 = min(image.width, int(x2 + bbox_width * expand_factor))
291
- expanded_y2 = min(image.height, int(y2 + bbox_height * expand_factor))
292
-
293
- input_boxes.append([[expanded_x1, expanded_y1, expanded_x2, expanded_y2]])
294
-
295
- print(f" Haupt-BBox: [{x1}, {y1}, {x2}, {y2}]")
296
- print(f" Erweiterte BBox: [{expanded_x1}, {expanded_y1}, {expanded_x2}, {expanded_y2}]")
297
- print(f" Anzahl BBox-Prompts: {len(input_boxes)}")
298
- else:
299
- # Standard für andere Modi
300
- input_boxes = [[[x1, y1, x2, y2]]]
301
- print(f" Standard-BBox: [{x1}, {y1}, {x2}, {y2}]")
302
 
 
303
  print(" Verarbeite Bild mit SAM 2 Processor...")
304
  inputs = self.sam_processor(
305
  image_np,
@@ -555,7 +531,7 @@ class ControlNetProcessor:
555
  if mode == "face_only_change":
556
  print("👤 GESICHTS-SPEZIFISCHES POSTPROCESSING")
557
 
558
- # 1. Größte zusammenhängende Komponente finden (sollte der Kopf sein)
559
  labeled_array, num_features = ndimage.label(mask_array)
560
 
561
  if num_features > 0:
@@ -569,37 +545,7 @@ class ControlNetProcessor:
569
  # NUR die größte Komponente behalten (der Kopf)
570
  mask_array = np.where(labeled_array == largest_component_idx, mask_array, 0)
571
 
572
- # 2. FORMBASIERTE OPTIMIERUNG FÜR KOPF
573
- print(" 🎯 Formbasierte Optimierung für Kopf")
574
-
575
- # Hole die Region-Eigenschaften für die größte Komponente
576
- labeled_single = np.where(labeled_array == largest_component_idx, 1, 0).astype(np.uint8)
577
- regions = measure.regionprops(labeled_single)
578
-
579
- if regions:
580
- region = regions[0]
581
-
582
- # Erweiterte Bounding Box für Kopf (etwas größer)
583
- minr, minc, maxr, maxc = region.bbox
584
- head_bbox_height = maxr - minr
585
- head_bbox_width = maxc - minc
586
-
587
- # Kopf sollte etwa 1.2-1.5 mal höher als breit sein
588
- aspect_ratio = head_bbox_height / head_bbox_width if head_bbox_width > 0 else 1.0
589
-
590
- print(f" 📏 Kopf-BBox: {head_bbox_width}×{head_bbox_height} (Ratio: {aspect_ratio:.2f})")
591
-
592
- # Wenn der Kopf zu "flach" ist (z.B. nur Haare), vertikal erweitern
593
- if aspect_ratio < 1.0 and head_bbox_height < bbox_height * 0.8:
594
- print(f" ⬇️ Kopf zu flach, vertikal erweitern")
595
- expand_y = int((bbox_height * 0.8 - head_bbox_height) / 2)
596
- minr = max(0, minr - expand_y)
597
- maxr = min(mask_array.shape[0], maxr + expand_y)
598
-
599
- # Fülle den erweiterten Bereich
600
- mask_array[minr:maxr, minc:maxc] = 255
601
-
602
- # 3. MORPHOLOGISCHE OPERATIONEN FÜR SAUBEREN KOPF
603
  print(" ⚙️ Morphologische Operationen für sauberen Kopf")
604
 
605
  # Zuerst CLOSE, um kleine Löcher im Kopf zu füllen
@@ -611,33 +557,49 @@ class ControlNetProcessor:
611
  kernel_open = np.ones((5, 5), np.uint8)
612
  mask_array = cv2.morphologyEx(mask_array, cv2.MORPH_OPEN, kernel_open, iterations=1)
613
  print(" • MORPH_OPEN (5x5) - Rauschen entfernen")
614
-
615
- # Sanfte Glättung der Kanten
616
- mask_array = cv2.GaussianBlur(mask_array, (5, 5), 1.0)
617
- mask_array = (mask_array > 127).astype(np.uint8) * 255
618
- print(" • GaussianBlur + Re-Threshold - Glatte Kanten")
619
 
620
- # 4. MASKE ZURÜCK AUF ORIGINALGRÖSSE (nur für face_only_change)
 
 
621
  print("-" * 60)
622
- print("🔄 MASKE VOM AUSSCHNITT ZURÜCK AUF ORIGINALGRÖSSE")
623
 
 
624
  temp_mask = Image.fromarray(mask_array).convert("L")
625
  print(f" Maskengröße auf Ausschnitt: {temp_mask.size}")
626
 
 
627
  final_mask = Image.new("L", original_image.size, 0)
628
  print(f" Leere Maske in Originalgröße: {final_mask.size}")
629
 
630
- final_mask.paste(temp_mask, (crop_x1, crop_y1))
631
- print(f" Maskenposition im Original: ({crop_x1}, {crop_y1})")
 
 
 
 
 
 
 
 
632
 
633
  mask_array = np.array(final_mask)
634
  print(f" ✅ Maske zurück auf Originalgröße skaliert: {mask_array.shape}")
635
 
 
636
  image = original_image
637
  print(f" 🔄 Bild-Referenz wieder auf Original gesetzt: {image.size}")
638
 
639
  elif mode == "focus_change":
640
  print("🎯 FOCUS-CHANGE POSTPROCESSING")
 
 
 
 
 
 
 
 
641
  mask_array = mask_array.copy()
642
 
643
  # Größte weiße Komponente behalten (Person)
@@ -655,6 +617,13 @@ class ControlNetProcessor:
655
 
656
  elif mode == "environment_change":
657
  print("🌳 ENVIRONMENT-CHANGE POSTPROCESSING")
 
 
 
 
 
 
 
658
  mask_array = 255 - mask_array # Invertiere Maske
659
  print(" ✅ Maske invertiert (Person schwarz, Hintergrund weiß)")
660
 
@@ -682,10 +651,8 @@ class ControlNetProcessor:
682
  # Warnungen basierend auf Abdeckung
683
  if coverage_ratio < 0.7:
684
  print(f" ⚠️ WARNUNG: Geringe Gesichtsabdeckung ({coverage_ratio:.1%})")
685
- print(f" 💡 Tipp: BBox könnte zu groß sein oder SAM erkennt Gesicht nicht vollständig")
686
  elif coverage_ratio > 1.3:
687
  print(f" ⚠️ WARNUNG: Sehr hohe Gesichtsabdeckung ({coverage_ratio:.1%})")
688
- print(f" 💡 Tipp: Maske könnte zu viel Hintergrund enthalten")
689
  elif 0.8 <= coverage_ratio <= 1.2:
690
  print(f" ✅ OPTIMALE Gesichtsabdeckung ({coverage_ratio:.1%})")
691
 
@@ -711,9 +678,17 @@ class ControlNetProcessor:
711
  print(f"Fehler: {str(e)[:200]}")
712
  import traceback
713
  traceback.print_exc()
 
 
714
  print("ℹ️ Fallback auf rechteckige Maske")
715
- return self._create_rectangular_mask(image, bbox_coords, mode)
716
-
 
 
 
 
 
 
717
 
718
  def _create_rectangular_mask(self, image, bbox_coords, mode):
719
  """Fallback: Erstellt rechteckige Maske"""
 
103
  print(f"⚠️ Fehler beim Glätten der Maske: {e}")
104
  return mask_array
105
 
 
106
  def create_sam_mask(self, image, bbox_coords, mode):
107
  """
108
  ERWEITERTE Funktion: Erstellt präzise Maske mit SAM 2
109
+ Korrigierte Version für face_only_change mit einzelner BBox
110
  """
111
  try:
112
  print("#" * 80)
 
169
  print(f" 📏 BBox Dimensionen: {bbox_width} × {bbox_height} px")
170
  print(f" 📐 Maximale BBox-Dimension: {bbox_max_dim} px")
171
 
172
+ # Crop-Größe berechnen (BBox × 2.5)
173
  crop_size = int(bbox_max_dim * 2.5)
174
  print(f" 🎯 Ziel-Crop-Größe: {crop_size} × {crop_size} px (BBox × 2.5)")
175
 
 
264
  print(f" BBox Koordinaten: [{x1}, {y1}, {x2}, {y2}]")
265
  print(f" BBox Dimensionen: {x2-x1}px × {y2-y1}px")
266
 
267
+ # 3. Vorbereitung für SAM2 - WICHTIG: NUR EINE BBOX
268
  print("-" * 60)
269
  print("🖼️ BILDAUFBEREITUNG FÜR SAM 2")
270
  image_np = np.array(image.convert("RGB"))
271
 
272
+ # KORREKTUR: Immer nur eine BBox verwenden (SAM 2 erwartet genau 1)
273
+ input_boxes = [[[x1, y1, x2, y2]]]
274
+ print(f" Konvertiere Bild zu NumPy Array: {image_np.shape}")
275
+ print(f" Erstelle EINZIGE Input Box: {input_boxes}")
276
+ print(" ℹ️ SAM 2 erwartet genau eine BBox pro Vorhersage")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
+ print("🎯 SCHRITT 4-5: SAM MIT BOX-PROMPT")
279
  print(" Verarbeite Bild mit SAM 2 Processor...")
280
  inputs = self.sam_processor(
281
  image_np,
 
531
  if mode == "face_only_change":
532
  print("👤 GESICHTS-SPEZIFISCHES POSTPROCESSING")
533
 
534
+ # 1. Größte zusammenhängende Komponente finden
535
  labeled_array, num_features = ndimage.label(mask_array)
536
 
537
  if num_features > 0:
 
545
  # NUR die größte Komponente behalten (der Kopf)
546
  mask_array = np.where(labeled_array == largest_component_idx, mask_array, 0)
547
 
548
+ # 2. MORPHOLOGISCHE OPERATIONEN FÜR SAUBEREN KOPF
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
549
  print(" ⚙️ Morphologische Operationen für sauberen Kopf")
550
 
551
  # Zuerst CLOSE, um kleine Löcher im Kopf zu füllen
 
557
  kernel_open = np.ones((5, 5), np.uint8)
558
  mask_array = cv2.morphologyEx(mask_array, cv2.MORPH_OPEN, kernel_open, iterations=1)
559
  print(" • MORPH_OPEN (5x5) - Rauschen entfernen")
 
 
 
 
 
560
 
561
+ # ============================================================
562
+ # KRITISCH: MASKE IMMER ZURÜCK AUF ORIGINALGRÖSSE (auch bei Fallback!)
563
+ # ============================================================
564
  print("-" * 60)
565
+ print("🔄 MASKE IMMER ZURÜCK AUF ORIGINALGRÖSSE TRANSFORMIEREN")
566
 
567
+ # WICHTIG: Immer die richtigen Crop-Koordinaten verwenden
568
  temp_mask = Image.fromarray(mask_array).convert("L")
569
  print(f" Maskengröße auf Ausschnitt: {temp_mask.size}")
570
 
571
+ # Maske auf ORIGINALBILDGRÖSSE bringen
572
  final_mask = Image.new("L", original_image.size, 0)
573
  print(f" Leere Maske in Originalgröße: {final_mask.size}")
574
 
575
+ # Immer die gespeicherten Crop-Koordinaten verwenden
576
+ if crop_x1 is not None and crop_y1 is not None:
577
+ final_mask.paste(temp_mask, (crop_x1, crop_y1))
578
+ print(f" Maskenposition im Original: ({crop_x1}, {crop_y1})")
579
+ else:
580
+ # Fallback: Zentrieren
581
+ x_offset = (original_image.width - temp_mask.width) // 2
582
+ y_offset = (original_image.height - temp_mask.height) // 2
583
+ final_mask.paste(temp_mask, (x_offset, y_offset))
584
+ print(f" ⚠️ Keine Crop-Koordinaten, zentriert: ({x_offset}, {y_offset})")
585
 
586
  mask_array = np.array(final_mask)
587
  print(f" ✅ Maske zurück auf Originalgröße skaliert: {mask_array.shape}")
588
 
589
+ # Bild-Referenz zurücksetzen
590
  image = original_image
591
  print(f" 🔄 Bild-Referenz wieder auf Original gesetzt: {image.size}")
592
 
593
  elif mode == "focus_change":
594
  print("🎯 FOCUS-CHANGE POSTPROCESSING")
595
+
596
+ # Für focus_change: Originalbildgröße beibehalten
597
+ if image.size != original_image.size:
598
+ print(f" ⚠️ Bildgröße angepasst: {image.size} → {original_image.size}")
599
+ temp_mask = Image.fromarray(mask_array).convert("L")
600
+ temp_mask = temp_mask.resize(original_image.size, Image.Resampling.NEAREST)
601
+ mask_array = np.array(temp_mask)
602
+
603
  mask_array = mask_array.copy()
604
 
605
  # Größte weiße Komponente behalten (Person)
 
617
 
618
  elif mode == "environment_change":
619
  print("🌳 ENVIRONMENT-CHANGE POSTPROCESSING")
620
+
621
+ # Für environment_change: Originalbildgröße beibehalten
622
+ if image.size != original_image.size:
623
+ temp_mask = Image.fromarray(mask_array).convert("L")
624
+ temp_mask = temp_mask.resize(original_image.size, Image.Resampling.NEAREST)
625
+ mask_array = np.array(temp_mask)
626
+
627
  mask_array = 255 - mask_array # Invertiere Maske
628
  print(" ✅ Maske invertiert (Person schwarz, Hintergrund weiß)")
629
 
 
651
  # Warnungen basierend auf Abdeckung
652
  if coverage_ratio < 0.7:
653
  print(f" ⚠️ WARNUNG: Geringe Gesichtsabdeckung ({coverage_ratio:.1%})")
 
654
  elif coverage_ratio > 1.3:
655
  print(f" ⚠️ WARNUNG: Sehr hohe Gesichtsabdeckung ({coverage_ratio:.1%})")
 
656
  elif 0.8 <= coverage_ratio <= 1.2:
657
  print(f" ✅ OPTIMALE Gesichtsabdeckung ({coverage_ratio:.1%})")
658
 
 
678
  print(f"Fehler: {str(e)[:200]}")
679
  import traceback
680
  traceback.print_exc()
681
+
682
+ # WICHTIG: Im Fallback immer die richtige Größe zurückgeben
683
  print("ℹ️ Fallback auf rechteckige Maske")
684
+ fallback_mask = self._create_rectangular_mask(original_image, original_bbox, mode)
685
+
686
+ # Sicherstellen, dass die Maske die richtige Größe hat
687
+ if fallback_mask.size != original_image.size:
688
+ print(f" ⚠️ Fallback-Maske angepasst: {fallback_mask.size} → {original_image.size}")
689
+ fallback_mask = fallback_mask.resize(original_image.size, Image.Resampling.NEAREST)
690
+
691
+ return fallback_mask
692
 
693
  def _create_rectangular_mask(self, image, bbox_coords, mode):
694
  """Fallback: Erstellt rechteckige Maske"""