Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf8ef5e9ec | |||
| 38f46d3f01 | |||
| 2e718ee4ae | |||
| d0c56ae6ef | |||
| 54adcc0fc6 | |||
| b11037173b | |||
| 6ba60f6332 | |||
| 6d69f69509 | |||
| 7830eb1da9 | |||
| aba00ba381 | |||
| 04a0a5c46c | |||
| 612cc43752 | |||
| 5b19333968 | |||
| 50db415069 | |||
| 61fc9269ed | |||
| 0499581305 | |||
| afcfc7d255 | |||
| f9fc0bbff1 | |||
| 054f693815 | |||
| 17db6ea2b4 | |||
| bfb4578c8b | |||
| 0a9d992544 | |||
| c835e97485 | |||
| e6592a11bf | |||
| 55b845c666 | |||
| db79243acc | |||
| 0db1f452fd | |||
| 76dbd57ad5 | |||
| b3ea4c8b93 | |||
| 9c935442ff | |||
| d7acc2468f | |||
| b7eebc619e | |||
| 9bb1c65db6 |
@@ -1,8 +1,4 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/psf/black
|
|
||||||
rev: 23.7.0
|
|
||||||
hooks:
|
|
||||||
- id: black
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.4.0
|
rev: v4.4.0
|
||||||
hooks:
|
hooks:
|
||||||
@@ -10,4 +6,8 @@ repos:
|
|||||||
- id: check-case-conflict
|
- id: check-case-conflict
|
||||||
- id: check-docstring-first
|
- id: check-docstring-first
|
||||||
- id: detect-private-key
|
- id: detect-private-key
|
||||||
- id: fix-byte-order-marker
|
- id: fix-byte-order-marker
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 23.7.0
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
@@ -1,3 +1,33 @@
|
|||||||
|
# 1.2.5
|
||||||
|
|
||||||
|
Allow seed selection in inpainting.
|
||||||
|
|
||||||
|
# 1.2.4
|
||||||
|
|
||||||
|
Fix default settings by marking only managed field as do_not_save.
|
||||||
|
|
||||||
|
See the discussion here : https://github.com/glucauze/sd-webui-faceswaplab/issues/62
|
||||||
|
|
||||||
|
# 1.2.3
|
||||||
|
|
||||||
|
Speed up ui : change the way default settings are manage by not storing them in ui-config.json
|
||||||
|
|
||||||
|
Migration : YOU NEED TO recreate ui-config.json (delete) or at least remove any faceswaplab reference to be able to use default settings again.
|
||||||
|
|
||||||
|
See this for explainations : https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/6109
|
||||||
|
|
||||||
|
# 1.2.2
|
||||||
|
|
||||||
|
+ Add NSFW filter option in settings (1 == disable)
|
||||||
|
+ Improve install speed
|
||||||
|
+ Install gpu requirements by default if --use-cpu is not used
|
||||||
|
+ Fix improved mask + color correction
|
||||||
|
+ Remove javascript, use https://github.com/w-e-w/sdwebui-close-confirmation-dialogue.git instead to prevent gradio from closing.
|
||||||
|
|
||||||
|
# 1.2.1 :
|
||||||
|
|
||||||
|
Add GPU support option : see https://github.com/glucauze/sd-webui-faceswaplab/pull/24
|
||||||
|
|
||||||
# 1.2.0 :
|
# 1.2.0 :
|
||||||
|
|
||||||
This version changes quite a few things.
|
This version changes quite a few things.
|
||||||
@@ -18,10 +48,14 @@ Bug fixes :
|
|||||||
|
|
||||||
In terms of the API, it is now possible to create a remote checkpoint and use it in units. See the example in client_api or the tests in the tests directory.
|
In terms of the API, it is now possible to create a remote checkpoint and use it in units. See the example in client_api or the tests in the tests directory.
|
||||||
|
|
||||||
|
See https://github.com/glucauze/sd-webui-faceswaplab/pull/19
|
||||||
|
|
||||||
# 1.1.2 :
|
# 1.1.2 :
|
||||||
|
|
||||||
+ Switch face checkpoint format from pkl to safetensors
|
+ Switch face checkpoint format from pkl to safetensors
|
||||||
|
|
||||||
|
See https://github.com/glucauze/sd-webui-faceswaplab/pull/4
|
||||||
|
|
||||||
## 1.1.1 :
|
## 1.1.1 :
|
||||||
|
|
||||||
+ Add settings for default inpainting prompts
|
+ Add settings for default inpainting prompts
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
# FaceSwapLab for a1111/Vlad
|
# FaceSwapLab for a1111/Vlad
|
||||||
|
|
||||||
Please read the documentation here : https://glucauze.github.io/sd-webui-faceswaplab/
|
V1.2.3 : Breaking change for settings, please read changelog.
|
||||||
|
|
||||||
|
Please read the documentation here : https://glucauze.github.io/sd-webui-faceswaplab/
|
||||||
|
|
||||||
|
You can also read the [doc discussion section](https://github.com/glucauze/sd-webui-faceswaplab/discussions/categories/guide-doc)
|
||||||
|
|
||||||
|
See [CHANGELOG.md](CHANGELOG.md) for changes in last versions.
|
||||||
|
|
||||||
FaceSwapLab is an extension for Stable Diffusion that simplifies face-swapping. It has evolved from sd-webui-faceswap and some part of sd-webui-roop. However, a substantial amount of the code has been rewritten to improve performance and to better manage masks.
|
FaceSwapLab is an extension for Stable Diffusion that simplifies face-swapping. It has evolved from sd-webui-faceswap and some part of sd-webui-roop. However, a substantial amount of the code has been rewritten to improve performance and to better manage masks.
|
||||||
|
|
||||||
@@ -14,7 +20,7 @@ While FaceSwapLab is still under development, it has reached a good level of sta
|
|||||||
|
|
||||||
In short:
|
In short:
|
||||||
|
|
||||||
+ **Ethical Guideline:** This extension should not be forked to create a public, easy way to bypass NSFW filtering. If you modify it for this purpose, keep it private, or you'll be banned.
|
+ **Ethical Guideline:** NSFW is now configurable due to performance issue. Please don't use this to do harm.
|
||||||
+ **License:** This software is distributed under the terms of the GNU Affero General Public License (AGPL), version 3 or later.
|
+ **License:** This software is distributed under the terms of the GNU Affero General Public License (AGPL), version 3 or later.
|
||||||
+ **Model License:** This software uses InsightFace's pre-trained models, which are available for non-commercial research purposes only.
|
+ **Model License:** This software uses InsightFace's pre-trained models, which are available for non-commercial research purposes only.
|
||||||
|
|
||||||
@@ -24,6 +30,61 @@ More on this here : https://glucauze.github.io/sd-webui-faceswaplab/
|
|||||||
|
|
||||||
+ Older versions of gradio don't work well with the extension. See this bug : https://github.com/glucauze/sd-webui-faceswaplab/issues/5
|
+ Older versions of gradio don't work well with the extension. See this bug : https://github.com/glucauze/sd-webui-faceswaplab/issues/5
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Here are some gifs to explain (non cherry picked, just random pictures) :
|
||||||
|
|
||||||
|
## Simple Usage (roop like)
|
||||||
|
|
||||||
|
This use codeformer on all faces (including non swapped)
|
||||||
|
|
||||||
|
[simple.webm](https://github.com/glucauze/sd-webui-faceswaplab/assets/137925069/de00b685-d441-44f9-bae3-71cd7abef113)
|
||||||
|
|
||||||
|
## Advanced options
|
||||||
|
|
||||||
|
This is use to improve results. This use upscaling and codeformer only on swapped faces
|
||||||
|
|
||||||
|
[advanced.webm](https://github.com/glucauze/sd-webui-faceswaplab/assets/137925069/50630311-bd25-487f-871b-0a44eecd435d)
|
||||||
|
|
||||||
|
## Inpainting
|
||||||
|
|
||||||
|
This add inpainting on faces :
|
||||||
|
|
||||||
|
[inpainting.webm](https://github.com/glucauze/sd-webui-faceswaplab/assets/137925069/3d3508e9-5be4-4566-8c41-8301b2d08355)
|
||||||
|
|
||||||
|
## Build and use checkpoints :
|
||||||
|
|
||||||
|
[build.webm](https://github.com/glucauze/sd-webui-faceswaplab/assets/137925069/e84e9a3c-840d-4536-9fbb-09ed256406d7)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Simple
|
||||||
|
|
||||||
|
1. Put a face in the reference.
|
||||||
|
2. Select a face number.
|
||||||
|
3. Select "Enable."
|
||||||
|
4. Select "CodeFormer" in **Global Post-Processing** tab.
|
||||||
|
|
||||||
|
Once you're happy with some results but want to improve, the next steps are to:
|
||||||
|
|
||||||
|
+ Use advanced settings in face units (which are not as complex as they might seem, it's basically fine tuning post-processing for each faces).
|
||||||
|
+ Use pre/post inpainting to tweak the image a bit for more natural results.
|
||||||
|
|
||||||
|
### Better
|
||||||
|
|
||||||
|
1. Put a face in the reference.
|
||||||
|
2. Select a face number.
|
||||||
|
3. Select "Enable."
|
||||||
|
|
||||||
|
4. In **Post-Processing** accordeon:
|
||||||
|
+ Select "CodeFormer"
|
||||||
|
+ Select "LDSR" or a faster model "003_realSR_BSRGAN_DFOWMFC_s64w8_SwinIR-L_x4_GAN" in upscaler. See [here for a list of upscalers](https://github.com/glucauze/sd-webui-faceswaplab/discussions/29).
|
||||||
|
+ Use sharpen, color_correction and improved mask
|
||||||
|
|
||||||
|
5. Disable "CodeFormer" in **Global Post-Processing** tab (otherwise it will be applied twice)
|
||||||
|
|
||||||
|
Don't hesitate to share config in the [discussion section](https://github.com/glucauze/sd-webui-faceswaplab/discussions).
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
+ **Face Unit Concept**: Similar to controlNet, the program introduces the concept of a face unit. You can configure up to 10 units (3 units are the default setting) in the program settings (sd).
|
+ **Face Unit Concept**: Similar to controlNet, the program introduces the concept of a face unit. You can configure up to 10 units (3 units are the default setting) in the program settings (sd).
|
||||||
@@ -32,6 +93,8 @@ More on this here : https://glucauze.github.io/sd-webui-faceswaplab/
|
|||||||
|
|
||||||
+ **Batch Processing**
|
+ **Batch Processing**
|
||||||
|
|
||||||
|
+ **GPU**
|
||||||
|
|
||||||
+ **Inpainting Fixes** : supports “only masked” and mask inpainting.
|
+ **Inpainting Fixes** : supports “only masked” and mask inpainting.
|
||||||
|
|
||||||
+ **Performance Improvements**: The overall performance of the software has been enhanced.
|
+ **Performance Improvements**: The overall performance of the software has been enhanced.
|
||||||
@@ -62,7 +125,7 @@ More on this here : https://glucauze.github.io/sd-webui-faceswaplab/
|
|||||||
|
|
||||||
+ **Upscaled Inswapper**: The program now includes an upscaled inswapper option, which improves results by incorporating upsampling, sharpness adjustment, and color correction before face is merged to the original image.
|
+ **Upscaled Inswapper**: The program now includes an upscaled inswapper option, which improves results by incorporating upsampling, sharpness adjustment, and color correction before face is merged to the original image.
|
||||||
|
|
||||||
+ **API with typing support** :
|
+ **API with typing support**
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
autoflake --in-place --remove-unused-variables -r --remove-all-unused-imports .
|
autoflake --in-place --remove-unused-variables -r --remove-all-unused-imports .
|
||||||
mypy --install-types
|
mypy --non-interactive --install-types
|
||||||
pre-commit run --all-files
|
pre-commit run --all-files
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class InpaintingOptions(BaseModel):
|
|||||||
inpainting_model: str = Field(
|
inpainting_model: str = Field(
|
||||||
description="Inpainting model", examples=["Current"], default="Current"
|
description="Inpainting model", examples=["Current"], default="Current"
|
||||||
)
|
)
|
||||||
|
inpainting_seed: int = Field(description="Inpainting Seed", ge=-1, default=-1)
|
||||||
|
|
||||||
|
|
||||||
class InswappperOptions(BaseModel):
|
class InswappperOptions(BaseModel):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
numpy==1.25.1
|
numpy
|
||||||
Pillow==10.0.0
|
Pillow
|
||||||
pydantic==1.10.9
|
pydantic
|
||||||
Requests==2.31.0
|
Requests
|
||||||
safetensors==0.3.1
|
safetensors>=0.3.1
|
||||||
|
|||||||
Binary file not shown.
@@ -16,6 +16,7 @@ gem "github-pages", "~> 228", group: :jekyll_plugins
|
|||||||
|
|
||||||
group :jekyll_plugins do
|
group :jekyll_plugins do
|
||||||
gem "webrick"
|
gem "webrick"
|
||||||
|
gem 'jekyll-toc'
|
||||||
end
|
end
|
||||||
|
|
||||||
# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem
|
# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||||
|
|||||||
@@ -190,6 +190,9 @@ GEM
|
|||||||
jekyll-seo-tag (~> 2.0)
|
jekyll-seo-tag (~> 2.0)
|
||||||
jekyll-titles-from-headings (0.5.3)
|
jekyll-titles-from-headings (0.5.3)
|
||||||
jekyll (>= 3.3, < 5.0)
|
jekyll (>= 3.3, < 5.0)
|
||||||
|
jekyll-toc (0.18.0)
|
||||||
|
jekyll (>= 3.9)
|
||||||
|
nokogiri (~> 1.12)
|
||||||
jekyll-watch (2.2.1)
|
jekyll-watch (2.2.1)
|
||||||
listen (~> 3.0)
|
listen (~> 3.0)
|
||||||
jemoji (0.12.0)
|
jemoji (0.12.0)
|
||||||
@@ -256,6 +259,7 @@ DEPENDENCIES
|
|||||||
github-pages (~> 228)
|
github-pages (~> 228)
|
||||||
http_parser.rb (~> 0.6.0)
|
http_parser.rb (~> 0.6.0)
|
||||||
jekyll (~> 3.9.3)
|
jekyll (~> 3.9.3)
|
||||||
|
jekyll-toc
|
||||||
minima (~> 2.5.1)
|
minima (~> 2.5.1)
|
||||||
tzinfo (>= 1, < 3)
|
tzinfo (>= 1, < 3)
|
||||||
tzinfo-data
|
tzinfo-data
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ author:
|
|||||||
minima:
|
minima:
|
||||||
skin: dark
|
skin: dark
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- jekyll-toc
|
||||||
|
|
||||||
# Exclude from processing.
|
# Exclude from processing.
|
||||||
# The following items will not be processed, by default.
|
# The following items will not be processed, by default.
|
||||||
# Any item listed under the `exclude:` key here will be automatically added to
|
# Any item listed under the `exclude:` key here will be automatically added to
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
layout: default
|
||||||
|
---
|
||||||
|
<article class="post">
|
||||||
|
|
||||||
|
<header class="post-header">
|
||||||
|
<h1 class="post-title">{{ page.title | escape }}</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="post-content">
|
||||||
|
{{ content | toc }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</article>
|
||||||
@@ -2,9 +2,39 @@
|
|||||||
layout: page
|
layout: page
|
||||||
title: Documentation
|
title: Documentation
|
||||||
permalink: /doc/
|
permalink: /doc/
|
||||||
|
toc: true
|
||||||
---
|
---
|
||||||
|
|
||||||
# Main Interface
|
You can also read the [doc discussion section](https://github.com/glucauze/sd-webui-faceswaplab/discussions/categories/guide-doc)
|
||||||
|
|
||||||
|
## TLDR: I Just Want Good Results:
|
||||||
|
|
||||||
|
1. Put a face in the reference.
|
||||||
|
2. Select a face number.
|
||||||
|
3. Select "Enable."
|
||||||
|
4. Select "CodeFormer" in global Post-Processing.
|
||||||
|
|
||||||
|
Once you're happy with some results but want to improve, the next steps are to:
|
||||||
|
|
||||||
|
+ Use advanced settings in face units (which are not as complex as they might seem, it's basically fine tuning post-processing for each faces).
|
||||||
|
+ Use pre/post inpainting to tweak the image a bit for more natural results.
|
||||||
|
|
||||||
|
### Getting better results
|
||||||
|
|
||||||
|
1. Put a face in the reference.
|
||||||
|
2. Select a face number.
|
||||||
|
3. Select "Enable."
|
||||||
|
|
||||||
|
4. In **Post-Processing** accordeon:
|
||||||
|
+ Select "CodeFormer"
|
||||||
|
+ Select "LDSR" or a faster model "003_realSR_BSRGAN_DFOWMFC_s64w8_SwinIR-L_x4_GAN" in upscaler. See [here for a list of upscalers](https://github.com/glucauze/sd-webui-faceswaplab/discussions/29).
|
||||||
|
+ Use sharpen, color_correction and improved mask
|
||||||
|
|
||||||
|
5. Disable "CodeFormer" in **Global Post-Processing** tab (otherwise it will be applied twice)
|
||||||
|
|
||||||
|
Don't hesitate to share config in the [discussion section](https://github.com/glucauze/sd-webui-faceswaplab/discussions).
|
||||||
|
|
||||||
|
## Main Interface
|
||||||
|
|
||||||
Here is the interface for FaceSwap Lab. It is available in the form of an accordion in both img2img and txt2img.
|
Here is the interface for FaceSwap Lab. It is available in the form of an accordion in both img2img and txt2img.
|
||||||
|
|
||||||
@@ -12,7 +42,7 @@ You can configure several units, each allowing you to replace a face. Here, 3 un
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### Face Unit
|
### Face Unit
|
||||||
|
|
||||||
The first thing to do is to activate the unit with **'enable'** if you want to use it.
|
The first thing to do is to activate the unit with **'enable'** if you want to use it.
|
||||||
|
|
||||||
@@ -25,7 +55,7 @@ Here are the main options for configuring a unit:
|
|||||||
|
|
||||||
**You must always have at least one reference face OR a checkpoint. If both are selected, the checkpoint will be used and the reference ignored.**
|
**You must always have at least one reference face OR a checkpoint. If both are selected, the checkpoint will be used and the reference ignored.**
|
||||||
|
|
||||||
#### Similarity
|
### Similarity
|
||||||
|
|
||||||
Always check for errors in the SD console. In particular, the absence of a reference face or a checkpoint can trigger errors.
|
Always check for errors in the SD console. In particular, the absence of a reference face or a checkpoint can trigger errors.
|
||||||
|
|
||||||
@@ -37,7 +67,7 @@ Always check for errors in the SD console. In particular, the absence of a refer
|
|||||||
+ **Same gender:** the gender of the source face will be determined and only faces of the same gender will be considered.
|
+ **Same gender:** the gender of the source face will be determined and only faces of the same gender will be considered.
|
||||||
+ **Sort by size:** faces will be sorted from largest to smallest.
|
+ **Sort by size:** faces will be sorted from largest to smallest.
|
||||||
|
|
||||||
#### Pre-Inpainting :
|
### Pre-Inpainting
|
||||||
|
|
||||||
This part is applied BEFORE face swapping and only on matching faces.
|
This part is applied BEFORE face swapping and only on matching faces.
|
||||||
|
|
||||||
@@ -47,7 +77,7 @@ You can use a specific model for the replacement, different from the model used
|
|||||||
|
|
||||||
For inpainting to be active, denoising must be greater than 0 and the Inpainting When option must be set to:
|
For inpainting to be active, denoising must be greater than 0 and the Inpainting When option must be set to:
|
||||||
|
|
||||||
#### Post-Processing & Advanced Masks Options : (upscaled inswapper)
|
### Post-Processing & Advanced Masks Options : (upscaled inswapper)
|
||||||
|
|
||||||
By default, these settings are disabled, but you can use the global settings to modify the default behavior. These options are called "Default Upscaled swapper..."
|
By default, these settings are disabled, but you can use the global settings to modify the default behavior. These options are called "Default Upscaled swapper..."
|
||||||
|
|
||||||
@@ -59,13 +89,13 @@ The purpose of this feature is to enhance the quality of the face in the final i
|
|||||||
|
|
||||||
The upscaled inswapper is disabled by default. It can be enabled in the sd options. Understanding the various steps helps explain why results may be unsatisfactory and how to address this issue.
|
The upscaled inswapper is disabled by default. It can be enabled in the sd options. Understanding the various steps helps explain why results may be unsatisfactory and how to address this issue.
|
||||||
|
|
||||||
+ **upscaler** : LDSR if None. The LDSR option generally gives the best results but at the expense of a lot of computational time. You should test other models to form an opinion. The 003_realSR_BSRGAN_DFOWMFC_s64w8_SwinIR-L_x4_GAN model seems to give good results in a reasonable amount of time. It's not possible to disable upscaling, but it is possible to choose LANCZOS for speed if Codeformer is enabled in the upscaled inswapper. The result is generally satisfactory.
|
+ **upscaler** : LDSR if None. The LDSR option generally gives the best results but at the expense of a lot of computational time. You should test other models to form an opinion. The [003_realSR_BSRGAN_DFOWMFC_s64w8_SwinIR-L_x4_GAN](https://github.com/JingyunLiang/SwinIR/releases/download/v0.0/003_realSR_BSRGAN_DFOWMFC_s64w8_SwinIR-L_x4_GAN.pth) model seems to give good results in a reasonable amount of time. It's not possible to disable upscaling, but it is possible to choose LANCZOS for speed if Codeformer is enabled in the upscaled inswapper. The result is generally satisfactory. You can check [here for an upscaler database](https://upscale.wiki/wiki/Model_Database) and [here for some comparison](https://phhofm.github.io/upscale/favorites.html). It is a test and try process.
|
||||||
+ **restorer** : The face restorer to be used if necessary. Codeformer generally gives good results.
|
+ **restorer** : The face restorer to be used if necessary. Codeformer generally gives good results.
|
||||||
+ **sharpening** can provide more natural results, but it may also add artifacts. The same goes for **color correction**. By default, these options are set to False.
|
+ **sharpening** can provide more natural results, but it may also add artifacts. The same goes for **color correction**. By default, these options are set to False.
|
||||||
+ **improved mask:** The segmentation mask for the upscaled swapper is designed to avoid the square mask and prevent degradation of the non-face parts of the image. It is based on the Codeformer implementation. If "Use improved segmented mask (use pastenet to mask only the face)" and "upscaled inswapper" are checked in the settings, the mask will only cover the face, and will not be squared. However, depending on the image, this might introduce different types of problems such as artifacts on the border of the face.
|
+ **improved mask:** The segmentation mask for the upscaled swapper is designed to avoid the square mask and prevent degradation of the non-face parts of the image. It is based on the Codeformer implementation. If "Use improved segmented mask (use pastenet to mask only the face)" and "upscaled inswapper" are checked in the settings, the mask will only cover the face, and will not be squared. However, depending on the image, this might introduce different types of problems such as artifacts on the border of the face.
|
||||||
+ **erosion factor:** it is possible to adjust the mask erosion parameters using the erosion settings. The higher this setting is, the more the mask is reduced.
|
+ **erosion factor:** it is possible to adjust the mask erosion parameters using the erosion settings. The higher this setting is, the more the mask is reduced.
|
||||||
|
|
||||||
#### Post-Inpainting :
|
### Post-Inpainting
|
||||||
|
|
||||||
This part is applied AFTER face swapping and only on matching faces.
|
This part is applied AFTER face swapping and only on matching faces.
|
||||||
|
|
||||||
@@ -122,7 +152,7 @@ The checkpoint can then be used in the main interface (use refresh button)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Processing order:
|
## Processing order
|
||||||
|
|
||||||
The extension is activated after all other extensions have been processed. During the execution, several steps take place.
|
The extension is activated after all other extensions have been processed. During the execution, several steps take place.
|
||||||
|
|
||||||
@@ -157,10 +187,56 @@ The API is documented in the FaceSwapLab tags in the http://localhost:7860/docs
|
|||||||
You don't have to use the api_utils.py file and pydantic types, but it can save time.
|
You don't have to use the api_utils.py file and pydantic types, but it can save time.
|
||||||
|
|
||||||
|
|
||||||
|
## Experimental GPU support
|
||||||
|
|
||||||
|
You need a sufficiently recent version of your SD environment. Using the GPU has a lot of little drawbacks to understand, but the performance gain is substantial.
|
||||||
|
|
||||||
|
In Version 1.2.1, the ability to use the GPU has been added, a setting that can be configured in SD at startup. Currently, this feature is only supported on Windows and Linux, as the necessary dependencies for Mac have not been included.
|
||||||
|
|
||||||
|
The `--faceswaplab_gpu` option in SD can be added to the args in webui-user.sh or webui-user.bat. **There is also an option in SD settings**.
|
||||||
|
|
||||||
|
The model stays loaded in VRAM and won't be unloaded after each use. As of now, I don't know a straightforward way to handle this, so it will occupy space continuously. If your system's VRAM is limited, enabling this option might not be advisable.
|
||||||
|
|
||||||
|
A change has also been made that could lead to some ripple effects. Previously, detection parameters such as det_size and det_thresh were automatically adjusted when a second model was loaded. This is no longer possible, so these parameters have been moved to the global settings to enable face detection.
|
||||||
|
|
||||||
|
The `auto_det_size` option emulates the old behavior. It has no difference on CPU. BUT it will load the model twice if you use GPU. That means more VRAM comsumption and twice the initial load time. If you don't want that, you can use a det_size of 320, read below.
|
||||||
|
|
||||||
|
If you enabled GPU and you are sure you avec a CUDA compatible card and the model keep using CPU provider, please checks that you have onnxruntime-gpu installed.
|
||||||
|
|
||||||
|
### SD.NEXT and GPU
|
||||||
|
|
||||||
|
Please read carefully.
|
||||||
|
|
||||||
|
Using the GPU requires the use of the onnxruntime-gpu>=1.15.0 dependency. For the moment, this conflicts with older SD.Next dependencies (tensorflow, which uses numpy and potentially rembg). You will need to check numpy>=1.24.2 and tensorflow>=2.13.0.
|
||||||
|
|
||||||
|
You should therefore be able to debug a little before activating the option. If you don't feel up to it, it's best not to use it.
|
||||||
|
|
||||||
|
The first time the swap is used, the program will continue to use the CPU, but will offer to install the GPU. You will then need to restart. This is due to the optimizations made by SD.Next to the installation scripts.
|
||||||
|
|
||||||
|
For SD.Next, the best is to install dependencies manually :
|
||||||
|
|
||||||
|
on windows :
|
||||||
|
|
||||||
|
```shell
|
||||||
|
.\venv\Scripts\activate
|
||||||
|
cd .\extensions\sd-webui-faceswaplab\
|
||||||
|
pip install .\requirements-gpu.txt
|
||||||
|
```
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
You can change the program's default behavior in your webui's global settings (FaceSwapLab section in settings). This is particularly useful if you want to have default options for inpainting or for post-processsing, for example.
|
You can change the program's default behavior in your webui's global settings (FaceSwapLab section in settings). This is particularly useful if you want to have default options for inpainting or for post-processsing, for example.
|
||||||
|
|
||||||
The interface must be restarted to take the changes into account. Sometimes you have to reboot the entire webui server.
|
The interface must be restarted to take the changes into account. Sometimes you have to reboot the entire webui server.
|
||||||
|
|
||||||
There may be display bugs on some radio buttons that may not display the value (Codeformer might look disabled for instance). Check the logs to ensure that the transformation has been applied.
|
There may be display bugs on some radio buttons that may not display the value (Codeformer might look disabled for instance). Check the logs to ensure that the transformation has been applied.
|
||||||
|
|
||||||
|
### det_size and det_thresh (detection accuracy and performances)
|
||||||
|
|
||||||
|
V1.2.1 : A change has been made that could lead to some ripple effects. Previously, detection parameters such as det_size and det_thresh were automatically adjusted when a second model was loaded. This is no longer possible, so these parameters have been moved to the global settings to enable face detection.
|
||||||
|
|
||||||
|
The `auto_det_size` option emulates the old behavior. It has no difference on CPU. BUT it will load the model twice if you use GPU. That means more VRAM comsumption and twice the initial load time. If you don't want that, you can use a det_size of 320, read below.
|
||||||
|
|
||||||
|
The `det_size` parameter defines the size of the detection area, controlling the spatial resolution at which faces are detected within an image. A larger detection size might capture more facial details, enhancing accuracy but potentially impacting processing speed. Conversely, the `det_thresh` parameter represents the detection threshold, serving as a sensitivity control for face detection. A higher threshold value leads to more conservative detection, capturing only the most prominent faces, while a lower threshold might detect more faces but could also result in more false positives.
|
||||||
|
|
||||||
|
It has been observed that a det_size value of 320 is more effective at detecting large faces. If there are issues with detecting large faces, switching to this value is recommended, though it might result in a loss of some quality.
|
||||||
|
|||||||
+12
-5
@@ -2,6 +2,7 @@
|
|||||||
layout: page
|
layout: page
|
||||||
title: FAQ
|
title: FAQ
|
||||||
permalink: /faq/
|
permalink: /faq/
|
||||||
|
toc: true
|
||||||
---
|
---
|
||||||
|
|
||||||
Our issue tracker often contains requests that may originate from a misunderstanding of the software's functionality. We aim to address these queries; however, due to time constraints, we may not be able to respond to each request individually. This FAQ section serves as a preliminary source of information for commonly raised concerns. We recommend reviewing these before submitting an issue.
|
Our issue tracker often contains requests that may originate from a misunderstanding of the software's functionality. We aim to address these queries; however, due to time constraints, we may not be able to respond to each request individually. This FAQ section serves as a preliminary source of information for commonly raised concerns. We recommend reviewing these before submitting an issue.
|
||||||
@@ -71,6 +72,16 @@ The quality of results is inherently tied to the capabilities of the model and c
|
|||||||
|
|
||||||
Consider this extension as a low-cost alternative to more sophisticated tools like Lora, or as an addition to such tools. It's important to **maintain realistic expectations of the results** provided by this extension.
|
Consider this extension as a low-cost alternative to more sophisticated tools like Lora, or as an addition to such tools. It's important to **maintain realistic expectations of the results** provided by this extension.
|
||||||
|
|
||||||
|
#### Why is a face not detected?
|
||||||
|
|
||||||
|
Face detection might be influenced by various factors and settings, particularly the det_size and det_thresh parameters. Here's how these could affect detection:
|
||||||
|
|
||||||
|
+ Detection Size (det_size): If the detection size is set too small, it may not capture large faces adequately. A value of 320 has been found to be more effective for detecting large faces, though it might result in a loss of some quality.
|
||||||
|
|
||||||
|
+ Detection Threshold (det_thresh): If the threshold is set too high, it can make the detection more conservative, capturing only the most prominent faces. A lower threshold might detect more faces but could also result in more false positives.
|
||||||
|
|
||||||
|
If a face is not being detected, adjusting these parameters might solve the issue. Try increasing the det_size if large faces are the problem, or experiment with different det_thresh values to find the balance that works best for your specific case.
|
||||||
|
|
||||||
|
|
||||||
#### Issue: Incorrect Gender Detection
|
#### Issue: Incorrect Gender Detection
|
||||||
|
|
||||||
@@ -78,11 +89,7 @@ The gender detection functionality is handled by the underlying analysis model.
|
|||||||
|
|
||||||
#### Why isn't GPU support included?
|
#### Why isn't GPU support included?
|
||||||
|
|
||||||
While implementing GPU support may seem straightforward, simply requiring a modification to the onnxruntime implementation and a change in providers in the swapper, there are reasons we haven't included it as a standard option.
|
GPU is supported via an option see [documentation](../doc/). This is expermental, use it carefully.
|
||||||
|
|
||||||
The primary consideration is the substantial VRAM usage of the SD models. Integrating the model on the GPU doesn't result in significant performance gains with the current state of the software. Moreover, the GPU support becomes truly beneficial when processing large numbers of frames or video. However, our experience indicates that this tends to cause more issues than it resolves.
|
|
||||||
|
|
||||||
Consequently, requests for GPU support as a standard feature will not be considered.
|
|
||||||
|
|
||||||
#### What is the 'Upscaled Inswapper' Option in SD FaceSwapLab?
|
#### What is the 'Upscaled Inswapper' Option in SD FaceSwapLab?
|
||||||
|
|
||||||
|
|||||||
+1
-3
@@ -20,7 +20,7 @@ While FaceSwapLab is still under development, it has reached a good level of sta
|
|||||||
|
|
||||||
In short:
|
In short:
|
||||||
|
|
||||||
+ **Ethical Guideline:** This extension should not be forked to create a public, easy way to circumvent NSFW filtering.
|
+ **Ethical Guideline:** This extension is **not intended to facilitate the creation of not safe for work (NSFW) or non-consensual deepfake content**. Its purpose is to bring consistency to image creation, making it easier to repair existing images, or bring characters back to life.
|
||||||
+ **License:** This software is distributed under the terms of the GNU Affero General Public License (AGPL), version 3 or later.
|
+ **License:** This software is distributed under the terms of the GNU Affero General Public License (AGPL), version 3 or later.
|
||||||
+ **Model License:** This software uses InsightFace's pre-trained models, which are available for non-commercial research purposes only.
|
+ **Model License:** This software uses InsightFace's pre-trained models, which are available for non-commercial research purposes only.
|
||||||
|
|
||||||
@@ -28,8 +28,6 @@ In short:
|
|||||||
|
|
||||||
This extension is **not intended to facilitate the creation of not safe for work (NSFW) or non-consensual deepfake content**. Its purpose is to bring consistency to image creation, making it easier to repair existing images, or bring characters back to life.
|
This extension is **not intended to facilitate the creation of not safe for work (NSFW) or non-consensual deepfake content**. Its purpose is to bring consistency to image creation, making it easier to repair existing images, or bring characters back to life.
|
||||||
|
|
||||||
While the code for this extension is licensed under the AGPL in compliance with models and other source materials, it's important to stress that **we strongly discourage any attempts to fork this project to create an uncensored version**. Any modifications to the code to enable the production of such content would be contrary to the ethical guidelines we advocate for.
|
|
||||||
|
|
||||||
We will comply with European regulations regarding this type of software. As required by law, the code may include both visible and invisible watermarks. If your local laws prohibit the use of this extension, you should not use it.
|
We will comply with European regulations regarding this type of software. As required by law, the code may include both visible and invisible watermarks. If your local laws prohibit the use of this extension, you should not use it.
|
||||||
|
|
||||||
From an ethical perspective, the main goal of this extension is to generate consistent images by swapping faces. It's important to note that we've done our best to integrate censorship features. However, when users can access the source code, they might bypass these censorship measures. That's why we urge users to use this extension responsibly and avoid any malicious use. We emphasize the importance of respecting people's privacy and consent when swapping faces in images. We discourage any activities that could harm others, invade their privacy, or negatively affect their well-being.
|
From an ethical perspective, the main goal of this extension is to generate consistent images by swapping faces. It's important to note that we've done our best to integrate censorship features. However, when users can access the source code, they might bypass these censorship measures. That's why we urge users to use this extension responsibly and avoid any malicious use. We emphasize the importance of respecting people's privacy and consent when swapping faces in images. We discourage any activities that could harm others, invade their privacy, or negatively affect their well-being.
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ permalink: /install/
|
|||||||
|
|
||||||
The extension runs mainly on the CPU to avoid the use of VRAM. However, it is recommended to follow the specifications recommended by sd/a1111 with regard to prerequisites. At the time of writing, a version of python lower than 11 is preferable (even if it works with python 3.11, model loading and performance may fall short of expectations).
|
The extension runs mainly on the CPU to avoid the use of VRAM. However, it is recommended to follow the specifications recommended by sd/a1111 with regard to prerequisites. At the time of writing, a version of python lower than 11 is preferable (even if it works with python 3.11, model loading and performance may fall short of expectations).
|
||||||
|
|
||||||
|
Older versions of gradio don’t work well with the extension. See this bug report : https://github.com/glucauze/sd-webui-faceswaplab/issues/5. It has been tested on 3.32.0
|
||||||
|
|
||||||
### Windows-User : Visual Studio ! Don't neglect this !
|
### Windows-User : Visual Studio ! Don't neglect this !
|
||||||
|
|
||||||
Before beginning the installation process, if you are using Windows, you need to install this requirement:
|
Before beginning the installation process, if you are using Windows, you need to install this requirement:
|
||||||
@@ -18,6 +20,12 @@ Before beginning the installation process, if you are using Windows, you need to
|
|||||||
|
|
||||||
3. OR if you don't want to install either the full Visual Studio suite or the VS C++ Build Tools: Follow the instructions provided in section VIII of the documentation.
|
3. OR if you don't want to install either the full Visual Studio suite or the VS C++ Build Tools: Follow the instructions provided in section VIII of the documentation.
|
||||||
|
|
||||||
|
## SD.Next / Vladmantic
|
||||||
|
|
||||||
|
SD.Next loading optimizations in relation to extension installation scripts can sometimes cause problems. This is particularly the case if you copy the script without installing it via the interface.
|
||||||
|
|
||||||
|
If you get an error after startup, try restarting the server.
|
||||||
|
|
||||||
## Manual Install
|
## Manual Install
|
||||||
|
|
||||||
To install the extension, follow the steps below:
|
To install the extension, follow the steps below:
|
||||||
|
|||||||
+54
-24
@@ -1,36 +1,66 @@
|
|||||||
import launch
|
import launch
|
||||||
import os
|
import os
|
||||||
import pkg_resources
|
|
||||||
import sys
|
import sys
|
||||||
|
import pkg_resources
|
||||||
|
from packaging.version import parse
|
||||||
|
|
||||||
|
|
||||||
req_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "requirements.txt")
|
def check_install() -> None:
|
||||||
|
use_gpu = True
|
||||||
|
|
||||||
print("Checking faceswaplab requirements")
|
if use_gpu and sys.platform != "darwin":
|
||||||
with open(req_file) as file:
|
print("Faceswaplab : Use GPU requirements")
|
||||||
for package in file:
|
req_file = os.path.join(
|
||||||
|
os.path.dirname(os.path.realpath(__file__)), "requirements-gpu.txt"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print("Faceswaplab : Use CPU requirements")
|
||||||
|
req_file = os.path.join(
|
||||||
|
os.path.dirname(os.path.realpath(__file__)), "requirements.txt"
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_installed(package: str) -> bool:
|
||||||
|
package_name = package.split("==")[0].split(">=")[0].strip()
|
||||||
try:
|
try:
|
||||||
python = sys.executable
|
installed_version = parse(
|
||||||
package = package.strip()
|
pkg_resources.get_distribution(package_name).version
|
||||||
|
)
|
||||||
|
except pkg_resources.DistributionNotFound:
|
||||||
|
return False
|
||||||
|
|
||||||
if not launch.is_installed(package.split("==")[0]):
|
if "==" in package:
|
||||||
print(f"Install {package}")
|
required_version = parse(package.split("==")[1])
|
||||||
launch.run_pip(
|
return installed_version == required_version
|
||||||
f"install {package}", f"sd-webui-faceswaplab requirement: {package}"
|
elif ">=" in package:
|
||||||
)
|
required_version = parse(package.split(">=")[1])
|
||||||
elif "==" in package:
|
return installed_version >= required_version
|
||||||
package_name, package_version = package.split("==")
|
else:
|
||||||
installed_version = pkg_resources.get_distribution(package_name).version
|
if package_name == "opencv-python":
|
||||||
if installed_version != package_version:
|
return launch.is_installed(package_name) or launch.is_installed("cv2")
|
||||||
print(
|
return launch.is_installed(package_name)
|
||||||
f"Install {package}, {installed_version} vs {package_version}"
|
|
||||||
)
|
print("Checking faceswaplab requirements")
|
||||||
|
with open(req_file) as file:
|
||||||
|
for package in file:
|
||||||
|
try:
|
||||||
|
package = package.strip()
|
||||||
|
|
||||||
|
if not is_installed(package):
|
||||||
|
print(f"Install {package}")
|
||||||
launch.run_pip(
|
launch.run_pip(
|
||||||
f"install {package}",
|
f"install {package}",
|
||||||
f"sd-webui-faceswaplab requirement: changing {package_name} version from {installed_version} to {package_version}",
|
f"sd-webui-faceswaplab requirement: {package}",
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
print(f"Warning: Failed to install {package}, faceswaplab will not work.")
|
print(
|
||||||
raise e
|
f"Warning: Failed to install {package}, faceswaplab may not work. Try to restart server or install dependencies manually."
|
||||||
|
)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
import timeit
|
||||||
|
|
||||||
|
check_time = timeit.timeit(check_install, number=1)
|
||||||
|
print(check_time)
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
window.onbeforeunload = function() {
|
|
||||||
// Prevent the stable diffusion window from being closed by mistake
|
|
||||||
return "Are you sure ?";
|
|
||||||
};
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[{"analyzerName":"intellisense-members-lstm-pylance","languageName":"python","identity":{"modelId":"E61945A9A512ED5E1A3EE3F1A2365B88F8FE","outputId":"E4E9EADA96734F01970E616FAB2FAC19","modifiedTimeUtc":"2020-08-11T14:06:50.811Z"},"filePath":"E61945A9A512ED5E1A3EE3F1A2365B88F8FE_E4E9EADA96734F01970E616FAB2FAC19","lastAccessTimeUtc":"2023-08-14T21:58:14.988Z"}]
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
cython
|
||||||
|
dill
|
||||||
|
ifnude
|
||||||
|
insightface==0.7.3
|
||||||
|
onnx>=1.14.0
|
||||||
|
protobuf>=3.20.2
|
||||||
|
opencv-python
|
||||||
|
pandas
|
||||||
|
pydantic
|
||||||
|
safetensors
|
||||||
|
onnxruntime>=1.15.0
|
||||||
|
onnxruntime-gpu>=1.15.0
|
||||||
+3
-2
@@ -1,9 +1,10 @@
|
|||||||
|
protobuf>=3.20.2
|
||||||
cython
|
cython
|
||||||
dill
|
dill
|
||||||
ifnude
|
ifnude
|
||||||
insightface==0.7.3
|
insightface==0.7.3
|
||||||
onnx==1.14.0
|
onnx>=1.14.0
|
||||||
onnxruntime==1.15.1
|
onnxruntime>=1.15.0
|
||||||
opencv-python
|
opencv-python
|
||||||
pandas
|
pandas
|
||||||
pydantic
|
pydantic
|
||||||
|
|||||||
+5
-24
@@ -2,11 +2,10 @@ import os
|
|||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from scripts.faceswaplab_utils.faceswaplab_logging import logger
|
from scripts.faceswaplab_utils.faceswaplab_logging import logger
|
||||||
from scripts.faceswaplab_swapping.swapper import is_sha1_matching
|
|
||||||
from scripts.faceswaplab_utils.models_utils import get_models
|
|
||||||
from scripts.faceswaplab_globals import *
|
from scripts.faceswaplab_globals import *
|
||||||
from packaging import version
|
from packaging import version
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
from scripts.faceswaplab_utils.models_utils import check_model
|
||||||
|
|
||||||
ALREADY_DONE = False
|
ALREADY_DONE = False
|
||||||
|
|
||||||
@@ -17,13 +16,11 @@ def check_configuration() -> None:
|
|||||||
if ALREADY_DONE:
|
if ALREADY_DONE:
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(f"FaceSwapLab {VERSION_FLAG} Config :")
|
|
||||||
|
|
||||||
# This has been moved here due to pb with sdnext in install.py not doing what a1111 is doing.
|
# This has been moved here due to pb with sdnext in install.py not doing what a1111 is doing.
|
||||||
models_dir = MODELS_DIR
|
models_dir = MODELS_DIR
|
||||||
faces_dir = FACES_DIR
|
faces_dir = FACES_DIR
|
||||||
|
|
||||||
model_url = "https://huggingface.co/henryruhs/roop/resolve/main/inswapper_128.onnx"
|
model_url = "https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128.onnx"
|
||||||
model_name = os.path.basename(model_url)
|
model_name = os.path.basename(model_url)
|
||||||
model_path = os.path.join(models_dir, model_name)
|
model_path = os.path.join(models_dir, model_name)
|
||||||
|
|
||||||
@@ -48,12 +45,9 @@ def check_configuration() -> None:
|
|||||||
os.makedirs(models_dir, exist_ok=True)
|
os.makedirs(models_dir, exist_ok=True)
|
||||||
os.makedirs(faces_dir, exist_ok=True)
|
os.makedirs(faces_dir, exist_ok=True)
|
||||||
|
|
||||||
if not is_sha1_matching(model_path, EXPECTED_INSWAPPER_SHA1):
|
if not os.path.exists(model_path):
|
||||||
logger.error(
|
download(model_url, model_path)
|
||||||
"Suspicious sha1 for model %s, check the model is valid or has been downloaded adequately. Should be %s",
|
check_model()
|
||||||
model_path,
|
|
||||||
EXPECTED_INSWAPPER_SHA1,
|
|
||||||
)
|
|
||||||
|
|
||||||
gradio_version = pkg_resources.get_distribution("gradio").version
|
gradio_version = pkg_resources.get_distribution("gradio").version
|
||||||
|
|
||||||
@@ -63,17 +57,4 @@ def check_configuration() -> None:
|
|||||||
gradio_version,
|
gradio_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not os.path.exists(model_path):
|
|
||||||
download(model_url, model_path)
|
|
||||||
|
|
||||||
def print_infos() -> None:
|
|
||||||
logger.info("FaceSwapLab config :")
|
|
||||||
logger.info("+ MODEL DIR : %s", models_dir)
|
|
||||||
models = get_models()
|
|
||||||
logger.info("+ MODELS: %s", models)
|
|
||||||
logger.info("+ FACES DIR : %s", faces_dir)
|
|
||||||
logger.info("+ ANALYZER DIR : %s", ANALYZER_DIR)
|
|
||||||
|
|
||||||
print_infos()
|
|
||||||
|
|
||||||
ALREADY_DONE = True
|
ALREADY_DONE = True
|
||||||
|
|||||||
+33
-24
@@ -1,29 +1,37 @@
|
|||||||
|
from scripts.configure import check_configuration
|
||||||
|
from scripts.faceswaplab_utils.sd_utils import get_sd_option
|
||||||
|
|
||||||
|
check_configuration()
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from scripts import faceswaplab_globals
|
from scripts import faceswaplab_globals
|
||||||
from scripts.configure import check_configuration
|
|
||||||
from scripts.faceswaplab_api import faceswaplab_api
|
from scripts.faceswaplab_api import faceswaplab_api
|
||||||
from scripts.faceswaplab_postprocessing import upscaling
|
from scripts.faceswaplab_postprocessing import upscaling
|
||||||
from scripts.faceswaplab_settings import faceswaplab_settings
|
from scripts.faceswaplab_settings import faceswaplab_settings
|
||||||
from scripts.faceswaplab_swapping import swapper
|
from scripts.faceswaplab_swapping import swapper
|
||||||
from scripts.faceswaplab_ui import faceswaplab_tab, faceswaplab_unit_ui
|
from scripts.faceswaplab_ui import faceswaplab_tab, faceswaplab_unit_ui
|
||||||
from scripts.faceswaplab_utils import faceswaplab_logging, imgutils, models_utils
|
from scripts.faceswaplab_utils import faceswaplab_logging, imgutils, models_utils
|
||||||
from scripts.faceswaplab_utils.models_utils import get_current_model
|
from scripts.faceswaplab_utils.models_utils import get_current_swap_model
|
||||||
from scripts.faceswaplab_utils.typing import *
|
from scripts.faceswaplab_utils.typing import *
|
||||||
from scripts.faceswaplab_utils.ui_utils import dataclasses_from_flat_list
|
from scripts.faceswaplab_utils.ui_utils import dataclasses_from_flat_list
|
||||||
|
from scripts.faceswaplab_utils.faceswaplab_logging import logger, save_img_debug
|
||||||
|
|
||||||
# Reload all the modules when using "apply and restart"
|
# Reload all the modules when using "apply and restart"
|
||||||
# This is mainly done for development purposes
|
# This is mainly done for development purposes
|
||||||
importlib.reload(swapper)
|
import logging
|
||||||
importlib.reload(faceswaplab_logging)
|
|
||||||
importlib.reload(faceswaplab_globals)
|
if logger.getEffectiveLevel() <= logging.DEBUG:
|
||||||
importlib.reload(imgutils)
|
importlib.reload(swapper)
|
||||||
importlib.reload(upscaling)
|
importlib.reload(faceswaplab_logging)
|
||||||
importlib.reload(faceswaplab_settings)
|
importlib.reload(faceswaplab_globals)
|
||||||
importlib.reload(models_utils)
|
importlib.reload(imgutils)
|
||||||
importlib.reload(faceswaplab_unit_ui)
|
importlib.reload(upscaling)
|
||||||
importlib.reload(faceswaplab_api)
|
importlib.reload(faceswaplab_settings)
|
||||||
|
importlib.reload(models_utils)
|
||||||
|
importlib.reload(faceswaplab_unit_ui)
|
||||||
|
importlib.reload(faceswaplab_api)
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
@@ -46,7 +54,6 @@ from scripts.faceswaplab_postprocessing.postprocessing_options import (
|
|||||||
PostProcessingOptions,
|
PostProcessingOptions,
|
||||||
)
|
)
|
||||||
from scripts.faceswaplab_ui.faceswaplab_unit_settings import FaceSwapUnitSettings
|
from scripts.faceswaplab_ui.faceswaplab_unit_settings import FaceSwapUnitSettings
|
||||||
from scripts.faceswaplab_utils.faceswaplab_logging import logger, save_img_debug
|
|
||||||
|
|
||||||
EXTENSION_PATH = os.path.join("extensions", "sd-webui-faceswaplab")
|
EXTENSION_PATH = os.path.join("extensions", "sd-webui-faceswaplab")
|
||||||
|
|
||||||
@@ -67,11 +74,10 @@ except:
|
|||||||
class FaceSwapScript(scripts.Script):
|
class FaceSwapScript(scripts.Script):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
check_configuration()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def units_count(self) -> int:
|
def units_count(self) -> int:
|
||||||
return opts.data.get("faceswaplab_units_count", 3)
|
return get_sd_option("faceswaplab_units_count", 3)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enabled(self) -> bool:
|
def enabled(self) -> bool:
|
||||||
@@ -80,7 +86,7 @@ class FaceSwapScript(scripts.Script):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def keep_original_images(self) -> bool:
|
def keep_original_images(self) -> bool:
|
||||||
return opts.data.get("faceswaplab_keep_original", False)
|
return get_sd_option("faceswaplab_keep_original", False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def swap_in_generated_units(self) -> List[FaceSwapUnitSettings]:
|
def swap_in_generated_units(self) -> List[FaceSwapUnitSettings]:
|
||||||
@@ -94,7 +100,7 @@ class FaceSwapScript(scripts.Script):
|
|||||||
return f"faceswaplab"
|
return f"faceswaplab"
|
||||||
|
|
||||||
def show(self, is_img2img: bool) -> bool:
|
def show(self, is_img2img: bool) -> bool:
|
||||||
return scripts.AlwaysVisible
|
return scripts.AlwaysVisible # type: ignore
|
||||||
|
|
||||||
def ui(self, is_img2img: bool) -> List[gr.components.Component]:
|
def ui(self, is_img2img: bool) -> List[gr.components.Component]:
|
||||||
with gr.Accordion(f"FaceSwapLab {VERSION_FLAG}", open=False):
|
with gr.Accordion(f"FaceSwapLab {VERSION_FLAG}", open=False):
|
||||||
@@ -103,7 +109,10 @@ class FaceSwapScript(scripts.Script):
|
|||||||
components += faceswaplab_unit_ui.faceswap_unit_ui(is_img2img, i)
|
components += faceswaplab_unit_ui.faceswap_unit_ui(is_img2img, i)
|
||||||
post_processing = faceswaplab_tab.postprocessing_ui()
|
post_processing = faceswaplab_tab.postprocessing_ui()
|
||||||
# If the order is modified, the before_process should be changed accordingly.
|
# If the order is modified, the before_process should be changed accordingly.
|
||||||
return components + post_processing
|
|
||||||
|
components = components + post_processing
|
||||||
|
|
||||||
|
return components
|
||||||
|
|
||||||
def read_config(
|
def read_config(
|
||||||
self, p: StableDiffusionProcessing, *components: Tuple[Any, ...]
|
self, p: StableDiffusionProcessing, *components: Tuple[Any, ...]
|
||||||
@@ -142,7 +151,7 @@ class FaceSwapScript(scripts.Script):
|
|||||||
(img, None) for img in p.init_images
|
(img, None) for img in p.init_images
|
||||||
]
|
]
|
||||||
new_inits = swapper.process_images_units(
|
new_inits = swapper.process_images_units(
|
||||||
get_current_model(),
|
get_current_swap_model(),
|
||||||
self.swap_in_source_units,
|
self.swap_in_source_units,
|
||||||
images=init_images,
|
images=init_images,
|
||||||
force_blend=True,
|
force_blend=True,
|
||||||
@@ -176,7 +185,7 @@ class FaceSwapScript(scripts.Script):
|
|||||||
for i, (img, info) in enumerate(zip(orig_images, orig_infotexts)):
|
for i, (img, info) in enumerate(zip(orig_images, orig_infotexts)):
|
||||||
batch_index = i % p.batch_size
|
batch_index = i % p.batch_size
|
||||||
swapped_images = swapper.process_images_units(
|
swapped_images = swapper.process_images_units(
|
||||||
get_current_model(),
|
get_current_swap_model(),
|
||||||
self.swap_in_generated_units,
|
self.swap_in_generated_units,
|
||||||
images=[(img, info)],
|
images=[(img, info)],
|
||||||
)
|
)
|
||||||
@@ -208,8 +217,8 @@ class FaceSwapScript(scripts.Script):
|
|||||||
swp_img,
|
swp_img,
|
||||||
p.outpath_samples,
|
p.outpath_samples,
|
||||||
"",
|
"",
|
||||||
p.all_seeds[batch_index],
|
p.all_seeds[batch_index], # type: ignore
|
||||||
p.all_prompts[batch_index],
|
p.all_prompts[batch_index], # type: ignore
|
||||||
opts.samples_format,
|
opts.samples_format,
|
||||||
info=new_info,
|
info=new_info,
|
||||||
p=p,
|
p=p,
|
||||||
@@ -226,7 +235,7 @@ class FaceSwapScript(scripts.Script):
|
|||||||
text = processed.infotexts[0]
|
text = processed.infotexts[0]
|
||||||
infotexts.insert(0, text)
|
infotexts.insert(0, text)
|
||||||
if opts.enable_pnginfo:
|
if opts.enable_pnginfo:
|
||||||
grid.info["parameters"] = text
|
grid.info["parameters"] = text # type: ignore
|
||||||
images.insert(0, grid)
|
images.insert(0, grid)
|
||||||
|
|
||||||
if opts.grid_save:
|
if opts.grid_save:
|
||||||
@@ -234,8 +243,8 @@ class FaceSwapScript(scripts.Script):
|
|||||||
grid,
|
grid,
|
||||||
p.outpath_grids,
|
p.outpath_grids,
|
||||||
"swapped-grid",
|
"swapped-grid",
|
||||||
p.all_seeds[0],
|
p.all_seeds[0], # type: ignore
|
||||||
p.all_prompts[0],
|
p.all_prompts[0], # type: ignore
|
||||||
opts.grid_format,
|
opts.grid_format,
|
||||||
info=text,
|
info=text,
|
||||||
short_filename=not opts.grid_extended_filename,
|
short_filename=not opts.grid_extended_filename,
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ from scripts.faceswaplab_postprocessing.postprocessing_options import (
|
|||||||
PostProcessingOptions,
|
PostProcessingOptions,
|
||||||
)
|
)
|
||||||
from client_api import api_utils
|
from client_api import api_utils
|
||||||
from scripts.faceswaplab_utils.face_checkpoints_utils import (
|
from scripts.faceswaplab_swapping.face_checkpoints import (
|
||||||
build_face_checkpoint_and_save,
|
build_face_checkpoint_and_save,
|
||||||
)
|
)
|
||||||
|
from scripts.faceswaplab_utils.typing import PILImage
|
||||||
|
|
||||||
|
|
||||||
def encode_to_base64(image: Union[str, Image.Image, np.ndarray]) -> str: # type: ignore
|
def encode_to_base64(image: Union[str, Image.Image, np.ndarray]) -> str: # type: ignore
|
||||||
@@ -99,7 +100,7 @@ def faceswaplab_api(_: gr.Blocks, app: FastAPI) -> None:
|
|||||||
pp_options = None
|
pp_options = None
|
||||||
units = get_faceswap_units_settings(request.units)
|
units = get_faceswap_units_settings(request.units)
|
||||||
|
|
||||||
swapped_images = swapper.batch_process(
|
swapped_images: Optional[List[PILImage]] = swapper.batch_process(
|
||||||
[src_image], None, units=units, postprocess_options=pp_options
|
[src_image], None, units=units, postprocess_options=pp_options
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
import os
|
import os
|
||||||
from modules import scripts
|
from modules import scripts
|
||||||
|
|
||||||
|
# Defining the absolute path for the 'faceswaplab' directory inside 'models' directory
|
||||||
MODELS_DIR = os.path.abspath(os.path.join("models", "faceswaplab"))
|
MODELS_DIR = os.path.abspath(os.path.join("models", "faceswaplab"))
|
||||||
|
# Defining the absolute path for the 'analysers' directory inside 'MODELS_DIR'
|
||||||
ANALYZER_DIR = os.path.abspath(os.path.join(MODELS_DIR, "analysers"))
|
ANALYZER_DIR = os.path.abspath(os.path.join(MODELS_DIR, "analysers"))
|
||||||
|
# Defining the absolute path for the 'parser' directory inside 'MODELS_DIR'
|
||||||
FACE_PARSER_DIR = os.path.abspath(os.path.join(MODELS_DIR, "parser"))
|
FACE_PARSER_DIR = os.path.abspath(os.path.join(MODELS_DIR, "parser"))
|
||||||
|
# Defining the absolute path for the 'faces' directory inside 'MODELS_DIR'
|
||||||
FACES_DIR = os.path.abspath(os.path.join(MODELS_DIR, "faces"))
|
FACES_DIR = os.path.abspath(os.path.join(MODELS_DIR, "faces"))
|
||||||
|
|
||||||
|
# Constructing the path for 'references' directory inside the 'extensions' and 'sd-webui-faceswaplab' directories, based on the base directory of scripts
|
||||||
REFERENCE_PATH = os.path.join(
|
REFERENCE_PATH = os.path.join(
|
||||||
scripts.basedir(), "extensions", "sd-webui-faceswaplab", "references"
|
scripts.basedir(), "extensions", "sd-webui-faceswaplab", "references"
|
||||||
)
|
)
|
||||||
|
|
||||||
VERSION_FLAG: str = "v1.2.0"
|
# Defining the version flag for the application
|
||||||
|
VERSION_FLAG: str = "v1.2.5"
|
||||||
|
# Defining the path for 'sd-webui-faceswaplab' inside the 'extensions' directory
|
||||||
EXTENSION_PATH = os.path.join("extensions", "sd-webui-faceswaplab")
|
EXTENSION_PATH = os.path.join("extensions", "sd-webui-faceswaplab")
|
||||||
|
|
||||||
# The NSFW score threshold. If any part of the image has a score greater than this threshold, the image will be considered NSFW.
|
# Defining the expected SHA1 hash value for 'INSWAPPER'
|
||||||
NSFW_SCORE_THRESHOLD: float = 0.7
|
|
||||||
EXPECTED_INSWAPPER_SHA1 = "17a64851eaefd55ea597ee41e5c18409754244c5"
|
EXPECTED_INSWAPPER_SHA1 = "17a64851eaefd55ea597ee41e5c18409754244c5"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import List
|
from typing import List, Optional
|
||||||
import gradio as gr
|
import gradio as gr
|
||||||
from client_api import api_utils
|
from client_api import api_utils
|
||||||
|
|
||||||
@@ -12,13 +12,14 @@ class InpaintingOptions:
|
|||||||
inpainting_steps: int = 20
|
inpainting_steps: int = 20
|
||||||
inpainting_sampler: str = "Euler"
|
inpainting_sampler: str = "Euler"
|
||||||
inpainting_model: str = "Current"
|
inpainting_model: str = "Current"
|
||||||
|
inpainting_seed: int = -1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_gradio(components: List[gr.components.Component]) -> "InpaintingOptions":
|
def from_gradio(components: List[gr.components.Component]) -> "InpaintingOptions":
|
||||||
return InpaintingOptions(*components)
|
return InpaintingOptions(*components) # type: ignore
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_api_dto(dto: api_utils.InpaintingOptions) -> "InpaintingOptions":
|
def from_api_dto(dto: Optional[api_utils.InpaintingOptions]) -> "InpaintingOptions":
|
||||||
"""
|
"""
|
||||||
Converts a InpaintingOptions object from an API DTO (Data Transfer Object).
|
Converts a InpaintingOptions object from an API DTO (Data Transfer Object).
|
||||||
|
|
||||||
@@ -38,4 +39,5 @@ class InpaintingOptions:
|
|||||||
inpainting_steps=dto.inpainting_steps,
|
inpainting_steps=dto.inpainting_steps,
|
||||||
inpainting_sampler=dto.inpainting_sampler,
|
inpainting_sampler=dto.inpainting_sampler,
|
||||||
inpainting_model=dto.inpainting_model,
|
inpainting_model=dto.inpainting_model,
|
||||||
|
inpainting_seed=dto.inpainting_seed,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ inpainting_steps : {options.inpainting_steps}
|
|||||||
)
|
)
|
||||||
|
|
||||||
i2i_kwargs = {
|
i2i_kwargs = {
|
||||||
|
"init_images": [img],
|
||||||
"sampler_name": options.inpainting_sampler,
|
"sampler_name": options.inpainting_sampler,
|
||||||
"do_not_save_samples": True,
|
"do_not_save_samples": True,
|
||||||
"steps": options.inpainting_steps,
|
"steps": options.inpainting_steps,
|
||||||
@@ -63,23 +64,16 @@ inpainting_steps : {options.inpainting_steps}
|
|||||||
"prompt": prompt,
|
"prompt": prompt,
|
||||||
"negative_prompt": negative_prompt,
|
"negative_prompt": negative_prompt,
|
||||||
"denoising_strength": options.inpainting_denoising_strengh,
|
"denoising_strength": options.inpainting_denoising_strengh,
|
||||||
"override_settings": {
|
"seed": options.inpainting_seed,
|
||||||
"return_mask_composite": False,
|
|
||||||
"save_images_before_face_restoration": False,
|
|
||||||
"save_images_before_highres_fix": False,
|
|
||||||
"save_images_before_color_correction": False,
|
|
||||||
"save_mask": False,
|
|
||||||
"save_mask_composite": False,
|
|
||||||
"samples_save": False,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
current_model_checkpoint = shared.opts.sd_model_checkpoint
|
current_model_checkpoint = shared.opts.sd_model_checkpoint
|
||||||
if options.inpainting_model and options.inpainting_model != "Current":
|
if options.inpainting_model and options.inpainting_model != "Current":
|
||||||
# Change checkpoint
|
# Change checkpoint
|
||||||
shared.opts.sd_model_checkpoint = options.inpainting_model
|
shared.opts.sd_model_checkpoint = options.inpainting_model
|
||||||
sd_models.select_checkpoint
|
sd_models.select_checkpoint
|
||||||
sd_models.load_model()
|
sd_models.load_model()
|
||||||
i2i_p = StableDiffusionProcessingImg2Img([img], **i2i_kwargs)
|
i2i_p = StableDiffusionProcessingImg2Img(**i2i_kwargs)
|
||||||
i2i_processed = processing.process_images(i2i_p)
|
i2i_processed = processing.process_images(i2i_p)
|
||||||
if options.inpainting_model and options.inpainting_model != "Current":
|
if options.inpainting_model and options.inpainting_model != "Current":
|
||||||
# Restore checkpoint
|
# Restore checkpoint
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from typing import Optional
|
||||||
from modules.face_restoration import FaceRestoration
|
from modules.face_restoration import FaceRestoration
|
||||||
from modules.upscaler import UpscalerData
|
from modules.upscaler import UpscalerData
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
@@ -27,17 +28,17 @@ class PostProcessingOptions:
|
|||||||
inpainting_when: InpaintingWhen = InpaintingWhen.BEFORE_UPSCALING
|
inpainting_when: InpaintingWhen = InpaintingWhen.BEFORE_UPSCALING
|
||||||
|
|
||||||
# (Don't use optional for this or gradio parsing will fail) :
|
# (Don't use optional for this or gradio parsing will fail) :
|
||||||
inpainting_options: InpaintingOptions = None
|
inpainting_options: InpaintingOptions = None # type: ignore
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def upscaler(self) -> UpscalerData:
|
def upscaler(self) -> Optional[UpscalerData]:
|
||||||
for upscaler in shared.sd_upscalers:
|
for upscaler in shared.sd_upscalers:
|
||||||
if upscaler.name == self.upscaler_name:
|
if upscaler.name == self.upscaler_name:
|
||||||
return upscaler
|
return upscaler
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def face_restorer(self) -> FaceRestoration:
|
def face_restorer(self) -> Optional[FaceRestoration]:
|
||||||
for face_restorer in shared.face_restorers:
|
for face_restorer in shared.face_restorers:
|
||||||
if face_restorer.name() == self.face_restorer_name:
|
if face_restorer.name() == self.face_restorer_name:
|
||||||
return face_restorer
|
return face_restorer
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ def upscale_img(image: PILImage, pp_options: PostProcessingOptions) -> PILImage:
|
|||||||
pp_options.scale,
|
pp_options.scale,
|
||||||
)
|
)
|
||||||
result_image = pp_options.upscaler.scaler.upscale(
|
result_image = pp_options.upscaler.scaler.upscale(
|
||||||
image, pp_options.scale, pp_options.upscaler.data_path
|
image, pp_options.scale, pp_options.upscaler.data_path # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
# FIXME : Could be better (managing images whose dimensions are not multiples of 16)
|
# FIXME : Could be better (managing images whose dimensions are not multiples of 16)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from scripts.faceswaplab_utils.models_utils import get_models
|
from scripts.faceswaplab_utils.models_utils import get_swap_models
|
||||||
from modules import script_callbacks, shared
|
from modules import script_callbacks, shared
|
||||||
import gradio as gr
|
import gradio as gr
|
||||||
|
|
||||||
|
|
||||||
def on_ui_settings() -> None:
|
def on_ui_settings() -> None:
|
||||||
section = ("faceswaplab", "FaceSwapLab")
|
section = ("faceswaplab", "FaceSwapLab")
|
||||||
models = get_models()
|
models = get_swap_models()
|
||||||
shared.opts.add_option(
|
shared.opts.add_option(
|
||||||
"faceswaplab_model",
|
"faceswaplab_model",
|
||||||
shared.OptionInfo(
|
shared.OptionInfo(
|
||||||
@@ -16,6 +16,16 @@ def on_ui_settings() -> None:
|
|||||||
section=section,
|
section=section,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
shared.opts.add_option(
|
||||||
|
"faceswaplab_use_gpu",
|
||||||
|
shared.OptionInfo(
|
||||||
|
False,
|
||||||
|
"Use GPU, only for CUDA on Windows/Linux - experimental and risky, can messed up dependencies (requires restart)",
|
||||||
|
gr.Checkbox,
|
||||||
|
{"interactive": True},
|
||||||
|
section=section,
|
||||||
|
),
|
||||||
|
)
|
||||||
shared.opts.add_option(
|
shared.opts.add_option(
|
||||||
"faceswaplab_keep_original",
|
"faceswaplab_keep_original",
|
||||||
shared.OptionInfo(
|
shared.OptionInfo(
|
||||||
@@ -36,12 +46,44 @@ def on_ui_settings() -> None:
|
|||||||
section=section,
|
section=section,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
shared.opts.add_option(
|
||||||
|
"faceswaplab_nsfw_threshold",
|
||||||
|
shared.OptionInfo(
|
||||||
|
0.7,
|
||||||
|
"NSFW score threshold. Any image part with a score above this value will be treated as NSFW (use extension responsibly !). 1=Disable filtering",
|
||||||
|
gr.Slider,
|
||||||
|
{"minimum": 0, "maximum": 1, "step": 0.01},
|
||||||
|
section=section,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
shared.opts.add_option(
|
||||||
|
"faceswaplab_det_size",
|
||||||
|
shared.OptionInfo(
|
||||||
|
640,
|
||||||
|
"det_size : Size of the detection area for face analysis. Higher values may improve quality but reduce speed. Low value may improve detection of very large face.",
|
||||||
|
gr.Slider,
|
||||||
|
{"minimum": 320, "maximum": 640, "step": 320},
|
||||||
|
section=section,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
shared.opts.add_option(
|
||||||
|
"faceswaplab_auto_det_size",
|
||||||
|
shared.OptionInfo(
|
||||||
|
True,
|
||||||
|
"Auto det_size : Will load model twice and test faces on each if needed (old behaviour). Takes more VRAM. Precedence over fixed det_size",
|
||||||
|
gr.Checkbox,
|
||||||
|
{"interactive": True},
|
||||||
|
section=section,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
shared.opts.add_option(
|
shared.opts.add_option(
|
||||||
"faceswaplab_detection_threshold",
|
"faceswaplab_detection_threshold",
|
||||||
shared.OptionInfo(
|
shared.OptionInfo(
|
||||||
0.5,
|
0.5,
|
||||||
"Face Detection threshold",
|
"det_thresh : Face Detection threshold",
|
||||||
gr.Slider,
|
gr.Slider,
|
||||||
{"minimum": 0.1, "maximum": 0.99, "step": 0.001},
|
{"minimum": 0.1, "maximum": 0.99, "step": 0.001},
|
||||||
section=section,
|
section=section,
|
||||||
@@ -169,7 +211,7 @@ def on_ui_settings() -> None:
|
|||||||
shared.opts.add_option(
|
shared.opts.add_option(
|
||||||
"faceswaplab_default_upscaled_swapper_improved_mask",
|
"faceswaplab_default_upscaled_swapper_improved_mask",
|
||||||
shared.OptionInfo(
|
shared.OptionInfo(
|
||||||
True,
|
False,
|
||||||
"Default Use improved segmented mask (use pastenet to mask only the face) (requires restart)",
|
"Default Use improved segmented mask (use pastenet to mask only the face) (requires restart)",
|
||||||
gr.Checkbox,
|
gr.Checkbox,
|
||||||
{"interactive": True},
|
{"interactive": True},
|
||||||
|
|||||||
+49
-32
@@ -11,7 +11,7 @@ from scripts.faceswaplab_swapping.upcaled_inswapper_options import InswappperOpt
|
|||||||
from scripts.faceswaplab_utils.faceswaplab_logging import logger
|
from scripts.faceswaplab_utils.faceswaplab_logging import logger
|
||||||
from scripts.faceswaplab_utils.typing import *
|
from scripts.faceswaplab_utils.typing import *
|
||||||
from scripts.faceswaplab_utils import imgutils
|
from scripts.faceswaplab_utils import imgutils
|
||||||
from scripts.faceswaplab_utils.models_utils import get_models
|
from scripts.faceswaplab_utils.models_utils import get_swap_models
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
import dill as pickle # will be removed in future versions
|
import dill as pickle # will be removed in future versions
|
||||||
@@ -38,8 +38,11 @@ def sanitize_name(name: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def build_face_checkpoint_and_save(
|
def build_face_checkpoint_and_save(
|
||||||
images: List[PILImage], name: str, overwrite: bool = False, path: str = None
|
images: List[PILImage],
|
||||||
) -> PILImage:
|
name: str,
|
||||||
|
overwrite: bool = False,
|
||||||
|
path: Optional[str] = None,
|
||||||
|
) -> Optional[PILImage]:
|
||||||
"""
|
"""
|
||||||
Builds a face checkpoint using the provided image files, performs face swapping,
|
Builds a face checkpoint using the provided image files, performs face swapping,
|
||||||
and saves the result to a file. If a blended face is successfully obtained and the face swapping
|
and saves the result to a file. If a blended face is successfully obtained and the face swapping
|
||||||
@@ -57,13 +60,17 @@ def build_face_checkpoint_and_save(
|
|||||||
name = sanitize_name(name)
|
name = sanitize_name(name)
|
||||||
images = images or []
|
images = images or []
|
||||||
logger.info("Build %s with %s images", name, len(images))
|
logger.info("Build %s with %s images", name, len(images))
|
||||||
faces = swapper.get_faces_from_img_files(images)
|
faces: List[Face] = swapper.get_faces_from_img_files(images=images)
|
||||||
blended_face = swapper.blend_faces(faces)
|
if faces is None or len(faces) == 0:
|
||||||
|
logger.error("No source faces found")
|
||||||
|
return None
|
||||||
|
|
||||||
|
blended_face: Optional[Face] = swapper.blend_faces(faces)
|
||||||
preview_path = os.path.join(
|
preview_path = os.path.join(
|
||||||
scripts.basedir(), "extensions", "sd-webui-faceswaplab", "references"
|
scripts.basedir(), "extensions", "sd-webui-faceswaplab", "references"
|
||||||
)
|
)
|
||||||
|
|
||||||
reference_preview_img: PILImage = None
|
reference_preview_img: PILImage
|
||||||
if blended_face:
|
if blended_face:
|
||||||
if blended_face["gender"] == 0:
|
if blended_face["gender"] == 0:
|
||||||
reference_preview_img = Image.open(
|
reference_preview_img = Image.open(
|
||||||
@@ -85,41 +92,51 @@ def build_face_checkpoint_and_save(
|
|||||||
"Failed to open reference image, cannot create preview : That should not happen unless you deleted the references folder or change the detection threshold."
|
"Failed to open reference image, cannot create preview : That should not happen unless you deleted the references folder or change the detection threshold."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result = swapper.swap_face(
|
result: swapper.ImageResult = swapper.swap_face(
|
||||||
reference_face=blended_face,
|
|
||||||
target_faces=[target_face],
|
target_faces=[target_face],
|
||||||
source_face=blended_face,
|
source_face=blended_face,
|
||||||
target_img=reference_preview_img,
|
target_img=reference_preview_img,
|
||||||
model=get_models()[0],
|
model=get_swap_models()[0],
|
||||||
swapping_options=InswappperOptions(face_restorer_name="Codeformer"),
|
swapping_options=InswappperOptions(
|
||||||
|
face_restorer_name="CodeFormer",
|
||||||
|
restorer_visibility=1,
|
||||||
|
upscaler_name="Lanczos",
|
||||||
|
codeformer_weight=1,
|
||||||
|
improved_mask=True,
|
||||||
|
color_corrections=False,
|
||||||
|
sharpen=True,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
preview_image = result.image
|
preview_image = result.image
|
||||||
|
|
||||||
if path:
|
if path:
|
||||||
file_path = path
|
file_path = path
|
||||||
else:
|
else:
|
||||||
file_path = os.path.join(get_checkpoint_path(), f"{name}.safetensors")
|
file_path = os.path.join(
|
||||||
if not overwrite:
|
get_checkpoint_path(), f"{name}.safetensors"
|
||||||
file_number = 1
|
)
|
||||||
while os.path.exists(file_path):
|
if not overwrite:
|
||||||
file_path = os.path.join(
|
file_number = 1
|
||||||
get_checkpoint_path(), f"{name}_{file_number}.safetensors"
|
while os.path.exists(file_path):
|
||||||
)
|
file_path = os.path.join(
|
||||||
file_number += 1
|
get_checkpoint_path(),
|
||||||
save_face(filename=file_path, face=blended_face)
|
f"{name}_{file_number}.safetensors",
|
||||||
preview_image.save(file_path + ".png")
|
)
|
||||||
try:
|
file_number += 1
|
||||||
data = load_face(file_path)
|
save_face(filename=file_path, face=blended_face)
|
||||||
logger.debug(data)
|
preview_image.save(file_path + ".png")
|
||||||
except Exception as e:
|
try:
|
||||||
logger.error("Error loading checkpoint, after creation %s", e)
|
data = load_face(file_path)
|
||||||
traceback.print_exc()
|
logger.debug(data)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Error loading checkpoint, after creation %s", e)
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
return preview_image
|
return preview_image
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.error("No face found")
|
logger.error("No face found")
|
||||||
return None
|
return None # type: ignore
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Failed to build checkpoint %s", e)
|
logger.error("Failed to build checkpoint %s", e)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@@ -140,7 +157,7 @@ def save_face(face: Face, filename: str) -> None:
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def load_face(name: str) -> Face:
|
def load_face(name: str) -> Optional[Face]:
|
||||||
if name.startswith("data:application/face;base64,"):
|
if name.startswith("data:application/face;base64,"):
|
||||||
with tempfile.NamedTemporaryFile(delete=True) as temp_file:
|
with tempfile.NamedTemporaryFile(delete=True) as temp_file:
|
||||||
api_utils.base64_to_safetensors(name, temp_file.name)
|
api_utils.base64_to_safetensors(name, temp_file.name)
|
||||||
@@ -83,7 +83,7 @@ def generate_face_mask(face_image: np.ndarray, device: torch.device) -> np.ndarr
|
|||||||
convert_bgr_to_rgb=True,
|
convert_bgr_to_rgb=True,
|
||||||
use_float32=True,
|
use_float32=True,
|
||||||
)
|
)
|
||||||
normalize(face_input, (0.5, 0.5, 0.5), (0.5, 0.5, 0.5), inplace=True)
|
normalize(face_input, (0.5, 0.5, 0.5), (0.5, 0.5, 0.5), inplace=True) # type: ignore
|
||||||
assert isinstance(face_input, torch.Tensor)
|
assert isinstance(face_input, torch.Tensor)
|
||||||
face_input = torch.unsqueeze(face_input, 0).to(device)
|
face_input = torch.unsqueeze(face_input, 0).to(device)
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ import os
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
import traceback
|
import traceback
|
||||||
from typing import Any, Dict, Generator, List, Set, Tuple, Optional
|
from typing import Any, Dict, Generator, List, Set, Tuple, Optional, Union
|
||||||
import tempfile
|
import tempfile
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
import sys
|
import sys
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
import hashlib
|
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import insightface
|
import insightface
|
||||||
@@ -27,18 +26,62 @@ from scripts.faceswaplab_utils.imgutils import (
|
|||||||
)
|
)
|
||||||
from scripts.faceswaplab_utils.faceswaplab_logging import logger, save_img_debug
|
from scripts.faceswaplab_utils.faceswaplab_logging import logger, save_img_debug
|
||||||
from scripts import faceswaplab_globals
|
from scripts import faceswaplab_globals
|
||||||
from modules.shared import opts
|
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from scripts.faceswaplab_ui.faceswaplab_unit_settings import FaceSwapUnitSettings
|
from scripts.faceswaplab_ui.faceswaplab_unit_settings import FaceSwapUnitSettings
|
||||||
from scripts.faceswaplab_postprocessing.postprocessing import enhance_image
|
from scripts.faceswaplab_postprocessing.postprocessing import enhance_image
|
||||||
from scripts.faceswaplab_postprocessing.postprocessing_options import (
|
from scripts.faceswaplab_postprocessing.postprocessing_options import (
|
||||||
PostProcessingOptions,
|
PostProcessingOptions,
|
||||||
)
|
)
|
||||||
from scripts.faceswaplab_utils.models_utils import get_current_model
|
from scripts.faceswaplab_utils.models_utils import get_current_swap_model
|
||||||
from scripts.faceswaplab_utils.typing import CV2ImgU8, PILImage, Face
|
from scripts.faceswaplab_utils.typing import CV2ImgU8, PILImage, Face
|
||||||
from scripts.faceswaplab_inpainting.i2i_pp import img2img_diffusion
|
from scripts.faceswaplab_inpainting.i2i_pp import img2img_diffusion
|
||||||
|
from modules import shared
|
||||||
|
import onnxruntime
|
||||||
|
from scripts.faceswaplab_utils.sd_utils import get_sd_option
|
||||||
|
|
||||||
providers = ["CPUExecutionProvider"]
|
|
||||||
|
def use_gpu() -> bool:
|
||||||
|
return (
|
||||||
|
getattr(shared.cmd_opts, "faceswaplab_gpu", False)
|
||||||
|
or get_sd_option("faceswaplab_use_gpu", False)
|
||||||
|
) and sys.platform != "darwin"
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def force_install_gpu_providers() -> None:
|
||||||
|
# Ugly Ugly hack due to SDNEXT :
|
||||||
|
try:
|
||||||
|
from scripts.faceswaplab_utils.install_utils import check_install
|
||||||
|
|
||||||
|
logger.warning("Try to reinstall gpu dependencies")
|
||||||
|
check_install()
|
||||||
|
logger.warning("IF onnxruntime-gpu has been installed successfully, RESTART")
|
||||||
|
logger.warning(
|
||||||
|
"On SD.NEXT/vladmantic you will also need to check numpy>=1.24.2 and tensorflow>=2.13.0"
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
logger.error(
|
||||||
|
"Reinstall has failed (which is normal on windows), please install requirements-gpu.txt manually to enable gpu."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_providers() -> List[str]:
|
||||||
|
providers = ["CPUExecutionProvider"]
|
||||||
|
if use_gpu():
|
||||||
|
if "CUDAExecutionProvider" in onnxruntime.get_available_providers():
|
||||||
|
providers = ["CUDAExecutionProvider"]
|
||||||
|
else:
|
||||||
|
logger.error(
|
||||||
|
"CUDAExecutionProvider not found in onnxruntime.available_providers : %s, use CPU instead. Check onnxruntime-gpu is installed.",
|
||||||
|
onnxruntime.get_available_providers(),
|
||||||
|
)
|
||||||
|
force_install_gpu_providers()
|
||||||
|
|
||||||
|
return providers
|
||||||
|
|
||||||
|
|
||||||
|
def is_cpu_provider() -> bool:
|
||||||
|
return get_providers() == ["CPUExecutionProvider"]
|
||||||
|
|
||||||
|
|
||||||
def cosine_similarity_face(face1: Face, face2: Face) -> float:
|
def cosine_similarity_face(face1: Face, face2: Face) -> float:
|
||||||
@@ -58,8 +101,9 @@ def cosine_similarity_face(face1: Face, face2: Face) -> float:
|
|||||||
non-negative similarity score.
|
non-negative similarity score.
|
||||||
"""
|
"""
|
||||||
# Reshape the face embeddings to have a shape of (1, -1)
|
# Reshape the face embeddings to have a shape of (1, -1)
|
||||||
vec1 = face1.embedding.reshape(1, -1)
|
assert face1.normed_embedding is not None and face2.normed_embedding is not None
|
||||||
vec2 = face2.embedding.reshape(1, -1)
|
vec1 = face1.normed_embedding.reshape(1, -1)
|
||||||
|
vec2 = face2.normed_embedding.reshape(1, -1)
|
||||||
|
|
||||||
# Calculate the cosine similarity between the reshaped embeddings
|
# Calculate the cosine similarity between the reshaped embeddings
|
||||||
similarity = cosine_similarity(vec1, vec2)
|
similarity = cosine_similarity(vec1, vec2)
|
||||||
@@ -95,16 +139,16 @@ def compare_faces(img1: PILImage, img2: PILImage) -> float:
|
|||||||
|
|
||||||
|
|
||||||
def batch_process(
|
def batch_process(
|
||||||
src_images: List[PILImage],
|
src_images: List[Union[PILImage, str]], # image or filename
|
||||||
save_path: Optional[str],
|
save_path: Optional[str],
|
||||||
units: List[FaceSwapUnitSettings],
|
units: List[FaceSwapUnitSettings],
|
||||||
postprocess_options: PostProcessingOptions,
|
postprocess_options: Optional[PostProcessingOptions],
|
||||||
) -> Optional[List[PILImage]]:
|
) -> Optional[List[PILImage]]:
|
||||||
"""
|
"""
|
||||||
Process a batch of images, apply face swapping according to the given settings, and optionally save the resulting images to a specified path.
|
Process a batch of images, apply face swapping according to the given settings, and optionally save the resulting images to a specified path.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
src_images (List[PILImage]): List of source PIL Images to process.
|
src_images (List[Union[PILImage, str]]): List of source PIL Images to process or list of images file names
|
||||||
save_path (Optional[str]): Destination path where the processed images will be saved. If None, no images are saved.
|
save_path (Optional[str]): Destination path where the processed images will be saved. If None, no images are saved.
|
||||||
units (List[FaceSwapUnitSettings]): List of FaceSwapUnitSettings to apply to the images.
|
units (List[FaceSwapUnitSettings]): List of FaceSwapUnitSettings to apply to the images.
|
||||||
postprocess_options (PostProcessingOptions): Post-processing settings to be applied to the images.
|
postprocess_options (PostProcessingOptions): Post-processing settings to be applied to the images.
|
||||||
@@ -123,11 +167,24 @@ def batch_process(
|
|||||||
if src_images is not None and len(units) > 0:
|
if src_images is not None and len(units) > 0:
|
||||||
result_images = []
|
result_images = []
|
||||||
for src_image in src_images:
|
for src_image in src_images:
|
||||||
|
path: str = ""
|
||||||
|
if isinstance(src_image, str):
|
||||||
|
if save_path:
|
||||||
|
path = os.path.join(
|
||||||
|
save_path, "swapped_" + os.path.basename(src_image)
|
||||||
|
)
|
||||||
|
src_image = Image.open(src_image)
|
||||||
|
elif save_path:
|
||||||
|
path = tempfile.NamedTemporaryFile(
|
||||||
|
delete=False, suffix=".png", dir=save_path
|
||||||
|
).name
|
||||||
|
assert isinstance(src_image, Image.Image)
|
||||||
|
|
||||||
current_images = []
|
current_images = []
|
||||||
swapped_images = process_images_units(
|
swapped_images = process_images_units(
|
||||||
get_current_model(), images=[(src_image, None)], units=units
|
get_current_swap_model(), images=[(src_image, None)], units=units
|
||||||
)
|
)
|
||||||
if len(swapped_images) > 0:
|
if swapped_images and len(swapped_images) > 0:
|
||||||
current_images += [img for img, _ in swapped_images]
|
current_images += [img for img, _ in swapped_images]
|
||||||
|
|
||||||
logger.info("%s images generated", len(current_images))
|
logger.info("%s images generated", len(current_images))
|
||||||
@@ -138,9 +195,6 @@ def batch_process(
|
|||||||
|
|
||||||
if save_path:
|
if save_path:
|
||||||
for img in current_images:
|
for img in current_images:
|
||||||
path = tempfile.NamedTemporaryFile(
|
|
||||||
delete=False, suffix=".png", dir=save_path
|
|
||||||
).name
|
|
||||||
img.save(path)
|
img.save(path)
|
||||||
|
|
||||||
result_images += current_images
|
result_images += current_images
|
||||||
@@ -157,7 +211,7 @@ def extract_faces(
|
|||||||
images: List[PILImage],
|
images: List[PILImage],
|
||||||
extract_path: Optional[str],
|
extract_path: Optional[str],
|
||||||
postprocess_options: PostProcessingOptions,
|
postprocess_options: PostProcessingOptions,
|
||||||
) -> Optional[List[str]]:
|
) -> Optional[List[PILImage]]:
|
||||||
"""
|
"""
|
||||||
Extracts faces from a list of image files.
|
Extracts faces from a list of image files.
|
||||||
|
|
||||||
@@ -180,14 +234,14 @@ def extract_faces(
|
|||||||
os.makedirs(extract_path, exist_ok=True)
|
os.makedirs(extract_path, exist_ok=True)
|
||||||
|
|
||||||
if images:
|
if images:
|
||||||
result_images = []
|
result_images: list[PILImage] = []
|
||||||
for img in images:
|
for img in images:
|
||||||
faces = get_faces(pil_to_cv2(img))
|
faces = get_faces(pil_to_cv2(img))
|
||||||
|
|
||||||
if faces:
|
if faces:
|
||||||
face_images = []
|
face_images = []
|
||||||
for face in faces:
|
for face in faces:
|
||||||
bbox = face.bbox.astype(int)
|
bbox = face.bbox.astype(int) # type: ignore
|
||||||
x_min, y_min, x_max, y_max = bbox
|
x_min, y_min, x_max, y_max = bbox
|
||||||
face_image = img.crop((x_min, y_min, x_max, y_max))
|
face_image = img.crop((x_min, y_min, x_max, y_max))
|
||||||
|
|
||||||
@@ -257,8 +311,12 @@ def capture_stdout() -> Generator[StringIO, None, None]:
|
|||||||
sys.stdout = original_stdout # Type: ignore
|
sys.stdout = original_stdout # Type: ignore
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1)
|
@lru_cache(maxsize=3)
|
||||||
def getAnalysisModel() -> insightface.app.FaceAnalysis:
|
def getAnalysisModel(
|
||||||
|
det_size: Tuple[int, int] = (640, 640),
|
||||||
|
det_thresh: float = 0.5,
|
||||||
|
use_gpu: bool = False,
|
||||||
|
) -> insightface.app.FaceAnalysis:
|
||||||
"""
|
"""
|
||||||
Retrieves the analysis model for face analysis.
|
Retrieves the analysis model for face analysis.
|
||||||
|
|
||||||
@@ -269,11 +327,16 @@ def getAnalysisModel() -> insightface.app.FaceAnalysis:
|
|||||||
if not os.path.exists(faceswaplab_globals.ANALYZER_DIR):
|
if not os.path.exists(faceswaplab_globals.ANALYZER_DIR):
|
||||||
os.makedirs(faceswaplab_globals.ANALYZER_DIR)
|
os.makedirs(faceswaplab_globals.ANALYZER_DIR)
|
||||||
|
|
||||||
logger.info("Load analysis model, will take some time. (> 30s)")
|
providers = get_providers()
|
||||||
|
logger.info(
|
||||||
|
f"Load analysis model det_size={det_size}, det_thresh={det_thresh}, providers = {providers}, will take some time. (> 30s)"
|
||||||
|
)
|
||||||
# Initialize the analysis model with the specified name and providers
|
# Initialize the analysis model with the specified name and providers
|
||||||
|
|
||||||
with tqdm(
|
with tqdm(
|
||||||
total=1, desc="Loading analysis model (first time is slow)", unit="model"
|
total=1,
|
||||||
|
desc=f"Loading {det_size} analysis model (first time is slow)",
|
||||||
|
unit="model",
|
||||||
) as pbar:
|
) as pbar:
|
||||||
with capture_stdout() as captured:
|
with capture_stdout() as captured:
|
||||||
model = insightface.app.FaceAnalysis(
|
model = insightface.app.FaceAnalysis(
|
||||||
@@ -281,6 +344,9 @@ def getAnalysisModel() -> insightface.app.FaceAnalysis:
|
|||||||
providers=providers,
|
providers=providers,
|
||||||
root=faceswaplab_globals.ANALYZER_DIR,
|
root=faceswaplab_globals.ANALYZER_DIR,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Prepare the analysis model for face detection with the specified detection size
|
||||||
|
model.prepare(ctx_id=0, det_thresh=det_thresh, det_size=det_size)
|
||||||
pbar.update(1)
|
pbar.update(1)
|
||||||
logger.info("%s", pformat(captured.getvalue()))
|
logger.info("%s", pformat(captured.getvalue()))
|
||||||
|
|
||||||
@@ -292,27 +358,10 @@ def getAnalysisModel() -> insightface.app.FaceAnalysis:
|
|||||||
raise FaceModelException("Loading of analysis model failed")
|
raise FaceModelException("Loading of analysis model failed")
|
||||||
|
|
||||||
|
|
||||||
def is_sha1_matching(file_path: str, expected_sha1: str) -> bool:
|
|
||||||
sha1_hash = hashlib.sha1(usedforsecurity=False)
|
|
||||||
try:
|
|
||||||
with open(file_path, "rb") as file:
|
|
||||||
for byte_block in iter(lambda: file.read(4096), b""):
|
|
||||||
sha1_hash.update(byte_block)
|
|
||||||
if sha1_hash.hexdigest() == expected_sha1:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
"Failed to check model hash, check the model is valid or has been downloaded adequately : %e",
|
|
||||||
e,
|
|
||||||
)
|
|
||||||
traceback.print_exc()
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1)
|
@lru_cache(maxsize=1)
|
||||||
def getFaceSwapModel(model_path: str) -> upscaled_inswapper.UpscaledINSwapper:
|
def getFaceSwapModel(
|
||||||
|
model_path: str, use_gpu: bool = False
|
||||||
|
) -> upscaled_inswapper.UpscaledINSwapper:
|
||||||
"""
|
"""
|
||||||
Retrieves the face swap model and initializes it if necessary.
|
Retrieves the face swap model and initializes it if necessary.
|
||||||
|
|
||||||
@@ -323,18 +372,11 @@ def getFaceSwapModel(model_path: str) -> upscaled_inswapper.UpscaledINSwapper:
|
|||||||
insightface.model_zoo.FaceModel: The face swap model.
|
insightface.model_zoo.FaceModel: The face swap model.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
expected_sha1 = "17a64851eaefd55ea597ee41e5c18409754244c5"
|
providers = get_providers()
|
||||||
if not is_sha1_matching(model_path, expected_sha1):
|
|
||||||
logger.error(
|
|
||||||
"Suspicious sha1 for model %s, check the model is valid or has been downloaded adequately. Should be %s",
|
|
||||||
model_path,
|
|
||||||
expected_sha1,
|
|
||||||
)
|
|
||||||
|
|
||||||
with tqdm(total=1, desc="Loading swap model", unit="model") as pbar:
|
with tqdm(total=1, desc="Loading swap model", unit="model") as pbar:
|
||||||
with capture_stdout() as captured:
|
with capture_stdout() as captured:
|
||||||
model = upscaled_inswapper.UpscaledINSwapper(
|
model = upscaled_inswapper.UpscaledINSwapper(
|
||||||
insightface.model_zoo.get_model(model_path, providers=providers)
|
insightface.model_zoo.get_model(model_path, providers=providers) # type: ignore
|
||||||
)
|
)
|
||||||
pbar.update(1)
|
pbar.update(1)
|
||||||
logger.info("%s", pformat(captured.getvalue()))
|
logger.info("%s", pformat(captured.getvalue()))
|
||||||
@@ -350,8 +392,8 @@ def getFaceSwapModel(model_path: str) -> upscaled_inswapper.UpscaledINSwapper:
|
|||||||
|
|
||||||
def get_faces(
|
def get_faces(
|
||||||
img_data: CV2ImgU8,
|
img_data: CV2ImgU8,
|
||||||
det_size: Tuple[int, int] = (640, 640),
|
|
||||||
det_thresh: Optional[float] = None,
|
det_thresh: Optional[float] = None,
|
||||||
|
det_size: Tuple[int, int] = (640, 640),
|
||||||
) -> List[Face]:
|
) -> List[Face]:
|
||||||
"""
|
"""
|
||||||
Detects and retrieves faces from an image using an analysis model.
|
Detects and retrieves faces from an image using an analysis model.
|
||||||
@@ -366,26 +408,40 @@ def get_faces(
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if det_thresh is None:
|
if det_thresh is None:
|
||||||
det_thresh = opts.data.get("faceswaplab_detection_threshold", 0.5)
|
det_thresh = get_sd_option("faceswaplab_detection_threshold", 0.5)
|
||||||
|
|
||||||
# Create a deep copy of the analysis model (otherwise det_size is attached to the analysis model and can't be changed)
|
auto_det_size = get_sd_option("faceswaplab_auto_det_size", True)
|
||||||
face_analyser = copy.deepcopy(getAnalysisModel())
|
if not auto_det_size:
|
||||||
|
x = get_sd_option("faceswaplab_det_size", 640)
|
||||||
|
det_size = (x, x)
|
||||||
|
|
||||||
# Prepare the analysis model for face detection with the specified detection size
|
face_analyser = getAnalysisModel(
|
||||||
face_analyser.prepare(ctx_id=0, det_thresh=det_thresh, det_size=det_size)
|
det_size=det_size, det_thresh=det_thresh, use_gpu=not is_cpu_provider()
|
||||||
|
)
|
||||||
|
|
||||||
# Get the detected faces from the image using the analysis model
|
# Get the detected faces from the image using the analysis model
|
||||||
face = face_analyser.get(img_data)
|
faces = face_analyser.get(img_data)
|
||||||
|
|
||||||
# If no faces are detected and the detection size is larger than 320x320,
|
# If no faces are detected and the detection size is larger than 320x320,
|
||||||
# recursively call the function with a smaller detection size
|
# recursively call the function with a smaller detection size
|
||||||
if len(face) == 0 and det_size[0] > 320 and det_size[1] > 320:
|
if len(faces) == 0:
|
||||||
det_size_half = (det_size[0] // 2, det_size[1] // 2)
|
if auto_det_size:
|
||||||
return get_faces(img_data, det_size=det_size_half, det_thresh=det_thresh)
|
if det_size[0] > 320 and det_size[1] > 320:
|
||||||
|
det_size_half = (det_size[0] // 2, det_size[1] // 2)
|
||||||
|
return get_faces(
|
||||||
|
img_data, det_size=det_size_half, det_thresh=det_thresh
|
||||||
|
)
|
||||||
|
|
||||||
|
# If no faces are detected print a warning to user about change in detection
|
||||||
|
else:
|
||||||
|
if det_size[0] > 320:
|
||||||
|
logger.warning(
|
||||||
|
"No faces detected, you might want to play with det_size by reducing it (in sd global settings). Lower (320) means more detection but less precise. Or activate auto-det-size."
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Sort the detected faces based on their x-coordinate of the bounding box
|
# Sort the detected faces based on their x-coordinate of the bounding box
|
||||||
return sorted(face, key=lambda x: x.bbox[0])
|
return sorted(faces, key=lambda x: x.bbox[0]) # type: ignore
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Failed to get faces %s", e)
|
logger.error("Failed to get faces %s", e)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@@ -422,7 +478,7 @@ def filter_faces(
|
|||||||
filtered_faces = sorted(
|
filtered_faces = sorted(
|
||||||
all_faces,
|
all_faces,
|
||||||
reverse=True,
|
reverse=True,
|
||||||
key=lambda x: (x.bbox[2] - x.bbox[0]) * (x.bbox[3] - x.bbox[1]),
|
key=lambda x: (x.bbox[2] - x.bbox[0]) * (x.bbox[3] - x.bbox[1]), # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
if filtering_options.source_gender is not None:
|
if filtering_options.source_gender is not None:
|
||||||
@@ -478,7 +534,7 @@ def get_or_default(l: List[Any], index: int, default: Any) -> Any:
|
|||||||
return l[index] if index < len(l) else default
|
return l[index] if index < len(l) else default
|
||||||
|
|
||||||
|
|
||||||
def get_faces_from_img_files(images: List[PILImage]) -> List[Optional[CV2ImgU8]]:
|
def get_faces_from_img_files(images: List[PILImage]) -> List[Face]:
|
||||||
"""
|
"""
|
||||||
Extracts faces from a list of image files.
|
Extracts faces from a list of image files.
|
||||||
|
|
||||||
@@ -490,7 +546,7 @@ def get_faces_from_img_files(images: List[PILImage]) -> List[Optional[CV2ImgU8]]
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
faces = []
|
faces: List[Face] = []
|
||||||
|
|
||||||
if len(images) > 0:
|
if len(images) > 0:
|
||||||
for img in images:
|
for img in images:
|
||||||
@@ -518,7 +574,7 @@ def blend_faces(faces: List[Face]) -> Optional[Face]:
|
|||||||
ValueError: If the embeddings have different shapes.
|
ValueError: If the embeddings have different shapes.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
embeddings = [face.embedding for face in faces]
|
embeddings: list[Any] = [face.embedding for face in faces]
|
||||||
|
|
||||||
if len(embeddings) > 0:
|
if len(embeddings) > 0:
|
||||||
embedding_shape = embeddings[0].shape
|
embedding_shape = embeddings[0].shape
|
||||||
@@ -544,19 +600,16 @@ def blend_faces(faces: List[Face]) -> Optional[Face]:
|
|||||||
|
|
||||||
|
|
||||||
def swap_face(
|
def swap_face(
|
||||||
reference_face: CV2ImgU8,
|
|
||||||
source_face: Face,
|
source_face: Face,
|
||||||
target_img: PILImage,
|
target_img: PILImage,
|
||||||
target_faces: List[Face],
|
target_faces: List[Face],
|
||||||
model: str,
|
model: str,
|
||||||
swapping_options: Optional[InswappperOptions],
|
swapping_options: Optional[InswappperOptions],
|
||||||
compute_similarity: bool = True,
|
|
||||||
) -> ImageResult:
|
) -> ImageResult:
|
||||||
"""
|
"""
|
||||||
Swaps faces in the target image with the source face.
|
Swaps faces in the target image with the source face.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
reference_face (CV2ImgU8): The reference face used for similarity comparison.
|
|
||||||
source_face (CV2ImgU8): The source face to be swapped.
|
source_face (CV2ImgU8): The source face to be swapped.
|
||||||
target_img (PILImage): The target image to swap faces in.
|
target_img (PILImage): The target image to swap faces in.
|
||||||
model (str): Path to the face swap model.
|
model (str): Path to the face swap model.
|
||||||
@@ -566,14 +619,16 @@ def swap_face(
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
return_result = ImageResult(target_img, {}, {})
|
return_result = ImageResult(target_img, {}, {})
|
||||||
target_img_cv2: CV2ImgU8 = cv2.cvtColor(np.array(target_img), cv2.COLOR_RGB2BGR)
|
target_img_cv2: CV2ImgU8 = cv2.cvtColor(
|
||||||
|
np.array(target_img), cv2.COLOR_RGB2BGR
|
||||||
|
).astype("uint8")
|
||||||
try:
|
try:
|
||||||
gender = source_face["gender"]
|
gender = source_face["gender"]
|
||||||
logger.info("Source Gender %s", gender)
|
logger.info("Source Gender %s", gender)
|
||||||
if source_face is not None:
|
if source_face is not None:
|
||||||
result = target_img_cv2
|
result: CV2ImgU8 = target_img_cv2
|
||||||
model_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), model)
|
model_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), model)
|
||||||
face_swapper = getFaceSwapModel(model_path)
|
face_swapper = getFaceSwapModel(model_path, use_gpu=not is_cpu_provider())
|
||||||
logger.info("Target faces count : %s", len(target_faces))
|
logger.info("Target faces count : %s", len(target_faces))
|
||||||
|
|
||||||
for i, swapped_face in enumerate(target_faces):
|
for i, swapped_face in enumerate(target_faces):
|
||||||
@@ -631,9 +686,9 @@ def process_image_unit(
|
|||||||
model: str,
|
model: str,
|
||||||
unit: FaceSwapUnitSettings,
|
unit: FaceSwapUnitSettings,
|
||||||
image: PILImage,
|
image: PILImage,
|
||||||
info: str = None,
|
info: Optional[str] = None,
|
||||||
force_blend: bool = False,
|
force_blend: bool = False,
|
||||||
) -> List[Tuple[PILImage, str]]:
|
) -> List[Tuple[PILImage, Optional[str]]]:
|
||||||
"""Process one image and return a List of (image, info) (one if blended, many if not).
|
"""Process one image and return a List of (image, info) (one if blended, many if not).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -674,7 +729,9 @@ def process_image_unit(
|
|||||||
sort_by_face_size=unit.sort_by_size,
|
sort_by_face_size=unit.sort_by_size,
|
||||||
)
|
)
|
||||||
|
|
||||||
target_faces = filter_faces(faces, filtering_options=face_filtering_options)
|
target_faces: List[Face] = filter_faces(
|
||||||
|
all_faces=faces, filtering_options=face_filtering_options
|
||||||
|
)
|
||||||
|
|
||||||
# Apply pre-inpainting to image
|
# Apply pre-inpainting to image
|
||||||
if unit.pre_inpainting.inpainting_denoising_strengh > 0:
|
if unit.pre_inpainting.inpainting_denoising_strengh > 0:
|
||||||
@@ -684,13 +741,11 @@ def process_image_unit(
|
|||||||
|
|
||||||
save_img_debug(image, "Before swap")
|
save_img_debug(image, "Before swap")
|
||||||
result: ImageResult = swap_face(
|
result: ImageResult = swap_face(
|
||||||
reference_face=reference_face,
|
|
||||||
source_face=src_face,
|
source_face=src_face,
|
||||||
target_img=current_image,
|
target_img=current_image,
|
||||||
target_faces=target_faces,
|
target_faces=target_faces,
|
||||||
model=model,
|
model=model,
|
||||||
swapping_options=unit.swapping_options,
|
swapping_options=unit.swapping_options,
|
||||||
compute_similarity=unit.compute_similarity,
|
|
||||||
)
|
)
|
||||||
# Apply post-inpainting to image
|
# Apply post-inpainting to image
|
||||||
if unit.post_inpainting.inpainting_denoising_strengh > 0:
|
if unit.post_inpainting.inpainting_denoising_strengh > 0:
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
from dataclasses import *
|
from dataclasses import *
|
||||||
|
from typing import Optional
|
||||||
from client_api import api_utils
|
from client_api import api_utils
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class InswappperOptions:
|
class InswappperOptions:
|
||||||
face_restorer_name: str = None
|
face_restorer_name: Optional[str] = None
|
||||||
restorer_visibility: float = 1
|
restorer_visibility: float = 1
|
||||||
codeformer_weight: float = 1
|
codeformer_weight: float = 1
|
||||||
upscaler_name: str = None
|
upscaler_name: Optional[str] = None
|
||||||
improved_mask: bool = False
|
improved_mask: bool = False
|
||||||
color_corrections: bool = False
|
color_corrections: bool = False
|
||||||
sharpen: bool = False
|
sharpen: bool = False
|
||||||
erosion_factor: float = 1.0
|
erosion_factor: float = 1.0
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_api_dto(dto: api_utils.InswappperOptions) -> "InswappperOptions":
|
def from_api_dto(dto: Optional[api_utils.InswappperOptions]) -> "InswappperOptions":
|
||||||
"""
|
"""
|
||||||
Converts a InpaintingOptions object from an API DTO (Data Transfer Object).
|
Converts a InpaintingOptions object from an API DTO (Data Transfer Object).
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
from typing import Any, Tuple, Union
|
from typing import Any, Optional, Tuple, Union
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from insightface.model_zoo.inswapper import INSwapper
|
from insightface.model_zoo.inswapper import INSwapper
|
||||||
from insightface.utils import face_align
|
from insightface.utils import face_align
|
||||||
from modules import processing, shared
|
from modules import processing, shared
|
||||||
from modules.shared import opts
|
|
||||||
from modules.upscaler import UpscalerData
|
from modules.upscaler import UpscalerData
|
||||||
|
|
||||||
from scripts.faceswaplab_postprocessing import upscaling
|
from scripts.faceswaplab_postprocessing import upscaling
|
||||||
@@ -14,13 +13,14 @@ from scripts.faceswaplab_postprocessing.postprocessing_options import (
|
|||||||
from scripts.faceswaplab_swapping.facemask import generate_face_mask
|
from scripts.faceswaplab_swapping.facemask import generate_face_mask
|
||||||
from scripts.faceswaplab_swapping.upcaled_inswapper_options import InswappperOptions
|
from scripts.faceswaplab_swapping.upcaled_inswapper_options import InswappperOptions
|
||||||
from scripts.faceswaplab_utils.imgutils import cv2_to_pil, pil_to_cv2
|
from scripts.faceswaplab_utils.imgutils import cv2_to_pil, pil_to_cv2
|
||||||
|
from scripts.faceswaplab_utils.sd_utils import get_sd_option
|
||||||
from scripts.faceswaplab_utils.typing import CV2ImgU8, Face
|
from scripts.faceswaplab_utils.typing import CV2ImgU8, Face
|
||||||
from scripts.faceswaplab_utils.faceswaplab_logging import logger
|
from scripts.faceswaplab_utils.faceswaplab_logging import logger
|
||||||
|
|
||||||
|
|
||||||
def get_upscaler() -> UpscalerData:
|
def get_upscaler() -> Optional[UpscalerData]:
|
||||||
for upscaler in shared.sd_upscalers:
|
for upscaler in shared.sd_upscalers:
|
||||||
if upscaler.name == opts.data.get(
|
if upscaler.name == get_sd_option(
|
||||||
"faceswaplab_upscaled_swapper_upscaler", "LDSR"
|
"faceswaplab_upscaled_swapper_upscaler", "LDSR"
|
||||||
):
|
):
|
||||||
return upscaler
|
return upscaler
|
||||||
@@ -130,8 +130,14 @@ class UpscaledINSwapper(INSwapper):
|
|||||||
self.__dict__.update(inswapper.__dict__)
|
self.__dict__.update(inswapper.__dict__)
|
||||||
|
|
||||||
def upscale_and_restore(
|
def upscale_and_restore(
|
||||||
self, img: CV2ImgU8, k: int = 2, inswapper_options: InswappperOptions = None
|
self,
|
||||||
|
img: CV2ImgU8,
|
||||||
|
k: int = 2,
|
||||||
|
inswapper_options: Optional[InswappperOptions] = None,
|
||||||
) -> CV2ImgU8:
|
) -> CV2ImgU8:
|
||||||
|
if inswapper_options is None:
|
||||||
|
return img
|
||||||
|
|
||||||
pil_img = cv2_to_pil(img)
|
pil_img = cv2_to_pil(img)
|
||||||
pp_options = PostProcessingOptions(
|
pp_options = PostProcessingOptions(
|
||||||
upscaler_name=inswapper_options.upscaler_name,
|
upscaler_name=inswapper_options.upscaler_name,
|
||||||
@@ -156,7 +162,7 @@ class UpscaledINSwapper(INSwapper):
|
|||||||
target_face: Face,
|
target_face: Face,
|
||||||
source_face: Face,
|
source_face: Face,
|
||||||
paste_back: bool = True,
|
paste_back: bool = True,
|
||||||
options: InswappperOptions = None,
|
options: Optional[InswappperOptions] = None,
|
||||||
) -> Union[CV2ImgU8, Tuple[CV2ImgU8, Any]]:
|
) -> Union[CV2ImgU8, Tuple[CV2ImgU8, Any]]:
|
||||||
aimg, M = face_align.norm_crop2(img, target_face.kps, self.input_size[0])
|
aimg, M = face_align.norm_crop2(img, target_face.kps, self.input_size[0])
|
||||||
blob = cv2.dnn.blobFromImage(
|
blob = cv2.dnn.blobFromImage(
|
||||||
@@ -166,9 +172,10 @@ class UpscaledINSwapper(INSwapper):
|
|||||||
(self.input_mean, self.input_mean, self.input_mean),
|
(self.input_mean, self.input_mean, self.input_mean),
|
||||||
swapRB=True,
|
swapRB=True,
|
||||||
)
|
)
|
||||||
latent = source_face.normed_embedding.reshape((1, -1))
|
latent = source_face.normed_embedding.reshape((1, -1)) # type: ignore
|
||||||
latent = np.dot(latent, self.emap)
|
latent = np.dot(latent, self.emap)
|
||||||
latent /= np.linalg.norm(latent)
|
latent /= np.linalg.norm(latent)
|
||||||
|
assert self.session is not None
|
||||||
pred = self.session.run(
|
pred = self.session.run(
|
||||||
self.output_names, {self.input_names[0]: blob, self.input_names[1]: latent}
|
self.output_names, {self.input_names[0]: blob, self.input_names[1]: latent}
|
||||||
)[0]
|
)[0]
|
||||||
@@ -195,7 +202,7 @@ class UpscaledINSwapper(INSwapper):
|
|||||||
logger.info("*" * 80)
|
logger.info("*" * 80)
|
||||||
logger.info(f"Inswapper")
|
logger.info(f"Inswapper")
|
||||||
|
|
||||||
if options.upscaler_name:
|
if options.upscaler_name and options.upscaler_name != "None":
|
||||||
# Upscale original image
|
# Upscale original image
|
||||||
k = 4
|
k = 4
|
||||||
aimg, M = face_align.norm_crop2(
|
aimg, M = face_align.norm_crop2(
|
||||||
@@ -209,13 +216,11 @@ class UpscaledINSwapper(INSwapper):
|
|||||||
bgr_fake, inswapper_options=options, k=k
|
bgr_fake, inswapper_options=options, k=k
|
||||||
)
|
)
|
||||||
|
|
||||||
if options.improved_mask:
|
fake_diff: CV2ImgU8 = None # type: ignore
|
||||||
logger.info("improved_mask")
|
|
||||||
mask = get_face_mask(aimg, bgr_fake)
|
|
||||||
bgr_fake = merge_images_with_mask(aimg, bgr_fake, mask)
|
|
||||||
|
|
||||||
# compute fake_diff before sharpen and color correction (better result)
|
if not options.improved_mask:
|
||||||
fake_diff = compute_diff(bgr_fake, aimg)
|
# If improved mask is not used, we should compute before sharpen and color correction (better diff)
|
||||||
|
fake_diff = compute_diff(bgr_fake, aimg=aimg)
|
||||||
|
|
||||||
if options.sharpen:
|
if options.sharpen:
|
||||||
logger.info("sharpen")
|
logger.info("sharpen")
|
||||||
@@ -232,6 +237,24 @@ class UpscaledINSwapper(INSwapper):
|
|||||||
)
|
)
|
||||||
bgr_fake = pil_to_cv2(bgr_fake_pil)
|
bgr_fake = pil_to_cv2(bgr_fake_pil)
|
||||||
|
|
||||||
|
if options.improved_mask:
|
||||||
|
if k == 1:
|
||||||
|
logger.warning(
|
||||||
|
"Please note that improved mask does not work well without upscaling. Set upscaling to Lanczos at least if you want speed and want to use improved mask."
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("improved_mask")
|
||||||
|
mask = get_face_mask(aimg, bgr_fake)
|
||||||
|
# save_img_debug(cv2_to_pil(bgr_fake), "Before Mask")
|
||||||
|
bgr_fake = merge_images_with_mask(aimg, bgr_fake, mask)
|
||||||
|
# save_img_debug(cv2_to_pil(bgr_fake), "After Mask")
|
||||||
|
|
||||||
|
fake_diff = compute_diff(bgr_fake, aimg=aimg)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
fake_diff is not None
|
||||||
|
), "fake diff is None, this should not happen"
|
||||||
|
|
||||||
logger.info("*" * 80)
|
logger.info("*" * 80)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -254,6 +277,7 @@ class UpscaledINSwapper(INSwapper):
|
|||||||
(target_img.shape[1], target_img.shape[0]),
|
(target_img.shape[1], target_img.shape[0]),
|
||||||
borderValue=0.0,
|
borderValue=0.0,
|
||||||
)
|
)
|
||||||
|
|
||||||
fake_diff = cv2.warpAffine(
|
fake_diff = cv2.warpAffine(
|
||||||
fake_diff,
|
fake_diff,
|
||||||
IM,
|
IM,
|
||||||
@@ -262,7 +286,6 @@ class UpscaledINSwapper(INSwapper):
|
|||||||
)
|
)
|
||||||
img_white[img_white > 20] = 255
|
img_white[img_white > 20] = 255
|
||||||
fthresh = 10
|
fthresh = 10
|
||||||
print("fthresh", fthresh)
|
|
||||||
fake_diff[fake_diff < fthresh] = 0
|
fake_diff[fake_diff < fthresh] = 0
|
||||||
fake_diff[fake_diff >= fthresh] = 255
|
fake_diff[fake_diff >= fthresh] = 255
|
||||||
img_mask = img_white
|
img_mask = img_white
|
||||||
@@ -270,7 +293,7 @@ class UpscaledINSwapper(INSwapper):
|
|||||||
mask_h = np.max(mask_h_inds) - np.min(mask_h_inds)
|
mask_h = np.max(mask_h_inds) - np.min(mask_h_inds)
|
||||||
mask_w = np.max(mask_w_inds) - np.min(mask_w_inds)
|
mask_w = np.max(mask_w_inds) - np.min(mask_w_inds)
|
||||||
mask_size = int(np.sqrt(mask_h * mask_w))
|
mask_size = int(np.sqrt(mask_h * mask_w))
|
||||||
erosion_factor = options.erosion_factor
|
erosion_factor = options.erosion_factor if options else 1
|
||||||
|
|
||||||
k = max(int(mask_size // 10 * erosion_factor), int(10 * erosion_factor))
|
k = max(int(mask_size // 10 * erosion_factor), int(10 * erosion_factor))
|
||||||
|
|
||||||
|
|||||||
@@ -1,68 +1,74 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
import gradio as gr
|
import gradio as gr
|
||||||
from modules.shared import opts
|
|
||||||
from modules import sd_models, sd_samplers
|
from modules import sd_models, sd_samplers
|
||||||
|
from scripts.faceswaplab_utils.sd_utils import get_sd_option
|
||||||
|
|
||||||
|
|
||||||
def face_inpainting_ui(
|
def face_inpainting_ui(id_prefix: str = "faceswaplab") -> List[gr.components.Component]:
|
||||||
name: str, id_prefix: str = "faceswaplab", description: str = ""
|
inpainting_denoising_strength = gr.Slider(
|
||||||
) -> List[gr.components.Component]:
|
0,
|
||||||
with gr.Accordion(name, open=False):
|
1,
|
||||||
gr.Markdown(description)
|
0,
|
||||||
inpainting_denoising_strength = gr.Slider(
|
step=0.01,
|
||||||
0,
|
elem_id=f"{id_prefix}_pp_inpainting_denoising_strength",
|
||||||
|
label="Denoising strenght",
|
||||||
|
)
|
||||||
|
|
||||||
|
inpainting_denoising_prompt = gr.Textbox(
|
||||||
|
get_sd_option(
|
||||||
|
"faceswaplab_pp_default_inpainting_prompt", "Portrait of a [gender]"
|
||||||
|
),
|
||||||
|
elem_id=f"{id_prefix}_pp_inpainting_denoising_prompt",
|
||||||
|
label="Inpainting prompt use [gender] instead of men or woman",
|
||||||
|
)
|
||||||
|
inpainting_denoising_negative_prompt = gr.Textbox(
|
||||||
|
get_sd_option("faceswaplab_pp_default_inpainting_negative_prompt", "blurry"),
|
||||||
|
elem_id=f"{id_prefix}_pp_inpainting_denoising_neg_prompt",
|
||||||
|
label="Inpainting negative prompt use [gender] instead of men or woman",
|
||||||
|
)
|
||||||
|
with gr.Row():
|
||||||
|
samplers_names = [s.name for s in sd_samplers.all_samplers]
|
||||||
|
inpainting_sampler = gr.Dropdown(
|
||||||
|
choices=samplers_names,
|
||||||
|
value=[samplers_names[0]],
|
||||||
|
label="Inpainting Sampler",
|
||||||
|
elem_id=f"{id_prefix}_pp_inpainting_sampler",
|
||||||
|
)
|
||||||
|
inpainting_denoising_steps = gr.Slider(
|
||||||
1,
|
1,
|
||||||
0,
|
150,
|
||||||
step=0.01,
|
20,
|
||||||
elem_id=f"{id_prefix}_pp_inpainting_denoising_strength",
|
step=1,
|
||||||
label="Denoising strenght",
|
label="Inpainting steps",
|
||||||
|
elem_id=f"{id_prefix}_pp_inpainting_steps",
|
||||||
)
|
)
|
||||||
|
|
||||||
inpainting_denoising_prompt = gr.Textbox(
|
inpaiting_model = gr.Dropdown(
|
||||||
opts.data.get(
|
choices=["Current"] + sd_models.checkpoint_tiles(),
|
||||||
"faceswaplab_pp_default_inpainting_prompt", "Portrait of a [gender]"
|
default="Current",
|
||||||
),
|
label="sd model (experimental)",
|
||||||
elem_id=f"{id_prefix}_pp_inpainting_denoising_prompt",
|
elem_id=f"{id_prefix}_pp_inpainting_sd_model",
|
||||||
label="Inpainting prompt use [gender] instead of men or woman",
|
)
|
||||||
)
|
|
||||||
inpainting_denoising_negative_prompt = gr.Textbox(
|
|
||||||
opts.data.get(
|
|
||||||
"faceswaplab_pp_default_inpainting_negative_prompt", "blurry"
|
|
||||||
),
|
|
||||||
elem_id=f"{id_prefix}_pp_inpainting_denoising_neg_prompt",
|
|
||||||
label="Inpainting negative prompt use [gender] instead of men or woman",
|
|
||||||
)
|
|
||||||
with gr.Row():
|
|
||||||
samplers_names = [s.name for s in sd_samplers.all_samplers]
|
|
||||||
inpainting_sampler = gr.Dropdown(
|
|
||||||
choices=samplers_names,
|
|
||||||
value=[samplers_names[0]],
|
|
||||||
label="Inpainting Sampler",
|
|
||||||
elem_id=f"{id_prefix}_pp_inpainting_sampler",
|
|
||||||
)
|
|
||||||
inpainting_denoising_steps = gr.Slider(
|
|
||||||
1,
|
|
||||||
150,
|
|
||||||
20,
|
|
||||||
step=1,
|
|
||||||
label="Inpainting steps",
|
|
||||||
elem_id=f"{id_prefix}_pp_inpainting_steps",
|
|
||||||
)
|
|
||||||
|
|
||||||
inpaiting_model = gr.Dropdown(
|
inpaiting_seed = gr.Number(
|
||||||
choices=["Current"] + sd_models.checkpoint_tiles(),
|
label="Inpainting seed",
|
||||||
default="Current",
|
value=0,
|
||||||
label="sd model (experimental)",
|
minimum=0,
|
||||||
elem_id=f"{id_prefix}_pp_inpainting_sd_model",
|
precision=0,
|
||||||
)
|
elem_id=f"{id_prefix}_pp_inpainting_seed",
|
||||||
|
)
|
||||||
|
|
||||||
gradio_components: List[gr.components.Component] = [
|
gradio_components: List[gr.components.Component] = [
|
||||||
inpainting_denoising_strength,
|
inpainting_denoising_strength,
|
||||||
inpainting_denoising_prompt,
|
inpainting_denoising_prompt,
|
||||||
inpainting_denoising_negative_prompt,
|
inpainting_denoising_negative_prompt,
|
||||||
inpainting_denoising_steps,
|
inpainting_denoising_steps,
|
||||||
inpainting_sampler,
|
inpainting_sampler,
|
||||||
inpaiting_model,
|
inpaiting_model,
|
||||||
]
|
inpaiting_seed,
|
||||||
|
]
|
||||||
|
|
||||||
return gradio_components
|
for component in gradio_components:
|
||||||
|
setattr(component, "do_not_save_to_config", True)
|
||||||
|
|
||||||
|
return gradio_components
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
import gradio as gr
|
import gradio as gr
|
||||||
import modules
|
from modules import shared
|
||||||
from modules import shared, sd_models
|
|
||||||
from modules.shared import opts
|
|
||||||
from scripts.faceswaplab_postprocessing.postprocessing_options import InpaintingWhen
|
from scripts.faceswaplab_postprocessing.postprocessing_options import InpaintingWhen
|
||||||
|
from scripts.faceswaplab_utils.sd_utils import get_sd_option
|
||||||
|
from scripts.faceswaplab_ui.faceswaplab_inpainting_ui import face_inpainting_ui
|
||||||
|
|
||||||
|
|
||||||
def postprocessing_ui() -> List[gr.components.Component]:
|
def postprocessing_ui() -> List[gr.components.Component]:
|
||||||
@@ -15,18 +15,19 @@ def postprocessing_ui() -> List[gr.components.Component]:
|
|||||||
face_restorer_name = gr.Radio(
|
face_restorer_name = gr.Radio(
|
||||||
label="Restore Face",
|
label="Restore Face",
|
||||||
choices=["None"] + [x.name() for x in shared.face_restorers],
|
choices=["None"] + [x.name() for x in shared.face_restorers],
|
||||||
value=lambda: opts.data.get(
|
value=get_sd_option(
|
||||||
"faceswaplab_pp_default_face_restorer",
|
"faceswaplab_pp_default_face_restorer",
|
||||||
"None",
|
shared.face_restorers[0].name(),
|
||||||
),
|
),
|
||||||
type="value",
|
type="value",
|
||||||
elem_id="faceswaplab_pp_face_restorer",
|
elem_id="faceswaplab_pp_face_restorer",
|
||||||
)
|
)
|
||||||
|
|
||||||
with gr.Column():
|
with gr.Column():
|
||||||
face_restorer_visibility = gr.Slider(
|
face_restorer_visibility = gr.Slider(
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
value=lambda: opts.data.get(
|
value=get_sd_option(
|
||||||
"faceswaplab_pp_default_face_restorer_visibility", 1
|
"faceswaplab_pp_default_face_restorer_visibility", 1
|
||||||
),
|
),
|
||||||
step=0.001,
|
step=0.001,
|
||||||
@@ -36,7 +37,7 @@ def postprocessing_ui() -> List[gr.components.Component]:
|
|||||||
codeformer_weight = gr.Slider(
|
codeformer_weight = gr.Slider(
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
value=lambda: opts.data.get(
|
value=get_sd_option(
|
||||||
"faceswaplab_pp_default_face_restorer_weight", 1
|
"faceswaplab_pp_default_face_restorer_weight", 1
|
||||||
),
|
),
|
||||||
step=0.001,
|
step=0.001,
|
||||||
@@ -45,7 +46,7 @@ def postprocessing_ui() -> List[gr.components.Component]:
|
|||||||
)
|
)
|
||||||
upscaler_name = gr.Dropdown(
|
upscaler_name = gr.Dropdown(
|
||||||
choices=[upscaler.name for upscaler in shared.sd_upscalers],
|
choices=[upscaler.name for upscaler in shared.sd_upscalers],
|
||||||
value=lambda: opts.data.get("faceswaplab_pp_default_upscaler", "None"),
|
value=get_sd_option("faceswaplab_pp_default_upscaler", "None"),
|
||||||
label="Upscaler",
|
label="Upscaler",
|
||||||
elem_id="faceswaplab_pp_upscaler",
|
elem_id="faceswaplab_pp_upscaler",
|
||||||
)
|
)
|
||||||
@@ -60,16 +61,15 @@ def postprocessing_ui() -> List[gr.components.Component]:
|
|||||||
upscaler_visibility = gr.Slider(
|
upscaler_visibility = gr.Slider(
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
value=lambda: opts.data.get(
|
value=get_sd_option("faceswaplab_pp_default_upscaler_visibility", 1),
|
||||||
"faceswaplab_pp_default_upscaler_visibility", 1
|
|
||||||
),
|
|
||||||
step=0.1,
|
step=0.1,
|
||||||
label="Upscaler visibility (if scale = 1)",
|
label="Upscaler visibility (if scale = 1)",
|
||||||
elem_id="faceswaplab_pp_upscaler_visibility",
|
elem_id="faceswaplab_pp_upscaler_visibility",
|
||||||
)
|
)
|
||||||
with gr.Accordion(f"Post Inpainting", open=True):
|
|
||||||
|
with gr.Accordion(label="Global-Inpainting (all faces)", open=False):
|
||||||
gr.Markdown(
|
gr.Markdown(
|
||||||
"""Inpainting sends image to inpainting with a mask on face (once for each faces)."""
|
"Inpainting sends image to inpainting with a mask on face (once for each faces)."
|
||||||
)
|
)
|
||||||
inpainting_when = gr.Dropdown(
|
inpainting_when = gr.Dropdown(
|
||||||
elem_id="faceswaplab_pp_inpainting_when",
|
elem_id="faceswaplab_pp_inpainting_when",
|
||||||
@@ -77,53 +77,9 @@ def postprocessing_ui() -> List[gr.components.Component]:
|
|||||||
value=[InpaintingWhen.BEFORE_RESTORE_FACE.value],
|
value=[InpaintingWhen.BEFORE_RESTORE_FACE.value],
|
||||||
label="Enable/When",
|
label="Enable/When",
|
||||||
)
|
)
|
||||||
inpainting_denoising_strength = gr.Slider(
|
global_inpainting = face_inpainting_ui("faceswaplab_gpp")
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
step=0.01,
|
|
||||||
elem_id="faceswaplab_pp_inpainting_denoising_strength",
|
|
||||||
label="Denoising strenght (will send face to img2img after processing)",
|
|
||||||
)
|
|
||||||
|
|
||||||
inpainting_denoising_prompt = gr.Textbox(
|
components = [
|
||||||
opts.data.get(
|
|
||||||
"faceswaplab_pp_default_inpainting_prompt", "Portrait of a [gender]"
|
|
||||||
),
|
|
||||||
elem_id="faceswaplab_pp_inpainting_denoising_prompt",
|
|
||||||
label="Inpainting prompt use [gender] instead of men or woman",
|
|
||||||
)
|
|
||||||
inpainting_denoising_negative_prompt = gr.Textbox(
|
|
||||||
opts.data.get(
|
|
||||||
"faceswaplab_pp_default_inpainting_negative_prompt", "blurry"
|
|
||||||
),
|
|
||||||
elem_id="faceswaplab_pp_inpainting_denoising_neg_prompt",
|
|
||||||
label="Inpainting negative prompt use [gender] instead of men or woman",
|
|
||||||
)
|
|
||||||
with gr.Row():
|
|
||||||
samplers_names = [s.name for s in modules.sd_samplers.all_samplers]
|
|
||||||
inpainting_sampler = gr.Dropdown(
|
|
||||||
choices=samplers_names,
|
|
||||||
value=[samplers_names[0]],
|
|
||||||
label="Inpainting Sampler",
|
|
||||||
elem_id="faceswaplab_pp_inpainting_sampler",
|
|
||||||
)
|
|
||||||
inpainting_denoising_steps = gr.Slider(
|
|
||||||
1,
|
|
||||||
150,
|
|
||||||
20,
|
|
||||||
step=1,
|
|
||||||
label="Inpainting steps",
|
|
||||||
elem_id="faceswaplab_pp_inpainting_steps",
|
|
||||||
)
|
|
||||||
|
|
||||||
inpaiting_model = gr.Dropdown(
|
|
||||||
choices=["Current"] + sd_models.checkpoint_tiles(),
|
|
||||||
default="Current",
|
|
||||||
label="sd model (experimental)",
|
|
||||||
elem_id="faceswaplab_pp_inpainting_sd_model",
|
|
||||||
)
|
|
||||||
return [
|
|
||||||
face_restorer_name,
|
face_restorer_name,
|
||||||
face_restorer_visibility,
|
face_restorer_visibility,
|
||||||
codeformer_weight,
|
codeformer_weight,
|
||||||
@@ -131,10 +87,9 @@ def postprocessing_ui() -> List[gr.components.Component]:
|
|||||||
upscaler_scale,
|
upscaler_scale,
|
||||||
upscaler_visibility,
|
upscaler_visibility,
|
||||||
inpainting_when,
|
inpainting_when,
|
||||||
inpainting_denoising_strength,
|
] + global_inpainting
|
||||||
inpainting_denoising_prompt,
|
|
||||||
inpainting_denoising_negative_prompt,
|
# Ask sd to not store in ui-config.json
|
||||||
inpainting_denoising_steps,
|
for component in components:
|
||||||
inpainting_sampler,
|
setattr(component, "do_not_save_to_config", True)
|
||||||
inpaiting_model,
|
return components
|
||||||
]
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import traceback
|
import traceback
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import *
|
from typing import *
|
||||||
|
from scripts.faceswaplab_swapping import face_checkpoints
|
||||||
|
from scripts.faceswaplab_utils.sd_utils import get_sd_option
|
||||||
from scripts.faceswaplab_utils.typing import *
|
from scripts.faceswaplab_utils.typing import *
|
||||||
import gradio as gr
|
import gradio as gr
|
||||||
import onnx
|
import onnx
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from modules.shared import opts
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
import scripts.faceswaplab_swapping.swapper as swapper
|
import scripts.faceswaplab_swapping.swapper as swapper
|
||||||
@@ -15,9 +16,9 @@ from scripts.faceswaplab_postprocessing.postprocessing_options import (
|
|||||||
from scripts.faceswaplab_ui.faceswaplab_postprocessing_ui import postprocessing_ui
|
from scripts.faceswaplab_ui.faceswaplab_postprocessing_ui import postprocessing_ui
|
||||||
from scripts.faceswaplab_ui.faceswaplab_unit_settings import FaceSwapUnitSettings
|
from scripts.faceswaplab_ui.faceswaplab_unit_settings import FaceSwapUnitSettings
|
||||||
from scripts.faceswaplab_ui.faceswaplab_unit_ui import faceswap_unit_ui
|
from scripts.faceswaplab_ui.faceswaplab_unit_ui import faceswap_unit_ui
|
||||||
from scripts.faceswaplab_utils import face_checkpoints_utils, imgutils
|
from scripts.faceswaplab_utils import imgutils
|
||||||
from scripts.faceswaplab_utils.faceswaplab_logging import logger
|
from scripts.faceswaplab_utils.faceswaplab_logging import logger
|
||||||
from scripts.faceswaplab_utils.models_utils import get_models
|
from scripts.faceswaplab_utils.models_utils import get_swap_models
|
||||||
from scripts.faceswaplab_utils.ui_utils import dataclasses_from_flat_list
|
from scripts.faceswaplab_utils.ui_utils import dataclasses_from_flat_list
|
||||||
|
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ def extract_faces(
|
|||||||
[PostProcessingOptions], components
|
[PostProcessingOptions], components
|
||||||
).pop()
|
).pop()
|
||||||
images = [
|
images = [
|
||||||
Image.open(file.name) for file in files
|
Image.open(file.name) for file in files # type: ignore
|
||||||
] # potentially greedy but Image.open is supposed to be lazy
|
] # potentially greedy but Image.open is supposed to be lazy
|
||||||
result_images = swapper.extract_faces(
|
result_images = swapper.extract_faces(
|
||||||
images, extract_path=extract_path, postprocess_options=postprocess_options
|
images, extract_path=extract_path, postprocess_options=postprocess_options
|
||||||
@@ -136,7 +137,7 @@ def analyse_faces(image: PILImage, det_threshold: float = 0.5) -> Optional[str]:
|
|||||||
|
|
||||||
|
|
||||||
def build_face_checkpoint_and_save(
|
def build_face_checkpoint_and_save(
|
||||||
batch_files: gr.File, name: str, overwrite: bool
|
batch_files: List[gr.File], name: str, overwrite: bool
|
||||||
) -> PILImage:
|
) -> PILImage:
|
||||||
"""
|
"""
|
||||||
Builds a face checkpoint using the provided image files, performs face swapping,
|
Builds a face checkpoint using the provided image files, performs face swapping,
|
||||||
@@ -154,17 +155,19 @@ def build_face_checkpoint_and_save(
|
|||||||
try:
|
try:
|
||||||
if not batch_files:
|
if not batch_files:
|
||||||
logger.error("No face found")
|
logger.error("No face found")
|
||||||
return None
|
return None # type: ignore (Optional not really supported by old gradio)
|
||||||
images = [Image.open(file.name) for file in batch_files]
|
images: list[PILImage] = [Image.open(file.name) for file in batch_files] # type: ignore
|
||||||
preview_image = face_checkpoints_utils.build_face_checkpoint_and_save(
|
preview_image: PILImage | None = (
|
||||||
images, name, overwrite=overwrite
|
face_checkpoints.build_face_checkpoint_and_save(
|
||||||
|
images=images, name=name, overwrite=overwrite
|
||||||
|
)
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Failed to build checkpoint %s", e)
|
logger.error("Failed to build checkpoint %s", e)
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return None
|
return None # type: ignore
|
||||||
return preview_image
|
return preview_image # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def explore_onnx_faceswap_model(model_path: str) -> pd.DataFrame:
|
def explore_onnx_faceswap_model(model_path: str) -> pd.DataFrame:
|
||||||
@@ -197,7 +200,7 @@ def explore_onnx_faceswap_model(model_path: str) -> pd.DataFrame:
|
|||||||
logger.error("Failed to explore model %s", e)
|
logger.error("Failed to explore model %s", e)
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return None
|
return None # type: ignore
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
|
||||||
@@ -205,7 +208,7 @@ def batch_process(
|
|||||||
files: List[gr.File], save_path: str, *components: Tuple[Any, ...]
|
files: List[gr.File], save_path: str, *components: Tuple[Any, ...]
|
||||||
) -> List[PILImage]:
|
) -> List[PILImage]:
|
||||||
try:
|
try:
|
||||||
units_count = opts.data.get("faceswaplab_units_count", 3)
|
units_count = get_sd_option("faceswaplab_units_count", 3)
|
||||||
|
|
||||||
classes: List[Any] = dataclasses_from_flat_list(
|
classes: List[Any] = dataclasses_from_flat_list(
|
||||||
[FaceSwapUnitSettings] * units_count + [PostProcessingOptions],
|
[FaceSwapUnitSettings] * units_count + [PostProcessingOptions],
|
||||||
@@ -216,15 +219,16 @@ def batch_process(
|
|||||||
]
|
]
|
||||||
postprocess_options = classes[-1]
|
postprocess_options = classes[-1]
|
||||||
|
|
||||||
images = [
|
images_paths = [file.name for file in files] # type: ignore
|
||||||
Image.open(file.name) for file in files
|
|
||||||
] # potentially greedy but Image.open is supposed to be lazy
|
|
||||||
|
|
||||||
return swapper.batch_process(
|
return (
|
||||||
images,
|
swapper.batch_process(
|
||||||
save_path=save_path,
|
images_paths,
|
||||||
units=units,
|
save_path=save_path,
|
||||||
postprocess_options=postprocess_options,
|
units=units,
|
||||||
|
postprocess_options=postprocess_options,
|
||||||
|
)
|
||||||
|
or []
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Batch Process error : %s", e)
|
logger.error("Batch Process error : %s", e)
|
||||||
@@ -234,7 +238,7 @@ def batch_process(
|
|||||||
|
|
||||||
|
|
||||||
def tools_ui() -> None:
|
def tools_ui() -> None:
|
||||||
models = get_models()
|
models = get_swap_models()
|
||||||
with gr.Tab("Tools"):
|
with gr.Tab("Tools"):
|
||||||
with gr.Tab("Build"):
|
with gr.Tab("Build"):
|
||||||
gr.Markdown(
|
gr.Markdown(
|
||||||
@@ -306,7 +310,7 @@ def tools_ui() -> None:
|
|||||||
label="Extracted faces",
|
label="Extracted faces",
|
||||||
show_label=False,
|
show_label=False,
|
||||||
elem_id="faceswaplab_extract_results",
|
elem_id="faceswaplab_extract_results",
|
||||||
).style(columns=[2], rows=[2])
|
)
|
||||||
extract_save_path = gr.Textbox(
|
extract_save_path = gr.Textbox(
|
||||||
label="Destination Directory",
|
label="Destination Directory",
|
||||||
value="",
|
value="",
|
||||||
@@ -362,7 +366,7 @@ def tools_ui() -> None:
|
|||||||
label="Batch result",
|
label="Batch result",
|
||||||
show_label=False,
|
show_label=False,
|
||||||
elem_id="faceswaplab_batch_results",
|
elem_id="faceswaplab_batch_results",
|
||||||
).style(columns=[2], rows=[2])
|
)
|
||||||
batch_save_path = gr.Textbox(
|
batch_save_path = gr.Textbox(
|
||||||
label="Destination Directory",
|
label="Destination Directory",
|
||||||
value="outputs/faceswap/",
|
value="outputs/faceswap/",
|
||||||
@@ -372,7 +376,7 @@ def tools_ui() -> None:
|
|||||||
"Process & Save", elem_id="faceswaplab_extract_btn"
|
"Process & Save", elem_id="faceswaplab_extract_btn"
|
||||||
)
|
)
|
||||||
unit_components = []
|
unit_components = []
|
||||||
for i in range(1, opts.data.get("faceswaplab_units_count", 3) + 1):
|
for i in range(1, get_sd_option("faceswaplab_units_count", 3) + 1):
|
||||||
unit_components += faceswap_unit_ui(False, i, id_prefix="faceswaplab_tab")
|
unit_components += faceswap_unit_ui(False, i, id_prefix="faceswaplab_tab")
|
||||||
|
|
||||||
upscale_options = postprocessing_ui()
|
upscale_options = postprocessing_ui()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from PIL import Image
|
|||||||
from scripts.faceswaplab_swapping.upcaled_inswapper_options import InswappperOptions
|
from scripts.faceswaplab_swapping.upcaled_inswapper_options import InswappperOptions
|
||||||
from scripts.faceswaplab_utils.imgutils import pil_to_cv2
|
from scripts.faceswaplab_utils.imgutils import pil_to_cv2
|
||||||
from scripts.faceswaplab_utils.faceswaplab_logging import logger
|
from scripts.faceswaplab_utils.faceswaplab_logging import logger
|
||||||
from scripts.faceswaplab_utils import face_checkpoints_utils
|
from scripts.faceswaplab_swapping import face_checkpoints
|
||||||
from scripts.faceswaplab_inpainting.faceswaplab_inpainting import InpaintingOptions
|
from scripts.faceswaplab_inpainting.faceswaplab_inpainting import InpaintingOptions
|
||||||
from client_api import api_utils
|
from client_api import api_utils
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ class FaceSwapUnitSettings:
|
|||||||
if self.source_face and self.source_face != "None":
|
if self.source_face and self.source_face != "None":
|
||||||
try:
|
try:
|
||||||
logger.info(f"loading face {self.source_face}")
|
logger.info(f"loading face {self.source_face}")
|
||||||
face = face_checkpoints_utils.load_face(self.source_face)
|
face = face_checkpoints.load_face(self.source_face)
|
||||||
self._reference_face = face
|
self._reference_face = face
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Failed to load checkpoint : %s", e)
|
logger.error("Failed to load checkpoint : %s", e)
|
||||||
@@ -169,7 +169,7 @@ class FaceSwapUnitSettings:
|
|||||||
if isinstance(file, Image.Image):
|
if isinstance(file, Image.Image):
|
||||||
img = file
|
img = file
|
||||||
else:
|
else:
|
||||||
img = Image.open(file.name)
|
img = Image.open(file.name) # type: ignore
|
||||||
|
|
||||||
face = swapper.get_or_default(
|
face = swapper.get_or_default(
|
||||||
swapper.get_faces(pil_to_cv2(img)), 0, None
|
swapper.get_faces(pil_to_cv2(img)), 0, None
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
from scripts.faceswaplab_ui.faceswaplab_inpainting_ui import face_inpainting_ui
|
from scripts.faceswaplab_ui.faceswaplab_inpainting_ui import face_inpainting_ui
|
||||||
from scripts.faceswaplab_utils.face_checkpoints_utils import get_face_checkpoints
|
from scripts.faceswaplab_swapping.face_checkpoints import get_face_checkpoints
|
||||||
import gradio as gr
|
import gradio as gr
|
||||||
from modules.shared import opts
|
|
||||||
from modules import shared
|
from modules import shared
|
||||||
|
from scripts.faceswaplab_utils.sd_utils import get_sd_option
|
||||||
|
|
||||||
|
|
||||||
def faceswap_unit_advanced_options(
|
def faceswap_unit_advanced_options(
|
||||||
is_img2img: bool, unit_num: int = 1, id_prefix: str = "faceswaplab_"
|
is_img2img: bool, unit_num: int = 1, id_prefix: str = "faceswaplab_"
|
||||||
) -> List[gr.components.Component]:
|
) -> List[gr.components.Component]:
|
||||||
with gr.Accordion(f"Post-Processing & Advanced Mask Options", open=False):
|
with gr.Accordion(f"Post-Processing & Advanced Mask Options", open=False):
|
||||||
gr.Markdown("""Post-processing and mask settings for unit faces""")
|
gr.Markdown(
|
||||||
|
"""Post-processing and mask settings for unit faces. Best result : checks all, use LDSR, use Codeformer"""
|
||||||
|
)
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
face_restorer_name = gr.Radio(
|
face_restorer_name = gr.Radio(
|
||||||
label="Restore Face",
|
label="Restore Face",
|
||||||
choices=["None"] + [x.name() for x in shared.face_restorers],
|
choices=["None"] + [x.name() for x in shared.face_restorers],
|
||||||
value=lambda: opts.data.get(
|
value=get_sd_option(
|
||||||
"faceswaplab_default_upscaled_swapper_face_restorer",
|
"faceswaplab_default_upscaled_swapper_face_restorer",
|
||||||
"None",
|
"None",
|
||||||
),
|
),
|
||||||
@@ -26,7 +28,7 @@ def faceswap_unit_advanced_options(
|
|||||||
face_restorer_visibility = gr.Slider(
|
face_restorer_visibility = gr.Slider(
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
value=lambda: opts.data.get(
|
value=get_sd_option(
|
||||||
"faceswaplab_default_upscaled_swapper_face_restorer_visibility",
|
"faceswaplab_default_upscaled_swapper_face_restorer_visibility",
|
||||||
1.0,
|
1.0,
|
||||||
),
|
),
|
||||||
@@ -37,7 +39,7 @@ def faceswap_unit_advanced_options(
|
|||||||
codeformer_weight = gr.Slider(
|
codeformer_weight = gr.Slider(
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
value=lambda: opts.data.get(
|
value=get_sd_option(
|
||||||
"faceswaplab_default_upscaled_swapper_face_restorer_weight", 1.0
|
"faceswaplab_default_upscaled_swapper_face_restorer_weight", 1.0
|
||||||
),
|
),
|
||||||
step=0.001,
|
step=0.001,
|
||||||
@@ -46,33 +48,25 @@ def faceswap_unit_advanced_options(
|
|||||||
)
|
)
|
||||||
upscaler_name = gr.Dropdown(
|
upscaler_name = gr.Dropdown(
|
||||||
choices=[upscaler.name for upscaler in shared.sd_upscalers],
|
choices=[upscaler.name for upscaler in shared.sd_upscalers],
|
||||||
value=lambda: opts.data.get(
|
value=get_sd_option("faceswaplab_default_upscaled_swapper_upscaler", ""),
|
||||||
"faceswaplab_default_upscaled_swapper_upscaler", ""
|
|
||||||
),
|
|
||||||
label="Upscaler",
|
label="Upscaler",
|
||||||
elem_id=f"{id_prefix}_face{unit_num}_upscaler",
|
elem_id=f"{id_prefix}_face{unit_num}_upscaler",
|
||||||
)
|
)
|
||||||
|
|
||||||
improved_mask = gr.Checkbox(
|
improved_mask = gr.Checkbox(
|
||||||
lambda: opts.data.get(
|
get_sd_option("faceswaplab_default_upscaled_swapper_improved_mask", False),
|
||||||
"faceswaplab_default_upscaled_swapper_improved_mask", False
|
|
||||||
),
|
|
||||||
interactive=True,
|
interactive=True,
|
||||||
label="Use improved segmented mask (use pastenet to mask only the face)",
|
label="Use improved segmented mask (use pastenet to mask only the face)",
|
||||||
elem_id=f"{id_prefix}_face{unit_num}_improved_mask",
|
elem_id=f"{id_prefix}_face{unit_num}_improved_mask",
|
||||||
)
|
)
|
||||||
color_corrections = gr.Checkbox(
|
color_corrections = gr.Checkbox(
|
||||||
lambda: opts.data.get(
|
get_sd_option("faceswaplab_default_upscaled_swapper_fixcolor", False),
|
||||||
"faceswaplab_default_upscaled_swapper_fixcolor", False
|
|
||||||
),
|
|
||||||
interactive=True,
|
interactive=True,
|
||||||
label="Use color corrections",
|
label="Use color corrections",
|
||||||
elem_id=f"{id_prefix}_face{unit_num}_color_corrections",
|
elem_id=f"{id_prefix}_face{unit_num}_color_corrections",
|
||||||
)
|
)
|
||||||
sharpen_face = gr.Checkbox(
|
sharpen_face = gr.Checkbox(
|
||||||
lambda: opts.data.get(
|
get_sd_option("faceswaplab_default_upscaled_swapper_sharpen", False),
|
||||||
"faceswaplab_default_upscaled_swapper_sharpen", False
|
|
||||||
),
|
|
||||||
interactive=True,
|
interactive=True,
|
||||||
label="sharpen face",
|
label="sharpen face",
|
||||||
elem_id=f"{id_prefix}_face{unit_num}_sharpen_face",
|
elem_id=f"{id_prefix}_face{unit_num}_sharpen_face",
|
||||||
@@ -80,13 +74,13 @@ def faceswap_unit_advanced_options(
|
|||||||
erosion_factor = gr.Slider(
|
erosion_factor = gr.Slider(
|
||||||
0.0,
|
0.0,
|
||||||
10.0,
|
10.0,
|
||||||
lambda: opts.data.get("faceswaplab_default_upscaled_swapper_erosion", 1.0),
|
get_sd_option("faceswaplab_default_upscaled_swapper_erosion", 1.0),
|
||||||
step=0.01,
|
step=0.01,
|
||||||
label="Upscaled swapper mask erosion factor, 1 = default behaviour.",
|
label="Upscaled swapper mask erosion factor, 1 = default behaviour.",
|
||||||
elem_id=f"{id_prefix}_face{unit_num}_erosion_factor",
|
elem_id=f"{id_prefix}_face{unit_num}_erosion_factor",
|
||||||
)
|
)
|
||||||
|
|
||||||
return [
|
components = [
|
||||||
face_restorer_name,
|
face_restorer_name,
|
||||||
face_restorer_visibility,
|
face_restorer_visibility,
|
||||||
codeformer_weight,
|
codeformer_weight,
|
||||||
@@ -97,6 +91,11 @@ def faceswap_unit_advanced_options(
|
|||||||
erosion_factor,
|
erosion_factor,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
for component in components:
|
||||||
|
setattr(component, "do_not_save_to_config", True)
|
||||||
|
|
||||||
|
return components
|
||||||
|
|
||||||
|
|
||||||
def faceswap_unit_ui(
|
def faceswap_unit_ui(
|
||||||
is_img2img: bool, unit_num: int = 1, id_prefix: str = "faceswaplab"
|
is_img2img: bool, unit_num: int = 1, id_prefix: str = "faceswaplab"
|
||||||
@@ -135,7 +134,7 @@ def faceswap_unit_ui(
|
|||||||
elem_id=f"{id_prefix}_face{unit_num}_refresh_checkpoints",
|
elem_id=f"{id_prefix}_face{unit_num}_refresh_checkpoints",
|
||||||
)
|
)
|
||||||
|
|
||||||
def refresh_fn(selected: str) -> None:
|
def refresh_fn(selected: str):
|
||||||
return gr.Dropdown.update(
|
return gr.Dropdown.update(
|
||||||
value=selected, choices=get_face_checkpoints()
|
value=selected, choices=get_face_checkpoints()
|
||||||
)
|
)
|
||||||
@@ -209,6 +208,16 @@ def faceswap_unit_ui(
|
|||||||
elem_id=f"{id_prefix}_face{unit_num}_swap_in_generated",
|
elem_id=f"{id_prefix}_face{unit_num}_swap_in_generated",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
gr.Markdown(
|
||||||
|
"""
|
||||||
|
## Advanced Options
|
||||||
|
|
||||||
|
**Simple :** If you have bad results and don't want to fine-tune here, just enable Codeformer in "Global Post-Processing".
|
||||||
|
Otherwise, read the [doc](https://glucauze.github.io/sd-webui-faceswaplab/doc/) to understand following options.
|
||||||
|
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
with gr.Accordion("Similarity", open=False):
|
with gr.Accordion("Similarity", open=False):
|
||||||
gr.Markdown("""Discard images with low similarity or no faces :""")
|
gr.Markdown("""Discard images with low similarity or no faces :""")
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
@@ -240,19 +249,19 @@ def faceswap_unit_ui(
|
|||||||
elem_id=f"{id_prefix}_face{unit_num}_min_ref_similarity",
|
elem_id=f"{id_prefix}_face{unit_num}_min_ref_similarity",
|
||||||
)
|
)
|
||||||
|
|
||||||
pre_inpainting = face_inpainting_ui(
|
with gr.Accordion(label="Pre-Inpainting (before swapping)", open=False):
|
||||||
name="Pre-Inpainting (Before swapping)",
|
gr.Markdown("Pre-inpainting sends face to inpainting before swapping")
|
||||||
id_prefix=f"{id_prefix}_face{unit_num}_preinpainting",
|
pre_inpainting = face_inpainting_ui(
|
||||||
description="Pre-inpainting sends face to inpainting before swapping",
|
id_prefix=f"{id_prefix}_face{unit_num}_preinpainting",
|
||||||
)
|
)
|
||||||
|
|
||||||
options = faceswap_unit_advanced_options(is_img2img, unit_num, id_prefix)
|
options = faceswap_unit_advanced_options(is_img2img, unit_num, id_prefix)
|
||||||
|
|
||||||
post_inpainting = face_inpainting_ui(
|
with gr.Accordion(label="Post-Inpainting (After swapping)", open=False):
|
||||||
name="Post-Inpainting (After swapping)",
|
gr.Markdown("Pre-inpainting sends face to inpainting before swapping")
|
||||||
id_prefix=f"{id_prefix}_face{unit_num}_postinpainting",
|
post_inpainting = face_inpainting_ui(
|
||||||
description="Post-inpainting sends face to inpainting after swapping",
|
id_prefix=f"{id_prefix}_face{unit_num}_postinpainting",
|
||||||
)
|
)
|
||||||
|
|
||||||
gradio_components: List[gr.components.Component] = (
|
gradio_components: List[gr.components.Component] = (
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ import cv2
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from math import isqrt, ceil
|
from math import isqrt, ceil
|
||||||
import torch
|
import torch
|
||||||
from ifnude import detect
|
|
||||||
from scripts.faceswaplab_globals import NSFW_SCORE_THRESHOLD
|
|
||||||
from modules import processing
|
from modules import processing
|
||||||
import base64
|
import base64
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
from scripts.faceswaplab_utils.sd_utils import get_sd_option
|
||||||
from scripts.faceswaplab_utils.typing import BoxCoords, CV2ImgU8, PILImage
|
from scripts.faceswaplab_utils.typing import BoxCoords, CV2ImgU8, PILImage
|
||||||
from scripts.faceswaplab_utils.faceswaplab_logging import logger
|
from scripts.faceswaplab_utils.faceswaplab_logging import logger
|
||||||
|
|
||||||
@@ -25,16 +24,27 @@ def check_against_nsfw(img: PILImage) -> bool:
|
|||||||
bool: True if any part of the image is considered NSFW, False otherwise.
|
bool: True if any part of the image is considered NSFW, False otherwise.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
NSFW_SCORE_THRESHOLD = get_sd_option("faceswaplab_nsfw_threshold", 0.7)
|
||||||
|
|
||||||
|
# For testing purpose :
|
||||||
|
if NSFW_SCORE_THRESHOLD >= 1:
|
||||||
|
return False
|
||||||
|
|
||||||
|
from ifnude import detect
|
||||||
|
|
||||||
shapes: List[bool] = []
|
shapes: List[bool] = []
|
||||||
chunks: List[Dict[str, Union[int, float]]] = detect(img)
|
chunks: List[Dict[str, Union[int, float]]] = detect(img)
|
||||||
|
|
||||||
for chunk in chunks:
|
for chunk in chunks:
|
||||||
|
logger.debug(
|
||||||
|
f"chunck score {chunk['score']}, threshold : {NSFW_SCORE_THRESHOLD}"
|
||||||
|
)
|
||||||
shapes.append(chunk["score"] > NSFW_SCORE_THRESHOLD)
|
shapes.append(chunk["score"] > NSFW_SCORE_THRESHOLD)
|
||||||
|
|
||||||
return any(shapes)
|
return any(shapes)
|
||||||
|
|
||||||
|
|
||||||
def pil_to_cv2(pil_img: PILImage) -> CV2ImgU8: # type: ignore
|
def pil_to_cv2(pil_img: PILImage) -> CV2ImgU8:
|
||||||
"""
|
"""
|
||||||
Convert a PIL Image into an OpenCV image (cv2).
|
Convert a PIL Image into an OpenCV image (cv2).
|
||||||
|
|
||||||
@@ -44,7 +54,7 @@ def pil_to_cv2(pil_img: PILImage) -> CV2ImgU8: # type: ignore
|
|||||||
Returns:
|
Returns:
|
||||||
CV2ImgU8: The input image converted to OpenCV format (BGR).
|
CV2ImgU8: The input image converted to OpenCV format (BGR).
|
||||||
"""
|
"""
|
||||||
return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
|
return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR).astype("uint8")
|
||||||
|
|
||||||
|
|
||||||
def cv2_to_pil(cv2_img: CV2ImgU8) -> PILImage: # type: ignore
|
def cv2_to_pil(cv2_img: CV2ImgU8) -> PILImage: # type: ignore
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
from types import ModuleType
|
||||||
|
|
||||||
|
|
||||||
|
def check_install() -> None:
|
||||||
|
# Very ugly hack :( due to sdnext optimization not calling install.py every time if git log has not changed
|
||||||
|
import importlib.util
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
current_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
check_install_path = os.path.join(current_dir, "..", "..", "install.py")
|
||||||
|
spec = importlib.util.spec_from_file_location("check_install", check_install_path)
|
||||||
|
if spec != None:
|
||||||
|
check_install: ModuleType = importlib.util.module_from_spec(spec)
|
||||||
|
sys.modules["check_install"] = check_install
|
||||||
|
spec.loader.exec_module(check_install) # type: ignore
|
||||||
|
check_install.check_install() # type: ignore
|
||||||
|
#### End of ugly hack :( !
|
||||||
@@ -3,12 +3,47 @@ import os
|
|||||||
from typing import List
|
from typing import List
|
||||||
import modules.scripts as scripts
|
import modules.scripts as scripts
|
||||||
from modules import scripts
|
from modules import scripts
|
||||||
from scripts.faceswaplab_globals import EXTENSION_PATH
|
from scripts.faceswaplab_globals import EXPECTED_INSWAPPER_SHA1, EXTENSION_PATH
|
||||||
from modules.shared import opts
|
from modules.shared import opts
|
||||||
from scripts.faceswaplab_utils.faceswaplab_logging import logger
|
from scripts.faceswaplab_utils.faceswaplab_logging import logger
|
||||||
|
import traceback
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
|
||||||
def get_models() -> List[str]:
|
def is_sha1_matching(file_path: str, expected_sha1: str) -> bool:
|
||||||
|
sha1_hash = hashlib.sha1(usedforsecurity=False)
|
||||||
|
try:
|
||||||
|
with open(file_path, "rb") as file:
|
||||||
|
for byte_block in iter(lambda: file.read(4096), b""):
|
||||||
|
sha1_hash.update(byte_block)
|
||||||
|
if sha1_hash.hexdigest() == expected_sha1:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
"Failed to check model hash, check the model is valid or has been downloaded adequately : %e",
|
||||||
|
e,
|
||||||
|
)
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def check_model() -> bool:
|
||||||
|
model_path = get_current_swap_model()
|
||||||
|
if not is_sha1_matching(
|
||||||
|
file_path=model_path, expected_sha1=EXPECTED_INSWAPPER_SHA1
|
||||||
|
):
|
||||||
|
logger.error(
|
||||||
|
"Suspicious sha1 for model %s, check the model is valid or has been downloaded adequately. Should be %s",
|
||||||
|
model_path,
|
||||||
|
EXPECTED_INSWAPPER_SHA1,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_swap_models() -> List[str]:
|
||||||
"""
|
"""
|
||||||
Retrieve a list of swap model files.
|
Retrieve a list of swap model files.
|
||||||
|
|
||||||
@@ -31,15 +66,16 @@ def get_models() -> List[str]:
|
|||||||
return models
|
return models
|
||||||
|
|
||||||
|
|
||||||
def get_current_model() -> str:
|
def get_current_swap_model() -> str:
|
||||||
model = opts.data.get("faceswaplab_model", None)
|
model = opts.data.get("faceswaplab_model", None) # type: ignore
|
||||||
if model is None:
|
if model is None:
|
||||||
models = get_models()
|
models = get_swap_models()
|
||||||
model = models[0] if len(models) else None
|
model = models[0] if len(models) else None
|
||||||
logger.info("Try to use model : %s", model)
|
logger.info("Try to use model : %s", model)
|
||||||
if not os.path.isfile(model):
|
if not os.path.isfile(model): # type: ignore
|
||||||
logger.error("The model %s cannot be found or loaded", model)
|
logger.error("The model %s cannot be found or loaded", model)
|
||||||
raise FileNotFoundError(
|
raise FileNotFoundError(
|
||||||
"No faceswap model found. Please add it to the faceswaplab directory."
|
"No faceswap model found. Please add it to the faceswaplab directory."
|
||||||
)
|
)
|
||||||
|
assert model is not None
|
||||||
return model
|
return model
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
from typing import Any
|
||||||
|
from modules.shared import opts
|
||||||
|
|
||||||
|
|
||||||
|
def get_sd_option(name: str, default: Any) -> Any:
|
||||||
|
assert opts.data is not None
|
||||||
|
return opts.data.get(name, default)
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from numpy import uint8
|
from numpy import uint8
|
||||||
from numpy.typing import NDArray
|
|
||||||
from insightface.app.common import Face as IFace
|
from insightface.app.common import Face as IFace
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
PILImage = Image.Image
|
PILImage = Image.Image
|
||||||
CV2ImgU8 = NDArray[uint8]
|
CV2ImgU8 = np.ndarray[int, np.dtype[uint8]]
|
||||||
Face = IFace
|
Face = IFace
|
||||||
BoxCoords = Tuple[int, int, int, int]
|
BoxCoords = Tuple[int, int, int, int]
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ def dataclass_from_flat_list(cls: type, values: Tuple[Any, ...]) -> Any:
|
|||||||
init_values[field.name] = field.type(*inner_values)
|
init_values[field.name] = field.type(*inner_values)
|
||||||
idx += len(inner_values)
|
idx += len(inner_values)
|
||||||
else:
|
else:
|
||||||
|
if idx >= len(values):
|
||||||
|
raise IndexError(
|
||||||
|
f"Expected more values for dataclass {cls}. Current index: {idx}, values length: {len(values)}"
|
||||||
|
)
|
||||||
value = values[idx]
|
value = values[idx]
|
||||||
init_values[field.name] = value
|
init_values[field.name] = value
|
||||||
idx += 1
|
idx += 1
|
||||||
|
|||||||
Reference in New Issue
Block a user