Update app.py
Browse files
app.py
CHANGED
|
@@ -3,6 +3,7 @@ from diffusers import StableDiffusionPipeline, StableDiffusionImg2ImgPipeline
|
|
| 3 |
from diffusers import StableDiffusionInpaintPipeline, AutoencoderKL
|
| 4 |
from diffusers import DPMSolverMultistepScheduler, PNDMScheduler
|
| 5 |
from controlnet_module import controlnet_processor
|
|
|
|
| 6 |
import torch
|
| 7 |
from PIL import Image, ImageDraw
|
| 8 |
import time
|
|
@@ -462,31 +463,24 @@ def load_txt2img(model_id):
|
|
| 462 |
print(f"❌ Auch Fallback fehlgeschlagen: {fallback_error}")
|
| 463 |
raise
|
| 464 |
|
|
|
|
| 465 |
def load_img2img():
|
| 466 |
-
"""Lädt das Inpainting-Modell mit DPMSolver++ Scheduler"""
|
| 467 |
global pipe_img2img
|
| 468 |
if pipe_img2img is None:
|
| 469 |
-
print("🔄 Lade Inpainting-Modell...")
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
)
|
| 484 |
-
|
| 485 |
-
print("✅ DPMSolver++ Multistep Scheduler für Inpainting konfiguriert")
|
| 486 |
-
|
| 487 |
-
except Exception as e:
|
| 488 |
-
print(f"❌ Fehler beim Laden des Inpainting-Modells: {e}")
|
| 489 |
-
raise
|
| 490 |
|
| 491 |
pipe_img2img.enable_attention_slicing()
|
| 492 |
pipe_img2img.enable_vae_tiling()
|
|
@@ -774,12 +768,7 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
|
| 774 |
mode, bbox_x1, bbox_y1, bbox_x2, bbox_y2,
|
| 775 |
progress=gr.Progress()):
|
| 776 |
"""
|
| 777 |
-
KORRIGIERTE HAUPTFUNKTION FÜR
|
| 778 |
-
|
| 779 |
-
WICHTIG: Verwendet den korrekten Compositing-Workflow:
|
| 780 |
-
1. Skaliert Bild und Maske gemeinsam
|
| 781 |
-
2. Führt Inpainting auf 512×512 durch
|
| 782 |
-
3. Kompositiert nur den bearbeiteten Bereich zurück ins Originalbild
|
| 783 |
"""
|
| 784 |
try:
|
| 785 |
if image is None:
|
|
@@ -854,11 +843,8 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
|
| 854 |
|
| 855 |
print(f"🎯 Finaler Prompt für {mode}: {enhanced_prompt}")
|
| 856 |
|
| 857 |
-
|
| 858 |
-
#Zur Überbrückung bis von der Pipelines Infos kommen!
|
| 859 |
-
progress(0, desc="Starte Generierung mit ControlNet...")
|
| 860 |
|
| 861 |
-
|
| 862 |
# ===== MODUS-SPEZIFISCHE EINSTELLUNGEN =====
|
| 863 |
adj_strength = min(0.85, strength * 1.25)
|
| 864 |
|
|
@@ -876,59 +862,14 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
|
|
| 876 |
keep_environment = True
|
| 877 |
controlnet_strength = adj_strength * 0.5
|
| 878 |
print(f"🎯 MODUS: Ausschließlich Gesicht → Depth+Canny (keep_environment=True)")
|
| 879 |
-
|
| 880 |
-
controlnet_steps = min(25, int(steps * 0.8))
|
| 881 |
-
print(f"⚙️ ControlNet Settings: Strength={controlnet_strength:.3f}, Steps={controlnet_steps}")
|
| 882 |
-
|
| 883 |
-
|
| 884 |
-
|
| 885 |
|
| 886 |
-
|
| 887 |
-
print("🔧 Punkt 1: Bereite verrauschtes Latent vor...")
|
| 888 |
-
|
| 889 |
-
# 1. Bild für Latent-Encoding vorbereiten (bereits skaliertes Bild verwenden)
|
| 890 |
-
if scaled_image is not None:
|
| 891 |
-
# img_for_latent ist das bereits herunterskalierte 512x512 Bild (mit Padding)
|
| 892 |
-
img_for_latent = scaled_image
|
| 893 |
-
else:
|
| 894 |
-
# Fallback, falls keine Skalierung stattfand
|
| 895 |
-
img_for_latent = image.convert("RGB").resize((IMG_SIZE, IMG_SIZE), Image.Resampling.LANCZOS)
|
| 896 |
-
|
| 897 |
-
# 2. In den Latent Space encoden (VAE)
|
| 898 |
-
with torch.no_grad():
|
| 899 |
-
# Bild zu Tensor konvertieren
|
| 900 |
-
img_tensor = pipe.feature_extractor(img_for_latent, return_tensors="pt").pixel_values.to(device)
|
| 901 |
-
# Encoden
|
| 902 |
-
init_latent_dist = pipe.vae.encode(img_tensor).latent_dist
|
| 903 |
-
init_latents = init_latent_dist.sample() # Latent mit zufälliger Variation aus der Verteilung
|
| 904 |
-
init_latents = init_latents * pipe.vae.config.scaling_factor # Skalierung anpassen
|
| 905 |
-
print(f"✅ VAE-Encoding abgeschlossen. Latent Shape: {init_latents.shape}")
|
| 906 |
-
|
| 907 |
-
# 3. Verrauschung basierend auf Strength (Scheduler)
|
| 908 |
-
# Strength=0.8 bedeutet: Starte bei 80% des Rauschprozesses (stark verrauscht)
|
| 909 |
-
strength = min(0.85, strength * 1.25) # Ihre existierende Stärke-Anpassung
|
| 910 |
-
latent_timestep = int(strength * pipe.scheduler.config.num_train_timesteps)
|
| 911 |
-
|
| 912 |
-
# Rauschen generieren
|
| 913 |
-
noise = torch.randn_like(init_latents)
|
| 914 |
-
|
| 915 |
-
# Verrauschte Latents erzeugen
|
| 916 |
-
noised_latents = pipe.scheduler.add_noise(init_latents, noise, torch.tensor([latent_timestep]))
|
| 917 |
-
print(f"✅ Verrauschung abgeschlossen (Strength: {strength}, Timestep: {latent_timestep})")
|
| 918 |
-
print(f" Noised Latents Shape: {noised_latents.shape}")
|
| 919 |
-
|
| 920 |
-
# Diese Variablen für später speichern:
|
| 921 |
-
# - noised_latents: Das verrauschte Start-Latent für die Denoising-Schleife
|
| 922 |
-
# - latent_timestep: Der Start-Timestep für die Denoising-Schleife
|
| 923 |
-
# - init_latents: Das unverrauschte Latent (für spätere Referenz)
|
| 924 |
-
|
| 925 |
-
|
| 926 |
-
progress(0.03, desc="ControlNet läuft...")
|
| 927 |
|
| 928 |
# ===== WICHTIG: VARIABLEN FÜR KOMPLETTEN WORKFLOW =====
|
| 929 |
original_mask = None
|
| 930 |
padding_info = None
|
| 931 |
-
|
|
|
|
| 932 |
|
| 933 |
if bbox_x1 is not None and bbox_y1 is not None and bbox_x2 is not None and bbox_y2 is not None:
|
| 934 |
print(f"🎯 BBox Koordinaten erhalten: [{bbox_x1}, {bbox_y1}, {bbox_x2}, {bbox_y2}]")
|
|
@@ -943,75 +884,48 @@ print(f" Noised Latents Shape: {noised_latents.shape}")
|
|
| 943 |
target_size=IMG_SIZE
|
| 944 |
)
|
| 945 |
|
| 946 |
-
#ControlNet ist ein paralleles Modell (CNN), das unveränderte Control-Maps (z. B. Tiefenkarten)
|
| 947 |
-
#verarbeitet und konditionierende Signale an das frozen UNet weiterleitet, um die Gesamtgeneration zu steuern,
|
| 948 |
-
#ohne pixelgenaue Manipulationen vorzunehmen. Es beeinflusst den Diffusionsprozess global/lokal durch Addition zu den Features.
|
| 949 |
-
#ControlNet-Bildgröße und Inpaint-Bildgröße müssen übereinstimmen!
|
| 950 |
-
controlnet_input = scaled_image # Verwende das skalierte Bild für ControlNet
|
| 951 |
print(f"✅ Gemeinsame Skalierung abgeschlossen")
|
| 952 |
print(f" Original: {image.size} → Skaliert: {scaled_image.size}")
|
| 953 |
else:
|
| 954 |
# Keine BBox: Normales Img2Img (ohne Maske)
|
| 955 |
print(f"ℹ️ Keine BBox angegeben → normales Img2Img (ohne Maske)")
|
| 956 |
-
|
|
|
|
| 957 |
|
| 958 |
-
|
| 959 |
-
|
|
|
|
|
|
|
| 960 |
|
| 961 |
-
|
| 962 |
-
image=
|
| 963 |
-
prompt=enhanced_prompt,
|
| 964 |
-
negative_prompt=combined_negative_prompt,
|
| 965 |
-
steps=controlnet_steps,
|
| 966 |
-
guidance_scale=guidance_scale,
|
| 967 |
-
controlnet_strength=controlnet_strength,
|
| 968 |
-
progress=None,
|
| 969 |
keep_environment=keep_environment
|
| 970 |
)
|
| 971 |
|
| 972 |
-
print(f"✅ ControlNet
|
| 973 |
-
print(f"✅ Inpaint Input Größe: {inpaint_input.size}")
|
| 974 |
|
| 975 |
progress(0.3, desc="ControlNet abgeschlossen – starte Inpaint...")
|
| 976 |
|
| 977 |
-
# ===== INPAINTING PIPELINE =====
|
| 978 |
-
pipe = load_img2img()
|
| 979 |
|
| 980 |
-
# Bild für Inpainting vorbereiten
|
| 981 |
-
if inpaint_input.size != (IMG_SIZE, IMG_SIZE):
|
| 982 |
-
print(f"⚠️ Inpaint Input hat unerwartete Größe {inpaint_input.size}, skaliere auf {IMG_SIZE}x{IMG_SIZE}")
|
| 983 |
-
img_resized = inpaint_input.convert("RGB").resize((IMG_SIZE, IMG_SIZE), Image.Resampling.LANCZOS)
|
| 984 |
-
else:
|
| 985 |
-
img_resized = inpaint_input.convert("RGB")
|
| 986 |
-
print(f"✅ Inpaint Input ist bereits {IMG_SIZE}x{IMG_SIZE}")
|
| 987 |
-
|
| 988 |
# ===== SEED UND GENERATOR =====
|
| 989 |
adj_guidance = min(guidance_scale, 12.0)
|
| 990 |
seed = random.randint(0, 2**32 - 1)
|
| 991 |
generator = torch.Generator(device=device).manual_seed(seed)
|
| 992 |
print(f"🌱 Inpaint Seed: {seed}")
|
| 993 |
|
| 994 |
-
# ===== MASKE FÜR INPAINTING VORBEREITEN =====
|
| 995 |
-
inpaint_mask = None
|
| 996 |
-
if original_mask is not None and padding_info is not None:
|
| 997 |
-
# Verwende die skalierte Maske für Inpainting
|
| 998 |
-
_, scaled_mask, _ = scale_image_and_mask_together(
|
| 999 |
-
image.convert("RGB"),
|
| 1000 |
-
original_mask,
|
| 1001 |
-
target_size=IMG_SIZE
|
| 1002 |
-
)
|
| 1003 |
-
inpaint_mask = scaled_mask
|
| 1004 |
-
print(f"✅ Maske für Inpainting vorbereitet: {inpaint_mask.size}")
|
| 1005 |
-
|
| 1006 |
# ===== FORTSCHRITTS-CALLBACK =====
|
| 1007 |
callback = ImageToImageProgressCallback(progress, int(steps), adj_strength)
|
| 1008 |
|
| 1009 |
-
# =====
|
|
|
|
| 1010 |
result = pipe(
|
| 1011 |
prompt=enhanced_prompt,
|
| 1012 |
negative_prompt=combined_negative_prompt,
|
| 1013 |
-
image=
|
| 1014 |
-
mask_image=
|
|
|
|
| 1015 |
strength=adj_strength,
|
| 1016 |
num_inference_steps=int(steps),
|
| 1017 |
guidance_scale=adj_guidance,
|
|
@@ -1020,6 +934,8 @@ print(f" Noised Latents Shape: {noised_latents.shape}")
|
|
| 1020 |
callback_on_step_end_tensor_inputs=[],
|
| 1021 |
)
|
| 1022 |
|
|
|
|
|
|
|
| 1023 |
# ===== KORREKTES COMPOSITING =====
|
| 1024 |
generated_image = result.images[0]
|
| 1025 |
|
|
@@ -1033,9 +949,9 @@ print(f" Noised Latents Shape: {noised_latents.shape}")
|
|
| 1033 |
)
|
| 1034 |
print(f"✅ Korrektes Compositing durchgeführt")
|
| 1035 |
else:
|
| 1036 |
-
# Keine Maske: Einfach das generierte Bild zurückgeben
|
| 1037 |
final_image = generated_image
|
| 1038 |
-
print(f"ℹ️ Keine Maske → Direkte Rückgabe des
|
| 1039 |
|
| 1040 |
end_time = time.time()
|
| 1041 |
duration = end_time - start_time
|
|
@@ -1053,6 +969,7 @@ print(f" Noised Latents Shape: {noised_latents.shape}")
|
|
| 1053 |
traceback.print_exc()
|
| 1054 |
return None
|
| 1055 |
|
|
|
|
| 1056 |
def update_bbox_from_image(image):
|
| 1057 |
"""Aktualisiert die Bounding-Box-Koordinaten wenn ein Bild hochgeladen wird"""
|
| 1058 |
if image is None:
|
|
|
|
| 3 |
from diffusers import StableDiffusionInpaintPipeline, AutoencoderKL
|
| 4 |
from diffusers import DPMSolverMultistepScheduler, PNDMScheduler
|
| 5 |
from controlnet_module import controlnet_processor
|
| 6 |
+
from diffusers import StableDiffusionControlNetInpaintPipeline, ControlNetModel
|
| 7 |
import torch
|
| 8 |
from PIL import Image, ImageDraw
|
| 9 |
import time
|
|
|
|
| 463 |
print(f"❌ Auch Fallback fehlgeschlagen: {fallback_error}")
|
| 464 |
raise
|
| 465 |
|
| 466 |
+
|
| 467 |
def load_img2img():
|
|
|
|
| 468 |
global pipe_img2img
|
| 469 |
if pipe_img2img is None:
|
| 470 |
+
print("🔄 Lade ControlNet-Inpainting-Modell...")
|
| 471 |
+
# Hier müssen die ControlNet-Modelle geladen werden
|
| 472 |
+
controlnet = ControlNetModel.from_pretrained("lllyasviel/sd-controlnet-canny", torch_dtype=torch_dtype)
|
| 473 |
+
# Oder für Multi-ControlNet: eine Liste von Modellen
|
| 474 |
+
|
| 475 |
+
pipe_img2img = StableDiffusionControlNetInpaintPipeline.from_pretrained(
|
| 476 |
+
"runwayml/stable-diffusion-v1-5",
|
| 477 |
+
controlnet=controlnet,
|
| 478 |
+
torch_dtype=torch_dtype,
|
| 479 |
+
safety_checker=None,
|
| 480 |
+
).to(device)
|
| 481 |
+
# ... Rest Ihrer Konfiguration (Scheduler, etc.)
|
| 482 |
+
return pipe_img2img
|
| 483 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 484 |
|
| 485 |
pipe_img2img.enable_attention_slicing()
|
| 486 |
pipe_img2img.enable_vae_tiling()
|
|
|
|
| 768 |
mode, bbox_x1, bbox_y1, bbox_x2, bbox_y2,
|
| 769 |
progress=gr.Progress()):
|
| 770 |
"""
|
| 771 |
+
KORRIGIERTE HAUPTFUNKTION FÜR CONTROLNET-GESTEUERTES INPAINTING
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 772 |
"""
|
| 773 |
try:
|
| 774 |
if image is None:
|
|
|
|
| 843 |
|
| 844 |
print(f"🎯 Finaler Prompt für {mode}: {enhanced_prompt}")
|
| 845 |
|
| 846 |
+
progress(0, desc="Starte Generierung...")
|
|
|
|
|
|
|
| 847 |
|
|
|
|
| 848 |
# ===== MODUS-SPEZIFISCHE EINSTELLUNGEN =====
|
| 849 |
adj_strength = min(0.85, strength * 1.25)
|
| 850 |
|
|
|
|
| 862 |
keep_environment = True
|
| 863 |
controlnet_strength = adj_strength * 0.5
|
| 864 |
print(f"🎯 MODUS: Ausschließlich Gesicht → Depth+Canny (keep_environment=True)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 865 |
|
| 866 |
+
print(f"⚙️ ControlNet Settings: Strength={controlnet_strength:.3f}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 867 |
|
| 868 |
# ===== WICHTIG: VARIABLEN FÜR KOMPLETTEN WORKFLOW =====
|
| 869 |
original_mask = None
|
| 870 |
padding_info = None
|
| 871 |
+
scaled_image = None
|
| 872 |
+
scaled_mask = None
|
| 873 |
|
| 874 |
if bbox_x1 is not None and bbox_y1 is not None and bbox_x2 is not None and bbox_y2 is not None:
|
| 875 |
print(f"🎯 BBox Koordinaten erhalten: [{bbox_x1}, {bbox_y1}, {bbox_x2}, {bbox_y2}]")
|
|
|
|
| 884 |
target_size=IMG_SIZE
|
| 885 |
)
|
| 886 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 887 |
print(f"✅ Gemeinsame Skalierung abgeschlossen")
|
| 888 |
print(f" Original: {image.size} → Skaliert: {scaled_image.size}")
|
| 889 |
else:
|
| 890 |
# Keine BBox: Normales Img2Img (ohne Maske)
|
| 891 |
print(f"ℹ️ Keine BBox angegeben → normales Img2Img (ohne Maske)")
|
| 892 |
+
scaled_image = image.convert("RGB").resize((IMG_SIZE, IMG_SIZE), Image.Resampling.LANCZOS)
|
| 893 |
+
scaled_mask = Image.new("L", (IMG_SIZE, IMG_SIZE), 255) # Volle Maske
|
| 894 |
|
| 895 |
+
progress(0.1, desc="ControlNet läuft...")
|
| 896 |
+
|
| 897 |
+
# ===== CONTROLNET: MAPS ERSTELLEN =====
|
| 898 |
+
print(f"📊 ControlNet Input Größe: {scaled_image.size}")
|
| 899 |
|
| 900 |
+
controlnet_maps = controlnet_processor.prepare_controlnet_maps(
|
| 901 |
+
image=scaled_image,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 902 |
keep_environment=keep_environment
|
| 903 |
)
|
| 904 |
|
| 905 |
+
print(f"✅ ControlNet Maps erstellt: {len(controlnet_maps)} Maps")
|
|
|
|
| 906 |
|
| 907 |
progress(0.3, desc="ControlNet abgeschlossen – starte Inpaint...")
|
| 908 |
|
| 909 |
+
# ===== CONTROLNET-INPAINTING PIPELINE =====
|
| 910 |
+
pipe = load_img2img() # MUSS StableDiffusionControlNetInpaintPipeline sein!
|
| 911 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 912 |
# ===== SEED UND GENERATOR =====
|
| 913 |
adj_guidance = min(guidance_scale, 12.0)
|
| 914 |
seed = random.randint(0, 2**32 - 1)
|
| 915 |
generator = torch.Generator(device=device).manual_seed(seed)
|
| 916 |
print(f"🌱 Inpaint Seed: {seed}")
|
| 917 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 918 |
# ===== FORTSCHRITTS-CALLBACK =====
|
| 919 |
callback = ImageToImageProgressCallback(progress, int(steps), adj_strength)
|
| 920 |
|
| 921 |
+
# ===== CONTROLNET-GESTEUERTES INPAINTING DURCHFÜHREN =====
|
| 922 |
+
print(f"🔄 Führe ControlNet-gesteuertes Inpainting durch...")
|
| 923 |
result = pipe(
|
| 924 |
prompt=enhanced_prompt,
|
| 925 |
negative_prompt=combined_negative_prompt,
|
| 926 |
+
image=scaled_image, # Das skalierte Originalbild
|
| 927 |
+
mask_image=scaled_mask, # Die skalierte Maske
|
| 928 |
+
control_image=controlnet_maps, # Die ControlNet-Maps als Liste
|
| 929 |
strength=adj_strength,
|
| 930 |
num_inference_steps=int(steps),
|
| 931 |
guidance_scale=adj_guidance,
|
|
|
|
| 934 |
callback_on_step_end_tensor_inputs=[],
|
| 935 |
)
|
| 936 |
|
| 937 |
+
print("✅ ControlNet-Inpainting abgeschlossen")
|
| 938 |
+
|
| 939 |
# ===== KORREKTES COMPOSITING =====
|
| 940 |
generated_image = result.images[0]
|
| 941 |
|
|
|
|
| 949 |
)
|
| 950 |
print(f"✅ Korrektes Compositing durchgeführt")
|
| 951 |
else:
|
| 952 |
+
# Keine Maske: Einfach das generierte Bild zurückgeben
|
| 953 |
final_image = generated_image
|
| 954 |
+
print(f"ℹ️ Keine Maske → Direkte Rückgabe des Bildes")
|
| 955 |
|
| 956 |
end_time = time.time()
|
| 957 |
duration = end_time - start_time
|
|
|
|
| 969 |
traceback.print_exc()
|
| 970 |
return None
|
| 971 |
|
| 972 |
+
|
| 973 |
def update_bbox_from_image(image):
|
| 974 |
"""Aktualisiert die Bounding-Box-Koordinaten wenn ein Bild hochgeladen wird"""
|
| 975 |
if image is None:
|