LogicGoInfotechSpaces commited on
Commit
4c4b7b9
·
verified ·
1 Parent(s): 26107c3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +70 -78
app.py CHANGED
@@ -150,104 +150,98 @@ swap_lock = threading.Lock()
150
  def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
151
  try:
152
  with swap_lock:
153
- # Clean workspace
154
  if os.path.exists(temp_dir):
155
  shutil.rmtree(temp_dir)
156
  os.makedirs(temp_dir, exist_ok=True)
157
 
158
  src_bgr = cv2.cvtColor(src_img, cv2.COLOR_RGB2BGR)
159
- tgt_bgr = cv2.cvtColor(tgt_img, cv2.COLOR_RGB2BGR)
160
 
161
  src_faces = face_analysis_app.get(src_bgr)
162
- tgt_faces = face_analysis_app.get(tgt_bgr)
163
 
164
  if not src_faces or not tgt_faces:
165
  return None, None, "❌ Face not detected in source or target image"
166
 
167
- src_face = src_faces[0]
168
- tgt_face = tgt_faces[0]
169
-
170
- # --- Adaptive expansion based on face size ---
171
- def adaptive_bbox(bbox, shape, factor=1.7):
172
- h, w = shape[:2]
173
  x1, y1, x2, y2 = map(int, bbox)
174
- bw, bh = x2 - x1, y2 - y1
175
- cx, cy = x1 + bw // 2, y1 + bh // 2
176
- new_w, new_h = int(bw * factor), int(bh * factor)
177
- nx1, ny1 = max(0, cx - new_w // 2), max(0, cy - new_h // 2)
178
- nx2, ny2 = min(w, cx + new_w // 2), min(h, cy + new_h // 2)
 
 
179
  return nx1, ny1, nx2, ny2
180
 
181
- sx1, sy1, sx2, sy2 = adaptive_bbox(src_face.bbox, src_bgr.shape)
182
- tx1, ty1, tx2, ty2 = adaptive_bbox(tgt_face.bbox, tgt_bgr.shape)
183
-
184
- src_crop = src_bgr[sy1:sy2, sx1:sx2]
185
- tgt_crop = tgt_bgr[ty1:ty2, tx1:tx2]
186
-
187
- src_face_crop = face_analysis_app.get(src_crop)
188
- tgt_face_crop = face_analysis_app.get(tgt_crop)
189
- if not src_face_crop or not tgt_face_crop:
190
- return None, None, "❌ Re-detection failed in cropped region"
191
-
192
- src_face_refined = src_face_crop[0]
193
- tgt_face_refined = tgt_face_crop[0]
194
-
195
- # --- Alignment correction using landmarks (similarity transform) ---
196
- def align_face(img, src_pts, tgt_pts):
197
- M, _ = cv2.estimateAffinePartial2D(src_pts, tgt_pts)
198
- if M is not None:
199
- aligned = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]), flags=cv2.INTER_LINEAR)
200
- return aligned
201
- return img
202
-
203
- src_pts = src_face_refined.landmark_2d_106[:68].astype(np.float32)
204
- tgt_pts = tgt_face_refined.landmark_2d_106[:68].astype(np.float32)
205
- src_aligned = align_face(src_crop, src_pts, tgt_pts)
206
-
207
- # --- Perform swap on aligned faces ---
208
- swapped_crop = swapper.get(tgt_crop, tgt_face_refined, src_face_refined)
209
- if swapped_crop is None:
210
- return None, None, "❌ Swap failed"
211
-
212
- # --- Feathered mask for smooth blending ---
213
- gray = cv2.cvtColor(swapped_crop, cv2.COLOR_BGR2GRAY)
214
- mask = np.where(gray > 5, 255, 0).astype(np.uint8)
215
- mask = cv2.GaussianBlur(mask, (41, 41), 15)
216
-
217
- # --- Tone normalization between faces ---
218
- def match_tone(src, ref):
219
- src_mean, src_std = cv2.meanStdDev(src)
220
- ref_mean, ref_std = cv2.meanStdDev(ref)
221
- adj = (src - src_mean) * (ref_std / (src_std + 1e-6)) + ref_mean
222
- return np.clip(adj, 0, 255).astype(np.uint8)
223
-
224
- swapped_tone = match_tone(swapped_crop, tgt_crop)
225
-
226
- # --- Seamless composite ---
227
- center = (tx1 + (tx2 - tx1)//2, ty1 + (ty2 - ty1)//2)
228
- try:
229
- blended = cv2.seamlessClone(swapped_tone, tgt_bgr, mask, center, cv2.NORMAL_CLONE)
230
- except Exception:
231
- blended = tgt_bgr.copy()
232
- h, w = swapped_tone.shape[:2]
233
- blended[ty1:ty1+h, tx1:tx1+w] = swapped_tone
234
-
235
- swapped_path = os.path.join(temp_dir, f"swapped_{uuid.uuid4().hex}.jpg")
236
- cv2.imwrite(swapped_path, blended)
237
-
238
- # --- CodeFormer enhancement ---
239
  cmd = f"python {CODEFORMER_PATH} -w 0.7 --input_path {swapped_path} --output_path {temp_dir} --bg_upsampler realesrgan --face_upsample"
240
  result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
241
  if result.returncode != 0:
242
  return None, None, f"❌ CodeFormer failed:\n{result.stderr}"
243
 
244
- final_dir = os.path.join(temp_dir, "final_results")
245
- final_files = [f for f in os.listdir(final_dir) if f.endswith(".png")]
246
  if not final_files:
247
- return None, None, "❌ No enhanced output found"
248
- final_path = os.path.join(final_dir, final_files[0])
249
 
 
250
  final_img = cv2.cvtColor(cv2.imread(final_path), cv2.COLOR_BGR2RGB)
 
251
  return final_img, final_path, ""
252
 
253
  except Exception as e:
@@ -255,8 +249,6 @@ def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
255
 
256
 
257
 
258
-
259
-
260
  # --------------------- Gradio ---------------------
261
  with gr.Blocks() as demo:
262
  gr.Markdown("Face Swap")
 
150
  def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
151
  try:
152
  with swap_lock:
153
+ # Prepare temporary directory
154
  if os.path.exists(temp_dir):
155
  shutil.rmtree(temp_dir)
156
  os.makedirs(temp_dir, exist_ok=True)
157
 
158
  src_bgr = cv2.cvtColor(src_img, cv2.COLOR_RGB2BGR)
159
+ tgt_bgr_full = cv2.cvtColor(tgt_img, cv2.COLOR_RGB2BGR)
160
 
161
  src_faces = face_analysis_app.get(src_bgr)
162
+ tgt_faces = face_analysis_app.get(tgt_bgr_full)
163
 
164
  if not src_faces or not tgt_faces:
165
  return None, None, "❌ Face not detected in source or target image"
166
 
167
+ def expand_bbox(bbox, img_shape, scale=1.6):
168
+ ih, iw = img_shape[:2]
 
 
 
 
169
  x1, y1, x2, y2 = map(int, bbox)
170
+ w, h = x2 - x1, y2 - y1
171
+ cx, cy = x1 + w // 2, y1 + h // 2
172
+ new_w, new_h = int(w * scale), int(h * scale)
173
+ nx1 = max(0, cx - new_w // 2)
174
+ ny1 = max(0, cy - new_h // 2)
175
+ nx2 = min(iw, cx + new_w // 2)
176
+ ny2 = min(ih, cy + new_h // 2)
177
  return nx1, ny1, nx2, ny2
178
 
179
+ src_face0 = src_faces[0]
180
+ tgt_face0 = tgt_faces[0]
181
+
182
+ # More accurate source face crop with slight expansion
183
+ s_x1, s_y1, s_x2, s_y2 = expand_bbox(src_face0.bbox, src_bgr.shape, scale=1.4)
184
+ src_crop = src_bgr[s_y1:s_y2, s_x1:s_x2]
185
+ src_crop_faces = face_analysis_app.get(src_crop)
186
+ if src_crop_faces:
187
+ src_for_swap = src_crop
188
+ src_face_for_swap = src_crop_faces[0]
189
+ else:
190
+ src_for_swap = src_bgr
191
+ src_face_for_swap = src_face0
192
+
193
+ # More aggressive target crop for precise landmark detection
194
+ t_x1, t_y1, t_x2, t_y2 = expand_bbox(tgt_face0.bbox, tgt_bgr_full.shape, scale=1.6)
195
+ tgt_crop = tgt_bgr_full[t_y1:t_y2, t_x1:t_x2]
196
+ tgt_crop_faces = face_analysis_app.get(tgt_crop)
197
+
198
+ if tgt_crop_faces:
199
+ tgt_for_swap = tgt_crop
200
+ tgt_face_for_swap = tgt_crop_faces[0]
201
+
202
+ swapped_crop = swapper.get(tgt_for_swap, tgt_face_for_swap, src_face_for_swap)
203
+ if swapped_crop is None:
204
+ return None, None, "❌ Face swap failed on crop"
205
+
206
+ # Create mask with threshold for seamlessClone
207
+ mask = cv2.cvtColor(swapped_crop, cv2.COLOR_BGR2GRAY)
208
+ _, mask = cv2.threshold(mask, 1, 255, cv2.THRESH_BINARY)
209
+
210
+ center = ((t_x1 + t_x2) // 2, (t_y1 + t_y2) // 2)
211
+
212
+ try:
213
+ blended = cv2.seamlessClone(swapped_crop, tgt_bgr_full, mask, center, cv2.NORMAL_CLONE)
214
+ except Exception:
215
+ # Fallback to direct paste if seamlessClone fails
216
+ blended = tgt_bgr_full.copy()
217
+ h, w = swapped_crop.shape[:2]
218
+ blended[t_y1:t_y1+h, t_x1:t_x1+w] = swapped_crop
219
+
220
+ swapped_path = os.path.join(temp_dir, f"swapped_{uuid.uuid4().hex[:8]}.jpg")
221
+ cv2.imwrite(swapped_path, blended)
222
+
223
+ else:
224
+ # Fallback: swap on full image if crop detection fails
225
+ swapped_bgr_full = swapper.get(tgt_bgr_full, tgt_face0, src_face0)
226
+ if swapped_bgr_full is None:
227
+ return None, None, "❌ Face swap failed on full image"
228
+ swapped_path = os.path.join(temp_dir, f"swapped_{uuid.uuid4().hex[:8]}.jpg")
229
+ cv2.imwrite(swapped_path, swapped_bgr_full)
230
+
231
+ # Run CodeFormer enhancement on the swapped image
 
 
 
 
 
232
  cmd = f"python {CODEFORMER_PATH} -w 0.7 --input_path {swapped_path} --output_path {temp_dir} --bg_upsampler realesrgan --face_upsample"
233
  result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
234
  if result.returncode != 0:
235
  return None, None, f"❌ CodeFormer failed:\n{result.stderr}"
236
 
237
+ final_results_dir = os.path.join(temp_dir, "final_results")
238
+ final_files = [f for f in os.listdir(final_results_dir) if f.endswith(".png")]
239
  if not final_files:
240
+ return None, None, "❌ No enhanced image found"
 
241
 
242
+ final_path = os.path.join(final_results_dir, final_files[0])
243
  final_img = cv2.cvtColor(cv2.imread(final_path), cv2.COLOR_BGR2RGB)
244
+
245
  return final_img, final_path, ""
246
 
247
  except Exception as e:
 
249
 
250
 
251
 
 
 
252
  # --------------------- Gradio ---------------------
253
  with gr.Blocks() as demo:
254
  gr.Markdown("Face Swap")