devkunalnaik commited on
Commit
937ca9c
·
1 Parent(s): 6faf48e

Perf: ONNX multi-thread, 320px det_size for video, fast-mode frame skip UI

Browse files
Files changed (3) hide show
  1. app.py +14 -4
  2. processors/face_swap.py +15 -3
  3. processors/video_processor.py +10 -0
app.py CHANGED
@@ -66,7 +66,7 @@ def body_swap_image(source_img, target_img, blend_strength):
66
  return None, f"Error: {e}"
67
 
68
 
69
- def face_swap_video(source_img, target_video, enhance, progress=gr.Progress()):
70
  if source_img is None or target_video is None:
71
  return None, "Please upload a source face image and a target video."
72
 
@@ -77,12 +77,13 @@ def face_swap_video(source_img, target_video, enhance, progress=gr.Progress()):
77
  target_video,
78
  mode="face",
79
  enhance=enhance,
 
80
  progress=progress,
81
  )
82
  return output_path, msg
83
 
84
 
85
- def body_swap_video(source_img, target_video, blend_strength, progress=gr.Progress()):
86
  if source_img is None or target_video is None:
87
  return None, "Please upload a source body image and a target video."
88
 
@@ -93,6 +94,7 @@ def body_swap_video(source_img, target_video, blend_strength, progress=gr.Progre
93
  target_video,
94
  mode="body",
95
  blend_strength=blend_strength,
 
96
  progress=progress,
97
  )
98
  return output_path, msg
@@ -174,6 +176,10 @@ with gr.Blocks(title="Face & Body Swapper", theme=gr.themes.Soft()) as demo:
174
  label="Enhance faces (sharpening + contrast)",
175
  value=True,
176
  )
 
 
 
 
177
  fv_btn = gr.Button("Swap Faces in Video", variant="primary")
178
  with gr.Column(scale=1):
179
  fv_output = gr.Video(label="Result Video")
@@ -181,7 +187,7 @@ with gr.Blocks(title="Face & Body Swapper", theme=gr.themes.Soft()) as demo:
181
 
182
  fv_btn.click(
183
  face_swap_video,
184
- inputs=[fv_source, fv_target, fv_enhance],
185
  outputs=[fv_output, fv_status],
186
  )
187
 
@@ -199,6 +205,10 @@ with gr.Blocks(title="Face & Body Swapper", theme=gr.themes.Soft()) as demo:
199
  minimum=0.1, maximum=1.0, value=0.85, step=0.05,
200
  label="Blend Strength",
201
  )
 
 
 
 
202
  bv_btn = gr.Button("Swap Body in Video", variant="primary")
203
  with gr.Column(scale=1):
204
  bv_output = gr.Video(label="Result Video")
@@ -206,7 +216,7 @@ with gr.Blocks(title="Face & Body Swapper", theme=gr.themes.Soft()) as demo:
206
 
207
  bv_btn.click(
208
  body_swap_video,
209
- inputs=[bv_source, bv_target, bv_blend],
210
  outputs=[bv_output, bv_status],
211
  )
212
 
 
66
  return None, f"Error: {e}"
67
 
68
 
69
+ def face_swap_video(source_img, target_video, enhance, fast_mode, progress=gr.Progress()):
70
  if source_img is None or target_video is None:
71
  return None, "Please upload a source face image and a target video."
72
 
 
77
  target_video,
78
  mode="face",
79
  enhance=enhance,
80
+ fast_mode=fast_mode,
81
  progress=progress,
82
  )
83
  return output_path, msg
84
 
85
 
86
+ def body_swap_video(source_img, target_video, blend_strength, fast_mode, progress=gr.Progress()):
87
  if source_img is None or target_video is None:
88
  return None, "Please upload a source body image and a target video."
89
 
 
94
  target_video,
95
  mode="body",
96
  blend_strength=blend_strength,
97
+ fast_mode=fast_mode,
98
  progress=progress,
99
  )
100
  return output_path, msg
 
176
  label="Enhance faces (sharpening + contrast)",
177
  value=True,
178
  )
179
+ fv_fast = gr.Checkbox(
180
+ label="⚡ Fast Mode — skip every other frame (~2× speed, slight motion blur)",
181
+ value=False,
182
+ )
183
  fv_btn = gr.Button("Swap Faces in Video", variant="primary")
184
  with gr.Column(scale=1):
185
  fv_output = gr.Video(label="Result Video")
 
187
 
188
  fv_btn.click(
189
  face_swap_video,
190
+ inputs=[fv_source, fv_target, fv_enhance, fv_fast],
191
  outputs=[fv_output, fv_status],
192
  )
193
 
 
205
  minimum=0.1, maximum=1.0, value=0.85, step=0.05,
206
  label="Blend Strength",
207
  )
208
+ bv_fast = gr.Checkbox(
209
+ label="⚡ Fast Mode — skip every other frame (~2× speed)",
210
+ value=False,
211
+ )
212
  bv_btn = gr.Button("Swap Body in Video", variant="primary")
213
  with gr.Column(scale=1):
214
  bv_output = gr.Video(label="Result Video")
 
216
 
217
  bv_btn.click(
218
  body_swap_video,
219
+ inputs=[bv_source, bv_target, bv_blend, bv_fast],
220
  outputs=[bv_output, bv_status],
221
  )
222
 
processors/face_swap.py CHANGED
@@ -102,16 +102,25 @@ class FaceSwapper:
102
 
103
  import insightface
104
  from insightface.app import FaceAnalysis
 
 
105
 
106
- # Face analysis (buffalo_l auto-downloads on first run)
107
- # Initialize face analysis (CPU-only for free HF Spaces tier)
 
 
 
 
 
 
 
108
  self._app = FaceAnalysis(
109
  name="buffalo_l",
110
  providers=["CPUExecutionProvider"],
111
  )
112
  self._app.prepare(ctx_id=-1, det_size=(640, 640))
113
 
114
- # inswapper model
115
  _download_inswapper()
116
  self._swapper = insightface.model_zoo.get_model(
117
  str(INSWAPPER_PATH),
@@ -270,7 +279,10 @@ class FaceSwapper:
270
  )
271
 
272
  if cached_target_faces is None:
 
 
273
  target_faces = self._app.get(target_bgr)
 
274
  else:
275
  target_faces = cached_target_faces
276
 
 
102
 
103
  import insightface
104
  from insightface.app import FaceAnalysis
105
+ import onnxruntime as ort
106
+ import multiprocessing
107
 
108
+ n_threads = multiprocessing.cpu_count()
109
+
110
+ # Use all available CPU cores for ONNX inference
111
+ sess_opts = ort.SessionOptions()
112
+ sess_opts.intra_op_num_threads = n_threads
113
+ sess_opts.inter_op_num_threads = n_threads
114
+ sess_opts.execution_mode = ort.ExecutionMode.ORT_PARALLEL
115
+
116
+ # Face analysis — 640 for images, 320 for video (set via swap_frame)
117
  self._app = FaceAnalysis(
118
  name="buffalo_l",
119
  providers=["CPUExecutionProvider"],
120
  )
121
  self._app.prepare(ctx_id=-1, det_size=(640, 640))
122
 
123
+ # inswapper model with multi-thread session options
124
  _download_inswapper()
125
  self._swapper = insightface.model_zoo.get_model(
126
  str(INSWAPPER_PATH),
 
279
  )
280
 
281
  if cached_target_faces is None:
282
+ # Use smaller det_size for video to speed up detection
283
+ self._app.det_model.input_size = (320, 320)
284
  target_faces = self._app.get(target_bgr)
285
+ self._app.det_model.input_size = (640, 640) # restore for images
286
  else:
287
  target_faces = cached_target_faces
288
 
processors/video_processor.py CHANGED
@@ -40,6 +40,7 @@ class VideoProcessor:
40
  mode: str = "face", # "face" | "body"
41
  enhance: bool = False,
42
  blend_strength: float = 0.85,
 
43
  progress=None,
44
  ) -> tuple[str | None, str]:
45
  """
@@ -82,6 +83,7 @@ class VideoProcessor:
82
  processed = 0
83
  errors = 0
84
  cached_tgt_faces = None # reused across DET_INTERVAL frames
 
85
 
86
  while True:
87
  ret, frame = cap.read()
@@ -94,6 +96,12 @@ class VideoProcessor:
94
  f"Processing frame {frame_idx + 1} / {total_frames}",
95
  )
96
 
 
 
 
 
 
 
97
  # Only re-detect target faces every DET_INTERVAL frames
98
  use_cache = (mode == "face") and (frame_idx % DET_INTERVAL != 0) and (cached_tgt_faces is not None)
99
 
@@ -109,9 +117,11 @@ class VideoProcessor:
109
 
110
  if result_frame is not None:
111
  writer.write(result_frame)
 
112
  processed += 1
113
  else:
114
  writer.write(frame) # keep original on failure
 
115
  errors += 1
116
 
117
  frame_idx += 1
 
40
  mode: str = "face", # "face" | "body"
41
  enhance: bool = False,
42
  blend_strength: float = 0.85,
43
+ fast_mode: bool = False, # skip every other frame (~2x speed)
44
  progress=None,
45
  ) -> tuple[str | None, str]:
46
  """
 
83
  processed = 0
84
  errors = 0
85
  cached_tgt_faces = None # reused across DET_INTERVAL frames
86
+ last_result = None # for fast_mode frame duplication
87
 
88
  while True:
89
  ret, frame = cap.read()
 
96
  f"Processing frame {frame_idx + 1} / {total_frames}",
97
  )
98
 
99
+ # Fast mode: skip odd frames — duplicate the previous processed result
100
+ if fast_mode and frame_idx % 2 == 1 and last_result is not None:
101
+ writer.write(last_result)
102
+ frame_idx += 1
103
+ continue
104
+
105
  # Only re-detect target faces every DET_INTERVAL frames
106
  use_cache = (mode == "face") and (frame_idx % DET_INTERVAL != 0) and (cached_tgt_faces is not None)
107
 
 
117
 
118
  if result_frame is not None:
119
  writer.write(result_frame)
120
+ last_result = result_frame
121
  processed += 1
122
  else:
123
  writer.write(frame) # keep original on failure
124
+ last_result = frame
125
  errors += 1
126
 
127
  frame_idx += 1