diff --git a/README.md b/README.md index 73c2c6b..8b5d9b0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ logo - ![Version](https://img.shields.io/badge/version-0.4.1_beta2-green?style=for-the-badge&labelColor=darkgreen)
+ ![Version](https://img.shields.io/badge/version-0.4.1_beta3-green?style=for-the-badge&labelColor=darkgreen)
[![Commit activity](https://img.shields.io/github/commit-activity/t/Gourieff/sd-webui-reactor/main?cacheSeconds=0)](https://github.com/Gourieff/sd-webui-reactor/commits/main) ![Last commit](https://img.shields.io/github/last-commit/Gourieff/sd-webui-reactor/main?cacheSeconds=0) [![Opened issues](https://img.shields.io/github/issues/Gourieff/sd-webui-reactor?color=red)](https://github.com/Gourieff/sd-webui-reactor/issues?cacheSeconds=0) diff --git a/README_RU.md b/README_RU.md index cc8c67a..160e8e3 100644 --- a/README_RU.md +++ b/README_RU.md @@ -2,7 +2,7 @@ logo - ![Version](https://img.shields.io/badge/версия-0.4.1_beta2-green?style=for-the-badge&labelColor=darkgreen)
+ ![Version](https://img.shields.io/badge/версия-0.4.1_beta3-green?style=for-the-badge&labelColor=darkgreen)
[![Commit activity](https://img.shields.io/github/commit-activity/t/Gourieff/sd-webui-reactor/main?cacheSeconds=0)](https://github.com/Gourieff/sd-webui-reactor/commits/main) ![Last commit](https://img.shields.io/github/last-commit/Gourieff/sd-webui-reactor/main?cacheSeconds=0) [![Opened issues](https://img.shields.io/github/issues/Gourieff/sd-webui-reactor?color=red)](https://github.com/Gourieff/sd-webui-reactor/issues?cacheSeconds=0) diff --git a/example/api_example.py b/example/api_example.py index 1867c20..a359148 100644 --- a/example/api_example.py +++ b/example/api_example.py @@ -8,7 +8,7 @@ time = datetime.now() today = date.today() current_date = today.strftime('%Y-%m-%d') current_time = time.strftime('%H-%M-%S') -output_file = 'outputs/api/output_'+current_date+'_'+current_time+'.png' # Output file path +output = 'outputs/api/output_'+current_date+'_'+current_time # Output file path + name index try: im = Image.open(input_file) except Exception as e: @@ -26,7 +26,7 @@ args=[ True, #1 Enable ReActor '0', #2 Comma separated face number(s) from swap-source image '0', #3 Comma separated face number(s) for target image (result) - 'C:\stable-diffusion-webui\models/roop\inswapper_128.onnx', #4 model path + 'C:\stable-diffusion-webui\models\insightface\inswapper_128.onnx', #4 model path 'CodeFormer', #4 Restore Face: None; CodeFormer; GFPGAN 1, #5 Restore visibility value True, #7 Restore face -> Upscale @@ -38,6 +38,7 @@ args=[ 1, #13 Console Log Level (0 - min, 1 - med or 2 - max) 0, #14 Gender Detection (Source) (0 - No, 1 - Female Only, 2 - Male Only) 0, #15 Gender Detection (Target) (0 - No, 1 - Female Only, 2 - Male Only) + False, #16 Save the original image(s) made before swapping ] # The args for ReActor can be found by @@ -70,6 +71,7 @@ finally: if result is not None: r = result.json() + n = 0 for i in r['images']: image = Image.open(io.BytesIO(base64.b64decode(i.split(",",1)[0]))) @@ -81,11 +83,13 @@ if result is not None: pnginfo = PngImagePlugin.PngInfo() pnginfo.add_text("parameters", response2.json().get("info")) + output_file = output+'_'+str(n)+'_.png' try: image.save(output_file, pnginfo=pnginfo) except Exception as e: print(e) finally: print(f'{output_file} is saved\nAll is done!') + n += 1 else: print('Something went wrong...') diff --git a/scripts/faceswap.py b/scripts/faceswap.py index 5e59e13..009a400 100644 --- a/scripts/faceswap.py +++ b/scripts/faceswap.py @@ -2,20 +2,25 @@ import os, glob import gradio as gr from PIL import Image +from typing import List + import modules.scripts as scripts from modules.upscaler import Upscaler, UpscalerData from modules import scripts, shared, images, scripts_postprocessing from modules.processing import ( + Processed, StableDiffusionProcessing, StableDiffusionProcessingImg2Img, ) from modules.face_restoration import FaceRestoration from modules.paths_internal import models_path +from modules.images import save_image from scripts.logger import logger from scripts.swapper import UpscaleOptions, swap_face, check_process_halt, reset_messaged from scripts.version import version_flag, app_title from scripts.console_log_patch import apply_logging_patch +from scripts.helpers import make_grid MODELS_PATH = None @@ -44,10 +49,11 @@ class FaceSwapScript(scripts.Script): def ui(self, is_img2img): with gr.Accordion(f"{app_title}", open=False): - with gr.Tab("Input"): + with gr.Tab("Main"): with gr.Column(): img = gr.inputs.Image(type="pil") 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") gr.Markdown("
") gr.Markdown("Source Image (above):") with gr.Row(): @@ -153,6 +159,7 @@ class FaceSwapScript(scripts.Script): console_logging_level, gender_source, gender_target, + save_original, ] @@ -200,6 +207,7 @@ class FaceSwapScript(scripts.Script): console_logging_level, gender_source, gender_target, + save_original, ): self.enable = enable if self.enable: @@ -221,6 +229,7 @@ class FaceSwapScript(scripts.Script): self.console_logging_level = console_logging_level self.gender_source = gender_source self.gender_target = gender_target + self.save_original = save_original 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": @@ -242,7 +251,8 @@ class FaceSwapScript(scripts.Script): logger.info("Working: source face index %s, target face index %s", self.source_faces_index, self.faces_index) for i in range(len(p.init_images)): - logger.info("Swap in %s", i) + if len(p.init_images) > 1: + logger.info("Swap in %s", i) result = swap_face( self.source, p.init_images[i], @@ -261,12 +271,67 @@ class FaceSwapScript(scripts.Script): else: logger.error("Please provide a source face") - def postprocess_batch(self, p, *args, **kwargs): + def postprocess(self, p: StableDiffusionProcessing, processed: Processed, *args): if self.enable: + + reset_messaged() + if check_process_halt(): + return + + 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 + + if self.swap_in_generated: + logger.info("Working: source face index %s, target face index %s", self.source_faces_index, self.faces_index) + if self.source is not None: + for i,(img,info) in enumerate(zip(orig_images, orig_infotexts)): + if check_process_halt(): + postprocess_run = False + break + if len(orig_images) > 1: + logger.info("Swap in %s", i) + result = swap_face( + self.source, + img, + source_faces_index=self.source_faces_index, + faces_index=self.faces_index, + model=self.model, + upscale_options=self.upscale_options, + gender_source=self.gender_source, + gender_target=self.gender_target, + ) + if result is not None: + suffix = "-swapped" + result_images.append(result) + try: + 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)") + else: + 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: + if self.enable and self.swap_in_generated and not self.save_original: current_job_number = shared.state.job_no + 1 job_count = shared.state.job_count @@ -274,7 +339,7 @@ class FaceSwapScript(scripts.Script): reset_messaged() if check_process_halt(): return - + if self.source is not None: logger.info("Working: source face index %s, target face index %s", self.source_faces_index, self.faces_index) image: Image.Image = script_pp.image diff --git a/scripts/helpers.py b/scripts/helpers.py new file mode 100644 index 0000000..0f5eb59 --- /dev/null +++ b/scripts/helpers.py @@ -0,0 +1,55 @@ +from collections import Counter +from PIL import Image +from math import isqrt, ceil +from typing import List + +def make_grid(image_list: List): + """ + Creates a square image by combining multiple images in a grid pattern. + + Args: + image_list (list): List of PIL Image objects to be combined. + + Returns: + PIL Image object: The resulting square image. + None: If the image_list is empty or contains only one image. + """ + + # Count the occurrences of each image size in the image_list + size_counter = Counter(image.size for image in image_list) + + # Get the most common image size (size with the highest count) + common_size = size_counter.most_common(1)[0][0] + + # Filter the image_list to include only images with the common size + image_list = [image for image in image_list if image.size == common_size] + + # Get the dimensions (width and height) of the common size + size = common_size + + # If there are more than one image in the image_list + if len(image_list) > 1: + num_images = len(image_list) + + # Calculate the number of rows and columns for the grid + rows = isqrt(num_images) + cols = ceil(num_images / rows) + + # Calculate the size of the square image + square_size = (cols * size[0], rows * size[1]) + + # Create a new RGB image with the square size + square_image = Image.new("RGB", square_size) + + # Paste each image onto the square image at the appropriate position + for i, image in enumerate(image_list): + row = i // cols + col = i % cols + + square_image.paste(image, (col * size[0], row * size[1])) + + # Return the resulting square image + return square_image + + # Return None if there are no images or only one image in the image_list + return None \ No newline at end of file diff --git a/scripts/reactor_api.py b/scripts/reactor_api.py index 9bab898..6c2106f 100644 --- a/scripts/reactor_api.py +++ b/scripts/reactor_api.py @@ -39,7 +39,7 @@ def get_upscaler(name): return None def get_models(): - models_path = os.path.join(scripts.basedir(), "models/roop/*") + models_path = os.path.join(scripts.basedir(), "models/insightface/*") models = glob.glob(models_path) models = [x for x in models if x.endswith(".onnx") or x.endswith(".pth")] return models diff --git a/scripts/swapper.py b/scripts/swapper.py index f7b57de..4047302 100644 --- a/scripts/swapper.py +++ b/scripts/swapper.py @@ -257,8 +257,7 @@ def swap_face( elif source_face is not None: result = target_img - model_path = os.path.join(models_path, "insightface", model) - face_swapper = getFaceSwapModel(model_path) + face_swapper = getFaceSwapModel(model) source_face_idx = 0 diff --git a/scripts/version.py b/scripts/version.py index d82c0dd..61b56b8 100644 --- a/scripts/version.py +++ b/scripts/version.py @@ -1,5 +1,5 @@ app_title = "ReActor" -version_flag = "v0.4.1-b2" +version_flag = "v0.4.1-b3" from scripts.logger import logger, get_Run, set_Run