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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +28 -52
app.py CHANGED
@@ -148,83 +148,54 @@ 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
- """
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
161
  if os.path.exists(temp_dir):
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
 
@@ -232,20 +203,16 @@ def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
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
@@ -253,7 +220,15 @@ def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
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:
@@ -273,6 +248,7 @@ def face_swap_and_enhance(src_img, tgt_img, temp_dir="/tmp/faceswap_work"):
273
  return None, None, f"❌ Error: {str(e)}"
274
 
275
 
 
276
  # --------------------- Gradio ---------------------
277
  with gr.Blocks() as demo:
278
  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
  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
 
 
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
 
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:
 
248
  return None, None, f"❌ Error: {str(e)}"
249
 
250
 
251
+
252
  # --------------------- Gradio ---------------------
253
  with gr.Blocks() as demo:
254
  gr.Markdown("Face Swap")