Update app.py
Browse files
app.py
CHANGED
|
@@ -245,6 +245,198 @@ def scale_image_and_mask_together(image, mask, target_size=512, bbox_coords=None
|
|
| 245 |
|
| 246 |
|
| 247 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
# Composition Workflow nach Ausgabe ControlnetInpaint-Pipeline
|
| 249 |
def enhanced_composite_with_sam(original_image, inpaint_result, original_mask,
|
| 250 |
padding_info, bbox_coords, mode):
|
|
|
|
| 245 |
|
| 246 |
|
| 247 |
|
| 248 |
+
def enhanced_composite_with_sam(original_image, inpaint_result, original_mask,
|
| 249 |
+
padding_info, bbox_coords, mode):
|
| 250 |
+
"""
|
| 251 |
+
COMPOSITING MIT SAM-MASKEN (MASKE-BASIERT)
|
| 252 |
+
Berücksichtigt die präzisen/erweiterten Kanten der SAM-Maske
|
| 253 |
+
"""
|
| 254 |
+
print(f"🎨 Verbessertes Compositing für Modus: {mode}")
|
| 255 |
+
|
| 256 |
+
# Extrahiere Padding-Info
|
| 257 |
+
x_offset = padding_info['x_offset']
|
| 258 |
+
y_offset = padding_info['y_offset']
|
| 259 |
+
scaled_width = padding_info['scaled_width']
|
| 260 |
+
scaled_height = padding_info['scaled_height']
|
| 261 |
+
scale_factor = padding_info['scale_factor']
|
| 262 |
+
original_width = padding_info['original_width']
|
| 263 |
+
original_height = padding_info['original_height']
|
| 264 |
+
|
| 265 |
+
# ==============================================
|
| 266 |
+
# FALL 1: Bild war bereits 512×512 (keine Skalierung)
|
| 267 |
+
# ==============================================
|
| 268 |
+
if scale_factor == 1.0 and x_offset == 0 and y_offset == 0:
|
| 269 |
+
print(f"✅ FALL 1: Bild 512×512 - einfaches Compositing")
|
| 270 |
+
|
| 271 |
+
if mode == "environment_change":
|
| 272 |
+
# Umgebung ändern: SAM-Maske invertieren (Objekt schützen)
|
| 273 |
+
mask_inverted = Image.eval(original_mask, lambda x: 255 - x)
|
| 274 |
+
soft_mask = mask_inverted.filter(ImageFilter.GaussianBlur(5))
|
| 275 |
+
|
| 276 |
+
original_with_alpha = original_image.copy().convert("RGBA")
|
| 277 |
+
original_with_alpha.putalpha(soft_mask)
|
| 278 |
+
|
| 279 |
+
final_image = inpaint_result.copy().convert("RGBA")
|
| 280 |
+
final_image.paste(original_with_alpha, (0, 0), original_with_alpha)
|
| 281 |
+
return final_image.convert("RGB")
|
| 282 |
+
|
| 283 |
+
else:
|
| 284 |
+
# Focus/Face: Direktes Alpha-Compositing
|
| 285 |
+
soft_mask = original_mask.filter(ImageFilter.GaussianBlur(5))
|
| 286 |
+
|
| 287 |
+
inpaint_rgba = inpaint_result.convert("RGBA")
|
| 288 |
+
mask_alpha = soft_mask.convert("L")
|
| 289 |
+
inpaint_rgba.putalpha(mask_alpha)
|
| 290 |
+
|
| 291 |
+
original_rgba = original_image.convert("RGBA")
|
| 292 |
+
|
| 293 |
+
# Composite
|
| 294 |
+
final_image = Image.new("RGBA", original_image.size, (0, 0, 0, 0))
|
| 295 |
+
final_image.paste(original_rgba, (0, 0))
|
| 296 |
+
final_image.paste(inpaint_rgba, (0, 0), inpaint_rgba)
|
| 297 |
+
return final_image.convert("RGB")
|
| 298 |
+
|
| 299 |
+
# ==============================================
|
| 300 |
+
# FALL 2 & 3: Bild wurde skaliert - MASKE-BASIERTES COMPOSITING
|
| 301 |
+
# ==============================================
|
| 302 |
+
print(f"🔄 FALL 2/3: Bild skaliert - MASKE-BASIERTES COMPOSITING")
|
| 303 |
+
|
| 304 |
+
# 1. PADDING ENTFERNEN von 512×512 Ergebnis
|
| 305 |
+
downscaled_result = inpaint_result.crop(
|
| 306 |
+
(x_offset, y_offset, x_offset + scaled_width, y_offset + scaled_height)
|
| 307 |
+
)
|
| 308 |
+
|
| 309 |
+
# 2. AUF ORIGINALGRÖßE SKALIEREN (Bearbeitetes Bild)
|
| 310 |
+
new_background = downscaled_result.resize(
|
| 311 |
+
(original_width, original_height),
|
| 312 |
+
Image.Resampling.LANCZOS
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
# 3. SAM-MASKE FÜR KOMPOSITING VORBEREITEN
|
| 316 |
+
print(f"📐 SAM-Maske Größe: {original_mask.size}")
|
| 317 |
+
|
| 318 |
+
if mode == "environment_change":
|
| 319 |
+
# ==============================================
|
| 320 |
+
# MODUS: UMWELT ÄNDERN (Objekt bleibt original)
|
| 321 |
+
# ==============================================
|
| 322 |
+
print("🌳 Modus: Umwelt ändern mit SAM-Maske")
|
| 323 |
+
|
| 324 |
+
# Invertierte Maske für Objekterhalt
|
| 325 |
+
mask_inverted = Image.eval(original_mask, lambda x: 255 - x)
|
| 326 |
+
|
| 327 |
+
# Weiche Kanten für natürlichen Übergang
|
| 328 |
+
soft_mask = mask_inverted.filter(ImageFilter.GaussianBlur(5))
|
| 329 |
+
|
| 330 |
+
# Originalbild mit Alpha-Kanal
|
| 331 |
+
original_with_alpha = original_image.copy().convert("RGBA")
|
| 332 |
+
original_with_alpha.putalpha(soft_mask)
|
| 333 |
+
|
| 334 |
+
# Compositing
|
| 335 |
+
final_image = new_background.copy().convert("RGBA")
|
| 336 |
+
final_image.paste(original_with_alpha, (0, 0), original_with_alpha)
|
| 337 |
+
|
| 338 |
+
print(f"✅ Umwelt-Compositing abgeschlossen")
|
| 339 |
+
|
| 340 |
+
else:
|
| 341 |
+
# ==============================================
|
| 342 |
+
# MODUS: FOCUS oder GESICHT ÄNDERN (MASKE-BASIERT)
|
| 343 |
+
# ==============================================
|
| 344 |
+
mode_name = "Focus" if mode == "focus_change" else "Gesicht"
|
| 345 |
+
print(f"👤 Modus: {mode_name} ändern - MASKE-BASIERT")
|
| 346 |
+
|
| 347 |
+
# WICHTIG: MASKE-BASIERTES AUSSCHNEIDEN
|
| 348 |
+
# 3a. SAM-Maske auf 512px skalieren
|
| 349 |
+
mask_on_512 = original_mask.resize((512, 512), Image.Resampling.LANCZOS)
|
| 350 |
+
|
| 351 |
+
# 3b. Bounding Box der Maske auf 512px finden
|
| 352 |
+
mask_array = np.array(mask_on_512)
|
| 353 |
+
white_pixels = np.where(mask_array > 128)
|
| 354 |
+
|
| 355 |
+
if len(white_pixels[0]) == 0:
|
| 356 |
+
print("⚠️ Keine weißen Pixel in Maske → Fallback auf BBox")
|
| 357 |
+
if bbox_coords and all(c is not None for c in bbox_coords):
|
| 358 |
+
# Fallback: User-BBox verwenden
|
| 359 |
+
mask_bbox_512 = (
|
| 360 |
+
int(bbox_coords[0] * scale_factor) + x_offset,
|
| 361 |
+
int(bbox_coords[1] * scale_factor) + y_offset,
|
| 362 |
+
int(bbox_coords[2] * scale_factor) + x_offset,
|
| 363 |
+
int(bbox_coords[3] * scale_factor) + y_offset
|
| 364 |
+
)
|
| 365 |
+
else:
|
| 366 |
+
# Keine BBox → gesamtes Bild
|
| 367 |
+
final_image = new_background
|
| 368 |
+
return final_image.convert("RGB")
|
| 369 |
+
else:
|
| 370 |
+
# MASKE-BASIERTE BBOX berechnen
|
| 371 |
+
y_min, x_min = white_pixels[0].min(), white_pixels[1].min()
|
| 372 |
+
y_max, x_max = white_pixels[0].max(), white_pixels[1].max()
|
| 373 |
+
|
| 374 |
+
# Puffer für bessere Ergebnisse
|
| 375 |
+
buffer = 15
|
| 376 |
+
x_min = max(0, x_min - buffer)
|
| 377 |
+
y_min = max(0, y_min - buffer)
|
| 378 |
+
x_max = min(512, x_max + buffer)
|
| 379 |
+
y_max = min(512, y_max + buffer)
|
| 380 |
+
|
| 381 |
+
mask_bbox_512 = (x_min, y_min, x_max, y_max)
|
| 382 |
+
|
| 383 |
+
print(f" 🎯 Maske-basierte BBox auf 512px: {mask_bbox_512}")
|
| 384 |
+
print(f" 📏 Größe: {x_max-x_min}×{y_max-y_min} Pixel")
|
| 385 |
+
|
| 386 |
+
# 3c. Bearbeiteten Bereich aus 512×512 ausschneiden (MASKE-BASIERT)
|
| 387 |
+
edited_region_512 = inpaint_result.crop(mask_bbox_512)
|
| 388 |
+
|
| 389 |
+
# 3d. Auf Original-Maskengröße skalieren
|
| 390 |
+
mask_width = mask_bbox_512[2] - mask_bbox_512[0]
|
| 391 |
+
mask_height = mask_bbox_512[3] - mask_bbox_512[1]
|
| 392 |
+
|
| 393 |
+
# Maske auf Ausschnitt-Größe zuschneiden
|
| 394 |
+
mask_cropped = mask_on_512.crop(mask_bbox_512)
|
| 395 |
+
|
| 396 |
+
# Auf Originalgröße skalieren (mit Maske als Alpha)
|
| 397 |
+
mask_original_cropped = mask_cropped.resize(
|
| 398 |
+
(original_width, original_height),
|
| 399 |
+
Image.Resampling.LANCZOS
|
| 400 |
+
)
|
| 401 |
+
|
| 402 |
+
edited_region_fullsize = edited_region_512.resize(
|
| 403 |
+
(original_width, original_height),
|
| 404 |
+
Image.Resampling.LANCZOS
|
| 405 |
+
)
|
| 406 |
+
|
| 407 |
+
# 3e. Weiche Kanten für natürliche Übergänge
|
| 408 |
+
soft_mask = mask_original_cropped.filter(ImageFilter.GaussianBlur(5))
|
| 409 |
+
|
| 410 |
+
# 3f. Alpha-Compositing
|
| 411 |
+
edited_rgba = edited_region_fullsize.convert("RGBA")
|
| 412 |
+
edited_rgba.putalpha(soft_mask)
|
| 413 |
+
|
| 414 |
+
# 3g. MASKE-BASIERTE POSITION bestimmen
|
| 415 |
+
# BBox der Maske im Originalbild finden
|
| 416 |
+
mask_original_array = np.array(original_mask)
|
| 417 |
+
white_original = np.where(mask_original_array > 128)
|
| 418 |
+
|
| 419 |
+
if len(white_original[0]) > 0:
|
| 420 |
+
paste_x = white_original[1].min()
|
| 421 |
+
paste_y = white_original[0].min()
|
| 422 |
+
print(f" 📍 Einfüge-Position (maske-basiert): ({paste_x}, {paste_y})")
|
| 423 |
+
else:
|
| 424 |
+
# Fallback: Zentriert
|
| 425 |
+
paste_x = (original_width - edited_rgba.width) // 2
|
| 426 |
+
paste_y = (original_height - edited_rgba.height) // 2
|
| 427 |
+
print(f" 📍 Einfüge-Position (zentriert): ({paste_x}, {paste_y})")
|
| 428 |
+
|
| 429 |
+
# 3h. Finales Compositing
|
| 430 |
+
final_image = original_image.copy().convert("RGBA")
|
| 431 |
+
final_image.paste(edited_rgba, (paste_x, paste_y), edited_rgba)
|
| 432 |
+
|
| 433 |
+
print(f" ✅ {mode_name}-Compositing maskenbasiert abgeschlossen")
|
| 434 |
+
|
| 435 |
+
print(f"✅ Korrektes Compositing abgeschlossen. Finale Größe: {final_image.size}")
|
| 436 |
+
|
| 437 |
+
return final_image.convert("RGB")
|
| 438 |
+
|
| 439 |
+
|
| 440 |
# Composition Workflow nach Ausgabe ControlnetInpaint-Pipeline
|
| 441 |
def enhanced_composite_with_sam(original_image, inpaint_result, original_mask,
|
| 442 |
padding_info, bbox_coords, mode):
|