Update app.py
Browse files
app.py
CHANGED
|
@@ -104,24 +104,44 @@ def auto_negative_prompt(positive_prompt):
|
|
| 104 |
else:
|
| 105 |
return base_negatives
|
| 106 |
|
| 107 |
-
# === GESICHTSMASKEN-FUNKTIONEN ===
|
| 108 |
-
def create_face_mask(image, bbox_coords,
|
| 109 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
mask = Image.new("L", image.size, 0) # Start mit komplett schwarzer Maske (alles geschützt)
|
| 111 |
|
| 112 |
if bbox_coords and all(coord is not None for coord in bbox_coords):
|
| 113 |
x1, y1, x2, y2 = bbox_coords
|
| 114 |
draw = ImageDraw.Draw(mask)
|
| 115 |
|
| 116 |
-
if
|
| 117 |
-
#
|
|
|
|
| 118 |
draw.rectangle([0, 0, image.size[0], image.size[1]], fill=255) # Alles weiß = verändern
|
| 119 |
draw.rectangle([x1, y1, x2, y2], fill=0) # Gesicht schwarz = geschützt (rechteckig)
|
| 120 |
-
print("
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
return mask
|
| 127 |
|
|
@@ -361,22 +381,51 @@ class ImageToImageProgressCallback:
|
|
| 361 |
self.progress(progress_percent / 100, desc="Generierung läuft...")
|
| 362 |
return callback_kwargs
|
| 363 |
|
| 364 |
-
# === NEUE FUNKTIONEN FÜR DIE FEATURES ===
|
| 365 |
-
def create_preview_image(image, bbox_coords,
|
| 366 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
if image is None:
|
| 368 |
return None
|
| 369 |
|
| 370 |
preview = image.copy()
|
| 371 |
draw = ImageDraw.Draw(preview)
|
| 372 |
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 376 |
else:
|
| 377 |
-
|
| 378 |
-
|
|
|
|
|
|
|
|
|
|
| 379 |
|
|
|
|
| 380 |
border_width = 8
|
| 381 |
draw.rectangle([0, 0, preview.width-1, preview.height-1],
|
| 382 |
outline=border_color, width=border_width)
|
|
@@ -384,29 +433,33 @@ def create_preview_image(image, bbox_coords, face_preserve, mode_color):
|
|
| 384 |
if bbox_coords and all(coord is not None for coord in bbox_coords):
|
| 385 |
x1, y1, x2, y2 = bbox_coords
|
| 386 |
|
| 387 |
-
|
| 388 |
draw.rectangle([x1, y1, x2, y2], outline=box_color, width=3)
|
| 389 |
|
|
|
|
| 390 |
text_color = (255, 255, 255)
|
| 391 |
-
bg_color = (0, 0, 0, 160)
|
| 392 |
|
|
|
|
| 393 |
text_bbox = draw.textbbox((x1, y1 - 25), mode_text)
|
| 394 |
draw.rectangle([text_bbox[0]-5, text_bbox[1]-2, text_bbox[2]+5, text_bbox[3]+2],
|
| 395 |
-
fill=
|
| 396 |
|
|
|
|
| 397 |
draw.text((x1, y1 - 25), mode_text, fill=text_color)
|
| 398 |
|
| 399 |
return preview
|
| 400 |
|
| 401 |
-
def update_live_preview(image, bbox_x1, bbox_y1, bbox_x2, bbox_y2,
|
| 402 |
-
"""
|
|
|
|
|
|
|
|
|
|
| 403 |
if image is None:
|
| 404 |
return None
|
| 405 |
|
| 406 |
bbox_coords = [bbox_x1, bbox_y1, bbox_x2, bbox_y2]
|
| 407 |
-
mode_color = "green" if face_preserve else "red"
|
| 408 |
|
| 409 |
-
return create_preview_image(image, bbox_coords,
|
| 410 |
|
| 411 |
def process_image_upload(image):
|
| 412 |
"""Verarbeitet Bild-Upload und gibt Bild + Koordinaten zurück"""
|
|
@@ -420,11 +473,12 @@ def process_image_upload(image):
|
|
| 420 |
bbox = auto_detect_face_area(image)
|
| 421 |
bbox_x1, bbox_y1, bbox_x2, bbox_y2 = bbox
|
| 422 |
|
| 423 |
-
|
|
|
|
| 424 |
|
| 425 |
return preview, bbox_x1, bbox_y1, bbox_x2, bbox_y2
|
| 426 |
|
| 427 |
-
# === HAUPTFUNKTIONEN ===
|
| 428 |
def text_to_image(prompt, model_id, steps, guidance_scale, progress=gr.Progress()):
|
| 429 |
try:
|
| 430 |
if not prompt or not prompt.strip():
|
|
@@ -518,8 +572,20 @@ def text_to_image(prompt, model_id, steps, guidance_scale, progress=gr.Progress(
|
|
| 518 |
return None, error_msg
|
| 519 |
|
| 520 |
def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
| 521 |
-
|
| 522 |
progress=gr.Progress()):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 523 |
try:
|
| 524 |
if image is None:
|
| 525 |
return None
|
|
@@ -527,17 +593,17 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
|
| 527 |
import time, random
|
| 528 |
start_time = time.time()
|
| 529 |
|
| 530 |
-
print(f"Img2Img Start →
|
| 531 |
-
print(f"
|
| 532 |
-
print(f"
|
| 533 |
-
print(f"
|
| 534 |
|
| 535 |
|
| 536 |
-
# =====
|
| 537 |
auto_negatives = auto_negative_prompt(prompt)
|
| 538 |
print(f"🤖 Automatisch generierter Negativ-Prompt: {auto_negatives}")
|
| 539 |
|
| 540 |
-
# =====
|
| 541 |
combined_negative_prompt = ""
|
| 542 |
|
| 543 |
if neg_prompt and neg_prompt.strip():
|
|
@@ -546,7 +612,6 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
|
| 546 |
print(f"👤 Benutzer Negativ-Prompt: {user_neg}")
|
| 547 |
|
| 548 |
# Entferne Duplikate zwischen automatischen und manuellen Prompts
|
| 549 |
-
# Konvertiere beide in Sets für einfachen Duplikatvergleich
|
| 550 |
user_words = [word.strip().lower() for word in user_neg.split(",")]
|
| 551 |
auto_words = [word.strip().lower() for word in auto_negatives.split(",")]
|
| 552 |
|
|
@@ -558,7 +623,7 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
|
| 558 |
if auto_word and auto_word not in user_words:
|
| 559 |
combined_words.append(auto_word)
|
| 560 |
|
| 561 |
-
# Zusammenfügen und Duplikate entfernen
|
| 562 |
unique_words = []
|
| 563 |
seen_words = set()
|
| 564 |
for word in combined_words:
|
|
@@ -573,26 +638,34 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
|
| 573 |
print(f"ℹ️ Kein manueller Negativ-Prompt, verwende nur automatischen: {combined_negative_prompt}")
|
| 574 |
|
| 575 |
print(f"✅ Finaler kombinierter Negativ-Prompt: {combined_negative_prompt}")
|
| 576 |
-
# ===== ENDE DER NEUEN LOGIK =====
|
| 577 |
|
| 578 |
|
| 579 |
progress(0, desc="Starte Generierung mit ControlNet...")
|
| 580 |
|
|
|
|
| 581 |
adj_strength = min(0.85, strength * 1.25)
|
| 582 |
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
controlnet_strength = adj_strength * 0.5
|
| 588 |
-
print(f"🎯
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 589 |
|
| 590 |
controlnet_steps = min(25, int(steps * 0.8))
|
| 591 |
-
|
| 592 |
-
print(f"🎯 Steps={steps}, ControlNet-Steps={controlnet_steps}, Strength={controlnet_strength:.3f}")
|
| 593 |
|
| 594 |
progress(0.05, desc="Erstelle ControlNet Maps...")
|
| 595 |
|
|
|
|
| 596 |
controlnet_output, inpaint_input = controlnet_processor.generate_with_controlnet(
|
| 597 |
image=image,
|
| 598 |
prompt=prompt,
|
|
@@ -601,7 +674,7 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
|
| 601 |
guidance_scale=guidance_scale,
|
| 602 |
controlnet_strength=controlnet_strength,
|
| 603 |
progress=progress,
|
| 604 |
-
keep_environment=
|
| 605 |
)
|
| 606 |
|
| 607 |
print(f"✅ ControlNet Output erhalten: {type(controlnet_output)}")
|
|
@@ -616,8 +689,9 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
|
| 616 |
adj_guidance = min(guidance_scale, 12.0)
|
| 617 |
seed = random.randint(0, 2**32 - 1)
|
| 618 |
generator = torch.Generator(device=device).manual_seed(seed)
|
| 619 |
-
print(f"
|
| 620 |
|
|
|
|
| 621 |
mask = None
|
| 622 |
if bbox_x1 and bbox_y1 and bbox_x2 and bbox_y2:
|
| 623 |
orig_w, orig_h = image.size
|
|
@@ -628,10 +702,12 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
|
| 628 |
int(bbox_x2 * scale_x),
|
| 629 |
int(bbox_y2 * scale_y)
|
| 630 |
]
|
| 631 |
-
print(f"Skalierte Koordinaten: {bbox_coords}")
|
| 632 |
-
|
|
|
|
|
|
|
| 633 |
if mask:
|
| 634 |
-
print("✅ Maske
|
| 635 |
else:
|
| 636 |
print("⚠️ Keine gültigen Koordinaten – keine Maske")
|
| 637 |
|
|
@@ -641,6 +717,7 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
|
| 641 |
|
| 642 |
callback = ImageToImageProgressCallback(progress, int(steps), adj_strength)
|
| 643 |
|
|
|
|
| 644 |
result = pipe(
|
| 645 |
prompt=prompt,
|
| 646 |
negative_prompt=combined_negative_prompt,
|
|
@@ -655,7 +732,10 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
|
| 655 |
)
|
| 656 |
|
| 657 |
end_time = time.time()
|
| 658 |
-
|
|
|
|
|
|
|
|
|
|
| 659 |
|
| 660 |
generated_image = result.images[0]
|
| 661 |
return generated_image
|
|
@@ -685,6 +765,10 @@ def update_model_settings(model_id):
|
|
| 685 |
)
|
| 686 |
|
| 687 |
def main_ui():
|
|
|
|
|
|
|
|
|
|
|
|
|
| 688 |
with gr.Blocks(
|
| 689 |
title="AI Image Generator",
|
| 690 |
theme=gr.themes.Base(),
|
|
@@ -778,6 +862,31 @@ def main_ui():
|
|
| 778 |
color: #721c24;
|
| 779 |
border: 1px solid #f5c6cb;
|
| 780 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 781 |
"""
|
| 782 |
) as demo:
|
| 783 |
|
|
@@ -872,7 +981,7 @@ def main_ui():
|
|
| 872 |
)
|
| 873 |
|
| 874 |
with gr.Tab("Bild zu Bild"):
|
| 875 |
-
gr.Markdown("## 🖼️ Bild zu Bild Transformation")
|
| 876 |
|
| 877 |
with gr.Row():
|
| 878 |
with gr.Column():
|
|
@@ -891,12 +1000,33 @@ def main_ui():
|
|
| 891 |
show_download_button=False
|
| 892 |
)
|
| 893 |
|
|
|
|
| 894 |
with gr.Row():
|
| 895 |
-
|
| 896 |
-
|
| 897 |
-
|
| 898 |
-
|
| 899 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 900 |
|
| 901 |
with gr.Row():
|
| 902 |
gr.Markdown("### 📐 Bildelementbereich anpassen")
|
|
@@ -967,10 +1097,12 @@ def main_ui():
|
|
| 967 |
with gr.Row():
|
| 968 |
gr.Markdown(
|
| 969 |
"### 📋 Hinweise:\n"
|
|
|
|
| 970 |
"• **🆕 Automatische Bildelementerkennung** setzt Koordinaten beim Upload\n"
|
| 971 |
-
"• **🆕 Live-Vorschau** zeigt farbige Rahmen je nach Modus
|
| 972 |
"• **🆕 Koordinaten-Schieberegler** für präzise Anpassung mit Live-Update\n"
|
| 973 |
-
"• **
|
|
|
|
| 974 |
)
|
| 975 |
|
| 976 |
transform_btn = gr.Button("🔄 Bild transformieren", variant="primary")
|
|
@@ -989,8 +1121,10 @@ def main_ui():
|
|
| 989 |
outputs=[preview_output, bbox_x1, bbox_y1, bbox_x2, bbox_y2]
|
| 990 |
)
|
| 991 |
|
| 992 |
-
|
|
|
|
| 993 |
|
|
|
|
| 994 |
for slider in [bbox_x1, bbox_y1, bbox_x2, bbox_y2]:
|
| 995 |
slider.change(
|
| 996 |
fn=update_live_preview,
|
|
@@ -998,18 +1132,20 @@ def main_ui():
|
|
| 998 |
outputs=preview_output
|
| 999 |
)
|
| 1000 |
|
| 1001 |
-
|
|
|
|
| 1002 |
fn=update_live_preview,
|
| 1003 |
inputs=coordinate_inputs,
|
| 1004 |
outputs=preview_output
|
| 1005 |
)
|
| 1006 |
|
|
|
|
| 1007 |
transform_btn.click(
|
| 1008 |
fn=img_to_image,
|
| 1009 |
inputs=[
|
| 1010 |
img_input, img_prompt, img_neg_prompt,
|
| 1011 |
strength_slider, img_steps, img_guidance,
|
| 1012 |
-
|
| 1013 |
],
|
| 1014 |
outputs=img_output,
|
| 1015 |
concurrency_limit=1
|
|
@@ -1027,4 +1163,4 @@ if __name__ == "__main__":
|
|
| 1027 |
show_error=True,
|
| 1028 |
share=False,
|
| 1029 |
ssr_mode=False # SSR deaktivieren für Stabilität
|
| 1030 |
-
)
|
|
|
|
| 104 |
else:
|
| 105 |
return base_negatives
|
| 106 |
|
| 107 |
+
# === GESICHTSMASKEN-FUNKTIONEN (ERWEITERT FÜR 3 MODI) ===
|
| 108 |
+
def create_face_mask(image, bbox_coords, mode):
|
| 109 |
+
"""
|
| 110 |
+
ERWEITERTE FUNKTION: Erzeugt Maske basierend auf 3 Modi
|
| 111 |
+
Weiße Bereiche werden VERÄNDERT, Schwarze bleiben ERHALTEN
|
| 112 |
+
|
| 113 |
+
Parameter:
|
| 114 |
+
- image: PIL Image
|
| 115 |
+
- bbox_coords: [x1, y1, x2, y2]
|
| 116 |
+
- mode: "environment_change", "focus_change", "face_only_change"
|
| 117 |
+
|
| 118 |
+
Returns:
|
| 119 |
+
- PIL Image (L-Modus, 0=schwarz=erhalten, 255=weiß=verändern)
|
| 120 |
+
"""
|
| 121 |
mask = Image.new("L", image.size, 0) # Start mit komplett schwarzer Maske (alles geschützt)
|
| 122 |
|
| 123 |
if bbox_coords and all(coord is not None for coord in bbox_coords):
|
| 124 |
x1, y1, x2, y2 = bbox_coords
|
| 125 |
draw = ImageDraw.Draw(mask)
|
| 126 |
|
| 127 |
+
if mode == "environment_change":
|
| 128 |
+
# MODUS 1: Umgebung ändern (Depth + Canny)
|
| 129 |
+
# Maske: Alles weiß AUSSER Gesicht (schwarz)
|
| 130 |
draw.rectangle([0, 0, image.size[0], image.size[1]], fill=255) # Alles weiß = verändern
|
| 131 |
draw.rectangle([x1, y1, x2, y2], fill=0) # Gesicht schwarz = geschützt (rechteckig)
|
| 132 |
+
print("🎯 MODUS: Umgebung ändern - Alles außer Gesicht wird verändert")
|
| 133 |
+
|
| 134 |
+
elif mode == "focus_change":
|
| 135 |
+
# MODUS 2: Focus verändern (OpenPose + Canny)
|
| 136 |
+
# Maske: Nur innerhalb der Box weiß (Rest schwarz)
|
| 137 |
+
draw.rectangle([x1, y1, x2, y2], fill=255) # Nur Box weiß = verändern
|
| 138 |
+
print("🎯 MODUS: Focus verändern - Nur innerhalb der Box wird verändert")
|
| 139 |
+
|
| 140 |
+
elif mode == "face_only_change":
|
| 141 |
+
# MODUS 3: Ausschließlich Gesicht (Depth + Canny)
|
| 142 |
+
# Maske: Nur innerhalb der Box weiß (Rest schwarz) - wie focus_change
|
| 143 |
+
draw.rectangle([x1, y1, x2, y2], fill=255) # Nur Box weiß = verändern
|
| 144 |
+
print("🎯 MODUS: Ausschließlich Gesicht - Nur Gesicht wird verändert")
|
| 145 |
|
| 146 |
return mask
|
| 147 |
|
|
|
|
| 381 |
self.progress(progress_percent / 100, desc="Generierung läuft...")
|
| 382 |
return callback_kwargs
|
| 383 |
|
| 384 |
+
# === NEUE FUNKTIONEN FÜR DIE FEATURES (ANGEPASST FÜR 3 MODI) ===
|
| 385 |
+
def create_preview_image(image, bbox_coords, mode):
|
| 386 |
+
"""
|
| 387 |
+
NEUE FUNKTION: Erstellt Vorschau basierend auf 3 Modi mit farbigen Rahmen
|
| 388 |
+
|
| 389 |
+
Parameter:
|
| 390 |
+
- image: PIL Image
|
| 391 |
+
- bbox_coords: [x1, y1, x2, y2]
|
| 392 |
+
- mode: "environment_change", "focus_change", "face_only_change"
|
| 393 |
+
|
| 394 |
+
Returns:
|
| 395 |
+
- PIL Image mit farbigem Rahmen und Text
|
| 396 |
+
"""
|
| 397 |
if image is None:
|
| 398 |
return None
|
| 399 |
|
| 400 |
preview = image.copy()
|
| 401 |
draw = ImageDraw.Draw(preview)
|
| 402 |
|
| 403 |
+
# Farben basierend auf Modus
|
| 404 |
+
if mode == "environment_change":
|
| 405 |
+
border_color = (0, 255, 0, 180) # Grün für Umgebung
|
| 406 |
+
mode_text = "UMGEBUNG ÄNDERN (Gesicht geschützt)"
|
| 407 |
+
box_color = (255, 255, 0, 200) # Gelb für geschütztes Gesicht
|
| 408 |
+
text_bg_color = (0, 128, 0, 160) # Dunkelgrün
|
| 409 |
+
|
| 410 |
+
elif mode == "focus_change":
|
| 411 |
+
border_color = (255, 165, 0, 180) # Orange für Focus
|
| 412 |
+
mode_text = "FOCUS VERÄNDERN (Gesicht+Körper)"
|
| 413 |
+
box_color = (255, 0, 0, 200) # Rot für Veränderungsbereich
|
| 414 |
+
text_bg_color = (255, 140, 0, 160) # Dunkelorange
|
| 415 |
+
|
| 416 |
+
elif mode == "face_only_change":
|
| 417 |
+
border_color = (255, 0, 0, 180) # Rot für nur Gesicht
|
| 418 |
+
mode_text = "NUR GESICHT VERÄNDERN"
|
| 419 |
+
box_color = (255, 0, 0, 200) # Rot für Veränderungsbereich
|
| 420 |
+
text_bg_color = (128, 0, 0, 160) # Dunkelrot
|
| 421 |
else:
|
| 422 |
+
# Fallback
|
| 423 |
+
border_color = (128, 128, 128, 180)
|
| 424 |
+
mode_text = "UNBEKANNTER MODUS"
|
| 425 |
+
box_color = (128, 128, 128, 200)
|
| 426 |
+
text_bg_color = (64, 64, 64, 160)
|
| 427 |
|
| 428 |
+
# Rahmen um das gesamte Bild
|
| 429 |
border_width = 8
|
| 430 |
draw.rectangle([0, 0, preview.width-1, preview.height-1],
|
| 431 |
outline=border_color, width=border_width)
|
|
|
|
| 433 |
if bbox_coords and all(coord is not None for coord in bbox_coords):
|
| 434 |
x1, y1, x2, y2 = bbox_coords
|
| 435 |
|
| 436 |
+
# Bounding Box zeichnen
|
| 437 |
draw.rectangle([x1, y1, x2, y2], outline=box_color, width=3)
|
| 438 |
|
| 439 |
+
# Modus-Text anzeigen
|
| 440 |
text_color = (255, 255, 255)
|
|
|
|
| 441 |
|
| 442 |
+
# Text-Hintergrund zeichnen
|
| 443 |
text_bbox = draw.textbbox((x1, y1 - 25), mode_text)
|
| 444 |
draw.rectangle([text_bbox[0]-5, text_bbox[1]-2, text_bbox[2]+5, text_bbox[3]+2],
|
| 445 |
+
fill=text_bg_color)
|
| 446 |
|
| 447 |
+
# Text zeichnen
|
| 448 |
draw.text((x1, y1 - 25), mode_text, fill=text_color)
|
| 449 |
|
| 450 |
return preview
|
| 451 |
|
| 452 |
+
def update_live_preview(image, bbox_x1, bbox_y1, bbox_x2, bbox_y2, mode):
|
| 453 |
+
"""
|
| 454 |
+
Aktualisiert die Live-Vorschau bei Koordinaten-Änderungen
|
| 455 |
+
NEU: Verwendet 3 Modi statt Boolean
|
| 456 |
+
"""
|
| 457 |
if image is None:
|
| 458 |
return None
|
| 459 |
|
| 460 |
bbox_coords = [bbox_x1, bbox_y1, bbox_x2, bbox_y2]
|
|
|
|
| 461 |
|
| 462 |
+
return create_preview_image(image, bbox_coords, mode)
|
| 463 |
|
| 464 |
def process_image_upload(image):
|
| 465 |
"""Verarbeitet Bild-Upload und gibt Bild + Koordinaten zurück"""
|
|
|
|
| 473 |
bbox = auto_detect_face_area(image)
|
| 474 |
bbox_x1, bbox_y1, bbox_x2, bbox_y2 = bbox
|
| 475 |
|
| 476 |
+
# Standardmodus: "environment_change" (Umgebung ändern)
|
| 477 |
+
preview = create_preview_image(image, bbox, "environment_change")
|
| 478 |
|
| 479 |
return preview, bbox_x1, bbox_y1, bbox_x2, bbox_y2
|
| 480 |
|
| 481 |
+
# === HAUPTFUNKTIONEN (ANGEPASST FÜR 3 MODI) ===
|
| 482 |
def text_to_image(prompt, model_id, steps, guidance_scale, progress=gr.Progress()):
|
| 483 |
try:
|
| 484 |
if not prompt or not prompt.strip():
|
|
|
|
| 572 |
return None, error_msg
|
| 573 |
|
| 574 |
def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
| 575 |
+
mode, bbox_x1, bbox_y1, bbox_x2, bbox_y2,
|
| 576 |
progress=gr.Progress()):
|
| 577 |
+
"""
|
| 578 |
+
HAUPTFUNKTION FÜR BILD-zu-BILD (ANGEPASST FÜR 3 MODI)
|
| 579 |
+
|
| 580 |
+
WICHTIG: Der 'mode' Parameter bestimmt:
|
| 581 |
+
- "environment_change": Depth + Canny, Maske außen weiß
|
| 582 |
+
- "focus_change": OpenPose + Canny, Maske innen weiß
|
| 583 |
+
- "face_only_change": Depth + Canny, Maske innen weiß
|
| 584 |
+
|
| 585 |
+
keep_environment Parameter für ControlNet:
|
| 586 |
+
- True für "environment_change" und "face_only_change" (Depth+Canny)
|
| 587 |
+
- False für "focus_change" (OpenPose+Canny)
|
| 588 |
+
"""
|
| 589 |
try:
|
| 590 |
if image is None:
|
| 591 |
return None
|
|
|
|
| 593 |
import time, random
|
| 594 |
start_time = time.time()
|
| 595 |
|
| 596 |
+
print(f"🚀 Img2Img Start → Modus: {mode}")
|
| 597 |
+
print(f"📊 Einstellungen: Strength: {strength}, Steps: {steps}, Guidance: {guidance_scale}")
|
| 598 |
+
print(f"📝 Prompt: {prompt}")
|
| 599 |
+
print(f"🚫 Negativ-Prompt: {neg_prompt}")
|
| 600 |
|
| 601 |
|
| 602 |
+
# ===== AUTOMATISCHEN NEGATIV-PROMPT GENERIEREN =====
|
| 603 |
auto_negatives = auto_negative_prompt(prompt)
|
| 604 |
print(f"🤖 Automatisch generierter Negativ-Prompt: {auto_negatives}")
|
| 605 |
|
| 606 |
+
# ===== KOMBINIERE MANUELLEN UND AUTOMATISCHEN PROMPT =====
|
| 607 |
combined_negative_prompt = ""
|
| 608 |
|
| 609 |
if neg_prompt and neg_prompt.strip():
|
|
|
|
| 612 |
print(f"👤 Benutzer Negativ-Prompt: {user_neg}")
|
| 613 |
|
| 614 |
# Entferne Duplikate zwischen automatischen und manuellen Prompts
|
|
|
|
| 615 |
user_words = [word.strip().lower() for word in user_neg.split(",")]
|
| 616 |
auto_words = [word.strip().lower() for word in auto_negatives.split(",")]
|
| 617 |
|
|
|
|
| 623 |
if auto_word and auto_word not in user_words:
|
| 624 |
combined_words.append(auto_word)
|
| 625 |
|
| 626 |
+
# Zusammenfügen und Duplikate entfernen
|
| 627 |
unique_words = []
|
| 628 |
seen_words = set()
|
| 629 |
for word in combined_words:
|
|
|
|
| 638 |
print(f"ℹ️ Kein manueller Negativ-Prompt, verwende nur automatischen: {combined_negative_prompt}")
|
| 639 |
|
| 640 |
print(f"✅ Finaler kombinierter Negativ-Prompt: {combined_negative_prompt}")
|
|
|
|
| 641 |
|
| 642 |
|
| 643 |
progress(0, desc="Starte Generierung mit ControlNet...")
|
| 644 |
|
| 645 |
+
# ===== MODUS-SPEZIFISCHE EINSTELLUNGEN =====
|
| 646 |
adj_strength = min(0.85, strength * 1.25)
|
| 647 |
|
| 648 |
+
# Bestimme keep_environment basierend auf Modus
|
| 649 |
+
if mode == "focus_change":
|
| 650 |
+
# MODUS 2: Focus verändern → OpenPose + Canny
|
| 651 |
+
keep_environment = False
|
| 652 |
controlnet_strength = adj_strength * 0.5
|
| 653 |
+
print(f"🎯 MODUS: Focus verändern → OpenPose+Canny (keep_environment=False)")
|
| 654 |
+
else:
|
| 655 |
+
# MODUS 1 & 3: Umgebung/Gesicht ändern → Depth + Canny
|
| 656 |
+
keep_environment = True
|
| 657 |
+
controlnet_strength = adj_strength * 0.8
|
| 658 |
+
if mode == "environment_change":
|
| 659 |
+
print(f"🎯 MODUS: Umgebung ändern → Depth+Canny (keep_environment=True)")
|
| 660 |
+
else:
|
| 661 |
+
print(f"🎯 MODUS: Ausschließlich Gesicht → Depth+Canny (keep_environment=True)")
|
| 662 |
|
| 663 |
controlnet_steps = min(25, int(steps * 0.8))
|
| 664 |
+
print(f"⚙️ ControlNet Settings: Strength={controlnet_strength:.3f}, Steps={controlnet_steps}")
|
|
|
|
| 665 |
|
| 666 |
progress(0.05, desc="Erstelle ControlNet Maps...")
|
| 667 |
|
| 668 |
+
# ===== CONTROLNET AUFRUF =====
|
| 669 |
controlnet_output, inpaint_input = controlnet_processor.generate_with_controlnet(
|
| 670 |
image=image,
|
| 671 |
prompt=prompt,
|
|
|
|
| 674 |
guidance_scale=guidance_scale,
|
| 675 |
controlnet_strength=controlnet_strength,
|
| 676 |
progress=progress,
|
| 677 |
+
keep_environment=keep_environment # Wichtig: Bestimmt ControlNet Typ!
|
| 678 |
)
|
| 679 |
|
| 680 |
print(f"✅ ControlNet Output erhalten: {type(controlnet_output)}")
|
|
|
|
| 689 |
adj_guidance = min(guidance_scale, 12.0)
|
| 690 |
seed = random.randint(0, 2**32 - 1)
|
| 691 |
generator = torch.Generator(device=device).manual_seed(seed)
|
| 692 |
+
print(f"🌱 Inpaint Seed: {seed}")
|
| 693 |
|
| 694 |
+
# ===== MASKE ERSTELLEN (BASIEREND AUF MODUS) =====
|
| 695 |
mask = None
|
| 696 |
if bbox_x1 and bbox_y1 and bbox_x2 and bbox_y2:
|
| 697 |
orig_w, orig_h = image.size
|
|
|
|
| 702 |
int(bbox_x2 * scale_x),
|
| 703 |
int(bbox_y2 * scale_y)
|
| 704 |
]
|
| 705 |
+
print(f"📐 Skalierte Koordinaten: {bbox_coords}")
|
| 706 |
+
|
| 707 |
+
# NEU: Modus-spezifische Maskenerstellung
|
| 708 |
+
mask = create_face_mask(img_resized, bbox_coords, mode)
|
| 709 |
if mask:
|
| 710 |
+
print(f"✅ Maske erstellt für Modus: {mode}")
|
| 711 |
else:
|
| 712 |
print("⚠️ Keine gültigen Koordinaten – keine Maske")
|
| 713 |
|
|
|
|
| 717 |
|
| 718 |
callback = ImageToImageProgressCallback(progress, int(steps), adj_strength)
|
| 719 |
|
| 720 |
+
# ===== INPAINT DURCHFÜHREN =====
|
| 721 |
result = pipe(
|
| 722 |
prompt=prompt,
|
| 723 |
negative_prompt=combined_negative_prompt,
|
|
|
|
| 732 |
)
|
| 733 |
|
| 734 |
end_time = time.time()
|
| 735 |
+
duration = end_time - start_time
|
| 736 |
+
print(f"✅ Transformation abgeschlossen in {duration:.2f} Sekunden")
|
| 737 |
+
print(f"🎯 Verwendeter Modus: {mode}")
|
| 738 |
+
print(f"⚙️ ControlNet: {'Depth+Canny' if keep_environment else 'OpenPose+Canny'}")
|
| 739 |
|
| 740 |
generated_image = result.images[0]
|
| 741 |
return generated_image
|
|
|
|
| 765 |
)
|
| 766 |
|
| 767 |
def main_ui():
|
| 768 |
+
"""
|
| 769 |
+
HAUPT-UI (ANGEPASST FÜR 3 MODI)
|
| 770 |
+
Wichtigste Änderung: Ersetzung der Checkbox durch Radio-Buttons
|
| 771 |
+
"""
|
| 772 |
with gr.Blocks(
|
| 773 |
title="AI Image Generator",
|
| 774 |
theme=gr.themes.Base(),
|
|
|
|
| 862 |
color: #721c24;
|
| 863 |
border: 1px solid #f5c6cb;
|
| 864 |
}
|
| 865 |
+
.radio-group {
|
| 866 |
+
background: #f8f9fa;
|
| 867 |
+
padding: 15px;
|
| 868 |
+
border-radius: 8px;
|
| 869 |
+
margin: 10px 0;
|
| 870 |
+
border: 2px solid #e9ecef;
|
| 871 |
+
}
|
| 872 |
+
.radio-item {
|
| 873 |
+
padding: 8px 12px;
|
| 874 |
+
margin: 5px 0;
|
| 875 |
+
border-radius: 4px;
|
| 876 |
+
transition: background 0.3s;
|
| 877 |
+
}
|
| 878 |
+
.radio-item:hover {
|
| 879 |
+
background: #e9ecef;
|
| 880 |
+
}
|
| 881 |
+
.radio-label {
|
| 882 |
+
font-weight: 600;
|
| 883 |
+
font-size: 14px;
|
| 884 |
+
}
|
| 885 |
+
.radio-description {
|
| 886 |
+
font-size: 12px;
|
| 887 |
+
color: #6c757d;
|
| 888 |
+
margin-left: 24px;
|
| 889 |
+
}
|
| 890 |
"""
|
| 891 |
) as demo:
|
| 892 |
|
|
|
|
| 981 |
)
|
| 982 |
|
| 983 |
with gr.Tab("Bild zu Bild"):
|
| 984 |
+
gr.Markdown("## 🖼️ Bild zu Bild Transformation (3 MODI)")
|
| 985 |
|
| 986 |
with gr.Row():
|
| 987 |
with gr.Column():
|
|
|
|
| 1000 |
show_download_button=False
|
| 1001 |
)
|
| 1002 |
|
| 1003 |
+
# ===== NEUE RADIO-BUTTONS STATT CHECKBOX =====
|
| 1004 |
with gr.Row():
|
| 1005 |
+
with gr.Column():
|
| 1006 |
+
gr.Markdown("### 🎛️ Transformations-Modus")
|
| 1007 |
+
|
| 1008 |
+
# NEU: 3 Radio-Buttons statt 1 Checkbox
|
| 1009 |
+
mode_radio = gr.Radio(
|
| 1010 |
+
choices=[
|
| 1011 |
+
("🌳 Umgebung ändern", "environment_change"),
|
| 1012 |
+
("🎯 Focus verändern", "focus_change"),
|
| 1013 |
+
("👤 Ausschließlich Gesicht", "face_only_change")
|
| 1014 |
+
],
|
| 1015 |
+
value="environment_change", # Standardmodus
|
| 1016 |
+
label="Wähle den Transformationsmodus:",
|
| 1017 |
+
info="Steuert, welcher Teil des Bildes verändert wird",
|
| 1018 |
+
elem_classes="radio-group"
|
| 1019 |
+
)
|
| 1020 |
+
|
| 1021 |
+
# Detailierte Erklärungen
|
| 1022 |
+
gr.Markdown("""
|
| 1023 |
+
<div style="font-size: 12px; color: #666; margin-top: 10px;">
|
| 1024 |
+
<strong>Modus-Erklärungen:</strong><br>
|
| 1025 |
+
• <strong>🌳 Umgebung ändern:</strong> Ändert alles AUSSER dem Gesicht (Depth+Canny)<br>
|
| 1026 |
+
• <strong>🎯 Focus verändern:</strong> Ändert Gesicht+Körper (OpenPose+Canny)<br>
|
| 1027 |
+
• <strong>👤 Ausschließlich Gesicht:</strong> Ändert NUR das Gesicht (Depth+Canny)
|
| 1028 |
+
</div>
|
| 1029 |
+
""")
|
| 1030 |
|
| 1031 |
with gr.Row():
|
| 1032 |
gr.Markdown("### 📐 Bildelementbereich anpassen")
|
|
|
|
| 1097 |
with gr.Row():
|
| 1098 |
gr.Markdown(
|
| 1099 |
"### 📋 Hinweise:\n"
|
| 1100 |
+
"• **🆕 3 Transformations-Modi** für präzise Kontrolle\n"
|
| 1101 |
"• **🆕 Automatische Bildelementerkennung** setzt Koordinaten beim Upload\n"
|
| 1102 |
+
"• **🆕 Live-Vorschau** zeigt farbige Rahmen je nach Modus\n"
|
| 1103 |
"• **🆕 Koordinaten-Schieberegler** für präzise Anpassung mit Live-Update\n"
|
| 1104 |
+
"• **ControlNet-Technologie** für konsistente Ergebnisse\n"
|
| 1105 |
+
"• **Automatische Negative Prompts** für bessere Qualität"
|
| 1106 |
)
|
| 1107 |
|
| 1108 |
transform_btn = gr.Button("🔄 Bild transformieren", variant="primary")
|
|
|
|
| 1121 |
outputs=[preview_output, bbox_x1, bbox_y1, bbox_x2, bbox_y2]
|
| 1122 |
)
|
| 1123 |
|
| 1124 |
+
# NEUE Input-Liste mit mode_radio statt face_preserve
|
| 1125 |
+
coordinate_inputs = [img_input, bbox_x1, bbox_y1, bbox_x2, bbox_y2, mode_radio]
|
| 1126 |
|
| 1127 |
+
# Live-Vorschau Updates für alle Steuerelemente
|
| 1128 |
for slider in [bbox_x1, bbox_y1, bbox_x2, bbox_y2]:
|
| 1129 |
slider.change(
|
| 1130 |
fn=update_live_preview,
|
|
|
|
| 1132 |
outputs=preview_output
|
| 1133 |
)
|
| 1134 |
|
| 1135 |
+
# NEU: Mode-Radio-Button ändert auch Live-Vorschau
|
| 1136 |
+
mode_radio.change(
|
| 1137 |
fn=update_live_preview,
|
| 1138 |
inputs=coordinate_inputs,
|
| 1139 |
outputs=preview_output
|
| 1140 |
)
|
| 1141 |
|
| 1142 |
+
# NEU: Transform-Button mit mode_radio statt face_preserve
|
| 1143 |
transform_btn.click(
|
| 1144 |
fn=img_to_image,
|
| 1145 |
inputs=[
|
| 1146 |
img_input, img_prompt, img_neg_prompt,
|
| 1147 |
strength_slider, img_steps, img_guidance,
|
| 1148 |
+
mode_radio, bbox_x1, bbox_y1, bbox_x2, bbox_y2
|
| 1149 |
],
|
| 1150 |
outputs=img_output,
|
| 1151 |
concurrency_limit=1
|
|
|
|
| 1163 |
show_error=True,
|
| 1164 |
share=False,
|
| 1165 |
ssr_mode=False # SSR deaktivieren für Stabilität
|
| 1166 |
+
)
|