add pp&mask options for each faces. Improve API. Requires more testing

main
Tran Xen 2 years ago
parent b773bda19f
commit 4533750c49

@ -14,7 +14,7 @@ While FaceSwapLab is still under development, it has reached a good level of sta
In short: In short:
+ **Ethical Guideline:** This extension should not be forked to create a public, easy way to circumvent NSFW filtering. + **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.
+ **License:** This software is distributed under the terms of the GNU Affero General Public License (AGPL), version 3 or later. + **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. + **Model License:** This software uses InsightFace's pre-trained models, which are available for non-commercial research purposes only.

@ -9,6 +9,7 @@ from io import BytesIO
from typing import List, Tuple, Optional from typing import List, Tuple, Optional
import numpy as np import numpy as np
import requests import requests
import safetensors
class InpaintingWhen(Enum): class InpaintingWhen(Enum):
@ -49,6 +50,23 @@ class InpaintingOptions(BaseModel):
) )
class InswappperOptions(BaseModel):
face_restorer_name: str = Field(
description="face restorer name", default="CodeFormer"
)
restorer_visibility: float = Field(
description="face restorer visibility", default=1, le=1, ge=0
)
codeformer_weight: float = Field(
description="face restorer codeformer weight", default=1, le=1, ge=0
)
upscaler_name: str = Field(description="upscaler name", default=None)
improved_mask: bool = Field(description="Use Improved Mask", default=False)
color_corrections: bool = Field(description="Use Color Correction", default=False)
sharpen: bool = Field(description="Sharpen Image", default=False)
erosion_factor: float = Field(description="Erosion Factor", default=1, le=10, ge=0)
class FaceSwapUnit(BaseModel): class FaceSwapUnit(BaseModel):
# The image given in reference # The image given in reference
source_img: str = Field( source_img: str = Field(
@ -118,6 +136,11 @@ class FaceSwapUnit(BaseModel):
default=None, default=None,
) )
swapping_options: Optional[InswappperOptions] = Field(
description="PostProcessing & Mask options",
default=None,
)
post_inpainting: Optional[InpaintingOptions] = Field( post_inpainting: Optional[InpaintingOptions] = Field(
description="Inpainting options", description="Inpainting options",
default=None, default=None,
@ -244,3 +267,26 @@ def compare_faces(
) )
return float(result.text) return float(result.text)
def safetensors_to_base64(file_path: str) -> str:
with open(file_path, "rb") as file:
file_bytes = file.read()
return "data:application/face;base64," + base64.b64encode(file_bytes).decode(
"utf-8"
)
def base64_to_safetensors(base64str: str, output_path: str) -> None:
try:
base64_data = base64str.split("base64,")[-1]
file_bytes = base64.b64decode(base64_data)
with open(output_path, "wb") as file:
file.write(file_bytes)
with safetensors.safe_open(output_path, framework="pt") as f:
print(output_path, "keys =", f.keys())
except Exception as e:
print("Error : failed to convert base64 string to safetensor", e)
import traceback
traceback.print_exc()

@ -1,6 +1,7 @@
import requests import requests
from api_utils import ( from api_utils import (
FaceSwapUnit, FaceSwapUnit,
InswappperOptions,
pil_to_base64, pil_to_base64,
PostProcessingOptions, PostProcessingOptions,
InpaintingWhen, InpaintingWhen,
@ -10,6 +11,7 @@ from api_utils import (
FaceSwapExtractRequest, FaceSwapExtractRequest,
FaceSwapCompareRequest, FaceSwapCompareRequest,
FaceSwapExtractResponse, FaceSwapExtractResponse,
safetensors_to_base64,
) )
address = "http://127.0.0.1:7860" address = "http://127.0.0.1:7860"
@ -94,3 +96,34 @@ response = FaceSwapExtractResponse.parse_obj(result.json())
for img in response.pil_images: for img in response.pil_images:
img.show() img.show()
#############################
# FaceSwap with local safetensors
# First face unit :
unit1 = FaceSwapUnit(
source_face=safetensors_to_base64("test.safetensors"),
faces_index=(0,), # Replace first face
swapping_options=InswappperOptions(
face_restorer_name="CodeFormer",
upscaler_name="LDSR",
improved_mask=True,
sharpen=True,
color_corrections=True,
),
)
# Prepare the request
request = FaceSwapRequest(image=pil_to_base64("test_image.png"), units=[unit1])
# Face Swap
result = requests.post(
url=f"{address}/faceswaplab/swap_face",
data=request.json(),
headers={"Content-Type": "application/json; charset=utf-8"},
)
response = FaceSwapResponse.parse_obj(result.json())
for img in response.pil_images:
img.show()

@ -0,0 +1,5 @@
numpy==1.25.1
Pillow==10.0.0
pydantic==1.10.9
Requests==2.31.0
safetensors==0.3.1

Binary file not shown.

@ -133,3 +133,25 @@ The model generates faces with a resolution of 128x128, which is relatively low.
SimSwap models are based on older InsightFace architectures, and SimSwap has not been released as a Python package. Its incorporation would complicate the process, and it does not guarantee any substantial gain. SimSwap models are based on older InsightFace architectures, and SimSwap has not been released as a Python package. Its incorporation would complicate the process, and it does not guarantee any substantial gain.
If you manage to implement SimSwap successfully, feel free to submit a pull request. If you manage to implement SimSwap successfully, feel free to submit a pull request.
#### Shasum of inswapper model
Check that your model is correct and not corrupted :
```shell
$>sha1sum inswapper_128.onnx
17a64851eaefd55ea597ee41e5c18409754244c5 inswapper_128.onnx
$>sha256sum inswapper_128.onnx
e4a3f08c753cb72d04e10aa0f7dbe3deebbf39567d4ead6dce08e98aa49e16af inswapper_128.onnx
$>sha512sum inswapper_128.onnx
4311f4ccd9da58ec544e912b32ac0cba95f5ab4b1a06ac367efd3e157396efbae1097f624f10e77dd811fbba0917fa7c96e73de44563aa6099e5f46830965069 inswapper_128.onnx
```
#### Gradio errors (issubclass() arg 1 must be a class)
Older versions of gradio don't work well with the extension. See this bug report : https://github.com/glucauze/sd-webui-faceswaplab/issues/5
It has been tested on 3.32.0

@ -1,10 +1,10 @@
cython cython
dill
ifnude ifnude
insightface==0.7.3 insightface==0.7.3
onnx==1.14.0 onnx==1.14.0
onnxruntime==1.15.1 onnxruntime==1.15.1
opencv-python==4.7.0.72 opencv-python
pandas pandas
pydantic==1.10.9 pydantic
dill==0.3.6
safetensors safetensors

@ -72,14 +72,6 @@ class FaceSwapScript(scripts.Script):
def units_count(self) -> int: def units_count(self) -> int:
return opts.data.get("faceswaplab_units_count", 3) return opts.data.get("faceswaplab_units_count", 3)
@property
def upscaled_swapper_in_generated(self) -> bool:
return opts.data.get("faceswaplab_upscaled_swapper", False)
@property
def upscaled_swapper_in_source(self) -> bool:
return opts.data.get("faceswaplab_upscaled_swapper_in_source", False)
@property @property
def enabled(self) -> bool: def enabled(self) -> bool:
"""Return True if any unit is enabled and the state is not interupted""" """Return True if any unit is enabled and the state is not interupted"""
@ -152,7 +144,6 @@ class FaceSwapScript(scripts.Script):
get_current_model(), get_current_model(),
self.swap_in_source_units, self.swap_in_source_units,
images=init_images, images=init_images,
upscaled_swapper=self.upscaled_swapper_in_source,
force_blend=True, force_blend=True,
) )
logger.info(f"processed init images: {len(init_images)}") logger.info(f"processed init images: {len(init_images)}")
@ -187,7 +178,6 @@ class FaceSwapScript(scripts.Script):
get_current_model(), get_current_model(),
self.swap_in_generated_units, self.swap_in_generated_units,
images=[(img, info)], images=[(img, info)],
upscaled_swapper=self.upscaled_swapper_in_generated,
) )
if swapped_images is None: if swapped_images is None:
continue continue

@ -91,6 +91,8 @@ def faceswaplab_api(_: gr.Blocks, app: FastAPI) -> None:
if src_image is not None: if src_image is not None:
if request.postprocessing: if request.postprocessing:
pp_options = PostProcessingOptions.from_api_dto(request.postprocessing) pp_options = PostProcessingOptions.from_api_dto(request.postprocessing)
else:
pp_options = None
units = get_faceswap_units_settings(request.units) units = get_faceswap_units_settings(request.units)
swapped_images = swapper.batch_process( swapped_images = swapper.batch_process(

@ -54,7 +54,7 @@ def on_ui_settings() -> None:
"faceswaplab_pp_default_face_restorer", "faceswaplab_pp_default_face_restorer",
shared.OptionInfo( shared.OptionInfo(
None, None,
"UI Default post processing face restorer (requires restart)", "UI Default global post processing face restorer (requires restart)",
gr.Dropdown, gr.Dropdown,
{ {
"interactive": True, "interactive": True,
@ -67,7 +67,7 @@ def on_ui_settings() -> None:
"faceswaplab_pp_default_face_restorer_visibility", "faceswaplab_pp_default_face_restorer_visibility",
shared.OptionInfo( shared.OptionInfo(
1, 1,
"UI Default post processing face restorer visibility (requires restart)", "UI Default global post processing face restorer visibility (requires restart)",
gr.Slider, gr.Slider,
{"minimum": 0, "maximum": 1, "step": 0.001}, {"minimum": 0, "maximum": 1, "step": 0.001},
section=section, section=section,
@ -77,7 +77,7 @@ def on_ui_settings() -> None:
"faceswaplab_pp_default_face_restorer_weight", "faceswaplab_pp_default_face_restorer_weight",
shared.OptionInfo( shared.OptionInfo(
1, 1,
"UI Default post processing face restorer weight (requires restart)", "UI Default global post processing face restorer weight (requires restart)",
gr.Slider, gr.Slider,
{"minimum": 0, "maximum": 1, "step": 0.001}, {"minimum": 0, "maximum": 1, "step": 0.001},
section=section, section=section,
@ -87,7 +87,7 @@ def on_ui_settings() -> None:
"faceswaplab_pp_default_upscaler", "faceswaplab_pp_default_upscaler",
shared.OptionInfo( shared.OptionInfo(
None, None,
"UI Default post processing upscaler (requires restart)", "UI Default global post processing upscaler (requires restart)",
gr.Dropdown, gr.Dropdown,
{ {
"interactive": True, "interactive": True,
@ -100,13 +100,15 @@ def on_ui_settings() -> None:
"faceswaplab_pp_default_upscaler_visibility", "faceswaplab_pp_default_upscaler_visibility",
shared.OptionInfo( shared.OptionInfo(
1, 1,
"UI Default post processing upscaler visibility(requires restart)", "UI Default global post processing upscaler visibility(requires restart)",
gr.Slider, gr.Slider,
{"minimum": 0, "maximum": 1, "step": 0.001}, {"minimum": 0, "maximum": 1, "step": 0.001},
section=section, section=section,
), ),
) )
# Inpainting
shared.opts.add_option( shared.opts.add_option(
"faceswaplab_pp_default_inpainting_prompt", "faceswaplab_pp_default_inpainting_prompt",
shared.OptionInfo( shared.OptionInfo(
@ -132,20 +134,10 @@ def on_ui_settings() -> None:
# UPSCALED SWAPPER # UPSCALED SWAPPER
shared.opts.add_option( shared.opts.add_option(
"faceswaplab_upscaled_swapper", "faceswaplab_default_upscaled_swapper_upscaler",
shared.OptionInfo(
False,
"Upscaled swapper. Applied only to the swapped faces. Apply transformations before merging with the original image.",
gr.Checkbox,
{"interactive": True},
section=section,
),
)
shared.opts.add_option(
"faceswaplab_upscaled_swapper_upscaler",
shared.OptionInfo( shared.OptionInfo(
None, None,
"Upscaled swapper upscaler (Recommanded : LDSR but slow)", "Default Upscaled swapper upscaler (Recommanded : LDSR but slow) (requires restart)",
gr.Dropdown, gr.Dropdown,
{ {
"interactive": True, "interactive": True,
@ -155,40 +147,40 @@ def on_ui_settings() -> None:
), ),
) )
shared.opts.add_option( shared.opts.add_option(
"faceswaplab_upscaled_swapper_sharpen", "faceswaplab_default_upscaled_swapper_sharpen",
shared.OptionInfo( shared.OptionInfo(
False, False,
"Upscaled swapper sharpen", "Default Upscaled swapper sharpen",
gr.Checkbox, gr.Checkbox,
{"interactive": True}, {"interactive": True},
section=section, section=section,
), ),
) )
shared.opts.add_option( shared.opts.add_option(
"faceswaplab_upscaled_swapper_fixcolor", "faceswaplab_default_upscaled_swapper_fixcolor",
shared.OptionInfo( shared.OptionInfo(
False, False,
"Upscaled swapper color correction", "Default Upscaled swapper color corrections (requires restart)",
gr.Checkbox, gr.Checkbox,
{"interactive": True}, {"interactive": True},
section=section, section=section,
), ),
) )
shared.opts.add_option( shared.opts.add_option(
"faceswaplab_upscaled_improved_mask", "faceswaplab_default_upscaled_swapper_improved_mask",
shared.OptionInfo( shared.OptionInfo(
True, True,
"Use improved segmented mask (use pastenet to mask only the face)", "Default Use improved segmented mask (use pastenet to mask only the face) (requires restart)",
gr.Checkbox, gr.Checkbox,
{"interactive": True}, {"interactive": True},
section=section, section=section,
), ),
) )
shared.opts.add_option( shared.opts.add_option(
"faceswaplab_upscaled_swapper_face_restorer", "faceswaplab_default_upscaled_swapper_face_restorer",
shared.OptionInfo( shared.OptionInfo(
None, None,
"Upscaled swapper face restorer", "Default Upscaled swapper face restorer (requires restart)",
gr.Dropdown, gr.Dropdown,
{ {
"interactive": True, "interactive": True,
@ -198,40 +190,30 @@ def on_ui_settings() -> None:
), ),
) )
shared.opts.add_option( shared.opts.add_option(
"faceswaplab_upscaled_swapper_face_restorer_visibility", "faceswaplab_default_upscaled_swapper_face_restorer_visibility",
shared.OptionInfo( shared.OptionInfo(
1, 1,
"Upscaled swapper face restorer visibility", "Default Upscaled swapper face restorer visibility (requires restart)",
gr.Slider, gr.Slider,
{"minimum": 0, "maximum": 1, "step": 0.001}, {"minimum": 0, "maximum": 1, "step": 0.001},
section=section, section=section,
), ),
) )
shared.opts.add_option( shared.opts.add_option(
"faceswaplab_upscaled_swapper_face_restorer_weight", "faceswaplab_default_upscaled_swapper_face_restorer_weight",
shared.OptionInfo( shared.OptionInfo(
1, 1,
"Upscaled swapper face restorer weight (codeformer)", "Default Upscaled swapper face restorer weight (codeformer) (requires restart)",
gr.Slider, gr.Slider,
{"minimum": 0, "maximum": 1, "step": 0.001}, {"minimum": 0, "maximum": 1, "step": 0.001},
section=section, section=section,
), ),
) )
shared.opts.add_option( shared.opts.add_option(
"faceswaplab_upscaled_swapper_fthresh", "faceswaplab_default_upscaled_swapper_erosion",
shared.OptionInfo(
10,
"Upscaled swapper fthresh (diff sensitivity) 10 = default behaviour. Low impact.",
gr.Slider,
{"minimum": 5, "maximum": 250, "step": 1},
section=section,
),
)
shared.opts.add_option(
"faceswaplab_upscaled_swapper_erosion",
shared.OptionInfo( shared.OptionInfo(
1, 1,
"Upscaled swapper mask erosion factor, 1 = default behaviour. The larger it is, the more blur is applied around the face. Too large and the facial change is no longer visible.", "Default Upscaled swapper mask erosion factor, 1 = default behaviour. The larger it is, the more blur is applied around the face. Too large and the facial change is no longer visible. (requires restart)",
gr.Slider, gr.Slider,
{"minimum": 0, "maximum": 10, "step": 0.001}, {"minimum": 0, "maximum": 10, "step": 0.001},
section=section, section=section,

@ -1,8 +1,14 @@
import copy import copy
import os import os
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Dict, List, Set, Tuple, Optional from pprint import pformat
from typing import Any, Dict, Generator, List, Set, Tuple, Optional
import tempfile import tempfile
from tqdm import tqdm
import sys
from io import StringIO
from contextlib import contextmanager
import hashlib
import cv2 import cv2
import insightface import insightface
@ -13,6 +19,7 @@ from PIL import Image
from sklearn.metrics.pairwise import cosine_similarity from sklearn.metrics.pairwise import cosine_similarity
from scripts.faceswaplab_swapping import upscaled_inswapper from scripts.faceswaplab_swapping import upscaled_inswapper
from scripts.faceswaplab_swapping.upcaled_inswapper_options import InswappperOptions
from scripts.faceswaplab_utils.imgutils import ( from scripts.faceswaplab_utils.imgutils import (
pil_to_cv2, pil_to_cv2,
check_against_nsfw, check_against_nsfw,
@ -117,19 +124,16 @@ def batch_process(
for src_image in src_images: for src_image in src_images:
current_images = [] current_images = []
swapped_images = process_images_units( swapped_images = process_images_units(
get_current_model(), get_current_model(), images=[(src_image, None)], units=units
images=[(src_image, None)],
units=units,
upscaled_swapper=opts.data.get(
"faceswaplab_upscaled_swapper", False
),
) )
if len(swapped_images) > 0: if len(swapped_images) > 0:
current_images += [img for img, _ in swapped_images] current_images += [img for img, _ in swapped_images]
logger.info("%s images generated", len(current_images)) logger.info("%s images generated", len(current_images))
for i, img in enumerate(current_images):
current_images[i] = enhance_image(img, postprocess_options) if postprocess_options:
for i, img in enumerate(current_images):
current_images[i] = enhance_image(img, postprocess_options)
if save_path: if save_path:
for img in current_images: for img in current_images:
@ -225,6 +229,33 @@ class FaceModelException(Exception):
super().__init__(self.message) super().__init__(self.message)
@contextmanager
def capture_stdout() -> Generator[StringIO, None, None]:
"""
Capture and yield the printed messages to stdout.
This context manager temporarily replaces sys.stdout with a StringIO object,
capturing all printed output. After the context block is exited, sys.stdout
is restored to its original value.
Example usage:
with capture_stdout() as captured:
print("Hello, World!")
output = captured.getvalue()
# output now contains "Hello, World!\n"
Returns:
A StringIO object containing the captured output.
"""
original_stdout = sys.stdout # Type: ignore
captured_stdout = StringIO()
sys.stdout = captured_stdout # Type: ignore
try:
yield captured_stdout
finally:
sys.stdout = original_stdout # Type: ignore
@lru_cache(maxsize=1) @lru_cache(maxsize=1)
def getAnalysisModel() -> insightface.app.FaceAnalysis: def getAnalysisModel() -> insightface.app.FaceAnalysis:
""" """
@ -237,11 +268,20 @@ def getAnalysisModel() -> insightface.app.FaceAnalysis:
if not os.path.exists(faceswaplab_globals.ANALYZER_DIR): if not os.path.exists(faceswaplab_globals.ANALYZER_DIR):
os.makedirs(faceswaplab_globals.ANALYZER_DIR) os.makedirs(faceswaplab_globals.ANALYZER_DIR)
logger.info("Load analysis model, will take some time.") logger.info("Load analysis model, will take some time. (> 30s)")
# Initialize the analysis model with the specified name and providers # Initialize the analysis model with the specified name and providers
return insightface.app.FaceAnalysis(
name="buffalo_l", providers=providers, root=faceswaplab_globals.ANALYZER_DIR with tqdm(total=1, desc="Loading analysis model", unit="model") as pbar:
) with capture_stdout() as captured:
model = insightface.app.FaceAnalysis(
name="buffalo_l",
providers=providers,
root=faceswaplab_globals.ANALYZER_DIR,
)
pbar.update(1)
logger.info("%s", pformat(captured.getvalue()))
return model
except Exception as e: except Exception as e:
logger.error( logger.error(
"Loading of swapping model failed, please check the requirements (On Windows, download and install Visual Studio. During the install, make sure to include the Python and C++ packages.)" "Loading of swapping model failed, please check the requirements (On Windows, download and install Visual Studio. During the install, make sure to include the Python and C++ packages.)"
@ -249,11 +289,8 @@ def getAnalysisModel() -> insightface.app.FaceAnalysis:
raise FaceModelException("Loading of analysis model failed") raise FaceModelException("Loading of analysis model failed")
import hashlib
def is_sha1_matching(file_path: str, expected_sha1: str) -> bool: def is_sha1_matching(file_path: str, expected_sha1: str) -> bool:
sha1_hash = hashlib.sha1() sha1_hash = hashlib.sha1(usedforsecurity=False)
with open(file_path, "rb") as file: with open(file_path, "rb") as file:
for byte_block in iter(lambda: file.read(4096), b""): for byte_block in iter(lambda: file.read(4096), b""):
@ -284,10 +321,15 @@ def getFaceSwapModel(model_path: str) -> upscaled_inswapper.UpscaledINSwapper:
expected_sha1, expected_sha1,
) )
# Initializes the face swap model using the specified model path. with tqdm(total=1, desc="Loading swap model", unit="model") as pbar:
return upscaled_inswapper.UpscaledINSwapper( with capture_stdout() as captured:
insightface.model_zoo.get_model(model_path, providers=providers) model = upscaled_inswapper.UpscaledINSwapper(
) insightface.model_zoo.get_model(model_path, providers=providers)
)
pbar.update(1)
logger.info("%s", pformat(captured.getvalue()))
return model
except Exception as e: except Exception as e:
logger.error( logger.error(
"Loading of swapping model failed, please check the requirements (On Windows, download and install Visual Studio. During the install, make sure to include the Python and C++ packages.)" "Loading of swapping model failed, please check the requirements (On Windows, download and install Visual Studio. During the install, make sure to include the Python and C++ packages.)"
@ -498,7 +540,7 @@ def swap_face(
target_img: PILImage, target_img: PILImage,
target_faces: List[Face], target_faces: List[Face],
model: str, model: str,
upscaled_swapper: bool = False, swapping_options: Optional[InswappperOptions],
compute_similarity: bool = True, compute_similarity: bool = True,
) -> ImageResult: ) -> ImageResult:
""" """
@ -532,7 +574,7 @@ def swap_face(
img=result, img=result,
target_face=swapped_face, target_face=swapped_face,
source_face=source_face, source_face=source_face,
upscale=upscaled_swapper, options=swapping_options,
) # type: ignore ) # type: ignore
result_image = Image.fromarray(cv2.cvtColor(result, cv2.COLOR_BGR2RGB)) result_image = Image.fromarray(cv2.cvtColor(result, cv2.COLOR_BGR2RGB))
@ -581,7 +623,6 @@ def process_image_unit(
unit: FaceSwapUnitSettings, unit: FaceSwapUnitSettings,
image: PILImage, image: PILImage,
info: str = None, info: str = None,
upscaled_swapper: bool = False,
force_blend: bool = False, force_blend: bool = False,
) -> List[Tuple[PILImage, str]]: ) -> List[Tuple[PILImage, str]]:
"""Process one image and return a List of (image, info) (one if blended, many if not). """Process one image and return a List of (image, info) (one if blended, many if not).
@ -639,7 +680,7 @@ def process_image_unit(
target_img=current_image, target_img=current_image,
target_faces=target_faces, target_faces=target_faces,
model=model, model=model,
upscaled_swapper=upscaled_swapper, swapping_options=unit.swapping_options,
compute_similarity=unit.compute_similarity, compute_similarity=unit.compute_similarity,
) )
# Apply post-inpainting to image # Apply post-inpainting to image
@ -694,7 +735,6 @@ def process_images_units(
model: str, model: str,
units: List[FaceSwapUnitSettings], units: List[FaceSwapUnitSettings],
images: List[Tuple[Optional[PILImage], Optional[str]]], images: List[Tuple[Optional[PILImage], Optional[str]]],
upscaled_swapper: bool = False,
force_blend: bool = False, force_blend: bool = False,
) -> Optional[List[Tuple[PILImage, str]]]: ) -> Optional[List[Tuple[PILImage, str]]]:
""" """
@ -725,13 +765,9 @@ def process_images_units(
processed_images = [] processed_images = []
for i, (image, info) in enumerate(images): for i, (image, info) in enumerate(images):
logger.debug("Processing image %s", i) logger.debug("Processing image %s", i)
swapped = process_image_unit( swapped = process_image_unit(model, units[0], image, info, force_blend)
model, units[0], image, info, upscaled_swapper, force_blend
)
logger.debug("Image %s -> %s images", i, len(swapped)) logger.debug("Image %s -> %s images", i, len(swapped))
nexts = process_images_units( nexts = process_images_units(model, units[1:], swapped, force_blend)
model, units[1:], swapped, upscaled_swapper, force_blend
)
if nexts: if nexts:
processed_images.extend(nexts) processed_images.extend(nexts)
else: else:

@ -0,0 +1,38 @@
from dataclasses import *
from client_api import api_utils
@dataclass
class InswappperOptions:
face_restorer_name: str = None
restorer_visibility: float = 1
codeformer_weight: float = 1
upscaler_name: 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":
"""
Converts a InpaintingOptions object from an API DTO (Data Transfer Object).
:param options: An object of api_utils.InpaintingOptions representing the
post-processing options as received from the API.
:return: A InpaintingOptions instance containing the translated values
from the API DTO.
"""
if dto is None:
return InswappperOptions()
return InswappperOptions(
face_restorer_name=dto.face_restorer_name,
restorer_visibility=dto.restorer_visibility,
codeformer_weight=dto.codeformer_weight,
upscaler_name=dto.upscaler_name,
improved_mask=dto.improved_mask,
color_corrections=dto.color_corrections,
sharpen=dto.sharpen,
erosion_factor=dto.erosion_factor,
)

@ -12,8 +12,10 @@ from scripts.faceswaplab_postprocessing.postprocessing_options import (
PostProcessingOptions, PostProcessingOptions,
) )
from scripts.faceswaplab_swapping.facemask import generate_face_mask 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.imgutils import cv2_to_pil, pil_to_cv2
from scripts.faceswaplab_utils.typing import CV2ImgU8, Face from scripts.faceswaplab_utils.typing import CV2ImgU8, Face
from scripts.faceswaplab_utils.faceswaplab_logging import logger
def get_upscaler() -> UpscalerData: def get_upscaler() -> UpscalerData:
@ -127,26 +129,25 @@ class UpscaledINSwapper(INSwapper):
def __init__(self, inswapper: INSwapper): def __init__(self, inswapper: INSwapper):
self.__dict__.update(inswapper.__dict__) self.__dict__.update(inswapper.__dict__)
def super_resolution(self, img: CV2ImgU8, k: int = 2) -> CV2ImgU8: def upscale_and_restore(
self, img: CV2ImgU8, k: int = 2, inswapper_options: InswappperOptions = None
) -> CV2ImgU8:
pil_img = cv2_to_pil(img) pil_img = cv2_to_pil(img)
options = PostProcessingOptions( pp_options = PostProcessingOptions(
upscaler_name=opts.data.get( upscaler_name=inswapper_options.upscaler_name,
"faceswaplab_upscaled_swapper_upscaler", "LDSR"
),
upscale_visibility=1, upscale_visibility=1,
scale=k, scale=k,
face_restorer_name=opts.data.get( face_restorer_name=inswapper_options.face_restorer_name,
"faceswaplab_upscaled_swapper_face_restorer", "" codeformer_weight=inswapper_options.codeformer_weight,
), restorer_visibility=inswapper_options.restorer_visibility,
codeformer_weight=opts.data.get(
"faceswaplab_upscaled_swapper_face_restorer_weight", 1
),
restorer_visibility=opts.data.get(
"faceswaplab_upscaled_swapper_face_restorer_visibility", 1
),
) )
upscaled = upscaling.upscale_img(pil_img, options)
upscaled = upscaling.restore_face(upscaled, options) upscaled = pil_img
if pp_options.upscaler_name:
upscaled = upscaling.upscale_img(pil_img, pp_options)
if pp_options.face_restorer_name:
upscaled = upscaling.restore_face(upscaled, pp_options)
return pil_to_cv2(upscaled) return pil_to_cv2(upscaled)
def get( def get(
@ -155,7 +156,7 @@ class UpscaledINSwapper(INSwapper):
target_face: Face, target_face: Face,
source_face: Face, source_face: Face,
paste_back: bool = True, paste_back: bool = True,
upscale: bool = True, options: InswappperOptions = None,
) -> Union[CV2ImgU8, Tuple[CV2ImgU8, Any]]: ) -> Union[CV2ImgU8, Tuple[CV2ImgU8, Any]]:
aimg, M = face_align.norm_crop2(img, target_face.kps, self.input_size[0]) aimg, M = face_align.norm_crop2(img, target_face.kps, self.input_size[0])
blob = cv2.dnn.blobFromImage( blob = cv2.dnn.blobFromImage(
@ -190,43 +191,48 @@ class UpscaledINSwapper(INSwapper):
fake_diff[:, -2:] = 0 fake_diff[:, -2:] = 0
return fake_diff return fake_diff
if upscale: if options:
print("*" * 80) logger.info("*" * 80)
print( logger.info(f"Upscaled inswapper")
f"Upscaled inswapper using {opts.data.get('faceswaplab_upscaled_swapper_upscaler', 'LDSR')}"
)
print("*" * 80)
k = 4 if options.upscaler_name:
aimg, M = face_align.norm_crop2( # Upscale original image
img, target_face.kps, self.input_size[0] * k k = 4
) aimg, M = face_align.norm_crop2(
img, target_face.kps, self.input_size[0] * k
)
else:
k = 1
# upscale and restore face : # upscale and restore face :
bgr_fake = self.super_resolution(bgr_fake, k) bgr_fake = self.upscale_and_restore(
bgr_fake, inswapper_options=options, k=k
)
if opts.data.get("faceswaplab_upscaled_improved_mask", True): if options.improved_mask:
mask = get_face_mask(aimg, bgr_fake) mask = get_face_mask(aimg, bgr_fake)
bgr_fake = merge_images_with_mask(aimg, bgr_fake, mask) bgr_fake = merge_images_with_mask(aimg, bgr_fake, mask)
# compute fake_diff before sharpen and color correction (better result) # compute fake_diff before sharpen and color correction (better result)
fake_diff = compute_diff(bgr_fake, aimg) fake_diff = compute_diff(bgr_fake, aimg)
if opts.data.get("faceswaplab_upscaled_swapper_sharpen", True): if options.sharpen:
print("sharpen") logger.info("sharpen")
# Add sharpness # Add sharpness
blurred = cv2.GaussianBlur(bgr_fake, (0, 0), 3) blurred = cv2.GaussianBlur(bgr_fake, (0, 0), 3)
bgr_fake = cv2.addWeighted(bgr_fake, 1.5, blurred, -0.5, 0) bgr_fake = cv2.addWeighted(bgr_fake, 1.5, blurred, -0.5, 0)
# Apply color corrections # Apply color corrections
if opts.data.get("faceswaplab_upscaled_swapper_fixcolor", True): if options.color_corrections:
print("color correction") logger.info("color correction")
correction = processing.setup_color_correction(cv2_to_pil(aimg)) correction = processing.setup_color_correction(cv2_to_pil(aimg))
bgr_fake_pil = processing.apply_color_correction( bgr_fake_pil = processing.apply_color_correction(
correction, cv2_to_pil(bgr_fake) correction, cv2_to_pil(bgr_fake)
) )
bgr_fake = pil_to_cv2(bgr_fake_pil) bgr_fake = pil_to_cv2(bgr_fake_pil)
logger.info("*" * 80)
else: else:
fake_diff = compute_diff(bgr_fake, aimg) fake_diff = compute_diff(bgr_fake, aimg)
@ -254,7 +260,7 @@ class UpscaledINSwapper(INSwapper):
borderValue=0.0, borderValue=0.0,
) )
img_white[img_white > 20] = 255 img_white[img_white > 20] = 255
fthresh = opts.data.get("faceswaplab_upscaled_swapper_fthresh", 10) fthresh = 10
print("fthresh", fthresh) print("fthresh", fthresh)
fake_diff[fake_diff < fthresh] = 0 fake_diff[fake_diff < fthresh] = 0
fake_diff[fake_diff >= fthresh] = 255 fake_diff[fake_diff >= fthresh] = 255
@ -263,9 +269,8 @@ class UpscaledINSwapper(INSwapper):
mask_h = np.max(mask_h_inds) - np.min(mask_h_inds) 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_w = np.max(mask_w_inds) - np.min(mask_w_inds)
mask_size = int(np.sqrt(mask_h * mask_w)) mask_size = int(np.sqrt(mask_h * mask_w))
erosion_factor = opts.data.get( erosion_factor = options.erosion_factor
"faceswaplab_upscaled_swapper_erosion", 1
)
k = max(int(mask_size // 10 * erosion_factor), int(10 * erosion_factor)) k = max(int(mask_size // 10 * erosion_factor), int(10 * erosion_factor))
kernel = np.ones((k, k), np.uint8) kernel = np.ones((k, k), np.uint8)

@ -17,7 +17,7 @@ def postprocessing_ui() -> List[gr.components.Component]:
choices=["None"] + [x.name() for x in shared.face_restorers], choices=["None"] + [x.name() for x in shared.face_restorers],
value=lambda: opts.data.get( value=lambda: opts.data.get(
"faceswaplab_pp_default_face_restorer", "faceswaplab_pp_default_face_restorer",
shared.face_restorers[0].name(), "None",
), ),
type="value", type="value",
elem_id="faceswaplab_pp_face_restorer", elem_id="faceswaplab_pp_face_restorer",

@ -249,6 +249,8 @@ def tools_ui() -> None:
preview = gr.components.Image( preview = gr.components.Image(
type="pil", type="pil",
label="Preview", label="Preview",
width=512,
height=512,
interactive=False, interactive=False,
elem_id="faceswaplab_build_preview_face", elem_id="faceswaplab_build_preview_face",
) )

@ -6,6 +6,7 @@ from typing import List, Optional, Set, Union
import gradio as gr import gradio as gr
from insightface.app.common import Face from insightface.app.common import Face
from PIL import Image from PIL import Image
from scripts.faceswaplab_swapping.upcaled_inswapper_options import InswappperOptions
from scripts.faceswaplab_utils.imgutils import pil_to_cv2 from scripts.faceswaplab_utils.imgutils import pil_to_cv2
from scripts.faceswaplab_utils.faceswaplab_logging import logger from scripts.faceswaplab_utils.faceswaplab_logging import logger
from scripts.faceswaplab_utils import face_checkpoints_utils from scripts.faceswaplab_utils import face_checkpoints_utils
@ -51,6 +52,8 @@ class FaceSwapUnitSettings:
swap_in_generated: bool swap_in_generated: bool
# Pre inpainting configuration (Don't use optional for this or gradio parsing will fail) : # Pre inpainting configuration (Don't use optional for this or gradio parsing will fail) :
pre_inpainting: InpaintingOptions pre_inpainting: InpaintingOptions
# Configure swapping options
swapping_options: InswappperOptions
# Post inpainting configuration (Don't use optional for this or gradio parsing will fail) : # Post inpainting configuration (Don't use optional for this or gradio parsing will fail) :
post_inpainting: InpaintingOptions post_inpainting: InpaintingOptions
@ -81,6 +84,7 @@ class FaceSwapUnitSettings:
swap_in_generated=True, swap_in_generated=True,
swap_in_source=False, swap_in_source=False,
pre_inpainting=InpaintingOptions.from_api_dto(dto.pre_inpainting), pre_inpainting=InpaintingOptions.from_api_dto(dto.pre_inpainting),
swapping_options=InswappperOptions.from_api_dto(dto.swapping_options),
post_inpainting=InpaintingOptions.from_api_dto(dto.post_inpainting), post_inpainting=InpaintingOptions.from_api_dto(dto.post_inpainting),
) )

@ -2,6 +2,100 @@ from typing import List
from scripts.faceswaplab_ui.faceswaplab_inpainting_ui import face_inpainting_ui 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_utils.face_checkpoints_utils import get_face_checkpoints
import gradio as gr import gradio as gr
from modules.shared import opts
from modules import shared
def faceswap_unit_advanced_options(
is_img2img: bool, unit_num: int = 1, id_prefix: str = "faceswaplab_"
) -> List[gr.components.Component]:
with gr.Accordion(f"Post-Processing & Advanced Mask Options", open=False):
gr.Markdown("""Post-processing and mask settings for unit faces""")
with gr.Row():
face_restorer_name = gr.Radio(
label="Restore Face",
choices=["None"] + [x.name() for x in shared.face_restorers],
value=lambda: opts.data.get(
"faceswaplab_default_upscaled_swapper_face_restorer",
"None",
),
type="value",
elem_id=f"{id_prefix}_face{unit_num}_face_restorer",
)
with gr.Column():
face_restorer_visibility = gr.Slider(
0,
1,
value=lambda: opts.data.get(
"faceswaplab_default_upscaled_swapper_face_restorer_visibility",
1.0,
),
step=0.001,
label="Restore visibility",
elem_id=f"{id_prefix}_face{unit_num}_face_restorer_visibility",
)
codeformer_weight = gr.Slider(
0,
1,
value=lambda: opts.data.get(
"faceswaplab_default_upscaled_swapper_face_restorer_weight", 1.0
),
step=0.001,
label="codeformer weight",
elem_id=f"{id_prefix}_face{unit_num}_face_restorer_weight",
)
upscaler_name = gr.Dropdown(
choices=[upscaler.name for upscaler in shared.sd_upscalers],
value=lambda: opts.data.get(
"faceswaplab_default_upscaled_swapper_upscaler", ""
),
label="Upscaler",
elem_id=f"{id_prefix}_face{unit_num}_upscaler",
)
improved_mask = gr.Checkbox(
lambda: opts.data.get(
"faceswaplab_default_upscaled_swapper_improved_mask", False
),
interactive=True,
label="Use improved segmented mask (use pastenet to mask only the face)",
elem_id=f"{id_prefix}_face{unit_num}_improved_mask",
)
color_corrections = gr.Checkbox(
lambda: opts.data.get(
"faceswaplab_default_upscaled_swapper_fixcolor", False
),
interactive=True,
label="Use color corrections",
elem_id=f"{id_prefix}_face{unit_num}_color_corrections",
)
sharpen_face = gr.Checkbox(
lambda: opts.data.get(
"faceswaplab_default_upscaled_swapper_sharpen", False
),
interactive=True,
label="sharpen face",
elem_id=f"{id_prefix}_face{unit_num}_sharpen_face",
)
erosion_factor = gr.Slider(
0.0,
10.0,
lambda: opts.data.get("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",
)
return [
face_restorer_name,
face_restorer_visibility,
codeformer_weight,
upscaler_name,
improved_mask,
color_corrections,
sharpen_face,
erosion_factor,
]
def faceswap_unit_ui( def faceswap_unit_ui(
@ -62,35 +156,6 @@ def faceswap_unit_ui(
elem_id=f"{id_prefix}_face{unit_num}_blend_faces", elem_id=f"{id_prefix}_face{unit_num}_blend_faces",
interactive=True, interactive=True,
) )
gr.Markdown("""Discard images with low similarity or no faces :""")
with gr.Row():
check_similarity = gr.Checkbox(
False,
placeholder="discard",
label="Check similarity",
elem_id=f"{id_prefix}_face{unit_num}_check_similarity",
)
compute_similarity = gr.Checkbox(
False,
label="Compute similarity",
elem_id=f"{id_prefix}_face{unit_num}_compute_similarity",
)
min_sim = gr.Slider(
0,
1,
0,
step=0.01,
label="Min similarity",
elem_id=f"{id_prefix}_face{unit_num}_min_similarity",
)
min_ref_sim = gr.Slider(
0,
1,
0,
step=0.01,
label="Min reference similarity",
elem_id=f"{id_prefix}_face{unit_num}_min_ref_similarity",
)
gr.Markdown( gr.Markdown(
"""Select the face to be swapped, you can sort by size or use the same gender as the desired face:""" """Select the face to be swapped, you can sort by size or use the same gender as the desired face:"""
@ -143,11 +208,46 @@ def faceswap_unit_ui(
visible=is_img2img, visible=is_img2img,
elem_id=f"{id_prefix}_face{unit_num}_swap_in_generated", elem_id=f"{id_prefix}_face{unit_num}_swap_in_generated",
) )
with gr.Accordion("Similarity", open=False):
gr.Markdown("""Discard images with low similarity or no faces :""")
with gr.Row():
check_similarity = gr.Checkbox(
False,
placeholder="discard",
label="Check similarity",
elem_id=f"{id_prefix}_face{unit_num}_check_similarity",
)
compute_similarity = gr.Checkbox(
False,
label="Compute similarity",
elem_id=f"{id_prefix}_face{unit_num}_compute_similarity",
)
min_sim = gr.Slider(
0,
1,
0,
step=0.01,
label="Min similarity",
elem_id=f"{id_prefix}_face{unit_num}_min_similarity",
)
min_ref_sim = gr.Slider(
0,
1,
0,
step=0.01,
label="Min reference similarity",
elem_id=f"{id_prefix}_face{unit_num}_min_ref_similarity",
)
pre_inpainting = face_inpainting_ui( pre_inpainting = face_inpainting_ui(
name="Pre-Inpainting (Before swapping)", name="Pre-Inpainting (Before swapping)",
id_prefix=f"{id_prefix}_face{unit_num}_preinpainting", id_prefix=f"{id_prefix}_face{unit_num}_preinpainting",
description="Pre-inpainting sends face to inpainting before swapping", description="Pre-inpainting sends face to inpainting before swapping",
) )
options = faceswap_unit_advanced_options(is_img2img, unit_num, id_prefix)
post_inpainting = face_inpainting_ui( post_inpainting = face_inpainting_ui(
name="Post-Inpainting (After swapping)", name="Post-Inpainting (After swapping)",
id_prefix=f"{id_prefix}_face{unit_num}_postinpainting", id_prefix=f"{id_prefix}_face{unit_num}_postinpainting",
@ -173,6 +273,7 @@ def faceswap_unit_ui(
swap_in_generated, swap_in_generated,
] ]
+ pre_inpainting + pre_inpainting
+ options
+ post_inpainting + post_inpainting
) )

@ -7,21 +7,19 @@ import torch
import modules.scripts as scripts import modules.scripts as scripts
from modules import scripts from modules import scripts
from scripts.faceswaplab_swapping.upcaled_inswapper_options import InswappperOptions
from scripts.faceswaplab_utils.faceswaplab_logging import logger from scripts.faceswaplab_utils.faceswaplab_logging import logger
from scripts.faceswaplab_utils.typing import * from scripts.faceswaplab_utils.typing import *
from scripts.faceswaplab_utils import imgutils from scripts.faceswaplab_utils import imgutils
from scripts.faceswaplab_postprocessing.postprocessing import enhance_image
from scripts.faceswaplab_postprocessing.postprocessing_options import (
PostProcessingOptions,
)
from scripts.faceswaplab_utils.models_utils import get_models from scripts.faceswaplab_utils.models_utils import get_models
from modules.shared import opts
import traceback import traceback
import dill as pickle # will be removed in future versions import dill as pickle # will be removed in future versions
from scripts.faceswaplab_swapping import swapper from scripts.faceswaplab_swapping import swapper
from pprint import pformat from pprint import pformat
import re import re
from client_api import api_utils
import tempfile
def sanitize_name(name: str) -> str: def sanitize_name(name: str) -> str:
@ -93,16 +91,9 @@ def build_face_checkpoint_and_save(
source_face=blended_face, source_face=blended_face,
target_img=reference_preview_img, target_img=reference_preview_img,
model=get_models()[0], model=get_models()[0],
upscaled_swapper=opts.data.get( swapping_options=InswappperOptions(face_restorer_name="Codeformer"),
"faceswaplab_upscaled_swapper", False
),
)
preview_image = enhance_image(
result.image,
PostProcessingOptions(
face_restorer_name="CodeFormer", restorer_visibility=1
),
) )
preview_image = result.image
file_path = os.path.join(get_checkpoint_path(), f"{name}.safetensors") file_path = os.path.join(get_checkpoint_path(), f"{name}.safetensors")
if not overwrite: if not overwrite:
@ -147,6 +138,16 @@ def save_face(face: Face, filename: str) -> None:
def load_face(name: str) -> Face: def load_face(name: str) -> 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)
face = {}
with safe_open(temp_file.name, framework="pt", device="cpu") as f:
for k in f.keys():
logger.debug("load key %s", k)
face[k] = f.get_tensor(k).numpy()
return Face(face)
filename = matching_checkpoint(name) filename = matching_checkpoint(name)
if filename is None: if filename is None:
return None return None

Loading…
Cancel
Save