Update app.py
Browse files
app.py
CHANGED
|
@@ -247,12 +247,13 @@ def scale_image_and_mask_together(image, mask, target_size=512, bbox_coords=None
|
|
| 247 |
return padded_image, padded_mask, padding_info
|
| 248 |
|
| 249 |
|
|
|
|
| 250 |
# Composition Workflow nach Ausgabe ControlnetInpaint-Pipeline
|
| 251 |
def enhanced_composite_with_sam(original_image, inpaint_result, original_mask,
|
| 252 |
padding_info, bbox_coords, mode):
|
| 253 |
"""
|
| 254 |
-
COMPOSITING MIT SAM-MASKEN
|
| 255 |
-
Berücksichtigt die präzisen
|
| 256 |
"""
|
| 257 |
print(f"🎨 Verbessertes Compositing für Modus: {mode}")
|
| 258 |
|
|
@@ -269,178 +270,118 @@ def enhanced_composite_with_sam(original_image, inpaint_result, original_mask,
|
|
| 269 |
# FALL 1: Bild war bereits 512×512 (keine Skalierung)
|
| 270 |
# ==============================================
|
| 271 |
if scale_factor == 1.0 and x_offset == 0 and y_offset == 0:
|
| 272 |
-
print(f"✅ FALL 1: Bild 512×512 -
|
| 273 |
-
|
| 274 |
-
if mode == "environment_change":
|
| 275 |
-
# Umgebung ändern: SAM-Maske invertieren (Objekt schützen)
|
| 276 |
-
mask_inverted = Image.eval(original_mask, lambda x: 255 - x)
|
| 277 |
-
soft_mask = mask_inverted.filter(ImageFilter.GaussianBlur(5))
|
| 278 |
-
|
| 279 |
-
original_with_alpha = original_image.copy().convert("RGBA")
|
| 280 |
-
original_with_alpha.putalpha(soft_mask)
|
| 281 |
-
|
| 282 |
-
final_image = inpaint_result.copy().convert("RGBA")
|
| 283 |
-
final_image.paste(original_with_alpha, (0, 0), original_with_alpha)
|
| 284 |
-
return final_image.convert("RGB")
|
| 285 |
-
|
| 286 |
-
else:
|
| 287 |
-
# Focus/Face: Direktes Alpha-Compositing
|
| 288 |
-
soft_mask = original_mask.filter(ImageFilter.GaussianBlur(5))
|
| 289 |
-
|
| 290 |
-
inpaint_rgba = inpaint_result.convert("RGBA")
|
| 291 |
-
mask_alpha = soft_mask.convert("L")
|
| 292 |
-
inpaint_rgba.putalpha(mask_alpha)
|
| 293 |
-
|
| 294 |
-
original_rgba = original_image.convert("RGBA")
|
| 295 |
-
|
| 296 |
-
# Composite
|
| 297 |
-
final_image = Image.new("RGBA", original_image.size, (0, 0, 0, 0))
|
| 298 |
-
final_image.paste(original_rgba, (0, 0))
|
| 299 |
-
final_image.paste(inpaint_rgba, (0, 0), inpaint_rgba)
|
| 300 |
-
return final_image.convert("RGB")
|
| 301 |
|
| 302 |
# ==============================================
|
| 303 |
-
# FALL 2 & 3: Bild wurde skaliert
|
| 304 |
# ==============================================
|
| 305 |
-
print(f"🔄 FALL 2/3: Bild skaliert -
|
| 306 |
|
| 307 |
# 1. PADDING ENTFERNEN von 512×512 Ergebnis
|
| 308 |
downscaled_result = inpaint_result.crop(
|
| 309 |
(x_offset, y_offset, x_offset + scaled_width, y_offset + scaled_height)
|
| 310 |
)
|
| 311 |
|
| 312 |
-
# 2. AUF ORIGINALGRÖßE SKALIEREN
|
| 313 |
-
|
| 314 |
-
(original_width, original_height),
|
| 315 |
-
Image.Resampling.LANCZOS
|
| 316 |
-
)
|
| 317 |
-
|
| 318 |
-
# 3. SAM-MASKE FÜR KOMPOSITING VORBEREITEN
|
| 319 |
-
print(f"📐 SAM-Maske Größe: {original_mask.size}")
|
| 320 |
|
| 321 |
if mode == "environment_change":
|
| 322 |
# ==============================================
|
| 323 |
# MODUS: UMWELT ÄNDERN (Objekt bleibt original)
|
|
|
|
|
|
|
| 324 |
# ==============================================
|
| 325 |
print("🌳 Modus: Umwelt ändern mit SAM-Maske")
|
| 326 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
# Invertierte Maske für Objekterhalt
|
| 328 |
mask_inverted = Image.eval(original_mask, lambda x: 255 - x)
|
| 329 |
|
| 330 |
# Weiche Kanten für natürlichen Übergang
|
| 331 |
-
soft_mask = mask_inverted.filter(ImageFilter.GaussianBlur(
|
| 332 |
-
|
| 333 |
-
# Originalbild mit Alpha-Kanal
|
| 334 |
-
original_with_alpha = original_image.copy().convert("RGBA")
|
| 335 |
original_with_alpha.putalpha(soft_mask)
|
| 336 |
|
| 337 |
# Compositing
|
| 338 |
final_image = new_background.copy().convert("RGBA")
|
| 339 |
final_image.paste(original_with_alpha, (0, 0), original_with_alpha)
|
| 340 |
|
| 341 |
-
print(f"✅ Umwelt-Compositing abgeschlossen")
|
| 342 |
-
|
| 343 |
else:
|
| 344 |
# ==============================================
|
| 345 |
-
# MODUS: FOCUS oder GESICHT ÄNDERN
|
|
|
|
|
|
|
| 346 |
# ==============================================
|
| 347 |
mode_name = "Focus" if mode == "focus_change" else "Gesicht"
|
| 348 |
-
print(f"👤 Modus: {mode_name} ändern
|
| 349 |
-
|
| 350 |
-
# WICHTIG: MASKE-BASIERTES AUSSCHNEIDEN
|
| 351 |
-
# 3a. SAM-Maske auf 512px skalieren
|
| 352 |
-
mask_on_512 = original_mask.resize((512, 512), Image.Resampling.LANCZOS)
|
| 353 |
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 376 |
|
| 377 |
-
#
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
|
|
|
| 383 |
|
| 384 |
-
|
|
|
|
|
|
|
| 385 |
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
mask_height = mask_bbox_512[3] - mask_bbox_512[1]
|
| 395 |
-
|
| 396 |
-
# Maske auf Ausschnitt-Größe zuschneiden
|
| 397 |
-
mask_cropped = mask_on_512.crop(mask_bbox_512)
|
| 398 |
-
|
| 399 |
-
# Auf Originalgröße skalieren (mit Maske als Alpha)
|
| 400 |
-
mask_original_cropped = mask_cropped.resize(
|
| 401 |
-
(original_width, original_height),
|
| 402 |
-
Image.Resampling.LANCZOS
|
| 403 |
-
)
|
| 404 |
-
|
| 405 |
-
edited_region_fullsize = edited_region_512.resize(
|
| 406 |
-
(original_width, original_height),
|
| 407 |
-
Image.Resampling.LANCZOS
|
| 408 |
-
)
|
| 409 |
-
|
| 410 |
-
# 3e. Weiche Kanten für natürliche Übergänge
|
| 411 |
-
soft_mask = mask_original_cropped.filter(ImageFilter.GaussianBlur(5))
|
| 412 |
-
|
| 413 |
-
# 3f. Alpha-Compositing
|
| 414 |
-
edited_rgba = edited_region_fullsize.convert("RGBA")
|
| 415 |
-
edited_rgba.putalpha(soft_mask)
|
| 416 |
-
|
| 417 |
-
# 3g. MASKE-BASIERTE POSITION bestimmen
|
| 418 |
-
# BBox der Maske im Originalbild finden
|
| 419 |
-
mask_original_array = np.array(original_mask)
|
| 420 |
-
white_original = np.where(mask_original_array > 128)
|
| 421 |
-
|
| 422 |
-
if len(white_original[0]) > 0:
|
| 423 |
-
paste_x = white_original[1].min()
|
| 424 |
-
paste_y = white_original[0].min()
|
| 425 |
-
print(f" 📍 Einfüge-Position (maske-basiert): ({paste_x}, {paste_y})")
|
| 426 |
-
else:
|
| 427 |
-
# Fallback: Zentriert
|
| 428 |
-
paste_x = (original_width - edited_rgba.width) // 2
|
| 429 |
-
paste_y = (original_height - edited_rgba.height) // 2
|
| 430 |
-
print(f" 📍 Einfüge-Position (zentriert): ({paste_x}, {paste_y})")
|
| 431 |
-
|
| 432 |
-
# 3h. Finales Compositing
|
| 433 |
-
final_image = original_image.copy().convert("RGBA")
|
| 434 |
-
final_image.paste(edited_rgba, (paste_x, paste_y), edited_rgba)
|
| 435 |
-
|
| 436 |
-
print(f" ✅ {mode_name}-Compositing maskenbasiert abgeschlossen")
|
| 437 |
|
| 438 |
print(f"✅ Korrektes Compositing abgeschlossen. Finale Größe: {final_image.size}")
|
| 439 |
|
| 440 |
return final_image.convert("RGB")
|
| 441 |
|
| 442 |
|
| 443 |
-
|
| 444 |
def auto_detect_face_area(image):
|
| 445 |
"""Optimierten Vorschlag für Gesichtsbereich ohne externe Bibliotheken"""
|
| 446 |
width, height = image.size
|
|
|
|
| 247 |
return padded_image, padded_mask, padding_info
|
| 248 |
|
| 249 |
|
| 250 |
+
|
| 251 |
# Composition Workflow nach Ausgabe ControlnetInpaint-Pipeline
|
| 252 |
def enhanced_composite_with_sam(original_image, inpaint_result, original_mask,
|
| 253 |
padding_info, bbox_coords, mode):
|
| 254 |
"""
|
| 255 |
+
COMPOSITING MIT SAM-MASKEN UND BBox-KOORDINATEN
|
| 256 |
+
Berücksichtigt die präzisen Kanten der SAM-Maske
|
| 257 |
"""
|
| 258 |
print(f"🎨 Verbessertes Compositing für Modus: {mode}")
|
| 259 |
|
|
|
|
| 270 |
# FALL 1: Bild war bereits 512×512 (keine Skalierung)
|
| 271 |
# ==============================================
|
| 272 |
if scale_factor == 1.0 and x_offset == 0 and y_offset == 0:
|
| 273 |
+
print(f"✅ FALL 1: Bild 512×512 - kein Compositing nötig")
|
| 274 |
+
return inpaint_result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
|
| 276 |
# ==============================================
|
| 277 |
+
# FALL 2 & 3: Bild wurde skaliert
|
| 278 |
# ==============================================
|
| 279 |
+
print(f"🔄 FALL 2/3: Bild skaliert - Compositing mit SAM-Maske")
|
| 280 |
|
| 281 |
# 1. PADDING ENTFERNEN von 512×512 Ergebnis
|
| 282 |
downscaled_result = inpaint_result.crop(
|
| 283 |
(x_offset, y_offset, x_offset + scaled_width, y_offset + scaled_height)
|
| 284 |
)
|
| 285 |
|
| 286 |
+
# 2. AUF ORIGINALGRÖßE SKALIEREN
|
| 287 |
+
final_image = original_image.copy()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
|
| 289 |
if mode == "environment_change":
|
| 290 |
# ==============================================
|
| 291 |
# MODUS: UMWELT ÄNDERN (Objekt bleibt original)
|
| 292 |
+
# In dem Fall muß die BBox nicht berücksichtigt werden da Originalbild ausgeschnitten wird
|
| 293 |
+
# anhand der SAM-Maske
|
| 294 |
# ==============================================
|
| 295 |
print("🌳 Modus: Umwelt ändern mit SAM-Maske")
|
| 296 |
|
| 297 |
+
# Gesamtes bearbeitetes Bild hochskalieren
|
| 298 |
+
new_background = downscaled_result.resize(
|
| 299 |
+
(original_width, original_height),
|
| 300 |
+
Image.Resampling.LANCZOS
|
| 301 |
+
)
|
| 302 |
+
|
| 303 |
+
# Originalbild mit SAM-Maske einfügen
|
| 304 |
+
original_with_alpha = original_image.copy().convert("RGBA")
|
| 305 |
+
|
| 306 |
# Invertierte Maske für Objekterhalt
|
| 307 |
mask_inverted = Image.eval(original_mask, lambda x: 255 - x)
|
| 308 |
|
| 309 |
# Weiche Kanten für natürlichen Übergang
|
| 310 |
+
soft_mask = mask_inverted.filter(ImageFilter.GaussianBlur(3))
|
|
|
|
|
|
|
|
|
|
| 311 |
original_with_alpha.putalpha(soft_mask)
|
| 312 |
|
| 313 |
# Compositing
|
| 314 |
final_image = new_background.copy().convert("RGBA")
|
| 315 |
final_image.paste(original_with_alpha, (0, 0), original_with_alpha)
|
| 316 |
|
|
|
|
|
|
|
| 317 |
else:
|
| 318 |
# ==============================================
|
| 319 |
+
# MODUS: FOCUS oder GESICHT ÄNDERN
|
| 320 |
+
# Hier muß die BBox berücksichtigt werden da generiertes Bild ausgeschnitten wird
|
| 321 |
+
# ohne die BBox wird entlang der SAM-Maske geschnitten -> ungenau!
|
| 322 |
# ==============================================
|
| 323 |
mode_name = "Focus" if mode == "focus_change" else "Gesicht"
|
| 324 |
+
print(f"👤 Modus: {mode_name} ändern mit SAM-Maske")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
|
| 326 |
+
if not bbox_coords or not all(c is not None for c in bbox_coords):
|
| 327 |
+
# Keine BBox: gesamtes Bild zurückgeben
|
| 328 |
+
final_image = downscaled_result.resize(
|
| 329 |
+
(original_width, original_height),
|
| 330 |
+
Image.Resampling.LANCZOS
|
| 331 |
+
)
|
| 332 |
+
return final_image.convert("RGB")
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
#BBox-Koordinaten korrekt transformieren
|
| 336 |
+
#Die BBox-Koordinaten müssen vom Originalbild nach 512x512 transformiert werden
|
| 337 |
+
bbox_scaled = (
|
| 338 |
+
int(bbox_coords[0] * scale_factor),
|
| 339 |
+
int(bbox_coords[1] * scale_factor),
|
| 340 |
+
int(bbox_coords[2] * scale_factor),
|
| 341 |
+
int(bbox_coords[3] * scale_factor)
|
| 342 |
+
)
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
#Mit den Padding-Offsets wird bei nicht quadratischen 512x512 Bildern
|
| 346 |
+
#das Padding hinzugefügt
|
| 347 |
+
bbox_in_512 = (
|
| 348 |
+
bbox_scaled[0] + x_offset,
|
| 349 |
+
bbox_scaled[1] + y_offset,
|
| 350 |
+
bbox_scaled[2] + x_offset,
|
| 351 |
+
bbox_scaled[3] + y_offset
|
| 352 |
+
)
|
| 353 |
+
|
| 354 |
+
if bbox_in_512[2] > bbox_in_512[0] and bbox_in_512[3] > bbox_in_512[1]:
|
| 355 |
+
# Bearbeiteten Bereich aus dem 512×512-Ergebnis ausschneiden
|
| 356 |
+
edited_region = inpaint_result.crop(bbox_in_512)
|
| 357 |
+
|
| 358 |
|
| 359 |
+
# Auf ORIGINAL-BBox-Größe skalieren
|
| 360 |
+
original_bbox_size = (bbox_coords[2] - bbox_coords[0],
|
| 361 |
+
bbox_coords[3] - bbox_coords[1])
|
| 362 |
+
edited_region_fullsize = edited_region.resize(
|
| 363 |
+
original_bbox_size,
|
| 364 |
+
Image.Resampling.LANCZOS
|
| 365 |
+
)
|
| 366 |
|
| 367 |
+
# SAM-Maske für den Bereich zuschneiden und weichzeichnen
|
| 368 |
+
mask_cropped = original_mask.crop(bbox_coords)
|
| 369 |
+
soft_mask = mask_cropped.filter(ImageFilter.GaussianBlur(3))
|
| 370 |
|
| 371 |
+
# Alpha-Compositing mit präziser SAM-Maske
|
| 372 |
+
edited_rgba = edited_region_fullsize.convert("RGBA")
|
| 373 |
+
mask_rgba = soft_mask.convert("L") # SAM-Maske als Alpha-Kanal
|
| 374 |
+
|
| 375 |
+
temp_image = Image.new("RGBA", original_bbox_size, (0, 0, 0, 0))
|
| 376 |
+
temp_image.paste(edited_rgba, (0, 0), mask_rgba)
|
| 377 |
+
|
| 378 |
+
final_image.paste(temp_image, (bbox_coords[0], bbox_coords[1]), temp_image)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
|
| 380 |
print(f"✅ Korrektes Compositing abgeschlossen. Finale Größe: {final_image.size}")
|
| 381 |
|
| 382 |
return final_image.convert("RGB")
|
| 383 |
|
| 384 |
|
|
|
|
| 385 |
def auto_detect_face_area(image):
|
| 386 |
"""Optimierten Vorschlag für Gesichtsbereich ohne externe Bibliotheken"""
|
| 387 |
width, height = image.size
|