@ -7,7 +7,7 @@ import tempfile
import cv2
import cv2
import insightface
import insightface
import numpy as np
import numpy as np
from insightface . app . common import Face
from insightface . app . common import Face as ISFace
from PIL import Image
from PIL import Image
from sklearn . metrics . pairwise import cosine_similarity
from sklearn . metrics . pairwise import cosine_similarity
@ -28,7 +28,8 @@ from scripts.faceswaplab_postprocessing.postprocessing_options import (
)
)
from scripts . faceswaplab_utils . models_utils import get_current_model
from scripts . faceswaplab_utils . models_utils import get_current_model
import gradio as gr
import gradio as gr
from scripts . faceswaplab_utils . typing import CV2ImgU8 , PILImage , Face
from scripts . faceswaplab_inpainting . i2i_pp import img2img_diffusion
providers = [ " CPUExecutionProvider " ]
providers = [ " CPUExecutionProvider " ]
@ -60,7 +61,7 @@ def cosine_similarity_face(face1: Face, face2: Face) -> float:
return max ( 0 , similarity [ 0 , 0 ] )
return max ( 0 , similarity [ 0 , 0 ] )
def compare_faces ( img1 : Image. Image , img2 : Image . Image) - > float :
def compare_faces ( img1 : PILImage, img2 : PIL Image) - > float :
"""
"""
Compares the similarity between two faces extracted from images using cosine similarity .
Compares the similarity between two faces extracted from images using cosine similarity .
@ -87,22 +88,22 @@ def compare_faces(img1: Image.Image, img2: Image.Image) -> float:
def batch_process (
def batch_process (
src_images : List [ Image. Image] ,
src_images : List [ PIL Image] ,
save_path : Optional [ str ] ,
save_path : Optional [ str ] ,
units : List [ FaceSwapUnitSettings ] ,
units : List [ FaceSwapUnitSettings ] ,
postprocess_options : PostProcessingOptions ,
postprocess_options : PostProcessingOptions ,
) - > Optional [ List [ Image. Image] ] :
) - > Optional [ List [ PIL Image] ] :
"""
"""
Process a batch of images , apply face swapping according to the given settings , and optionally save the resulting images to a specified path .
Process a batch of images , apply face swapping according to the given settings , and optionally save the resulting images to a specified path .
Args :
Args :
src_images ( List [ Image. Image] ) : List of source PIL Images to process .
src_images ( List [ PIL Image] ) : List of source PIL Images to process .
save_path ( Optional [ str ] ) : Destination path where the processed images will be saved . If None , no images are saved .
save_path ( Optional [ str ] ) : Destination path where the processed images will be saved . If None , no images are saved .
units ( List [ FaceSwapUnitSettings ] ) : List of FaceSwapUnitSettings to apply to the images .
units ( List [ FaceSwapUnitSettings ] ) : List of FaceSwapUnitSettings to apply to the images .
postprocess_options ( PostProcessingOptions ) : Post - processing settings to be applied to the images .
postprocess_options ( PostProcessingOptions ) : Post - processing settings to be applied to the images .
Returns :
Returns :
Optional [ List [ Image. Image] ] : List of processed images , or None in case of an exception .
Optional [ List [ PIL Image] ] : List of processed images , or None in case of an exception .
Raises :
Raises :
Any exceptions raised by the underlying process will be logged and the function will return None .
Any exceptions raised by the underlying process will be logged and the function will return None .
@ -149,7 +150,7 @@ def batch_process(
def extract_faces (
def extract_faces (
images : List [ Image. Image] ,
images : List [ PIL Image] ,
extract_path : Optional [ str ] ,
extract_path : Optional [ str ] ,
postprocess_options : PostProcessingOptions ,
postprocess_options : PostProcessingOptions ,
) - > Optional [ List [ str ] ] :
) - > Optional [ List [ str ] ] :
@ -206,7 +207,7 @@ def extract_faces(
return result_images
return result_images
except Exception as e :
except Exception as e :
logger . info ( " Failed to extract : %s " , e )
logger . error ( " Failed to extract : %s " , e )
import traceback
import traceback
traceback . print_exc ( )
traceback . print_exc ( )
@ -273,16 +274,15 @@ def getFaceSwapModel(model_path: str) -> upscaled_inswapper.UpscaledINSwapper:
def get_faces (
def get_faces (
img_data : np. ndarray , # type: ignore
img_data : CV2ImgU8,
det_size : Tuple [ int , int ] = ( 640 , 640 ) ,
det_size : Tuple [ int , int ] = ( 640 , 640 ) ,
det_thresh : Optional [ float ] = None ,
det_thresh : Optional [ float ] = None ,
sort_by_face_size : bool = False ,
) - > List [ Face ] :
) - > List [ Face ] :
"""
"""
Detects and retrieves faces from an image using an analysis model .
Detects and retrieves faces from an image using an analysis model .
Args :
Args :
img_data ( np. ndarray ) : The image data as a NumPy array .
img_data ( CV2ImgU8 ) : The image data as a NumPy array .
det_size ( tuple ) : The desired detection size ( width , height ) . Defaults to ( 640 , 640 ) .
det_size ( tuple ) : The desired detection size ( width , height ) . Defaults to ( 640 , 640 ) .
sort_by_face_size ( bool ) : Will sort the faces by their size from larger to smaller face
sort_by_face_size ( bool ) : Will sort the faces by their size from larger to smaller face
@ -309,26 +309,55 @@ def get_faces(
return get_faces ( img_data , det_size = det_size_half , det_thresh = det_thresh )
return get_faces ( img_data , det_size = det_size_half , det_thresh = det_thresh )
try :
try :
if sort_by_face_size :
return sorted (
face ,
reverse = True ,
key = lambda x : ( x . bbox [ 2 ] - x . bbox [ 0 ] ) * ( x . bbox [ 3 ] - x . bbox [ 1 ] ) ,
)
# Sort the detected faces based on their x-coordinate of the bounding box
# Sort the detected faces based on their x-coordinate of the bounding box
return sorted ( face , key = lambda x : x . bbox [ 0 ] )
return sorted ( face , key = lambda x : x . bbox [ 0 ] )
except Exception as e :
except Exception as e :
return [ ]
return [ ]
def filter_faces (
all_faces : List [ Face ] ,
faces_index : Set [ int ] ,
source_gender : int = None ,
sort_by_face_size : bool = False ,
) - > List [ Face ] :
"""
Sorts and filters a list of faces based on specified criteria .
This function takes a list of Face objects and can sort them by face size and filter them by gender .
Sorting by face size is performed if sort_by_face_size is set to True , and filtering by gender is
performed if source_gender is provided .
: param faces : A list of Face objects representing the faces to be sorted and filtered .
: param faces_index : A set of faces index
: param source_gender : An optional integer representing the gender by which to filter the faces .
If provided , only faces with the specified gender will be included in the result .
: param sort_by_face_size : A boolean indicating whether to sort the faces by size . If True , faces are
sorted in descending order by size , calculated as the area of the bounding box .
: return : A list of Face objects sorted and filtered according to the specified criteria .
"""
filtered_faces = copy . copy ( all_faces )
if sort_by_face_size :
filtered_faces = sorted (
all_faces ,
reverse = True ,
key = lambda x : ( x . bbox [ 2 ] - x . bbox [ 0 ] ) * ( x . bbox [ 3 ] - x . bbox [ 1 ] ) ,
)
if source_gender is not None :
filtered_faces = [
face for face in filtered_faces if face [ " gender " ] == source_gender
]
return [ face for i , face in enumerate ( filtered_faces ) if i in faces_index ]
@dataclass
@dataclass
class ImageResult :
class ImageResult :
"""
"""
Represents the result of an image swap operation
Represents the result of an image swap operation
"""
"""
image : Image . Image
image : PIL Image
"""
"""
The image object with the swapped face
The image object with the swapped face
"""
"""
@ -362,7 +391,7 @@ def get_or_default(l: List[Any], index: int, default: Any) -> Any:
return l [ index ] if index < len ( l ) else default
return l [ index ] if index < len ( l ) else default
def get_faces_from_img_files ( files : List [ gr . File ] ) - > List [ Optional [ np. ndarray ] ] : # type: ignore
def get_faces_from_img_files ( files : List [ gr . File ] ) - > List [ Optional [ CV2ImgU8] ] :
"""
"""
Extracts faces from a list of image files .
Extracts faces from a list of image files .
@ -388,7 +417,7 @@ def get_faces_from_img_files(files: List[gr.File]) -> List[Optional[np.ndarray]]
return faces
return faces
def blend_faces ( faces : List [ Face ] ) - > Face:
def blend_faces ( faces : List [ Face ] ) - > Optional[ Face] :
"""
"""
Blends the embeddings of multiple faces into a single face .
Blends the embeddings of multiple faces into a single face .
@ -418,16 +447,10 @@ def blend_faces(faces: List[Face]) -> Face:
# Create a new Face object using the properties of the first face in the list
# Create a new Face object using the properties of the first face in the list
# Assign the blended embedding to the blended Face object
# Assign the blended embedding to the blended Face object
blended = Face(
blended = IS Face(
embedding = blended_embedding , gender = faces [ 0 ] . gender , age = faces [ 0 ] . age
embedding = blended_embedding , gender = faces [ 0 ] . gender , age = faces [ 0 ] . age
)
)
assert (
not np . array_equal ( blended . embedding , faces [ 0 ] . embedding )
if len ( faces ) > 1
else True
) , " If len(faces)>0, the blended embedding should not be the same than the first image "
return blended
return blended
# Return None if the input list is empty
# Return None if the input list is empty
@ -435,85 +458,80 @@ def blend_faces(faces: List[Face]) -> Face:
def swap_face (
def swap_face (
reference_face : np . ndarray , # type: ignore
reference_face : CV2ImgU8 ,
source_face : np . ndarray , # type: ignore
source_face : Face ,
target_img : Image . Image ,
target_img : PILImage ,
target_faces : List [ Face ] ,
model : str ,
model : str ,
faces_index : Set [ int ] = { 0 } ,
same_gender : bool = True ,
upscaled_swapper : bool = False ,
upscaled_swapper : bool = False ,
compute_similarity : bool = True ,
compute_similarity : bool = True ,
sort_by_face_size : bool = False ,
) - > ImageResult :
) - > ImageResult :
"""
"""
Swaps faces in the target image with the source face .
Swaps faces in the target image with the source face .
Args :
Args :
reference_face ( np. ndarray ) : The reference face used for similarity comparison .
reference_face ( CV2ImgU8 ) : The reference face used for similarity comparison .
source_face ( np. ndarray ) : The source face to be swapped .
source_face ( CV2ImgU8 ) : The source face to be swapped .
target_img ( Image. Image) : The target image to swap faces in .
target_img ( PIL Image) : The target image to swap faces in .
model ( str ) : Path to the face swap model .
model ( str ) : Path to the face swap model .
faces_index ( Set [ int ] , optional ) : Set of indices specifying which faces to swap . Defaults to { 0 } .
same_gender ( bool , optional ) : If True , only swap faces with the same gender as the source face . Defaults to True .
Returns :
Returns :
ImageResult : An object containing the swapped image and similarity scores .
ImageResult : An object containing the swapped image and similarity scores .
"""
"""
return_result = ImageResult ( target_img , { } , { } )
return_result = ImageResult ( target_img , { } , { } )
target_img_cv2 : CV2ImgU8 = cv2 . cvtColor ( np . array ( target_img ) , cv2 . COLOR_RGB2BGR )
try :
try :
target_img = cv2 . cvtColor ( np . array ( target_img ) , cv2 . COLOR_RGB2BGR )
gender = source_face [ " gender " ]
gender = source_face [ " gender " ]
logger . info ( " Source Gender %s " , gender )
logger . info ( " Source Gender %s " , gender )
if source_face is not None :
if source_face is not None :
result = target_img
result = target_img _cv2
model_path = os . path . join ( os . path . abspath ( os . path . dirname ( __file__ ) ) , model )
model_path = os . path . join ( os . path . abspath ( os . path . dirname ( __file__ ) ) , model )
face_swapper = getFaceSwapModel ( model_path )
face_swapper = getFaceSwapModel ( model_path )
target_faces = get_faces ( target_img , sort_by_face_size = sort_by_face_size )
logger . info ( " Target faces count : %s " , len ( target_faces ) )
logger . info ( " Target faces count : %s " , len ( target_faces ) )
if same_gender :
target_faces = [ x for x in target_faces if x [ " gender " ] == gender ]
logger . info ( " Target Gender Matches count %s " , len ( target_faces ) )
for i , swapped_face in enumerate ( target_faces ) :
for i , swapped_face in enumerate ( target_faces ) :
logger . info ( f " swap face { i } " )
logger . info ( f " swap face { i } " )
if i in faces_index :
# type : ignore
result = face_swapper . get (
result = face_swapper . get (
img = result ,
result , swapped_face , source_face , upscale = upscaled_swapper
target_face = swapped_face ,
)
source_face = source_face ,
upscale = upscaled_swapper ,
) # type: ignore
result_image = Image . fromarray ( cv2 . cvtColor ( result , cv2 . COLOR_BGR2RGB ) )
result_image = Image . fromarray ( cv2 . cvtColor ( result , cv2 . COLOR_BGR2RGB ) )
return_result . image = result_image
return_result . image = result_image
if compute_similarity :
# FIXME : recompute similarity
try :
result_faces = get_faces (
# if compute_similarity:
cv2 . cvtColor ( np . array ( result_image ) , cv2 . COLOR_RGB2BGR ) ,
# try:
sort_by_face_size = sort_by_face_size ,
# result_faces = get_faces(
)
# cv2.cvtColor(np.array(result_image), cv2.COLOR_RGB2BGR),
if same_gender :
# sort_by_face_size=sort_by_face_size,
result_faces = [
# )
x for x in result_faces if x [ " gender " ] == gender
# if same_gender:
]
# result_faces = [
# x for x in result_faces if x["gender"] == gender
for i , swapped_face in enumerate ( result_faces ) :
# ]
logger . info ( f " compare face { i } " )
if i in faces_index and i < len ( target_faces ) :
# for i, swapped_face in enumerate(result_faces):
return_result . similarity [ i ] = cosine_similarity_face (
# logger.info(f"compare face {i}")
source_face , swapped_face
# if i in faces_index and i < len(target_faces):
)
# return_result.similarity[i] = cosine_similarity_face(
return_result . ref_similarity [ i ] = cosine_similarity_face (
# source_face, swapped_face
reference_face , swapped_face
# )
)
# return_result.ref_similarity[i] = cosine_similarity_face(
# reference_face, swapped_face
logger . info ( f " similarity { return_result . similarity } " )
# )
logger . info ( f " ref similarity { return_result . ref_similarity } " )
# logger.info(f"similarity {return_result.similarity}")
except Exception as e :
# logger.info(f"ref similarity {return_result.ref_similarity}")
logger . error ( " Similarity processing failed %s " , e )
raise e
# except Exception as e:
# logger.error("Similarity processing failed %s", e)
# raise e
except Exception as e :
except Exception as e :
logger . error ( " Conversion failed %s " , e )
logger . error ( " Conversion failed %s " , e )
raise e
raise e
@ -523,11 +541,11 @@ def swap_face(
def process_image_unit (
def process_image_unit (
model : str ,
model : str ,
unit : FaceSwapUnitSettings ,
unit : FaceSwapUnitSettings ,
image : Image. Image,
image : PIL Image,
info : str = None ,
info : str = None ,
upscaled_swapper : bool = False ,
upscaled_swapper : bool = False ,
force_blend : bool = False ,
force_blend : bool = False ,
) - > List [ Tuple [ Image. Image, str ] ] :
) - > List [ Tuple [ PIL Image, 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).
Args :
Args :
@ -541,6 +559,8 @@ def process_image_unit(
results = [ ]
results = [ ]
if unit . enable :
if unit . enable :
faces = get_faces ( pil_to_cv2 ( image ) )
if check_against_nsfw ( image ) :
if check_against_nsfw ( image ) :
return [ ( image , info ) ]
return [ ( image , info ) ]
if not unit . blend_faces and not force_blend :
if not unit . blend_faces and not force_blend :
@ -549,15 +569,10 @@ def process_image_unit(
else :
else :
logger . info ( " blend all faces together " )
logger . info ( " blend all faces together " )
src_faces = [ unit . blended_faces ]
src_faces = [ unit . blended_faces ]
assert (
not np . array_equal (
unit . reference_face . embedding , src_faces [ 0 ] . embedding
)
if len ( unit . faces ) > 1
else True
) , " Reference face cannot be the same as blended "
for i , src_face in enumerate ( src_faces ) :
for i , src_face in enumerate ( src_faces ) :
current_image = image
logger . info ( f " Process face { i } " )
logger . info ( f " Process face { i } " )
if unit . reference_face is not None :
if unit . reference_face is not None :
reference_face = unit . reference_face
reference_face = unit . reference_face
@ -565,18 +580,35 @@ def process_image_unit(
logger . info ( " Use source face as reference face " )
logger . info ( " Use source face as reference face " )
reference_face = src_face
reference_face = src_face
target_faces = filter_faces (
faces ,
faces_index = unit . faces_index ,
source_gender = src_face [ " gender " ] if unit . same_gender else None ,
sort_by_face_size = unit . sort_by_size ,
)
# Apply pre-inpainting to image
if unit . pre_inpainting . inpainting_denoising_strengh > 0 :
current_image = img2img_diffusion (
img = current_image , faces = target_faces , options = unit . pre_inpainting
)
save_img_debug ( image , " Before swap " )
save_img_debug ( image , " Before swap " )
result : ImageResult = swap_face (
result : ImageResult = swap_face (
reference_face ,
reference_face = reference_face ,
src_face ,
s ource_face= s rc_face,
image ,
target_img= current_ image,
faces_index = unit . faces_index ,
target_faces= target_faces ,
model = model ,
model = model ,
same_gender = unit . same_gender ,
upscaled_swapper = upscaled_swapper ,
upscaled_swapper = upscaled_swapper ,
compute_similarity = unit . compute_similarity ,
compute_similarity = unit . compute_similarity ,
sort_by_face_size = unit . sort_by_size ,
)
)
# Apply post-inpainting to image
if unit . post_inpainting . inpainting_denoising_strengh > 0 :
result . image = img2img_diffusion (
img = result . image , faces = target_faces , options = unit . post_inpainting
)
save_img_debug ( result . image , " After swap " )
save_img_debug ( result . image , " After swap " )
if result . image is None :
if result . image is None :
@ -610,17 +642,17 @@ def process_image_unit(
def process_images_units (
def process_images_units (
model : str ,
model : str ,
units : List [ FaceSwapUnitSettings ] ,
units : List [ FaceSwapUnitSettings ] ,
images : List [ Tuple [ Optional [ Image. Image] , Optional [ str ] ] ] ,
images : List [ Tuple [ Optional [ PIL Image] , Optional [ str ] ] ] ,
upscaled_swapper : bool = False ,
upscaled_swapper : bool = False ,
force_blend : bool = False ,
force_blend : bool = False ,
) - > Optional [ List [ Tuple [ Image. Image, str ] ] ] :
) - > Optional [ List [ Tuple [ PIL Image, str ] ] ] :
"""
"""
Process a list of images using a specified model and unit settings for face swapping .
Process a list of images using a specified model and unit settings for face swapping .
Args :
Args :
model ( str ) : The name of the model to use for processing .
model ( str ) : The name of the model to use for processing .
units ( List [ FaceSwapUnitSettings ] ) : A list of settings for face swap units to apply on each image .
units ( List [ FaceSwapUnitSettings ] ) : A list of settings for face swap units to apply on each image .
images ( List [ Tuple [ Optional [ Image. Image] , Optional [ str ] ] ] ) : A list of tuples , each containing
images ( List [ Tuple [ Optional [ PIL Image] , Optional [ str ] ] ] ) : A list of tuples , each containing
an image and its associated info string . If an image or info string is not available ,
an image and its associated info string . If an image or info string is not available ,
its value can be None .
its value can be None .
upscaled_swapper ( bool , optional ) : If True , uses an upscaled version of the face swapper .
upscaled_swapper ( bool , optional ) : If True , uses an upscaled version of the face swapper .
@ -629,7 +661,7 @@ def process_images_units(
image . Defaults to False .
image . Defaults to False .
Returns :
Returns :
Optional [ List [ Tuple [ Image. Image, str ] ] ] : A list of tuples , each containing a processed image
Optional [ List [ Tuple [ PIL Image, str ] ] ] : A list of tuples , each containing a processed image
and its associated info string . If no units are provided for processing , returns None .
and its associated info string . If no units are provided for processing , returns None .
"""
"""