huge changes, inpainting in faces unit, change faces processing, change api, refactor, requires further testing

This commit is contained in:
Tran Xen
2023-08-01 20:17:43 +02:00
parent 1d9b3a64dc
commit ee7f7d09d2
24 changed files with 786 additions and 418 deletions
@@ -0,0 +1,68 @@
from typing import List
import gradio as gr
from modules.shared import opts
from modules import sd_models, sd_samplers
def face_inpainting_ui(
name: str, id_prefix: str = "faceswaplab", description: str = ""
) -> List[gr.components.Component]:
with gr.Accordion(name, open=False):
gr.Markdown(description)
inpainting_denoising_strength = gr.Slider(
0,
1,
0,
step=0.01,
elem_id=f"{id_prefix}_pp_inpainting_denoising_strength",
label="Denoising strenght",
)
inpainting_denoising_prompt = gr.Textbox(
opts.data.get(
"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(
"faceswaplab_pp_default_inpainting_negative_prompt", "blurry"
),
elem_id=f"{id_prefix}_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 sd_samplers.all_samplers]
inpainting_sampler = gr.Dropdown(
choices=samplers_names,
value=[samplers_names[0]],
label="Inpainting Sampler",
elem_id=f"{id_prefix}_pp_inpainting_sampler",
)
inpainting_denoising_steps = gr.Slider(
1,
150,
20,
step=1,
label="Inpainting steps",
elem_id=f"{id_prefix}_pp_inpainting_steps",
)
inpaiting_model = gr.Dropdown(
choices=["Current"] + sd_models.checkpoint_tiles(),
default="Current",
label="sd model (experimental)",
elem_id=f"{id_prefix}_pp_inpainting_sd_model",
)
gradio_components: List[gr.components.Component] = [
inpainting_denoising_strength,
inpainting_denoising_prompt,
inpainting_denoising_negative_prompt,
inpainting_denoising_steps,
inpainting_sampler,
inpaiting_model,
]
return gradio_components
@@ -7,9 +7,9 @@ from scripts.faceswaplab_postprocessing.postprocessing_options import Inpainting
def postprocessing_ui() -> List[gr.components.Component]:
with gr.Tab(f"Post-Processing"):
with gr.Tab(f"Global Post-Processing"):
gr.Markdown(
"""Upscaling is performed on the whole image. Upscaling happens before face restoration."""
"""Upscaling is performed on the whole image and all faces (including not swapped). Upscaling happens before face restoration."""
)
with gr.Row():
face_restorer_name = gr.Radio(
@@ -130,11 +130,11 @@ def postprocessing_ui() -> List[gr.components.Component]:
upscaler_name,
upscaler_scale,
upscaler_visibility,
inpainting_when,
inpainting_denoising_strength,
inpainting_denoising_prompt,
inpainting_denoising_negative_prompt,
inpainting_denoising_steps,
inpainting_sampler,
inpainting_when,
inpaiting_model,
]
+80 -55
View File
@@ -1,31 +1,32 @@
import os
import re
import traceback
from pprint import pformat, pprint
from scripts.faceswaplab_utils import face_utils
from typing import *
from scripts.faceswaplab_utils.typing import *
import gradio as gr
import modules.scripts as scripts
import onnx
import pandas as pd
from scripts.faceswaplab_ui.faceswaplab_unit_ui import faceswap_unit_ui
from scripts.faceswaplab_ui.faceswaplab_postprocessing_ui import postprocessing_ui
from modules import scripts
from PIL import Image
from modules.shared import opts
from PIL import Image
from scripts.faceswaplab_utils import imgutils
from scripts.faceswaplab_utils.models_utils import get_models
from scripts.faceswaplab_utils.faceswaplab_logging import logger
import scripts.faceswaplab_swapping.swapper as swapper
from scripts.faceswaplab_postprocessing.postprocessing import enhance_image
from scripts.faceswaplab_postprocessing.postprocessing_options import (
PostProcessingOptions,
)
from scripts.faceswaplab_postprocessing.postprocessing import enhance_image
from dataclasses import fields
from typing import Any, Dict, List, Optional
from scripts.faceswaplab_ui.faceswaplab_postprocessing_ui import postprocessing_ui
from scripts.faceswaplab_ui.faceswaplab_unit_settings import FaceSwapUnitSettings
import re
from scripts.faceswaplab_ui.faceswaplab_unit_ui import faceswap_unit_ui
from scripts.faceswaplab_utils import face_utils, imgutils
from scripts.faceswaplab_utils.faceswaplab_logging import logger
from scripts.faceswaplab_utils.models_utils import get_models
from scripts.faceswaplab_utils.ui_utils import dataclasses_from_flat_list
def compare(img1: Image.Image, img2: Image.Image) -> str:
def compare(img1: PILImage, img2: PILImage) -> str:
"""
Compares the similarity between two faces extracted from images using cosine similarity.
@@ -43,14 +44,15 @@ def compare(img1: Image.Image, img2: Image.Image) -> str:
except Exception as e:
logger.error("Fail to compare", e)
traceback.print_exc()
return "You need 2 images to compare"
def extract_faces(
files: List[gr.File],
extract_path: Optional[str],
*components: List[gr.components.Component],
) -> Optional[List[Image.Image]]:
*components: Tuple[gr.components.Component, ...],
) -> Optional[List[PILImage]]:
"""
Extracts faces from a list of image files.
@@ -69,22 +71,32 @@ def extract_faces(
If no faces are found, None is returned.
"""
postprocess_options = PostProcessingOptions(*components) # type: ignore
images = [
Image.open(file.name) for file in files
] # potentially greedy but Image.open is supposed to be lazy
return swapper.extract_faces(
images, extract_path=extract_path, postprocess_options=postprocess_options
)
if files and len(files) == 0:
logger.error("You need at least one image file to extract")
return []
try:
postprocess_options = PostProcessingOptions(*components) # type: ignore
images = [
Image.open(file.name) for file in files
] # potentially greedy but Image.open is supposed to be lazy
result_images = swapper.extract_faces(
images, extract_path=extract_path, postprocess_options=postprocess_options
)
return result_images
except Exception as e:
logger.error("Failed to extract : %s", e)
traceback.print_exc()
return None
def analyse_faces(image: Image.Image, det_threshold: float = 0.5) -> Optional[str]:
def analyse_faces(image: PILImage, det_threshold: float = 0.5) -> Optional[str]:
"""
Function to analyze the faces in an image and provide a detailed report.
Parameters
----------
image : PIL.Image.Image
image : PIL.PILImage
The input image where faces will be detected. The image must be a PIL Image object.
det_threshold : float, optional
@@ -122,6 +134,7 @@ def analyse_faces(image: Image.Image, det_threshold: float = 0.5) -> Optional[st
except Exception as e:
logger.error("Analysis Failed : %s", e)
traceback.print_exc()
return None
@@ -142,7 +155,7 @@ def sanitize_name(name: str) -> str:
def build_face_checkpoint_and_save(
batch_files: gr.File, name: str
) -> Optional[Image.Image]:
) -> 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
@@ -153,7 +166,7 @@ def build_face_checkpoint_and_save(
name (str): The name assigned to the face checkpoint.
Returns:
PIL.Image.Image or None: The resulting swapped face image if the process is successful; None otherwise.
PIL.PILImage or None: The resulting swapped face image if the process is successful; None otherwise.
"""
try:
@@ -170,7 +183,7 @@ def build_face_checkpoint_and_save(
os.makedirs(faces_path, exist_ok=True)
target_img = None
target_img: PILImage = None
if blended_face:
if blended_face["gender"] == 0:
target_img = Image.open(os.path.join(preview_path, "woman.png"))
@@ -180,15 +193,30 @@ def build_face_checkpoint_and_save(
if name == "":
name = "default_name"
pprint(blended_face)
result = swapper.swap_face(
blended_face, blended_face, target_img, get_models()[0]
)
result_image = enhance_image(
result.image,
PostProcessingOptions(
face_restorer_name="CodeFormer", restorer_visibility=1
),
target_face = swapper.get_or_default(
swapper.get_faces(imgutils.pil_to_cv2(target_img)), 0, None
)
if target_face is None:
logger.error(
"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(
reference_face=blended_face,
target_faces=[target_face],
source_face=blended_face,
target_img=target_img,
model=get_models()[0],
upscaled_swapper=opts.data.get(
"faceswaplab_upscaled_swapper", False
),
)
result_image = enhance_image(
result.image,
PostProcessingOptions(
face_restorer_name="CodeFormer", restorer_visibility=1
),
)
file_path = os.path.join(faces_path, f"{name}.safetensors")
file_number = 1
@@ -202,14 +230,16 @@ def build_face_checkpoint_and_save(
face_utils.save_face(filename=file_path, face=blended_face)
try:
data = face_utils.load_face(filename=file_path)
print(data)
logger.debug(data)
except Exception as e:
print(e)
return result_image
print("No face found")
logger.error("No face found")
except Exception as e:
logger.error("Failed to build checkpoint %s", e)
traceback.print_exc()
return None
return target_img
@@ -242,36 +272,32 @@ def explore_onnx_faceswap_model(model_path: str) -> pd.DataFrame:
df = pd.DataFrame(data)
except Exception as e:
logger.info("Failed to explore model %s", e)
logger.error("Failed to explore model %s", e)
traceback.print_exc()
return None
return df
def batch_process(
files: List[gr.File], save_path: str, *components: List[gr.components.Component]
) -> Optional[List[Image.Image]]:
files: List[gr.File], save_path: str, *components: Tuple[Any, ...]
) -> Optional[List[PILImage]]:
try:
units_count = opts.data.get("faceswaplab_units_count", 3)
units: List[FaceSwapUnitSettings] = []
# Parse and convert units flat components into FaceSwapUnitSettings
for i in range(0, units_count):
units += [FaceSwapUnitSettings.get_unit_configuration(i, components)] # type: ignore
for i, u in enumerate(units):
logger.debug("%s, %s", pformat(i), pformat(u))
# Parse the postprocessing options
# We must first find where to start from (after face swapping units)
len_conf: int = len(fields(FaceSwapUnitSettings))
shift: int = units_count * len_conf
postprocess_options = PostProcessingOptions(
*components[shift : shift + len(fields(PostProcessingOptions))] # type: ignore
classes: List[Any] = dataclasses_from_flat_list(
[FaceSwapUnitSettings] * units_count + [PostProcessingOptions],
components,
)
logger.debug("%s", pformat(postprocess_options))
units: List[FaceSwapUnitSettings] = [
u for u in classes if isinstance(u, FaceSwapUnitSettings)
]
postprocess_options = classes[-1]
images = [
Image.open(file.name) for file in files
] # potentially greedy but Image.open is supposed to be lazy
return swapper.batch_process(
images,
save_path=save_path,
@@ -280,7 +306,6 @@ def batch_process(
)
except Exception as e:
logger.error("Batch Process error : %s", e)
import traceback
traceback.print_exc()
return None
@@ -1,15 +1,16 @@
from scripts.faceswaplab_swapping import swapper
import numpy as np
import base64
import io
from dataclasses import dataclass, fields
from typing import Any, List, Optional, Set, Union
from dataclasses import dataclass
from typing import List, Optional, Set, Union
import gradio as gr
from insightface.app.common import Face
from PIL import Image
from scripts.faceswaplab_utils.imgutils import pil_to_cv2
from scripts.faceswaplab_utils.faceswaplab_logging import logger
from scripts.faceswaplab_utils import face_utils
from scripts.faceswaplab_inpainting.faceswaplab_inpainting import InpaintingOptions
from client_api import api_utils
@dataclass
@@ -17,11 +18,11 @@ class FaceSwapUnitSettings:
# ORDER of parameters is IMPORTANT. It should match the result of faceswap_unit_ui
# The image given in reference
source_img: Union[Image.Image, str]
source_img: Optional[Union[Image.Image, str]]
# The checkpoint file
source_face: str
source_face: Optional[str]
# The batch source images
_batch_files: Union[gr.components.File, List[Image.Image]]
_batch_files: Optional[Union[gr.components.File, List[Image.Image]]]
# Will blend faces if True
blend_faces: bool
# Enable this unit
@@ -48,14 +49,39 @@ class FaceSwapUnitSettings:
swap_in_source: bool
# Swap in the generated image in img2img (always on for txt2img)
swap_in_generated: bool
# Pre inpainting configuration (Don't use optional for this or gradio parsing will fail) :
pre_inpainting: InpaintingOptions
# Post inpainting configuration (Don't use optional for this or gradio parsing will fail) :
post_inpainting: InpaintingOptions
@staticmethod
def get_unit_configuration(
unit: int, components: List[gr.components.Component]
) -> Any:
fields_count = len(fields(FaceSwapUnitSettings))
def from_api_dto(dto: api_utils.FaceSwapUnit) -> "FaceSwapUnitSettings":
"""
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.
"""
return FaceSwapUnitSettings(
*components[unit * fields_count : unit * fields_count + fields_count]
source_img=api_utils.base64_to_pil(dto.source_img),
source_face=dto.source_face,
_batch_files=dto.get_batch_images(),
blend_faces=dto.blend_faces,
enable=True,
same_gender=dto.same_gender,
sort_by_size=dto.sort_by_size,
check_similarity=dto.check_similarity,
_compute_similarity=dto.compute_similarity,
min_ref_sim=dto.min_ref_sim,
min_sim=dto.min_sim,
_faces_index=",".join([str(i) for i in (dto.faces_index)]),
reference_face_index=dto.reference_face_index,
swap_in_generated=True,
swap_in_source=False,
pre_inpainting=InpaintingOptions.from_api_dto(dto.pre_inpainting),
post_inpainting=InpaintingOptions.from_api_dto(dto.post_inpainting),
)
@property
@@ -156,24 +182,5 @@ class FaceSwapUnitSettings:
"""
if not hasattr(self, "_blended_faces"):
self._blended_faces = swapper.blend_faces(self.faces)
assert (
all(
[
not np.array_equal(
self._blended_faces.embedding, face.embedding
)
for face in self.faces
]
)
if len(self.faces) > 1
else True
), "Blended faces cannot be the same as one of the face if len(face)>0"
assert (
not np.array_equal(
self._blended_faces.embedding, self.reference_face.embedding
)
if len(self.faces) > 1
else True
), "Blended faces cannot be the same as reference face if len(face)>0"
return self._blended_faces
+35 -17
View File
@@ -1,4 +1,5 @@
from typing import List
from scripts.faceswaplab_ui.faceswaplab_inpainting_ui import face_inpainting_ui
from scripts.faceswaplab_utils.face_utils import get_face_checkpoints
import gradio as gr
@@ -142,22 +143,39 @@ def faceswap_unit_ui(
visible=is_img2img,
elem_id=f"{id_prefix}_face{unit_num}_swap_in_generated",
)
pre_inpainting = face_inpainting_ui(
name="Pre-Inpainting (Before swapping)",
id_prefix=f"{id_prefix}_face{unit_num}_preinpainting",
description="Pre-inpainting sends face to inpainting before swapping",
)
post_inpainting = face_inpainting_ui(
name="Post-Inpainting (After swapping)",
id_prefix=f"{id_prefix}_face{unit_num}_postinpainting",
description="Post-inpainting sends face to inpainting after swapping",
)
gradio_components: List[gr.components.Component] = (
[
img,
face,
batch_files,
blend_faces,
enable,
same_gender,
sort_by_size,
check_similarity,
compute_similarity,
min_sim,
min_ref_sim,
target_faces_index,
reference_faces_index,
swap_in_source,
swap_in_generated,
]
+ pre_inpainting
+ post_inpainting
)
# If changed, you need to change FaceSwapUnitSettings accordingly
# ORDER of parameters is IMPORTANT. It should match the result of FaceSwapUnitSettings
return [
img,
face,
batch_files,
blend_faces,
enable,
same_gender,
sort_by_size,
check_similarity,
compute_similarity,
min_sim,
min_ref_sim,
target_faces_index,
reference_faces_index,
swap_in_source,
swap_in_generated,
]
return gradio_components