UPDATE: Blended Faces, CU12, Detection Tab

+VersionUP (0.7.0 alpha1)
FR #143 #255 (partly) #280 (partly) #352
Issue #319
This commit is contained in:
Art Gourieff 2024-02-11 19:52:01 +07:00
parent 4a7a367ea1
commit 96d7a06291
12 changed files with 314 additions and 67 deletions

View File

@ -2,7 +2,7 @@
<img src="https://github.com/Gourieff/Assets/raw/main/sd-webui-reactor/ReActor_logo_NEW_EN.png?raw=true" alt="logo" width="180px"/>
![Version](https://img.shields.io/badge/version-0.6.1-brightgreen?style=for-the-badge&labelColor=darkgreen)
![Version](https://img.shields.io/badge/version-0.7.0_alpha1-lightgreen?style=for-the-badge&labelColor=darkgreen)
<a href="https://boosty.to/artgourieff" target="_blank">
<img src="https://lovemet.ru/www/boosty.jpg" width="108" alt="Support Me on Boosty"/>
@ -40,6 +40,15 @@
## What's new in the latest updates
### 0.7.0 <sub><sup>ALPHA1
- You can now blend faces to build blended face models ("Tools->Face Models->Blend") - due to popular demand
<img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-01.jpg?raw=true" alt="0.7.0-whatsnew-01" width="100%"/><img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-02.jpg?raw=true" alt="0.7.0-whatsnew-02" width="100%"/>
- CUDA 12 Support in the Installer script for 1.17.0 ORT-GPU library
- New tab "Detection" with "Threshold" and "Max Faces" parameters
### 0.6.1 <sub><sup>BETA3
- 'Force Upscale' option inside the 'Upscale' tab: ReActor will run the Upscaler even if there's no face is detected (FR https://github.com/Gourieff/sd-webui-reactor/issues/116)

View File

@ -2,7 +2,7 @@
<img src="https://github.com/Gourieff/Assets/raw/main/sd-webui-reactor/ReActor_logo_NEW_RU.png?raw=true" alt="logo" width="180px"/>
![Version](https://img.shields.io/badge/версия-0.6.1-brightgreen-green?style=for-the-badge&labelColor=darkgreen)
![Version](https://img.shields.io/badge/версия-0.7.0_alpha1-lightgreen?style=for-the-badge&labelColor=darkgreen)
<a href="https://boosty.to/artgourieff" target="_blank">
<img src="https://lovemet.ru/www/boosty.jpg" width="108" alt="Поддержать проект на Boosty"/>
@ -39,6 +39,15 @@
## Что нового в последних обновлениях
### 0.7.0 <sub><sup>ALPHA1
- По многочисленным просьбам появилась возможность строить смешанные модели лиц ("Tools->Face Models->Blend")
<img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-01.jpg?raw=true" alt="0.7.0-whatsnew-01" width="100%"/><img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-02.jpg?raw=true" alt="0.7.0-whatsnew-02" width="100%"/>
- Поддержка CUDA 12 в скрипте установщика для библиотеки ORT-GPU версии 1.17.0
- Новая вкладка "Detection" с параметрами "Threshold" и "Max Faces"
### 0.6.1 <sub><sup>BETA3
- Опция 'Force Upscale' внутри вкладки 'Upscale': апскейл выполнится, даже если не было обнаружено ни одного лица (FR https://github.com/Gourieff/sd-webui-reactor/issues/116)

View File

@ -50,6 +50,8 @@ args=[
None, #25 skip it for API
True, #26 Randomly select an image from the path
True, #27 Force Upscale even if no face found
0.6, #28 Face Detection Threshold
2, #29 Maximum number of faces to detect (0 is unlimited)
]
# The args for ReActor can be found by

View File

@ -97,11 +97,14 @@ with open(req_file) as file:
install_count = 0
ort = "onnxruntime-gpu"
import torch
cuda_version = None
try:
if torch.cuda.is_available():
cuda_version = torch.version.cuda
print(f"CUDA {cuda_version}")
if first_run or last_device is None:
last_device = "CUDA"
elif torch.backends.mps.is_available() or hasattr(torch,'dml'):
elif torch.backends.mps.is_available() or hasattr(torch,'dml') or hasattr(torch,'privateuseone'):
ort = "onnxruntime"
# to prevent errors when ORT-GPU is installed but we want ORT instead:
if first_run:
@ -114,7 +117,11 @@ with open(req_file) as file:
last_device = "CPU"
with open(os.path.join(BASE_PATH, "last_device.txt"), "w") as txt:
txt.write(last_device)
if not is_installed(ort,"1.16.1",False):
if cuda_version is not None and float(cuda_version)>=12: # CU12
if not is_installed(ort,"1.17.0",False):
install_count += 1
pip_install(ort,"--extra-index-url", "https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/")
elif not is_installed(ort,"1.16.1",False):
install_count += 1
pip_install(ort, "-U")
except Exception as e:

View File

@ -2,3 +2,4 @@ import reactor_ui.reactor_upscale_ui as ui_upscale
import reactor_ui.reactor_tools_ui as ui_tools
import reactor_ui.reactor_settings_ui as ui_settings
import reactor_ui.reactor_main_ui as ui_main
import reactor_ui.reactor_detection_ui as ui_detection

View File

@ -0,0 +1,54 @@
import gradio as gr
from scripts.reactor_swapper import (
clear_faces,
clear_faces_list,
clear_faces_target,
clear_faces_all
)
# TAB DETECTION
def show(show_br: bool = True):
with gr.Tab("Detection"):
with gr.Row():
det_thresh = gr.Slider(
minimum=0.1,
maximum=1.0,
value=0.5,
step=0.01,
label="Threshold",
info="The higher the value, the more sensitive the detection is to what is considered a face (0.5 by default)",
scale=2
)
det_maxnum = gr.Slider(
minimum=0,
maximum=20,
value=0,
step=1,
label="Max Faces",
info="Maximum number of faces to detect (0 is unlimited)",
scale=1
)
# gr.Markdown("<br>", visible=show_br)
gr.Markdown("Hashed images get processed with previously set detection parameters (the face is hashed with all available parameters to bypass the analyzer and speed up the process). Please clear the hash if you want to apply new detection settings.", visible=show_br)
with gr.Row():
imgs_hash_clear_single = gr.Button(
value="Clear Source Images Hash (Single)",
scale=1
)
imgs_hash_clear_multiple = gr.Button(
value="Clear Source Images Hash (Multiple)",
scale=1
)
imgs_hash_clear_target = gr.Button(
value="Clear Target Image Hash",
scale=1
)
imgs_hash_clear_all = gr.Button(
value="Clear All Hash"
)
progressbar_area = gr.Markdown("")
imgs_hash_clear_single.click(clear_faces,None,[progressbar_area])
imgs_hash_clear_multiple.click(clear_faces_list,None,[progressbar_area])
imgs_hash_clear_target.click(clear_faces_target,None,[progressbar_area])
imgs_hash_clear_all.click(clear_faces_all,None,[progressbar_area])
return det_thresh, det_maxnum

View File

@ -1,25 +1,61 @@
import gradio as gr
from scripts.reactor_swapper import build_face_model
from scripts.reactor_swapper import build_face_model, blend_faces
# TAB TOOLS
def show():
with gr.Tab("Tools"):
with gr.Tab("Face Models"):
gr.Markdown("Load an image containing one person, name it and click 'Build and Save'")
img_fm = gr.Image(
type="pil",
label="Load Image to build Face Model",
)
with gr.Row(equal_height=True):
fm_name = gr.Textbox(
value="",
placeholder="Please type any name (e.g. Elena)",
label="Face Model Name",
with gr.Tab("Single"):
gr.Markdown("Load an image containing one person, name it and click 'Build and Save'")
img_fm = gr.Image(
type="pil",
label="Load an Image to build -Face Model-",
)
with gr.Row(equal_height=True):
fm_name = gr.Textbox(
value="",
placeholder="Please type any name (e.g. Elena)",
label="Face Model Name",
)
save_fm_btn = gr.Button("Build and Save")
save_fm = gr.Markdown("You can find saved models in 'models/reactor/faces'")
save_fm_btn.click(
build_face_model,
inputs=[img_fm, fm_name],
outputs=[save_fm],
)
with gr.Tab("Blend"):
gr.Markdown("Load a set of images containing any person, name it and click 'Build and Save'")
with gr.Row():
imgs_fm = gr.Files(
label=f"Load Images to build -Blended Face Model-",
file_types=["image"]
)
with gr.Column():
compute_method = gr.Radio(
["Mean", "Median", "Mode"],
value="Mean",
label="Compute Method",
type="index",
info="Mean (recommended) - Average value (best result 👍); Median* - Mid-point value (may be funny 😅); Mode - Most common value (may be scary 😨); *Mean and Median will be simillar if you load two images"
)
shape_check = gr.Checkbox(
False,
label="Check -Embedding Shape- on Similarity",
info="(Experimental) Turn it ON if you want to skip the faces which are too much different from the first one in the list to prevent some probable 'shape mismatches'"
)
with gr.Row(equal_height=True):
fm_name = gr.Textbox(
value="",
placeholder="Please type any name (e.g. Elena)",
label="Face Model Name",
)
save_fm_btn = gr.Button("Build and Save")
save_fm = gr.Markdown("You can find saved models in 'models/reactor/faces'")
save_fm_btn.click(
blend_faces,
inputs=[imgs_fm, fm_name, compute_method, shape_check],
outputs=[save_fm],
)
save_fm_btn = gr.Button("Build and Save")
save_fm = gr.Markdown("You can find saved models in 'models/reactor/faces'")
save_fm_btn.click(
build_face_model,
inputs=[img_fm, fm_name],
outputs=[save_fm],
)

View File

@ -1,7 +1,7 @@
'''
Thanks SpenserCai for the original version of the roop api script
-----------------------------------
--- ReActor External API v1.0.4 ---
--- ReActor External API v1.0.5 ---
-----------------------------------
'''
import os, glob
@ -14,7 +14,7 @@ from modules.api import api
import gradio as gr
from scripts.reactor_swapper import EnhancementOptions, swap_face
from scripts.reactor_swapper import EnhancementOptions, swap_face, DetectionOptions
from scripts.reactor_logger import logger
@ -77,7 +77,9 @@ def reactor_api(_: gr.Blocks, app: FastAPI):
face_model: str = Body("None",title="Filename of the face model (from 'models/reactor/faces'), e.g. elena.safetensors"),
source_folder: str = Body("",title="The path to the folder containing source faces images"),
random_image: int = Body(0,title="Randomly select an image from the path"),
upscale_force: int = Body(0,title="Force Upscale even if no face found")
upscale_force: int = Body(0,title="Force Upscale even if no face found"),
det_thresh: float = Body(0.5,title="Face Detection Threshold"),
det_maxnum: int = Body(0,title="Maximum number of faces to detect (0 is unlimited)"),
):
s_image = api.decode_base64_to_image(source_image) if select_source == 0 else None
t_image = api.decode_base64_to_image(target_image)
@ -90,10 +92,11 @@ def reactor_api(_: gr.Blocks, app: FastAPI):
random_image = False if random_image == 0 else True
upscale_force = False if upscale_force == 0 else True
up_options = EnhancementOptions(do_restore_first=restore_first_bool, scale=scale, upscaler=get_upscaler(upscaler), upscale_visibility=upscale_visibility,face_restorer=get_face_restorer(face_restorer),restorer_visibility=restorer_visibility,codeformer_weight=codeformer_weight,upscale_force=upscale_force)
det_options = DetectionOptions(det_thresh=det_thresh, det_maxnum=det_maxnum)
use_model = get_full_model(model)
if use_model is None:
Exception("Model not found")
result = swap_face(s_image, t_image, use_model, sf_index, f_index, up_options, gender_s, gender_t, True, True, device, mask_face, select_source, face_model, source_folder, None, random_image)
result = swap_face(s_image, t_image, use_model, sf_index, f_index, up_options, gender_s, gender_t, True, True, device, mask_face, select_source, face_model, source_folder, None, random_image,det_options)
if save_to_file == 1:
if result_file_path == "":
result_file_path = default_file_path()

View File

@ -15,20 +15,27 @@ from modules.processing import (
from modules.face_restoration import FaceRestoration
from modules.images import save_image
from reactor_ui import ui_main, ui_upscale, ui_tools, ui_settings
from reactor_ui import (
ui_main,
ui_upscale,
ui_tools,
ui_settings,
ui_detection,
)
from scripts.reactor_logger import logger
from scripts.reactor_swapper import (
EnhancementOptions,
swap_face,
check_process_halt,
EnhancementOptions,
DetectionOptions,
swap_face,
check_process_halt,
reset_messaged,
)
from scripts.reactor_version import version_flag, app_title
from scripts.console_log_patch import apply_logging_patch
from scripts.reactor_helpers import (
make_grid,
set_Device,
get_SDNEXT
make_grid,
set_Device,
get_SDNEXT,
)
from scripts.reactor_globals import SWAPPER_MODELS_PATH #, DEVICE, DEVICE_LIST
@ -65,6 +72,9 @@ class FaceSwapScript(scripts.Script):
}
img, imgs, select_source, face_model, source_folder, save_original, mask_face, source_faces_index, gender_source, faces_index, gender_target, face_restorer_name, face_restorer_visibility, codeformer_weight, swap_in_source, swap_in_generated, random_image = ui_main.show(is_img2img=is_img2img, **msgs)
# TAB DETECTION
det_thresh, det_maxnum = ui_detection.show()
# TAB UPSCALE
restore_first, upscaler_name, upscaler_scale, upscaler_visibility, upscale_force = ui_upscale.show()
@ -104,7 +114,9 @@ class FaceSwapScript(scripts.Script):
source_folder,
imgs,
random_image,
upscale_force
upscale_force,
det_thresh,
det_maxnum
]
@ -134,6 +146,13 @@ class FaceSwapScript(scripts.Script):
codeformer_weight=self.codeformer_weight,
upscale_force=self.upscale_force
)
@property
def detection_options(self) -> DetectionOptions:
return DetectionOptions(
det_thresh=self.det_thresh,
det_maxnum=self.det_maxnum
)
def process(
self,
@ -166,6 +185,8 @@ class FaceSwapScript(scripts.Script):
imgs,
random_image,
upscale_force,
det_thresh,
det_maxnum
):
self.enable = enable
if self.enable:
@ -202,6 +223,8 @@ class FaceSwapScript(scripts.Script):
self.source_imgs = imgs
self.random_image = random_image
self.upscale_force = upscale_force
self.det_thresh=det_thresh
self.det_maxnum=det_maxnum
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":
@ -266,6 +289,7 @@ class FaceSwapScript(scripts.Script):
source_folder = None,
source_imgs = None,
random_image = False,
detection_options=self.detection_options,
)
p.init_images[i] = result
# result_path = get_image_path(p.init_images[i], p.outpath_samples, "", p.all_seeds[i], p.all_prompts[i], "txt", p=p, suffix="-swapped")
@ -333,6 +357,7 @@ class FaceSwapScript(scripts.Script):
source_folder = self.source_folder,
source_imgs = self.source_imgs,
random_image = self.random_image,
detection_options=self.detection_options,
)
if self.select_source == 2 or (self.select_source == 0 and self.source_imgs is not None and self.source is None):
@ -413,7 +438,7 @@ class FaceSwapScript(scripts.Script):
return
# if (self.source is not None and self.select_source == 0) or ((self.face_model is not None and self.face_model != "None") and self.select_source == 1):
logger.status("! Working: source face index %s, target face index %s", self.source_faces_index, self.faces_index)
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(
self.source,
@ -433,6 +458,7 @@ class FaceSwapScript(scripts.Script):
source_folder = None,
source_imgs = None,
random_image = False,
detection_options=self.detection_options,
)
self.result = result
try:
@ -471,6 +497,9 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
}
img, imgs, select_source, face_model, source_folder, save_original, mask_face, source_faces_index, gender_source, faces_index, gender_target, face_restorer_name, face_restorer_visibility, codeformer_weight, swap_in_source, swap_in_generated, random_image = ui_main.show(is_img2img=False, show_br=False, **msgs)
# TAB DETECTION
det_thresh, det_maxnum = ui_detection.show()
# TAB UPSCALE
restore_first, upscaler_name, upscaler_scale, upscaler_visibility, upscale_force = ui_upscale.show(show_br=False)
@ -506,6 +535,8 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
'imgs': imgs,
'random_image': random_image,
'upscale_force': upscale_force,
'det_thresh': det_thresh,
'det_maxnum': det_maxnum,
}
return args
@ -535,6 +566,13 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
codeformer_weight=self.codeformer_weight,
upscale_force=self.upscale_force,
)
@property
def detection_options(self) -> DetectionOptions:
return DetectionOptions(
det_thresh=self.det_thresh,
det_maxnum=self.det_maxnum
)
def process(self, pp: scripts_postprocessing.PostprocessedImage, **args):
if args['enable']:
@ -563,6 +601,8 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
self.source_imgs = args['imgs']
self.random_image = args['random_image']
self.upscale_force = args['upscale_force']
self.det_thresh = args['det_thresh']
self.det_maxnum = args['det_maxnum']
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":
@ -622,6 +662,7 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
source_folder=self.source_folder,
source_imgs=self.source_imgs,
random_image=self.random_image,
detection_options=self.detection_options,
)
if self.select_source == 2 or (self.select_source == 0 and self.source_imgs is not None and self.source is None):
if len(result) > 0 and swapped > 0:

View File

@ -6,6 +6,7 @@ from typing import List, Union
import cv2
import numpy as np
from PIL import Image
from scipy import stats
import insightface
from insightface.app.common import Face
@ -66,6 +67,10 @@ class EnhancementOptions:
codeformer_weight: float = 0.5
upscale_force: bool = False
@dataclass
class DetectionOptions:
det_thresh: float = 0.5
det_maxnum: int = 0
MESSAGED_STOPPED = False
MESSAGED_SKIPPED = False
@ -106,12 +111,33 @@ TARGET_IMAGE_HASH = None
SOURCE_FACES_LIST = []
SOURCE_IMAGE_LIST_HASH = []
def clear_faces():
global SOURCE_FACES, SOURCE_IMAGE_HASH
SOURCE_FACES = None
SOURCE_IMAGE_HASH = None
logger.status("Source Images Hash has been reset (for Single Source or Face Model)")
def clear_faces_list():
global SOURCE_FACES_LIST, SOURCE_IMAGE_LIST_HASH
SOURCE_FACES_LIST = []
SOURCE_IMAGE_LIST_HASH = []
logger.status("Source Images Hash has been reset (for Multiple or Folder Source)")
def clear_faces_target():
global TARGET_FACES, TARGET_IMAGE_HASH
TARGET_FACES = None
TARGET_IMAGE_HASH = None
logger.status("Target Images Hash has been reset")
def clear_faces_all():
global SOURCE_FACES, SOURCE_IMAGE_HASH, SOURCE_FACES_LIST, SOURCE_IMAGE_LIST_HASH, TARGET_FACES, TARGET_IMAGE_HASH
SOURCE_FACES = None
SOURCE_IMAGE_HASH = None
TARGET_FACES = None
TARGET_IMAGE_HASH = None
SOURCE_FACES_LIST = []
SOURCE_IMAGE_LIST_HASH = []
logger.status("All Images Hash has been reset")
def getAnalysisModel():
global ANALYSIS_MODEL
@ -270,13 +296,13 @@ def half_det_size(det_size):
logger.status("Trying to halve 'det_size' parameter")
return (det_size[0] // 2, det_size[1] // 2)
def analyze_faces(img_data: np.ndarray, det_size=(640, 640)):
def analyze_faces(img_data: np.ndarray, det_size=(640, 640), det_thresh=0.5, det_maxnum=0):
logger.info("Applied Execution Provider: %s", PROVIDERS[0])
face_analyser = copy.deepcopy(getAnalysisModel())
face_analyser.prepare(ctx_id=0, det_size=det_size)
return face_analyser.get(img_data)
face_analyser.prepare(ctx_id=0, det_thresh=det_thresh, det_size=det_size)
return face_analyser.get(img_data, max_num=det_maxnum)
def get_face_single(img_data: np.ndarray, face, face_index=0, det_size=(640, 640), gender_source=0, gender_target=0):
def get_face_single(img_data: np.ndarray, face, face_index=0, det_size=(640, 640), gender_source=0, gender_target=0, det_thresh=0.5, det_maxnum=0):
buffalo_path = os.path.join(models_path, "insightface/models/buffalo_l.zip")
if os.path.exists(buffalo_path):
@ -299,20 +325,20 @@ def get_face_single(img_data: np.ndarray, face, face_index=0, det_size=(640, 640
if gender_source != 0:
if len(face) == 0 and det_size[0] > 320 and det_size[1] > 320:
det_size_half = half_det_size(det_size)
return get_face_single(img_data, analyze_faces(img_data, det_size_half), face_index, det_size_half, gender_source, gender_target)
return get_face_single(img_data, analyze_faces(img_data, det_size_half, det_thresh, det_maxnum), face_index, det_size_half, gender_source, gender_target, det_thresh, det_maxnum)
faces, wrong_gender = get_face_gender(face,face_index,gender_source,"Source",gender_detected)
return faces, wrong_gender, face_age, face_gender
if gender_target != 0:
if len(face) == 0 and det_size[0] > 320 and det_size[1] > 320:
det_size_half = half_det_size(det_size)
return get_face_single(img_data, analyze_faces(img_data, det_size_half), face_index, det_size_half, gender_source, gender_target)
return get_face_single(img_data, analyze_faces(img_data, det_size_half, det_thresh, det_maxnum), face_index, det_size_half, gender_source, gender_target, det_thresh, det_maxnum)
faces, wrong_gender = get_face_gender(face,face_index,gender_target,"Target",gender_detected)
return faces, wrong_gender, face_age, face_gender
if len(face) == 0 and det_size[0] > 320 and det_size[1] > 320:
det_size_half = half_det_size(det_size)
return get_face_single(img_data, analyze_faces(img_data, det_size_half), face_index, det_size_half, gender_source, gender_target)
return get_face_single(img_data, analyze_faces(img_data, det_size_half, det_thresh, det_maxnum), face_index, det_size_half, gender_source, gender_target, det_thresh, det_maxnum)
try:
return sorted(face, key=lambda x: x.bbox[0])[face_index], 0, face_age, face_gender
@ -338,6 +364,7 @@ def swap_face(
source_folder: str = "",
source_imgs: Union[List, None] = None,
random_image: bool = False,
detection_options: Union[DetectionOptions, None] = None,
):
global SOURCE_FACES, SOURCE_IMAGE_HASH, TARGET_FACES, TARGET_IMAGE_HASH, PROVIDERS, SOURCE_FACES_LIST, SOURCE_IMAGE_LIST_HASH
@ -413,15 +440,15 @@ def swap_face(
if len(SOURCE_FACES_LIST) == 0:
logger.status(f"Analyzing Source Image {i}: {source_images_names[i]}...")
source_faces = analyze_faces(source_image)
source_faces = analyze_faces(source_image, det_thresh=detection_options.det_thresh, det_maxnum=detection_options.det_maxnum)
SOURCE_FACES_LIST = [source_faces]
elif len(SOURCE_FACES_LIST) == i and not source_image_same:
logger.status(f"Analyzing Source Image {i}: {source_images_names[i]}...")
source_faces = analyze_faces(source_image)
source_faces = analyze_faces(source_image, det_thresh=detection_options.det_thresh, det_maxnum=detection_options.det_maxnum)
SOURCE_FACES_LIST.append(source_faces)
elif len(SOURCE_FACES_LIST) != i and not source_image_same:
logger.status(f"Analyzing Source Image {i}: {source_images_names[i]}...")
source_faces = analyze_faces(source_image)
source_faces = analyze_faces(source_image, det_thresh=detection_options.det_thresh, det_maxnum=detection_options.det_maxnum)
SOURCE_FACES_LIST[i] = source_faces
elif source_image_same:
logger.status("(Image %s) Using Hashed Source Face(s) Model...", i)
@ -429,7 +456,7 @@ def swap_face(
else:
logger.status(f"Analyzing Source Image {i}...")
source_faces = analyze_faces(source_image)
source_faces = analyze_faces(source_image, det_thresh=detection_options.det_thresh, det_maxnum=detection_options.det_maxnum)
if source_faces is not None:
source_faces_ff.append(source_faces)
@ -453,7 +480,7 @@ def swap_face(
if TARGET_FACES is None or not target_image_same:
logger.status("Analyzing Target Image...")
target_faces = analyze_faces(target_img)
target_faces = analyze_faces(target_img, det_thresh=detection_options.det_thresh, det_maxnum=detection_options.det_maxnum)
TARGET_FACES = target_faces
elif target_image_same:
logger.status("Using Hashed Target Face(s) Model...")
@ -461,12 +488,12 @@ def swap_face(
else:
logger.status("Analyzing Target Image...")
target_faces = analyze_faces(target_img)
target_faces = analyze_faces(target_img, det_thresh=detection_options.det_thresh, det_maxnum=detection_options.det_maxnum)
for i,source_faces in enumerate(source_faces_ff):
logger.status("(Image %s) Detecting Source Face, Index = %s", i, source_faces_index[0])
source_face, wrong_gender, source_age, source_gender = get_face_single(source_img_ff[i], source_faces, face_index=source_faces_index[0], gender_source=gender_source)
source_face, wrong_gender, source_age, source_gender = get_face_single(source_img_ff[i], source_faces, face_index=source_faces_index[0], gender_source=gender_source, det_thresh=detection_options.det_thresh, det_maxnum=detection_options.det_maxnum)
if source_age != "None" or source_gender != "None":
logger.status("(Image %s) Detected: -%s- y.o. %s", i, source_age, source_gender)
@ -476,7 +503,7 @@ def swap_face(
elif source_face is not None:
result_image, output, swapped = operate(source_img_ff[i],target_img,target_img_orig,model,source_faces_index,faces_index,source_faces,target_faces,gender_source,gender_target,source_face,wrong_gender,source_age,source_gender,output,swapped,mask_face,entire_mask_image,enhancement_options)
result_image, output, swapped = operate(source_img_ff[i],target_img,target_img_orig,model,source_faces_index,faces_index,source_faces,target_faces,gender_source,gender_target,source_face,wrong_gender,source_age,source_gender,output,swapped,mask_face,entire_mask_image,enhancement_options,detection_options)
result.append(result_image)
@ -513,7 +540,7 @@ def swap_face(
if SOURCE_FACES is None or not source_image_same:
logger.status("Analyzing Source Image...")
source_faces = analyze_faces(source_img)
source_faces = analyze_faces(source_img, det_thresh=detection_options.det_thresh, det_maxnum=detection_options.det_maxnum)
SOURCE_FACES = source_faces
elif source_image_same:
logger.status("Using Hashed Source Face(s) Model...")
@ -521,7 +548,7 @@ def swap_face(
else:
logger.status("Analyzing Source Image...")
source_faces = analyze_faces(source_img)
source_faces = analyze_faces(source_img, det_thresh=detection_options.det_thresh, det_maxnum=detection_options.det_maxnum)
elif select_source == 1 and (face_model is not None and face_model != "None"):
source_face_model = [load_face_model(face_model)]
@ -555,7 +582,7 @@ def swap_face(
if TARGET_FACES is None or not target_image_same:
logger.status("Analyzing Target Image...")
target_faces = analyze_faces(target_img)
target_faces = analyze_faces(target_img, det_thresh=detection_options.det_thresh, det_maxnum=detection_options.det_maxnum)
TARGET_FACES = target_faces
elif target_image_same:
logger.status("Using Hashed Target Face(s) Model...")
@ -563,11 +590,11 @@ def swap_face(
else:
logger.status("Analyzing Target Image...")
target_faces = analyze_faces(target_img)
target_faces = analyze_faces(target_img, det_thresh=detection_options.det_thresh, det_maxnum=detection_options.det_maxnum)
logger.status("Detecting Source Face, Index = %s", source_faces_index[0])
if select_source == 0 and source_img is not None:
source_face, wrong_gender, source_age, source_gender = get_face_single(source_img, source_faces, face_index=source_faces_index[0], gender_source=gender_source)
source_face, wrong_gender, source_age, source_gender = get_face_single(source_img, source_faces, face_index=source_faces_index[0], gender_source=gender_source, det_thresh=detection_options.det_thresh, det_maxnum=detection_options.det_maxnum)
else:
source_face = sorted(source_faces, key=lambda x: x.bbox[0])[source_faces_index[0]]
wrong_gender = 0
@ -585,7 +612,7 @@ def swap_face(
elif source_face is not None:
result_image, output, swapped = operate(source_img,target_img,target_img_orig,model,source_faces_index,faces_index,source_faces,target_faces,gender_source,gender_target,source_face,wrong_gender,source_age,source_gender,output,swapped,mask_face,entire_mask_image,enhancement_options)
result_image, output, swapped = operate(source_img,target_img,target_img_orig,model,source_faces_index,faces_index,source_faces,target_faces,gender_source,gender_target,source_face,wrong_gender,source_age,source_gender,output,swapped,mask_face,entire_mask_image,enhancement_options,detection_options)
else:
logger.status("No source face(s) in the provided Index")
@ -599,7 +626,7 @@ def swap_face(
return result_image, [], 0
def build_face_model(image: Image.Image, name: str):
def build_face_model(image: Image.Image, name: str, save_model: bool = True):
if image is None:
error_msg = "Please load an Image"
logger.error(error_msg)
@ -610,20 +637,77 @@ def build_face_model(image: Image.Image, name: str):
return error_msg
apply_logging_patch(1)
image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
logger.status("Building Face Model...")
if save_model:
logger.status("Building Face Model...")
face_model = analyze_faces(image)
if face_model is not None and len(face_model) > 0:
face_model_path = os.path.join(FACE_MODELS_PATH, name + ".safetensors")
save_face_model(face_model[0],face_model_path)
logger.status("--Done!--")
done_msg = f"Face model has been saved to '{face_model_path}'"
logger.status(done_msg)
return done_msg
if save_model:
face_model_path = os.path.join(FACE_MODELS_PATH, name + ".safetensors")
save_face_model(face_model[0],face_model_path)
logger.status("--Done!--")
done_msg = f"Face model has been saved to '{face_model_path}'"
logger.status(done_msg)
return done_msg
else:
return face_model[0]
else:
no_face_msg = "No face found, please try another image"
logger.error(no_face_msg)
return no_face_msg
def blend_faces(images_list: List, name: str, compute_method: int = 0, shape_check: bool = False):
faces = []
embeddings = []
images: List[Image.Image] = []
images, images_names = get_images_from_list(images_list)
for i,image in enumerate(images):
logger.status(f"Building Face Model for {images_names[i]}...")
face = build_face_model(image,str(i),save_model=False)
if isinstance(face, str):
# logger.error(f"No faces found in {images_names[i]}, skipping")
continue
if shape_check:
if i == 0:
embedding_shape = face.embedding.shape
elif face.embedding.shape != embedding_shape:
logger.error(f"Embedding Shape Mismatch for {images_names[i]}, skipping")
continue
faces.append(face)
embeddings.append(face.embedding)
if len(faces) > 0:
# if shape_check:
# embedding_shape = embeddings[0].shape
# for embedding in embeddings:
# if embedding.shape != embedding_shape:
# logger.error("Embedding Shape Mismatch")
# break
compute_method_name = "Mean" if compute_method == 0 else "Median" if compute_method == 1 else "Mode"
logger.status(f"Blending with Compute Method {compute_method_name}...")
blended_embedding = np.mean(embeddings, axis=0) if compute_method == 0 else np.median(embeddings, axis=0) if compute_method == 1 else stats.mode(embeddings, axis=0)[0].astype(np.float32)
blended_face = Face(
bbox=faces[0].bbox,
kps=faces[0].kps,
det_score=faces[0].det_score,
landmark_3d_68=faces[0].landmark_3d_68,
pose=faces[0].pose,
landmark_2d_106=faces[0].landmark_2d_106,
embedding=blended_embedding,
gender=faces[0].gender,
age=faces[0].age
)
if blended_face is not None:
face_model_path = os.path.join(FACE_MODELS_PATH, name + ".safetensors")
save_face_model(blended_face,face_model_path)
logger.status("--Done!--")
done_msg = f"Face model has been saved to '{face_model_path}'"
logger.status(done_msg)
return done_msg
else:
no_face_msg = "Something went wrong, please try another set of images"
logger.error(no_face_msg)
return no_face_msg
return "No faces found"
def operate(
source_img,
@ -645,6 +729,7 @@ def operate(
mask_face,
entire_mask_image,
enhancement_options,
detection_options,
):
result = target_img
face_swapper = getFaceSwapModel(model)
@ -656,7 +741,7 @@ def operate(
return result_image, [], 0
if len(source_faces_index) > 1 and source_face_idx > 0:
logger.status("Detecting Source Face, Index = %s", source_faces_index[source_face_idx])
source_face, wrong_gender, source_age, source_gender = get_face_single(source_img, source_faces, face_index=source_faces_index[source_face_idx], gender_source=gender_source)
source_face, wrong_gender, source_age, source_gender = get_face_single(source_img, source_faces, face_index=source_faces_index[source_face_idx], gender_source=gender_source, det_thresh=detection_options.det_thresh, det_maxnum=detection_options.det_maxnum)
if source_age != "None" or source_gender != "None":
logger.status("Detected: -%s- y.o. %s", source_age, source_gender)
@ -667,7 +752,7 @@ def operate(
if source_face is not None and wrong_gender == 0:
logger.status("Detecting Target Face, Index = %s", face_num)
target_face, wrong_gender, target_age, target_gender = get_face_single(target_img, target_faces, face_index=face_num, gender_target=gender_target)
target_face, wrong_gender, target_age, target_gender = get_face_single(target_img, target_faces, face_index=face_num, gender_target=gender_target, det_thresh=detection_options.det_thresh, det_maxnum=detection_options.det_maxnum)
if target_age != "None" or target_gender != "None":
logger.status("Detected: -%s- y.o. %s", target_age, target_gender)

View File

@ -1,5 +1,5 @@
app_title = "ReActor"
version_flag = "v0.6.1"
version_flag = "v0.7.0-a1"
from scripts.reactor_logger import logger, get_Run, set_Run
from scripts.reactor_globals import DEVICE