Spaces:
Running
Running
Commit Β·
1c23235
1
Parent(s): 4a9cb39
Fix video glitches: OpenCV-only enhance for video, even dimensions for XVID, softer unsharp
Browse files- app.py +2 -2
- processors/face_swap.py +4 -1
- processors/video_processor.py +17 -7
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=
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 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, (
|
| 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, (
|
| 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 |
-
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
| 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,
|
| 269 |
preset="fast",
|
| 270 |
-
pix_fmt="yuv420p",
|
| 271 |
-
**{"vf": "unsharp=
|
| 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")
|