Skip to content

Commit

Permalink
Merge pull request #19 from glucauze/v.1.2.0
Browse files Browse the repository at this point in the history
V.1.2.0. Please reads documentation and changelog
  • Loading branch information
glucauze committed Aug 3, 2023
2 parents 1f3bffc + 26ac286 commit 7282abf
Show file tree
Hide file tree
Showing 37 changed files with 1,817 additions and 814 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
# 1.2.0 :

This version changes quite a few things.

+ The upscaled inswapper options are now moved to each face unit. This makes it possible to fine-tune the settings for each face.

+ Upscaled inswapper configuration in sd now concerns default values in each unit's interface.

+ Pre- and post-inpainting is now possible for each face. Here too, default options are set in the main sd settings.

+ Codeformer is no longer the default in post-processing. Don't be surprised if you get bad results by default. You can set it to default in the application's global settings

Bug fixes :

+ The problem of saving the grid should be solved.
+ The downscaling problem for inpainting should be solved.
+ Change model download logic and add checksum. This should prevent some bugs.

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.

# 1.1.2 :

+ Switch face checkpoint format from pkl to safetensors
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ While FaceSwapLab is still under development, it has reached a good level of sta

In short:

+ **Ethical Guideline:** This extension should not be forked to create a public, easy way to circumvent NSFW filtering.
+ **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.
+ **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.

More on this here : https://glucauze.github.io/sd-webui-faceswaplab/

### Known problems (wontfix):

+ Older versions of gradio don't work well with the extension. See this bug : https://github.com/glucauze/sd-webui-faceswaplab/issues/5

### 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).
Expand Down
121 changes: 92 additions & 29 deletions client_api/api_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import List, Tuple, Optional
import numpy as np
import requests
import safetensors


class InpaintingWhen(Enum):
Expand All @@ -18,6 +19,54 @@ class InpaintingWhen(Enum):
AFTER_ALL = "After All"


class InpaintingOptions(BaseModel):
inpainting_denoising_strengh: float = Field(
description="Inpainting denoising strenght", default=0, lt=1, ge=0
)
inpainting_prompt: str = Field(
description="Inpainting denoising strenght",
examples=["Portrait of a [gender]"],
default="Portrait of a [gender]",
)
inpainting_negative_prompt: str = Field(
description="Inpainting denoising strenght",
examples=[
"Deformed, blurry, bad anatomy, disfigured, poorly drawn face, mutation"
],
default="",
)
inpainting_steps: int = Field(
description="Inpainting steps",
examples=["Portrait of a [gender]"],
ge=1,
le=150,
default=20,
)
inpainting_sampler: str = Field(
description="Inpainting sampler", examples=["Euler"], default="Euler"
)
inpainting_model: str = Field(
description="Inpainting model", examples=["Current"], default="Current"
)


class InswappperOptions(BaseModel):
face_restorer_name: str = Field(
description="face restorer name", default="CodeFormer"
)
restorer_visibility: float = Field(
description="face restorer visibility", default=1, le=1, ge=0
)
codeformer_weight: float = Field(
description="face restorer codeformer weight", default=1, le=1, ge=0
)
upscaler_name: str = Field(description="upscaler name", default=None)
improved_mask: bool = Field(description="Use Improved Mask", default=False)
color_corrections: bool = Field(description="Use Color Correction", default=False)
sharpen: bool = Field(description="Sharpen Image", default=False)
erosion_factor: float = Field(description="Erosion Factor", default=1, le=10, ge=0)


class FaceSwapUnit(BaseModel):
# The image given in reference
source_img: str = Field(
Expand Down Expand Up @@ -82,6 +131,21 @@ class FaceSwapUnit(BaseModel):
default=0,
)

pre_inpainting: Optional[InpaintingOptions] = Field(
description="Inpainting options",
default=None,
)

swapping_options: Optional[InswappperOptions] = Field(
description="PostProcessing & Mask options",
default=None,
)

post_inpainting: Optional[InpaintingOptions] = Field(
description="Inpainting options",
default=None,
)

def get_batch_images(self) -> List[Image.Image]:
images = []
if self.batch_images:
Expand All @@ -104,39 +168,15 @@ class PostProcessingOptions(BaseModel):
upscaler_visibility: float = Field(
description="upscaler visibility", default=1, le=1, ge=0
)

inpainting_denoising_strengh: float = Field(
description="Inpainting denoising strenght", default=0, lt=1, ge=0
)
inpainting_prompt: str = Field(
description="Inpainting denoising strenght",
examples=["Portrait of a [gender]"],
default="Portrait of a [gender]",
)
inpainting_negative_prompt: str = Field(
description="Inpainting denoising strenght",
examples=[
"Deformed, blurry, bad anatomy, disfigured, poorly drawn face, mutation"
],
default="",
)
inpainting_steps: int = Field(
description="Inpainting steps",
examples=["Portrait of a [gender]"],
ge=1,
le=150,
default=20,
)
inpainting_sampler: str = Field(
description="Inpainting sampler", examples=["Euler"], default="Euler"
)
inpainting_when: InpaintingWhen = Field(
description="When inpainting happens",
examples=[e.value for e in InpaintingWhen.__members__.values()],
default=InpaintingWhen.NEVER,
)
inpainting_model: str = Field(
description="Inpainting model", examples=["Current"], default="Current"

inpainting_options: Optional[InpaintingOptions] = Field(
description="Inpainting options",
default=None,
)


Expand All @@ -147,7 +187,7 @@ class FaceSwapRequest(BaseModel):
default=None,
)
units: List[FaceSwapUnit]
postprocessing: Optional[PostProcessingOptions]
postprocessing: Optional[PostProcessingOptions] = None


class FaceSwapResponse(BaseModel):
Expand Down Expand Up @@ -227,3 +267,26 @@ def compare_faces(
)

return float(result.text)


def safetensors_to_base64(file_path: str) -> str:
with open(file_path, "rb") as file:
file_bytes = file.read()
return "data:application/face;base64," + base64.b64encode(file_bytes).decode(
"utf-8"
)


def base64_to_safetensors(base64str: str, output_path: str) -> None:
try:
base64_data = base64str.split("base64,")[-1]
file_bytes = base64.b64decode(base64_data)
with open(output_path, "wb") as file:
file.write(file_bytes)
with safetensors.safe_open(output_path, framework="pt") as f:
print(output_path, "keys =", f.keys())
except Exception as e:
print("Error : failed to convert base64 string to safetensor", e)
import traceback

traceback.print_exc()
70 changes: 64 additions & 6 deletions client_api/faceswaplab_api_example.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
from typing import List
import requests
from api_utils import (
FaceSwapRequest,
FaceSwapUnit,
PostProcessingOptions,
FaceSwapResponse,
InswappperOptions,
base64_to_safetensors,
pil_to_base64,
PostProcessingOptions,
InpaintingWhen,
FaceSwapCompareRequest,
InpaintingOptions,
FaceSwapRequest,
FaceSwapResponse,
FaceSwapExtractRequest,
FaceSwapCompareRequest,
FaceSwapExtractResponse,
safetensors_to_base64,
)

address = "http://127.0.0.1:7860"

# This has been tested on Linux platforms. This might requires some minor adaptations for windows.


#############################
# FaceSwap
Expand All @@ -37,9 +44,11 @@
restorer_visibility=1,
upscaler_name="Lanczos",
scale=4,
inpainting_steps=30,
inpainting_denoising_strengh=0.1,
inpainting_when=InpaintingWhen.BEFORE_RESTORE_FACE,
inpainting_options=InpaintingOptions(
inpainting_steps=30,
inpainting_denoising_strengh=0.1,
),
)

# Prepare the request
Expand Down Expand Up @@ -91,3 +100,52 @@

for img in response.pil_images:
img.show()


#############################
# Build checkpoint

source_images: List[str] = [
pil_to_base64("../references/man.png"),
pil_to_base64("../references/woman.png"),
]

result = requests.post(
url=f"{address}/faceswaplab/build",
json=source_images,
headers={"Content-Type": "application/json; charset=utf-8"},
)

base64_to_safetensors(result.json(), output_path="test.safetensors")

#############################
# FaceSwap with local safetensors

# First face unit :
unit1 = FaceSwapUnit(
source_face=safetensors_to_base64(
"test.safetensors"
), # convert the checkpoint to base64
faces_index=(0,), # Replace first face
swapping_options=InswappperOptions(
face_restorer_name="CodeFormer",
upscaler_name="LDSR",
improved_mask=True,
sharpen=True,
color_corrections=True,
),
)

# Prepare the request
request = FaceSwapRequest(image=pil_to_base64("test_image.png"), units=[unit1])

# Face Swap
result = requests.post(
url=f"{address}/faceswaplab/swap_face",
data=request.json(),
headers={"Content-Type": "application/json; charset=utf-8"},
)
response = FaceSwapResponse.parse_obj(result.json())

for img in response.pil_images:
img.show()
5 changes: 5 additions & 0 deletions client_api/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
numpy==1.25.1
Pillow==10.0.0
pydantic==1.10.9
Requests==2.31.0
safetensors==0.3.1
Binary file added client_api/test.safetensors
Binary file not shown.
Binary file modified docs/assets/images/doc_mi.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 7282abf

Please sign in to comment.