Compare commits

..

44 Commits
v0.6.1 ... main

Author SHA1 Message Date
Евгений Гурьев | Eugene Gourieff | 古仁
2c6a6e6352 VersionUP
0.7.1 Beta2
2024-10-08 03:40:22 +07:00
Евгений Гурьев | Eugene Gourieff | 古仁
c7b8556856 FIX.1: onnx, onnxruntime-gpu stable versions
Issues: #483 #486 #502 #536 #538 #539
2024-10-08 03:39:49 +07:00
Евгений Гурьев | Eugene Gourieff | 古仁
0458e56cb9 UPDATE: What's new 2024-09-23 15:05:45 +07:00
Евгений Гурьев | Eugene Gourieff | 古仁
17f031356a FIX: Shape check False by default 2024-09-23 14:57:17 +07:00
Евгений Гурьев | Eugene Gourieff | 古仁
58ddc725db VersionUP
0.7.1 Beta1 - Alpha passed
2024-09-23 14:41:14 +07:00
Евгений Гурьев | Eugene Gourieff | 古仁
d74478114f FIX: async fn, fn name 2024-09-23 14:34:19 +07:00
Евгений Гурьев | Eugene Gourieff | 古仁
f680b43100
Merge pull request #438 from KY00KIM/main
UPDATE: Add api-endpoint for building face model
2024-09-15 17:45:30 +07:00
Евгений Гурьев | Eugene Gourieff | 古仁
7298d8650d
Merge pull request #452 from ryanwalder/main
UPDATE: Sort models list alphabetically
2024-09-15 17:44:29 +07:00
Евгений Гурьев | Eugene Gourieff | 古仁
1242914609
Merge pull request #527 from zappityzap/pr-face-index-spaces
UPDATE: Allow using spaces in face index lists
2024-09-15 17:43:59 +07:00
zappityzap
da963b8796 allow using spaces in face index lists 2024-09-14 15:29:05 -07:00
Евгений Гурьев | Eugene Gourieff | 古仁
4493fd7133 FIX: Gradio4 "update" deprecation
Issues: #507 #496 #491
2024-08-19 15:06:39 +07:00
Art Gourieff
613b7d74ea VersionUP
0.7.1 Alpha1
2024-06-26 21:07:09 +07:00
Art Gourieff
1ee4d0e949 Merge branch 'main' into evolve 2024-06-26 21:03:16 +07:00
Art Gourieff
5255d497a7 VersionUP
0.7.0 - Beta passed
2024-06-26 20:54:34 +07:00
Art Gourieff
9b0cc02791 Merge branch 'equalize' into evolve 2024-06-26 20:49:55 +07:00
Ryan Walder
ad51f737d1 Sort models list alphabetically 2024-05-16 18:45:24 +01:00
KYOOKIM
d2f6b7d29d Addbuild facemodel api 2024-04-28 08:15:44 +09:00
Art Gourieff
f57c2acc55 Merge branch 'main' into equalize 2024-04-19 11:34:16 +07:00
Eugene Gourieff
d2e78be2b3
Merge pull request #424 from w-e-w/albumentations==1.4.3
FIX: albumentations==1.4.3
2024-04-19 11:32:12 +07:00
w-e-w
8f2106569c albumentations==1.4.3 2024-04-18 22:53:48 +09:00
Eugene Gourieff
be31df8040
Merge pull request #404 from light-and-ray/save_extra_images
UPDATE: Save all images in extras tab
2024-04-07 01:29:51 +07:00
Eugene Gourieff
2b945ae0b2
Merge pull request #403 from light-and-ray/main
UPDATE: Separate single and multiple sources with tabs
2024-04-07 01:28:36 +07:00
Art Gourieff
7633e58182 UPDATE: Old SD.WebUI < 1.7.0 support 2024-04-07 01:25:13 +07:00
Art Gourieff
97598387b1 UPDATE: async api try-1, XYZ init 2024-04-07 01:21:40 +07:00
Art Gourieff
1b706db767 FIX: Insightface default_providers patch 2024-04-07 01:17:13 +07:00
Art Gourieff
8651bc2639 FIX: Typo 2024-04-07 01:16:00 +07:00
Andray
b7b094ba78 save all images in extras tab 2024-03-21 13:28:01 +04:00
Andray
bb3e97c74c separate single and multiple sources with tabs 2024-03-21 12:48:48 +04:00
Art Gourieff
ae57149962 UPDATE: API instruction 2024-03-15 17:20:38 +07:00
Art Gourieff
d7e7ddff7b VersionUP (0.7.0 beta7) 2024-03-15 17:03:13 +07:00
Art Gourieff
9989610fa7 UPDATE: API RGBA support, GET facemodels show
FR: #394
D: #384
2024-03-12 20:25:33 +07:00
Eugene Gourieff
0185d7a2af
Merge pull request #386 from 0312birdzhang/patch-1
fix typo: models_path
2024-03-09 11:37:41 +07:00
Art Gourieff
99b0f19553 UPDATE: RGBA support for Extras Single Image
+VersionUP (0.7.0 beta6)
2024-03-09 02:36:11 +07:00
BirdZhang
0d03e020bb
fix typo: models_path 2024-03-08 21:01:09 +08:00
Art Gourieff
e215bd6763 HOTFIX: Rollback to ORT --extra-index-url if CU12
+VersionUP (0.7.0 beta5)
2024-03-06 01:27:24 +07:00
Art Gourieff
8fc0de58f6 UPDATE: ORT dependency for CU12
+VersionUP (0.7.0 beta4)
2024-03-05 12:38:38 +07:00
Art Gourieff
b6e394de62 FIX: No source 'face model' when batch
Issue: https://github.com/Gourieff/sd-webui-reactor/discussions/379

+VersionUP (0.7.0 beta3)
2024-03-05 01:15:28 +07:00
Art Gourieff
6c3bf1c6c4 UPDATE: XYZ Improved - "Face Model" axis is ready
+VersionUP (0.7.0 beta2)
2024-03-02 13:24:20 +07:00
Art Gourieff
565704285b UPDATE: XYZ Script support
FR: #329
+VersionUP (0.7.0 beta1)
2024-03-01 20:50:52 +07:00
Art Gourieff
8fafab9777 UPDATE: Details 2024-03-01 13:21:06 +07:00
Art Gourieff
d897370276 FIX: Typo 2024-03-01 13:05:31 +07:00
Art Gourieff
778ddec96d FIX: Insightface Py310 whl URL 2024-02-24 19:57:46 +07:00
Art Gourieff
9778e4f208 UPDATE: det_size with face model build
FR #247
2024-02-20 19:19:05 +07:00
Art Gourieff
96d7a06291 UPDATE: Blended Faces, CU12, Detection Tab
+VersionUP (0.7.0 alpha1)
FR #143 #255 (partly) #280 (partly) #352
Issue #319
2024-02-11 19:52:01 +07:00
18 changed files with 790 additions and 169 deletions

36
API.md
View File

@ -40,7 +40,7 @@ curl -X POST \
"target_image": "data:image/png;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAABCAAD/7g...",
"source_faces_index": [0],
"face_index": [0],
"upscaler": "4x_Struzan_300000",
"upscaler": "4x_NMKD-Siax_200k",
"scale": 2,
"upscale_visibility": 1,
"face_restorer": "CodeFormer",
@ -50,13 +50,26 @@ curl -X POST \
"gender_source": 0,
"gender_target": 0,
"save_to_file": 0,
"result_file_path": ""
"result_file_path": "",
"device": "CUDA",
"mask_face": 1,
"select_source": 1,
"face_model": "elena.safetensors",
"source_folder": "C:/faces",
"random_image": 1,
"upscale_force": 1
}'
```
* Set `"upscaler"` to `"None"` and `"scale"` to `1` if you don't need to upscale;
* Set `"save_to_file"` to `1` if you need to save result to a file;
* `"result_file_path"` is set to the `"outputs/api"` folder by default (please, create the folder beforehand to avoid any errors) with a timestamped filename; (output_YYYY-MM-DD_hh-mm-ss), you can set any specific path, e.g. `"C:/stable-diffusion-webui/outputs/api/output.png"`.
* `"result_file_path"` is set to the `"outputs/api"` folder by default (please, create the folder beforehand to avoid any errors) with a timestamped filename; (output_YYYY-MM-DD_hh-mm-ss), you can set any specific path, e.g. `"C:/stable-diffusion-webui/outputs/api/output.png"`;
* Set `"mask_face"` to `1` if you want ReActor to mask the face or to `0` if want ReActor to create a bbox around the face;
* Set `"select_source"` to: 0 - Image, 1 - Face Model, 2 - Source Folder;
* Set `"face_model"` to the face model file you want to choose if you set `"select_source": 1`;
* Set `"source_folder"` to the path with source images (with faces you need as the results) if you set `"select_source": 2`;
* Set `"random_image"` to `1` if want ReActor to choose a random image from the path of `"source_folder"`;
* Set `"upscale_force"` to `1` if you want ReActor to upscale the image even if no face found.
You can find full usage examples with all the available parameters in the "example" folder: [cURL](./example/api_external.curl), [JSON](./example/api_external.json).
@ -69,3 +82,20 @@ As a result you recieve a "base64" image:
A list of available models can be seen by GET:
* http://127.0.0.1:7860/reactor/models
* http://127.0.0.1:7860/reactor/upscalers
* http://127.0.0.1:7860/reactor/facemodels
### FaceModel Buid API
Send POST to http://127.0.0.1:7860/reactor/facemodels with body:
```
{
"source_images": ["data:image/png;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAABQAAD/7g...","data:image/png;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAABQAAD/7g...","data:image/png;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAABQAAD/7g..."],
"name": "my_super_model",
"compute_method": 0
}
```
where:<br>
"source_images" is a list of base64 encoded images,<br>
"compute_method" is: 0 - Mean, 1- Median, 2 - Mode

View File

@ -2,7 +2,7 @@
<img src="https://github.com/Gourieff/Assets/raw/main/sd-webui-reactor/ReActor_logo_NEW_EN.png?raw=true" alt="logo" width="180px"/>
![Version](https://img.shields.io/badge/version-0.6.1-brightgreen?style=for-the-badge&labelColor=darkgreen)
![Version](https://img.shields.io/badge/version-0.7.1_beta2-green?style=for-the-badge&labelColor=darkgreen)
<a href="https://boosty.to/artgourieff" target="_blank">
<img src="https://lovemet.ru/www/boosty.jpg" width="108" alt="Support Me on Boosty"/>
@ -40,6 +40,48 @@
## What's new in the latest updates
### 0.7.1 <sub><sup>BETA1
- Allow spaces for face indexes (e.g.: 0, 1, 2)
- Sorting of face models list alphabetically
- [FaceModels Build API](./API.md#facemodel-build-api)
- Fixes and improvements
<details>
<summary><a>Click to expand more</a></summary>
### 0.7.0 <sub><sup>BETA2
- X/Y/Z is improved! One more parameter is ready: you can now select several face models to create a variation of swaps to choose the best one!
<img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-05.jpg?raw=true" alt="0.7.0-whatsnew-05" width="100%"/>
To use "Face Model" axis - you should enable ReActor and choose any face model as the Source:<br>
<img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-07.jpg?raw=true" alt="0.7.0-whatsnew-07" width="50%"/><img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-06.jpg?raw=true" alt="0.7.0-whatsnew-06" width="50%"/>
Full size demo image: [xyz_demo_2.png](https://raw.githubusercontent.com/Gourieff/Assets/main/sd-webui-reactor/xyz_demo_2.png)
### 0.7.0 <sub><sup>BETA1
- X/Y/Z Script support (up to 3 axes: CodeFormer Weight, Restorer Visibility, Face Mask Correction)
<img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-03.jpg?raw=true" alt="0.7.0-whatsnew-03" width="100%"/>
<img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-04.jpg?raw=true" alt="0.7.0-whatsnew-04" width="100%"/>
Full size demo image: [xyz_demo.png](https://raw.githubusercontent.com/Gourieff/Assets/main/sd-webui-reactor/xyz_demo.png)
__Don't forget to enable ReActor and set any source (to prevent "no source" error)__
### 0.7.0 <sub><sup>ALPHA1
- You can now blend faces to build blended face models ("Tools->Face Models->Blend") - due to popular demand
<img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-01.jpg?raw=true" alt="0.7.0-whatsnew-01" width="100%"/><img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-02.jpg?raw=true" alt="0.7.0-whatsnew-02" width="100%"/>
- CUDA 12 Support in the Installer script for 1.17.0 ORT-GPU library
- New tab "Detection" with "Threshold" and "Max Faces" parameters
### 0.6.1 <sub><sup>BETA3
- 'Force Upscale' option inside the 'Upscale' tab: ReActor will run the Upscaler even if there's no face is detected (FR https://github.com/Gourieff/sd-webui-reactor/issues/116)
@ -71,11 +113,13 @@
<img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/face_model_demo_01.jpg?raw=true" alt="0.5.0-whatsnew-01" width="100%"/>
</details>
## Installation
[Automatic1111](#a1111) | [Vladmandic SD.Next](#sdnext) | [Google Colab SD WebUI](#colab)
[A1111 WebUI / WebUI-Forge](#a1111) | [SD.Next](#sdnext) | [Google Colab SD WebUI](#colab)
<a name="a1111">If you use [AUTOMATIC1111 web-ui](https://github.com/AUTOMATIC1111/stable-diffusion-webui/):
<a name="a1111">If you use [AUTOMATIC1111 SD WebUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui/) or [SD WebUI Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge):
1. (For Windows Users):
- Install **Visual Studio 2022** (Community version, for example - you need this step to build some of dependencies):
@ -272,7 +316,7 @@ If this method doesn't help - there is some other extension that has a wrong ver
### **VIII. (For Windows users) If you still cannot build Insightface for some reasons or just don't want to install Visual Studio or VS C++ Build Tools - do the following:**
1. Close (stop) your SD WebUI Server if it's running
2. Download and put [prebuilt Insightface package](https://github.com/Gourieff/sd-webui-reactor/raw/main/example/insightface-0.7.3-cp310-cp310-win_amd64.whl) into the stable-diffusion-webui (or SD.Next) root folder where you have "webui-user.bat" file or (A1111 Portable) "run.bat"
2. Download and put [prebuilt Insightface package](https://github.com/Gourieff/Assets/raw/main/Insightface/insightface-0.7.3-cp310-cp310-win_amd64.whl) into the stable-diffusion-webui (or SD.Next) root folder where you have "webui-user.bat" file or (A1111 Portable) "run.bat"
3. From stable-diffusion-webui (or SD.Next) root folder run CMD and `.\venv\Scripts\activate`<br>OR<br>(A1111 Portable) Run CMD
4. Then update your PIP: `python -m pip install -U pip`<br>OR<br>(A1111 Portable)`system\python\python.exe -m pip install -U pip`
5. Then install Insightface: `pip install insightface-0.7.3-cp310-cp310-win_amd64.whl`<br>OR<br>(A1111 Portable)`system\python\python.exe -m pip install insightface-0.7.3-cp310-cp310-win_amd64.whl`
@ -309,7 +353,7 @@ For the installation instruction follow the [ReActor Node repo](https://github.c
This software is meant to be a productive contribution to the rapidly growing AI-generated media industry. It will help artists with tasks such as animating a custom character or using the character as a model for clothing etc.
The developers of this software are aware of its possible unethical applicaitons and are committed to take preventative measures against them. We will continue to develop this project in the positive direction while adhering to law and ethics.
The developers of this software are aware of its possible unethical application and are committed to take preventative measures against them. We will continue to develop this project in the positive direction while adhering to law and ethics.
Users of this software are expected to use this software responsibly while abiding the local law. If face of a real person is being used, users are suggested to get consent from the concerned person and clearly mention that it is a deepfake when posting content online. **Developers and Contributors of this software are not responsible for actions of end-users.**

View File

@ -2,7 +2,7 @@
<img src="https://github.com/Gourieff/Assets/raw/main/sd-webui-reactor/ReActor_logo_NEW_RU.png?raw=true" alt="logo" width="180px"/>
![Version](https://img.shields.io/badge/версия-0.6.1-brightgreen-green?style=for-the-badge&labelColor=darkgreen)
![Version](https://img.shields.io/badge/версия-0.7.1_beta2-green?style=for-the-badge&labelColor=darkgreen)
<a href="https://boosty.to/artgourieff" target="_blank">
<img src="https://lovemet.ru/www/boosty.jpg" width="108" alt="Поддержать проект на Boosty"/>
@ -39,6 +39,44 @@
## Что нового в последних обновлениях
### 0.7.1 <sub><sup>BETA1
- Использование пробелов в индексах лиц (пример: 0, 1, 2)
- Список моделей лиц теперь отсортирован по алфавиту
- [API для создания моделей лиц](./API.md#facemodel-build-api)
- Правки и улучшения
<details>
<summary><a>Нажмите, чтобы посмотреть больше</a></summary>
### 0.7.0 <sub><sup>BETA2
- X/Y/Z опция улучшена! Добавлен ещё один параметр: теперь вы можете выбрать несколько моделей лиц для создания вариации замененных лиц, чтобы выбрать наилучшие!
<img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-05.jpg?raw=true" alt="0.7.0-whatsnew-05" width="100%"/>
Чтобы использовать ось "Face Model" - активируйте РеАктор и выбирите любую модель лица в качестве источника:<br>
<img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-07.jpg?raw=true" alt="0.7.0-whatsnew-07" width="50%"/><img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-06.jpg?raw=true" alt="0.7.0-whatsnew-06" width="50%"/>
Полноразмерное демо-изображение: [xyz_demo_2.png](https://raw.githubusercontent.com/Gourieff/Assets/main/sd-webui-reactor/xyz_demo_2.png)
### 0.7.0 <sub><sup>BETA1
- Поддержка X/Y/Z скрипта (до 3-х параметров: CodeFormer Weight, Restorer Visibility, Face Mask Correction)
<img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-03.jpg?raw=true" alt="0.7.0-whatsnew-03" width="100%"/>
Полноразмерное демо-изображение: [xyz_demo.png](https://raw.githubusercontent.com/Gourieff/Assets/main/sd-webui-reactor/xyz_demo.png)
### 0.7.0 <sub><sup>ALPHA1
- По многочисленным просьбам появилась возможность строить смешанные модели лиц ("Tools->Face Models->Blend")
<img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-01.jpg?raw=true" alt="0.7.0-whatsnew-01" width="100%"/><img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/0.7.0-whatsnew-02.jpg?raw=true" alt="0.7.0-whatsnew-02" width="100%"/>
- Поддержка CUDA 12 в скрипте установщика для библиотеки ORT-GPU версии 1.17.0
- Новая вкладка "Detection" с параметрами "Threshold" и "Max Faces"
### 0.6.1 <sub><sup>BETA3
- Опция 'Force Upscale' внутри вкладки 'Upscale': апскейл выполнится, даже если не было обнаружено ни одного лица (FR https://github.com/Gourieff/sd-webui-reactor/issues/116)
@ -70,13 +108,15 @@
<img src="https://github.com/Gourieff/Assets/blob/main/sd-webui-reactor/face_model_demo_01.jpg?raw=true" alt="0.5.0-whatsnew-01" width="100%"/>
</details>
<a name="installation">
## Установка
[Automatic1111](#a1111) | [Vladmandic SD.Next](#sdnext) | [Google Colab SD WebUI](#colab)
[A1111 WebUI / WebUI-Forge](#a1111) | [SD.Next](#sdnext) | [Google Colab SD WebUI](#colab)
<a name="a1111">Если вы используете [AUTOMATIC1111 Web-UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui/):
<a name="a1111">Если вы используете [AUTOMATIC1111 SD WebUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui/) или [SD WebUI Forge](https://github.com/lllyasviel/stable-diffusion-webui-forge):
1. (Для пользователей Windows):
- Установите **Visual Studio 2022** (Например, версию Community - этот шаг нужен для правильной компиляции библиотеки Insightface):
@ -279,7 +319,7 @@ Inpainting также работает, но замена лица происх
### **VIII. (Для пользователей Windows) Если вы до сих пор не можете установить пакет Insightface по каким-то причинам или же просто не желаете устанавливать Visual Studio или VS C++ Build Tools - сделайте следующее:**
1. Закройте (остановите) SD WebUI Сервер, если он запущен
2. Скачайте готовый [пакет Insightface](https://github.com/Gourieff/sd-webui-reactor/raw/main/example/insightface-0.7.3-cp310-cp310-win_amd64.whl) и сохраните его в корневую директорию stable-diffusion-webui (или SD.Next) - туда, где лежит файл "webui-user.bat" или (A1111 Portable) "run.bat"
2. Скачайте готовый [пакет Insightface](https://github.com/Gourieff/Assets/raw/main/Insightface/insightface-0.7.3-cp310-cp310-win_amd64.whl) и сохраните его в корневую директорию stable-diffusion-webui (или SD.Next) - туда, где лежит файл "webui-user.bat" или (A1111 Portable) "run.bat"
3. Из корневой директории откройте Консоль (CMD) и выполните `.\venv\Scripts\activate`<br>ИЛИ<br>(A1111 Portable) Откройте Консоль (CMD)
4. Обновите PIP: `python -m pip install -U pip`<br>ИЛИ<br>(A1111 Portable)`system\python\python.exe -m pip install -U pip`
5. Затем установите Insightface: `pip install insightface-0.7.3-cp310-cp310-win_amd64.whl`<br>ИЛИ<br>(A1111 Portable)`system\python\python.exe -m pip install insightface-0.7.3-cp310-cp310-win_amd64.whl`

View File

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

View File

@ -12,7 +12,7 @@ except:
try:
from modules.paths import models_path
except:
model_path = os.path.abspath("models")
models_path = os.path.abspath("models")
BASE_PATH = os.path.dirname(os.path.realpath(__file__))
@ -21,20 +21,6 @@ req_file = os.path.join(BASE_PATH, "requirements.txt")
models_dir = os.path.join(models_path, "insightface")
# DEPRECATED:
# models_dir_old = os.path.join(models_path, "roop")
# if os.path.exists(models_dir_old):
# if not os.listdir(models_dir_old) and (not os.listdir(models_dir) or not os.path.exists(models_dir)):
# os.rename(models_dir_old, models_dir)
# else:
# import shutil
# for file in os.listdir(models_dir_old):
# shutil.move(os.path.join(models_dir_old, file), os.path.join(models_dir, file))
# try:
# os.rmdir(models_dir_old)
# except Exception as e:
# print(f"OSError: {e}")
model_url = "https://huggingface.co/datasets/Gourieff/ReActor/resolve/main/models/inswapper_128.onnx"
model_name = os.path.basename(model_url)
model_path = os.path.join(models_dir, model_name)
@ -97,11 +83,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 +103,17 @@ 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:
if float(cuda_version)>=12: # CU12.x
extra_index_url = "https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/"
else: # CU11.8
extra_index_url = "https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-11/pypi/simple"
if not is_installed(ort,"1.17.1",True):
install_count += 1
ort = "onnxruntime-gpu==1.17.1"
pip_uninstall("onnxruntime", "onnxruntime-gpu")
pip_install(ort,"--extra-index-url",extra_index_url)
elif not is_installed(ort,"1.18.1",False):
install_count += 1
pip_install(ort, "-U")
except Exception as e:

View File

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

View File

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

View File

@ -11,9 +11,14 @@ from modules import shared
# SAVE_ORIGINAL: bool = False
def update_fm_list(selected: str):
try: # GR3.x
return gr.Dropdown.update(
value=selected, choices=get_model_names(get_facemodels)
)
except: # GR4.x
return gr.Dropdown(
value=selected, choices=get_model_names(get_facemodels)
)
# TAB MAIN
def show(is_img2img: bool, show_br: bool = True, **msgs):
@ -24,6 +29,7 @@ def show(is_img2img: bool, show_br: bool = True, **msgs):
if evt.index == 2:
# if SAVE_ORIGINAL != selected:
# SAVE_ORIGINAL = selected
try: # GR3.x
return {
control_col_1: gr.Column.update(visible=False),
control_col_2: gr.Column.update(visible=False),
@ -31,7 +37,16 @@ def show(is_img2img: bool, show_br: bool = True, **msgs):
# save_original: gr.Checkbox.update(value=False,visible=False),
imgs_hash_clear: gr.Button.update(visible=True)
}
except: # GR4.x
return {
control_col_1: gr.Column(visible=False),
control_col_2: gr.Column(visible=False),
control_col_3: gr.Column(visible=True),
# save_original: gr.Checkbox.update(value=False,visible=False),
imgs_hash_clear: gr.Button(visible=True)
}
if evt.index == 0:
try: # GR3.x
return {
control_col_1: gr.Column.update(visible=True),
control_col_2: gr.Column.update(visible=False),
@ -39,7 +54,16 @@ def show(is_img2img: bool, show_br: bool = True, **msgs):
# save_original: gr.Checkbox.update(value=SAVE_ORIGINAL,visible=show_br),
imgs_hash_clear: gr.Button.update(visible=False)
}
except: # GR4.x
return {
control_col_1: gr.Column(visible=True),
control_col_2: gr.Column(visible=False),
control_col_3: gr.Column(visible=False),
# save_original: gr.Checkbox.update(value=SAVE_ORIGINAL,visible=show_br),
imgs_hash_clear: gr.Button(visible=False)
}
if evt.index == 1:
try: # GR3.x
return {
control_col_1: gr.Column.update(visible=False),
control_col_2: gr.Column.update(visible=True),
@ -47,6 +71,14 @@ def show(is_img2img: bool, show_br: bool = True, **msgs):
# save_original: gr.Checkbox.update(value=SAVE_ORIGINAL,visible=show_br),
imgs_hash_clear: gr.Button.update(visible=False)
}
except: # GR4.x
return {
control_col_1: gr.Column(visible=False),
control_col_2: gr.Column(visible=True),
control_col_3: gr.Column(visible=False),
# save_original: gr.Checkbox.update(value=SAVE_ORIGINAL,visible=show_br),
imgs_hash_clear: gr.Button(visible=False)
}
progressbar_area = gr.Markdown("")
with gr.Tab("Main"):
@ -85,16 +117,21 @@ def show(is_img2img: bool, show_br: bool = True, **msgs):
imgs_hash_clear.click(clear_faces_list,None,[progressbar_area])
gr.Markdown("<br>", visible=show_br)
with gr.Column(visible=True) as control_col_1:
gr.Markdown("<center>🔽🔽🔽 Single Image has priority when both Areas in use 🔽🔽🔽</center>")
with gr.Row():
selected_tab = gr.Textbox('tab_single', visible=False)
with gr.Tabs() as tab_single:
with gr.Tab('Single'):
img = gr.Image(
type="pil",
label="Single Source Image",
)
with gr.Tab('Multiple') as tab_multiple:
imgs = gr.Files(
label=f"Multiple Source Images{msgs['extra_multiple_source']}",
file_types=["image"],
)
tab_single.select(fn=lambda: 'tab_single', inputs=[], outputs=[selected_tab])
tab_multiple.select(fn=lambda: 'tab_multiple', inputs=[], outputs=[selected_tab])
with gr.Column(visible=False) as control_col_3:
gr.Markdown("<span style='display:block;text-align:right;padding-right:3px;margin: -15px 0;font-size:1.1em'><sup>Clear Hash if you see the previous face was swapped instead of the new one</sup></span>")
with gr.Row():
@ -189,4 +226,4 @@ def show(is_img2img: bool, show_br: bool = True, **msgs):
# 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)
select_source.select(on_select_source,None,[control_col_1,control_col_2,control_col_3,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, random_image
return img, imgs, selected_tab, 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

View File

@ -1,14 +1,16 @@
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"):
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 Image to build Face Model",
label="Load an Image to build -Face Model-",
)
with gr.Row(equal_height=True):
fm_name = gr.Textbox(
@ -23,3 +25,37 @@ def show():
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 similar 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],
)

View File

@ -1,3 +1,4 @@
albumentations==1.4.3
insightface==0.7.3
onnx>=1.14.0
onnx==1.16.1
opencv-python>=4.7.0.72

View File

@ -2,7 +2,7 @@ import os.path as osp
import glob
import logging
import insightface
from insightface.model_zoo.model_zoo import ModelRouter, PickableInferenceSession
from insightface.model_zoo.model_zoo import ModelRouter, PickableInferenceSession, get_default_providers
from insightface.model_zoo.retinaface import RetinaFace
from insightface.model_zoo.landmark import Landmark
from insightface.model_zoo.attribute import Attribute
@ -97,15 +97,20 @@ def patched_inswapper_init(self, model_file=None, session=None):
self.input_size = tuple(input_shape[2:4][::-1])
def patch_insightface(get_model, faceanalysis_init, faceanalysis_prepare, inswapper_init):
def patched_get_default_providers():
return ['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']
def patch_insightface(get_default_providers, get_model, faceanalysis_init, faceanalysis_prepare, inswapper_init):
insightface.model_zoo.model_zoo.get_default_providers = get_default_providers
insightface.model_zoo.model_zoo.ModelRouter.get_model = get_model
insightface.app.FaceAnalysis.__init__ = faceanalysis_init
insightface.app.FaceAnalysis.prepare = faceanalysis_prepare
insightface.model_zoo.inswapper.INSwapper.__init__ = inswapper_init
original_functions = [ModelRouter.get_model, FaceAnalysis.__init__, FaceAnalysis.prepare, INSwapper.__init__]
patched_functions = [patched_get_model, patched_faceanalysis_init, patched_faceanalysis_prepare, patched_inswapper_init]
original_functions = [patched_get_default_providers, ModelRouter.get_model, FaceAnalysis.__init__, FaceAnalysis.prepare, INSwapper.__init__]
patched_functions = [patched_get_default_providers, patched_get_model, patched_faceanalysis_init, patched_faceanalysis_prepare, patched_inswapper_init]
def apply_logging_patch(console_logging_level):

View File

@ -1,12 +1,23 @@
'''
Thanks SpenserCai for the original version of the roop api script
-----------------------------------
--- ReActor External API v1.0.4 ---
--- ReActor External API v1.0.8a ---
-----------------------------------
'''
import os, glob
from datetime import datetime, date
from fastapi import FastAPI, Body
# from fastapi.exceptions import HTTPException
# from io import BytesIO
# from PIL import Image
# import base64
# import numpy as np
# import cv2
import asyncio
from concurrent.futures import ThreadPoolExecutor
# from concurrent.futures.process import ProcessPoolExecutor
# from contextlib import asynccontextmanager
# import multiprocessing
# from modules.api.models import *
from modules import scripts, shared
@ -14,8 +25,32 @@ from modules.api import api
import gradio as gr
from scripts.reactor_swapper import EnhancementOptions, swap_face
from scripts.reactor_swapper import EnhancementOptions, blend_faces, swap_face, DetectionOptions
from scripts.reactor_logger import logger
from scripts.reactor_helpers import get_facemodels
# @asynccontextmanager
# async def lifespan(app: FastAPI):
# app.state.executor = ProcessPoolExecutor(max_workers=4)
# yield
# app.state.executor.shutdown()
# app = FastAPI(lifespan=lifespan)
# def run_app(a: FastAPI):
# global app
# a = app
# return a
# _executor_tp = ThreadPoolExecutor(max_workers=8)
# def entry_point():
# _executor_pp = ProcessPoolExecutor(max_workers=8)
# pool = multiprocessing.Pool(4)
async def run_event(app, fn, *args):
loop = asyncio.get_event_loop()
return await loop.run_in_executor(app.state.executor, fn, *args)
def default_file_path():
@ -52,7 +87,21 @@ def get_full_model(model_name):
return model
return None
# def decode_base64_to_image_rgba(encoding):
# if encoding.startswith("data:image/"):
# encoding = encoding.split(";")[1].split(",")[1]
# try:
# im_bytes = base64.b64decode(encoding)
# im_arr = np.frombuffer(im_bytes, dtype=np.uint8) # im_arr is one-dim Numpy array
# img = cv2.imdecode(im_arr, flags=cv2.IMREAD_UNCHANGED)
# img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGBA)
# image = Image.fromarray(img, mode="RGBA")
# return image
# except Exception as e:
# raise HTTPException(status_code=500, detail="Invalid encoded image") from e
def reactor_api(_: gr.Blocks, app: FastAPI):
app.state.executor = ThreadPoolExecutor(max_workers=8)
@app.post("/reactor/image")
async def reactor_image(
source_image: str = Body("",title="Source Face Image"),
@ -77,10 +126,18 @@ 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)
if t_image.mode == 'RGBA':
_, _, _, alpha = t_image.split()
else:
alpha = None
sf_index = source_faces_index
f_index = face_index
gender_s = gender_source
@ -90,19 +147,30 @@ 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)
args = [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]
# result,_,_ = pool.map(swap_face, *args)
result,_,_ = await run_event(app,swap_face,*args)
# 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 alpha is not None:
result = result.convert("RGBA")
result.putalpha(alpha)
if save_to_file == 1:
if result_file_path == "":
result_file_path = default_file_path()
try:
result[0].save(result_file_path, format='PNG')
file_format = os.path.split(result_file_path)[1].split(".")[1]
result.save(result_file_path, format=file_format)
logger.status("Result has been saved to: %s", result_file_path)
except Exception as e:
logger.error("Error while saving result: %s",e)
return {"image": api.encode_pil_to_base64(result[0])}
return {"image": api.encode_pil_to_base64(result)}
@app.get("/reactor/models")
async def reactor_models():
@ -114,9 +182,23 @@ def reactor_api(_: gr.Blocks, app: FastAPI):
names = [upscaler.name for upscaler in shared.sd_upscalers]
return {"upscalers": names}
@app.get("/reactor/facemodels")
async def reactor_facemodels():
facemodels = [os.path.split(model)[1].split(".")[0] for model in get_facemodels()]
return {"facemodels": facemodels}
@app.post("/reactor/facemodels")
async def reactor_facemodels_build(
source_images: list[str] = Body([""],title="Source Face Image List"),
name: str = Body("",title="Face Model Name"),
compute_method: int = Body(0,title="Compute Method (Mean, Median, Mode)"),
):
images = [api.decode_base64_to_image(img) for img in source_images]
blend_faces(images, name, compute_method, False, is_api=True)
return {"facemodels": [os.path.split(model)[1].split(".")[0] for model in get_facemodels()]}
try:
import modules.script_callbacks as script_callbacks
script_callbacks.on_app_started(reactor_api)
except:
pass

View File

@ -6,7 +6,7 @@ from typing import List
import modules.scripts as scripts
from modules.upscaler import Upscaler, UpscalerData
from modules import scripts, shared, images, scripts_postprocessing, ui_components
from modules import scripts, shared, images, scripts_postprocessing
from modules.processing import (
Processed,
StableDiffusionProcessing,
@ -15,10 +15,17 @@ 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,
DetectionOptions,
swap_face,
check_process_halt,
reset_messaged,
@ -28,10 +35,24 @@ from scripts.console_log_patch import apply_logging_patch
from scripts.reactor_helpers import (
make_grid,
set_Device,
get_SDNEXT
get_SDNEXT,
)
from scripts.reactor_globals import SWAPPER_MODELS_PATH #, DEVICE, DEVICE_LIST
def IA_cap(cond: bool, label: str=""):
return None
try:
from modules.ui_components import InputAccordion
NO_IA = False
except:
NO_IA = True
InputAccordion = IA_cap
def check_old_webui():
return NO_IA
class FaceSwapScript(scripts.Script):
def title(self):
@ -41,19 +62,12 @@ class FaceSwapScript(scripts.Script):
return scripts.AlwaysVisible
def ui(self, is_img2img):
with ui_components.InputAccordion(False, label=f"{app_title}") as enable:
# with gr.Accordion(f"{app_title}", open=False):
with (
gr.Accordion(f"{app_title}", open=False) if check_old_webui() else InputAccordion(False, label=f"{app_title}") as enable
):
# 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)
# SD.Next fix
if get_SDNEXT():
# SD.Next or A1111 1.52:
if get_SDNEXT() or check_old_webui():
enable = gr.Checkbox(False, label="Enable")
# enable = gr.Checkbox(False, label="Enable", info=f"The Fast and Simple FaceSwap Extension - {version_flag}")
@ -63,7 +77,10 @@ class FaceSwapScript(scripts.Script):
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, random_image = ui_main.show(is_img2img=is_img2img, **msgs)
img, imgs, selected_tab, 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 +121,10 @@ class FaceSwapScript(scripts.Script):
source_folder,
imgs,
random_image,
upscale_force
upscale_force,
det_thresh,
det_maxnum,
selected_tab,
]
@ -135,6 +155,13 @@ class FaceSwapScript(scripts.Script):
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,
p: StableDiffusionProcessing,
@ -166,6 +193,9 @@ class FaceSwapScript(scripts.Script):
imgs,
random_image,
upscale_force,
det_thresh,
det_maxnum,
selected_tab,
):
self.enable = enable
if self.enable:
@ -177,7 +207,10 @@ class FaceSwapScript(scripts.Script):
return
global SWAPPER_MODELS_PATH
if selected_tab == "tab_single":
self.source = img
else:
self.source = None
self.face_restorer_name = face_restorer_name
self.upscaler_scale = upscaler_scale
self.upscaler_visibility = upscaler_visibility
@ -199,18 +232,23 @@ class FaceSwapScript(scripts.Script):
self.select_source = select_source
self.face_model = face_model
self.source_folder = source_folder
if selected_tab == "tab_single":
self.source_imgs = None
else:
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":
self.gender_target = 0
self.source_faces_index = [
int(x) for x in source_faces_index.strip(",").split(",") if x.isnumeric()
int(x) for x in source_faces_index.strip().replace(" ", "").strip(",").split(",") if x.isnumeric()
]
self.faces_index = [
int(x) for x in faces_index.strip(",").split(",") if x.isnumeric()
int(x) for x in faces_index.strip().replace(" ", "").strip(",").split(",") if x.isnumeric()
]
if len(self.source_faces_index) == 0:
self.source_faces_index = [0]
@ -229,6 +267,13 @@ class FaceSwapScript(scripts.Script):
if self.upscale_force is None:
self.upscale_force = False
if shared.state.job_count > 0:
# logger.debug(f"Job count: {shared.state.job_count}")
self.face_restorer_visibility = shared.opts.data['restorer_visibility'] if 'restorer_visibility' in shared.opts.data.keys() else face_restorer_visibility
self.codeformer_weight = shared.opts.data['codeformer_weight'] if 'codeformer_weight' in shared.opts.data.keys() else codeformer_weight
self.mask_face = shared.opts.data['mask_face'] if 'mask_face' in shared.opts.data.keys() else mask_face
self.face_model = shared.opts.data['face_model'] if 'face_model' in shared.opts.data.keys() else face_model
logger.debug("*** Set Device")
set_Device(self.device)
@ -266,6 +311,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 +379,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 +460,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 +480,7 @@ class FaceSwapScript(scripts.Script):
source_folder = None,
source_imgs = None,
random_image = False,
detection_options=self.detection_options,
)
self.result = result
try:
@ -455,11 +503,14 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
order = 20000
def ui(self):
with ui_components.InputAccordion(False, label=f"{app_title}") as enable:
with (
gr.Accordion(f"{app_title}", open=False) if check_old_webui() else InputAccordion(False, label=f"{app_title}") as enable
):
# with ui_components.InputAccordion(False, label=f"{app_title}") as enable:
# with gr.Accordion(f"{app_title}", open=False):
# SD.Next fix
if get_SDNEXT():
# SD.Next or A1111 1.52:
if get_SDNEXT() or check_old_webui():
enable = gr.Checkbox(False, label="Enable")
# enable = gr.Checkbox(False, label="Enable", info=f"The Fast and Simple FaceSwap Extension - {version_flag}")
@ -467,9 +518,12 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
# TAB MAIN
msgs: dict = {
"extra_multiple_source": " | Сomparison grid as a result",
"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, random_image = ui_main.show(is_img2img=False, show_br=False, **msgs)
img, imgs, selected_tab, 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 +560,9 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
'imgs': imgs,
'random_image': random_image,
'upscale_force': upscale_force,
'det_thresh': det_thresh,
'det_maxnum': det_maxnum,
'selected_tab': selected_tab,
}
return args
@ -536,6 +593,13 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
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']:
reset_messaged()
@ -543,7 +607,10 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
return
global SWAPPER_MODELS_PATH
if args['selected_tab'] == "tab_single":
self.source = args['img']
else:
self.source = None
self.face_restorer_name = args['face_restorer_name']
self.upscaler_scale = args['upscaler_scale']
self.upscaler_visibility = args['upscaler_visibility']
@ -560,9 +627,14 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
self.select_source = args['select_source']
self.face_model = args['face_model']
self.source_folder = args['source_folder']
if args['selected_tab'] == "tab_single":
self.source_imgs = None
else:
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":
@ -604,6 +676,15 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
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
# Extract alpha channel
logger.debug(f"image = {image}")
if image.mode == 'RGBA':
_, _, _, alpha = image.split()
else:
alpha = None
logger.debug(f"alpha = {alpha}")
result, output, swapped = swap_face(
self.source,
image,
@ -622,11 +703,16 @@ 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:
image = result[0]
if len(result) > 1:
if hasattr(pp, 'extra_images'):
image = result[0]
pp.extra_images.extend(result[1:])
else:
grid = make_grid(result)
result.insert(0, grid)
image = grid
@ -638,6 +724,13 @@ class FaceSwapScriptExtras(scripts_postprocessing.ScriptPostprocessing):
else:
try:
pp.info["ReActor"] = True
if alpha is not None:
logger.debug(f"result = {result}")
result = result.convert("RGBA")
result.putalpha(alpha)
logger.debug(f"result_alpha = {result}")
pp.image = result
logger.status("---Done!---")
except Exception:

View File

@ -203,9 +203,12 @@ def get_facemodels():
def get_model_names(get_models):
models = get_models()
names = ["None"]
names = []
for x in models:
names.append(os.path.basename(x))
# Sort ignoring case during sort but retain in output
names.sort(key=str.lower)
names.insert(0, "None")
return names
def get_images_from_folder(path: str):

View File

@ -6,6 +6,7 @@ from typing import List, Union
import cv2
import numpy as np
from PIL import Image
from scipy import stats
import insightface
from insightface.app.common import Face
@ -66,6 +67,10 @@ class EnhancementOptions:
codeformer_weight: float = 0.5
upscale_force: bool = False
@dataclass
class DetectionOptions:
det_thresh: float = 0.5
det_maxnum: int = 0
MESSAGED_STOPPED = False
MESSAGED_SKIPPED = False
@ -106,12 +111,33 @@ TARGET_IMAGE_HASH = None
SOURCE_FACES_LIST = []
SOURCE_IMAGE_LIST_HASH = []
def clear_faces():
global SOURCE_FACES, SOURCE_IMAGE_HASH
SOURCE_FACES = None
SOURCE_IMAGE_HASH = None
logger.status("Source Images Hash has been reset (for Single Source or Face Model)")
def clear_faces_list():
global SOURCE_FACES_LIST, SOURCE_IMAGE_LIST_HASH
SOURCE_FACES_LIST = []
SOURCE_IMAGE_LIST_HASH = []
logger.status("Source Images Hash has been reset (for Multiple or Folder Source)")
def clear_faces_target():
global TARGET_FACES, TARGET_IMAGE_HASH
TARGET_FACES = None
TARGET_IMAGE_HASH = None
logger.status("Target Images Hash has been reset")
def clear_faces_all():
global SOURCE_FACES, SOURCE_IMAGE_HASH, SOURCE_FACES_LIST, SOURCE_IMAGE_LIST_HASH, TARGET_FACES, TARGET_IMAGE_HASH
SOURCE_FACES = None
SOURCE_IMAGE_HASH = None
TARGET_FACES = None
TARGET_IMAGE_HASH = None
SOURCE_FACES_LIST = []
SOURCE_IMAGE_LIST_HASH = []
logger.status("All Images Hash has been reset")
def getAnalysisModel():
global ANALYSIS_MODEL
@ -140,13 +166,14 @@ def restore_face(image: Image, enhancement_options: EnhancementOptions):
if enhancement_options.face_restorer is not None:
original_image = result_image.copy()
logger.status("Restoring the face with %s", enhancement_options.face_restorer.name())
numpy_image = np.array(result_image)
if enhancement_options.face_restorer.name() == "CodeFormer":
logger.status("Restoring the face with %s (weight: %s)", enhancement_options.face_restorer.name(), enhancement_options.codeformer_weight)
numpy_image = codeformer_model.codeformer.restore(
numpy_image, w=enhancement_options.codeformer_weight
)
else: # GFPGAN:
logger.status("Restoring the face with %s", enhancement_options.face_restorer.name())
numpy_image = gfpgan_model.gfpgan_fix_faces(numpy_image)
# numpy_image = enhancement_options.face_restorer.restore(numpy_image)
restored_image = Image.fromarray(numpy_image)
@ -270,13 +297,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 +326,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 +365,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 +441,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 +457,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 +481,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 +489,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 +504,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 +541,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,16 +549,16 @@ 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)]
if source_face_model is not None:
source_faces_index = [0]
source_faces = source_face_model
logger.status("Using Loaded Source Face Model...")
logger.status(f"Using Loaded Source Face Model: {face_model}")
else:
logger.error(f"Cannot load Face Model File: {face_model}.safetensors")
logger.error(f"Cannot load Face Model File: {face_model}")
else:
logger.error("Cannot detect any Source")
@ -555,7 +583,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 +591,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 +613,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 +627,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, det_size=(640, 640)):
if image is None:
error_msg = "Please load an Image"
logger.error(error_msg)
@ -610,20 +638,91 @@ 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)
if save_model:
logger.status("Building Face Model...")
face_model = analyze_faces(image)
face_model = analyze_faces(image, det_size)
if len(face_model) == 0:
det_size_half = half_det_size(det_size)
face_model = analyze_faces(image, det_size_half)
if face_model is not None and len(face_model) > 0:
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, is_api: bool = False):
faces = []
embeddings = []
images: List[Image.Image] = []
if not is_api:
images, images_names = get_images_from_list(images_list)
else:
images = images_list
for i,image in enumerate(images):
if not is_api:
logger.status(f"Building Face Model for {images_names[i]}...")
else:
logger.status(f"Building Face Model for Image {i+1}...")
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:
if not is_api:
logger.error(f"Embedding Shape Mismatch for {images_names[i]}, skipping")
else:
logger.error(f"Embedding Shape Mismatch for Image {i+1}, 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 +744,7 @@ def operate(
mask_face,
entire_mask_image,
enhancement_options,
detection_options,
):
result = target_img
face_swapper = getFaceSwapModel(model)
@ -656,7 +756,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 +767,7 @@ def operate(
if source_face is not None and wrong_gender == 0:
logger.status("Detecting Target Face, Index = %s", face_num)
target_face, wrong_gender, target_age, target_gender = get_face_single(target_img, target_faces, face_index=face_num, gender_target=gender_target)
target_face, wrong_gender, target_age, target_gender = get_face_single(target_img, target_faces, face_index=face_num, gender_target=gender_target, det_thresh=detection_options.det_thresh, det_maxnum=detection_options.det_maxnum)
if target_age != "None" or target_gender != "None":
logger.status("Detected: -%s- y.o. %s", target_age, target_gender)

View File

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

94
scripts/reactor_xyz.py Normal file
View File

@ -0,0 +1,94 @@
'''
Thanks @ledahu for contributing
'''
from modules import scripts
from modules.shared import opts
from scripts.reactor_helpers import (
get_model_names,
get_facemodels
)
# xyz_grid = [x for x in scripts.scripts_data if x.script_class.__module__ == "xyz_grid.py"][0].module
def find_module(module_names):
if isinstance(module_names, str):
module_names = [s.strip() for s in module_names.split(",")]
for data in scripts.scripts_data:
if data.script_class.__module__ in module_names and hasattr(data, "module"):
return data.module
return None
def bool_(string):
string = str(string)
if string in ["None", ""]:
return None
elif string.lower() in ["true", "1"]:
return True
elif string.lower() in ["false", "0"]:
return False
else:
raise ValueError(f"Could not convert string to boolean: {string}")
def choices_bool():
return ["False", "True"]
def choices_face_models():
return get_model_names(get_facemodels)
def float_applier(value_name:str, min_range:float = 0, max_range:float = 1):
"""
Returns a function that applies the given value to the given value_name in opts.data.
"""
def validate(value_name:str, value:str):
value = float(value)
# validate value
if not min_range == 0:
assert value >= min_range, f"Value {value} for {value_name} must be greater than or equal to {min_range}"
if not max_range == 1:
assert value <= max_range, f"Value {value} for {value_name} must be less than or equal to {max_range}"
def apply_float(p, x, xs):
validate(value_name, x)
opts.data[value_name] = float(x)
return apply_float
def bool_applier(value_name:str):
def apply_bool(p, x, xs):
x_normed = bool_(x)
opts.data[value_name] = x_normed
# print(f'normed = {x_normed}')
return apply_bool
def str_applier(value_name:str):
def apply_str(p, x, xs):
opts.data[value_name] = x
return apply_str
def add_axis_options(xyz_grid):
extra_axis_options = [
xyz_grid.AxisOption("[ReActor] CodeFormer Weight", float, float_applier("codeformer_weight", 0, 1)),
xyz_grid.AxisOption("[ReActor] Restorer Visibility", float, float_applier("restorer_visibility", 0, 1)),
xyz_grid.AxisOption("[ReActor] Face Mask Correction", str, bool_applier("mask_face"), choices=choices_bool),
xyz_grid.AxisOption("[ReActor] Face Models", str, str_applier("face_model"), choices=choices_face_models),
]
set_a = {opt.label for opt in xyz_grid.axis_options}
set_b = {opt.label for opt in extra_axis_options}
if set_a.intersection(set_b):
return
xyz_grid.axis_options.extend(extra_axis_options)
def run():
xyz_grid = find_module("xyz_grid.py, xy_grid.py")
if xyz_grid:
add_axis_options(xyz_grid)
# XYZ init:
try:
import modules.script_callbacks as script_callbacks
script_callbacks.on_before_ui(run)
except:
pass