Adds reface ratio option
This commit is contained in:
@@ -47,6 +47,7 @@ Evolved from the foundations of the [Refacer](https://github.com/xaviviro/reface
|
|||||||
* **Single Face** (Fast): all faces are replaced with a single face. Ideal for images, GIFs or videos with a single face
|
* **Single Face** (Fast): all faces are replaced with a single face. Ideal for images, GIFs or videos with a single face
|
||||||
* **Multiple Faces** (Fast): faces are replaced with the faces you provide based on their order from left to right
|
* **Multiple Faces** (Fast): faces are replaced with the faces you provide based on their order from left to right
|
||||||
* **Faces by Match** (Slower): faces are first detected and replaced with the faces you provide.
|
* **Faces by Match** (Slower): faces are first detected and replaced with the faces you provide.
|
||||||
|
* Reface ratio: full face to half face.
|
||||||
* Improved GPU detection
|
* Improved GPU detection
|
||||||
* Support for multi-page TIFF
|
* Support for multi-page TIFF
|
||||||
* Uses local Gradio cache with auto-cleanup on startup
|
* Uses local Gradio cache with auto-cleanup on startup
|
||||||
|
|||||||
135
app.py
135
app.py
@@ -56,8 +56,9 @@ def run_image(*vars):
|
|||||||
image_path = vars[0]
|
image_path = vars[0]
|
||||||
origins = vars[1:(num_faces+1)]
|
origins = vars[1:(num_faces+1)]
|
||||||
destinations = vars[(num_faces+1):(num_faces*2)+1]
|
destinations = vars[(num_faces+1):(num_faces*2)+1]
|
||||||
thresholds = vars[(num_faces*2)+1:-1]
|
thresholds = vars[(num_faces*2)+1:-2]
|
||||||
face_mode = vars[-1]
|
face_mode = vars[-2]
|
||||||
|
partial_reface_ratio = vars[-1]
|
||||||
|
|
||||||
disable_similarity = (face_mode in ["Single Face", "Multiple Faces"])
|
disable_similarity = (face_mode in ["Single Face", "Multiple Faces"])
|
||||||
multiple_faces_mode = (face_mode == "Multiple Faces")
|
multiple_faces_mode = (face_mode == "Multiple Faces")
|
||||||
@@ -71,15 +72,16 @@ def run_image(*vars):
|
|||||||
'threshold': thresholds[k] if not multiple_faces_mode else 0.0
|
'threshold': thresholds[k] if not multiple_faces_mode else 0.0
|
||||||
})
|
})
|
||||||
|
|
||||||
return refacer.reface_image(image_path, faces, disable_similarity=disable_similarity, multiple_faces_mode=multiple_faces_mode)
|
return refacer.reface_image(image_path, faces, disable_similarity=disable_similarity, multiple_faces_mode=multiple_faces_mode, partial_reface_ratio=partial_reface_ratio)
|
||||||
|
|
||||||
def run(*vars):
|
def run(*vars):
|
||||||
video_path = vars[0]
|
video_path = vars[0]
|
||||||
origins = vars[1:(num_faces+1)]
|
origins = vars[1:(num_faces+1)]
|
||||||
destinations = vars[(num_faces+1):(num_faces*2)+1]
|
destinations = vars[(num_faces+1):(num_faces*2)+1]
|
||||||
thresholds = vars[(num_faces*2)+1:-2]
|
thresholds = vars[(num_faces*2)+1:-3]
|
||||||
preview = vars[-2]
|
preview = vars[-3]
|
||||||
face_mode = vars[-1]
|
face_mode = vars[-2]
|
||||||
|
partial_reface_ratio = vars[-1]
|
||||||
|
|
||||||
disable_similarity = (face_mode in ["Single Face", "Multiple Faces"])
|
disable_similarity = (face_mode in ["Single Face", "Multiple Faces"])
|
||||||
multiple_faces_mode = (face_mode == "Multiple Faces")
|
multiple_faces_mode = (face_mode == "Multiple Faces")
|
||||||
@@ -93,7 +95,7 @@ def run(*vars):
|
|||||||
'threshold': thresholds[k] if not multiple_faces_mode else 0.0
|
'threshold': thresholds[k] if not multiple_faces_mode else 0.0
|
||||||
})
|
})
|
||||||
|
|
||||||
mp4_path, gif_path = refacer.reface(video_path, faces, preview=preview, disable_similarity=disable_similarity, multiple_faces_mode=multiple_faces_mode)
|
mp4_path, gif_path = refacer.reface(video_path, faces, preview=preview, disable_similarity=disable_similarity, multiple_faces_mode=multiple_faces_mode, partial_reface_ratio=partial_reface_ratio)
|
||||||
return mp4_path, gif_path if gif_path else None
|
return mp4_path, gif_path if gif_path else None
|
||||||
|
|
||||||
def load_first_frame(filepath):
|
def load_first_frame(filepath):
|
||||||
@@ -106,36 +108,26 @@ def extract_faces_auto(filepath, refacer_instance, max_faces=5, isvideo=False):
|
|||||||
if filepath is None:
|
if filepath is None:
|
||||||
return [None] * max_faces
|
return [None] * max_faces
|
||||||
|
|
||||||
# Check if video is too large
|
if isvideo and os.path.getsize(filepath) > 5 * 1024 * 1024:
|
||||||
if isvideo:
|
print("Video too large for auto-extract, skipping face extraction.")
|
||||||
if os.path.getsize(filepath) > 5 * 1024 * 1024: # larger than 5MB
|
return [None] * max_faces
|
||||||
print("Video too large for auto-extract, skipping face extraction.")
|
|
||||||
return [None] * max_faces
|
|
||||||
|
|
||||||
# Load first frame
|
|
||||||
frame = load_first_frame(filepath)
|
frame = load_first_frame(filepath)
|
||||||
if frame is None:
|
if frame is None:
|
||||||
return [None] * max_faces
|
return [None] * max_faces
|
||||||
|
|
||||||
print("Loaded frame shape:", frame.shape)
|
|
||||||
|
|
||||||
# Handle weird TIFF/multipage dimensions
|
|
||||||
while len(frame.shape) > 3:
|
while len(frame.shape) > 3:
|
||||||
frame = frame[0] # Keep taking the first slice until (H, W, C)
|
frame = frame[0]
|
||||||
|
|
||||||
print("Fixed frame shape:", frame.shape)
|
|
||||||
|
|
||||||
if frame.shape[-1] != 3:
|
if frame.shape[-1] != 3:
|
||||||
raise ValueError(f"Expected last dimension to be 3 (RGB), but got {frame.shape[-1]}")
|
raise ValueError(f"Expected last dimension to be 3 (RGB), but got {frame.shape[-1]}")
|
||||||
|
|
||||||
# Create temp image inside ./tmp
|
|
||||||
temp_image_path = os.path.join("./tmp", f"temp_face_extract_{int(time.time() * 1000)}.png")
|
temp_image_path = os.path.join("./tmp", f"temp_face_extract_{int(time.time() * 1000)}.png")
|
||||||
Image.fromarray(frame).save(temp_image_path)
|
Image.fromarray(frame).save(temp_image_path)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
faces = refacer_instance.extract_faces_from_image(temp_image_path, max_faces=max_faces)
|
faces = refacer_instance.extract_faces_from_image(temp_image_path, max_faces=max_faces)
|
||||||
output_faces = faces + [None] * (max_faces - len(faces))
|
return faces + [None] * (max_faces - len(faces))
|
||||||
return output_faces
|
|
||||||
finally:
|
finally:
|
||||||
if os.path.exists(temp_image_path):
|
if os.path.exists(temp_image_path):
|
||||||
try:
|
try:
|
||||||
@@ -154,9 +146,15 @@ def toggle_tabs_and_faces(mode, face_tabs, origin_faces):
|
|||||||
tab_updates = [gr.update(visible=True) for _ in range(len(face_tabs))]
|
tab_updates = [gr.update(visible=True) for _ in range(len(face_tabs))]
|
||||||
origin_updates = [gr.update(visible=True) for _ in range(len(origin_faces))]
|
origin_updates = [gr.update(visible=True) for _ in range(len(origin_faces))]
|
||||||
return tab_updates + origin_updates
|
return tab_updates + origin_updates
|
||||||
|
|
||||||
|
def handle_tif_preview(filepath):
|
||||||
|
if filepath is None:
|
||||||
|
return None
|
||||||
|
preview_path = os.path.join("./tmp", f"tif_preview_{int(time.time() * 1000)}.jpg")
|
||||||
|
Image.open(filepath).convert('RGB').save(preview_path)
|
||||||
|
return preview_path
|
||||||
|
|
||||||
# --- UI ---
|
# --- UI ---
|
||||||
|
|
||||||
theme = gr.themes.Base(primary_hue="blue", secondary_hue="cyan")
|
theme = gr.themes.Base(primary_hue="blue", secondary_hue="cyan")
|
||||||
|
|
||||||
with gr.Blocks(theme=theme, title="NeoRefacer - AI Refacer") as demo:
|
with gr.Blocks(theme=theme, title="NeoRefacer - AI Refacer") as demo:
|
||||||
@@ -179,11 +177,8 @@ with gr.Blocks(theme=theme, title="NeoRefacer - AI Refacer") as demo:
|
|||||||
image_output = gr.Image(label="Refaced image", interactive=False, type="filepath")
|
image_output = gr.Image(label="Refaced image", interactive=False, type="filepath")
|
||||||
|
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
face_mode_image = gr.Radio(
|
face_mode_image = gr.Radio(["Single Face", "Multiple Faces", "Faces By Match"], value="Single Face", label="Replacement Mode")
|
||||||
choices=["Single Face", "Multiple Faces", "Faces By Match"],
|
partial_reface_ratio_image = gr.Slider(label="Reface Ratio (0 = Full Face, 0.5 = Half Face)", minimum=0.0, maximum=0.5, value=0.0, step=0.1)
|
||||||
value="Single Face",
|
|
||||||
label="Replacement Mode"
|
|
||||||
)
|
|
||||||
image_btn = gr.Button("Reface Image", variant="primary")
|
image_btn = gr.Button("Reface Image", variant="primary")
|
||||||
|
|
||||||
origin_image, destination_image, thresholds_image, face_tabs_image = [], [], [], []
|
origin_image, destination_image, thresholds_image, face_tabs_image = [], [], [], []
|
||||||
@@ -199,29 +194,12 @@ with gr.Blocks(theme=theme, title="NeoRefacer - AI Refacer") as demo:
|
|||||||
thresholds_image.append(threshold)
|
thresholds_image.append(threshold)
|
||||||
face_tabs_image.append(tab)
|
face_tabs_image.append(tab)
|
||||||
|
|
||||||
face_mode_image.change(
|
face_mode_image.change(fn=lambda mode: toggle_tabs_and_faces(mode, face_tabs_image, origin_image), inputs=[face_mode_image], outputs=face_tabs_image + origin_image)
|
||||||
fn=lambda mode: toggle_tabs_and_faces(mode, face_tabs_image, origin_image),
|
demo.load(fn=lambda: toggle_tabs_and_faces("Single Face", face_tabs_image, origin_image), inputs=None, outputs=face_tabs_image + origin_image)
|
||||||
inputs=[face_mode_image],
|
|
||||||
outputs=face_tabs_image + origin_image
|
|
||||||
)
|
|
||||||
|
|
||||||
demo.load(
|
image_btn.click(fn=run_image, inputs=[image_input] + origin_image + destination_image + thresholds_image + [face_mode_image, partial_reface_ratio_image], outputs=[image_output])
|
||||||
fn=lambda: toggle_tabs_and_faces("Single Face", face_tabs_image, origin_image),
|
image_input.change(fn=lambda filepath: extract_faces_auto(filepath, refacer, max_faces=num_faces), inputs=image_input, outputs=origin_image)
|
||||||
inputs=None,
|
image_input.change(fn=lambda _: 0.0, inputs=image_input, outputs=partial_reface_ratio_image)
|
||||||
outputs=face_tabs_image + origin_image
|
|
||||||
)
|
|
||||||
|
|
||||||
image_btn.click(
|
|
||||||
fn=run_image,
|
|
||||||
inputs=[image_input] + origin_image + destination_image + thresholds_image + [face_mode_image],
|
|
||||||
outputs=[image_output]
|
|
||||||
)
|
|
||||||
|
|
||||||
image_input.change(
|
|
||||||
fn=lambda filepath: extract_faces_auto(filepath, refacer, max_faces=num_faces),
|
|
||||||
inputs=image_input,
|
|
||||||
outputs=origin_image
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- GIF MODE ---
|
# --- GIF MODE ---
|
||||||
with gr.Tab("GIF Mode"):
|
with gr.Tab("GIF Mode"):
|
||||||
@@ -232,14 +210,10 @@ with gr.Blocks(theme=theme, title="NeoRefacer - AI Refacer") as demo:
|
|||||||
gif_file_output = gr.Image(label="Refaced GIF (GIF)", type="filepath")
|
gif_file_output = gr.Image(label="Refaced GIF (GIF)", type="filepath")
|
||||||
|
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
face_mode_gif = gr.Radio(
|
face_mode_gif = gr.Radio(["Single Face", "Multiple Faces", "Faces By Match"], value="Single Face", label="Replacement Mode")
|
||||||
choices=["Single Face", "Multiple Faces", "Faces By Match"],
|
partial_reface_ratio_gif = gr.Slider(label="Reface Ratio (0 = Full Face, 0.5 = Half Face)", minimum=0.0, maximum=0.5, value=0.0, step=0.1)
|
||||||
value="Single Face",
|
|
||||||
label="Replacement Mode"
|
|
||||||
)
|
|
||||||
gif_btn = gr.Button("Reface GIF", variant="primary")
|
gif_btn = gr.Button("Reface GIF", variant="primary")
|
||||||
|
preview_checkbox_gif = gr.Checkbox(label="Preview Generation (skip 90% of frames)", value=False)
|
||||||
preview_checkbox_gif = gr.Checkbox(label="Preview Generation (skip 90% of frames)", value=False)
|
|
||||||
|
|
||||||
origin_gif, destination_gif, thresholds_gif, face_tabs_gif = [], [], [], []
|
origin_gif, destination_gif, thresholds_gif, face_tabs_gif = [], [], [], []
|
||||||
|
|
||||||
@@ -254,34 +228,15 @@ with gr.Blocks(theme=theme, title="NeoRefacer - AI Refacer") as demo:
|
|||||||
thresholds_gif.append(threshold)
|
thresholds_gif.append(threshold)
|
||||||
face_tabs_gif.append(tab)
|
face_tabs_gif.append(tab)
|
||||||
|
|
||||||
face_mode_gif.change(
|
face_mode_gif.change(fn=lambda mode: toggle_tabs_and_faces(mode, face_tabs_gif, origin_gif), inputs=[face_mode_gif], outputs=face_tabs_gif + origin_gif)
|
||||||
fn=lambda mode: toggle_tabs_and_faces(mode, face_tabs_gif, origin_gif),
|
demo.load(fn=lambda: toggle_tabs_and_faces("Single Face", face_tabs_gif, origin_gif), inputs=None, outputs=face_tabs_gif + origin_gif)
|
||||||
inputs=[face_mode_gif],
|
|
||||||
outputs=face_tabs_gif + origin_gif
|
|
||||||
)
|
|
||||||
|
|
||||||
demo.load(
|
gif_btn.click(fn=run, inputs=[gif_input] + origin_gif + destination_gif + thresholds_gif + [preview_checkbox_gif, face_mode_gif, partial_reface_ratio_gif], outputs=[gif_output, gif_file_output])
|
||||||
fn=lambda: toggle_tabs_and_faces("Single Face", face_tabs_gif, origin_gif),
|
|
||||||
inputs=None,
|
|
||||||
outputs=face_tabs_gif + origin_gif
|
|
||||||
)
|
|
||||||
|
|
||||||
gif_btn.click(
|
gif_input.change(fn=lambda filepath: extract_faces_auto(filepath, refacer, max_faces=num_faces), inputs=gif_input, outputs=origin_gif)
|
||||||
fn=lambda *args: run(*args),
|
gif_input.change(fn=lambda file: file, inputs=gif_input, outputs=[gif_preview])
|
||||||
inputs=[gif_input] + origin_gif + destination_gif + thresholds_gif + [preview_checkbox_gif, face_mode_gif],
|
gif_input.change(fn=lambda _: 0.0, inputs=gif_input, outputs=partial_reface_ratio_gif)
|
||||||
outputs=[gif_output, gif_file_output]
|
|
||||||
)
|
|
||||||
|
|
||||||
gif_input.change(
|
|
||||||
fn=lambda filepath: extract_faces_auto(filepath, refacer, max_faces=num_faces),
|
|
||||||
inputs=gif_input,
|
|
||||||
outputs=origin_gif
|
|
||||||
)
|
|
||||||
gif_input.change(
|
|
||||||
fn=lambda file: file,
|
|
||||||
inputs=gif_input,
|
|
||||||
outputs=[gif_preview]
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- TIF MODE ---
|
# --- TIF MODE ---
|
||||||
with gr.Tab("TIFF Mode"):
|
with gr.Tab("TIFF Mode"):
|
||||||
@@ -297,6 +252,7 @@ with gr.Blocks(theme=theme, title="NeoRefacer - AI Refacer") as demo:
|
|||||||
value="Single Face",
|
value="Single Face",
|
||||||
label="Replacement Mode"
|
label="Replacement Mode"
|
||||||
)
|
)
|
||||||
|
partial_reface_ratio_tif = gr.Slider(label="Reface Ratio (0 = Full Face, 0.5 = Half Face)", minimum=0.0, maximum=0.5, value=0.0, step=0.1)
|
||||||
tif_btn = gr.Button("Reface TIF", variant="primary")
|
tif_btn = gr.Button("Reface TIF", variant="primary")
|
||||||
|
|
||||||
origin_tif, destination_tif, thresholds_tif, face_tabs_tif = [], [], [], []
|
origin_tif, destination_tif, thresholds_tif, face_tabs_tif = [], [], [], []
|
||||||
@@ -343,7 +299,7 @@ with gr.Blocks(theme=theme, title="NeoRefacer - AI Refacer") as demo:
|
|||||||
|
|
||||||
tif_btn.click(
|
tif_btn.click(
|
||||||
fn=lambda tif_path, *args: process_tif(tif_path, *args),
|
fn=lambda tif_path, *args: process_tif(tif_path, *args),
|
||||||
inputs=[tif_input] + origin_tif + destination_tif + thresholds_tif + [face_mode_tif],
|
inputs=[tif_input] + origin_tif + destination_tif + thresholds_tif + [face_mode_tif, partial_reface_ratio_tif],
|
||||||
outputs=[tif_preview, tif_output_preview, tif_output_file]
|
outputs=[tif_preview, tif_output_preview, tif_output_file]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -354,14 +310,12 @@ with gr.Blocks(theme=theme, title="NeoRefacer - AI Refacer") as demo:
|
|||||||
)
|
)
|
||||||
|
|
||||||
tif_input.change(
|
tif_input.change(
|
||||||
fn=lambda filepath: (
|
fn=handle_tif_preview,
|
||||||
Image.open(filepath).convert('RGB').save(
|
|
||||||
(preview_path := os.path.join("./tmp", f"tif_preview_{int(time.time() * 1000)}.jpg"))
|
|
||||||
) or preview_path
|
|
||||||
),
|
|
||||||
inputs=tif_input,
|
inputs=tif_input,
|
||||||
outputs=tif_preview
|
outputs=tif_preview
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tif_input.change(fn=lambda _: 0.0, inputs=tif_input, outputs=partial_reface_ratio_tif)
|
||||||
|
|
||||||
|
|
||||||
# --- VIDEO MODE ---
|
# --- VIDEO MODE ---
|
||||||
@@ -376,6 +330,7 @@ with gr.Blocks(theme=theme, title="NeoRefacer - AI Refacer") as demo:
|
|||||||
value="Single Face",
|
value="Single Face",
|
||||||
label="Replacement Mode"
|
label="Replacement Mode"
|
||||||
)
|
)
|
||||||
|
partial_reface_ratio_video = gr.Slider(label="Reface Ratio (0 = Full Face, 0.5 = Half Face)", minimum=0.0, maximum=0.5, value=0.0, step=0.1)
|
||||||
video_btn = gr.Button("Reface Video", variant="primary")
|
video_btn = gr.Button("Reface Video", variant="primary")
|
||||||
|
|
||||||
preview_checkbox_video = gr.Checkbox(label="Preview Generation (skip 90% of frames)", value=False)
|
preview_checkbox_video = gr.Checkbox(label="Preview Generation (skip 90% of frames)", value=False)
|
||||||
@@ -410,10 +365,12 @@ with gr.Blocks(theme=theme, title="NeoRefacer - AI Refacer") as demo:
|
|||||||
inputs=video_input,
|
inputs=video_input,
|
||||||
outputs=origin_video
|
outputs=origin_video
|
||||||
)
|
)
|
||||||
|
|
||||||
|
video_input.change(fn=lambda _: 0.0, inputs=video_input, outputs=partial_reface_ratio_video)
|
||||||
|
|
||||||
video_btn.click(
|
video_btn.click(
|
||||||
fn=lambda *args: run(*args),
|
fn=lambda *args: run(*args),
|
||||||
inputs=[video_input] + origin_video + destination_video + thresholds_video + [preview_checkbox_video, face_mode_video],
|
inputs=[video_input] + origin_video + destination_video + thresholds_video + [preview_checkbox_video, face_mode_video, partial_reface_ratio_video],
|
||||||
outputs=[video_output, gr.File(visible=False)]
|
outputs=[video_output, gr.File(visible=False)]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
215
refacer.py
215
refacer.py
@@ -55,6 +55,46 @@ class Refacer:
|
|||||||
self.__check_providers()
|
self.__check_providers()
|
||||||
self.total_mem = psutil.virtual_memory().total
|
self.total_mem = psutil.virtual_memory().total
|
||||||
self.__init_apps()
|
self.__init_apps()
|
||||||
|
|
||||||
|
def _partial_face_blend(self, original_frame, swapped_frame, face):
|
||||||
|
h_frame, w_frame = original_frame.shape[:2]
|
||||||
|
|
||||||
|
x1, y1, x2, y2 = map(int, face.bbox)
|
||||||
|
x1 = max(0, min(x1, w_frame-1))
|
||||||
|
y1 = max(0, min(y1, h_frame-1))
|
||||||
|
x2 = max(0, min(x2, w_frame))
|
||||||
|
y2 = max(0, min(y2, h_frame))
|
||||||
|
|
||||||
|
if x2 <= x1 or y2 <= y1:
|
||||||
|
print(f"Invalid bbox: {x1},{y1},{x2},{y2}")
|
||||||
|
return swapped_frame
|
||||||
|
|
||||||
|
w = x2 - x1
|
||||||
|
h = y2 - y1
|
||||||
|
cutoff = int(h * (1.0 - self.blend_height_ratio))
|
||||||
|
|
||||||
|
swap_crop = swapped_frame[y1:y2, x1:x2].copy()
|
||||||
|
orig_crop = original_frame[y1:y2, x1:x2].copy()
|
||||||
|
|
||||||
|
mask = np.ones((h, w, 3), dtype=np.float32)
|
||||||
|
transition = 40
|
||||||
|
|
||||||
|
if cutoff < h:
|
||||||
|
blend_start = max(cutoff - transition // 2, 0)
|
||||||
|
blend_end = min(cutoff + transition // 2, h)
|
||||||
|
|
||||||
|
if blend_end > blend_start:
|
||||||
|
alpha = np.linspace(1.0, 0.0, blend_end - blend_start)[:, np.newaxis, np.newaxis]
|
||||||
|
mask[blend_start:blend_end, :, :] = alpha
|
||||||
|
mask[blend_end:, :, :] = 0.0
|
||||||
|
|
||||||
|
blended_crop = (swap_crop.astype(np.float32) * mask + orig_crop.astype(np.float32) * (1.0 - mask)).astype(np.uint8)
|
||||||
|
|
||||||
|
blended_frame = swapped_frame.copy()
|
||||||
|
blended_frame[y1:y2, x1:x2] = blended_crop
|
||||||
|
|
||||||
|
return blended_frame
|
||||||
|
|
||||||
|
|
||||||
def __download_with_progress(self, url, output_path):
|
def __download_with_progress(self, url, output_path):
|
||||||
response = requests.get(url, stream=True)
|
response = requests.get(url, stream=True)
|
||||||
@@ -182,33 +222,53 @@ class Refacer:
|
|||||||
faces = self.__get_faces(frame, max_num=0)
|
faces = self.__get_faces(frame, max_num=0)
|
||||||
if not faces:
|
if not faces:
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
if self.disable_similarity:
|
if self.disable_similarity:
|
||||||
for face in faces:
|
for face in faces:
|
||||||
frame = self.face_swapper.get(frame, face, self.replacement_faces[0][1], paste_back=True)
|
swapped = self.face_swapper.get(frame, face, self.replacement_faces[0][1], paste_back=True)
|
||||||
|
if hasattr(self, 'partial_reface_ratio') and self.partial_reface_ratio > 0.0:
|
||||||
|
self.blend_height_ratio = self.partial_reface_ratio
|
||||||
|
frame = self._partial_face_blend(frame, swapped, face)
|
||||||
|
else:
|
||||||
|
frame = swapped
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
def process_faces(self, frame):
|
def process_faces(self, frame):
|
||||||
faces = self.__get_faces(frame, max_num=0)
|
faces = self.__get_faces(frame, max_num=0)
|
||||||
if not faces:
|
if not faces:
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
faces = sorted(faces, key=lambda face: face.bbox[0]) # Sort left to right
|
faces = sorted(faces, key=lambda face: face.bbox[0])
|
||||||
|
|
||||||
if self.multiple_faces_mode:
|
if self.multiple_faces_mode:
|
||||||
for idx, face in enumerate(faces):
|
for idx, face in enumerate(faces):
|
||||||
if idx >= len(self.replacement_faces):
|
if idx >= len(self.replacement_faces):
|
||||||
break
|
break
|
||||||
frame = self.face_swapper.get(frame, face, self.replacement_faces[idx][1], paste_back=True)
|
swapped = self.face_swapper.get(frame, face, self.replacement_faces[idx][1], paste_back=True)
|
||||||
|
if hasattr(self, 'partial_reface_ratio') and self.partial_reface_ratio > 0.0:
|
||||||
|
self.blend_height_ratio = self.partial_reface_ratio
|
||||||
|
frame = self._partial_face_blend(frame, swapped, face)
|
||||||
|
else:
|
||||||
|
frame = swapped
|
||||||
elif self.disable_similarity:
|
elif self.disable_similarity:
|
||||||
for face in faces:
|
for face in faces:
|
||||||
frame = self.face_swapper.get(frame, face, self.replacement_faces[0][1], paste_back=True)
|
swapped = self.face_swapper.get(frame, face, self.replacement_faces[0][1], paste_back=True)
|
||||||
|
if hasattr(self, 'partial_reface_ratio') and self.partial_reface_ratio > 0.0:
|
||||||
|
self.blend_height_ratio = self.partial_reface_ratio
|
||||||
|
frame = self._partial_face_blend(frame, swapped, face)
|
||||||
|
else:
|
||||||
|
frame = swapped
|
||||||
else:
|
else:
|
||||||
for rep_face in self.replacement_faces:
|
for rep_face in self.replacement_faces:
|
||||||
for i in range(len(faces) - 1, -1, -1):
|
for i in range(len(faces) - 1, -1, -1):
|
||||||
sim = self.rec_app.compute_sim(rep_face[0], faces[i].embedding)
|
sim = self.rec_app.compute_sim(rep_face[0], faces[i].embedding)
|
||||||
if sim >= rep_face[2]:
|
if sim >= rep_face[2]:
|
||||||
frame = self.face_swapper.get(frame, faces[i], rep_face[1], paste_back=True)
|
swapped = self.face_swapper.get(frame, faces[i], rep_face[1], paste_back=True)
|
||||||
|
if hasattr(self, 'partial_reface_ratio') and self.partial_reface_ratio > 0.0:
|
||||||
|
self.blend_height_ratio = self.partial_reface_ratio
|
||||||
|
frame = self._partial_face_blend(frame, swapped, faces[i])
|
||||||
|
else:
|
||||||
|
frame = swapped
|
||||||
del faces[i]
|
del faces[i]
|
||||||
break
|
break
|
||||||
return frame
|
return frame
|
||||||
@@ -229,36 +289,37 @@ class Refacer:
|
|||||||
if audio_stream is not None:
|
if audio_stream is not None:
|
||||||
self.video_has_audio = True
|
self.video_has_audio = True
|
||||||
|
|
||||||
def reface(self, video_path, faces, preview=False, disable_similarity=False, multiple_faces_mode=False):
|
def reface(self, video_path, faces, preview=False, disable_similarity=False, multiple_faces_mode=False, partial_reface_ratio=0.0):
|
||||||
original_name = osp.splitext(osp.basename(video_path))[0]
|
original_name = osp.splitext(osp.basename(video_path))[0]
|
||||||
timestamp = str(int(time.time()))
|
timestamp = str(int(time.time()))
|
||||||
filename = f"{original_name}_preview.mp4" if preview else f"{original_name}_{timestamp}.mp4"
|
filename = f"{original_name}_preview.mp4" if preview else f"{original_name}_{timestamp}.mp4"
|
||||||
|
|
||||||
self.__check_video_has_audio(video_path)
|
self.__check_video_has_audio(video_path)
|
||||||
|
|
||||||
if preview:
|
if preview:
|
||||||
os.makedirs("output/preview", exist_ok=True)
|
os.makedirs("output/preview", exist_ok=True)
|
||||||
output_video_path = os.path.join('output', 'preview', filename)
|
output_video_path = os.path.join('output', 'preview', filename)
|
||||||
else:
|
else:
|
||||||
os.makedirs("output", exist_ok=True)
|
os.makedirs("output", exist_ok=True)
|
||||||
output_video_path = os.path.join('output', filename)
|
output_video_path = os.path.join('output', filename)
|
||||||
|
|
||||||
self.prepare_faces(faces, disable_similarity=disable_similarity, multiple_faces_mode=multiple_faces_mode)
|
self.prepare_faces(faces, disable_similarity=disable_similarity, multiple_faces_mode=multiple_faces_mode)
|
||||||
self.first_face = False if multiple_faces_mode else (faces[0].get("origin") is None or disable_similarity)
|
self.first_face = False if multiple_faces_mode else (faces[0].get("origin") is None or disable_similarity)
|
||||||
|
self.partial_reface_ratio = partial_reface_ratio
|
||||||
|
|
||||||
cap = cv2.VideoCapture(video_path, cv2.CAP_FFMPEG)
|
cap = cv2.VideoCapture(video_path, cv2.CAP_FFMPEG)
|
||||||
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||||
fps = cap.get(cv2.CAP_PROP_FPS)
|
fps = cap.get(cv2.CAP_PROP_FPS)
|
||||||
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||||
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||||
|
|
||||||
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
||||||
output = cv2.VideoWriter(output_video_path, fourcc, fps, (frame_width, frame_height))
|
output = cv2.VideoWriter(output_video_path, fourcc, fps, (frame_width, frame_height))
|
||||||
|
|
||||||
frames = []
|
frames = []
|
||||||
frame_index = 0
|
frame_index = 0
|
||||||
skip_rate = 10 if preview else 1
|
skip_rate = 10 if preview else 1
|
||||||
|
|
||||||
with tqdm(total=total_frames, desc="Extracting frames") as pbar:
|
with tqdm(total=total_frames, desc="Extracting frames") as pbar:
|
||||||
while cap.isOpened():
|
while cap.isOpened():
|
||||||
flag, frame = cap.read()
|
flag, frame = cap.read()
|
||||||
@@ -272,24 +333,28 @@ class Refacer:
|
|||||||
gc.collect()
|
gc.collect()
|
||||||
frame_index += 1
|
frame_index += 1
|
||||||
pbar.update()
|
pbar.update()
|
||||||
|
|
||||||
cap.release()
|
cap.release()
|
||||||
if frames:
|
if frames:
|
||||||
self.reface_group(faces, frames, output)
|
self.reface_group(faces, frames, output)
|
||||||
output.release()
|
output.release()
|
||||||
|
|
||||||
converted_path = self.__convert_video(video_path, output_video_path, preview=preview)
|
converted_path = self.__convert_video(video_path, output_video_path, preview=preview)
|
||||||
|
|
||||||
if video_path.lower().endswith(".gif"):
|
if video_path.lower().endswith(".gif"):
|
||||||
if preview:
|
if preview:
|
||||||
gif_output_path = os.path.join("output", "preview", os.path.basename(converted_path).replace(".mp4", ".gif"))
|
gif_output_path = os.path.join("output", "preview", os.path.basename(converted_path).replace(".mp4", ".gif"))
|
||||||
else:
|
else:
|
||||||
gif_output_path = os.path.join("output", "gifs", os.path.basename(converted_path).replace(".mp4", ".gif"))
|
gif_output_path = os.path.join("output", "gifs", os.path.basename(converted_path).replace(".mp4", ".gif"))
|
||||||
|
|
||||||
self.__generate_gif(converted_path, gif_output_path)
|
self.__generate_gif(converted_path, gif_output_path)
|
||||||
return converted_path, gif_output_path
|
return converted_path, gif_output_path
|
||||||
|
|
||||||
return converted_path, None
|
return converted_path, None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __generate_gif(self, video_path, gif_output_path):
|
def __generate_gif(self, video_path, gif_output_path):
|
||||||
os.makedirs(os.path.dirname(gif_output_path), exist_ok=True)
|
os.makedirs(os.path.dirname(gif_output_path), exist_ok=True)
|
||||||
@@ -314,61 +379,61 @@ class Refacer:
|
|||||||
print(f"Refaced video saved at: {os.path.abspath(new_path)}")
|
print(f"Refaced video saved at: {os.path.abspath(new_path)}")
|
||||||
return new_path
|
return new_path
|
||||||
|
|
||||||
def reface_image(self, image_path, faces, disable_similarity=False, multiple_faces_mode=False):
|
def reface_image(self, image_path, faces, disable_similarity=False, multiple_faces_mode=False, partial_reface_ratio=0.0):
|
||||||
self.prepare_faces(faces, disable_similarity=disable_similarity, multiple_faces_mode=multiple_faces_mode)
|
self.prepare_faces(faces, disable_similarity=disable_similarity, multiple_faces_mode=multiple_faces_mode)
|
||||||
self.first_face = False if multiple_faces_mode else (faces[0].get("origin") is None or disable_similarity)
|
self.first_face = False if multiple_faces_mode else (faces[0].get("origin") is None or disable_similarity)
|
||||||
|
self.partial_reface_ratio = partial_reface_ratio
|
||||||
|
|
||||||
|
ext = osp.splitext(image_path)[1].lower()
|
||||||
|
os.makedirs("output", exist_ok=True)
|
||||||
|
original_name = osp.splitext(osp.basename(image_path))[0]
|
||||||
|
timestamp = str(int(time.time()))
|
||||||
|
|
||||||
|
if ext in ['.tif', '.tiff']:
|
||||||
|
pil_img = Image.open(image_path)
|
||||||
|
frames = []
|
||||||
|
|
||||||
|
page_count = 0
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
pil_img.seek(page_count)
|
||||||
|
page_count += 1
|
||||||
|
except EOFError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
pil_img = Image.open(image_path)
|
||||||
|
|
||||||
|
with tqdm(total=page_count, desc="Processing TIFF pages") as pbar:
|
||||||
|
for page in range(page_count):
|
||||||
|
pil_img.seek(page)
|
||||||
|
bgr_image = cv2.cvtColor(np.array(pil_img.convert('RGB')), cv2.COLOR_RGB2BGR)
|
||||||
|
refaced_bgr = self.process_first_face(bgr_image.copy()) if self.first_face else self.process_faces(bgr_image.copy())
|
||||||
|
enhanced_bgr = enhance_image_memory(refaced_bgr)
|
||||||
|
enhanced_rgb = cv2.cvtColor(enhanced_bgr, cv2.COLOR_BGR2RGB)
|
||||||
|
enhanced_pil = Image.fromarray(enhanced_rgb)
|
||||||
|
frames.append(enhanced_pil)
|
||||||
|
pbar.update(1)
|
||||||
|
|
||||||
|
output_path = os.path.join("output", f"{original_name}_{timestamp}.tif")
|
||||||
|
frames[0].save(output_path, save_all=True, append_images=frames[1:], compression="tiff_deflate")
|
||||||
|
print(f"Saved multipage refaced TIFF to {output_path}")
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
else:
|
||||||
|
bgr_image = cv2.imread(image_path)
|
||||||
|
if bgr_image is None:
|
||||||
|
raise ValueError("Failed to read input image")
|
||||||
|
|
||||||
|
refaced_bgr = self.process_first_face(bgr_image.copy()) if self.first_face else self.process_faces(bgr_image.copy())
|
||||||
|
refaced_rgb = cv2.cvtColor(refaced_bgr, cv2.COLOR_BGR2RGB)
|
||||||
|
pil_img = Image.fromarray(refaced_rgb)
|
||||||
|
filename = f"{original_name}_{timestamp}.jpg"
|
||||||
|
output_path = os.path.join("output", filename)
|
||||||
|
pil_img.save(output_path, format='JPEG', quality=100, subsampling=0)
|
||||||
|
output_path = enhance_image(output_path)
|
||||||
|
print(f"Saved refaced image to {output_path}")
|
||||||
|
return output_path
|
||||||
|
|
||||||
ext = osp.splitext(image_path)[1].lower()
|
|
||||||
os.makedirs("output", exist_ok=True)
|
|
||||||
original_name = osp.splitext(osp.basename(image_path))[0]
|
|
||||||
timestamp = str(int(time.time()))
|
|
||||||
|
|
||||||
if ext in ['.tif', '.tiff']:
|
|
||||||
pil_img = Image.open(image_path)
|
|
||||||
frames = []
|
|
||||||
|
|
||||||
# First, count pages
|
|
||||||
page_count = 0
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
pil_img.seek(page_count)
|
|
||||||
page_count += 1
|
|
||||||
except EOFError:
|
|
||||||
pass # End of pages
|
|
||||||
|
|
||||||
# Re-open to start real processing
|
|
||||||
pil_img = Image.open(image_path)
|
|
||||||
|
|
||||||
with tqdm(total=page_count, desc="Processing TIFF pages") as pbar:
|
|
||||||
for page in range(page_count):
|
|
||||||
pil_img.seek(page)
|
|
||||||
bgr_image = cv2.cvtColor(np.array(pil_img.convert('RGB')), cv2.COLOR_RGB2BGR)
|
|
||||||
refaced_bgr = self.process_first_face(bgr_image.copy()) if self.first_face else self.process_faces(bgr_image.copy())
|
|
||||||
enhanced_bgr = enhance_image_memory(refaced_bgr)
|
|
||||||
enhanced_rgb = cv2.cvtColor(enhanced_bgr, cv2.COLOR_BGR2RGB)
|
|
||||||
enhanced_pil = Image.fromarray(enhanced_rgb)
|
|
||||||
frames.append(enhanced_pil)
|
|
||||||
pbar.update(1)
|
|
||||||
|
|
||||||
output_path = os.path.join("output", f"{original_name}_{timestamp}.tif")
|
|
||||||
frames[0].save(output_path, save_all=True, append_images=frames[1:], compression="tiff_deflate")
|
|
||||||
print(f"Saved multipage refaced TIFF to {output_path}")
|
|
||||||
return output_path
|
|
||||||
|
|
||||||
else:
|
|
||||||
bgr_image = cv2.imread(image_path)
|
|
||||||
if bgr_image is None:
|
|
||||||
raise ValueError("Failed to read input image")
|
|
||||||
|
|
||||||
refaced_bgr = self.process_first_face(bgr_image.copy()) if self.first_face else self.process_faces(bgr_image.copy())
|
|
||||||
refaced_rgb = cv2.cvtColor(refaced_bgr, cv2.COLOR_BGR2RGB)
|
|
||||||
pil_img = Image.fromarray(refaced_rgb)
|
|
||||||
filename = f"{original_name}_{timestamp}.jpg"
|
|
||||||
output_path = os.path.join("output", filename)
|
|
||||||
pil_img.save(output_path, format='JPEG', quality=100, subsampling=0)
|
|
||||||
output_path = enhance_image(output_path)
|
|
||||||
print(f"Saved refaced image to {output_path}")
|
|
||||||
return output_path
|
|
||||||
|
|
||||||
def extract_faces_from_image(self, image_path, max_faces=5):
|
def extract_faces_from_image(self, image_path, max_faces=5):
|
||||||
frame = cv2.imread(image_path)
|
frame = cv2.imread(image_path)
|
||||||
|
|||||||
Reference in New Issue
Block a user