devkunalnaik commited on
Commit
1c23235
Β·
1 Parent(s): 4a9cb39

Fix video glitches: OpenCV-only enhance for video, even dimensions for XVID, softer unsharp

Browse files
app.py CHANGED
@@ -175,8 +175,8 @@ with gr.Blocks(title="Face & Body Swapper", theme=gr.themes.Soft()) as demo:
175
  fv_source = gr.Image(label="Source Face Image", type="pil")
176
  fv_target = gr.Video(label="Target Video")
177
  fv_enhance = gr.Checkbox(
178
- label="Enhance faces (sharpening + contrast)",
179
- value=True,
180
  )
181
  fv_fast = gr.Checkbox(
182
  label="⚑ Fast Mode β€” skip every other frame (~2Γ— speed, slight motion blur)",
 
175
  fv_source = gr.Image(label="Source Face Image", type="pil")
176
  fv_target = gr.Video(label="Target Video")
177
  fv_enhance = gr.Checkbox(
178
+ label="Enhance faces (sharpening + contrast) β€” adds time per frame",
179
+ value=False,
180
  )
181
  fv_fast = gr.Checkbox(
182
  label="⚑ Fast Mode β€” skip every other frame (~2Γ— speed, slight motion blur)",
processors/face_swap.py CHANGED
@@ -466,7 +466,10 @@ class FaceSwapper:
466
  result = self._swapper.get(result, tgt_face, source_face, paste_back=True)
467
 
468
  if enhance:
469
- result = self._enhance_codeformer(result, target_faces)
 
 
 
470
 
471
  # Scale back up to original frame size
472
  if scale_down < 1.0:
 
466
  result = self._swapper.get(result, tgt_face, source_face, paste_back=True)
467
 
468
  if enhance:
469
+ # Use OpenCV enhancement for video β€” CodeFormer causes per-frame
470
+ # temporal flicker (each frame independently restored = jitter).
471
+ # OpenCV unsharp+CLAHE is temporally stable and fast.
472
+ result = self._enhance_opencv(result, target_faces)
473
 
474
  # Scale back up to original frame size
475
  if scale_down < 1.0:
processors/video_processor.py CHANGED
@@ -106,14 +106,17 @@ class VideoProcessor:
106
 
107
  # Use AVI + XVID for the intermediate file β€” far more reliable than
108
  # mp4v on Linux (HF Spaces). FFmpeg converts it to H.264/mp4 after.
 
 
 
109
  raw_out_path = tempfile.mktemp(suffix="_raw.avi")
110
  fourcc = cv2.VideoWriter_fourcc(*"XVID")
111
- writer = cv2.VideoWriter(raw_out_path, fourcc, fps, (width, height))
112
  if not writer.isOpened():
113
  # XVID not available β€” fall back to MJPG
114
  raw_out_path = tempfile.mktemp(suffix="_raw.avi")
115
  fourcc = cv2.VideoWriter_fourcc(*"MJPG")
116
- writer = cv2.VideoWriter(raw_out_path, fourcc, fps, (width, height))
117
 
118
  frame_idx = start_frame # absolute frame number in the source video
119
  processed = 0
@@ -155,12 +158,19 @@ class VideoProcessor:
155
  cached_tgt_faces = new_faces if new_faces else cached_tgt_faces
156
 
157
  if result_frame is not None:
 
 
 
 
158
  writer.write(result_frame)
159
  last_result = result_frame
160
  processed += 1
161
  else:
162
- writer.write(frame)
163
- last_result = frame
 
 
 
164
  errors += 1
165
 
166
  frame_idx += 1
@@ -265,10 +275,10 @@ class VideoProcessor:
265
 
266
  out_kwargs = dict(
267
  vcodec="libx264",
268
- crf=18, # 18 = visually lossless (was 23)
269
  preset="fast",
270
- pix_fmt="yuv420p", # widest player compatibility
271
- **{"vf": "unsharp=5:5:1.0:5:5:0.0"}, # mild luma sharpening
272
  )
273
  if has_audio:
274
  out_kwargs.update(acodec="aac", audio_bitrate="192k")
 
106
 
107
  # Use AVI + XVID for the intermediate file β€” far more reliable than
108
  # mp4v on Linux (HF Spaces). FFmpeg converts it to H.264/mp4 after.
109
+ # XVID/MJPG require even dimensions β€” round down if necessary.
110
+ enc_w = width - (width % 2)
111
+ enc_h = height - (height % 2)
112
  raw_out_path = tempfile.mktemp(suffix="_raw.avi")
113
  fourcc = cv2.VideoWriter_fourcc(*"XVID")
114
+ writer = cv2.VideoWriter(raw_out_path, fourcc, fps, (enc_w, enc_h))
115
  if not writer.isOpened():
116
  # XVID not available β€” fall back to MJPG
117
  raw_out_path = tempfile.mktemp(suffix="_raw.avi")
118
  fourcc = cv2.VideoWriter_fourcc(*"MJPG")
119
+ writer = cv2.VideoWriter(raw_out_path, fourcc, fps, (enc_w, enc_h))
120
 
121
  frame_idx = start_frame # absolute frame number in the source video
122
  processed = 0
 
158
  cached_tgt_faces = new_faces if new_faces else cached_tgt_faces
159
 
160
  if result_frame is not None:
161
+ # Ensure frame matches writer dimensions (even crop if needed)
162
+ rf_h, rf_w = result_frame.shape[:2]
163
+ if rf_w != enc_w or rf_h != enc_h:
164
+ result_frame = cv2.resize(result_frame, (enc_w, enc_h), interpolation=cv2.INTER_LINEAR)
165
  writer.write(result_frame)
166
  last_result = result_frame
167
  processed += 1
168
  else:
169
+ frm = frame[:enc_h, :enc_w] if (frame.shape[1] > enc_w or frame.shape[0] > enc_h) else frame
170
+ if frm.shape[1] != enc_w or frm.shape[0] != enc_h:
171
+ frm = cv2.resize(frm, (enc_w, enc_h), interpolation=cv2.INTER_LINEAR)
172
+ writer.write(frm)
173
+ last_result = frm
174
  errors += 1
175
 
176
  frame_idx += 1
 
275
 
276
  out_kwargs = dict(
277
  vcodec="libx264",
278
+ crf=18,
279
  preset="fast",
280
+ pix_fmt="yuv420p",
281
+ **{"vf": "unsharp=3:3:0.3:3:3:0.0"}, # subtle luma sharpening, no ringing
282
  )
283
  if has_audio:
284
  out_kwargs.update(acodec="aac", audio_bitrate="192k")