diff --git a/README.md b/README.md index dc3da48..695634b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ logo - ![Version](https://img.shields.io/badge/version-0.5.1-brightgreen?style=for-the-badge&labelColor=darkgreen) + ![Version](https://img.shields.io/badge/version-0.6.0_alpha1-lightgreen?style=for-the-badge&labelColor=darkgreen) Support Me on Boosty @@ -38,11 +38,20 @@ -## What's new in the latest update +## What's new in the latest updates + +### 0.6.0 ALPHA1 + +- UI reworked +- You can now load several source images (with reference faces) or set the path to the folder containing faces images + +0.6.0-whatsnew-01 + +0.6.0-whatsnew-02 ### 0.5.1 -- You can now save face models as "safetensors" files (stored in `\models\reactor\faces`) and load them into ReActor, keeping super lightweight face models of the faces you use; +- You can save face models as "safetensors" files (stored in `\models\reactor\faces`) and load them into ReActor, keeping super lightweight face models of the faces you use; - "Face Mask Correction" option - if you encounter some pixelation around face contours, this option will be useful; 0.5.0-whatsnew-01 @@ -59,8 +68,8 @@ - OR only **VS C++ Build Tools** (if you don't need the whole Visual Studio) and select "Desktop Development with C++" under "Workloads -> Desktop & Mobile": https://visualstudio.microsoft.com/visual-cpp-build-tools/ - OR if you don't want to install VS or VS C++ BT - follow [this steps (sec. VIII)](#insightfacebuild) -2. In web-ui, go to the "Extensions" tab and use this URL `https://github.com/Gourieff/sd-webui-reactor` in the "Install from URL" tab and click "Install" -3. Please, wait for several minutes until the installation process will be finished +2. In web-ui, go to the "Extensions" tab, load "Available" extensions and type "ReActor" in the search field or use this URL `https://github.com/Gourieff/sd-webui-reactor` in the "Install from URL" tab - and click "Install" +3. Please, wait for several minutes until the installation process will be finished (be patient, don't interrupt the process) 4. Check the last message in your SD-WebUI Console: * If you see the message "--- PLEASE, RESTART the Server! ---" - so, do it, stop the Server (CTRL+C or CMD+C) and start it again - or just go to the "Installed" tab, click "Apply and restart UI" * If you see the message "Done!", just reload the UI @@ -73,7 +82,7 @@ 3. Go to (Windows)`automatic\venv\Scripts` or (MacOS/Linux)`automatic/venv/bin`, run Terminal or Console (cmd) for that folder and type `activate` 4. Run `pip install insightface==0.7.3` 5. Run SD.Next, go to the "Extensions" tab and use this URL `https://github.com/Gourieff/sd-webui-reactor` in the "Install from URL" tab and click "Install" -6. Please, wait for several minutes until the installation process will be finished +6. Please, wait for several minutes until the installation process will be finished (be patient, don't interrupt the process) 7. Check the last message in your SD.Next Console: * If you see the message "--- PLEASE, RESTART the Server! ---" - stop the Server (CTRL+C or CMD+C) or just close your console 8. Go to the `automatic\extensions\sd-webui-reactor` directory - if you see there `models\insightface` folder with the file `inswapper_128.onnx`, just move the file to the `automatic\models\insightface` folder @@ -81,8 +90,8 @@ If you use [Cagliostro Colab UI](https://github.com/Linaqruf/sd-notebook-collection): -1. In active WebUI, go to the "Extensions" tab and use this URL `https://github.com/Gourieff/sd-webui-reactor` in the "Install from URL" tab and click "Install" -2. Please, wait for several minutes until the installation process will be finished +1. In active WebUI, go to the "Extensions" tab, load "Available" extensions and type "ReActor" in the search field or use this URL `https://github.com/Gourieff/sd-webui-reactor` in the "Install from URL" tab - and click "Install" +2. Please, wait for several minutes until the installation process will be finished (be patient, don't interrupt the process) 3. When you see the message "--- PLEASE, RESTART the Server! ---" (in your Colab Notebook Start UI section "Start Cagliostro Colab UI") - just go to the "Installed" tab and click "Apply and restart UI" 4. Enjoy! @@ -99,7 +108,7 @@ - Ability to set the **Postprocessing order** - **100% compatibility** with different **SD WebUIs**: Automatic1111, SD.Next, Cagliostro Colab UI - **Fast performance** even with CPU, ReActor for SD WebUI is absolutely not picky about how powerful your GPU is -- **CUDA** acceleration support from the version 0.5.0 +- **CUDA** acceleration support since version 0.5.0 - **[API](/API.md) support**: both SD WebUI built-in and external (via POST/GET requests) - **ComfyUI [support](https://github.com/Gourieff/comfyui-reactor-node)** - **Mac M1/M2 [support](https://github.com/Gourieff/sd-webui-reactor/issues/42)** @@ -191,7 +200,7 @@ Please, check the path where "inswapper_128.onnx" model is stored. It must be in 7. Then one-by-one: - `pip install insightface==0.7.3` - `pip install onnx` - - `pip install onnxruntime-gpu>=1.16.1` + - `pip install "onnxruntime-gpu>=1.16.1"` - `pip install opencv-python` - `pip install tqdm` 8. Type `deactivate`, you can close your Terminal or Console and start your SD WebUI, ReActor should start OK - if not, welcome to the Issues section. @@ -226,7 +235,7 @@ and put it to the `stable-diffusion-webui\models\insightface` replacing existing 4. Then: - `python -m pip install -U pip` - `pip uninstall -y onnxruntime onnxruntime-gpu onnxruntime-silicon onnxruntime-extensions` -- `pip install onnxruntime-gpu>=1.16.1` +- `pip install "onnxruntime-gpu>=1.16.1"` If it didn't help - it seems that you have another extension reinstalling `onnxruntime` when SD WebUI checks requirements. Please see your extensions list. Some extensions can causes reinstalling of `onnxruntime-gpu` to `onnxruntime<1.16.1` every time SD WebUI runs.
ORT 1.16.0 has a bug https://github.com/microsoft/onnxruntime/issues/17631 - don't install it! @@ -239,7 +248,7 @@ If it didn't help - it seems that you have another extension reinstalling `onnxr 5. Then: - `python -m pip install -U pip` - `pip uninstall protobuf` -- `pip install protobuf>=3.20.3` +- `pip install "protobuf>=3.20.3"` If this method doesn't help - there is some other extension that has a wrong version of protobuf dependence and SD WebUI installs it on a startup requirements check diff --git a/README_RU.md b/README_RU.md index e7a07a1..78bb651 100644 --- a/README_RU.md +++ b/README_RU.md @@ -2,7 +2,7 @@ logo - ![Version](https://img.shields.io/badge/версия-0.5.1-brightgreen?style=for-the-badge&labelColor=darkgreen) + ![Version](https://img.shields.io/badge/версия-0.6.0_alpha1-lightgreen?style=for-the-badge&labelColor=darkgreen)
Поддержать проект на Boosty @@ -37,7 +37,14 @@ -## Что нового в последнем обновлении +## Что нового в последних обновлениях + +### 0.6.0 ALPHA1 + +- UI переработан +- Появилась возможность загружать несколько исходных изображений с лицами или задавать путь к папке, содержащей такие изображения + +0.6.0-whatsnew-01 ### 0.5.1 @@ -60,8 +67,8 @@ - ИЛИ только **VS C++ Build Tools** (если вам не нужен весь пакет Visual Studio), выберите "Desktop Development with C++" в разделе "Workloads -> Desktop & Mobile": https://visualstudio.microsoft.com/visual-cpp-build-tools/ - ИЛИ если же вы не хотите устанавливать что-либо из вышеуказанного - выполните [следующие шаги (пункт VIII)](#insightfacebuild) -2. Внутри SD Web-UI перейдите во вкладку "Extensions" и вставьте ссылку `https://github.com/Gourieff/sd-webui-reactor` в "Install from URL" и нажмите "Install" -3. Пожалуйста, подождите несколько минут, пока процесс установки полностью не завершится +2. Внутри SD Web-UI перейдите во вкладку "Extensions", загрузите список доступных расширений (вкладка "Available") и введите "ReActor" в строке поиска или же вставьте ссылку `https://github.com/Gourieff/sd-webui-reactor` в "Install from URL" - и нажмите "Install" +3. Пожалуйста, подождите несколько минут, пока процесс установки полностью не завершится (наберитесь терпения, не прерывайте процесс) 4. Проверьте последнее сообщение в консоли SD-WebUI: * Если вы видите "--- PLEASE, RESTART the Server! ---" - остановите Сервер (CTRL+C или CMD+C) и запустите его заново - ИЛИ же перейдите во вкладку "Installed", нажмите "Apply and restart UI" * Если вы видите "Done!", просто перезагрузите UI, нажав на "Reload UI" @@ -74,7 +81,7 @@ 3. Перейдите в (Windows)`automatic\venv\Scripts` или (MacOS/Linux)`automatic/venv/bin`, запустите Терминал или Консоль (cmd) для данной папки и выполните `activate` 4. Выполните `pip install insightface==0.7.3` 5. Запустите SD.Next, перейдите во вкладку "Extensions", вставьте эту ссылку `https://github.com/Gourieff/sd-webui-reactor` в "Install from URL" и нажмите "Install" -6. Пожалуйста, подождите несколько минут, пока процесс установки полностью не завершится +6. Пожалуйста, подождите несколько минут, пока процесс установки полностью не завершится (наберитесь терпения, не прерывайте процесс) 7. Проверьте последнее сообщение в консоли SD.Next: * Если вы видите "--- PLEASE, RESTART the Server! ---" - остановите Сервер (CTRL+C или CMD+C) или просто закройте консоль 8. Перейдите в директорию `automatic\extensions\sd-webui-reactor` - если вы видите там папку `models\insightface` с файлом `inswapper_128.onnx` внутри, переместите его в папку `automatic\models\insightface` @@ -82,8 +89,8 @@ Если вы используете [Cagliostro Colab UI](https://github.com/Linaqruf/sd-notebook-collection): -1. В активном WebUI, перейдите во вкладку "Extensions", вставьте ссылку `https://github.com/Gourieff/sd-webui-reactor` в "Install from URL" и нажмите "Install" -2. Пожалуйста, подождите некоторое время, пока процесс установки полностью не завершится +1. В активном WebUI перейдите во вкладку "Extensions", загрузите список доступных расширений (вкладка "Available") и введите "ReActor" в строке поиска или же вставьте ссылку `https://github.com/Gourieff/sd-webui-reactor` в "Install from URL" - и нажмите "Install" +2. Пожалуйста, подождите некоторое время, пока процесс установки полностью не завершится (наберитесь терпения, не прерывайте процесс) 3. Когда вы увидите сообщение "--- PLEASE, RESTART the Server! ---" (в секции "Start UI" вашего ноутбука "Start Cagliostro Colab UI") - перейдите во вкладку "Installed" и нажмите "Apply and restart UI" 4. Готово! @@ -198,7 +205,7 @@ Inpainting также работает, но замена лица происх 7. Далее: - `pip install insightface==0.7.3` - `pip install onnx` - - `pip install onnxruntime-gpu>=1.16.1` + - `pip install "onnxruntime-gpu>=1.16.1"` - `pip install opencv-python` - `pip install tqdm` 8. Выполните `deactivate`, закройте Терминал или Консоль и запустите SD WebUI, ReActor должен запуститься без к-л проблем - если же нет, добро пожаловать в раздел "Issues". @@ -233,7 +240,7 @@ Inpainting также работает, но замена лица происх 4. Затем: - `python -m pip install -U pip` - `pip uninstall -y onnxruntime onnxruntime-gpu onnxruntime-silicon onnxruntime-extensions` -- `pip install onnxruntime-gpu>=1.16.1` +- `pip install "onnxruntime-gpu>=1.16.1"` Если это не помогло - значит какое-то другое расширение переустанавливает `onnxruntime` всякий раз, когда SD WebUI проверяет требования пакетов. Внимательно посмотрите список активных расширений. Некоторые расширения могут вызывать переустановку `onnxruntime-gpu` на версию `onnxruntime<1.16.1` при каждом запуске SD WebUI.
ORT 1.16.0 выкатили с ошибкой https://github.com/microsoft/onnxruntime/issues/17631 - не устанавливайте её! @@ -246,7 +253,7 @@ Inpainting также работает, но замена лица происх 5. Затем: - `python -m pip install -U pip` - `pip uninstall protobuf` -- `pip install protobuf>=3.20.3` +- `pip install "protobuf>=3.20.3"` Если это не помгло - значит, есть к-л другое расширение, которое использует неподходящую версию пакета protobuf, и SD WebUI устанавливает эту версию при каждом запуске. diff --git a/example/api_example.py b/example/api_example.py index f3485c1..bcd2504 100644 --- a/example/api_example.py +++ b/example/api_example.py @@ -44,8 +44,9 @@ args=[ False, #19 Target Image Hash Check, False - by default "CUDA", #20 CPU or CUDA (if you have it), CPU - by default True, #21 Face Mask Correction - 1, #22 Select Source, 0 - Image, 1 - Face Model - "elena.safetensors", #23 Filename of the face model (from "models/reactor/faces"), e.g. elena.safetensors + 1, #22 Select Source, 0 - Image, 1 - Face Model, 2 - Source Folder + "elena.safetensors", #23 Filename of the face model (from "models/reactor/faces"), e.g. elena.safetensors, don't forger to set #22 to 1 + "C:\PATH_TO_FACES_IMAGES", #24 The path to the folder containing source faces images, don't forger to set #22 to 2 ] # The args for ReActor can be found by diff --git a/modules/reactor_mask.py b/reactor_modules/reactor_mask.py similarity index 100% rename from modules/reactor_mask.py rename to reactor_modules/reactor_mask.py diff --git a/reactor_ui/__init__.py b/reactor_ui/__init__.py new file mode 100644 index 0000000..00711fd --- /dev/null +++ b/reactor_ui/__init__.py @@ -0,0 +1,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 diff --git a/reactor_ui/reactor_main_ui.py b/reactor_ui/reactor_main_ui.py new file mode 100644 index 0000000..5ed4212 --- /dev/null +++ b/reactor_ui/reactor_main_ui.py @@ -0,0 +1,182 @@ +import gradio as gr +from scripts.reactor_helpers import ( + get_model_names, + get_facemodels +) +from scripts.reactor_swapper import ( + clear_faces_list, +) +from modules import shared + +SAVE_ORIGINAL: bool = False + +def update_fm_list(selected: str): + return gr.Dropdown.update( + value=selected, choices=get_model_names(get_facemodels) + ) + +# TAB MAIN +def show(is_img2img: bool, show_br: bool = True, **msgs): + + def on_select_source(selected: bool, evt: gr.SelectData): + global SAVE_ORIGINAL + if evt.index == 2: + if SAVE_ORIGINAL != selected: + SAVE_ORIGINAL = selected + return { + control_col_1: gr.Column.update(visible=False), + control_col_2: gr.Column.update(visible=False), + control_col_3: gr.Column.update(visible=True), + save_original: gr.Checkbox.update(value=False,visible=False), + imgs_hash_clear: gr.Button.update(visible=True) + } + if evt.index == 0: + return { + control_col_1: gr.Column.update(visible=True), + control_col_2: gr.Column.update(visible=False), + control_col_3: gr.Column.update(visible=False), + save_original: gr.Checkbox.update(value=SAVE_ORIGINAL,visible=show_br), + imgs_hash_clear: gr.Button.update(visible=False) + } + if evt.index == 1: + return { + control_col_1: gr.Column.update(visible=False), + control_col_2: gr.Column.update(visible=True), + control_col_3: gr.Column.update(visible=False), + save_original: gr.Checkbox.update(value=SAVE_ORIGINAL,visible=show_br), + imgs_hash_clear: gr.Button.update(visible=False) + } + + progressbar_area = gr.Markdown("") + with gr.Tab("Main"): + with gr.Column(): + with gr.Row(): + select_source = gr.Radio( + ["Image(s)","Face Model","Folder"], + value="Image(s)", + label="Select Source", + type="index", + scale=1, + ) + with gr.Column(visible=False) as control_col_2: + with gr.Row(): + face_models = get_model_names(get_facemodels) + face_model = gr.Dropdown( + choices=face_models, + label="Choose Face Model", + value="None", + scale=1, + ) + fm_update = gr.Button( + value="🔄", + variant="tool", + ) + fm_update.click( + update_fm_list, + inputs=[face_model], + outputs=[face_model], + ) + imgs_hash_clear = gr.Button( + value="Clear Source Images Hash", + scale=1, + visible=False, + ) + imgs_hash_clear.click(clear_faces_list,None,[progressbar_area]) + gr.Markdown("
", visible=show_br) + with gr.Column(visible=True) as control_col_1: + gr.Markdown("
🔽🔽🔽 Single Image has priority when both Areas in use 🔽🔽🔽
") + with gr.Row(): + img = gr.Image( + type="pil", + label="Single Source Image", + ) + imgs = gr.Files( + label=f"Multiple Source Images{msgs['extra_multiple_source']}", + file_types=["image"], + ) + with gr.Column(visible=False) as control_col_3: + gr.Markdown("Clear Hash if you see the previous face was swapped instead of the new one") + source_folder = gr.Textbox( + value="", + placeholder="Paste here the path to the folder containing source faces images", + label=f"Source Folder{msgs['extra_multiple_source']}", + ) + setattr(face_model, "do_not_save_to_config", True) + if is_img2img: + save_original = gr.Checkbox( + False, + label="Save Original (Swap in generated only)", + info="Save the original image(s) made before swapping (it always saves Original when you use Multiple Images or Folder)" + ) + else: + save_original = gr.Checkbox( + False, + label="Save Original", + info="Save the original image(s) made before swapping (it always saves Original when you use Multiple Images or Folder)", + visible=show_br + ) + # imgs.upload(on_files_upload_uncheck_so,[save_original],[save_original],show_progress=False) + # imgs.clear(on_files_clear,None,[save_original],show_progress=False) + imgs.clear(clear_faces_list,None,None,show_progress=False) + mask_face = gr.Checkbox( + False, + label="Face Mask Correction", + info="Apply this option if you see some pixelation around face contours" + ) + gr.Markdown("
", visible=show_br) + gr.Markdown("Source Image (above):") + with gr.Row(): + source_faces_index = gr.Textbox( + value="0", + placeholder="Which face(s) to use as Source (comma separated)", + label="Comma separated face number(s); Example: 0,2,1", + ) + gender_source = gr.Radio( + ["No", "Female Only", "Male Only"], + value="No", + label="Gender Detection (Source)", + type="index", + ) + gr.Markdown("
", visible=show_br) + gr.Markdown("Target Image (result):") + with gr.Row(): + faces_index = gr.Textbox( + value="0", + placeholder="Which face(s) to Swap into Target (comma separated)", + label="Comma separated face number(s); Example: 1,0,2", + ) + gender_target = gr.Radio( + ["No", "Female Only", "Male Only"], + value="No", + label="Gender Detection (Target)", + type="index", + ) + gr.Markdown("
", visible=show_br) + with gr.Row(): + face_restorer_name = gr.Radio( + label="Restore Face", + choices=["None"] + [x.name() for x in shared.face_restorers], + value=shared.face_restorers[0].name(), + type="value", + ) + with gr.Column(): + face_restorer_visibility = gr.Slider( + 0, 1, 1, step=0.1, label="Restore Face Visibility" + ) + codeformer_weight = gr.Slider( + 0, 1, 0.5, step=0.1, label="CodeFormer Weight", info="0 = maximum effect, 1 = minimum effect" + ) + gr.Markdown("
", visible=show_br) + swap_in_source = gr.Checkbox( + False, + label="Swap in source image", + visible=is_img2img, + ) + swap_in_generated = gr.Checkbox( + True, + label="Swap in generated image", + visible=is_img2img, + ) + select_source.select(on_select_source,[save_original],[control_col_1,control_col_2,control_col_3,save_original,imgs_hash_clear],show_progress=False) + + return 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 diff --git a/reactor_ui/reactor_settings_ui.py b/reactor_ui/reactor_settings_ui.py new file mode 100644 index 0000000..a5447a5 --- /dev/null +++ b/reactor_ui/reactor_settings_ui.py @@ -0,0 +1,77 @@ +import gradio as gr +from scripts.reactor_logger import logger +from scripts.reactor_helpers import get_models, set_Device +from scripts.reactor_globals import DEVICE, DEVICE_LIST +try: + import torch.cuda as cuda + EP_is_visible = True if cuda.is_available() else False +except: + EP_is_visible = False + +def update_models_list(selected: str): + return gr.Dropdown.update( + value=selected, choices=get_models() + ) + +def show(hash_check_block: bool = True): + # TAB SETTINGS + with gr.Tab("Settings"): + models = get_models() + with gr.Row(visible=EP_is_visible): + device = gr.Radio( + label="Execution Provider", + choices=DEVICE_LIST, + value=DEVICE, + type="value", + info="If you already run 'Generate' - RESTART is required to apply. Click 'Save', (A1111) Extensions Tab -> 'Apply and restart UI' or (SD.Next) close the Server and start it again", + scale=2, + ) + save_device_btn = gr.Button("Save", scale=0) + save = gr.Markdown("", visible=EP_is_visible) + setattr(device, "do_not_save_to_config", True) + save_device_btn.click( + set_Device, + inputs=[device], + outputs=[save], + ) + with gr.Row(): + if len(models) == 0: + logger.warning( + "You should at least have one model in models directory, please read the doc here: https://github.com/Gourieff/sd-webui-reactor/" + ) + model = gr.Dropdown( + choices=models, + label="Model not found, please download one and refresh the list" + ) + else: + model = gr.Dropdown( + choices=models, label="Model", value=models[0] + ) + models_update = gr.Button( + value="🔄", + variant="tool", + ) + models_update.click( + update_models_list, + inputs=[model], + outputs=[model], + ) + console_logging_level = gr.Radio( + ["No log", "Minimum", "Default"], + value="Minimum", + label="Console Log Level", + type="index" + ) + gr.Markdown("
", visible=hash_check_block) + with gr.Row(visible=hash_check_block): + source_hash_check = gr.Checkbox( + True, + label="Source Image Hash Check", + info="Recommended to keep it ON. Processing is faster when Source Image is the same." + ) + target_hash_check = gr.Checkbox( + False, + label="Target Image Hash Check", + info="Affects if you use Extras tab or img2img with only 'Swap in source image' on." + ) + return model, device, console_logging_level, source_hash_check, target_hash_check \ No newline at end of file diff --git a/reactor_ui/reactor_tools_ui.py b/reactor_ui/reactor_tools_ui.py new file mode 100644 index 0000000..0abcb0e --- /dev/null +++ b/reactor_ui/reactor_tools_ui.py @@ -0,0 +1,25 @@ +import gradio as gr +from scripts.reactor_swapper import build_face_model + +# 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", + ) + 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/reactor_ui/reactor_upscale_ui.py b/reactor_ui/reactor_upscale_ui.py new file mode 100644 index 0000000..71216a4 --- /dev/null +++ b/reactor_ui/reactor_upscale_ui.py @@ -0,0 +1,39 @@ +import gradio as gr +from modules import shared + +def update_upscalers_list(selected: str): + return gr.Dropdown.update( + value=selected, choices=[upscaler.name for upscaler in shared.sd_upscalers] + ) + +# TAB UPSCALE +def show(show_br: bool = True): + with gr.Tab("Upscale"): + restore_first = gr.Checkbox( + True, + label="1. Restore Face -> 2. Upscale (-Uncheck- if you want vice versa)", + info="Postprocessing Order" + ) + with gr.Row(): + upscaler_name = gr.Dropdown( + choices=[upscaler.name for upscaler in shared.sd_upscalers], + label="Upscaler", + value="None", + info="Won't scale if you choose -Swap in Source- via img2img, only 1x-postprocessing will affect (texturing, denoising, restyling etc.)" + ) + upscalers_update = gr.Button( + value="🔄", + variant="tool", + ) + upscalers_update.click( + update_upscalers_list, + inputs=[upscaler_name], + outputs=[upscaler_name], + ) + gr.Markdown("
", visible=show_br) + with gr.Row(): + upscaler_scale = gr.Slider(1, 8, 1, step=0.1, label="Scale by") + upscaler_visibility = gr.Slider( + 0, 1, 1, step=0.1, label="Upscaler Visibility (if scale = 1)" + ) + return restore_first, upscaler_name, upscaler_scale, upscaler_visibility \ No newline at end of file diff --git a/scripts/reactor_api.py b/scripts/reactor_api.py index b5df7fa..2fbd5b5 100644 --- a/scripts/reactor_api.py +++ b/scripts/reactor_api.py @@ -73,8 +73,9 @@ def reactor_api(_: gr.Blocks, app: FastAPI): result_file_path: str = Body("",title="(if 'save_to_file = 1') Result file path"), device: str = Body("CPU",title="CPU or CUDA (if you have it)"), mask_face: int = Body(0,title="Face Mask Correction, 1 - True, 0 - False"), - select_source: int = Body(0,title="Select Source, 0 - Image, 1 - Face Model"), - face_model: str = Body("None",title="Filename of the face model (from 'models/reactor/faces'), e.g. elena.safetensors") + select_source: int = Body(0,title="Select Source, 0 - Image, 1 - Face Model, 2 - Source Folder"), + 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") ): s_image = api.decode_base64_to_image(source_image) t_image = api.decode_base64_to_image(target_image) @@ -88,7 +89,7 @@ def reactor_api(_: gr.Blocks, app: FastAPI): 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) + 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) 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 623e275..8812a4f 100644 --- a/scripts/reactor_faceswap.py +++ b/scripts/reactor_faceswap.py @@ -1,11 +1,6 @@ import os, glob import gradio as gr from PIL import Image -try: - import torch.cuda as cuda - EP_is_visible = True if cuda.is_available() else False -except: - EP_is_visible = False from typing import List @@ -19,43 +14,22 @@ from modules.processing import ( ) from modules.face_restoration import FaceRestoration from modules.images import save_image -try: - from modules.paths_internal import models_path -except: - try: - from modules.paths import models_path - except: - model_path = os.path.abspath("models") +from reactor_ui import ui_main, ui_upscale, ui_tools, ui_settings from scripts.reactor_logger import logger from scripts.reactor_swapper import ( EnhancementOptions, swap_face, check_process_halt, - reset_messaged, - build_face_model + 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, get_image_path, set_Device, get_model_names, get_facemodels -from scripts.reactor_globals import DEVICE, DEVICE_LIST - - -MODELS_PATH = None - -def get_models(): - global MODELS_PATH - models_path_init = os.path.join(models_path, "insightface/*") - models = glob.glob(models_path_init) - models = [x for x in models if x.endswith(".onnx") or x.endswith(".pth")] - models_names = [] - for model in models: - model_path = os.path.split(model) - if MODELS_PATH is None: - MODELS_PATH = model_path[0] - model_name = model_path[1] - models_names.append(model_name) - return models_names +from scripts.reactor_helpers import ( + make_grid, + set_Device, +) +from scripts.reactor_globals import SWAPPER_MODELS_PATH #, DEVICE, DEVICE_LIST class FaceSwapScript(scripts.Script): @@ -68,237 +42,33 @@ class FaceSwapScript(scripts.Script): def ui(self, is_img2img): with gr.Accordion(f"{app_title}", open=False): - def update_fm_list(selected: str): - return gr.Dropdown.update( - value=selected, choices=get_model_names(get_facemodels) - ) - def update_upscalers_list(selected: str): - return gr.Dropdown.update( - value=selected, choices=[upscaler.name for upscaler in shared.sd_upscalers] - ) - def update_models_list(selected: str): - return gr.Dropdown.update( - value=selected, choices=get_models() - ) + # def on_files_upload_uncheck_so(selected: bool): + # global SAVE_ORIGINAL + # SAVE_ORIGINAL = selected + # return gr.Checkbox.update(value=False,visible=False) + # def on_files_clear(): + # clear_faces_list() + # return gr.Checkbox.update(value=SAVE_ORIGINAL,visible=True) + enable = gr.Checkbox(False, label="Enable", info=f"The Fast and Simple FaceSwap Extension - {version_flag}") + gr.Markdown("
") + # TAB MAIN - with gr.Tab("Main"): - with gr.Column(): - img = gr.Image( - type="pil", - label="Source Image", - ) - # face_model = gr.File( - # file_types=[".safetensors"], - # label="Face Model", - # show_label=True, - # ) - enable = gr.Checkbox(False, label="Enable", info=f"The Fast and Simple FaceSwap Extension - {version_flag}") - gr.Markdown("
") - with gr.Row(): - select_source = gr.Radio( - ["Image","Face Model"], - value="Image", - label="Select Source", - type="index", - scale=1, - ) - face_models = get_model_names(get_facemodels) - face_model = gr.Dropdown( - choices=face_models, - label="Choose Face Model", - value="None", - scale=2, - ) - fm_update = gr.Button( - value="🔄", - variant="tool", - ) - fm_update.click( - update_fm_list, - inputs=[face_model], - outputs=[face_model], - ) - setattr(face_model, "do_not_save_to_config", True) - 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="Face Mask Correction", - info="Apply this option if you see some pixelation around face contours" - ) - gr.Markdown("
") - gr.Markdown("Source Image (above):") - with gr.Row(): - source_faces_index = gr.Textbox( - value="0", - placeholder="Which face(s) to use as Source (comma separated)", - label="Comma separated face number(s); Example: 0,2,1", - ) - gender_source = gr.Radio( - ["No", "Female Only", "Male Only"], - value="No", - label="Gender Detection (Source)", - type="index", - ) - gr.Markdown("
") - gr.Markdown("Target Image (result):") - with gr.Row(): - faces_index = gr.Textbox( - value="0", - placeholder="Which face(s) to Swap into Target (comma separated)", - label="Comma separated face number(s); Example: 1,0,2", - ) - gender_target = gr.Radio( - ["No", "Female Only", "Male Only"], - value="No", - label="Gender Detection (Target)", - type="index", - ) - gr.Markdown("
") - with gr.Row(): - face_restorer_name = gr.Radio( - label="Restore Face", - choices=["None"] + [x.name() for x in shared.face_restorers], - value=shared.face_restorers[0].name(), - type="value", - ) - with gr.Column(): - face_restorer_visibility = gr.Slider( - 0, 1, 1, step=0.1, label="Restore Face Visibility" - ) - codeformer_weight = gr.Slider( - 0, 1, 0.5, step=0.1, label="CodeFormer Weight", info="0 = maximum effect, 1 = minimum effect" - ) - gr.Markdown("
") - swap_in_source = gr.Checkbox( - False, - label="Swap in source image", - visible=is_img2img, - ) - swap_in_generated = gr.Checkbox( - True, - label="Swap in generated image", - visible=is_img2img, - ) + msgs: dict = { + "extra_multiple_source": "", + } + 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 = ui_main.show(is_img2img=is_img2img, **msgs) # TAB UPSCALE - with gr.Tab("Upscale"): - restore_first = gr.Checkbox( - True, - label="1. Restore Face -> 2. Upscale (-Uncheck- if you want vice versa)", - info="Postprocessing Order" - ) - with gr.Row(): - upscaler_name = gr.Dropdown( - choices=[upscaler.name for upscaler in shared.sd_upscalers], - label="Upscaler", - value="None", - info="Won't scale if you choose -Swap in Source- via img2img, only 1x-postprocessing will affect (texturing, denoising, restyling etc.)" - ) - upscalers_update = gr.Button( - value="🔄", - variant="tool", - ) - upscalers_update.click( - update_upscalers_list, - inputs=[upscaler_name], - outputs=[upscaler_name], - ) - gr.Markdown("
") - with gr.Row(): - upscaler_scale = gr.Slider(1, 8, 1, step=0.1, label="Scale by") - upscaler_visibility = gr.Slider( - 0, 1, 1, step=0.1, label="Upscaler Visibility (if scale = 1)" - ) - + restore_first, upscaler_name, upscaler_scale, upscaler_visibility = ui_upscale.show() + # TAB TOOLS - 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", - ) - 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], - ) + ui_tools.show() # TAB SETTINGS - with gr.Tab("Settings"): - models = get_models() - with gr.Row(visible=EP_is_visible): - device = gr.Radio( - label="Execution Provider", - choices=DEVICE_LIST, - value=DEVICE, - type="value", - info="If you already run 'Generate' - RESTART is required to apply. Click 'Save', (A1111) Extensions Tab -> 'Apply and restart UI' or (SD.Next) close the Server and start it again", - scale=2, - ) - save_device_btn = gr.Button("Save", scale=0) - save = gr.Markdown("", visible=EP_is_visible) - setattr(device, "do_not_save_to_config", True) - save_device_btn.click( - set_Device, - inputs=[device], - outputs=[save], - ) - with gr.Row(): - if len(models) == 0: - logger.warning( - "You should at least have one model in models directory, please read the doc here: https://github.com/Gourieff/sd-webui-reactor/" - ) - model = gr.Dropdown( - choices=models, - label="Model not found, please download one and refresh the list" - ) - else: - model = gr.Dropdown( - choices=models, label="Model", value=models[0] - ) - models_update = gr.Button( - value="🔄", - variant="tool", - ) - models_update.click( - update_models_list, - inputs=[model], - outputs=[model], - ) - console_logging_level = gr.Radio( - ["No log", "Minimum", "Default"], - value="Minimum", - label="Console Log Level", - type="index" - ) - gr.Markdown("
") - with gr.Row(): - source_hash_check = gr.Checkbox( - True, - label="Source Image Hash Check", - info="Recommended to keep it ON. Processing is faster when Source Image is the same." - ) - target_hash_check = gr.Checkbox( - False, - label="Target Image Hash Check", - info="Affects if you use Extras tab or img2img with only 'Swap in source image' on." - ) + model, device, console_logging_level, source_hash_check, target_hash_check = ui_settings.show() - gr.Markdown("by Eugene Gourieff") + gr.Markdown("by
Eugene Gourieff") return [ img, @@ -325,6 +95,8 @@ class FaceSwapScript(scripts.Script): mask_face, select_source, face_model, + source_folder, + imgs, ] @@ -381,6 +153,8 @@ class FaceSwapScript(scripts.Script): mask_face, select_source, face_model, + source_folder, + imgs, ): self.enable = enable if self.enable: @@ -391,7 +165,7 @@ class FaceSwapScript(scripts.Script): if check_process_halt(): return - global MODELS_PATH + global SWAPPER_MODELS_PATH self.source = img self.face_restorer_name = face_restorer_name self.upscaler_scale = upscaler_scale @@ -401,7 +175,7 @@ class FaceSwapScript(scripts.Script): self.upscaler_name = upscaler_name self.swap_in_source = swap_in_source self.swap_in_generated = swap_in_generated - self.model = os.path.join(MODELS_PATH,model) + self.model = os.path.join(SWAPPER_MODELS_PATH,model) self.console_logging_level = console_logging_level self.gender_source = gender_source self.gender_target = gender_target @@ -413,6 +187,8 @@ class FaceSwapScript(scripts.Script): self.mask_face = mask_face self.select_source = select_source self.face_model = face_model + self.source_folder = source_folder + self.source_imgs = imgs 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": @@ -439,7 +215,7 @@ class FaceSwapScript(scripts.Script): logger.debug("*** Set Device") set_Device(self.device) - 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): + if ((self.source is not None or self.source_imgs 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) or ((self.source_folder is not None and self.source_folder != "") and self.select_source == 2): logger.debug("*** Log patch") apply_logging_patch(console_logging_level) if isinstance(p, StableDiffusionProcessingImg2Img) and self.swap_in_source: @@ -463,6 +239,8 @@ class FaceSwapScript(scripts.Script): mask_face=self.mask_face, select_source=self.select_source, face_model = self.face_model, + source_folder = None, + source_imgs = None, ) 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") @@ -486,7 +264,7 @@ class FaceSwapScript(scripts.Script): if check_process_halt(): return - if self.save_original: + if self.save_original or ((self.select_source == 2 and self.source_folder is not None and self.source_folder != "") or (self.select_source == 0 and self.source_imgs is not None and self.source is None)): postprocess_run: bool = True @@ -497,8 +275,13 @@ class FaceSwapScript(scripts.Script): # result_info: List = processed.infotexts if self.swap_in_generated: + logger.status("Working: source face index %s, target face index %s", self.source_faces_index, self.faces_index) - # if self.source is not None: + + if self.source is not None: + # self.source_folder = None + self.source_imgs = None + for i,(img,info) in enumerate(zip(orig_images, orig_infotexts)): if check_process_halt(): postprocess_run = False @@ -520,16 +303,32 @@ class FaceSwapScript(scripts.Script): mask_face=self.mask_face, select_source=self.select_source, face_model = self.face_model, + source_folder = self.source_folder, + source_imgs = self.source_imgs, ) - if result is not None and swapped > 0: - result_images.append(result) - suffix = "-swapped" - 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)") - elif result is None: - logger.error("Cannot create a result image") + + 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: + result_images.extend(result) + suffix = "-swapped" + for i,x in enumerate(result): + try: + img_path = save_image(result[i], 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)") + elif len(result) == 0: + logger.error("Cannot create a result image") + + else: + if result is not None and swapped > 0: + result_images.append(result) + suffix = "-swapped" + 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)") + elif result is None: + logger.error("Cannot create a result image") # if len(output) != 0: # split_fullfn = os.path.splitext(img_path[0]) @@ -554,7 +353,7 @@ class FaceSwapScript(scripts.Script): 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 and ((self.select_source == 0 and self.source is not None) or self.select_source == 1): logger.debug("*** Check postprocess_image") @@ -583,6 +382,8 @@ class FaceSwapScript(scripts.Script): mask_face=self.mask_face, select_source=self.select_source, face_model = self.face_model, + source_folder = None, + source_imgs = None, ) try: pp = scripts_postprocessing.PostprocessedImage(result) @@ -606,197 +407,24 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing): def ui(self): with gr.Accordion(f"{app_title}", open=False): - def update_fm_list(selected: str): - return gr.Dropdown.update( - value=selected, choices=get_model_names(get_facemodels) - ) - def update_upscalers_list(selected: str): - return gr.Dropdown.update( - value=selected, choices=[upscaler.name for upscaler in shared.sd_upscalers] - ) - def update_models_list(selected: str): - return gr.Dropdown.update( - value=selected, choices=get_models() - ) - - # TAB MAIN - with gr.Tab("Main"): - with gr.Column(): - img = gr.Image(type="pil") - enable = gr.Checkbox(False, label="Enable", info=f"The Fast and Simple FaceSwap Extension - {version_flag}") - # gr.Markdown("
") - with gr.Row(): - select_source = gr.Radio( - ["Image","Face Model"], - value="Image", - label="Select Source", - type="index", - scale=1, - ) - face_models = get_model_names(get_facemodels) - face_model = gr.Dropdown( - choices=face_models, - label="Choose Face Model", - value="None", - scale=2, - ) - fm_update = gr.Button( - value="🔄", - variant="tool", - ) - fm_update.click( - update_fm_list, - inputs=[face_model], - outputs=[face_model], - ) - setattr(face_model, "do_not_save_to_config", True) - mask_face = gr.Checkbox( - False, - label="Face Mask Correction", - info="Apply this option if you see some pixelation around face contours" - ) - gr.Markdown("Source Image (above):") - with gr.Row(): - source_faces_index = gr.Textbox( - value="0", - placeholder="Which face(s) to use as Source (comma separated)", - label="Comma separated face number(s); Example: 0,2,1", - ) - gender_source = gr.Radio( - ["No", "Female Only", "Male Only"], - value="No", - label="Gender Detection (Source)", - type="index", - ) - gr.Markdown("Target Image (result):") - with gr.Row(): - faces_index = gr.Textbox( - value="0", - placeholder="Which face(s) to Swap into Target (comma separated)", - label="Comma separated face number(s); Example: 1,0,2", - ) - gender_target = gr.Radio( - ["No", "Female Only", "Male Only"], - value="No", - label="Gender Detection (Target)", - type="index", - ) - with gr.Row(): - face_restorer_name = gr.Radio( - label="Restore Face", - choices=["None"] + [x.name() for x in shared.face_restorers], - value=shared.face_restorers[0].name(), - type="value", - ) - with gr.Column(): - face_restorer_visibility = gr.Slider( - 0, 1, 1, step=0.1, label="Restore Face Visibility" - ) - codeformer_weight = gr.Slider( - 0, 1, 0.5, step=0.1, label="CodeFormer Weight", info="0 = maximum effect, 1 = minimum effect" - ) + enable = gr.Checkbox(False, label="Enable", info=f"The Fast and Simple FaceSwap Extension - {version_flag}") + # TAB MAIN + msgs: dict = { + "extra_multiple_source": " | Сomparison grid as a result", + } + 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 = ui_main.show(is_img2img=False, show_br=False, **msgs) + # TAB UPSCALE - with gr.Tab("Upscale"): - restore_first = gr.Checkbox( - True, - label="1. Restore Face -> 2. Upscale (-Uncheck- if you want vice versa)", - info="Postprocessing Order" - ) - with gr.Row(): - upscaler_name = gr.Dropdown( - choices=[upscaler.name for upscaler in shared.sd_upscalers], - label="Upscaler", - value="None", - info="Won't scale if you choose -Swap in Source- via img2img, only 1x-postprocessing will affect (texturing, denoising, restyling etc.)" - ) - upscalers_update = gr.Button( - value="🔄", - variant="tool", - ) - upscalers_update.click( - update_upscalers_list, - inputs=[upscaler_name], - outputs=[upscaler_name], - ) - with gr.Row(): - upscaler_scale = gr.Slider(1, 8, 1, step=0.1, label="Scale by") - upscaler_visibility = gr.Slider( - 0, 1, 1, step=0.1, label="Upscaler Visibility (if scale = 1)" - ) - + restore_first, upscaler_name, upscaler_scale, upscaler_visibility = ui_upscale.show(show_br=False) + # TAB TOOLS - 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", - ) - 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], - ) - + ui_tools.show() + # TAB SETTINGS - with gr.Tab("Settings"): - models = get_models() - with gr.Row(visible=EP_is_visible): - device = gr.Radio( - label="Execution Provider", - choices=DEVICE_LIST, - value=DEVICE, - type="value", - info="If you already run 'Generate' - RESTART is required to apply. Click 'Save', (A1111) Extensions Tab -> 'Apply and restart UI' or (SD.Next) close the Server and start it again", - scale=2, - ) - save_device_btn = gr.Button("Save", scale=0) - save = gr.Markdown("", visible=EP_is_visible) - setattr(device, "do_not_save_to_config", True) - save_device_btn.click( - set_Device, - inputs=[device], - outputs=[save], - ) - with gr.Row(): - if len(models) == 0: - logger.warning( - "You should at least have one model in models directory, please read the doc here: https://github.com/Gourieff/sd-webui-reactor/" - ) - model = gr.Dropdown( - choices=models, - label="Model not found, please download one and refresh the list", - ) - else: - model = gr.Dropdown( - choices=models, label="Model", value=models[0] - ) - models_update = gr.Button( - value="🔄", - variant="tool", - ) - models_update.click( - update_models_list, - inputs=[model], - outputs=[model], - ) - console_logging_level = gr.Radio( - ["No log", "Minimum", "Default"], - value="Minimum", - label="Console Log Level", - type="index", - ) - - gr.Markdown("by Eugene Gourieff") + model, device, console_logging_level, source_hash_check, target_hash_check = ui_settings.show(hash_check_block=False) + + gr.Markdown("by Eugene Gourieff") args = { 'img': img, @@ -818,6 +446,8 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing): 'mask_face': mask_face, 'select_source': select_source, 'face_model': face_model, + 'source_folder': source_folder, + 'imgs': imgs, } return args @@ -853,7 +483,7 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing): if check_process_halt(): return - global MODELS_PATH + global SWAPPER_MODELS_PATH self.source = args['img'] self.face_restorer_name = args['face_restorer_name'] self.upscaler_scale = args['upscaler_scale'] @@ -861,7 +491,7 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing): self.face_restorer_visibility = args['face_restorer_visibility'] self.restore_first = args['restore_first'] self.upscaler_name = args['upscaler_name'] - self.model = os.path.join(MODELS_PATH, args['model']) + self.model = os.path.join(SWAPPER_MODELS_PATH, args['model']) self.console_logging_level = args['console_logging_level'] self.gender_source = args['gender_source'] self.gender_target = args['gender_target'] @@ -870,6 +500,8 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing): self.mask_face = args['mask_face'] self.select_source = args['select_source'] self.face_model = args['face_model'] + self.source_folder = args['source_folder'] + self.source_imgs = args['imgs'] 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": @@ -893,10 +525,16 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing): reset_messaged() set_Device(self.device) + + logger.debug("We're here: process() 1") - 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): + 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) or ((self.source_folder is not None and self.source_folder != "") and self.select_source == 2) or ((self.source_imgs is not None and self.source is None) and self.select_source == 0): + + logger.debug("We're here: process() 2") + 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) + # if self.select_source != 2: image: Image.Image = pp.image result, output, swapped = swap_face( self.source, @@ -913,12 +551,27 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing): mask_face=self.mask_face, select_source=self.select_source, face_model=self.face_model, + source_folder=self.source_folder, + source_imgs=self.source_imgs, ) - try: - pp.info["ReActor"] = True - pp.image = result - logger.status("---Done!---") - except Exception: - logger.error("Cannot create a result image") + 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: + image = result[0] + if len(result) > 1: + grid = make_grid(result) + result.insert(0, grid) + image = grid + pp.info["ReActor"] = True + pp.image = image + logger.status("---Done!---") + else: + logger.error("Cannot create a result image") + else: + try: + pp.info["ReActor"] = True + pp.image = result + logger.status("---Done!---") + except Exception: + logger.error("Cannot create a result image") else: logger.error("Please provide a source face") diff --git a/scripts/reactor_globals.py b/scripts/reactor_globals.py index f828254..7e8f4dc 100644 --- a/scripts/reactor_globals.py +++ b/scripts/reactor_globals.py @@ -14,7 +14,8 @@ BASE_PATH = os.path.join(Path(__file__).parents[1]) DEVICE_LIST: list = ["CPU", "CUDA"] MODELS_PATH = models_path -REACTOR_MODELS_PATH = os.path.join(models_path, "reactor") +SWAPPER_MODELS_PATH = os.path.join(MODELS_PATH, "insightface") +REACTOR_MODELS_PATH = os.path.join(MODELS_PATH, "reactor") FACE_MODELS_PATH = os.path.join(REACTOR_MODELS_PATH, "faces") if not os.path.exists(REACTOR_MODELS_PATH): diff --git a/scripts/reactor_helpers.py b/scripts/reactor_helpers.py index cfce05f..2db883d 100644 --- a/scripts/reactor_helpers.py +++ b/scripts/reactor_helpers.py @@ -13,6 +13,16 @@ from modules.images import FilenameGenerator, get_next_sequence_number from modules import shared, script_callbacks from scripts.reactor_globals import DEVICE, BASE_PATH, FACE_MODELS_PATH +try: + from modules.paths_internal import models_path +except: + try: + from modules.paths import models_path + except: + model_path = os.path.abspath("models") + +MODELS_PATH = None + def set_Device(value): global DEVICE DEVICE = value @@ -155,6 +165,20 @@ def save_face_model(face: Face, filename: str) -> None: except Exception as e: print(f"Error: {e}") +def get_models(): + global MODELS_PATH + models_path_init = os.path.join(models_path, "insightface/*") + models = glob.glob(models_path_init) + models = [x for x in models if x.endswith(".onnx") or x.endswith(".pth")] + models_names = [] + for model in models: + model_path = os.path.split(model) + if MODELS_PATH is None: + MODELS_PATH = model_path[0] + model_name = model_path[1] + models_names.append(model_name) + return models_names + def load_face_model(filename: str): face = {} model_path = os.path.join(FACE_MODELS_PATH, filename) @@ -175,3 +199,11 @@ def get_model_names(get_models): for x in models: names.append(os.path.basename(x)) return names + +def get_images_from_folder(path: str): + images_path = os.path.join(path, "*") + images = glob.glob(images_path) + return [Image.open(x) for x in images if x.endswith(('jpg', 'png', 'jpeg', 'webp', 'bmp'))] + +def get_images_from_list(imgs: List): + return [Image.open(os.path.abspath(x.name)) for x in imgs] diff --git a/scripts/reactor_swapper.py b/scripts/reactor_swapper.py index 69b1e62..75f44af 100644 --- a/scripts/reactor_swapper.py +++ b/scripts/reactor_swapper.py @@ -11,7 +11,14 @@ import insightface from insightface.app.common import Face from scripts.reactor_globals import FACE_MODELS_PATH -from scripts.reactor_helpers import get_image_md5hash, get_Device, save_face_model, load_face_model +from scripts.reactor_helpers import ( + get_image_md5hash, + get_Device, + save_face_model, + load_face_model, + get_images_from_folder, + get_images_from_list +) from scripts.console_log_patch import apply_logging_patch from modules.face_restoration import FaceRestoration @@ -22,7 +29,7 @@ except: # SD.Next from modules.upscaler import UpscalerData from modules.shared import state from scripts.reactor_logger import logger -from modules.reactor_mask import apply_face_mask +from reactor_modules.reactor_mask import apply_face_mask try: from modules.paths_internal import models_path @@ -92,6 +99,14 @@ SOURCE_FACES = None SOURCE_IMAGE_HASH = None TARGET_FACES = None TARGET_IMAGE_HASH = None +SOURCE_FACES_LIST = [] +SOURCE_IMAGE_LIST_HASH = [] + +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 getAnalysisModel(): @@ -315,8 +330,11 @@ def swap_face( mask_face: bool = False, select_source: int = 0, face_model: str = "None", + source_folder: str = "", + source_imgs: Union[List, None] = None, ): - global SOURCE_FACES, SOURCE_IMAGE_HASH, TARGET_FACES, TARGET_IMAGE_HASH, PROVIDERS + global SOURCE_FACES, SOURCE_IMAGE_HASH, TARGET_FACES, TARGET_IMAGE_HASH, PROVIDERS, SOURCE_FACES_LIST, SOURCE_IMAGE_LIST_HASH + result_image = target_img PROVIDERS = ["CUDAExecutionProvider"] if device == "CUDA" else ["CPUExecutionProvider"] @@ -347,179 +365,229 @@ def swap_face( output: List = [] output_info: str = "" swapped = 0 + + # ***************** + # SWAP from FOLDER or MULTIPLE images: - if select_source == 0 and source_img is not None: + if (select_source == 0 and source_imgs is not None) or (select_source == 2 and (source_folder is not None and source_folder != "")): + + result = [] + + source_images = get_images_from_folder(source_folder) if select_source == 2 else get_images_from_list(source_imgs) + + if len(source_images) > 0: + source_img_ff = [] + source_faces_ff = [] + for i, source_image in enumerate(source_images): + + source_image = cv2.cvtColor(np.array(source_image), cv2.COLOR_RGB2BGR) + source_img_ff.append(source_image) + + if source_hash_check: + + source_image_md5hash = get_image_md5hash(source_image) + + if len(SOURCE_IMAGE_LIST_HASH) == 0: + SOURCE_IMAGE_LIST_HASH = [source_image_md5hash] + source_image_same = False + elif len(SOURCE_IMAGE_LIST_HASH) == i: + SOURCE_IMAGE_LIST_HASH.append(source_image_md5hash) + source_image_same = False + else: + source_image_same = True if SOURCE_IMAGE_LIST_HASH[i] == source_image_md5hash else False + if not source_image_same: + SOURCE_IMAGE_LIST_HASH[i] = source_image_md5hash + + logger.info("(Image %s) Source Image MD5 Hash = %s", i, SOURCE_IMAGE_LIST_HASH[i]) + logger.info("(Image %s) Source Image the Same? %s", i, source_image_same) + + if len(SOURCE_FACES_LIST) == 0: + logger.status(f"Analyzing Source Image {i}...") + source_faces = analyze_faces(source_image) + SOURCE_FACES_LIST = [source_faces] + elif len(SOURCE_FACES_LIST) == i and not source_image_same: + logger.status(f"Analyzing Source Image {i}...") + source_faces = analyze_faces(source_image) + 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_faces = analyze_faces(source_image) + SOURCE_FACES_LIST[i] = source_faces + elif source_image_same: + logger.status("(Image %s) Using Hashed Source Face(s) Model...", i) + source_faces = SOURCE_FACES_LIST[i] + + else: + logger.status(f"Analyzing Source Image {i}...") + source_faces = analyze_faces(source_image) + + if source_faces is not None: + source_faces_ff.append(source_faces) - source_img = cv2.cvtColor(np.array(source_img), cv2.COLOR_RGB2BGR) + if len(source_faces_ff) > 0: - if source_hash_check: - - source_image_md5hash = get_image_md5hash(source_img) - - if SOURCE_IMAGE_HASH is None: - SOURCE_IMAGE_HASH = source_image_md5hash - source_image_same = False - else: - source_image_same = True if SOURCE_IMAGE_HASH == source_image_md5hash else False - if not source_image_same: - SOURCE_IMAGE_HASH = source_image_md5hash - - logger.info("Source Image MD5 Hash = %s", SOURCE_IMAGE_HASH) - logger.info("Source Image the Same? %s", source_image_same) - - if SOURCE_FACES is None or not source_image_same: - logger.status("Analyzing Source Image...") - source_faces = analyze_faces(source_img) - SOURCE_FACES = source_faces - elif source_image_same: - logger.status("Using Hashed Source Face(s) Model...") - source_faces = SOURCE_FACES - - else: - logger.status("Analyzing Source Image...") - source_faces = analyze_faces(source_img) - - elif select_source == 1 and (face_model is not None and face_model != "None"): - source_face_model = [load_face_model(face_model)] - if source_face_model is not None: - source_faces_index = [0] - source_faces = source_face_model - logger.status("Using Loaded Source Face Model...") - else: - logger.error(f"Cannot load Face Model File: {face_model}.safetensors") - else: - logger.error("Cannot detect any Source") - - if source_faces is not None: - - if target_hash_check: - - target_image_md5hash = get_image_md5hash(target_img) - - if TARGET_IMAGE_HASH is None: - TARGET_IMAGE_HASH = target_image_md5hash - target_image_same = False - else: - target_image_same = True if TARGET_IMAGE_HASH == target_image_md5hash else False - if not target_image_same: - TARGET_IMAGE_HASH = target_image_md5hash - - logger.info("Target Image MD5 Hash = %s", TARGET_IMAGE_HASH) - logger.info("Target Image the Same? %s", target_image_same) + if target_hash_check: - if TARGET_FACES is None or not target_image_same: + target_image_md5hash = get_image_md5hash(target_img) + + if TARGET_IMAGE_HASH is None: + TARGET_IMAGE_HASH = target_image_md5hash + target_image_same = False + else: + target_image_same = True if TARGET_IMAGE_HASH == target_image_md5hash else False + if not target_image_same: + TARGET_IMAGE_HASH = target_image_md5hash + + logger.info("Target Image MD5 Hash = %s", TARGET_IMAGE_HASH) + logger.info("Target Image the Same? %s", target_image_same) + + if TARGET_FACES is None or not target_image_same: + logger.status("Analyzing Target Image...") + target_faces = analyze_faces(target_img) + TARGET_FACES = target_faces + elif target_image_same: + logger.status("Using Hashed Target Face(s) Model...") + target_faces = TARGET_FACES + + else: logger.status("Analyzing Target Image...") target_faces = analyze_faces(target_img) - TARGET_FACES = target_faces - elif target_image_same: - logger.status("Using Hashed Target Face(s) Model...") - target_faces = TARGET_FACES - - else: - logger.status("Analyzing Target Image...") - target_faces = analyze_faces(target_img) - 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) - else: - source_face = sorted(source_faces, key=lambda x: x.bbox[0])[source_faces_index[0]] - wrong_gender = 0 - source_age = source_face["age"] - source_gender = "Female" if source_face["gender"] == 0 else "Male" - if source_age != "None" or source_gender != "None": - logger.status("Detected: -%s- y.o. %s", source_age, source_gender) + for i,source_faces in enumerate(source_faces_ff): - output_info = f"SourceFaceIndex={source_faces_index[0]};Age={source_age};Gender={source_gender}\n" - output.append(output_info) - - if len(source_faces_index) != 0 and len(source_faces_index) != 1 and len(source_faces_index) != len(faces_index): - logger.status("Source Faces must have no entries (default=0), one entry, or same number of entries as target faces.") - elif source_face is not None: - - result = target_img - face_swapper = getFaceSwapModel(model) - - source_face_idx = 0 - - for face_num in faces_index: - if check_process_halt(): - 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) - if source_age != "None" or source_gender != "None": - logger.status("Detected: -%s- y.o. %s", source_age, source_gender) - - output_info = f"SourceFaceIndex={source_faces_index[source_face_idx]};Age={source_age};Gender={source_gender}\n" - output.append(output_info) - - source_face_idx += 1 - - 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) - if target_age != "None" or target_gender != "None": - logger.status("Detected: -%s- y.o. %s", target_age, target_gender) - - output_info = f"TargetFaceIndex={face_num};Age={target_age};Gender={target_gender}\n" - output.append(output_info) - - if target_face is not None and wrong_gender == 0: - logger.status("Swapping Source into Target") - swapped_image = face_swapper.get(result, target_face, source_face) - - if mask_face: - result = apply_face_mask(swapped_image=swapped_image,target_image=result,target_face=target_face,entire_mask_image=entire_mask_image) - else: - result = swapped_image - swapped += 1 - - elif wrong_gender == 1: - wrong_gender = 0 - - if source_face_idx == len(source_faces_index): - result_image = Image.fromarray(cv2.cvtColor(result, cv2.COLOR_BGR2RGB)) - - if enhancement_options is not None and len(source_faces_index) > 1: - result_image = enhance_image(result_image, enhancement_options) - - return result_image, output, swapped - - else: - logger.status(f"No target face found for {face_num}") + 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) - elif wrong_gender == 1: - wrong_gender = 0 - - if source_face_idx == len(source_faces_index): - result_image = Image.fromarray(cv2.cvtColor(result, cv2.COLOR_BGR2RGB)) - - if enhancement_options is not None and len(source_faces_index) > 1: - result_image = enhance_image(result_image, enhancement_options) - - return result_image, output, swapped - - else: - logger.status(f"No source face found for face number {source_face_idx}.") + if source_age != "None" or source_gender != "None": + logger.status("(Image %s) Detected: -%s- y.o. %s", i, source_age, source_gender) + + if len(source_faces_index) != 0 and len(source_faces_index) != 1 and len(source_faces_index) != len(faces_index): + logger.status("Source Faces must have no entries (default=0), one entry, or same number of entries as target faces.") + + 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.append(result_image) + + result = [result_image] if len(result) == 0 else result + + return result, output, swapped + + # END + # ***************** + + # *********************** + # SWAP from IMG or MODEL: - result_image = Image.fromarray(cv2.cvtColor(result, cv2.COLOR_BGR2RGB)) - - if enhancement_options is not None and swapped > 0: - if mask_face and entire_mask_image is not None: - result_image = enhance_image_and_mask(result_image, enhancement_options,Image.fromarray(target_img_orig),Image.fromarray(entire_mask_image).convert("L")) - else: - result_image = enhance_image(result_image, enhancement_options) - elif mask_face and entire_mask_image is not None and swapped > 0: - result_image = Image.composite(result_image,Image.fromarray(target_img_orig),Image.fromarray(entire_mask_image).convert("L")) - - else: - logger.status("No source face(s) in the provided Index") else: - logger.status("No source face(s) found") - - return result_image, output, swapped + + if select_source == 0 and source_img is not None: + + source_img = cv2.cvtColor(np.array(source_img), cv2.COLOR_RGB2BGR) + if source_hash_check: + + source_image_md5hash = get_image_md5hash(source_img) + + if SOURCE_IMAGE_HASH is None: + SOURCE_IMAGE_HASH = source_image_md5hash + source_image_same = False + else: + source_image_same = True if SOURCE_IMAGE_HASH == source_image_md5hash else False + if not source_image_same: + SOURCE_IMAGE_HASH = source_image_md5hash + + logger.info("Source Image MD5 Hash = %s", SOURCE_IMAGE_HASH) + logger.info("Source Image the Same? %s", source_image_same) + + if SOURCE_FACES is None or not source_image_same: + logger.status("Analyzing Source Image...") + source_faces = analyze_faces(source_img) + SOURCE_FACES = source_faces + elif source_image_same: + logger.status("Using Hashed Source Face(s) Model...") + source_faces = SOURCE_FACES + + else: + logger.status("Analyzing Source Image...") + source_faces = analyze_faces(source_img) + + elif select_source == 1 and (face_model is not None and face_model != "None"): + source_face_model = [load_face_model(face_model)] + if source_face_model is not None: + source_faces_index = [0] + source_faces = source_face_model + logger.status("Using Loaded Source Face Model...") + else: + logger.error(f"Cannot load Face Model File: {face_model}.safetensors") + + else: + logger.error("Cannot detect any Source") + return result_image, [], 0 + + if source_faces is not None: + + if target_hash_check: + + target_image_md5hash = get_image_md5hash(target_img) + + if TARGET_IMAGE_HASH is None: + TARGET_IMAGE_HASH = target_image_md5hash + target_image_same = False + else: + target_image_same = True if TARGET_IMAGE_HASH == target_image_md5hash else False + if not target_image_same: + TARGET_IMAGE_HASH = target_image_md5hash + + logger.info("Target Image MD5 Hash = %s", TARGET_IMAGE_HASH) + logger.info("Target Image the Same? %s", target_image_same) + + if TARGET_FACES is None or not target_image_same: + logger.status("Analyzing Target Image...") + target_faces = analyze_faces(target_img) + TARGET_FACES = target_faces + elif target_image_same: + logger.status("Using Hashed Target Face(s) Model...") + target_faces = TARGET_FACES + + else: + logger.status("Analyzing Target Image...") + target_faces = analyze_faces(target_img) + + 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) + else: + source_face = sorted(source_faces, key=lambda x: x.bbox[0])[source_faces_index[0]] + wrong_gender = 0 + source_age = source_face["age"] + source_gender = "Female" if source_face["gender"] == 0 else "Male" + + if source_age != "None" or source_gender != "None": + logger.status("Detected: -%s- y.o. %s", source_age, source_gender) + + output_info = f"SourceFaceIndex={source_faces_index[0]};Age={source_age};Gender={source_gender}\n" + output.append(output_info) + + if len(source_faces_index) != 0 and len(source_faces_index) != 1 and len(source_faces_index) != len(faces_index): + logger.status("Source Faces must have no entries (default=0), one entry, or same number of entries as target faces.") + + 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) + + else: + logger.status("No source face(s) in the provided Index") + else: + logger.status("No source face(s) found") + + return result_image, output, swapped + + # END + # ********************** + + return result_image, [], 0 def build_face_model(image: Image.Image, name: str): if image is None: @@ -545,3 +613,103 @@ def build_face_model(image: Image.Image, name: str): no_face_msg = "No face found, please try another image" logger.error(no_face_msg) return no_face_msg + + +def 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 = target_img + face_swapper = getFaceSwapModel(model) + + source_face_idx = 0 + + for face_num in faces_index: + if check_process_halt(): + 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) + if source_age != "None" or source_gender != "None": + logger.status("Detected: -%s- y.o. %s", source_age, source_gender) + + output_info = f"SourceFaceIndex={source_faces_index[source_face_idx]};Age={source_age};Gender={source_gender}\n" + output.append(output_info) + + source_face_idx += 1 + + 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) + if target_age != "None" or target_gender != "None": + logger.status("Detected: -%s- y.o. %s", target_age, target_gender) + + output_info = f"TargetFaceIndex={face_num};Age={target_age};Gender={target_gender}\n" + output.append(output_info) + + if target_face is not None and wrong_gender == 0: + logger.status("Swapping Source into Target") + swapped_image = face_swapper.get(result, target_face, source_face) + + if mask_face: + result = apply_face_mask(swapped_image=swapped_image,target_image=result,target_face=target_face,entire_mask_image=entire_mask_image) + else: + result = swapped_image + swapped += 1 + + elif wrong_gender == 1: + wrong_gender = 0 + + if source_face_idx == len(source_faces_index): + result_image = Image.fromarray(cv2.cvtColor(result, cv2.COLOR_BGR2RGB)) + + if enhancement_options is not None and len(source_faces_index) > 1: + result_image = enhance_image(result_image, enhancement_options) + + return result_image, output, swapped + + else: + logger.status(f"No target face found for {face_num}") + + elif wrong_gender == 1: + wrong_gender = 0 + + if source_face_idx == len(source_faces_index): + result_image = Image.fromarray(cv2.cvtColor(result, cv2.COLOR_BGR2RGB)) + + if enhancement_options is not None and len(source_faces_index) > 1: + result_image = enhance_image(result_image, enhancement_options) + + return result_image, output, swapped + + else: + logger.status(f"No source face found for face number {source_face_idx}.") + + result_image = Image.fromarray(cv2.cvtColor(result, cv2.COLOR_BGR2RGB)) + + if enhancement_options is not None and swapped > 0: + if mask_face and entire_mask_image is not None: + result_image = enhance_image_and_mask(result_image, enhancement_options,Image.fromarray(target_img_orig),Image.fromarray(entire_mask_image).convert("L")) + else: + result_image = enhance_image(result_image, enhancement_options) + elif mask_face and entire_mask_image is not None and swapped > 0: + result_image = Image.composite(result_image,Image.fromarray(target_img_orig),Image.fromarray(entire_mask_image).convert("L")) + + return result_image, output, swapped diff --git a/scripts/reactor_version.py b/scripts/reactor_version.py index 9e68bc1..996347a 100644 --- a/scripts/reactor_version.py +++ b/scripts/reactor_version.py @@ -1,5 +1,5 @@ app_title = "ReActor" -version_flag = "v0.5.1" +version_flag = "v0.6.0-a1" from scripts.reactor_logger import logger, get_Run, set_Run