|
|
@ -24,39 +24,76 @@ from scripts.faceswaplab_postprocessing.postprocessing_options import (
|
|
|
|
)
|
|
|
|
)
|
|
|
|
from scripts.faceswaplab_postprocessing.postprocessing import enhance_image
|
|
|
|
from scripts.faceswaplab_postprocessing.postprocessing import enhance_image
|
|
|
|
from dataclasses import fields
|
|
|
|
from dataclasses import fields
|
|
|
|
from typing import Any, List, Optional, Union
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from scripts.faceswaplab_ui.faceswaplab_unit_settings import FaceSwapUnitSettings
|
|
|
|
from scripts.faceswaplab_ui.faceswaplab_unit_settings import FaceSwapUnitSettings
|
|
|
|
from scripts.faceswaplab_utils.models_utils import get_current_model
|
|
|
|
from scripts.faceswaplab_utils.models_utils import get_current_model
|
|
|
|
import re
|
|
|
|
import re
|
|
|
|
|
|
|
|
from scripts.faceswaplab_globals import REFERENCE_PATH
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def compare(img1: Image.Image, img2: Image.Image) -> Union[float, str]:
|
|
|
|
def compare(img1: Image.Image, img2: Image.Image) -> str:
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
Compares the similarity between two faces extracted from images using cosine similarity.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
|
|
img1: The first image containing a face.
|
|
|
|
|
|
|
|
img2: The second image containing a face.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
A str of a float value representing the similarity between the two faces (0 to 1).
|
|
|
|
|
|
|
|
Returns"You need 2 images to compare" if one or both of the images do not contain any faces.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
try:
|
|
|
|
if img1 is not None and img2 is not None:
|
|
|
|
if img1 is not None and img2 is not None:
|
|
|
|
return swapper.compare_faces(img1, img2)
|
|
|
|
return str(swapper.compare_faces(img1, img2))
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
logger.error("Fail to compare", e)
|
|
|
|
|
|
|
|
|
|
|
|
return "You need 2 images to compare"
|
|
|
|
return "You need 2 images to compare"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def extract_faces(
|
|
|
|
def extract_faces(
|
|
|
|
files,
|
|
|
|
files: Optional[List[str]],
|
|
|
|
extract_path,
|
|
|
|
extract_path: Optional[str],
|
|
|
|
*components: List[gr.components.Component],
|
|
|
|
*components: List[gr.components.Component],
|
|
|
|
):
|
|
|
|
) -> Optional[List[str]]:
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
Extracts faces from a list of image files.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Given a list of image file paths, this function opens each image, extracts the faces,
|
|
|
|
|
|
|
|
and saves them in a specified directory. Post-processing is applied to each extracted face,
|
|
|
|
|
|
|
|
and the processed faces are saved as separate PNG files.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
|
|
|
|
files (Optional[List[str]]): List of file paths to the images to extract faces from.
|
|
|
|
|
|
|
|
extract_path (Optional[str]): Path where the extracted faces will be saved.
|
|
|
|
|
|
|
|
If no path is provided, a temporary directory will be created.
|
|
|
|
|
|
|
|
components (List[gr.components.Component]): List of components for post-processing.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
Optional[List[str]]: List of file paths to the saved images of the extracted faces.
|
|
|
|
|
|
|
|
If no faces are found, None is returned.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
postprocess_options = PostProcessingOptions(*components) # type: ignore
|
|
|
|
postprocess_options = PostProcessingOptions(*components) # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
if not extract_path:
|
|
|
|
if not extract_path:
|
|
|
|
tempfile.mkdtemp()
|
|
|
|
extract_path = tempfile.mkdtemp()
|
|
|
|
if files is not None:
|
|
|
|
|
|
|
|
|
|
|
|
if files:
|
|
|
|
images = []
|
|
|
|
images = []
|
|
|
|
for file in files:
|
|
|
|
for file in files:
|
|
|
|
img = Image.open(file.name).convert("RGB")
|
|
|
|
img = Image.open(file).convert("RGB")
|
|
|
|
faces = swapper.get_faces(pil_to_cv2(img))
|
|
|
|
faces = swapper.get_faces(pil_to_cv2(img))
|
|
|
|
|
|
|
|
|
|
|
|
if faces:
|
|
|
|
if faces:
|
|
|
|
face_images = []
|
|
|
|
face_images = []
|
|
|
|
for face in faces:
|
|
|
|
for face in faces:
|
|
|
|
bbox = face.bbox.astype(int)
|
|
|
|
bbox = face.bbox.astype(int)
|
|
|
|
x_min, y_min, x_max, y_max = bbox
|
|
|
|
x_min, y_min, x_max, y_max = bbox
|
|
|
|
face_image = img.crop((x_min, y_min, x_max, y_max))
|
|
|
|
face_image = img.crop((x_min, y_min, x_max, y_max))
|
|
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
if (
|
|
|
|
postprocess_options.face_restorer_name
|
|
|
|
postprocess_options.face_restorer_name
|
|
|
|
or postprocess_options.restorer_visibility
|
|
|
|
or postprocess_options.restorer_visibility
|
|
|
@ -64,21 +101,55 @@ def extract_faces(
|
|
|
|
postprocess_options.scale = (
|
|
|
|
postprocess_options.scale = (
|
|
|
|
1 if face_image.width > 512 else 512 // face_image.width
|
|
|
|
1 if face_image.width > 512 else 512 // face_image.width
|
|
|
|
)
|
|
|
|
)
|
|
|
|
face_image = enhance_image(
|
|
|
|
face_image = enhance_image(face_image, postprocess_options)
|
|
|
|
face_image,
|
|
|
|
|
|
|
|
postprocess_options,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
path = tempfile.NamedTemporaryFile(
|
|
|
|
path = tempfile.NamedTemporaryFile(
|
|
|
|
delete=False, suffix=".png", dir=extract_path
|
|
|
|
delete=False, suffix=".png", dir=extract_path
|
|
|
|
).name
|
|
|
|
).name
|
|
|
|
face_image.save(path)
|
|
|
|
face_image.save(path)
|
|
|
|
face_images.append(path)
|
|
|
|
face_images.append(path)
|
|
|
|
|
|
|
|
|
|
|
|
images += face_images
|
|
|
|
images += face_images
|
|
|
|
|
|
|
|
|
|
|
|
return images
|
|
|
|
return images
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
logger.info("Failed to extract : %s", e)
|
|
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def analyse_faces(image: Image.Image, det_threshold: float = 0.5) -> str:
|
|
|
|
def analyse_faces(image: Image.Image, 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
|
|
|
|
|
|
|
|
The input image where faces will be detected. The image must be a PIL Image object.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
det_threshold : float, optional
|
|
|
|
|
|
|
|
The detection threshold for the face detection process, by default 0.5. It determines
|
|
|
|
|
|
|
|
the confidence level at which the function will consider a detected object as a face.
|
|
|
|
|
|
|
|
Value should be in the range [0, 1], with higher values indicating greater certainty.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
|
|
|
-------
|
|
|
|
|
|
|
|
str or None
|
|
|
|
|
|
|
|
Returns a formatted string providing details about each face detected in the image.
|
|
|
|
|
|
|
|
For each face, the string will include an index and a set of facial details.
|
|
|
|
|
|
|
|
In the event of an exception (e.g., analysis failure), the function will log the error
|
|
|
|
|
|
|
|
and return None.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Raises
|
|
|
|
|
|
|
|
------
|
|
|
|
|
|
|
|
This function handles exceptions internally and does not raise.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Examples
|
|
|
|
|
|
|
|
--------
|
|
|
|
|
|
|
|
>>> image = Image.open("test.jpg")
|
|
|
|
|
|
|
|
>>> print(analyse_faces(image, 0.7))
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
faces = swapper.get_faces(imgutils.pil_to_cv2(image), det_thresh=det_threshold)
|
|
|
|
faces = swapper.get_faces(imgutils.pil_to_cv2(image), det_thresh=det_threshold)
|
|
|
|
result = ""
|
|
|
|
result = ""
|
|
|
@ -86,11 +157,12 @@ def analyse_faces(image: Image.Image, det_threshold: float = 0.5) -> str:
|
|
|
|
result += f"\nFace {i} \n" + "=" * 40 + "\n"
|
|
|
|
result += f"\nFace {i} \n" + "=" * 40 + "\n"
|
|
|
|
result += pformat(face) + "\n"
|
|
|
|
result += pformat(face) + "\n"
|
|
|
|
result += "=" * 40
|
|
|
|
result += "=" * 40
|
|
|
|
return result
|
|
|
|
return result if result else None
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
except Exception as e:
|
|
|
|
logger.error("Analysis Failed : %s", e)
|
|
|
|
logger.error("Analysis Failed : %s", e)
|
|
|
|
return "Analysis Failed"
|
|
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def sanitize_name(name: str) -> str:
|
|
|
|
def sanitize_name(name: str) -> str:
|
|
|
@ -116,14 +188,15 @@ def build_face_checkpoint_and_save(
|
|
|
|
Returns:
|
|
|
|
Returns:
|
|
|
|
PIL.Image.Image or None: The resulting swapped face image if the process is successful; None otherwise.
|
|
|
|
PIL.Image.Image or None: The resulting swapped face image if the process is successful; None otherwise.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
name = sanitize_name(name)
|
|
|
|
name = sanitize_name(name)
|
|
|
|
batch_files = batch_files or []
|
|
|
|
batch_files = batch_files or []
|
|
|
|
logger.info("Build %s %s", name, [x.name for x in batch_files])
|
|
|
|
logger.info("Build %s %s", name, [x.name for x in batch_files])
|
|
|
|
faces = swapper.get_faces_from_img_files(batch_files)
|
|
|
|
faces = swapper.get_faces_from_img_files(batch_files)
|
|
|
|
blended_face = swapper.blend_faces(faces)
|
|
|
|
blended_face = swapper.blend_faces(faces)
|
|
|
|
preview_path = os.path.join(
|
|
|
|
preview_path = REFERENCE_PATH
|
|
|
|
scripts.basedir(), "extensions", "sd-webui-faceswaplab", "references"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
faces_path = os.path.join(scripts.basedir(), "models", "faceswaplab", "faces")
|
|
|
|
faces_path = os.path.join(scripts.basedir(), "models", "faceswaplab", "faces")
|
|
|
|
|
|
|
|
|
|
|
|
os.makedirs(faces_path, exist_ok=True)
|
|
|
|
os.makedirs(faces_path, exist_ok=True)
|
|
|
@ -172,12 +245,16 @@ def build_face_checkpoint_and_save(
|
|
|
|
return result_image
|
|
|
|
return result_image
|
|
|
|
|
|
|
|
|
|
|
|
print("No face found")
|
|
|
|
print("No face found")
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
logger.error("Failed to build checkpoint %s", e)
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
return target_img
|
|
|
|
return target_img
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def explore_onnx_faceswap_model(model_path):
|
|
|
|
def explore_onnx_faceswap_model(model_path: str) -> pd.DataFrame:
|
|
|
|
data = {
|
|
|
|
try:
|
|
|
|
|
|
|
|
data: Dict[str, Any] = {
|
|
|
|
"Node Name": [],
|
|
|
|
"Node Name": [],
|
|
|
|
"Op Type": [],
|
|
|
|
"Op Type": [],
|
|
|
|
"Inputs": [],
|
|
|
|
"Inputs": [],
|
|
|
@ -201,11 +278,14 @@ def explore_onnx_faceswap_model(model_path):
|
|
|
|
data["Attributes"].append(attributes)
|
|
|
|
data["Attributes"].append(attributes)
|
|
|
|
|
|
|
|
|
|
|
|
df = pd.DataFrame(data)
|
|
|
|
df = pd.DataFrame(data)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
logger.info("Failed to explore model %s", e)
|
|
|
|
|
|
|
|
return None
|
|
|
|
return df
|
|
|
|
return df
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def batch_process(
|
|
|
|
def batch_process(
|
|
|
|
files, save_path, *components: List[gr.components.Component]
|
|
|
|
files: List[gr.File], save_path: str, *components: List[gr.components.Component]
|
|
|
|
) -> Optional[List[Image.Image]]:
|
|
|
|
) -> Optional[List[Image.Image]]:
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
if save_path is not None:
|
|
|
|
if save_path is not None:
|
|
|
@ -216,7 +296,7 @@ def batch_process(
|
|
|
|
|
|
|
|
|
|
|
|
# Parse and convert units flat components into FaceSwapUnitSettings
|
|
|
|
# Parse and convert units flat components into FaceSwapUnitSettings
|
|
|
|
for i in range(0, units_count):
|
|
|
|
for i in range(0, units_count):
|
|
|
|
units += [FaceSwapUnitSettings.get_unit_configuration(i, components)]
|
|
|
|
units += [FaceSwapUnitSettings.get_unit_configuration(i, components)] # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
for i, u in enumerate(units):
|
|
|
|
for i, u in enumerate(units):
|
|
|
|
logger.debug("%s, %s", pformat(i), pformat(u))
|
|
|
|
logger.debug("%s, %s", pformat(i), pformat(u))
|
|
|
|