Added Multi Face
Added ability to upload multiple source face files and have it apply them all to each diffused image
This commit is contained in:
parent
de84e022ee
commit
0f7f73d47f
@ -1,5 +1,6 @@
|
||||
import os, glob
|
||||
import gradio as gr
|
||||
import tempfile
|
||||
from PIL import Image
|
||||
try:
|
||||
import torch.cuda as cuda
|
||||
@ -8,7 +9,7 @@ except:
|
||||
EP_is_visible = False
|
||||
|
||||
from typing import List
|
||||
|
||||
from PIL import Image
|
||||
import modules.scripts as scripts
|
||||
from modules.upscaler import Upscaler, UpscalerData
|
||||
from modules import scripts, shared, images, scripts_postprocessing
|
||||
@ -65,10 +66,11 @@ class FaceSwapScript(scripts.Script):
|
||||
with gr.Tab("Main"):
|
||||
with gr.Column():
|
||||
img = gr.Image(type="pil")
|
||||
face_files = gr.File(label="Multiple Source Face Files",file_count="multiple",file_types=["image"],info="Upload multiple face files and each file will be processed in post processing")
|
||||
enable = gr.Checkbox(False, label="Enable", info=f"The Fast and Simple FaceSwap Extension - {version_flag}")
|
||||
save_original = gr.Checkbox(False, label="Save Original", info="Save the original image(s) made before swapping; If you use \"img2img\" - this option will affect with \"Swap in generated\" only")
|
||||
mask_face = gr.Checkbox(False, label="Mask Faces", info="Attempt to mask only the faces and eliminate pixelation of the image around the contours. Additional settings in the Masking tab.")
|
||||
|
||||
|
||||
gr.Markdown("<br>")
|
||||
gr.Markdown("Source Image (above):")
|
||||
with gr.Row():
|
||||
@ -143,13 +145,16 @@ class FaceSwapScript(scripts.Script):
|
||||
)
|
||||
with gr.Tab("Masking"):
|
||||
save_face_mask = gr.Checkbox(False, label="Save Face Mask", info="Save the face mask as a separate image with alpha transparency.")
|
||||
use_minimal_area = gr.Checkbox(MaskOption.DEFAULT_USE_MINIMAL_AREA, label="Use Minimal Area", info="Use the least amount of area for the mask as possible. This is good for multiple faces that are close together or for preserving the most of the surrounding image.")
|
||||
|
||||
mask_areas = gr.CheckboxGroup(
|
||||
label="Mask areas", choices=["Face", "Hair", "Hat", "Neck"], type="value", value= MaskOption.DEFAULT_FACE_AREAS
|
||||
)
|
||||
mask_blur = gr.Slider(label="Mask blur ", minimum=0, maximum=64, step=1, value=MaskOption.DEFAULT_MASK_BLUR,info="The number of pixels from the outer edge of the mask to blur.")
|
||||
face_size = gr.Radio(
|
||||
label = "Face Size", choices = [512,256,128],value=MaskOption.DEFAULT_FACE_SIZE,type="value", info="Size of the masked area. Use larger numbers if the face is expected to be large, smaller if small. Default is 512."
|
||||
)
|
||||
mask_blur = gr.Slider(label="Mask blur", minimum=0, maximum=64, step=1, value=12,info="The number of pixels from the outer edge of the mask to blur.")
|
||||
|
||||
mask_vignette_fallback_threshold = gr.Slider(
|
||||
minimum=0.1,
|
||||
maximum=1.0,
|
||||
@ -158,7 +163,6 @@ class FaceSwapScript(scripts.Script):
|
||||
label="Vignette fallback threshold",
|
||||
info="Switch to a rectangular vignette mask when masked area is only this specified percentage of Face Size."
|
||||
)
|
||||
use_minimal_area = gr.Checkbox(MaskOption.DEFAULT_USE_MINIMAL_AREA, label="Use Minimal Area", info="Use the least amount of area for the mask as possible. This is good for multiple faces that are close together or for preserving the most of the surrounding image.")
|
||||
with gr.Tab("Settings"):
|
||||
models = get_models()
|
||||
with gr.Row(visible=EP_is_visible):
|
||||
@ -239,7 +243,7 @@ class FaceSwapScript(scripts.Script):
|
||||
use_minimal_area,
|
||||
face_size,
|
||||
mask_vignette_fallback_threshold,
|
||||
|
||||
face_files
|
||||
]
|
||||
|
||||
|
||||
@ -304,22 +308,23 @@ class FaceSwapScript(scripts.Script):
|
||||
target_hash_check,
|
||||
device,
|
||||
mask_face,
|
||||
save_face_mask,
|
||||
save_face_mask:bool,
|
||||
mask_areas,
|
||||
mask_blur,
|
||||
mask_blur:int,
|
||||
mask_use_minimal_area,
|
||||
mask_face_size,
|
||||
mask_vignette_fallback_threshold,
|
||||
|
||||
face_files
|
||||
):
|
||||
self.enable = enable
|
||||
if self.enable:
|
||||
|
||||
|
||||
reset_messaged()
|
||||
if check_process_halt():
|
||||
return
|
||||
|
||||
global MODELS_PATH
|
||||
|
||||
self.source = img
|
||||
self.face_restorer_name = face_restorer_name
|
||||
self.upscaler_scale = upscaler_scale
|
||||
@ -345,6 +350,7 @@ class FaceSwapScript(scripts.Script):
|
||||
self.mask_face_size = mask_face_size
|
||||
self.mask_vignette_fallback_threshold = mask_vignette_fallback_threshold
|
||||
self.mask_use_minimal_area = mask_use_minimal_area
|
||||
self.face_files = face_files
|
||||
if self.gender_source is None or self.gender_source == "No":
|
||||
self.gender_source = 0
|
||||
if self.gender_target is None or self.gender_target == "No":
|
||||
@ -367,9 +373,9 @@ class FaceSwapScript(scripts.Script):
|
||||
self.target_hash_check = False
|
||||
|
||||
set_Device(self.device)
|
||||
logger.status(f"Self: {self}")
|
||||
apply_logging_patch(console_logging_level)
|
||||
if self.source is not None:
|
||||
apply_logging_patch(console_logging_level)
|
||||
|
||||
|
||||
if isinstance(p, StableDiffusionProcessingImg2Img) and self.swap_in_source:
|
||||
logger.status("Working: source face index %s, target face index %s", self.source_faces_index, self.faces_index)
|
||||
@ -401,7 +407,7 @@ class FaceSwapScript(scripts.Script):
|
||||
if shared.state.interrupted or shared.state.skipped:
|
||||
return
|
||||
|
||||
else:
|
||||
elif self.face_files is None or len(self.face_files) == 0:
|
||||
logger.error("Please provide a source face")
|
||||
|
||||
def postprocess(self, p: StableDiffusionProcessing, processed: Processed, *args):
|
||||
@ -410,14 +416,15 @@ class FaceSwapScript(scripts.Script):
|
||||
reset_messaged()
|
||||
if check_process_halt():
|
||||
return
|
||||
postprocess_run: bool = True
|
||||
|
||||
orig_images : List[Image.Image] = processed.images[processed.index_of_first_image:]
|
||||
orig_infotexts : List[str] = processed.infotexts[processed.index_of_first_image:]
|
||||
result_images:List[Image.Image] = []
|
||||
|
||||
|
||||
if self.save_original:
|
||||
|
||||
postprocess_run: bool = True
|
||||
|
||||
orig_images : List[Image.Image] = processed.images[processed.index_of_first_image:]
|
||||
orig_infotexts : List[str] = processed.infotexts[processed.index_of_first_image:]
|
||||
|
||||
result_images: List = processed.images
|
||||
# result_info: List = processed.infotexts
|
||||
|
||||
@ -469,7 +476,48 @@ class FaceSwapScript(scripts.Script):
|
||||
# fullfn = split_fullfn[0] + ".txt"
|
||||
# with open(fullfn, 'w', encoding="utf8") as f:
|
||||
# f.writelines(output)
|
||||
|
||||
if self.face_files is not None and len(self.face_files) > 0:
|
||||
|
||||
for i,(img,info) in enumerate(zip(orig_images, orig_infotexts)):
|
||||
for j,f_img in enumerate(self.face_files):
|
||||
|
||||
if check_process_halt():
|
||||
postprocess_run = False
|
||||
break
|
||||
if len(self.face_files) > 1:
|
||||
logger.status("Swap in face file #%s", j+1)
|
||||
result, output, swapped, masked_faces = swap_face(
|
||||
Image.open(os.path.abspath(f_img.name)),
|
||||
img,
|
||||
source_faces_index=self.source_faces_index,
|
||||
faces_index=self.faces_index,
|
||||
model=self.model,
|
||||
enhancement_options=self.enhancement_options,
|
||||
gender_source=self.gender_source,
|
||||
gender_target=self.gender_target,
|
||||
source_hash_check=self.source_hash_check,
|
||||
target_hash_check=self.target_hash_check,
|
||||
device=self.device,
|
||||
mask_face=self.mask_face,
|
||||
mask_options=self.mask_options
|
||||
)
|
||||
if result is not None and swapped > 0:
|
||||
result_images.append(result)
|
||||
suffix = f"-swapped-ff-{j+1}"
|
||||
try:
|
||||
img_path = save_image(result, p.outpath_samples, "", p.all_seeds[0], p.all_prompts[0], "png",info=info, p=p, suffix=suffix)
|
||||
except:
|
||||
logger.error("Cannot save a result image - please, check SD WebUI Settings (Saving and Paths)")
|
||||
if self.mask_face and self.save_face_mask and masked_faces is not None:
|
||||
result_images.append(masked_faces)
|
||||
suffix = f"-mask-ff-{j+1}"
|
||||
try:
|
||||
img_path = save_image(masked_faces, p.outpath_samples, "", p.all_seeds[0], p.all_prompts[0], "png",info=info, p=p, suffix=suffix)
|
||||
except:
|
||||
logger.error("Cannot save a Masked Face image - please, check SD WebUI Settings (Saving and Paths)")
|
||||
|
||||
elif result is None:
|
||||
logger.error("Cannot create a result image")
|
||||
if shared.opts.return_grid and len(result_images) > 2 and postprocess_run:
|
||||
grid = make_grid(result_images)
|
||||
result_images.insert(0, grid)
|
||||
@ -480,13 +528,62 @@ class FaceSwapScript(scripts.Script):
|
||||
|
||||
processed.images = result_images
|
||||
# processed.infotexts = result_info
|
||||
|
||||
elif self.face_files is not None and len(self.face_files) > 0:
|
||||
for i,(img,info) in enumerate(zip(orig_images, orig_infotexts)):
|
||||
for j,f_img in enumerate(self.face_files):
|
||||
|
||||
if check_process_halt():
|
||||
postprocess_run = False
|
||||
break
|
||||
if len(self.face_files) > 1:
|
||||
logger.status("Swap in face file #%s", j+1)
|
||||
result, output, swapped, masked_faces = swap_face(
|
||||
Image.open(os.path.abspath(f_img.name)),
|
||||
img,
|
||||
source_faces_index=self.source_faces_index,
|
||||
faces_index=self.faces_index,
|
||||
model=self.model,
|
||||
enhancement_options=self.enhancement_options,
|
||||
gender_source=self.gender_source,
|
||||
gender_target=self.gender_target,
|
||||
source_hash_check=self.source_hash_check,
|
||||
target_hash_check=self.target_hash_check,
|
||||
device=self.device,
|
||||
mask_face=self.mask_face,
|
||||
mask_options=self.mask_options
|
||||
)
|
||||
if result is not None and swapped > 0:
|
||||
result_images.append(result)
|
||||
suffix = f"-swapped-ff-{j+1}"
|
||||
try:
|
||||
img_path = save_image(result, p.outpath_samples, "", p.all_seeds[0], p.all_prompts[0], "png",info=info, p=p, suffix=suffix)
|
||||
except:
|
||||
logger.error("Cannot save a result image - please, check SD WebUI Settings (Saving and Paths)")
|
||||
if self.mask_face and self.save_face_mask and masked_faces is not None:
|
||||
result_images.append(masked_faces)
|
||||
suffix = f"-mask-ff-{j+1}"
|
||||
try:
|
||||
img_path = save_image(masked_faces, p.outpath_samples, "", p.all_seeds[0], p.all_prompts[0], "png",info=info, p=p, suffix=suffix)
|
||||
except:
|
||||
logger.error("Cannot save a Masked Face image - please, check SD WebUI Settings (Saving and Paths)")
|
||||
|
||||
elif result is None:
|
||||
logger.error("Cannot create a result image")
|
||||
if shared.opts.return_grid and len(result_images) > 2 and postprocess_run:
|
||||
grid = make_grid(result_images)
|
||||
result_images.insert(0, grid)
|
||||
try:
|
||||
save_image(grid, p.outpath_grids, "grid", p.all_seeds[0], p.all_prompts[0], shared.opts.grid_format, info=info, short_filename=not shared.opts.grid_extended_filename, p=p, grid=True)
|
||||
except:
|
||||
logger.error("Cannot save a grid - please, check SD WebUI Settings (Saving and Paths)")
|
||||
|
||||
processed.images = result_images
|
||||
def postprocess_batch(self, p, *args, **kwargs):
|
||||
if self.enable and not self.save_original:
|
||||
images = kwargs["images"]
|
||||
|
||||
def postprocess_image(self, p, script_pp: scripts.PostprocessImageArgs, *args):
|
||||
if self.enable and self.swap_in_generated and not self.save_original:
|
||||
if self.enable and self.swap_in_generated and not ( self.save_original or ( self.face_files is not None and len(self.face_files) > 0)):
|
||||
|
||||
current_job_number = shared.state.job_no + 1
|
||||
job_count = shared.state.job_count
|
||||
@ -498,7 +595,7 @@ class FaceSwapScript(scripts.Script):
|
||||
if self.source is not None:
|
||||
logger.status("Working: source face index %s, target face index %s", self.source_faces_index, self.faces_index)
|
||||
image: Image.Image = script_pp.image
|
||||
result, output, swapped = swap_face(
|
||||
result, output, swapped, masked_faces = swap_face(
|
||||
self.source,
|
||||
image,
|
||||
source_faces_index=self.source_faces_index,
|
||||
@ -510,7 +607,8 @@ class FaceSwapScript(scripts.Script):
|
||||
source_hash_check=self.source_hash_check,
|
||||
target_hash_check=self.target_hash_check,
|
||||
device=self.device,
|
||||
mask_face=self.mask_face
|
||||
mask_face=self.mask_face,
|
||||
mask_options=self.mask_options
|
||||
)
|
||||
try:
|
||||
pp = scripts_postprocessing.PostprocessedImage(result)
|
||||
@ -525,7 +623,7 @@ class FaceSwapScript(scripts.Script):
|
||||
# f.writelines(output)
|
||||
except:
|
||||
logger.error("Cannot create a result image")
|
||||
|
||||
|
||||
|
||||
class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
|
||||
name = 'ReActor'
|
||||
@ -536,9 +634,9 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
|
||||
with gr.Tab("Main"):
|
||||
with gr.Column():
|
||||
img = gr.Image(type="pil")
|
||||
face_files = gr.File(file_count="multiple",file_types=["image"],info="Upload multiple face files and each file will be processed in post processing")
|
||||
enable = gr.Checkbox(False, label="Enable", info=f"The Fast and Simple FaceSwap Extension - {version_flag}")
|
||||
mask_face = gr.Checkbox(False, label="Mask Faces", info="Attempt to mask only the faces and eliminate pixelation of the image around the contours. Additional settings in the Masking tab.")
|
||||
|
||||
gr.Markdown("Source Image (above):")
|
||||
with gr.Row():
|
||||
source_faces_index = gr.Textbox(
|
||||
@ -599,13 +697,16 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
|
||||
)
|
||||
with gr.Tab("Masking"):
|
||||
save_face_mask = gr.Checkbox(False, label="Save Face Mask", info="Save the face mask as a separate image with alpha transparency.")
|
||||
use_minimal_area = gr.Checkbox(MaskOption.DEFAULT_USE_MINIMAL_AREA, label="Use Minimal Area", info="Use the least amount of area for the mask as possible. This is good for multiple faces that are close together or for preserving the most of the surrounding image.")
|
||||
|
||||
mask_areas = gr.CheckboxGroup(
|
||||
label="Mask areas", choices=["Face", "Hair", "Hat", "Neck"], type="value", value= MaskOption.DEFAULT_FACE_AREAS
|
||||
)
|
||||
mask_blur = gr.Slider(label="Mask blur ", minimum=0, maximum=64, step=1, value=MaskOption.DEFAULT_MASK_BLUR,info="The number of pixels from the outer edge of the mask to blur.")
|
||||
face_size = gr.Radio(
|
||||
label = "Face Size", choices = [512,256,128],value=MaskOption.DEFAULT_FACE_SIZE,type="value", info="Size of the masked area. Use larger numbers if the face is expected to be large, smaller if small. Default is 512."
|
||||
)
|
||||
mask_blur = gr.Slider(label="Mask blur", minimum=0, maximum=64, step=1, value=12,info="The number of pixels from the outer edge of the mask to blur.")
|
||||
|
||||
mask_vignette_fallback_threshold = gr.Slider(
|
||||
minimum=0.1,
|
||||
maximum=1.0,
|
||||
@ -614,7 +715,6 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
|
||||
label="Vignette fallback threshold",
|
||||
info="Switch to a rectangular vignette mask when masked area is only this specified percentage of Face Size."
|
||||
)
|
||||
use_minimal_area = gr.Checkbox(MaskOption.DEFAULT_USE_MINIMAL_AREA, label="Use Minimal Area", info="Use the least amount of area for the mask as possible. This is good for multiple faces that are close together or for preserving the most of the surrounding image.")
|
||||
|
||||
with gr.Tab("Settings"):
|
||||
models = get_models()
|
||||
@ -654,7 +754,7 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
|
||||
label="Console Log Level",
|
||||
type="index",
|
||||
)
|
||||
|
||||
|
||||
args = {
|
||||
'img': img,
|
||||
'enable': enable,
|
||||
@ -679,6 +779,7 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
|
||||
'mask_vignette_fallback_threshold':mask_vignette_fallback_threshold,
|
||||
'face_size':face_size,
|
||||
'use_minimal_area':use_minimal_area,
|
||||
'face_files':face_files
|
||||
}
|
||||
return args
|
||||
|
||||
@ -713,9 +814,9 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
|
||||
mask_areas = self.mask_areas,
|
||||
save_face_mask = self.save_face_mask,
|
||||
mask_blur = self.mask_blur,
|
||||
face_size = self.mask_face_size,
|
||||
face_size = self.face_size,
|
||||
vignette_fallback_threshold = self.mask_vignette_fallback_threshold,
|
||||
use_minimal_area = self.mask_use_minimal_area,
|
||||
use_minimal_area = self.use_minimal_area,
|
||||
)
|
||||
def process(self, pp: scripts_postprocessing.PostprocessedImage, **args):
|
||||
if args['enable']:
|
||||
@ -723,6 +824,7 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
|
||||
if check_process_halt():
|
||||
return
|
||||
global MODELS_PATH
|
||||
|
||||
self.source = args['img']
|
||||
self.face_restorer_name = args['face_restorer_name']
|
||||
self.upscaler_scale = args['upscaler_scale']
|
||||
@ -743,6 +845,7 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
|
||||
self.mask_vignette_fallback_threshold= args['mask_vignette_fallback_threshold']
|
||||
self.face_size= args['face_size']
|
||||
self.use_minimal_area= args['use_minimal_area']
|
||||
self.face_files = args['face_files']
|
||||
if self.gender_source is None or self.gender_source == "No":
|
||||
self.gender_source = 0
|
||||
if self.gender_target is None or self.gender_target == "No":
|
||||
@ -764,12 +867,12 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
|
||||
reset_messaged()
|
||||
|
||||
set_Device(self.device)
|
||||
|
||||
apply_logging_patch(self.console_logging_level)
|
||||
if self.source is not None:
|
||||
apply_logging_patch(self.console_logging_level)
|
||||
|
||||
logger.status("Working: source face index %s, target face index %s", self.source_faces_index, self.faces_index)
|
||||
image: Image.Image = pp.image
|
||||
result, output, swapped = swap_face(
|
||||
result, output, swapped,masked_faces = swap_face(
|
||||
self.source,
|
||||
image,
|
||||
source_faces_index=self.source_faces_index,
|
||||
|
||||
@ -43,10 +43,10 @@ else:
|
||||
PROVIDERS = ["CPUExecutionProvider"]
|
||||
class MaskOption:
|
||||
DEFAULT_FACE_AREAS = ["Face"]
|
||||
DEFAULT_FACE_SIZE = 512
|
||||
DEFAULT_VIGNETTE_THRESHOLD = 0.1
|
||||
DEFAULT_MASK_BLUR = 12,
|
||||
DEFAULT_USE_MINIMAL_AREA = True
|
||||
DEFAULT_FACE_SIZE:int = 512
|
||||
DEFAULT_VIGNETTE_THRESHOLD:float = 0.1
|
||||
DEFAULT_MASK_BLUR:int = 12,
|
||||
DEFAULT_USE_MINIMAL_AREA:bool = True
|
||||
|
||||
@dataclass
|
||||
class EnhancementOptions:
|
||||
@ -63,7 +63,7 @@ class EnhancementOptions:
|
||||
class MaskOptions:
|
||||
mask_areas:List[str]
|
||||
save_face_mask: bool = False
|
||||
mask_blur:int = 12
|
||||
mask_blur:int = 12
|
||||
face_size:int = 512
|
||||
vignette_fallback_threshold:float =0.10
|
||||
use_minimal_area:bool = True
|
||||
@ -336,7 +336,7 @@ def swap_face(
|
||||
):
|
||||
global SOURCE_FACES, SOURCE_IMAGE_HASH, TARGET_FACES, TARGET_IMAGE_HASH, PROVIDERS
|
||||
result_image = target_img
|
||||
|
||||
masked_faces = None
|
||||
PROVIDERS = ["CUDAExecutionProvider"] if device == "CUDA" else ["CPUExecutionProvider"]
|
||||
|
||||
if check_process_halt():
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user