diff --git a/images/choose_model.png b/images/choose_model.png
new file mode 100644
index 0000000..7341725
Binary files /dev/null and b/images/choose_model.png differ
diff --git a/images/compare.png b/images/compare.png
new file mode 100644
index 0000000..a620d24
Binary files /dev/null and b/images/compare.png differ
diff --git a/images/fix_seam.png b/images/fix_seam.png
new file mode 100644
index 0000000..166fc55
Binary files /dev/null and b/images/fix_seam.png differ
diff --git a/images/preview_face.png b/images/preview_face.png
new file mode 100644
index 0000000..fb2aa0f
Binary files /dev/null and b/images/preview_face.png differ
diff --git a/images/prompt.png b/images/prompt.png
new file mode 100644
index 0000000..f7ab26f
Binary files /dev/null and b/images/prompt.png differ
diff --git a/images/seamless_face.png b/images/seamless_face.png
new file mode 100644
index 0000000..74dbf3c
Binary files /dev/null and b/images/seamless_face.png differ
diff --git a/images/upload_face.png b/images/upload_face.png
new file mode 100644
index 0000000..3078319
Binary files /dev/null and b/images/upload_face.png differ
diff --git a/nodes.py b/nodes.py
index bb256a1..b289257 100644
--- a/nodes.py
+++ b/nodes.py
@@ -31,7 +31,7 @@ class GenerationParameterInput:
RETURN_NAMES = ("parameters", )
FUNCTION = "mux"
- CATEGORY = "ArtBot2023"
+ CATEGORY = "CFaceSwap"
def mux(self, seed, image_width, image_height, steps, cfg, sampler_name, scheduler, denoise, parameters={}):
parameters["seed"] = seed
@@ -56,7 +56,7 @@ class GenertaionParameterOutput:
RETURN_NAMES = ("parameters", "seed", "image_width", "image_height", "steps", "cfg", "sampler_name", "scheduler", "denoise", )
FUNCTION = "demux"
- CATEGORY = "ArtBot2023"
+ CATEGORY = "CFaceSwap"
def demux(self, parameters):
seed = parameters["seed"]
@@ -79,7 +79,7 @@ class LoadRetinaFace:
RETURN_TYPES = ("RETINAFACE", )
RETURN_NAMES = ("MODEL", )
FUNCTION = "load"
- CATEGORY = "ArtBot2023"
+ CATEGORY = "CFaceSwap"
def load(self):
from facexlib.detection import init_detection_model
return (init_detection_model("retinaface_resnet50", model_rootpath=self.models_dir), )
@@ -102,7 +102,7 @@ class CropFace:
)
RETURN_NAMES = ("face_image", "preview", "bbox")
FUNCTION = "crop"
- CATEGORY = "ArtBot2023"
+ CATEGORY = "CFaceSwap"
def crop(self, model: RetinaFace, image: torch.Tensor, confidence: float, margin: int):
with torch.no_grad():
@@ -214,7 +214,7 @@ class UncropFace:
}
RETURN_TYPES = ("IMAGE", )
- CATEGORY = "ArtBot2023"
+ CATEGORY = "CFaceSwap"
FUNCTION = "uncrop"
def uncrop(self, image: torch.Tensor, bbox: BBox, face: torch.Tensor, mask: torch.Tensor):
bbox_face, bbox_mask = self.scale_face(face.squeeze(), mask, bbox[2])
@@ -265,7 +265,7 @@ class LoadBisenet:
RETURN_TYPES = ("BISENET", )
FUNCTION = "load"
- CATEGORY = "ArtBot2023"
+ CATEGORY = "CFaceSwap"
def load(self):
from facexlib.parsing import init_parsing_model
return (init_parsing_model("bisenet", model_rootpath=self.models_dir), )
@@ -288,7 +288,7 @@ class SegFace:
)
RETURN_NAMES = ("image", "mask")
FUNCTION = "segment"
- CATEGORY = "ArtBot2023"
+ CATEGORY = "CFaceSwap"
# labels: 0 'background'
# 1 'skin', 2 'l_brow', 3 'r_brow', 4 'l_eye', 5 'r_eye',
@@ -340,7 +340,7 @@ class ImageFullBBox:
RETURN_TYPES = ("BBOX", )
FUNCTION = "bbox"
- CATEGORY = "ArtBot2023"
+ CATEGORY = "CFaceSwap"
def bbox(self, image: torch.Tensor):
image = image.squeeze()
return ((0,0,image.shape[1],image.shape[0]), )
@@ -358,7 +358,7 @@ class ColorBlend:
RETURN_TYPES = ("IMAGE", )
FUNCTION = "blend"
- CATEGORY = "ArtBot2023"
+ CATEGORY = "CFaceSwap"
def blend(self, blend_image: torch.Tensor, base_image: torch.Tensor, mode: Literal["Hue", "Saturation", "Color", "Luminosity"]):
from .blend import color_blend
return (cv2tensor(color_blend(base_image=tensor2cv(base_image), blend_image=tensor2cv(blend_image), mode=mode)), )
@@ -377,7 +377,7 @@ class ExcludeFacialFeature:
RETURN_TYPES = ("IMAGE", )
FUNCTION = "exclude"
- CATEGORY = "ArtBot2023"
+ CATEGORY = "CFaceSwap"
annotation_name = ['background',
'skin', 'l_brow', 'r_brow', 'l_eye', 'r_eye',
'eye_g', 'l_ear', 'r_ear', 'ear_r', 'nose',
@@ -422,7 +422,7 @@ class MaskContour:
RETURN_TYPES = ("MASK", )
FUNCTION = "find_contour"
- CATEGORY = "ArtBot2023"
+ CATEGORY = "CFaceSwap"
def find_contour(self, mask: torch.Tensor):
mask_np: np.ndarray = mask.squeeze().cpu().numpy().astype('uint8')
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..163d23c
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,67 @@
+# CharacterFaceSwap
+
+## Overview
+
+Welcome to the Character Face Swap workflow! It is specialized for your target character face swap. Use LoRA and embeddings to enhance character concept in stable diffusion. Whether you're a fan of video games, anime, or photorealistic, swap face with your favorite characters in a realistic and seamless manner.
+
+
+
+## Installation
+
+### ControlNet
+
+ControlNet ip2p model is used for visual conditioning, download [ip2p](https://huggingface.co/lllyasviel/ControlNet-v1-1/blob/main/control_v11e_sd15_ip2p.pth) and put it in `ComfyUI/models/controlnet`
+
+### Character Face Swap
+
+Recommend using [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager).
+
+Install then load workflow
+
+For manual installation,
+
+```bash
+cd ComfyUI/custom_nodes/
+git clone https://github.com/ArtBot2023/CharacterFaceSwap.git
+cd CharacterFaceSwap
+# if you use ported ComfyUI
+../../../python_embeded/python -m pip install -r requirements
+# otherwise
+pip install -r requirements
+```
+
+In your ComfyUI, load workflows in `custom_nodes/CharacterFaceSwap/workflows`.
+
+
+## How It Works
+
+Choose Your Model: Choose Checkpoint and LoRA trained for your character.
+
+
+
+Upload Base Image and Character Face
+
+
+
+Text Prompt: write prompts to describe target face, use LoRA keywords and embeddings.
+
+
+
+Generate Character Face: You can check character face generation in Preview. Download Face with Seam, and Seam Mask.
+
+
+
+Seam Fix Inpainting: Use webui inpainting to fix seam. Check [FAQ](#faq)
+
+
+
+Upload Seamless Face: Upload inpainting result to Seamless Face, and Queue Prompt again. Done!
+
+
+
+
+## FAQ
+
+**Q**: Why not use ComfyUI for inpainting?
+
+**A**: ComfyUI currently have issue about inpainting models, see [issue](https://github.com/comfyanonymous/ComfyUI/issues/1186) for detail. If anyone find a solution, please notify me.
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..cbc00df
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+facexlib >= 0.2.5
diff --git a/tmp.png b/tmp.png
deleted file mode 100644
index 8bff6ba..0000000
Binary files a/tmp.png and /dev/null differ