Add support for TIFF and improve output
This commit is contained in:
4
LICENSE
4
LICENSE
@@ -72,8 +72,8 @@ Please follow the instructions provided below:
|
|||||||
- Remove the subdirectories basicsr and facelib
|
- Remove the subdirectories basicsr and facelib
|
||||||
- Remove within weights subdirectory Codeformer and facelib.
|
- Remove within weights subdirectory Codeformer and facelib.
|
||||||
- Remove codeformer_wrapper.py
|
- Remove codeformer_wrapper.py
|
||||||
- Edit refacer.py and remove the import: codeformer_wrapper import enhance_image
|
- Edit refacer.py and remove the import: codeformer_wrapper
|
||||||
- Within def reface_image, comment the line: output_path = enhance_image(output_path)
|
- Adjust the code so that it doesn't calls the enhance functions from the commented wrapper
|
||||||
- That's all!
|
- That's all!
|
||||||
|
|
||||||
Failure to remove `codeformer` when required may violate the terms of its license.
|
Failure to remove `codeformer` when required may violate the terms of its license.
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -1,10 +1,10 @@
|
|||||||
<img src="https://raw.githubusercontent.com/MechasAI/NeoRefacer/main/icon.png"/>
|
<img src="https://raw.githubusercontent.com/MechasAI/NeoRefacer/main/icon.png"/>
|
||||||
|
|
||||||
# NeoRefacer: Images. GIFs. Full-length videos.
|
# NeoRefacer: Images. GIFs. TIFFs. Full-length videos.
|
||||||
|
|
||||||
In a future where identity flows like data and reality is just another layer, NeoRefacer gives you the power to transform.
|
In a future where identity flows like data and reality is just another layer, NeoRefacer gives you the power to transform.
|
||||||
|
|
||||||
Images. GIFs. Full-length videos.
|
Images. GIFs. TIFFs. Full-length videos.
|
||||||
|
|
||||||
All yours to reface and reimagine - with a single pulse of electricity.
|
All yours to reface and reimagine - with a single pulse of electricity.
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ Evolved from the foundations of the [Refacer](https://github.com/xaviviro/reface
|
|||||||
[OFFICIAL WEBSITE](https://www.mechas.ai/projects-neorefacer.php)
|
[OFFICIAL WEBSITE](https://www.mechas.ai/projects-neorefacer.php)
|
||||||
|
|
||||||
## Core DNA of NeoRefacer
|
## Core DNA of NeoRefacer
|
||||||
* **Instant Identity Shift** - Swap faces in images, GIFs, and movies faster than your neural implants can blink.
|
* **Instant Identity Shift** - Swap faces in images, GIFs, multi-page TIFFs and movies faster than your neural implants can blink.
|
||||||
* **Overclocked Engine** - Optimized for CPU rebels and GPU warlords.
|
* **Overclocked Engine** - Optimized for CPU rebels and GPU warlords.
|
||||||
* **Feature Film Reface** - Not just TikToks. Full two-hour cinematic overthrows.
|
* **Feature Film Reface** - Not just TikToks. Full two-hour cinematic overthrows.
|
||||||
* **Targeted Strike Modes** - Single-face raids, multi-face takeovers, or precision-targeted matchups.
|
* **Targeted Strike Modes** - Single-face raids, multi-face takeovers, or precision-targeted matchups.
|
||||||
@@ -37,7 +37,7 @@ Evolved from the foundations of the [Refacer](https://github.com/xaviviro/reface
|
|||||||
|
|
||||||
## What's New (Since Refacer)
|
## What's New (Since Refacer)
|
||||||
|
|
||||||
* Image, GIF and Video reface modes
|
* Image, GIF, TIFF and Video reface modes
|
||||||
* Significantly faster processing
|
* Significantly faster processing
|
||||||
* Automatic image enhancing (Image mode)
|
* Automatic image enhancing (Image mode)
|
||||||
* Improved video output quality
|
* Improved video output quality
|
||||||
@@ -48,8 +48,10 @@ Evolved from the foundations of the [Refacer](https://github.com/xaviviro/reface
|
|||||||
* **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.
|
||||||
* Improved GPU detection
|
* Improved GPU detection
|
||||||
|
* Support for multi-page TIFF
|
||||||
* Uses local Gradio cache with auto-cleanup on startup
|
* Uses local Gradio cache with auto-cleanup on startup
|
||||||
* Includes a bulk image refacer utility (refacer_bulk.py)
|
* Includes a bulk image refacer utility (refacer_bulk.py)
|
||||||
|
* Videos and images are saved to the root of /output, and GIFs are saved to /output/gifs and previews are saved to /output/preview subdirectory
|
||||||
|
|
||||||
NeoRefacer, just like the original Refacer project, requires no training - just one photo and you're ready to go.
|
NeoRefacer, just like the original Refacer project, requires no training - just one photo and you're ready to go.
|
||||||
|
|
||||||
|
|||||||
97
app.py
97
app.py
@@ -106,17 +106,29 @@ 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
|
# Check if video is too large
|
||||||
if isvideo:
|
if isvideo:
|
||||||
if os.path.getsize(filepath) > 5 * 1024 * 1024: # larger than 5MB
|
if os.path.getsize(filepath) > 5 * 1024 * 1024: # larger than 5MB
|
||||||
print("Video too large for auto-extract, skipping face extraction.")
|
print("Video too large for auto-extract, skipping face extraction.")
|
||||||
return [None] * max_faces
|
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
|
||||||
|
|
||||||
# Create manual temp image inside ./tmp
|
print("Loaded frame shape:", frame.shape)
|
||||||
|
|
||||||
|
# Handle weird TIFF/multipage dimensions
|
||||||
|
while len(frame.shape) > 3:
|
||||||
|
frame = frame[0] # Keep taking the first slice until (H, W, C)
|
||||||
|
|
||||||
|
print("Fixed frame shape:", frame.shape)
|
||||||
|
|
||||||
|
if frame.shape[-1] != 3:
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -271,6 +283,87 @@ with gr.Blocks(theme=theme, title="NeoRefacer - AI Refacer") as demo:
|
|||||||
outputs=[gif_preview]
|
outputs=[gif_preview]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# --- TIF MODE ---
|
||||||
|
with gr.Tab("TIFF Mode"):
|
||||||
|
with gr.Row():
|
||||||
|
tif_input = gr.File(label="Original TIF", file_types=[".tif", ".tiff"])
|
||||||
|
tif_preview = gr.Image(label="TIF Preview (Cover Page)", type="filepath")
|
||||||
|
tif_output_preview = gr.Image(label="Refaced TIF Preview (Cover Page)", type="filepath")
|
||||||
|
tif_output_file = gr.File(label="Refaced TIF (Download)", interactive=False)
|
||||||
|
|
||||||
|
with gr.Row():
|
||||||
|
face_mode_tif = gr.Radio(
|
||||||
|
choices=["Single Face", "Multiple Faces", "Faces By Match"],
|
||||||
|
value="Single Face",
|
||||||
|
label="Replacement Mode"
|
||||||
|
)
|
||||||
|
tif_btn = gr.Button("Reface TIF", variant="primary")
|
||||||
|
|
||||||
|
origin_tif, destination_tif, thresholds_tif, face_tabs_tif = [], [], [], []
|
||||||
|
|
||||||
|
for i in range(num_faces):
|
||||||
|
with gr.Tab(f"Face #{i+1}") as tab:
|
||||||
|
with gr.Row():
|
||||||
|
origin = gr.Image(label="Face to replace")
|
||||||
|
destination = gr.Image(label="Destination face")
|
||||||
|
threshold = gr.Slider(label="Threshold", minimum=0.0, maximum=1.0, value=0.2)
|
||||||
|
origin_tif.append(origin)
|
||||||
|
destination_tif.append(destination)
|
||||||
|
thresholds_tif.append(threshold)
|
||||||
|
face_tabs_tif.append(tab)
|
||||||
|
|
||||||
|
face_mode_tif.change(
|
||||||
|
fn=lambda mode: toggle_tabs_and_faces(mode, face_tabs_tif, origin_tif),
|
||||||
|
inputs=[face_mode_tif],
|
||||||
|
outputs=face_tabs_tif + origin_tif
|
||||||
|
)
|
||||||
|
|
||||||
|
demo.load(
|
||||||
|
fn=lambda: toggle_tabs_and_faces("Single Face", face_tabs_tif, origin_tif),
|
||||||
|
inputs=None,
|
||||||
|
outputs=face_tabs_tif + origin_tif
|
||||||
|
)
|
||||||
|
|
||||||
|
def process_tif(tif_path, *vars):
|
||||||
|
original_img = Image.open(tif_path)
|
||||||
|
if hasattr(original_img, "n_frames") and original_img.n_frames > 1:
|
||||||
|
original_img.seek(0)
|
||||||
|
temp_preview_path = os.path.join("./tmp", f"tif_preview_{int(time.time() * 1000)}.jpg")
|
||||||
|
original_img.convert('RGB').save(temp_preview_path)
|
||||||
|
|
||||||
|
refaced_path = run_image(tif_path, *vars)
|
||||||
|
|
||||||
|
refaced_img = Image.open(refaced_path)
|
||||||
|
if hasattr(refaced_img, "n_frames") and refaced_img.n_frames > 1:
|
||||||
|
refaced_img.seek(0)
|
||||||
|
temp_refaced_preview_path = os.path.join("./tmp", f"refaced_tif_preview_{int(time.time() * 1000)}.jpg")
|
||||||
|
refaced_img.convert('RGB').save(temp_refaced_preview_path)
|
||||||
|
|
||||||
|
return temp_preview_path, temp_refaced_preview_path, refaced_path
|
||||||
|
|
||||||
|
tif_btn.click(
|
||||||
|
fn=lambda tif_path, *args: process_tif(tif_path, *args),
|
||||||
|
inputs=[tif_input] + origin_tif + destination_tif + thresholds_tif + [face_mode_tif],
|
||||||
|
outputs=[tif_preview, tif_output_preview, tif_output_file]
|
||||||
|
)
|
||||||
|
|
||||||
|
tif_input.change(
|
||||||
|
fn=lambda filepath: extract_faces_auto(filepath, refacer, max_faces=num_faces),
|
||||||
|
inputs=tif_input,
|
||||||
|
outputs=origin_tif
|
||||||
|
)
|
||||||
|
|
||||||
|
tif_input.change(
|
||||||
|
fn=lambda filepath: (
|
||||||
|
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,
|
||||||
|
outputs=tif_preview
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# --- VIDEO MODE ---
|
# --- VIDEO MODE ---
|
||||||
with gr.Tab("Video Mode"):
|
with gr.Tab("Video Mode"):
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
# codeformer_wrapper.py
|
|
||||||
# Copyright (c) 2022 Shangchen Zhou
|
|
||||||
# Modifications and additions copyright (c) 2025 Felipe Daragon
|
|
||||||
|
|
||||||
# License: CC BY-NC-SA 4.0 (https://github.com/felipedaragon/codeformer/blob/main/README.md)
|
|
||||||
# Same as the original code by Shangchen Zhou.
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import torch
|
import torch
|
||||||
import cv2
|
import cv2
|
||||||
|
import numpy as np
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from torchvision.transforms.functional import normalize
|
from torchvision.transforms.functional import normalize
|
||||||
from basicsr.utils import img2tensor, tensor2img
|
from basicsr.utils import img2tensor, tensor2img
|
||||||
@@ -15,10 +9,8 @@ from basicsr.utils.download_util import load_file_from_url
|
|||||||
from facelib.utils.face_restoration_helper import FaceRestoreHelper
|
from facelib.utils.face_restoration_helper import FaceRestoreHelper
|
||||||
from basicsr.utils.registry import ARCH_REGISTRY
|
from basicsr.utils.registry import ARCH_REGISTRY
|
||||||
|
|
||||||
# Prepare device
|
|
||||||
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
||||||
|
|
||||||
# Load CodeFormer model once
|
|
||||||
pretrain_model_url = {
|
pretrain_model_url = {
|
||||||
'restoration': 'https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/codeformer.pth',
|
'restoration': 'https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/codeformer.pth',
|
||||||
}
|
}
|
||||||
@@ -32,9 +24,8 @@ checkpoint = torch.load(ckpt_path)['params_ema']
|
|||||||
net.load_state_dict(checkpoint)
|
net.load_state_dict(checkpoint)
|
||||||
net.eval()
|
net.eval()
|
||||||
|
|
||||||
# Load helper
|
|
||||||
face_helper = FaceRestoreHelper(
|
face_helper = FaceRestoreHelper(
|
||||||
upscale_factor=1, # No background upscaling
|
upscale_factor=1,
|
||||||
face_size=512,
|
face_size=512,
|
||||||
crop_ratio=(1, 1),
|
crop_ratio=(1, 1),
|
||||||
det_model='retinaface_resnet50',
|
det_model='retinaface_resnet50',
|
||||||
@@ -43,36 +34,18 @@ face_helper = FaceRestoreHelper(
|
|||||||
device=device
|
device=device
|
||||||
)
|
)
|
||||||
|
|
||||||
def enhance_image(input_image_path: str, w: float = 0.5) -> str:
|
def _enhance_img(img: np.ndarray, w: float = 0.5) -> np.ndarray:
|
||||||
"""
|
"""
|
||||||
Enhances an input image using CodeFormer and saves it with a '.enhanced.jpg' suffix.
|
Internal helper to enhance a numpy image with CodeFormer.
|
||||||
|
|
||||||
Args:
|
|
||||||
input_image_path (str): Path to the input image (JPG or PNG).
|
|
||||||
w (float): Balance quality and fidelity (default=0.5).
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: Path to the enhanced image.
|
|
||||||
"""
|
"""
|
||||||
input_path = Path(input_image_path)
|
|
||||||
output_path = input_path.with_name(f"{input_path.stem}.enhanced.jpg")
|
|
||||||
|
|
||||||
# Clean previous state
|
|
||||||
face_helper.clean_all()
|
face_helper.clean_all()
|
||||||
|
|
||||||
# Load image
|
|
||||||
img = cv2.imread(str(input_path), cv2.IMREAD_COLOR)
|
|
||||||
if img is None:
|
|
||||||
raise ValueError(f"Cannot read image: {input_image_path}")
|
|
||||||
|
|
||||||
face_helper.read_image(img)
|
face_helper.read_image(img)
|
||||||
num_faces = face_helper.get_face_landmarks_5(only_center_face=False, resize=640, eye_dist_threshold=5)
|
num_faces = face_helper.get_face_landmarks_5(only_center_face=False, resize=640, eye_dist_threshold=5)
|
||||||
if num_faces == 0:
|
if num_faces == 0:
|
||||||
raise ValueError(f"No faces detected in: {input_image_path}")
|
return img # Return original if no faces detected
|
||||||
|
|
||||||
face_helper.align_warp_face()
|
face_helper.align_warp_face()
|
||||||
|
|
||||||
# Enhance each face
|
|
||||||
for cropped_face in face_helper.cropped_faces:
|
for cropped_face in face_helper.cropped_faces:
|
||||||
cropped_face_t = img2tensor(cropped_face / 255., bgr2rgb=True, float32=True)
|
cropped_face_t = img2tensor(cropped_face / 255., bgr2rgb=True, float32=True)
|
||||||
normalize(cropped_face_t, (0.5, 0.5, 0.5), (0.5, 0.5, 0.5), inplace=True)
|
normalize(cropped_face_t, (0.5, 0.5, 0.5), (0.5, 0.5, 0.5), inplace=True)
|
||||||
@@ -85,13 +58,30 @@ def enhance_image(input_image_path: str, w: float = 0.5) -> str:
|
|||||||
restored_face = restored_face.astype('uint8')
|
restored_face = restored_face.astype('uint8')
|
||||||
face_helper.add_restored_face(restored_face)
|
face_helper.add_restored_face(restored_face)
|
||||||
|
|
||||||
# Paste faces back
|
|
||||||
face_helper.get_inverse_affine(None)
|
face_helper.get_inverse_affine(None)
|
||||||
restored_img = face_helper.paste_faces_to_input_image()
|
restored_img = face_helper.paste_faces_to_input_image()
|
||||||
|
return restored_img
|
||||||
|
|
||||||
|
def enhance_image(input_image_path: str, w: float = 0.5) -> str:
|
||||||
|
"""
|
||||||
|
Enhances an input image using CodeFormer and saves it with a '.enhanced.jpg' suffix.
|
||||||
|
"""
|
||||||
|
input_path = Path(input_image_path)
|
||||||
|
output_path = input_path.with_name(f"{input_path.stem}.enhanced.jpg")
|
||||||
|
|
||||||
|
img = cv2.imread(str(input_path), cv2.IMREAD_COLOR)
|
||||||
|
if img is None:
|
||||||
|
raise ValueError(f"Cannot read image: {input_image_path}")
|
||||||
|
|
||||||
|
restored_img = _enhance_img(img, w=w)
|
||||||
|
|
||||||
# Save output
|
|
||||||
os.makedirs(output_path.parent, exist_ok=True)
|
os.makedirs(output_path.parent, exist_ok=True)
|
||||||
cv2.imwrite(str(output_path), restored_img)
|
cv2.imwrite(str(output_path), restored_img)
|
||||||
|
|
||||||
print(f"Enhanced image saved to: {output_path}")
|
print(f"Enhanced image saved to: {output_path}")
|
||||||
return str(output_path)
|
return str(output_path)
|
||||||
|
|
||||||
|
def enhance_image_memory(img: np.ndarray, w: float = 0.5) -> np.ndarray:
|
||||||
|
"""
|
||||||
|
Enhances an input image entirely in memory and returns the enhanced image.
|
||||||
|
"""
|
||||||
|
return _enhance_img(img, w=w)
|
||||||
|
|||||||
56
refacer.py
56
refacer.py
@@ -22,7 +22,7 @@ import subprocess
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import time
|
import time
|
||||||
from codeformer_wrapper import enhance_image
|
from codeformer_wrapper import enhance_image, enhance_image_memory
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
gc = __import__('gc')
|
gc = __import__('gc')
|
||||||
@@ -235,8 +235,14 @@ class Refacer:
|
|||||||
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:
|
||||||
|
os.makedirs("output/preview", exist_ok=True)
|
||||||
|
output_video_path = os.path.join('output', 'preview', filename)
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -275,13 +281,18 @@ class Refacer:
|
|||||||
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"):
|
||||||
gif_output_path = converted_path.replace(".mp4", ".gif")
|
if preview:
|
||||||
|
gif_output_path = os.path.join("output", "preview", os.path.basename(converted_path).replace(".mp4", ".gif"))
|
||||||
|
else:
|
||||||
|
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)
|
||||||
print(f"Generating GIF at {gif_output_path}")
|
print(f"Generating GIF at {gif_output_path}")
|
||||||
(
|
(
|
||||||
ffmpeg
|
ffmpeg
|
||||||
@@ -307,6 +318,44 @@ class Refacer:
|
|||||||
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)
|
||||||
|
|
||||||
|
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)
|
bgr_image = cv2.imread(image_path)
|
||||||
if bgr_image is None:
|
if bgr_image is None:
|
||||||
raise ValueError("Failed to read input image")
|
raise ValueError("Failed to read input image")
|
||||||
@@ -314,9 +363,6 @@ class Refacer:
|
|||||||
refaced_bgr = self.process_first_face(bgr_image.copy()) if self.first_face else self.process_faces(bgr_image.copy())
|
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)
|
refaced_rgb = cv2.cvtColor(refaced_bgr, cv2.COLOR_BGR2RGB)
|
||||||
pil_img = Image.fromarray(refaced_rgb)
|
pil_img = Image.fromarray(refaced_rgb)
|
||||||
os.makedirs("output", exist_ok=True)
|
|
||||||
original_name = osp.splitext(osp.basename(image_path))[0]
|
|
||||||
timestamp = str(int(time.time()))
|
|
||||||
filename = f"{original_name}_{timestamp}.jpg"
|
filename = f"{original_name}_{timestamp}.jpg"
|
||||||
output_path = os.path.join("output", filename)
|
output_path = os.path.join("output", filename)
|
||||||
pil_img.save(output_path, format='JPEG', quality=100, subsampling=0)
|
pil_img.save(output_path, format='JPEG', quality=100, subsampling=0)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
ffmpeg_python==0.2.0
|
ffmpeg_python==0.2.0
|
||||||
imageio[ffmpeg]==2.37.0
|
imageio[ffmpeg]==2.37.0
|
||||||
|
imagecodecs==2025.3.30
|
||||||
gradio==5.22.0
|
gradio==5.22.0
|
||||||
insightface==0.7.3
|
insightface==0.7.3
|
||||||
numpy==1.24.3
|
numpy==1.24.3
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
ffmpeg_python==0.2.0
|
ffmpeg_python==0.2.0
|
||||||
imageio[ffmpeg]==2.37.0
|
imageio[ffmpeg]==2.37.0
|
||||||
|
imagecodecs==2025.3.30
|
||||||
gradio==5.22.0
|
gradio==5.22.0
|
||||||
insightface==0.7.3
|
insightface==0.7.3
|
||||||
numpy==1.24.3
|
numpy==1.24.3
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
ffmpeg_python==0.2.0
|
ffmpeg_python==0.2.0
|
||||||
imageio[ffmpeg]==2.37.0
|
imageio[ffmpeg]==2.37.0
|
||||||
|
imagecodecs==2025.3.30
|
||||||
gradio==5.22.0
|
gradio==5.22.0
|
||||||
insightface==0.7.3
|
insightface==0.7.3
|
||||||
numpy==1.24.3
|
numpy==1.24.3
|
||||||
|
|||||||
Reference in New Issue
Block a user