mirror of
https://github.com/facefusion/facefusion.git
synced 2026-08-23 17:22:30 +02:00
Next (#144)
* Improve typing for our callbacks * Return 0 for get_download_size * Introduce ONNX powered face enhancer * Introduce ONNX powered face enhancer * Introduce ONNX powered face enhancer * Remove tile processing from frame enhancer * Fix video compress translation for libvpx-vp9 * Allow zero values for video compression * Develop (#134) * Introduce model options to the frame processors * Finish UI to select frame processors models * Simplify frame processors options * Fix lint in CI * Rename all kind of settings to options * Add blend to enhancers * Simplify webcam mode naming * Bypass SSL issues under Windows * Fix blend of frame enhancer * Massive CLI refactoring, Register and apply ARGS via the frame processors * Refine UI theme and introduce donate button * Update dependencies and fix cpu only torch * Update dependencies and fix cpu only torch * Fix theme, Fix frame_processors in headless mode * Remove useless astype * Disable CoreML for the ONNX face enhancer * Disable CoreML for the ONNX face enhancer * Predict webcam too * Improve resize of preview * Change output quality defaults, Move options to the right * Support for codeformer model * Update the typo * Add GPEN and GFPGAN 1.2 * Extract blend_frame methods * Extend the installer * Revert broken Gradio * Rework on ui components * Move output path selector to the output options * Remove tons of pointless component updates * Reset more base theme styling * Use latest Gradio * Fix the sliders * More styles * Update torch to 2.1.0 * Add RealESRNet_x4plus * Fix that button * Use latest onnxruntime-silicon * Looks stable to me * Lowercase model keys, Update preview and readme
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
from typing import List
|
||||
|
||||
face_swapper_models : List[str] = [ 'inswapper_128', 'inswapper_128_fp16' ]
|
||||
face_enhancer_models : List[str] = [ 'codeformer', 'gfpgan_1.2', 'gfpgan_1.3', 'gfpgan_1.4', 'gpen_bfr_512' ]
|
||||
frame_enhancer_models : List[str] = [ 'realesrgan_x2plus', 'realesrgan_x4plus', 'realesrnet_x4plus' ]
|
||||
@@ -5,17 +5,22 @@ import psutil
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from queue import Queue
|
||||
from types import ModuleType
|
||||
from typing import Any, List, Callable
|
||||
from typing import Any, List
|
||||
from tqdm import tqdm
|
||||
|
||||
import facefusion.globals
|
||||
from facefusion import wording
|
||||
from facefusion.typing import Process_Frames
|
||||
|
||||
FRAME_PROCESSORS_MODULES : List[ModuleType] = []
|
||||
FRAME_PROCESSORS_METHODS =\
|
||||
[
|
||||
'get_frame_processor',
|
||||
'clear_frame_processor',
|
||||
'get_options',
|
||||
'set_options',
|
||||
'register_args',
|
||||
'apply_args',
|
||||
'pre_check',
|
||||
'pre_process',
|
||||
'process_frame',
|
||||
@@ -57,7 +62,7 @@ def clear_frame_processors_modules() -> None:
|
||||
FRAME_PROCESSORS_MODULES = []
|
||||
|
||||
|
||||
def multi_process_frames(source_path : str, temp_frame_paths : List[str], process_frames : Callable[[str, List[str], Callable[[], None]], None]) -> None:
|
||||
def multi_process_frames(source_path : str, temp_frame_paths : List[str], process_frames : Process_Frames) -> None:
|
||||
progress_bar_format = '{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}{postfix}]'
|
||||
with tqdm(total = len(temp_frame_paths), desc = wording.get('processing'), unit = 'frame', dynamic_ncols = True, bar_format = progress_bar_format) as progress:
|
||||
with ThreadPoolExecutor(max_workers = facefusion.globals.execution_thread_count) as executor:
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
from typing import Optional
|
||||
|
||||
face_swapper_model : Optional[str] = None
|
||||
face_enhancer_model : Optional[str] = None
|
||||
face_enhancer_blend : Optional[int] = None
|
||||
frame_enhancer_model : Optional[str] = None
|
||||
frame_enhancer_blend : Optional[int] = None
|
||||
@@ -1,21 +1,53 @@
|
||||
from typing import Any, List, Callable
|
||||
from typing import Any, List, Tuple, Dict, Literal, Optional
|
||||
from argparse import ArgumentParser
|
||||
import cv2
|
||||
import threading
|
||||
from gfpgan.utils import GFPGANer
|
||||
import numpy
|
||||
import onnxruntime
|
||||
|
||||
import facefusion.globals
|
||||
from facefusion import wording, utilities
|
||||
from facefusion import wording
|
||||
from facefusion.core import update_status
|
||||
from facefusion.face_analyser import get_many_faces, clear_face_analyser
|
||||
from facefusion.typing import Frame, Face, ProcessMode
|
||||
from facefusion.typing import Face, Frame, Matrix, Update_Process, ProcessMode, ModelValue, OptionsWithModel
|
||||
from facefusion.utilities import conditional_download, resolve_relative_path, is_image, is_video, is_file, is_download_done
|
||||
from facefusion.vision import read_image, read_static_image, write_image
|
||||
from facefusion.processors.frame import globals as frame_processors_globals
|
||||
from facefusion.processors.frame import choices as frame_processors_choices
|
||||
|
||||
FRAME_PROCESSOR = None
|
||||
THREAD_SEMAPHORE : threading.Semaphore = threading.Semaphore()
|
||||
THREAD_LOCK : threading.Lock = threading.Lock()
|
||||
NAME = 'FACEFUSION.FRAME_PROCESSOR.FACE_ENHANCER'
|
||||
MODEL_URL = 'https://github.com/facefusion/facefusion-assets/releases/download/models/GFPGANv1.4.pth'
|
||||
MODEL_PATH = resolve_relative_path('../.assets/models/GFPGANv1.4.pth')
|
||||
MODELS : Dict[str, ModelValue] =\
|
||||
{
|
||||
'codeformer':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/codeformer.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/codeformer.onnx')
|
||||
},
|
||||
'gfpgan_1.2':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/GFPGANv1.2.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/GFPGANv1.2.onnx')
|
||||
},
|
||||
'gfpgan_1.3':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/GFPGANv1.3.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/GFPGANv1.3.onnx')
|
||||
},
|
||||
'gfpgan_1.4':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/GFPGANv1.4.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/GFPGANv1.4.onnx')
|
||||
},
|
||||
'gpen_bfr_512':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/GPEN-BFR-512.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/GPEN-BFR-512.onnx')
|
||||
}
|
||||
}
|
||||
OPTIONS : Optional[OptionsWithModel] = None
|
||||
|
||||
|
||||
def get_frame_processor() -> Any:
|
||||
@@ -23,11 +55,8 @@ def get_frame_processor() -> Any:
|
||||
|
||||
with THREAD_LOCK:
|
||||
if FRAME_PROCESSOR is None:
|
||||
FRAME_PROCESSOR = GFPGANer(
|
||||
model_path = MODEL_PATH,
|
||||
upscale = 1,
|
||||
device = utilities.get_device(facefusion.globals.execution_providers)
|
||||
)
|
||||
model_path = get_options('model').get('path')
|
||||
FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = facefusion.globals.execution_providers)
|
||||
return FRAME_PROCESSOR
|
||||
|
||||
|
||||
@@ -37,18 +66,49 @@ def clear_frame_processor() -> None:
|
||||
FRAME_PROCESSOR = None
|
||||
|
||||
|
||||
def get_options(key : Literal[ 'model' ]) -> Any:
|
||||
global OPTIONS
|
||||
|
||||
if OPTIONS is None:
|
||||
OPTIONS =\
|
||||
{
|
||||
'model': MODELS[frame_processors_globals.face_enhancer_model]
|
||||
}
|
||||
return OPTIONS.get(key)
|
||||
|
||||
|
||||
def set_options(key : Literal[ 'model' ], value : Any) -> None:
|
||||
global OPTIONS
|
||||
|
||||
OPTIONS[key] = value
|
||||
|
||||
|
||||
def register_args(program : ArgumentParser) -> None:
|
||||
program.add_argument('--face-enhancer-model', help = wording.get('frame_processor_model_help'), dest = 'face_enhancer_model', default = 'gfpgan_1.4', choices = frame_processors_choices.face_enhancer_models)
|
||||
program.add_argument('--face-enhancer-blend', help = wording.get('frame_processor_blend_help'), dest= 'face_enhancer_blend', type = int, default= 100, choices = range(101), metavar = '[0-100]')
|
||||
|
||||
|
||||
def apply_args(program : ArgumentParser) -> None:
|
||||
args = program.parse_args()
|
||||
frame_processors_globals.face_enhancer_model = args.face_enhancer_model
|
||||
frame_processors_globals.face_enhancer_blend = args.face_enhancer_blend
|
||||
|
||||
|
||||
def pre_check() -> bool:
|
||||
if not facefusion.globals.skip_download:
|
||||
download_directory_path = resolve_relative_path('../.assets/models')
|
||||
conditional_download(download_directory_path, [ MODEL_URL ])
|
||||
model_url = get_options('model').get('url')
|
||||
conditional_download(download_directory_path, [ model_url ])
|
||||
return True
|
||||
|
||||
|
||||
def pre_process(mode : ProcessMode) -> bool:
|
||||
if not facefusion.globals.skip_download and not is_download_done(MODEL_URL, MODEL_PATH):
|
||||
model_url = get_options('model').get('url')
|
||||
model_path = get_options('model').get('path')
|
||||
if not facefusion.globals.skip_download and not is_download_done(model_url, model_path):
|
||||
update_status(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME)
|
||||
return False
|
||||
elif not is_file(MODEL_PATH):
|
||||
elif not is_file(model_path):
|
||||
update_status(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME)
|
||||
return False
|
||||
if mode in [ 'output', 'preview' ] and not is_image(facefusion.globals.target_path) and not is_video(facefusion.globals.target_path):
|
||||
@@ -66,22 +126,77 @@ def post_process() -> None:
|
||||
read_static_image.cache_clear()
|
||||
|
||||
|
||||
def enhance_face(target_face : Face, temp_frame : Frame) -> Frame:
|
||||
start_x, start_y, end_x, end_y = map(int, target_face['bbox'])
|
||||
padding_x = int((end_x - start_x) * 0.5)
|
||||
padding_y = int((end_y - start_y) * 0.5)
|
||||
start_x = max(0, start_x - padding_x)
|
||||
start_y = max(0, start_y - padding_y)
|
||||
end_x = max(0, end_x + padding_x)
|
||||
end_y = max(0, end_y + padding_y)
|
||||
crop_frame = temp_frame[start_y:end_y, start_x:end_x]
|
||||
if crop_frame.size:
|
||||
with THREAD_SEMAPHORE:
|
||||
_, _, crop_frame = get_frame_processor().enhance(
|
||||
crop_frame,
|
||||
paste_back = True
|
||||
)
|
||||
temp_frame[start_y:end_y, start_x:end_x] = crop_frame
|
||||
def enhance_face(target_face: Face, temp_frame: Frame) -> Frame:
|
||||
frame_processor = get_frame_processor()
|
||||
crop_frame, affine_matrix = warp_face(target_face, temp_frame)
|
||||
crop_frame = prepare_crop_frame(crop_frame)
|
||||
frame_processor_inputs = {}
|
||||
for frame_processor_input in frame_processor.get_inputs():
|
||||
if frame_processor_input.name == 'input':
|
||||
frame_processor_inputs[frame_processor_input.name] = crop_frame
|
||||
if frame_processor_input.name == 'weight':
|
||||
frame_processor_inputs[frame_processor_input.name] = numpy.array([ 1 ], dtype = numpy.double)
|
||||
with THREAD_SEMAPHORE:
|
||||
crop_frame = frame_processor.run(None, frame_processor_inputs)[0][0]
|
||||
crop_frame = normalize_crop_frame(crop_frame)
|
||||
paste_frame = paste_back(temp_frame, crop_frame, affine_matrix)
|
||||
temp_frame = blend_frame(temp_frame, paste_frame)
|
||||
return temp_frame
|
||||
|
||||
|
||||
def warp_face(target_face : Face, temp_frame : Frame) -> Tuple[Frame, Matrix]:
|
||||
template = numpy.array(
|
||||
[
|
||||
[ 192.98138, 239.94708 ],
|
||||
[ 318.90277, 240.1936 ],
|
||||
[ 256.63416, 314.01935 ],
|
||||
[ 201.26117, 371.41043 ],
|
||||
[ 313.08905, 371.15118 ]
|
||||
])
|
||||
affine_matrix = cv2.estimateAffinePartial2D(target_face['kps'], template, method = cv2.LMEDS)[0]
|
||||
crop_frame = cv2.warpAffine(temp_frame, affine_matrix, (512, 512))
|
||||
return crop_frame, affine_matrix
|
||||
|
||||
|
||||
def prepare_crop_frame(crop_frame : Frame) -> Frame:
|
||||
crop_frame = crop_frame[:, :, ::-1] / 255.0
|
||||
crop_frame = (crop_frame - 0.5) / 0.5
|
||||
crop_frame = numpy.expand_dims(crop_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32)
|
||||
return crop_frame
|
||||
|
||||
|
||||
def normalize_crop_frame(crop_frame : Frame) -> Frame:
|
||||
crop_frame = numpy.clip(crop_frame, -1, 1)
|
||||
crop_frame = (crop_frame + 1) / 2
|
||||
crop_frame = crop_frame.transpose(1, 2, 0)
|
||||
crop_frame = (crop_frame * 255.0).round()
|
||||
crop_frame = crop_frame.astype(numpy.uint8)[:, :, ::-1]
|
||||
return crop_frame
|
||||
|
||||
|
||||
def paste_back(temp_frame : Frame, crop_frame : Frame, affine_matrix : Matrix) -> Frame:
|
||||
inverse_affine_matrix = cv2.invertAffineTransform(affine_matrix)
|
||||
temp_frame_height, temp_frame_width = temp_frame.shape[0:2]
|
||||
crop_frame_height, crop_frame_width = crop_frame.shape[0:2]
|
||||
inverse_crop_frame = cv2.warpAffine(crop_frame, inverse_affine_matrix, (temp_frame_width, temp_frame_height))
|
||||
inverse_mask = numpy.ones((crop_frame_height, crop_frame_width, 3), dtype = numpy.float32)
|
||||
inverse_mask_frame = cv2.warpAffine(inverse_mask, inverse_affine_matrix, (temp_frame_width, temp_frame_height))
|
||||
inverse_mask_frame = cv2.erode(inverse_mask_frame, numpy.ones((2, 2)))
|
||||
inverse_mask_border = inverse_mask_frame * inverse_crop_frame
|
||||
inverse_mask_area = numpy.sum(inverse_mask_frame) // 3
|
||||
inverse_mask_edge = int(inverse_mask_area ** 0.5) // 20
|
||||
inverse_mask_radius = inverse_mask_edge * 2
|
||||
inverse_mask_center = cv2.erode(inverse_mask_frame, numpy.ones((inverse_mask_radius, inverse_mask_radius)))
|
||||
inverse_mask_blur_size = inverse_mask_edge * 2 + 1
|
||||
inverse_mask_blur_area = cv2.GaussianBlur(inverse_mask_center, (inverse_mask_blur_size, inverse_mask_blur_size), 0)
|
||||
temp_frame = inverse_mask_blur_area * inverse_mask_border + (1 - inverse_mask_blur_area) * temp_frame
|
||||
temp_frame = temp_frame.clip(0, 255).astype(numpy.uint8)
|
||||
return temp_frame
|
||||
|
||||
|
||||
def blend_frame(temp_frame : Frame, paste_frame : Frame) -> Frame:
|
||||
face_enhancer_blend = 1 - (frame_processors_globals.face_enhancer_blend / 100)
|
||||
temp_frame = cv2.addWeighted(temp_frame, face_enhancer_blend, paste_frame, 1 - face_enhancer_blend, 0)
|
||||
return temp_frame
|
||||
|
||||
|
||||
@@ -93,7 +208,7 @@ def process_frame(source_face : Face, reference_face : Face, temp_frame : Frame)
|
||||
return temp_frame
|
||||
|
||||
|
||||
def process_frames(source_path : str, temp_frame_paths : List[str], update_progress: Callable[[], None]) -> None:
|
||||
def process_frames(source_path : str, temp_frame_paths : List[str], update_progress : Update_Process) -> None:
|
||||
for temp_frame_path in temp_frame_paths:
|
||||
temp_frame = read_image(temp_frame_path)
|
||||
result_frame = process_frame(None, None, temp_frame)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Any, List, Callable
|
||||
from typing import Any, List, Dict, Literal, Optional
|
||||
from argparse import ArgumentParser
|
||||
import insightface
|
||||
import threading
|
||||
|
||||
@@ -8,15 +9,29 @@ from facefusion import wording
|
||||
from facefusion.core import update_status
|
||||
from facefusion.face_analyser import get_one_face, get_many_faces, find_similar_faces, clear_face_analyser
|
||||
from facefusion.face_reference import get_face_reference, set_face_reference
|
||||
from facefusion.typing import Face, Frame, ProcessMode
|
||||
from facefusion.typing import Face, Frame, Update_Process, ProcessMode, ModelValue, OptionsWithModel
|
||||
from facefusion.utilities import conditional_download, resolve_relative_path, is_image, is_video, is_file, is_download_done
|
||||
from facefusion.vision import read_image, read_static_image, write_image
|
||||
from facefusion.processors.frame import globals as frame_processors_globals
|
||||
from facefusion.processors.frame import choices as frame_processors_choices
|
||||
|
||||
FRAME_PROCESSOR = None
|
||||
THREAD_LOCK : threading.Lock = threading.Lock()
|
||||
NAME = 'FACEFUSION.FRAME_PROCESSOR.FACE_SWAPPER'
|
||||
MODEL_URL = 'https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128.onnx'
|
||||
MODEL_PATH = resolve_relative_path('../.assets/models/inswapper_128.onnx')
|
||||
MODELS : Dict[str, ModelValue] =\
|
||||
{
|
||||
'inswapper_128':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/inswapper_128.onnx')
|
||||
},
|
||||
'inswapper_128_fp16':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128_fp16.onnx',
|
||||
'path': resolve_relative_path('../.assets/models/inswapper_128_fp16.onnx')
|
||||
}
|
||||
}
|
||||
OPTIONS : Optional[OptionsWithModel] = None
|
||||
|
||||
|
||||
def get_frame_processor() -> Any:
|
||||
@@ -24,7 +39,8 @@ def get_frame_processor() -> Any:
|
||||
|
||||
with THREAD_LOCK:
|
||||
if FRAME_PROCESSOR is None:
|
||||
FRAME_PROCESSOR = insightface.model_zoo.get_model(MODEL_PATH, providers = facefusion.globals.execution_providers)
|
||||
model_path = get_options('model').get('path')
|
||||
FRAME_PROCESSOR = insightface.model_zoo.get_model(model_path, providers = facefusion.globals.execution_providers)
|
||||
return FRAME_PROCESSOR
|
||||
|
||||
|
||||
@@ -34,18 +50,47 @@ def clear_frame_processor() -> None:
|
||||
FRAME_PROCESSOR = None
|
||||
|
||||
|
||||
def get_options(key : Literal[ 'model' ]) -> Any:
|
||||
global OPTIONS
|
||||
|
||||
if OPTIONS is None:
|
||||
OPTIONS = \
|
||||
{
|
||||
'model': MODELS[frame_processors_globals.face_swapper_model]
|
||||
}
|
||||
return OPTIONS.get(key)
|
||||
|
||||
|
||||
def set_options(key : Literal[ 'model' ], value : Any) -> None:
|
||||
global OPTIONS
|
||||
|
||||
OPTIONS[key] = value
|
||||
|
||||
|
||||
def register_args(program : ArgumentParser) -> None:
|
||||
program.add_argument('--face-swapper-model', help = wording.get('frame_processor_model_help'), dest = 'face_swapper_model', default = 'inswapper_128', choices = frame_processors_choices.face_swapper_models)
|
||||
|
||||
|
||||
def apply_args(program : ArgumentParser) -> None:
|
||||
args = program.parse_args()
|
||||
frame_processors_globals.face_swapper_model = args.face_swapper_model
|
||||
|
||||
|
||||
def pre_check() -> bool:
|
||||
if not facefusion.globals.skip_download:
|
||||
download_directory_path = resolve_relative_path('../.assets/models')
|
||||
conditional_download(download_directory_path, [ MODEL_URL ])
|
||||
model_url = get_options('model').get('url')
|
||||
conditional_download(download_directory_path, [ model_url ])
|
||||
return True
|
||||
|
||||
|
||||
def pre_process(mode : ProcessMode) -> bool:
|
||||
if not facefusion.globals.skip_download and not is_download_done(MODEL_URL, MODEL_PATH):
|
||||
model_url = get_options('model').get('url')
|
||||
model_path = get_options('model').get('path')
|
||||
if not facefusion.globals.skip_download and not is_download_done(model_url, model_path):
|
||||
update_status(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME)
|
||||
return False
|
||||
elif not is_file(MODEL_PATH):
|
||||
elif not is_file(model_path):
|
||||
update_status(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME)
|
||||
return False
|
||||
if not is_image(facefusion.globals.source_path):
|
||||
@@ -87,7 +132,7 @@ def process_frame(source_face : Face, reference_face : Face, temp_frame : Frame)
|
||||
return temp_frame
|
||||
|
||||
|
||||
def process_frames(source_path : str, temp_frame_paths : List[str], update_progress: Callable[[], None]) -> None:
|
||||
def process_frames(source_path : str, temp_frame_paths : List[str], update_progress : Update_Process) -> None:
|
||||
source_face = get_one_face(read_static_image(source_path))
|
||||
reference_face = get_face_reference() if 'reference' in facefusion.globals.face_recognition else None
|
||||
for temp_frame_path in temp_frame_paths:
|
||||
|
||||
@@ -1,23 +1,47 @@
|
||||
from typing import Any, List, Callable
|
||||
from typing import Any, List, Dict, Literal, Optional
|
||||
from argparse import ArgumentParser
|
||||
import threading
|
||||
import cv2
|
||||
from basicsr.archs.rrdbnet_arch import RRDBNet
|
||||
from realesrgan import RealESRGANer
|
||||
|
||||
import facefusion.globals
|
||||
import facefusion.processors.frame.core as frame_processors
|
||||
from facefusion import wording, utilities
|
||||
from facefusion import wording
|
||||
from facefusion.core import update_status
|
||||
from facefusion.face_analyser import clear_face_analyser
|
||||
from facefusion.typing import Frame, Face, ProcessMode
|
||||
from facefusion.utilities import conditional_download, resolve_relative_path, is_file, is_download_done
|
||||
from facefusion.typing import Frame, Face, Update_Process, ProcessMode, ModelValue, OptionsWithModel
|
||||
from facefusion.utilities import conditional_download, resolve_relative_path, is_file, is_download_done, get_device
|
||||
from facefusion.vision import read_image, read_static_image, write_image
|
||||
from facefusion.processors.frame import globals as frame_processors_globals
|
||||
from facefusion.processors.frame import choices as frame_processors_choices
|
||||
|
||||
FRAME_PROCESSOR = None
|
||||
THREAD_SEMAPHORE : threading.Semaphore = threading.Semaphore()
|
||||
THREAD_LOCK : threading.Lock = threading.Lock()
|
||||
NAME = 'FACEFUSION.FRAME_PROCESSOR.FRAME_ENHANCER'
|
||||
MODEL_URL = 'https://github.com/facefusion/facefusion-assets/releases/download/models/RealESRGAN_x4plus.pth'
|
||||
MODEL_PATH = resolve_relative_path('../.assets/models/RealESRGAN_x4plus.pth')
|
||||
MODELS: Dict[str, ModelValue] =\
|
||||
{
|
||||
'realesrgan_x2plus':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/RealESRGAN_x2plus.pth',
|
||||
'path': resolve_relative_path('../.assets/models/RealESRGAN_x2plus.pth'),
|
||||
'scale': 2
|
||||
},
|
||||
'realesrgan_x4plus':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/RealESRGAN_x4plus.pth',
|
||||
'path': resolve_relative_path('../.assets/models/RealESRGAN_x4plus.pth'),
|
||||
'scale': 4
|
||||
},
|
||||
'realesrnet_x4plus':
|
||||
{
|
||||
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/RealESRNet_x4plus.pth',
|
||||
'path': resolve_relative_path('../.assets/models/RealESRNet_x4plus.pth'),
|
||||
'scale': 4
|
||||
}
|
||||
}
|
||||
OPTIONS : Optional[OptionsWithModel] = None
|
||||
|
||||
|
||||
def get_frame_processor() -> Any:
|
||||
@@ -25,21 +49,17 @@ def get_frame_processor() -> Any:
|
||||
|
||||
with THREAD_LOCK:
|
||||
if FRAME_PROCESSOR is None:
|
||||
model_path = get_options('model').get('path')
|
||||
model_scale = get_options('model').get('scale')
|
||||
FRAME_PROCESSOR = RealESRGANer(
|
||||
model_path = MODEL_PATH,
|
||||
model_path = model_path,
|
||||
model = RRDBNet(
|
||||
num_in_ch = 3,
|
||||
num_out_ch = 3,
|
||||
num_feat = 64,
|
||||
num_block = 23,
|
||||
num_grow_ch = 32,
|
||||
scale = 4
|
||||
scale = model_scale
|
||||
),
|
||||
device = utilities.get_device(facefusion.globals.execution_providers),
|
||||
tile = 512,
|
||||
tile_pad = 32,
|
||||
pre_pad = 0,
|
||||
scale = 4
|
||||
device = get_device(facefusion.globals.execution_providers),
|
||||
scale = model_scale
|
||||
)
|
||||
return FRAME_PROCESSOR
|
||||
|
||||
@@ -50,18 +70,49 @@ def clear_frame_processor() -> None:
|
||||
FRAME_PROCESSOR = None
|
||||
|
||||
|
||||
def get_options(key : Literal[ 'model' ]) -> Any:
|
||||
global OPTIONS
|
||||
|
||||
if OPTIONS is None:
|
||||
OPTIONS = \
|
||||
{
|
||||
'model': MODELS[frame_processors_globals.frame_enhancer_model]
|
||||
}
|
||||
return OPTIONS.get(key)
|
||||
|
||||
|
||||
def set_options(key : Literal[ 'model' ], value : Any) -> None:
|
||||
global OPTIONS
|
||||
|
||||
OPTIONS[key] = value
|
||||
|
||||
|
||||
def register_args(program : ArgumentParser) -> None:
|
||||
program.add_argument('--frame-enhancer-model', help = wording.get('frame_processor_model_help'), dest = 'frame_enhancer_model', default = 'realesrgan_x2plus', choices = frame_processors_choices.frame_enhancer_models)
|
||||
program.add_argument('--frame-enhancer-blend', help = wording.get('frame_processor_blend_help'), dest = 'frame_enhancer_blend', type = int, default = 100, choices = range(101), metavar = '[0-100]')
|
||||
|
||||
|
||||
def apply_args(program : ArgumentParser) -> None:
|
||||
args = program.parse_args()
|
||||
frame_processors_globals.frame_enhancer_model = args.frame_enhancer_model
|
||||
frame_processors_globals.frame_enhancer_blend = args.frame_enhancer_blend
|
||||
|
||||
|
||||
def pre_check() -> bool:
|
||||
if not facefusion.globals.skip_download:
|
||||
download_directory_path = resolve_relative_path('../.assets/models')
|
||||
conditional_download(download_directory_path, [ MODEL_URL ])
|
||||
model_url = get_options('model').get('url')
|
||||
conditional_download(download_directory_path, [ model_url ])
|
||||
return True
|
||||
|
||||
|
||||
def pre_process(mode : ProcessMode) -> bool:
|
||||
if not facefusion.globals.skip_download and not is_download_done(MODEL_URL, MODEL_PATH):
|
||||
model_url = get_options('model').get('url')
|
||||
model_path = get_options('model').get('path')
|
||||
if not facefusion.globals.skip_download and not is_download_done(model_url, model_path):
|
||||
update_status(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME)
|
||||
return False
|
||||
elif not is_file(MODEL_PATH):
|
||||
elif not is_file(model_path):
|
||||
update_status(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME)
|
||||
return False
|
||||
if mode == 'output' and not facefusion.globals.output_path:
|
||||
@@ -78,7 +129,15 @@ def post_process() -> None:
|
||||
|
||||
def enhance_frame(temp_frame : Frame) -> Frame:
|
||||
with THREAD_SEMAPHORE:
|
||||
temp_frame, _ = get_frame_processor().enhance(temp_frame, outscale = 1)
|
||||
paste_frame, _ = get_frame_processor().enhance(temp_frame)
|
||||
temp_frame = blend_frame(temp_frame, paste_frame)
|
||||
return temp_frame
|
||||
|
||||
|
||||
def blend_frame(temp_frame : Frame, paste_frame : Frame) -> Frame:
|
||||
frame_enhancer_blend = 1 - (frame_processors_globals.frame_enhancer_blend / 100)
|
||||
temp_frame = cv2.resize(temp_frame, (paste_frame.shape[1], paste_frame.shape[0]))
|
||||
temp_frame = cv2.addWeighted(temp_frame, frame_enhancer_blend, paste_frame, 1 - frame_enhancer_blend, 0)
|
||||
return temp_frame
|
||||
|
||||
|
||||
@@ -86,7 +145,7 @@ def process_frame(source_face : Face, reference_face : Face, temp_frame : Frame)
|
||||
return enhance_frame(temp_frame)
|
||||
|
||||
|
||||
def process_frames(source_path : str, temp_frame_paths : List[str], update_progress: Callable[[], None]) -> None:
|
||||
def process_frames(source_path : str, temp_frame_paths : List[str], update_progress : Update_Process) -> None:
|
||||
for temp_frame_path in temp_frame_paths:
|
||||
temp_frame = read_image(temp_frame_path)
|
||||
result_frame = process_frame(None, None, temp_frame)
|
||||
|
||||
Reference in New Issue
Block a user