diff --git a/README.md b/README.md
index 4f8bdc2..63ddfef 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
- 
+ 
@@ -40,6 +40,15 @@
## What's new in the latest updates
+### 0.7.0 ALPHA1
+
+- You can now blend faces to build blended face models ("Tools->Face Models->Blend") - due to popular demand
+
+
+
+- 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 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)
diff --git a/README_RU.md b/README_RU.md
index 2204dd6..ab50a72 100644
--- a/README_RU.md
+++ b/README_RU.md
@@ -2,7 +2,7 @@
- 
+ 
@@ -39,6 +39,15 @@
## Что нового в последних обновлениях
+### 0.7.0 ALPHA1
+
+- По многочисленным просьбам появилась возможность строить смешанные модели лиц ("Tools->Face Models->Blend")
+
+
+
+- Поддержка CUDA 12 в скрипте установщика для библиотеки ORT-GPU версии 1.17.0
+- Новая вкладка "Detection" с параметрами "Threshold" и "Max Faces"
+
### 0.6.1 BETA3
- Опция 'Force Upscale' внутри вкладки 'Upscale': апскейл выполнится, даже если не было обнаружено ни одного лица (FR https://github.com/Gourieff/sd-webui-reactor/issues/116)
diff --git a/example/api_example.py b/example/api_example.py
index f683ee5..d3bc64a 100644
--- a/example/api_example.py
+++ b/example/api_example.py
@@ -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
diff --git a/example/insightface-0.7.3-cp310-cp310-win_amd64.whl b/example/insightface-0.7.3-cp310-cp310-win_amd64.whl
deleted file mode 100644
index 4f71ef2..0000000
Binary files a/example/insightface-0.7.3-cp310-cp310-win_amd64.whl and /dev/null differ
diff --git a/install.py b/install.py
index 444a8f6..10fce4c 100644
--- a/install.py
+++ b/install.py
@@ -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:
diff --git a/reactor_ui/__init__.py b/reactor_ui/__init__.py
index 00711fd..22bba1f 100644
--- a/reactor_ui/__init__.py
+++ b/reactor_ui/__init__.py
@@ -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
diff --git a/reactor_ui/reactor_detection_ui.py b/reactor_ui/reactor_detection_ui.py
new file mode 100644
index 0000000..58f7111
--- /dev/null
+++ b/reactor_ui/reactor_detection_ui.py
@@ -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("
", 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
diff --git a/reactor_ui/reactor_tools_ui.py b/reactor_ui/reactor_tools_ui.py
index 0abcb0e..a131ac8 100644
--- a/reactor_ui/reactor_tools_ui.py
+++ b/reactor_ui/reactor_tools_ui.py
@@ -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],
- )
diff --git a/scripts/reactor_api.py b/scripts/reactor_api.py
index c38ad13..7991d81 100644
--- a/scripts/reactor_api.py
+++ b/scripts/reactor_api.py
@@ -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()
diff --git a/scripts/reactor_faceswap.py b/scripts/reactor_faceswap.py
index 604884a..6f21fb5 100644
--- a/scripts/reactor_faceswap.py
+++ b/scripts/reactor_faceswap.py
@@ -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:
diff --git a/scripts/reactor_swapper.py b/scripts/reactor_swapper.py
index 6d34648..76a6e39 100644
--- a/scripts/reactor_swapper.py
+++ b/scripts/reactor_swapper.py
@@ -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)
diff --git a/scripts/reactor_version.py b/scripts/reactor_version.py
index ec858c1..9c9c4c8 100644
--- a/scripts/reactor_version.py
+++ b/scripts/reactor_version.py
@@ -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