huge changes, inpainting in faces unit, change faces processing, change api, refactor, requires further testing
This commit is contained in:
@@ -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,
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user