LogicGoInfotechSpaces commited on
Commit
23f3aba
·
verified ·
1 Parent(s): edd029e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +97 -12
app.py CHANGED
@@ -148,6 +148,13 @@ async def log_faceswap_hit(token: str, status: str = "success"):
148
  swap_lock = threading.Lock()
149
 
150
  def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
 
 
 
 
 
 
 
151
  try:
152
  with swap_lock:
153
  # Use a temp dir for intermediate files
@@ -155,21 +162,98 @@ def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
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
- if not src_faces or not tgt_faces:
164
- return None, None, "❌ Face not detected in one of the images"
165
-
166
- swapped_path = os.path.join(temp_dir, f"swapped_{uuid.uuid4().hex[:8]}.jpg")
167
- swapped_bgr = swapper.get(tgt_bgr, tgt_faces[0], src_faces[0])
168
- if swapped_bgr is None:
169
- return None, None, "❌ Face swap failed"
170
-
171
- cv2.imwrite(swapped_path, swapped_bgr)
172
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  cmd = f"python {CODEFORMER_PATH} -w 0.7 --input_path {swapped_path} --output_path {temp_dir} --bg_upsampler realesrgan --face_upsample"
174
  result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
175
  if result.returncode != 0:
@@ -188,6 +272,7 @@ def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
188
  except Exception as e:
189
  return None, None, f"❌ Error: {str(e)}"
190
 
 
191
  # --------------------- Gradio ---------------------
192
  with gr.Blocks() as demo:
193
  gr.Markdown("Face Swap")
 
148
  swap_lock = threading.Lock()
149
 
150
  def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
151
+ """
152
+ Improved face-swap function:
153
+ - If target is a full-body image (small face), crop an expanded face region,
154
+ run swap on the crop, then blend back into the full image using seamlessClone.
155
+ - Uses insightface detection on crops to get reliable landmarks for swapping.
156
+ - Runs CodeFormer as before on the final blended image.
157
+ """
158
  try:
159
  with swap_lock:
160
  # Use a temp dir for intermediate files
 
162
  shutil.rmtree(temp_dir)
163
  os.makedirs(temp_dir, exist_ok=True)
164
 
165
+ # Convert RGB -> BGR for OpenCV / insightface
166
  src_bgr = cv2.cvtColor(src_img, cv2.COLOR_RGB2BGR)
167
+ tgt_bgr_full = cv2.cvtColor(tgt_img, cv2.COLOR_RGB2BGR)
168
 
169
+ # Detect faces on original images
170
  src_faces = face_analysis_app.get(src_bgr)
171
+ tgt_faces = face_analysis_app.get(tgt_bgr_full)
172
+
173
+ if not src_faces:
174
+ return None, None, "❌ Face not detected in source image"
175
+ if not tgt_faces:
176
+ return None, None, "❌ Face not detected in target image"
177
+
178
+ # Helper: expand bbox by factor, keep inside image bounds
179
+ def expand_bbox(bbox, img_shape, scale=1.6):
180
+ # bbox expected as [x1, y1, x2, y2]
181
+ ih, iw = img_shape[:2]
182
+ x1, y1, x2, y2 = map(int, bbox)
183
+ w = x2 - x1
184
+ h = y2 - y1
185
+ cx = x1 + w // 2
186
+ cy = y1 + h // 2
187
+ new_w = int(w * scale)
188
+ new_h = int(h * scale)
189
+ nx1 = max(0, cx - new_w // 2)
190
+ ny1 = max(0, cy - new_h // 2)
191
+ nx2 = min(iw, cx + new_w // 2)
192
+ ny2 = min(ih, cy + new_h // 2)
193
+ return nx1, ny1, nx2, ny2
194
+
195
+ # Choose primary faces (first detected)
196
+ src_face0 = src_faces[0]
197
+ tgt_face0 = tgt_faces[0]
198
+
199
+ # Crop source face to improve alignment (detect again inside crop)
200
+ s_x1, s_y1, s_x2, s_y2 = expand_bbox(src_face0.bbox, src_bgr.shape, scale=1.4)
201
+ src_crop = src_bgr[s_y1:s_y2, s_x1:s_x2]
202
+ # Re-run detection on source crop to get accurate face object relative to crop
203
+ src_crop_faces = face_analysis_app.get(src_crop)
204
+ if not src_crop_faces:
205
+ # fallback: continue with original face object
206
+ src_for_swap = src_bgr
207
+ src_face_for_swap = src_face0
208
+ else:
209
+ src_for_swap = src_crop
210
+ src_face_for_swap = src_crop_faces[0]
211
+
212
+ # For target: expand bbox more aggressively (full-body images)
213
+ t_x1, t_y1, t_x2, t_y2 = expand_bbox(tgt_face0.bbox, tgt_bgr_full.shape, scale=1.6)
214
+ tgt_crop = tgt_bgr_full[t_y1:t_y2, t_x1:t_x2]
215
+ # Re-run detection on target crop
216
+ tgt_crop_faces = face_analysis_app.get(tgt_crop)
217
+ if not tgt_crop_faces:
218
+ # If re-detect fails (rare), try original face object on full image
219
+ # Do swap directly on full image
220
+ swapped_bgr_full = swapper.get(tgt_bgr_full, tgt_face0, src_face0)
221
+ if swapped_bgr_full is None:
222
+ return None, None, "❌ Face swap failed on full image"
223
+ # Save and proceed to enhancement as before
224
+ swapped_path = os.path.join(temp_dir, f"swapped_{uuid.uuid4().hex[:8]}.jpg")
225
+ cv2.imwrite(swapped_path, swapped_bgr_full)
226
+ else:
227
+ # Swap on the cropped face region (safer and higher-quality)
228
+ tgt_for_swap = tgt_crop
229
+ tgt_face_for_swap = tgt_crop_faces[0]
230
+
231
+ swapped_crop = swapper.get(tgt_for_swap, tgt_face_for_swap, src_face_for_swap)
232
+ if swapped_crop is None:
233
+ return None, None, "❌ Face swap failed on crop"
234
+
235
+ # blended result: place swapped_crop back into full target image using seamlessClone
236
+ # Prepare mask for seamlessClone (use non-black pixels)
237
+ mask = cv2.cvtColor(swapped_crop, cv2.COLOR_BGR2GRAY)
238
+ _, mask = cv2.threshold(mask, 1, 255, cv2.THRESH_BINARY)
239
+
240
+ # Destination center for seamlessClone (center of crop in full image coords)
241
+ center = ( (t_x1 + t_x2) // 2, (t_y1 + t_y2) // 2 )
242
+
243
+ # Use NORMAL_CLONE for better blending
244
+ try:
245
+ # seamlessClone expects same depth and types
246
+ blended = cv2.seamlessClone(swapped_crop, tgt_bgr_full, mask, center, cv2.NORMAL_CLONE)
247
+ except Exception as e:
248
+ # If seamlessClone fails (e.g., mismatched sizes), fallback to simple paste
249
+ blended = tgt_bgr_full.copy()
250
+ h, w = swapped_crop.shape[:2]
251
+ blended[t_y1:t_y1+h, t_x1:t_x1+w] = swapped_crop
252
+
253
+ swapped_path = os.path.join(temp_dir, f"swapped_{uuid.uuid4().hex[:8]}.jpg")
254
+ cv2.imwrite(swapped_path, blended)
255
+
256
+ # Run CodeFormer on the resulting swapped image (same as before)
257
  cmd = f"python {CODEFORMER_PATH} -w 0.7 --input_path {swapped_path} --output_path {temp_dir} --bg_upsampler realesrgan --face_upsample"
258
  result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
259
  if result.returncode != 0:
 
272
  except Exception as e:
273
  return None, None, f"❌ Error: {str(e)}"
274
 
275
+
276
  # --------------------- Gradio ---------------------
277
  with gr.Blocks() as demo:
278
  gr.Markdown("Face Swap")