Astridkraft commited on
Commit
0c4f4cc
·
verified ·
1 Parent(s): c37ab74

Update controlnet_facefix.py

Browse files
Files changed (1) hide show
  1. controlnet_facefix.py +138 -178
controlnet_facefix.py CHANGED
@@ -1,13 +1,14 @@
1
- # controlnet_facefix.py - NUR QUALITÄTSVERBESSERUNG MIT OPENPOSE + DEPTH
2
  import torch
3
- from diffusers import StableDiffusionControlNetPipeline, ControlNetModel
4
- from PIL import Image
5
  import time
6
  import cv2
7
  import numpy as np
 
8
 
9
  print("="*60)
10
- print("FACE-FIX: QUALITÄTSVERBESSERUNG MIT OPENPOSE + DEPTH")
11
  print("="*60)
12
 
13
  _components_loaded = False
@@ -16,253 +17,212 @@ _controlnet_pose = None
16
  _pipeline = None
17
 
18
  def _initialize_components():
19
- """Lade OpenPose und Depth ControlNets"""
20
  global _components_loaded, _controlnet_depth, _controlnet_pose
21
 
22
  if _components_loaded:
23
  return True
24
-
25
- try:
26
- print("1. Lade ControlNet Depth (für 3D-Struktur)...")
27
- _controlnet_depth = ControlNetModel.from_pretrained(
28
- "lllyasviel/sd-controlnet-depth",
29
- torch_dtype=torch.float16
30
- )
31
- print(" ✅ ControlNet Depth OK")
32
- except Exception as e:
33
- print(f" ❌ ControlNet Depth Fehler: {e}")
34
- return False
35
 
36
  try:
37
- print("2. Lade ControlNet OpenPose (für Pose-Erhaltung)...")
38
  _controlnet_pose = ControlNetModel.from_pretrained(
39
  "lllyasviel/sd-controlnet-openpose",
40
  torch_dtype=torch.float16
41
  )
42
- print(" ControlNet OpenPose OK")
 
 
 
 
 
 
43
  except Exception as e:
44
- print(f" ControlNet OpenPose Fehler: {e}")
45
  return False
46
-
47
- _components_loaded = True
48
- print("✅ OPENPOSE + DEPTH GELADEN")
49
- return True
50
 
51
- def _extract_depth_map(image):
52
- """Depth Map für maximale Strukturerhaltung"""
53
  try:
54
  img_array = np.array(image.convert("RGB"))
55
-
56
- # Konvertiere zu Graustufen
57
  gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
58
 
59
- # Wende Gaußschen Blur an für weichere Depth Map
60
- blurred = cv2.GaussianBlur(gray, (7, 7), 0)
61
-
62
- # Adaptive Histogram Equalization für bessere Tiefenwahrnehmung
63
- clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
64
- enhanced = clahe.apply(blurred)
65
-
66
- # Invertiere für bessere Depth-Darstellung (helle = nah, dunkel = fern)
67
- inverted = 255 - enhanced
68
 
69
- # Normalisiere
70
- depth_normalized = cv2.normalize(inverted, None, 0, 255, cv2.NORM_MINMAX)
71
-
72
- # Zu RGB konvertieren
73
- depth_rgb = cv2.cvtColor(depth_normalized.astype(np.uint8), cv2.COLOR_GRAY2RGB)
74
-
75
- return Image.fromarray(depth_rgb)
76
- except Exception as e:
77
- print(f"Depth Map Fehler: {e}")
78
- # Fallback: einfache Graustufen
79
- return image.convert("L").convert("RGB")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
- def _extract_pose_map(image):
82
- """Pose Map mit Fokus auf Gesichtskonturen"""
83
  try:
84
  img_array = np.array(image.convert("RGB"))
85
 
86
- # Mehrere Canny-Ebenen für verschiedene Detailstufen
87
- gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
88
-
89
- # 1. Feine Gesichtsdetails (niedriger Threshold)
90
- fine_details = cv2.Canny(gray, 20, 60)
91
-
92
- # 2. Mittlere Konturen
93
- medium_contours = cv2.Canny(gray, 40, 100)
94
-
95
- # 3. Starke Kanten
96
- strong_edges = cv2.Canny(gray, 80, 160)
97
-
98
- # Kombiniere mit Gewichtung (feine Details stärker gewichtet)
99
- combined = cv2.addWeighted(fine_details, 0.6, medium_contours, 0.3, 0)
100
- combined = cv2.addWeighted(combined, 0.8, strong_edges, 0.2, 0)
101
-
102
- # Minimal dilation für Kontinuität
103
- kernel = np.ones((1, 1), np.uint8)
104
- pose_edges = cv2.dilate(combined, kernel, iterations=1)
105
-
106
- # Konvertiere zu RGB
107
- pose_rgb = cv2.cvtColor(pose_edges, cv2.COLOR_GRAY2RGB)
108
-
109
- return Image.fromarray(pose_rgb)
110
- except Exception as e:
111
- print(f"Pose Map Fehler: {e}")
112
- # Fallback
113
- edges = cv2.Canny(np.array(image.convert("RGB")), 50, 150)
114
- return Image.fromarray(edges).convert("RGB")
115
 
116
  def apply_facefix(image: Image.Image, prompt: str, negative_prompt: str, seed: int, model_id: str):
117
  """
118
- QUALITÄTSVERBESSERUNG MIT MAXIMALER INHALTSERHALTUNG
119
 
120
- Verwendet:
121
- 1. OpenPose: Behält exakte Pose und Gesichtsstruktur
122
- 2. Depth: Behält 3D-Struktur und räumliche Anordnung
123
-
124
- Strategie: MAXIMALE ControlNet-Stärke + Qualitäts-prompts
125
  """
126
- print("\n" + "🔧"*50)
127
- print("FACE-FIX: QUALITÄTSVERBESSERUNG MIT OPENPOSE+DEPTH")
128
- print(f" Original: {image.size}")
129
- print(f" Seed: {seed}")
130
- print("🔧"*50)
131
 
132
  start_time = time.time()
133
 
134
- # 1. Komponenten initialisieren
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  if not _initialize_components():
136
- print("❌ OpenPose/Depth konnten nicht geladen werden")
137
- return image
138
 
139
- # 2. Control Maps erstellen
140
- print("\n📐 Erstelle Control Maps...")
141
  original_size = image.size
142
-
143
- # Standardgröße für ControlNet
144
  control_size = (512, 512)
145
  resized_image = image.resize(control_size, Image.Resampling.LANCZOS)
146
 
147
- # Depth Map (für 3D-Struktur)
148
- depth_img = _extract_depth_map(resized_image)
149
-
150
- # Pose Map (für Gesichts- und Körperstruktur)
151
- pose_img = _extract_pose_map(resized_image)
152
-
153
- # Optional: Debug speichern
154
- depth_img.save("debug_depth_enhanced.png")
155
- pose_img.save("debug_pose_enhanced.png")
156
 
157
- # 3. Pipeline erstellen
158
  global _pipeline
159
  if _pipeline is None:
160
  try:
161
- print("🔄 Lade Pipeline mit OpenPose + Depth...")
162
  _pipeline = StableDiffusionControlNetPipeline.from_pretrained(
163
  model_id,
164
- controlnet=[_controlnet_pose, _controlnet_depth], # OpenPose zuerst, dann Depth
165
  torch_dtype=torch.float16,
166
  safety_checker=None,
167
  requires_safety_checker=False,
168
  )
169
 
170
- # Optimierungen
171
  _pipeline.enable_attention_slicing()
172
  _pipeline.enable_vae_slicing()
173
 
174
- print("✅ Pipeline mit OpenPose+Depth geladen")
175
  except Exception as e:
176
  print(f"❌ Pipeline Fehler: {e}")
177
- return image
178
 
179
  try:
180
- # 4. Auf Device bewegen
181
  device = "cuda" if torch.cuda.is_available() else "cpu"
182
  print(f" Device: {device}")
183
  pipeline = _pipeline.to(device)
184
 
185
- # 5. PROMPT-STRATEGIE FÜR QUALITÄTSVERBESSERUNG:
186
- # Original-Prompt + Qualitäts-Keywords, ABER KEINE neuen Inhalte
 
 
187
 
188
- # Basierend auf originalem Prompt, aber fokus auf Qualität
189
- if "face" in prompt.lower() or "portrait" in prompt.lower():
190
- quality_prompt = f"{prompt}, professional portrait, sharp focus, detailed skin, perfect face, clear eyes, high resolution, 8k"
191
- else:
192
- quality_prompt = f"{prompt}, high quality, sharp focus, detailed, professional photography, no artifacts"
193
-
194
- # Negative Prompts für Qualitätsverbesserung
195
- quality_negative = (
196
- f"{negative_prompt}, "
197
- "blurry, out of focus, lowres, low quality, jpeg artifacts, "
198
- "compression artifacts, pixelated, grainy, noisy, "
199
- "deformed, distorted, bad anatomy, mutation, ugly"
200
- )
201
-
202
- # 6. KRITISCHE PARAMETER FÜR INHALTSERHALTUNG:
203
- # Hohe ControlNet-Stärken für maximale Kontrolle
204
- # OpenPose: Hoch für Pose-Erhaltung
205
- # Depth: Hoch für Strukturerhaltung
206
-
207
- print("\n⚙️ Starte Qualitätsverbesserung mit Parametern:")
208
- print(f" • OpenPose Strength: 0.95 (sehr hoch für Pose-Erhaltung)")
209
- print(f" • Depth Strength: 0.85 (hoch für 3D-Struktur)")
210
- print(f" • Steps: 25")
211
- print(f" • CFG: 5.0 (niedrig für weniger 'Kreativität')")
212
 
213
  result = pipeline(
214
- prompt=quality_prompt,
215
- negative_prompt=quality_negative,
216
- image=[pose_img, depth_img], # OpenPose zuerst, dann Depth
217
- controlnet_conditioning_scale=[0.95, 0.85], # SEHR HOHE WERTE
218
- num_inference_steps=25, # Ausreichend für Qualität
219
- guidance_scale=5.0, # NIEDRIG für minimale Änderung
220
- generator=torch.Generator(device).manual_seed(seed), # GLEICHER SEED
221
  height=512,
222
  width=512,
223
  ).images[0]
224
 
225
- # 7. Zurück auf Originalgröße
226
  if original_size != (512, 512):
227
  result = result.resize(original_size, Image.Resampling.LANCZOS)
228
 
229
- duration = time.time() - start_time
 
 
230
 
231
- print(f"\n" + "✅"*50)
232
- print("✅ QUALITÄTSVERBESSERUNG ABGESCHLOSSEN")
233
- print(f"✅ Dauer: {duration:.1f}s")
234
- print(f"✅ Parameter: OpenPose=0.95, Depth=0.85")
235
- print(f"✅ Gleicher Seed: {seed}")
236
- print(f"✅ Größe: {original_size} → {result.size}")
237
- print("✅"*50)
238
 
239
- # Optional: Vergleich erstellen
240
- try:
241
- comparison = Image.new('RGB', (original_size[0] * 2, original_size[1]))
242
- comparison.paste(image, (0, 0))
243
- comparison.paste(result, (original_size[0], 0))
244
-
245
- # Füge Beschriftung hinzu
246
- from PIL import ImageDraw, ImageFont
247
- draw = ImageDraw.Draw(comparison)
248
-
249
- # Einfache Beschriftung
250
- draw.text((10, 10), "Vorher", fill=(255, 255, 255))
251
- draw.text((original_size[0] + 10, 10), "Nachher", fill=(255, 255, 255))
252
-
253
- comparison.save("quality_improvement_comparison.png")
254
- print(f"📊 Vergleich gespeichert: quality_improvement_comparison.png")
255
- except Exception as e:
256
- print(f"⚠️ Konnte Vergleich nicht speichern: {e}")
257
 
258
- return result
259
 
260
  except Exception as e:
261
- print(f"\n❌ FEHLER: {e}")
262
- import traceback
263
- traceback.print_exc()
264
- return image
265
 
266
  print("="*60)
267
- print("FACE-FIX BEREIT (OpenPose + Depth)")
268
  print("="*60)
 
1
+ # controlnet_facefix.py - PURE QUALITY ENHANCEMENT WITH MINIMAL CHANGE
2
  import torch
3
+ from diffusers import StableDiffusionControlNetPipeline, ControlNetModel, AutoencoderKL
4
+ from PIL import Image, ImageFilter, ImageEnhance
5
  import time
6
  import cv2
7
  import numpy as np
8
+ from torchvision import transforms
9
 
10
  print("="*60)
11
+ print("FACE-FIX: REINE QUALITÄTSVERBESSERUNG - MINIMALE ÄNDERUNG")
12
  print("="*60)
13
 
14
  _components_loaded = False
 
17
  _pipeline = None
18
 
19
  def _initialize_components():
20
+ """Lade nur notwendige Komponenten"""
21
  global _components_loaded, _controlnet_depth, _controlnet_pose
22
 
23
  if _components_loaded:
24
  return True
25
+
26
+ print("⚠️ Lade nur OpenPose (Depth wird deaktiviert)...")
 
 
 
 
 
 
 
 
 
27
 
28
  try:
29
+ # NUR OPENPOSE - Depth verändert zu viel
30
  _controlnet_pose = ControlNetModel.from_pretrained(
31
  "lllyasviel/sd-controlnet-openpose",
32
  torch_dtype=torch.float16
33
  )
34
+ print("✅ OpenPose geladen")
35
+
36
+ # Depth wird NICHT geladen - es verändert den Hintergrund zu stark
37
+ _controlnet_depth = None
38
+
39
+ _components_loaded = True
40
+ return True
41
  except Exception as e:
42
+ print(f"❌ Fehler: {e}")
43
  return False
 
 
 
 
44
 
45
+ def _extract_precise_pose(image):
46
+ """SEHR PRÄZISE Pose-Extraktion nur für Gesicht"""
47
  try:
48
  img_array = np.array(image.convert("RGB"))
 
 
49
  gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
50
 
51
+ # EXTREM NIEDRIGE Thresholds für minimale Kanten
52
+ edges = cv2.Canny(gray, 15, 45) # Nur feinste Kanten
 
 
 
 
 
 
 
53
 
54
+ # Face detection für Fokus
55
+ face_cascade = cv2.CascadeClassifier(
56
+ cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
57
+ )
58
+ faces = face_cascade.detectMultiScale(gray, 1.1, 4)
59
+
60
+ # Erstelle leere Pose Map
61
+ pose_map = np.zeros_like(img_array)
62
+
63
+ # Nur Gesichtskanten einfügen
64
+ if len(faces) > 0:
65
+ for (x, y, w, h) in faces:
66
+ # Extrahiere Gesichtsregion
67
+ face_region = edges[y:y+h, x:x+w]
68
+ # Nur 10% der stärksten Kanten behalten
69
+ threshold = np.percentile(face_region[face_region > 0], 90)
70
+ face_region[face_region < threshold] = 0
71
+ pose_map[y:y+h, x:x+w, 0] = face_region
72
+ else:
73
+ # Falls kein Gesicht erkannt, minimale Kanten
74
+ pose_map[..., 0] = edges * 0.3 # Noch schwächer
75
+
76
+ return Image.fromarray(pose_map)
77
+ except:
78
+ # Fallback: minimale Kanten
79
+ gray = cv2.cvtColor(np.array(image.convert("RGB")), cv2.COLOR_RGB2GRAY)
80
+ edges = cv2.Canny(gray, 10, 30) * 0.2 # Sehr schwach
81
+ return Image.fromarray(edges).convert("RGB")
82
 
83
+ def _apply_face_enhancement(image):
84
+ """EINFACHE Face Enhancement ohne AI"""
85
  try:
86
  img_array = np.array(image.convert("RGB"))
87
 
88
+ # 1. Scharfe Kanten (nur leicht)
89
+ sharpened = cv2.filter2D(img_array, -1,
90
+ np.array([[-0.5, -0.5, -0.5],
91
+ [-0.5, 5.0, -0.5],
92
+ [-0.5, -0.5, -0.5]]) / 3.0)
93
+
94
+ # 2. Leichter De-Noise
95
+ denoised = cv2.fastNlMeansDenoisingColored(sharpened, None, 3, 3, 7, 21)
96
+
97
+ # 3. Kontrast leicht erhöhen
98
+ lab = cv2.cvtColor(denoised, cv2.COLOR_RGB2LAB)
99
+ l, a, b = cv2.split(lab)
100
+ clahe = cv2.createCLAHE(clipLimit=1.0, tileGridSize=(8,8))
101
+ l = clahe.apply(l)
102
+ enhanced = cv2.merge([l, a, b])
103
+ enhanced = cv2.cvtColor(enhanced, cv2.COLOR_LAB2RGB)
104
+
105
+ return Image.fromarray(enhanced)
106
+ except:
107
+ return image
 
 
 
 
 
 
 
 
 
108
 
109
  def apply_facefix(image: Image.Image, prompt: str, negative_prompt: str, seed: int, model_id: str):
110
  """
111
+ SUPER-SUBTILE QUALITÄTSVERBESSERUNG
112
 
113
+ Strategie:
114
+ 1. NUR OpenPose (kein Depth - das verändert zu viel)
115
+ 2. SEHR niedrige ControlNet-Stärke
116
+ 3. Fast kein CFG Scale
117
+ 4. Identischer Prompt
118
  """
119
+ print("\n" + "🎯"*50)
120
+ print("SUBTILE QUALITÄTSVERBESSERUNG")
121
+ print(f" Größe: {image.size}")
122
+ print("🎯"*50)
 
123
 
124
  start_time = time.time()
125
 
126
+ # OPTION 1: Einfache non-AI Verbesserung (empfohlen)
127
+ print("\n⚡ OPTION 1: Einfache non-AI Verbesserung...")
128
+ enhanced = _apply_face_enhancement(image)
129
+
130
+ # Optional: AI-Verbesserung nur wenn nötig
131
+ use_ai_enhancement = False # Auf False setzen für minimale Änderung
132
+
133
+ if not use_ai_enhancement:
134
+ duration = time.time() - start_time
135
+ print(f"✅ Non-AI Verbesserung in {duration:.1f}s")
136
+ return enhanced
137
+
138
+ # OPTION 2: Minimale AI-Verbesserung (falls gewünscht)
139
+ print("⚠️ Starte MINIMALE AI-Verbesserung...")
140
+
141
  if not _initialize_components():
142
+ return enhanced
 
143
 
144
+ # Control Map vorbereiten
 
145
  original_size = image.size
 
 
146
  control_size = (512, 512)
147
  resized_image = image.resize(control_size, Image.Resampling.LANCZOS)
148
 
149
+ # MINIMALE Pose Map
150
+ pose_img = _extract_precise_pose(resized_image)
151
+ pose_img.save("debug_minimal_pose.png")
 
 
 
 
 
 
152
 
153
+ # Pipeline (nur falls nicht geladen)
154
  global _pipeline
155
  if _pipeline is None:
156
  try:
157
+ print("🔄 Lade Pipeline...")
158
  _pipeline = StableDiffusionControlNetPipeline.from_pretrained(
159
  model_id,
160
+ controlnet=[_controlnet_pose], # NUR OpenPose!
161
  torch_dtype=torch.float16,
162
  safety_checker=None,
163
  requires_safety_checker=False,
164
  )
165
 
 
166
  _pipeline.enable_attention_slicing()
167
  _pipeline.enable_vae_slicing()
168
 
169
+ print("✅ Pipeline geladen")
170
  except Exception as e:
171
  print(f"❌ Pipeline Fehler: {e}")
172
+ return enhanced
173
 
174
  try:
 
175
  device = "cuda" if torch.cuda.is_available() else "cpu"
176
  print(f" Device: {device}")
177
  pipeline = _pipeline.to(device)
178
 
179
+ # KRITISCHE ÄNDERUNGEN:
180
+ # 1. GLEICHER PROMPT wie ursprünglich
181
+ # 2. SEHR niedrige ControlNet-Stärke
182
+ # 3. FAST KEIN CFG
183
 
184
+ print("\n⚙️ EXTREM SUBTILE PARAMETER:")
185
+ print(" OpenPose Strength: 0.3 (SEHR NIEDRIG)")
186
+ print(" Steps: 15 (wenig)")
187
+ print(" • CFG: 2.0 (fast kein Guidance)")
188
+ print(" Gleicher Seed")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
 
190
  result = pipeline(
191
+ prompt=prompt, # WICHTIG: GLEICHER PROMPT!
192
+ negative_prompt=f"{negative_prompt}, deformed, blurry",
193
+ image=[pose_img], # Nur Pose
194
+ controlnet_conditioning_scale=[0.3], # EXTREM NIEDRIG
195
+ num_inference_steps=15, # WENIG Steps
196
+ guidance_scale=2.0, # FAST KEIN CFG
197
+ generator=torch.Generator(device).manual_seed(seed + 100), # Leicht anderer Seed
198
  height=512,
199
  width=512,
200
  ).images[0]
201
 
202
+ # Zurück auf Originalgröße
203
  if original_size != (512, 512):
204
  result = result.resize(original_size, Image.Resampling.LANCZOS)
205
 
206
+ # 50/50 Blend mit Original für noch weniger Änderung
207
+ result_array = np.array(result).astype(float)
208
+ original_array = np.array(image).astype(float)
209
 
210
+ # 70% Original, 30% AI-result
211
+ blended = (original_array * 0.7 + result_array * 0.3).astype(np.uint8)
212
+ final_result = Image.fromarray(blended)
 
 
 
 
213
 
214
+ duration = time.time() - start_time
215
+ print(f"\n✅ SUBTILE VERBESSERUNG in {duration:.1f}s")
216
+ print(f" • 70% Original, 30% AI")
217
+ print(f" • OpenPose: 0.3")
218
+ print(f" • CFG: 2.0")
 
 
 
 
 
 
 
 
 
 
 
 
 
219
 
220
+ return final_result
221
 
222
  except Exception as e:
223
+ print(f"\n❌ AI-Verbesserung fehlgeschlagen: {e}")
224
+ return enhanced
 
 
225
 
226
  print("="*60)
227
+ print("FACE-FIX: REINE QUALITÄTSVERBESSERUNG")
228
  print("="*60)