From 17db6ea2b4886df634b56221205735c5aade051e Mon Sep 17 00:00:00 2001 From: Tran Xen <137925069+glucauze@users.noreply.github.com> Date: Mon, 14 Aug 2023 22:22:48 +0200 Subject: [PATCH 1/8] add error reporting in dataclass_from_flat_list --- scripts/faceswaplab_utils/ui_utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/faceswaplab_utils/ui_utils.py b/scripts/faceswaplab_utils/ui_utils.py index fc02120..73a9e25 100644 --- a/scripts/faceswaplab_utils/ui_utils.py +++ b/scripts/faceswaplab_utils/ui_utils.py @@ -14,6 +14,10 @@ def dataclass_from_flat_list(cls: type, values: Tuple[Any, ...]) -> Any: init_values[field.name] = field.type(*inner_values) idx += len(inner_values) else: + if idx >= len(values): + raise IndexError( + f"Expected more values for dataclass {cls}. Current index: {idx}, values length: {len(values)}" + ) value = values[idx] init_values[field.name] = value idx += 1 From 054f6938159762676f227fa5f0e605f05c06f36e Mon Sep 17 00:00:00 2001 From: Tran Xen <137925069+glucauze@users.noreply.github.com> Date: Tue, 15 Aug 2023 01:17:26 +0200 Subject: [PATCH 2/8] speed up install and config --- check.sh | 2 +- install.py | 7 ++- models.json | 1 + requirements-gpu.txt | 1 - scripts/configure.py | 46 +----------------- scripts/faceswaplab.py | 18 +++---- scripts/faceswaplab_globals.py | 15 ++++-- .../faceswaplab_settings.py | 4 +- scripts/faceswaplab_swapping/swapper.py | 6 +-- scripts/faceswaplab_ui/faceswaplab_tab.py | 4 +- .../face_checkpoints_utils.py | 4 +- scripts/faceswaplab_utils/install_utils.py | 18 +++++++ scripts/faceswaplab_utils/models_utils.py | 47 ++++++++++++++++--- 13 files changed, 99 insertions(+), 74 deletions(-) create mode 100644 models.json create mode 100644 scripts/faceswaplab_utils/install_utils.py diff --git a/check.sh b/check.sh index d5e3b5f..bbfd7d5 100755 --- a/check.sh +++ b/check.sh @@ -1,4 +1,4 @@ #!/bin/bash autoflake --in-place --remove-unused-variables -r --remove-all-unused-imports . -mypy --install-types +mypy --non-interactive --install-types pre-commit run --all-files diff --git a/install.py b/install.py index f903986..4cfe80d 100644 --- a/install.py +++ b/install.py @@ -36,6 +36,8 @@ def is_installed(package: str) -> bool: required_version = parse(package.split(">=")[1]) return installed_version >= required_version else: + if package_name == "opencv-python": + return launch.is_installed(package_name) or launch.is_installed("cv2") return launch.is_installed(package_name) print("Checking faceswaplab requirements") @@ -59,4 +61,7 @@ def is_installed(package: str) -> bool: raise e -check_install() +import timeit + +check_time = timeit.timeit(check_install, number=1) +print(check_time) diff --git a/models.json b/models.json new file mode 100644 index 0000000..f474cb2 --- /dev/null +++ b/models.json @@ -0,0 +1 @@ +[{"analyzerName":"intellisense-members-lstm-pylance","languageName":"python","identity":{"modelId":"E61945A9A512ED5E1A3EE3F1A2365B88F8FE","outputId":"E4E9EADA96734F01970E616FAB2FAC19","modifiedTimeUtc":"2020-08-11T14:06:50.811Z"},"filePath":"E61945A9A512ED5E1A3EE3F1A2365B88F8FE_E4E9EADA96734F01970E616FAB2FAC19","lastAccessTimeUtc":"2023-08-14T21:58:14.988Z"}] \ No newline at end of file diff --git a/requirements-gpu.txt b/requirements-gpu.txt index 645b201..a26f6c2 100644 --- a/requirements-gpu.txt +++ b/requirements-gpu.txt @@ -3,7 +3,6 @@ dill ifnude insightface==0.7.3 onnx>=1.14.0 -opencv-python pandas pydantic safetensors diff --git a/scripts/configure.py b/scripts/configure.py index 1340690..aa25e80 100644 --- a/scripts/configure.py +++ b/scripts/configure.py @@ -1,51 +1,15 @@ import os from tqdm import tqdm -import traceback import urllib.request from scripts.faceswaplab_utils.faceswaplab_logging import logger from scripts.faceswaplab_globals import * from packaging import version import pkg_resources -import hashlib +from scripts.faceswaplab_utils.models_utils import check_model ALREADY_DONE = False -def check_install() -> None: - # Very ugly hack :( due to sdnext optimization not calling install.py every time if git log has not changed - import importlib.util - import sys - import os - - current_dir = os.path.dirname(os.path.realpath(__file__)) - check_install_path = os.path.join(current_dir, "..", "install.py") - spec = importlib.util.spec_from_file_location("check_install", check_install_path) - check_install = importlib.util.module_from_spec(spec) - sys.modules["check_install"] = check_install - spec.loader.exec_module(check_install) - check_install.check_install() # type: ignore - #### End of ugly hack :( ! - - -def is_sha1_matching(file_path: str, expected_sha1: str) -> bool: - sha1_hash = hashlib.sha1(usedforsecurity=False) - try: - with open(file_path, "rb") as file: - for byte_block in iter(lambda: file.read(4096), b""): - sha1_hash.update(byte_block) - if sha1_hash.hexdigest() == expected_sha1: - return True - else: - return False - except Exception as e: - logger.error( - "Failed to check model hash, check the model is valid or has been downloaded adequately : %e", - e, - ) - traceback.print_exc() - return False - - def check_configuration() -> None: global ALREADY_DONE @@ -83,13 +47,7 @@ def download(url: str, path: str) -> None: if not os.path.exists(model_path): download(model_url, model_path) - - if not is_sha1_matching(model_path, EXPECTED_INSWAPPER_SHA1): - logger.error( - "Suspicious sha1 for model %s, check the model is valid or has been downloaded adequately. Should be %s", - model_path, - EXPECTED_INSWAPPER_SHA1, - ) + check_model() gradio_version = pkg_resources.get_distribution("gradio").version diff --git a/scripts/faceswaplab.py b/scripts/faceswaplab.py index 835be58..a116fb5 100644 --- a/scripts/faceswaplab.py +++ b/scripts/faceswaplab.py @@ -12,7 +12,7 @@ from scripts.faceswaplab_swapping import swapper from scripts.faceswaplab_ui import faceswaplab_tab, faceswaplab_unit_ui from scripts.faceswaplab_utils import faceswaplab_logging, imgutils, models_utils -from scripts.faceswaplab_utils.models_utils import get_current_model +from scripts.faceswaplab_utils.models_utils import get_current_swap_model from scripts.faceswaplab_utils.typing import * from scripts.faceswaplab_utils.ui_utils import dataclasses_from_flat_list from scripts.faceswaplab_utils.faceswaplab_logging import logger, save_img_debug @@ -99,7 +99,7 @@ def title(self) -> str: return f"faceswaplab" def show(self, is_img2img: bool) -> bool: - return scripts.AlwaysVisible + return scripts.AlwaysVisible # type: ignore def ui(self, is_img2img: bool) -> List[gr.components.Component]: with gr.Accordion(f"FaceSwapLab {VERSION_FLAG}", open=False): @@ -147,7 +147,7 @@ def process( (img, None) for img in p.init_images ] new_inits = swapper.process_images_units( - get_current_model(), + get_current_swap_model(), self.swap_in_source_units, images=init_images, force_blend=True, @@ -181,7 +181,7 @@ def postprocess( for i, (img, info) in enumerate(zip(orig_images, orig_infotexts)): batch_index = i % p.batch_size swapped_images = swapper.process_images_units( - get_current_model(), + get_current_swap_model(), self.swap_in_generated_units, images=[(img, info)], ) @@ -213,8 +213,8 @@ def postprocess( swp_img, p.outpath_samples, "", - p.all_seeds[batch_index], - p.all_prompts[batch_index], + p.all_seeds[batch_index], # type: ignore + p.all_prompts[batch_index], # type: ignore opts.samples_format, info=new_info, p=p, @@ -231,7 +231,7 @@ def postprocess( text = processed.infotexts[0] infotexts.insert(0, text) if opts.enable_pnginfo: - grid.info["parameters"] = text + grid.info["parameters"] = text # type: ignore images.insert(0, grid) if opts.grid_save: @@ -239,8 +239,8 @@ def postprocess( grid, p.outpath_grids, "swapped-grid", - p.all_seeds[0], - p.all_prompts[0], + p.all_seeds[0], # type: ignore + p.all_prompts[0], # type: ignore opts.grid_format, info=text, short_filename=not opts.grid_extended_filename, diff --git a/scripts/faceswaplab_globals.py b/scripts/faceswaplab_globals.py index 7fa0b08..7d50774 100644 --- a/scripts/faceswaplab_globals.py +++ b/scripts/faceswaplab_globals.py @@ -1,18 +1,27 @@ import os from modules import scripts +from modules.shared import opts +# Defining the absolute path for the 'faceswaplab' directory inside 'models' directory MODELS_DIR = os.path.abspath(os.path.join("models", "faceswaplab")) +# Defining the absolute path for the 'analysers' directory inside 'MODELS_DIR' ANALYZER_DIR = os.path.abspath(os.path.join(MODELS_DIR, "analysers")) +# Defining the absolute path for the 'parser' directory inside 'MODELS_DIR' FACE_PARSER_DIR = os.path.abspath(os.path.join(MODELS_DIR, "parser")) +# Defining the absolute path for the 'faces' directory inside 'MODELS_DIR' FACES_DIR = os.path.abspath(os.path.join(MODELS_DIR, "faces")) +# Constructing the path for 'references' directory inside the 'extensions' and 'sd-webui-faceswaplab' directories, based on the base directory of scripts REFERENCE_PATH = os.path.join( scripts.basedir(), "extensions", "sd-webui-faceswaplab", "references" ) -VERSION_FLAG: str = "v1.2.1" +# Defining the version flag for the application +VERSION_FLAG: str = "v1.2.2" +# Defining the path for 'sd-webui-faceswaplab' inside the 'extensions' directory EXTENSION_PATH = os.path.join("extensions", "sd-webui-faceswaplab") -# The NSFW score threshold. If any part of the image has a score greater than this threshold, the image will be considered NSFW. -NSFW_SCORE_THRESHOLD: float = 0.7 +# Defining the NSFW score threshold. Any image part with a score above this value will be treated as NSFW (Not Safe For Work) +NSFW_SCORE_THRESHOLD: float = opts.data.get("faceswaplab_nsfw_threshold", 0.7) # type: ignore +# Defining the expected SHA1 hash value for 'INSWAPPER' EXPECTED_INSWAPPER_SHA1 = "17a64851eaefd55ea597ee41e5c18409754244c5" diff --git a/scripts/faceswaplab_settings/faceswaplab_settings.py b/scripts/faceswaplab_settings/faceswaplab_settings.py index 289ee25..5183869 100644 --- a/scripts/faceswaplab_settings/faceswaplab_settings.py +++ b/scripts/faceswaplab_settings/faceswaplab_settings.py @@ -1,11 +1,11 @@ -from scripts.faceswaplab_utils.models_utils import get_models +from scripts.faceswaplab_utils.models_utils import get_swap_models from modules import script_callbacks, shared import gradio as gr def on_ui_settings() -> None: section = ("faceswaplab", "FaceSwapLab") - models = get_models() + models = get_swap_models() shared.opts.add_option( "faceswaplab_model", shared.OptionInfo( diff --git a/scripts/faceswaplab_swapping/swapper.py b/scripts/faceswaplab_swapping/swapper.py index 168ba7e..146cd6b 100644 --- a/scripts/faceswaplab_swapping/swapper.py +++ b/scripts/faceswaplab_swapping/swapper.py @@ -33,7 +33,7 @@ from scripts.faceswaplab_postprocessing.postprocessing_options import ( PostProcessingOptions, ) -from scripts.faceswaplab_utils.models_utils import get_current_model +from scripts.faceswaplab_utils.models_utils import get_current_swap_model from scripts.faceswaplab_utils.typing import CV2ImgU8, PILImage, Face from scripts.faceswaplab_inpainting.i2i_pp import img2img_diffusion from modules import shared @@ -51,7 +51,7 @@ def use_gpu() -> bool: def force_install_gpu_providers() -> None: # Ugly Ugly hack due to SDNEXT : try: - from scripts.configure import check_install + from scripts.faceswaplab_utils.install_utils import check_install logger.warning("Try to reinstall gpu dependencies") check_install() @@ -180,7 +180,7 @@ def batch_process( current_images = [] swapped_images = process_images_units( - get_current_model(), images=[(src_image, None)], units=units + get_current_swap_model(), images=[(src_image, None)], units=units ) if len(swapped_images) > 0: current_images += [img for img, _ in swapped_images] diff --git a/scripts/faceswaplab_ui/faceswaplab_tab.py b/scripts/faceswaplab_ui/faceswaplab_tab.py index 8b99545..6415a36 100644 --- a/scripts/faceswaplab_ui/faceswaplab_tab.py +++ b/scripts/faceswaplab_ui/faceswaplab_tab.py @@ -17,7 +17,7 @@ from scripts.faceswaplab_ui.faceswaplab_unit_ui import faceswap_unit_ui from scripts.faceswaplab_utils import face_checkpoints_utils, imgutils from scripts.faceswaplab_utils.faceswaplab_logging import logger -from scripts.faceswaplab_utils.models_utils import get_models +from scripts.faceswaplab_utils.models_utils import get_swap_models from scripts.faceswaplab_utils.ui_utils import dataclasses_from_flat_list @@ -232,7 +232,7 @@ def batch_process( def tools_ui() -> None: - models = get_models() + models = get_swap_models() with gr.Tab("Tools"): with gr.Tab("Build"): gr.Markdown( diff --git a/scripts/faceswaplab_utils/face_checkpoints_utils.py b/scripts/faceswaplab_utils/face_checkpoints_utils.py index bf652f1..280359e 100644 --- a/scripts/faceswaplab_utils/face_checkpoints_utils.py +++ b/scripts/faceswaplab_utils/face_checkpoints_utils.py @@ -11,7 +11,7 @@ from scripts.faceswaplab_utils.faceswaplab_logging import logger from scripts.faceswaplab_utils.typing import * from scripts.faceswaplab_utils import imgutils -from scripts.faceswaplab_utils.models_utils import get_models +from scripts.faceswaplab_utils.models_utils import get_swap_models import traceback import dill as pickle # will be removed in future versions @@ -90,7 +90,7 @@ def build_face_checkpoint_and_save( target_faces=[target_face], source_face=blended_face, target_img=reference_preview_img, - model=get_models()[0], + model=get_swap_models()[0], swapping_options=InswappperOptions(face_restorer_name="Codeformer"), ) preview_image = result.image diff --git a/scripts/faceswaplab_utils/install_utils.py b/scripts/faceswaplab_utils/install_utils.py new file mode 100644 index 0000000..5cd7bf0 --- /dev/null +++ b/scripts/faceswaplab_utils/install_utils.py @@ -0,0 +1,18 @@ +from types import ModuleType + + +def check_install() -> None: + # Very ugly hack :( due to sdnext optimization not calling install.py every time if git log has not changed + import importlib.util + import sys + import os + + current_dir = os.path.dirname(os.path.realpath(__file__)) + check_install_path = os.path.join(current_dir, "..", "..", "install.py") + spec = importlib.util.spec_from_file_location("check_install", check_install_path) + if spec != None: + check_install: ModuleType = importlib.util.module_from_spec(spec) + sys.modules["check_install"] = check_install + spec.loader.exec_module(check_install) # type: ignore + check_install.check_install() # type: ignore + #### End of ugly hack :( ! diff --git a/scripts/faceswaplab_utils/models_utils.py b/scripts/faceswaplab_utils/models_utils.py index 6bde15e..e235dec 100644 --- a/scripts/faceswaplab_utils/models_utils.py +++ b/scripts/faceswaplab_utils/models_utils.py @@ -3,12 +3,47 @@ from typing import List import modules.scripts as scripts from modules import scripts -from scripts.faceswaplab_globals import EXTENSION_PATH +from scripts.faceswaplab_globals import EXPECTED_INSWAPPER_SHA1, EXTENSION_PATH from modules.shared import opts from scripts.faceswaplab_utils.faceswaplab_logging import logger +import traceback +import hashlib -def get_models() -> List[str]: +def is_sha1_matching(file_path: str, expected_sha1: str) -> bool: + sha1_hash = hashlib.sha1(usedforsecurity=False) + try: + with open(file_path, "rb") as file: + for byte_block in iter(lambda: file.read(4096), b""): + sha1_hash.update(byte_block) + if sha1_hash.hexdigest() == expected_sha1: + return True + else: + return False + except Exception as e: + logger.error( + "Failed to check model hash, check the model is valid or has been downloaded adequately : %e", + e, + ) + traceback.print_exc() + return False + + +def check_model() -> bool: + model_path = get_current_swap_model() + if not is_sha1_matching( + file_path=model_path, expected_sha1=EXPECTED_INSWAPPER_SHA1 + ): + logger.error( + "Suspicious sha1 for model %s, check the model is valid or has been downloaded adequately. Should be %s", + model_path, + EXPECTED_INSWAPPER_SHA1, + ) + return False + return True + + +def get_swap_models() -> List[str]: """ Retrieve a list of swap model files. @@ -31,13 +66,13 @@ def get_models() -> List[str]: return models -def get_current_model() -> str: - model = opts.data.get("faceswaplab_model", None) +def get_current_swap_model() -> str: + model = opts.data.get("faceswaplab_model", None) # type: ignore if model is None: - models = get_models() + models = get_swap_models() model = models[0] if len(models) else None logger.info("Try to use model : %s", model) - if not os.path.isfile(model): + if not os.path.isfile(model): # type: ignore logger.error("The model %s cannot be found or loaded", model) raise FileNotFoundError( "No faceswap model found. Please add it to the faceswaplab directory." From f9fc0bbff1d4126e34904a930597bee010851d99 Mon Sep 17 00:00:00 2001 From: Tran Xen <137925069+glucauze@users.noreply.github.com> Date: Tue, 15 Aug 2023 14:58:10 +0200 Subject: [PATCH 3/8] fix install wip --- requirements-gpu.txt | 2 ++ requirements.txt | 1 + scripts/faceswaplab_utils/imgutils.py | 7 +++++-- scripts/faceswaplab_utils/typing.py | 4 ++-- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/requirements-gpu.txt b/requirements-gpu.txt index a26f6c2..e914f8d 100644 --- a/requirements-gpu.txt +++ b/requirements-gpu.txt @@ -3,6 +3,8 @@ dill ifnude insightface==0.7.3 onnx>=1.14.0 +protobuf>=3.20.2 +opencv-python pandas pydantic safetensors diff --git a/requirements.txt b/requirements.txt index f6581d5..cf934a2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +protobuf>=3.20.2 cython dill ifnude diff --git a/scripts/faceswaplab_utils/imgutils.py b/scripts/faceswaplab_utils/imgutils.py index e8d67fb..3b55b6c 100644 --- a/scripts/faceswaplab_utils/imgutils.py +++ b/scripts/faceswaplab_utils/imgutils.py @@ -29,12 +29,15 @@ def check_against_nsfw(img: PILImage) -> bool: chunks: List[Dict[str, Union[int, float]]] = detect(img) for chunk in chunks: + logger.debug( + f"chunck score {chunk['score']}, threshold : {NSFW_SCORE_THRESHOLD}" + ) shapes.append(chunk["score"] > NSFW_SCORE_THRESHOLD) return any(shapes) -def pil_to_cv2(pil_img: PILImage) -> CV2ImgU8: # type: ignore +def pil_to_cv2(pil_img: PILImage) -> CV2ImgU8: """ Convert a PIL Image into an OpenCV image (cv2). @@ -44,7 +47,7 @@ def pil_to_cv2(pil_img: PILImage) -> CV2ImgU8: # type: ignore Returns: CV2ImgU8: The input image converted to OpenCV format (BGR). """ - return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR) + return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR).astype("uint8") def cv2_to_pil(cv2_img: CV2ImgU8) -> PILImage: # type: ignore diff --git a/scripts/faceswaplab_utils/typing.py b/scripts/faceswaplab_utils/typing.py index d102d63..fb14503 100644 --- a/scripts/faceswaplab_utils/typing.py +++ b/scripts/faceswaplab_utils/typing.py @@ -1,10 +1,10 @@ from typing import Tuple from numpy import uint8 -from numpy.typing import NDArray from insightface.app.common import Face as IFace from PIL import Image +import numpy as np PILImage = Image.Image -CV2ImgU8 = NDArray[uint8] +CV2ImgU8 = np.ndarray[int, np.dtype[uint8]] Face = IFace BoxCoords = Tuple[int, int, int, int] From afcfc7d255fa8ef01a4cf27fe12aa175ae2fbc54 Mon Sep 17 00:00:00 2001 From: Tran Xen <137925069+glucauze@users.noreply.github.com> Date: Wed, 16 Aug 2023 01:11:02 +0200 Subject: [PATCH 4/8] wip, add nsfw option due to perf, still some mypy warnings --- scripts/faceswaplab.py | 5 ++- scripts/faceswaplab_api/faceswaplab_api.py | 2 +- scripts/faceswaplab_globals.py | 3 -- .../faceswaplab_inpainting.py | 6 +-- .../postprocessing_options.py | 7 ++-- .../faceswaplab_postprocessing/upscaling.py | 2 +- .../faceswaplab_settings.py | 10 +++++ .../face_checkpoints.py} | 3 +- scripts/faceswaplab_swapping/facemask.py | 2 +- scripts/faceswaplab_swapping/swapper.py | 34 +++++++-------- .../upcaled_inswapper_options.py | 7 ++-- .../upscaled_inswapper.py | 23 ++++++---- .../faceswaplab_inpainting_ui.py | 6 +-- .../faceswaplab_postprocessing_ui.py | 18 ++++---- scripts/faceswaplab_ui/faceswaplab_tab.py | 42 ++++++++++--------- .../faceswaplab_unit_settings.py | 6 +-- scripts/faceswaplab_ui/faceswaplab_unit_ui.py | 20 ++++----- scripts/faceswaplab_utils/imgutils.py | 8 +++- scripts/faceswaplab_utils/models_utils.py | 1 + scripts/faceswaplab_utils/sd_utils.py | 7 ++++ 20 files changed, 123 insertions(+), 89 deletions(-) rename scripts/{faceswaplab_utils/face_checkpoints_utils.py => faceswaplab_swapping/face_checkpoints.py} (98%) create mode 100644 scripts/faceswaplab_utils/sd_utils.py diff --git a/scripts/faceswaplab.py b/scripts/faceswaplab.py index a116fb5..4863d20 100644 --- a/scripts/faceswaplab.py +++ b/scripts/faceswaplab.py @@ -1,4 +1,5 @@ from scripts.configure import check_configuration +from scripts.faceswaplab_utils.sd_utils import get_sd_option check_configuration() @@ -76,7 +77,7 @@ def __init__(self) -> None: @property def units_count(self) -> int: - return opts.data.get("faceswaplab_units_count", 3) + return get_sd_option("faceswaplab_units_count", 3) @property def enabled(self) -> bool: @@ -85,7 +86,7 @@ def enabled(self) -> bool: @property def keep_original_images(self) -> bool: - return opts.data.get("faceswaplab_keep_original", False) + return get_sd_option("faceswaplab_keep_original", False) @property def swap_in_generated_units(self) -> List[FaceSwapUnitSettings]: diff --git a/scripts/faceswaplab_api/faceswaplab_api.py b/scripts/faceswaplab_api/faceswaplab_api.py index ef6803f..cb000f8 100644 --- a/scripts/faceswaplab_api/faceswaplab_api.py +++ b/scripts/faceswaplab_api/faceswaplab_api.py @@ -18,7 +18,7 @@ PostProcessingOptions, ) from client_api import api_utils -from scripts.faceswaplab_utils.face_checkpoints_utils import ( +from scripts.faceswaplab_swapping.face_checkpoints import ( build_face_checkpoint_and_save, ) diff --git a/scripts/faceswaplab_globals.py b/scripts/faceswaplab_globals.py index 7d50774..ca1f76b 100644 --- a/scripts/faceswaplab_globals.py +++ b/scripts/faceswaplab_globals.py @@ -1,6 +1,5 @@ import os from modules import scripts -from modules.shared import opts # Defining the absolute path for the 'faceswaplab' directory inside 'models' directory MODELS_DIR = os.path.abspath(os.path.join("models", "faceswaplab")) @@ -21,7 +20,5 @@ # Defining the path for 'sd-webui-faceswaplab' inside the 'extensions' directory EXTENSION_PATH = os.path.join("extensions", "sd-webui-faceswaplab") -# Defining the NSFW score threshold. Any image part with a score above this value will be treated as NSFW (Not Safe For Work) -NSFW_SCORE_THRESHOLD: float = opts.data.get("faceswaplab_nsfw_threshold", 0.7) # type: ignore # Defining the expected SHA1 hash value for 'INSWAPPER' EXPECTED_INSWAPPER_SHA1 = "17a64851eaefd55ea597ee41e5c18409754244c5" diff --git a/scripts/faceswaplab_inpainting/faceswaplab_inpainting.py b/scripts/faceswaplab_inpainting/faceswaplab_inpainting.py index 716d1b3..d4409a1 100644 --- a/scripts/faceswaplab_inpainting/faceswaplab_inpainting.py +++ b/scripts/faceswaplab_inpainting/faceswaplab_inpainting.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import List +from typing import List, Optional import gradio as gr from client_api import api_utils @@ -15,10 +15,10 @@ class InpaintingOptions: @staticmethod def from_gradio(components: List[gr.components.Component]) -> "InpaintingOptions": - return InpaintingOptions(*components) + return InpaintingOptions(*components) # type: ignore @staticmethod - def from_api_dto(dto: api_utils.InpaintingOptions) -> "InpaintingOptions": + def from_api_dto(dto: Optional[api_utils.InpaintingOptions]) -> "InpaintingOptions": """ Converts a InpaintingOptions object from an API DTO (Data Transfer Object). diff --git a/scripts/faceswaplab_postprocessing/postprocessing_options.py b/scripts/faceswaplab_postprocessing/postprocessing_options.py index b7f7bcd..70f65fd 100644 --- a/scripts/faceswaplab_postprocessing/postprocessing_options.py +++ b/scripts/faceswaplab_postprocessing/postprocessing_options.py @@ -1,3 +1,4 @@ +from typing import Optional from modules.face_restoration import FaceRestoration from modules.upscaler import UpscalerData from dataclasses import dataclass @@ -27,17 +28,17 @@ class PostProcessingOptions: inpainting_when: InpaintingWhen = InpaintingWhen.BEFORE_UPSCALING # (Don't use optional for this or gradio parsing will fail) : - inpainting_options: InpaintingOptions = None + inpainting_options: InpaintingOptions = None # type: ignore @property - def upscaler(self) -> UpscalerData: + def upscaler(self) -> Optional[UpscalerData]: for upscaler in shared.sd_upscalers: if upscaler.name == self.upscaler_name: return upscaler return None @property - def face_restorer(self) -> FaceRestoration: + def face_restorer(self) -> Optional[FaceRestoration]: for face_restorer in shared.face_restorers: if face_restorer.name() == self.face_restorer_name: return face_restorer diff --git a/scripts/faceswaplab_postprocessing/upscaling.py b/scripts/faceswaplab_postprocessing/upscaling.py index 746f229..bc0c257 100644 --- a/scripts/faceswaplab_postprocessing/upscaling.py +++ b/scripts/faceswaplab_postprocessing/upscaling.py @@ -17,7 +17,7 @@ def upscale_img(image: PILImage, pp_options: PostProcessingOptions) -> PILImage: pp_options.scale, ) result_image = pp_options.upscaler.scaler.upscale( - image, pp_options.scale, pp_options.upscaler.data_path + image, pp_options.scale, pp_options.upscaler.data_path # type: ignore ) # FIXME : Could be better (managing images whose dimensions are not multiples of 16) diff --git a/scripts/faceswaplab_settings/faceswaplab_settings.py b/scripts/faceswaplab_settings/faceswaplab_settings.py index 5183869..c6c24ff 100644 --- a/scripts/faceswaplab_settings/faceswaplab_settings.py +++ b/scripts/faceswaplab_settings/faceswaplab_settings.py @@ -46,6 +46,16 @@ def on_ui_settings() -> None: section=section, ), ) + shared.opts.add_option( + "faceswaplab_nsfw_threshold", + shared.OptionInfo( + 0.7, + "NSFW score threshold. Any image part with a score above this value will be treated as NSFW (use extension responsibly !)", + gr.Slider, + {"minimum": 0, "maximum": 1, "step": 0.01}, + section=section, + ), + ) shared.opts.add_option( "faceswaplab_det_size", diff --git a/scripts/faceswaplab_utils/face_checkpoints_utils.py b/scripts/faceswaplab_swapping/face_checkpoints.py similarity index 98% rename from scripts/faceswaplab_utils/face_checkpoints_utils.py rename to scripts/faceswaplab_swapping/face_checkpoints.py index 280359e..89ea6ff 100644 --- a/scripts/faceswaplab_utils/face_checkpoints_utils.py +++ b/scripts/faceswaplab_swapping/face_checkpoints.py @@ -63,7 +63,7 @@ def build_face_checkpoint_and_save( scripts.basedir(), "extensions", "sd-webui-faceswaplab", "references" ) - reference_preview_img: PILImage = None + reference_preview_img: PILImage if blended_face: if blended_face["gender"] == 0: reference_preview_img = Image.open( @@ -86,7 +86,6 @@ def build_face_checkpoint_and_save( ) else: result = swapper.swap_face( - reference_face=blended_face, target_faces=[target_face], source_face=blended_face, target_img=reference_preview_img, diff --git a/scripts/faceswaplab_swapping/facemask.py b/scripts/faceswaplab_swapping/facemask.py index d49b713..e16f63e 100644 --- a/scripts/faceswaplab_swapping/facemask.py +++ b/scripts/faceswaplab_swapping/facemask.py @@ -83,7 +83,7 @@ def generate_face_mask(face_image: np.ndarray, device: torch.device) -> np.ndarr convert_bgr_to_rgb=True, use_float32=True, ) - normalize(face_input, (0.5, 0.5, 0.5), (0.5, 0.5, 0.5), inplace=True) + normalize(face_input, (0.5, 0.5, 0.5), (0.5, 0.5, 0.5), inplace=True) # type: ignore assert isinstance(face_input, torch.Tensor) face_input = torch.unsqueeze(face_input, 0).to(device) diff --git a/scripts/faceswaplab_swapping/swapper.py b/scripts/faceswaplab_swapping/swapper.py index 146cd6b..ceb8e56 100644 --- a/scripts/faceswaplab_swapping/swapper.py +++ b/scripts/faceswaplab_swapping/swapper.py @@ -26,7 +26,6 @@ ) from scripts.faceswaplab_utils.faceswaplab_logging import logger, save_img_debug from scripts import faceswaplab_globals -from modules.shared import opts from functools import lru_cache from scripts.faceswaplab_ui.faceswaplab_unit_settings import FaceSwapUnitSettings from scripts.faceswaplab_postprocessing.postprocessing import enhance_image @@ -38,12 +37,13 @@ from scripts.faceswaplab_inpainting.i2i_pp import img2img_diffusion from modules import shared import onnxruntime +from scripts.faceswaplab_utils.sd_utils import get_sd_option def use_gpu() -> bool: return ( getattr(shared.cmd_opts, "faceswaplab_gpu", False) - or opts.data.get("faceswaplab_use_gpu", False) + or get_sd_option("faceswaplab_use_gpu", False) ) and sys.platform != "darwin" @@ -166,6 +166,7 @@ def batch_process( if src_images is not None and len(units) > 0: result_images = [] for src_image in src_images: + path: str = "" if isinstance(src_image, str): if save_path: path = os.path.join( @@ -182,7 +183,7 @@ def batch_process( swapped_images = process_images_units( get_current_swap_model(), images=[(src_image, None)], units=units ) - if len(swapped_images) > 0: + if swapped_images and len(swapped_images) > 0: current_images += [img for img, _ in swapped_images] logger.info("%s images generated", len(current_images)) @@ -209,7 +210,7 @@ def extract_faces( images: List[PILImage], extract_path: Optional[str], postprocess_options: PostProcessingOptions, -) -> Optional[List[str]]: +) -> Optional[List[PILImage]]: """ Extracts faces from a list of image files. @@ -232,14 +233,14 @@ def extract_faces( os.makedirs(extract_path, exist_ok=True) if images: - result_images = [] + result_images: list[PILImage] = [] for img in images: faces = get_faces(pil_to_cv2(img)) if faces: face_images = [] for face in faces: - bbox = face.bbox.astype(int) + bbox = face.bbox.astype(int) # type: ignore x_min, y_min, x_max, y_max = bbox face_image = img.crop((x_min, y_min, x_max, y_max)) @@ -370,7 +371,7 @@ def getFaceSwapModel(model_path: str) -> upscaled_inswapper.UpscaledINSwapper: with tqdm(total=1, desc="Loading swap model", unit="model") as pbar: with capture_stdout() as captured: model = upscaled_inswapper.UpscaledINSwapper( - insightface.model_zoo.get_model(model_path, providers=providers) + insightface.model_zoo.get_model(model_path, providers=providers) # type: ignore ) pbar.update(1) logger.info("%s", pformat(captured.getvalue())) @@ -402,11 +403,11 @@ def get_faces( """ if det_thresh is None: - det_thresh = opts.data.get("faceswaplab_detection_threshold", 0.5) + det_thresh = get_sd_option("faceswaplab_detection_threshold", 0.5) - auto_det_size = opts.data.get("faceswaplab_auto_det_size", True) + auto_det_size = get_sd_option("faceswaplab_auto_det_size", True) if not auto_det_size: - x = opts.data.get("faceswaplab_det_size", 640) + x = get_sd_option("faceswaplab_det_size", 640) det_size = (x, x) face_analyser = getAnalysisModel(det_size, det_thresh) @@ -433,7 +434,7 @@ def get_faces( try: # Sort the detected faces based on their x-coordinate of the bounding box - return sorted(faces, key=lambda x: x.bbox[0]) + return sorted(faces, key=lambda x: x.bbox[0]) # type: ignore except Exception as e: logger.error("Failed to get faces %s", e) traceback.print_exc() @@ -470,7 +471,7 @@ def filter_faces( filtered_faces = sorted( all_faces, reverse=True, - key=lambda x: (x.bbox[2] - x.bbox[0]) * (x.bbox[3] - x.bbox[1]), + key=lambda x: (x.bbox[2] - x.bbox[0]) * (x.bbox[3] - x.bbox[1]), # type: ignore ) if filtering_options.source_gender is not None: @@ -566,7 +567,7 @@ def blend_faces(faces: List[Face]) -> Optional[Face]: ValueError: If the embeddings have different shapes. """ - embeddings = [face.embedding for face in faces] + embeddings: list[Any] = [face.embedding for face in faces] if len(embeddings) > 0: embedding_shape = embeddings[0].shape @@ -592,7 +593,6 @@ def blend_faces(faces: List[Face]) -> Optional[Face]: def swap_face( - reference_face: CV2ImgU8, source_face: Face, target_img: PILImage, target_faces: List[Face], @@ -604,7 +604,6 @@ def swap_face( Swaps faces in the target image with the source face. Args: - reference_face (CV2ImgU8): The reference face used for similarity comparison. source_face (CV2ImgU8): The source face to be swapped. target_img (PILImage): The target image to swap faces in. model (str): Path to the face swap model. @@ -614,7 +613,9 @@ def swap_face( """ return_result = ImageResult(target_img, {}, {}) - target_img_cv2: CV2ImgU8 = cv2.cvtColor(np.array(target_img), cv2.COLOR_RGB2BGR) + target_img_cv2: CV2ImgU8 = cv2.cvtColor( + np.array(target_img), cv2.COLOR_RGB2BGR + ).astype("uint8") try: gender = source_face["gender"] logger.info("Source Gender %s", gender) @@ -732,7 +733,6 @@ def process_image_unit( save_img_debug(image, "Before swap") result: ImageResult = swap_face( - reference_face=reference_face, source_face=src_face, target_img=current_image, target_faces=target_faces, diff --git a/scripts/faceswaplab_swapping/upcaled_inswapper_options.py b/scripts/faceswaplab_swapping/upcaled_inswapper_options.py index efba4e4..759235e 100644 --- a/scripts/faceswaplab_swapping/upcaled_inswapper_options.py +++ b/scripts/faceswaplab_swapping/upcaled_inswapper_options.py @@ -1,20 +1,21 @@ from dataclasses import * +from typing import Optional from client_api import api_utils @dataclass class InswappperOptions: - face_restorer_name: str = None + face_restorer_name: Optional[str] = None restorer_visibility: float = 1 codeformer_weight: float = 1 - upscaler_name: str = None + upscaler_name: Optional[str] = None improved_mask: bool = False color_corrections: bool = False sharpen: bool = False erosion_factor: float = 1.0 @staticmethod - def from_api_dto(dto: api_utils.InswappperOptions) -> "InswappperOptions": + def from_api_dto(dto: Optional[api_utils.InswappperOptions]) -> "InswappperOptions": """ Converts a InpaintingOptions object from an API DTO (Data Transfer Object). diff --git a/scripts/faceswaplab_swapping/upscaled_inswapper.py b/scripts/faceswaplab_swapping/upscaled_inswapper.py index 691f929..30f4907 100644 --- a/scripts/faceswaplab_swapping/upscaled_inswapper.py +++ b/scripts/faceswaplab_swapping/upscaled_inswapper.py @@ -1,10 +1,9 @@ -from typing import Any, Tuple, Union +from typing import Any, Optional, Tuple, Union import cv2 import numpy as np from insightface.model_zoo.inswapper import INSwapper from insightface.utils import face_align from modules import processing, shared -from modules.shared import opts from modules.upscaler import UpscalerData from scripts.faceswaplab_postprocessing import upscaling @@ -14,13 +13,14 @@ from scripts.faceswaplab_swapping.facemask import generate_face_mask from scripts.faceswaplab_swapping.upcaled_inswapper_options import InswappperOptions from scripts.faceswaplab_utils.imgutils import cv2_to_pil, pil_to_cv2 +from scripts.faceswaplab_utils.sd_utils import get_sd_option from scripts.faceswaplab_utils.typing import CV2ImgU8, Face from scripts.faceswaplab_utils.faceswaplab_logging import logger -def get_upscaler() -> UpscalerData: +def get_upscaler() -> Optional[UpscalerData]: for upscaler in shared.sd_upscalers: - if upscaler.name == opts.data.get( + if upscaler.name == get_sd_option( "faceswaplab_upscaled_swapper_upscaler", "LDSR" ): return upscaler @@ -130,8 +130,14 @@ def __init__(self, inswapper: INSwapper): self.__dict__.update(inswapper.__dict__) def upscale_and_restore( - self, img: CV2ImgU8, k: int = 2, inswapper_options: InswappperOptions = None + self, + img: CV2ImgU8, + k: int = 2, + inswapper_options: Optional[InswappperOptions] = None, ) -> CV2ImgU8: + if inswapper_options is None: + return img + pil_img = cv2_to_pil(img) pp_options = PostProcessingOptions( upscaler_name=inswapper_options.upscaler_name, @@ -156,7 +162,7 @@ def get( target_face: Face, source_face: Face, paste_back: bool = True, - options: InswappperOptions = None, + options: Optional[InswappperOptions] = None, ) -> Union[CV2ImgU8, Tuple[CV2ImgU8, Any]]: aimg, M = face_align.norm_crop2(img, target_face.kps, self.input_size[0]) blob = cv2.dnn.blobFromImage( @@ -166,9 +172,10 @@ def get( (self.input_mean, self.input_mean, self.input_mean), swapRB=True, ) - latent = source_face.normed_embedding.reshape((1, -1)) + latent = source_face.normed_embedding.reshape((1, -1)) # type: ignore latent = np.dot(latent, self.emap) latent /= np.linalg.norm(latent) + assert self.session is not None pred = self.session.run( self.output_names, {self.input_names[0]: blob, self.input_names[1]: latent} )[0] @@ -274,7 +281,7 @@ def compute_diff(bgr_fake: CV2ImgU8, aimg: CV2ImgU8) -> CV2ImgU8: mask_h = np.max(mask_h_inds) - np.min(mask_h_inds) mask_w = np.max(mask_w_inds) - np.min(mask_w_inds) mask_size = int(np.sqrt(mask_h * mask_w)) - erosion_factor = options.erosion_factor + erosion_factor = options.erosion_factor if options else 1 k = max(int(mask_size // 10 * erosion_factor), int(10 * erosion_factor)) diff --git a/scripts/faceswaplab_ui/faceswaplab_inpainting_ui.py b/scripts/faceswaplab_ui/faceswaplab_inpainting_ui.py index bb4dd93..052fa17 100644 --- a/scripts/faceswaplab_ui/faceswaplab_inpainting_ui.py +++ b/scripts/faceswaplab_ui/faceswaplab_inpainting_ui.py @@ -1,7 +1,7 @@ from typing import List import gradio as gr -from modules.shared import opts from modules import sd_models, sd_samplers +from scripts.faceswaplab_utils.sd_utils import get_sd_option def face_inpainting_ui( @@ -19,14 +19,14 @@ def face_inpainting_ui( ) inpainting_denoising_prompt = gr.Textbox( - opts.data.get( + get_sd_option( "faceswaplab_pp_default_inpainting_prompt", "Portrait of a [gender]" ), elem_id=f"{id_prefix}_pp_inpainting_denoising_prompt", label="Inpainting prompt use [gender] instead of men or woman", ) inpainting_denoising_negative_prompt = gr.Textbox( - opts.data.get( + get_sd_option( "faceswaplab_pp_default_inpainting_negative_prompt", "blurry" ), elem_id=f"{id_prefix}_pp_inpainting_denoising_neg_prompt", diff --git a/scripts/faceswaplab_ui/faceswaplab_postprocessing_ui.py b/scripts/faceswaplab_ui/faceswaplab_postprocessing_ui.py index ec12a82..8209854 100644 --- a/scripts/faceswaplab_ui/faceswaplab_postprocessing_ui.py +++ b/scripts/faceswaplab_ui/faceswaplab_postprocessing_ui.py @@ -2,8 +2,8 @@ import gradio as gr import modules from modules import shared, sd_models -from modules.shared import opts from scripts.faceswaplab_postprocessing.postprocessing_options import InpaintingWhen +from scripts.faceswaplab_utils.sd_utils import get_sd_option def postprocessing_ui() -> List[gr.components.Component]: @@ -15,7 +15,7 @@ def postprocessing_ui() -> List[gr.components.Component]: face_restorer_name = gr.Radio( label="Restore Face", choices=["None"] + [x.name() for x in shared.face_restorers], - value=lambda: opts.data.get( + value=lambda: get_sd_option( "faceswaplab_pp_default_face_restorer", shared.face_restorers[0].name(), ), @@ -26,7 +26,7 @@ def postprocessing_ui() -> List[gr.components.Component]: face_restorer_visibility = gr.Slider( 0, 1, - value=lambda: opts.data.get( + value=lambda: get_sd_option( "faceswaplab_pp_default_face_restorer_visibility", 1 ), step=0.001, @@ -36,7 +36,7 @@ def postprocessing_ui() -> List[gr.components.Component]: codeformer_weight = gr.Slider( 0, 1, - value=lambda: opts.data.get( + value=lambda: get_sd_option( "faceswaplab_pp_default_face_restorer_weight", 1 ), step=0.001, @@ -45,7 +45,7 @@ def postprocessing_ui() -> List[gr.components.Component]: ) upscaler_name = gr.Dropdown( choices=[upscaler.name for upscaler in shared.sd_upscalers], - value=lambda: opts.data.get("faceswaplab_pp_default_upscaler", "None"), + value=lambda: get_sd_option("faceswaplab_pp_default_upscaler", "None"), label="Upscaler", elem_id="faceswaplab_pp_upscaler", ) @@ -60,7 +60,7 @@ def postprocessing_ui() -> List[gr.components.Component]: upscaler_visibility = gr.Slider( 0, 1, - value=lambda: opts.data.get( + value=lambda: get_sd_option( "faceswaplab_pp_default_upscaler_visibility", 1 ), step=0.1, @@ -87,21 +87,21 @@ def postprocessing_ui() -> List[gr.components.Component]: ) inpainting_denoising_prompt = gr.Textbox( - opts.data.get( + get_sd_option( "faceswaplab_pp_default_inpainting_prompt", "Portrait of a [gender]" ), elem_id="faceswaplab_pp_inpainting_denoising_prompt", label="Inpainting prompt use [gender] instead of men or woman", ) inpainting_denoising_negative_prompt = gr.Textbox( - opts.data.get( + get_sd_option( "faceswaplab_pp_default_inpainting_negative_prompt", "blurry" ), elem_id="faceswaplab_pp_inpainting_denoising_neg_prompt", label="Inpainting negative prompt use [gender] instead of men or woman", ) with gr.Row(): - samplers_names = [s.name for s in modules.sd_samplers.all_samplers] + samplers_names = [s.name for s in modules.sd_samplers.all_samplers] # type: ignore inpainting_sampler = gr.Dropdown( choices=samplers_names, value=[samplers_names[0]], diff --git a/scripts/faceswaplab_ui/faceswaplab_tab.py b/scripts/faceswaplab_ui/faceswaplab_tab.py index 6415a36..1e1e593 100644 --- a/scripts/faceswaplab_ui/faceswaplab_tab.py +++ b/scripts/faceswaplab_ui/faceswaplab_tab.py @@ -1,11 +1,12 @@ import traceback from pprint import pformat from typing import * +from scripts.faceswaplab_swapping import face_checkpoints +from scripts.faceswaplab_utils.sd_utils import get_sd_option from scripts.faceswaplab_utils.typing import * import gradio as gr import onnx import pandas as pd -from modules.shared import opts from PIL import Image import scripts.faceswaplab_swapping.swapper as swapper @@ -15,7 +16,7 @@ from scripts.faceswaplab_ui.faceswaplab_postprocessing_ui import postprocessing_ui from scripts.faceswaplab_ui.faceswaplab_unit_settings import FaceSwapUnitSettings from scripts.faceswaplab_ui.faceswaplab_unit_ui import faceswap_unit_ui -from scripts.faceswaplab_utils import face_checkpoints_utils, imgutils +from scripts.faceswaplab_utils import imgutils from scripts.faceswaplab_utils.faceswaplab_logging import logger from scripts.faceswaplab_utils.models_utils import get_swap_models from scripts.faceswaplab_utils.ui_utils import dataclasses_from_flat_list @@ -74,7 +75,7 @@ def extract_faces( [PostProcessingOptions], components ).pop() images = [ - Image.open(file.name) for file in files + Image.open(file.name) for file in files # type: ignore ] # potentially greedy but Image.open is supposed to be lazy result_images = swapper.extract_faces( images, extract_path=extract_path, postprocess_options=postprocess_options @@ -136,7 +137,7 @@ def analyse_faces(image: PILImage, det_threshold: float = 0.5) -> Optional[str]: def build_face_checkpoint_and_save( - batch_files: gr.File, name: str, overwrite: bool + batch_files: List[gr.File], name: str, overwrite: bool ) -> PILImage: """ Builds a face checkpoint using the provided image files, performs face swapping, @@ -154,16 +155,16 @@ def build_face_checkpoint_and_save( try: if not batch_files: logger.error("No face found") - return None - images = [Image.open(file.name) for file in batch_files] - preview_image = face_checkpoints_utils.build_face_checkpoint_and_save( + return None # type: ignore (Optional not really supported by old gradio) + images = [Image.open(file.name) for file in batch_files] # type: ignore + preview_image = face_checkpoints.build_face_checkpoint_and_save( images, name, overwrite=overwrite ) except Exception as e: logger.error("Failed to build checkpoint %s", e) traceback.print_exc() - return None + return None # type: ignore return preview_image @@ -197,7 +198,7 @@ def explore_onnx_faceswap_model(model_path: str) -> pd.DataFrame: logger.error("Failed to explore model %s", e) traceback.print_exc() - return None + return None # type: ignore return df @@ -205,7 +206,7 @@ def batch_process( files: List[gr.File], save_path: str, *components: Tuple[Any, ...] ) -> List[PILImage]: try: - units_count = opts.data.get("faceswaplab_units_count", 3) + units_count = get_sd_option("faceswaplab_units_count", 3) classes: List[Any] = dataclasses_from_flat_list( [FaceSwapUnitSettings] * units_count + [PostProcessingOptions], @@ -216,13 +217,16 @@ def batch_process( ] postprocess_options = classes[-1] - images_paths = [file.name for file in files] + images_paths = [file.name for file in files] # type: ignore - return swapper.batch_process( - images_paths, - save_path=save_path, - units=units, - postprocess_options=postprocess_options, + return ( + swapper.batch_process( + images_paths, + save_path=save_path, + units=units, + postprocess_options=postprocess_options, + ) + or [] ) except Exception as e: logger.error("Batch Process error : %s", e) @@ -304,7 +308,7 @@ def tools_ui() -> None: label="Extracted faces", show_label=False, elem_id="faceswaplab_extract_results", - ).style(columns=[2], rows=[2]) + ) extract_save_path = gr.Textbox( label="Destination Directory", value="", @@ -360,7 +364,7 @@ def tools_ui() -> None: label="Batch result", show_label=False, elem_id="faceswaplab_batch_results", - ).style(columns=[2], rows=[2]) + ) batch_save_path = gr.Textbox( label="Destination Directory", value="outputs/faceswap/", @@ -370,7 +374,7 @@ def tools_ui() -> None: "Process & Save", elem_id="faceswaplab_extract_btn" ) unit_components = [] - for i in range(1, opts.data.get("faceswaplab_units_count", 3) + 1): + for i in range(1, get_sd_option("faceswaplab_units_count", 3) + 1): unit_components += faceswap_unit_ui(False, i, id_prefix="faceswaplab_tab") upscale_options = postprocessing_ui() diff --git a/scripts/faceswaplab_ui/faceswaplab_unit_settings.py b/scripts/faceswaplab_ui/faceswaplab_unit_settings.py index 84db6d3..b108d9a 100644 --- a/scripts/faceswaplab_ui/faceswaplab_unit_settings.py +++ b/scripts/faceswaplab_ui/faceswaplab_unit_settings.py @@ -9,7 +9,7 @@ from scripts.faceswaplab_swapping.upcaled_inswapper_options import InswappperOptions from scripts.faceswaplab_utils.imgutils import pil_to_cv2 from scripts.faceswaplab_utils.faceswaplab_logging import logger -from scripts.faceswaplab_utils import face_checkpoints_utils +from scripts.faceswaplab_swapping import face_checkpoints from scripts.faceswaplab_inpainting.faceswaplab_inpainting import InpaintingOptions from client_api import api_utils @@ -124,7 +124,7 @@ def reference_face(self) -> Optional[Face]: if self.source_face and self.source_face != "None": try: logger.info(f"loading face {self.source_face}") - face = face_checkpoints_utils.load_face(self.source_face) + face = face_checkpoints.load_face(self.source_face) self._reference_face = face except Exception as e: logger.error("Failed to load checkpoint : %s", e) @@ -169,7 +169,7 @@ def faces(self) -> List[Face]: if isinstance(file, Image.Image): img = file else: - img = Image.open(file.name) + img = Image.open(file.name) # type: ignore face = swapper.get_or_default( swapper.get_faces(pil_to_cv2(img)), 0, None diff --git a/scripts/faceswaplab_ui/faceswaplab_unit_ui.py b/scripts/faceswaplab_ui/faceswaplab_unit_ui.py index a76997e..127cec0 100644 --- a/scripts/faceswaplab_ui/faceswaplab_unit_ui.py +++ b/scripts/faceswaplab_ui/faceswaplab_unit_ui.py @@ -1,9 +1,9 @@ from typing import List from scripts.faceswaplab_ui.faceswaplab_inpainting_ui import face_inpainting_ui -from scripts.faceswaplab_utils.face_checkpoints_utils import get_face_checkpoints +from scripts.faceswaplab_swapping.face_checkpoints import get_face_checkpoints import gradio as gr -from modules.shared import opts from modules import shared +from scripts.faceswaplab_utils.sd_utils import get_sd_option def faceswap_unit_advanced_options( @@ -17,7 +17,7 @@ def faceswap_unit_advanced_options( face_restorer_name = gr.Radio( label="Restore Face", choices=["None"] + [x.name() for x in shared.face_restorers], - value=lambda: opts.data.get( + value=lambda: get_sd_option( "faceswaplab_default_upscaled_swapper_face_restorer", "None", ), @@ -28,7 +28,7 @@ def faceswap_unit_advanced_options( face_restorer_visibility = gr.Slider( 0, 1, - value=lambda: opts.data.get( + value=lambda: get_sd_option( "faceswaplab_default_upscaled_swapper_face_restorer_visibility", 1.0, ), @@ -39,7 +39,7 @@ def faceswap_unit_advanced_options( codeformer_weight = gr.Slider( 0, 1, - value=lambda: opts.data.get( + value=lambda: get_sd_option( "faceswaplab_default_upscaled_swapper_face_restorer_weight", 1.0 ), step=0.001, @@ -48,7 +48,7 @@ def faceswap_unit_advanced_options( ) upscaler_name = gr.Dropdown( choices=[upscaler.name for upscaler in shared.sd_upscalers], - value=lambda: opts.data.get( + value=lambda: get_sd_option( "faceswaplab_default_upscaled_swapper_upscaler", "" ), label="Upscaler", @@ -56,7 +56,7 @@ def faceswap_unit_advanced_options( ) improved_mask = gr.Checkbox( - lambda: opts.data.get( + lambda: get_sd_option( "faceswaplab_default_upscaled_swapper_improved_mask", False ), interactive=True, @@ -64,7 +64,7 @@ def faceswap_unit_advanced_options( elem_id=f"{id_prefix}_face{unit_num}_improved_mask", ) color_corrections = gr.Checkbox( - lambda: opts.data.get( + lambda: get_sd_option( "faceswaplab_default_upscaled_swapper_fixcolor", False ), interactive=True, @@ -72,7 +72,7 @@ def faceswap_unit_advanced_options( elem_id=f"{id_prefix}_face{unit_num}_color_corrections", ) sharpen_face = gr.Checkbox( - lambda: opts.data.get( + lambda: get_sd_option( "faceswaplab_default_upscaled_swapper_sharpen", False ), interactive=True, @@ -82,7 +82,7 @@ def faceswap_unit_advanced_options( erosion_factor = gr.Slider( 0.0, 10.0, - lambda: opts.data.get("faceswaplab_default_upscaled_swapper_erosion", 1.0), + lambda: get_sd_option("faceswaplab_default_upscaled_swapper_erosion", 1.0), step=0.01, label="Upscaled swapper mask erosion factor, 1 = default behaviour.", elem_id=f"{id_prefix}_face{unit_num}_erosion_factor", diff --git a/scripts/faceswaplab_utils/imgutils.py b/scripts/faceswaplab_utils/imgutils.py index 3b55b6c..2306253 100644 --- a/scripts/faceswaplab_utils/imgutils.py +++ b/scripts/faceswaplab_utils/imgutils.py @@ -6,10 +6,10 @@ from math import isqrt, ceil import torch from ifnude import detect -from scripts.faceswaplab_globals import NSFW_SCORE_THRESHOLD from modules import processing import base64 from collections import Counter +from scripts.faceswaplab_utils.sd_utils import get_sd_option from scripts.faceswaplab_utils.typing import BoxCoords, CV2ImgU8, PILImage from scripts.faceswaplab_utils.faceswaplab_logging import logger @@ -25,6 +25,12 @@ def check_against_nsfw(img: PILImage) -> bool: bool: True if any part of the image is considered NSFW, False otherwise. """ + NSFW_SCORE_THRESHOLD = get_sd_option("faceswaplab_nsfw_threshold", 0.7) + + # For testing purpose : + if NSFW_SCORE_THRESHOLD >= 1: + return False + shapes: List[bool] = [] chunks: List[Dict[str, Union[int, float]]] = detect(img) diff --git a/scripts/faceswaplab_utils/models_utils.py b/scripts/faceswaplab_utils/models_utils.py index e235dec..2d8b8e9 100644 --- a/scripts/faceswaplab_utils/models_utils.py +++ b/scripts/faceswaplab_utils/models_utils.py @@ -77,4 +77,5 @@ def get_current_swap_model() -> str: raise FileNotFoundError( "No faceswap model found. Please add it to the faceswaplab directory." ) + assert model is not None return model diff --git a/scripts/faceswaplab_utils/sd_utils.py b/scripts/faceswaplab_utils/sd_utils.py new file mode 100644 index 0000000..fd797ff --- /dev/null +++ b/scripts/faceswaplab_utils/sd_utils.py @@ -0,0 +1,7 @@ +from typing import Any +from modules.shared import opts + + +def get_sd_option(name: str, default: Any) -> Any: + assert opts.data is not None + return opts.data.get(name, default) From 0499581305b7c6e6739eb38a81d534c9e07e23e0 Mon Sep 17 00:00:00 2001 From: Tran Xen <137925069+glucauze@users.noreply.github.com> Date: Wed, 16 Aug 2023 17:07:48 +0200 Subject: [PATCH 5/8] fix preview in build --- scripts/faceswaplab_api/faceswaplab_api.py | 3 +- .../faceswaplab_swapping/face_checkpoints.py | 76 ++++++++++++------- scripts/faceswaplab_swapping/swapper.py | 16 ++-- scripts/faceswaplab_ui/faceswaplab_tab.py | 10 ++- 4 files changed, 63 insertions(+), 42 deletions(-) diff --git a/scripts/faceswaplab_api/faceswaplab_api.py b/scripts/faceswaplab_api/faceswaplab_api.py index cb000f8..3a0ee9f 100644 --- a/scripts/faceswaplab_api/faceswaplab_api.py +++ b/scripts/faceswaplab_api/faceswaplab_api.py @@ -21,6 +21,7 @@ from scripts.faceswaplab_swapping.face_checkpoints import ( build_face_checkpoint_and_save, ) +from scripts.faceswaplab_utils.typing import PILImage def encode_to_base64(image: Union[str, Image.Image, np.ndarray]) -> str: # type: ignore @@ -99,7 +100,7 @@ async def swap_face( pp_options = None units = get_faceswap_units_settings(request.units) - swapped_images = swapper.batch_process( + swapped_images: Optional[List[PILImage]] = swapper.batch_process( [src_image], None, units=units, postprocess_options=pp_options ) diff --git a/scripts/faceswaplab_swapping/face_checkpoints.py b/scripts/faceswaplab_swapping/face_checkpoints.py index 89ea6ff..ad952d3 100644 --- a/scripts/faceswaplab_swapping/face_checkpoints.py +++ b/scripts/faceswaplab_swapping/face_checkpoints.py @@ -38,8 +38,11 @@ def sanitize_name(name: str) -> str: def build_face_checkpoint_and_save( - images: List[PILImage], name: str, overwrite: bool = False, path: str = None -) -> PILImage: + images: List[PILImage], + name: str, + overwrite: bool = False, + path: Optional[str] = None, +) -> Optional[PILImage]: """ Builds a face checkpoint using the provided image files, performs face swapping, and saves the result to a file. If a blended face is successfully obtained and the face swapping @@ -57,8 +60,12 @@ def build_face_checkpoint_and_save( name = sanitize_name(name) images = images or [] logger.info("Build %s with %s images", name, len(images)) - faces = swapper.get_faces_from_img_files(images) - blended_face = swapper.blend_faces(faces) + faces: List[Face] = swapper.get_faces_from_img_files(images=images) + if faces is None or len(faces) == 0: + logger.error("No source faces found") + return None + + blended_face: Optional[Face] = swapper.blend_faces(faces) preview_path = os.path.join( scripts.basedir(), "extensions", "sd-webui-faceswaplab", "references" ) @@ -85,40 +92,51 @@ def build_face_checkpoint_and_save( "Failed to open reference image, cannot create preview : That should not happen unless you deleted the references folder or change the detection threshold." ) else: - result = swapper.swap_face( + result: swapper.ImageResult = swapper.swap_face( target_faces=[target_face], source_face=blended_face, target_img=reference_preview_img, model=get_swap_models()[0], - swapping_options=InswappperOptions(face_restorer_name="Codeformer"), + swapping_options=InswappperOptions( + face_restorer_name="CodeFormer", + restorer_visibility=1, + upscaler_name="Lanczos", + codeformer_weight=1, + improved_mask=True, + color_corrections=False, + sharpen=True, + ), ) preview_image = result.image - if path: - file_path = path - else: - file_path = os.path.join(get_checkpoint_path(), f"{name}.safetensors") - if not overwrite: - file_number = 1 - while os.path.exists(file_path): - file_path = os.path.join( - get_checkpoint_path(), f"{name}_{file_number}.safetensors" - ) - file_number += 1 - save_face(filename=file_path, face=blended_face) - preview_image.save(file_path + ".png") - try: - data = load_face(file_path) - logger.debug(data) - except Exception as e: - logger.error("Error loading checkpoint, after creation %s", e) - traceback.print_exc() - - return preview_image + if path: + file_path = path + else: + file_path = os.path.join( + get_checkpoint_path(), f"{name}.safetensors" + ) + if not overwrite: + file_number = 1 + while os.path.exists(file_path): + file_path = os.path.join( + get_checkpoint_path(), + f"{name}_{file_number}.safetensors", + ) + file_number += 1 + save_face(filename=file_path, face=blended_face) + preview_image.save(file_path + ".png") + try: + data = load_face(file_path) + logger.debug(data) + except Exception as e: + logger.error("Error loading checkpoint, after creation %s", e) + traceback.print_exc() + + return preview_image else: logger.error("No face found") - return None + return None # type: ignore except Exception as e: logger.error("Failed to build checkpoint %s", e) traceback.print_exc() @@ -139,7 +157,7 @@ def save_face(face: Face, filename: str) -> None: raise e -def load_face(name: str) -> Face: +def load_face(name: str) -> Optional[Face]: if name.startswith("data:application/face;base64,"): with tempfile.NamedTemporaryFile(delete=True) as temp_file: api_utils.base64_to_safetensors(name, temp_file.name) diff --git a/scripts/faceswaplab_swapping/swapper.py b/scripts/faceswaplab_swapping/swapper.py index ceb8e56..0c58396 100644 --- a/scripts/faceswaplab_swapping/swapper.py +++ b/scripts/faceswaplab_swapping/swapper.py @@ -141,7 +141,7 @@ def batch_process( src_images: List[Union[PILImage, str]], # image or filename save_path: Optional[str], units: List[FaceSwapUnitSettings], - postprocess_options: PostProcessingOptions, + postprocess_options: Optional[PostProcessingOptions], ) -> Optional[List[PILImage]]: """ Process a batch of images, apply face swapping according to the given settings, and optionally save the resulting images to a specified path. @@ -527,7 +527,7 @@ def get_or_default(l: List[Any], index: int, default: Any) -> Any: return l[index] if index < len(l) else default -def get_faces_from_img_files(images: List[PILImage]) -> List[Optional[CV2ImgU8]]: +def get_faces_from_img_files(images: List[PILImage]) -> List[Face]: """ Extracts faces from a list of image files. @@ -539,7 +539,7 @@ def get_faces_from_img_files(images: List[PILImage]) -> List[Optional[CV2ImgU8]] """ - faces = [] + faces: List[Face] = [] if len(images) > 0: for img in images: @@ -598,7 +598,6 @@ def swap_face( target_faces: List[Face], model: str, swapping_options: Optional[InswappperOptions], - compute_similarity: bool = True, ) -> ImageResult: """ Swaps faces in the target image with the source face. @@ -680,9 +679,9 @@ def process_image_unit( model: str, unit: FaceSwapUnitSettings, image: PILImage, - info: str = None, + info: Optional[str] = None, force_blend: bool = False, -) -> List[Tuple[PILImage, str]]: +) -> List[Tuple[PILImage, Optional[str]]]: """Process one image and return a List of (image, info) (one if blended, many if not). Args: @@ -723,7 +722,9 @@ def process_image_unit( sort_by_face_size=unit.sort_by_size, ) - target_faces = filter_faces(faces, filtering_options=face_filtering_options) + target_faces: List[Face] = filter_faces( + all_faces=faces, filtering_options=face_filtering_options + ) # Apply pre-inpainting to image if unit.pre_inpainting.inpainting_denoising_strengh > 0: @@ -738,7 +739,6 @@ def process_image_unit( target_faces=target_faces, model=model, swapping_options=unit.swapping_options, - compute_similarity=unit.compute_similarity, ) # Apply post-inpainting to image if unit.post_inpainting.inpainting_denoising_strengh > 0: diff --git a/scripts/faceswaplab_ui/faceswaplab_tab.py b/scripts/faceswaplab_ui/faceswaplab_tab.py index 1e1e593..ec0e268 100644 --- a/scripts/faceswaplab_ui/faceswaplab_tab.py +++ b/scripts/faceswaplab_ui/faceswaplab_tab.py @@ -156,16 +156,18 @@ def build_face_checkpoint_and_save( if not batch_files: logger.error("No face found") return None # type: ignore (Optional not really supported by old gradio) - images = [Image.open(file.name) for file in batch_files] # type: ignore - preview_image = face_checkpoints.build_face_checkpoint_and_save( - images, name, overwrite=overwrite + images: list[PILImage] = [Image.open(file.name) for file in batch_files] # type: ignore + preview_image: PILImage | None = ( + face_checkpoints.build_face_checkpoint_and_save( + images=images, name=name, overwrite=overwrite + ) ) except Exception as e: logger.error("Failed to build checkpoint %s", e) traceback.print_exc() return None # type: ignore - return preview_image + return preview_image # type: ignore def explore_onnx_faceswap_model(model_path: str) -> pd.DataFrame: From 61fc9269ede679213d554f923e6dad54d4bad3f1 Mon Sep 17 00:00:00 2001 From: Tran Xen <137925069+glucauze@users.noreply.github.com> Date: Wed, 16 Aug 2023 18:25:09 +0200 Subject: [PATCH 6/8] makes GPU requirements default if CPU --use-cpu options is not used, remove faceswaplab_gpu --- README.md | 2 +- docs/index.markdown | 4 +--- install.py | 6 +++--- scripts/faceswaplab_swapping/swapper.py | 21 ++++++++++++++------- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e97d8cf..1a2d8ae 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ While FaceSwapLab is still under development, it has reached a good level of sta In short: -+ **Ethical Guideline:** This extension should not be forked to create a public, easy way to bypass NSFW filtering. If you modify it for this purpose, keep it private, or you'll be banned. ++ **Ethical Guideline:** NSFW is now configurable due to performance issue. Please don't use this to do harm. + **License:** This software is distributed under the terms of the GNU Affero General Public License (AGPL), version 3 or later. + **Model License:** This software uses InsightFace's pre-trained models, which are available for non-commercial research purposes only. diff --git a/docs/index.markdown b/docs/index.markdown index 53b97cd..2e77d8b 100644 --- a/docs/index.markdown +++ b/docs/index.markdown @@ -20,7 +20,7 @@ While FaceSwapLab is still under development, it has reached a good level of sta In short: -+ **Ethical Guideline:** This extension should not be forked to create a public, easy way to circumvent NSFW filtering. ++ **Ethical Guideline:** This extension is **not intended to facilitate the creation of not safe for work (NSFW) or non-consensual deepfake content**. Its purpose is to bring consistency to image creation, making it easier to repair existing images, or bring characters back to life. + **License:** This software is distributed under the terms of the GNU Affero General Public License (AGPL), version 3 or later. + **Model License:** This software uses InsightFace's pre-trained models, which are available for non-commercial research purposes only. @@ -28,8 +28,6 @@ In short: This extension is **not intended to facilitate the creation of not safe for work (NSFW) or non-consensual deepfake content**. Its purpose is to bring consistency to image creation, making it easier to repair existing images, or bring characters back to life. -While the code for this extension is licensed under the AGPL in compliance with models and other source materials, it's important to stress that **we strongly discourage any attempts to fork this project to create an uncensored version**. Any modifications to the code to enable the production of such content would be contrary to the ethical guidelines we advocate for. - We will comply with European regulations regarding this type of software. As required by law, the code may include both visible and invisible watermarks. If your local laws prohibit the use of this extension, you should not use it. From an ethical perspective, the main goal of this extension is to generate consistent images by swapping faces. It's important to note that we've done our best to integrate censorship features. However, when users can access the source code, they might bypass these censorship measures. That's why we urge users to use this extension responsibly and avoid any malicious use. We emphasize the importance of respecting people's privacy and consent when swapping faces in images. We discourage any activities that could harm others, invade their privacy, or negatively affect their well-being. diff --git a/install.py b/install.py index 4cfe80d..3f7a763 100644 --- a/install.py +++ b/install.py @@ -7,15 +7,15 @@ def check_install() -> None: - use_gpu = getattr( - shared.cmd_opts, "faceswaplab_gpu", False - ) or shared.opts.data.get("faceswaplab_use_gpu", False) + use_gpu = not getattr(shared.cmd_opts, "use-cpu", False) if use_gpu and sys.platform != "darwin": + print("Faceswaplab : Use GPU requirements") req_file = os.path.join( os.path.dirname(os.path.realpath(__file__)), "requirements-gpu.txt" ) else: + print("Faceswaplab : Use CPU requirements") req_file = os.path.join( os.path.dirname(os.path.realpath(__file__)), "requirements.txt" ) diff --git a/scripts/faceswaplab_swapping/swapper.py b/scripts/faceswaplab_swapping/swapper.py index 0c58396..0e9c55c 100644 --- a/scripts/faceswaplab_swapping/swapper.py +++ b/scripts/faceswaplab_swapping/swapper.py @@ -101,8 +101,9 @@ def cosine_similarity_face(face1: Face, face2: Face) -> float: non-negative similarity score. """ # Reshape the face embeddings to have a shape of (1, -1) - vec1 = face1.embedding.reshape(1, -1) - vec2 = face2.embedding.reshape(1, -1) + assert face1.normed_embedding is not None and face2.normed_embedding is not None + vec1 = face1.normed_embedding.reshape(1, -1) + vec2 = face2.normed_embedding.reshape(1, -1) # Calculate the cosine similarity between the reshaped embeddings similarity = cosine_similarity(vec1, vec2) @@ -312,7 +313,9 @@ def capture_stdout() -> Generator[StringIO, None, None]: @lru_cache(maxsize=3) def getAnalysisModel( - det_size: Tuple[int, int] = (640, 640), det_thresh: float = 0.5 + det_size: Tuple[int, int] = (640, 640), + det_thresh: float = 0.5, + use_gpu: bool = False, ) -> insightface.app.FaceAnalysis: """ Retrieves the analysis model for face analysis. @@ -356,7 +359,9 @@ def getAnalysisModel( @lru_cache(maxsize=1) -def getFaceSwapModel(model_path: str) -> upscaled_inswapper.UpscaledINSwapper: +def getFaceSwapModel( + model_path: str, use_gpu: bool = False +) -> upscaled_inswapper.UpscaledINSwapper: """ Retrieves the face swap model and initializes it if necessary. @@ -410,7 +415,9 @@ def get_faces( x = get_sd_option("faceswaplab_det_size", 640) det_size = (x, x) - face_analyser = getAnalysisModel(det_size, det_thresh) + face_analyser = getAnalysisModel( + det_size=det_size, det_thresh=det_thresh, use_gpu=not is_cpu_provider() + ) # Get the detected faces from the image using the analysis model faces = face_analyser.get(img_data) @@ -619,9 +626,9 @@ def swap_face( gender = source_face["gender"] logger.info("Source Gender %s", gender) if source_face is not None: - result = target_img_cv2 + result: CV2ImgU8 = target_img_cv2 model_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), model) - face_swapper = getFaceSwapModel(model_path) + face_swapper = getFaceSwapModel(model_path, use_gpu=not is_cpu_provider()) logger.info("Target faces count : %s", len(target_faces)) for i, swapped_face in enumerate(target_faces): From 50db415069f8de6f34240d644d12d704c126fd49 Mon Sep 17 00:00:00 2001 From: Tran Xen <137925069+glucauze@users.noreply.github.com> Date: Wed, 16 Aug 2023 18:38:15 +0200 Subject: [PATCH 7/8] update doc --- README.md | 29 +++++++++++++++++++ docs/documentation.markdown | 15 ++++++++++ .../faceswaplab_settings.py | 2 +- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a2d8ae..26b1561 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,35 @@ More on this here : https://glucauze.github.io/sd-webui-faceswaplab/ + Older versions of gradio don't work well with the extension. See this bug : https://github.com/glucauze/sd-webui-faceswaplab/issues/5 +## Quick Start + +### Simple + +1. Put a face in the reference. +2. Select a face number. +3. Select "Enable." +4. Select "CodeFormer" in **Global Post-Processing** tab. + +Once you're happy with some results but want to improve, the next steps are to: + ++ Use advanced settings in face units (which are not as complex as they might seem, it's basically fine tuning post-processing for each faces). ++ Use pre/post inpainting to tweak the image a bit for more natural results. + +### Better + +1. Put a face in the reference. +2. Select a face number. +3. Select "Enable." + +4. In **Post-Processing** accordeon: + + Select "CodeFormer" + + Select "LDSR" or a faster model "003_realSR_BSRGAN_DFOWMFC_s64w8_SwinIR-L_x4_GAN" in upscaler. See [here for a list of upscalers](https://github.com/glucauze/sd-webui-faceswaplab/discussions/29). + + Use sharpen, color_correction and improved mask + +5. Disable "CodeFormer" in **Global Post-Processing** tab (otherwise it will be applied twice) + +Don't hesitate to share config in the [discussion section](https://github.com/glucauze/sd-webui-faceswaplab/discussions). + ### Features + **Face Unit Concept**: Similar to controlNet, the program introduces the concept of a face unit. You can configure up to 10 units (3 units are the default setting) in the program settings (sd). diff --git a/docs/documentation.markdown b/docs/documentation.markdown index c66155b..1f49d87 100644 --- a/docs/documentation.markdown +++ b/docs/documentation.markdown @@ -17,6 +17,21 @@ Once you're happy with some results but want to improve, the next steps are to: + Use advanced settings in face units (which are not as complex as they might seem, it's basically fine tuning post-processing for each faces). + Use pre/post inpainting to tweak the image a bit for more natural results. +### Getting better results + +1. Put a face in the reference. +2. Select a face number. +3. Select "Enable." + +4. In **Post-Processing** accordeon: + + Select "CodeFormer" + + Select "LDSR" or a faster model "003_realSR_BSRGAN_DFOWMFC_s64w8_SwinIR-L_x4_GAN" in upscaler. See [here for a list of upscalers](https://github.com/glucauze/sd-webui-faceswaplab/discussions/29). + + Use sharpen, color_correction and improved mask + +5. Disable "CodeFormer" in **Global Post-Processing** tab (otherwise it will be applied twice) + +Don't hesitate to share config in the [discussion section](https://github.com/glucauze/sd-webui-faceswaplab/discussions). + ## Main Interface Here is the interface for FaceSwap Lab. It is available in the form of an accordion in both img2img and txt2img. diff --git a/scripts/faceswaplab_settings/faceswaplab_settings.py b/scripts/faceswaplab_settings/faceswaplab_settings.py index c6c24ff..6db0e2d 100644 --- a/scripts/faceswaplab_settings/faceswaplab_settings.py +++ b/scripts/faceswaplab_settings/faceswaplab_settings.py @@ -50,7 +50,7 @@ def on_ui_settings() -> None: "faceswaplab_nsfw_threshold", shared.OptionInfo( 0.7, - "NSFW score threshold. Any image part with a score above this value will be treated as NSFW (use extension responsibly !)", + "NSFW score threshold. Any image part with a score above this value will be treated as NSFW (use extension responsibly !). 1=Disable filtering", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}, section=section, From 5b193339688f9aa42f1e39a5b6d690dd4e500a90 Mon Sep 17 00:00:00 2001 From: Tran Xen <137925069+glucauze@users.noreply.github.com> Date: Wed, 16 Aug 2023 23:33:18 +0200 Subject: [PATCH 8/8] update doc --- README.md | 4 +++- install.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 26b1561..ce73660 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ Don't hesitate to share config in the [discussion section](https://github.com/gl + **Batch Processing** ++ **GPU** + + **Inpainting Fixes** : supports “only masked” and mask inpainting. + **Performance Improvements**: The overall performance of the software has been enhanced. @@ -91,7 +93,7 @@ Don't hesitate to share config in the [discussion section](https://github.com/gl + **Upscaled Inswapper**: The program now includes an upscaled inswapper option, which improves results by incorporating upsampling, sharpness adjustment, and color correction before face is merged to the original image. -+ **API with typing support** : ++ **API with typing support** ## Installation diff --git a/install.py b/install.py index 3f7a763..6b9523a 100644 --- a/install.py +++ b/install.py @@ -56,7 +56,7 @@ def is_installed(package: str) -> bool: except Exception as e: print(e) print( - f"Warning: Failed to install {package}, faceswaplab will not work." + f"Warning: Failed to install {package}, faceswaplab may not work. Try to restart server or install dependencies manually." ) raise e