48 Commits
Author SHA1 Message Date
toto 1d09d46f16 update sim 2023-12-18 16:08:37 +08:00
toto bac4d69fba update sim 2023-12-18 14:40:09 +08:00
toto 0c06d3e3bf update socre 2023-12-18 14:29:24 +08:00
toto 7861f289c5 update sim 2023-12-18 11:58:05 +08:00
toto fd5f4737fe update log 2023-12-17 07:19:19 +08:00
toto fb2a1e3283 update 2023-12-17 07:08:29 +08:00
toto 75cb3303a9 not download base model 2023-12-17 06:54:14 +08:00
toto 2763361b55 update 2023-12-17 06:53:25 +08:00
toto 3f70c7eb6b update insightface; 2023-12-17 00:36:09 +08:00
toto 5beb93db0a update insightface 2023-12-17 00:35:44 +08:00
toto 98d3b8ac13 fix log error 2023-12-16 22:44:29 +08:00
toto c871825137 update pip install 2023-12-16 22:41:11 +08:00
toto 26245524ea update import 2023-12-16 22:27:08 +08:00
toto 2292031e5f add log 2023-12-16 22:25:46 +08:00
toto 017dcee81b update root path 2023-12-16 22:22:56 +08:00
toto 6d6eb679d5 update 2023-12-16 22:13:48 +08:00
toto 2abacd7fde update pip 2023-12-16 21:43:12 +08:00
toto 6a2d356ed7 add some pack 2023-12-16 21:36:21 +08:00
toto 97c93eff3d window 2023-12-16 20:13:00 +08:00
toto 2598a6efe4 update depen 2023-12-16 19:54:39 +08:00
toto dedd32d29e update 2023-12-16 19:29:37 +08:00
toto a839966e9f update 2023-12-16 19:15:10 +08:00
toto c8e3535869 update nodes 2023-12-16 18:54:17 +08:00
tototianhao 359e72cd2e update plugin 2023-12-16 18:46:32 +08:00
tototianhao a11d54294f update depen 2023-12-15 17:52:12 +08:00
TaylorGoulding 6013eb426c Update requirements.txt 2023-12-15 17:45:26 +08:00
TaylorGoulding 0b005db217 Update requirements.txt 2023-12-15 17:45:12 +08:00
TaylorGoulding 658f057ccd Update __init__.py 2023-12-15 17:42:31 +08:00
TaylorGoulding 5b77e1cf3d Update requirements.txt 2023-12-15 17:32:57 +08:00
toto a6dbede699 update image size 2023-12-12 14:42:24 +08:00
toto dc01281d85 update jpg 2023-12-12 14:40:59 +08:00
toto 948b679e70 udpate 2023-12-12 13:19:41 +08:00
toto 6335f3c3d4 update 2023-12-12 13:07:40 +08:00
toto 3744780d0b add prcode 2023-12-12 13:07:08 +08:00
tototianhao 425924bcbb update wechat image 2023-11-12 00:17:11 +08:00
toto d364f2874a update wechat png 2023-11-02 10:54:36 +08:00
toto e75c9a402a update 2023-11-01 14:46:51 +08:00
toto e45b89d111 bugfix makeup transfer error 2023-10-30 20:56:51 +08:00
toto 3c61c01ba1 bugfix face fusion roop model can't detect face 2023-10-25 22:45:56 +08:00
toto a41dfa668c bugfix 2023-10-25 21:45:38 +08:00
toto e54e9ea579 add workflow readme 2023-10-25 21:21:20 +08:00
toto d9ade2e531 add two super node, simplify progress 2023-10-25 21:15:49 +08:00
toto 4abaf4cd92 retain face choose face 2023-10-25 11:50:21 +08:00
toto 60bebf7b02 add more info 2023-10-24 22:05:04 +08:00
toto 4874aa13ae add v1.1 workflow 2023-10-24 21:41:01 +08:00
toto a9e70be153 merge v1.1.0 2023-10-24 21:39:54 +08:00
TaylorGoulding 8eb6c7713b Update README_zh-CN.md 2023-10-24 00:01:39 +08:00
toto e53dff4b45 删除过期节点 2023-10-23 21:36:09 +08:00
17 changed files with 8191 additions and 2966 deletions
+1
View File
@@ -1,2 +1,3 @@
__pycache__
.idea
*.DS_Store
+16 -5
View File
@@ -1,6 +1,6 @@
# [Portrait-Maker](https://github.com/THtianhao/ComfyUI-Portrait-Maker)
This project is an adaptation of [EasyPhoto](https://github.com/aigc-apps/sd-webui-EasyPhoto), which breaks down the process of [EasyPhoto](https://github.com/aigc-apps/sd-webui-EasyPhoto) and will add a series of operations on human portraits in the future.
![](./images/easyphoto.png)
![](./images/easyphoto.jpg)
English | [简体中文](./README_zh-CN.md)
@@ -11,13 +11,21 @@ If you have any questions or suggestions, you can reach us through:
- Email: tototianhao@gmail.com
- telegram: https://t.me/+JoFE2vqHU4phZjg1
- QQ Group: 10419777
- WeChat Group: <img src="./images/wechat.jpg" width="200">
- WeChat Group: <img src="./images/wechat.jpg" width="300">
## V1.2.0 Update
1. Add PM_SuperColorTransfer node to simplify the color transfer process
2. Add PM_SuperMakeUpTransfer node to simplify the process of makeup transfer
3. Add v1.2.0 workflow
## V1.1.0 Update
1. faceskin adds blur option
2. Add PM_FaceShapMatch node. See node introduction for details.
3. Add PM_MakeUpTransfer node. See node introduction for details.
3. Add a super-resolution model to the PM_PortraitEnhancement node. This super-resolution model can not highlight faces.
2. Add PM_FaceShapMatch node. same as easyphot faceshap match
3. Add PM_MakeUpTransfer node. same as easyphoto makeup transfer.
4. Add a super-resolution model to the PM_PortraitEnhancement node. This super-resolution model can not highlight faces.
5. Add v1.1.0 workflow
6. RetinaFace supports face selection
## V1.0.0 Update
1. Added log for model downloads.
@@ -67,6 +75,7 @@ Click "Load" in the right panel of ComfyUI and select the ./workflow/easyphoto_w
* RetainFace PM: Perform matting using models from Model Scope. [Link](https://www.modelscope.cn/models/damo/cv_resnet50_face-detection_retinaface/summary)
* image: Input image
* multi_user_facecrop_ratio: Multiplicative factor for extracting the head region.
* face_index : Choose which face
* FaceFusion PM: Merge faces from two images.
* image: Input image
@@ -123,6 +132,8 @@ Click "Load" in the right panel of ComfyUI and select the ./workflow/easyphoto_w
* Face Shape Match PM: Apply a certain level of fusion between the diffused image and the original image to reduce differences around the face.
* Makeup Transfer PM: Use a GAN network model to perform makeup transfer.
* SuperMakeUpTransfer PM:(Multi-node integration) makeup by merging two pictures
* SuperColorTransfer PM:(Multi-node integration) transfer the colors of two pictures
## Contribution
If you find any issues or have suggestions for improvement, feel free to contribute. Follow these steps:
+15 -5
View File
@@ -2,7 +2,7 @@
这个项目改编于[EasyPhoto](https://github.com/aigc-apps/sd-webui-EasyPhoto),对于[EasyPhoto](https://github.com/aigc-apps/sd-webui-EasyPhoto)进行了流程上的拆解,后续会加入其他项目处理人物头像上的系列操作。
![](./images/easyphoto.png)
![](./images/easyphoto.jpg)
English | [简体中文](./README_zh-CN.md)
@@ -13,13 +13,20 @@ English | [简体中文](./README_zh-CN.md)
- 电子邮件:tototianhao@gmail.com
- telegram: https://t.me/+JoFE2vqHU4phZjg1
- QQ 群:10419777
- 微信群: <img src="./images/wechat.jpg" width="200">
- 微信群: <img src="./images/wechat.jpg" width="300">
## V1.2.0 Update
1. 增加PM_SuperColorTransfer 节点,简化了颜色迁移的流程
2. 增加PM_SuperMakeUpTransfer 节点,简化了进行装扮迁移的流程
3. 增加v1.2.0 workflow
## v1.1.0 更新
1. faceskin 增加模糊选项
2. 增加 PM_FaceShapMatch节点 详情查看节点介绍
3. 增加 PM_MakeUpTransfer节点 详情查看节点介绍
3. PM_PortraitEnhancement节点增加一种超分模型,此超分模型可以对人脸不做高光
2. 增加 PM_FaceShapMatch节点 与easyphoto的FaceshapMatch一致
3. 增加 PM_MakeUpTransfer节点 与easyphoto的MakeupTransfer一致
4. PM_PortraitEnhancement节点增加一种超分模型,此超分模型可以对人脸不做高光
5. 增加v1.1.0 workflow
6. RetinaFace 支持选择人脸
## v1.0.0 更新
@@ -70,6 +77,7 @@ Easyphoto工作位置: [./workflow/easyphoto.json](./workflows/easyphoto.json )
* RetainFace PM:使用Model Scope中的模型进行抠图 [链接](https://www.modelscope.cn/models/damo/cv_resnet50_face-detection_retinaface/summary)
* image:输入图像
* multi_user_facecrop_ratio:提取头像区域的倍数
* face_index : 选择第几个人脸
* FaceFusion PM:将两张图像的人脸进行融合
* image:输入图像
* user_image:要融合的头像
@@ -110,6 +118,8 @@ Easyphoto工作位置: [./workflow/easyphoto.json](./workflows/easyphoto.json )
* GetImageInfo PM: 提取图片的宽高
* FaceShapMatchPM: 扩散后的图片和原图片进行一定的融合,减少脸旁边的差异
* MakeUpTransferPM: 使用gan网络模型对妆容进行一定的迁移
* SuperMakeUpTransferPM:(多节点的整合)融合两张图片的装扮
* SuperColorTransferPM:(多节点的整合)迁移两张图片的颜色
## 贡献
+10 -30
View File
@@ -1,12 +1,9 @@
import sys
import os
import os, sys
main_path = os.path.dirname(__file__)
sys.path.append(main_path)
import subprocess
import threading
import portrait.install
import requests
from tqdm import tqdm
from portrait.nodes import *
@@ -14,30 +11,6 @@ from portrait.nodes import *
# import pydevd_pycharm
# pydevd_pycharm.settrace('49.7.62.197', port=10090, stdoutToServer=True, stderrToServer=True)
def handle_stream(stream, prefix):
for line in stream:
print(prefix, line, end="")
def run_script(cmd, cwd='.'):
process = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1)
stdout_thread = threading.Thread(target=handle_stream, args=(process.stdout, ""))
stderr_thread = threading.Thread(target=handle_stream, args=(process.stderr, "[!]"))
stdout_thread.start()
stderr_thread.start()
stdout_thread.join()
stderr_thread.join()
return process.wait()
print("## installing dependencies")
requirements_path = os.path.join(main_path, "requirements.txt")
run_script([sys.executable, '-s', '-m', 'pip', 'install', '-q', '-r', requirements_path])
def urldownload_progressbar(url, file_path):
response = requests.get(url, stream=True)
total_size = int(response.headers.get('content-length', 0))
@@ -77,6 +50,9 @@ NODE_CLASS_MAPPINGS = {
"PM_GetImageInfo": GetImageInfoPM,
"PM_MakeUpTransfer": MakeUpTransferPM,
"PM_FaceShapMatch": FaceShapMatchPM,
"PM_SuperColorTransfer": SuperColorTransferPM,
"PM_SuperMakeUpTransfer": SuperMakeUpTransferPM,
"PM_Similarity": SimilarityPM,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"PM_RetinaFace": "RetinaFace PM",
@@ -95,7 +71,11 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"PM_ImageResizeTarget": "ImageResizeTarget PM",
"PM_GetImageInfo": "GetImageInfo PM",
"PM_MakeUpTransfer": "MakeUpTransfer PM",
"PM_FaceShapMatch":"FaceShapMatch PM"
"PM_FaceShapMatch": "FaceShapMatch PM",
"PM_SuperColorTransfer": "SuperColorTransfer PM",
"PM_SuperMakeUpTransfer": "SuperMakeUpTransfer PM",
"PM_Similarity": "Similarity PM",
}
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 754 KiB

Regular → Executable
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 679 KiB

Binary file not shown.
+2 -2
View File
@@ -6,7 +6,7 @@ utils_path = os.path.join(root_path, "utils")
models_path = os.path.join(root_path, "models")
# save_dirs
urls = [
"https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/webui/ChilloutMix-ni-fp16.safetensors",
# "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/webui/ChilloutMix-ni-fp16.safetensors",
"https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/webui/control_v11p_sd15_openpose.pth",
"https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/webui/control_v11p_sd15_canny.pth",
"https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/webui/control_v11f1e_sd15_tile.pth",
@@ -23,7 +23,7 @@ urls = [
]
filenames = [
os.path.join(folder_names_and_paths['checkpoints'][0][0], "Chilloutmix-Ni-pruned-fp16-fix.safetensors"),
# os.path.join(folder_names_and_paths['checkpoints'][0][0], "Chilloutmix-Ni-pruned-fp16-fix.safetensors"),
os.path.join(folder_names_and_paths['controlnet'][0][0], "control_v11p_sd15_openpose.pth"),
os.path.join(folder_names_and_paths['controlnet'][0][0], "control_v11p_sd15_canny.pth"),
os.path.join(folder_names_and_paths['controlnet'][0][0], "control_v11f1e_sd15_tile.pth"),
+107
View File
@@ -0,0 +1,107 @@
import sys
import subprocess
import threading
import locale
import traceback
import re
from portrait.config import *
windows_not_install = ['mmcv_full\n']
def log(msg, end=None, file=None):
print('Portrait Maker ==============', msg, end=end, file=file)
def handle_stream(stream, is_stdout):
stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')
for msg in stream:
if is_stdout:
log(msg, end="", file=sys.stdout)
else:
log(msg, end="", file=sys.stderr)
def process_wrap(cmd_str, cwd=None, handler=None):
log(f"[Portrait Maker] EXECUTE: {cmd_str} in '{cwd}'")
process = subprocess.Popen(cmd_str, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1)
if handler is None:
handler = handle_stream
stdout_thread = threading.Thread(target=handler, args=(process.stdout, True))
stderr_thread = threading.Thread(target=handler, args=(process.stderr, False))
stdout_thread.start()
stderr_thread.start()
stdout_thread.join()
stderr_thread.join()
return process.wait()
# ---
pip_list = None
def get_installed_packages():
global pip_list
if pip_list is None:
try:
result = subprocess.check_output([sys.executable, '-m', 'pip', 'list'], universal_newlines=True)
pip_list = set([line.split()[0].lower() for line in result.split('\n') if line.strip()])
except subprocess.CalledProcessError as e:
log(f"[ComfyUI-Manager] Failed to retrieve the information of installed pip packages.")
return set()
return pip_list
def is_installed(name):
name = name.strip()
pattern = r'([^<>!=]+)([<>!=]=?)'
match = re.search(pattern, name)
if match:
name = match.group(1)
result = name.lower() in get_installed_packages()
return result
def check_and_install_requirements(file_path):
log(file_path)
version = sys.version_info[:2]
if os.path.exists(file_path):
with open(file_path, 'r') as file:
lines = file.readlines()
for line in lines:
log(line)
if not is_installed(line):
if platform.system() == "Windows" and version[1] == 11 and 'insightface' in line:
process_wrap(pip_install + ['insightface-0.7.3-cp311-cp311-win_amd64.whl'], cwd=root_path)
continue
if platform.system() == "Windows" and line in windows_not_install:
log(f"windows skip {line}")
continue
log(f"install {line}")
process_wrap(pip_install + [line], cwd=root_path)
return False
return True
try:
import platform
log("### ComfyUI-Portrait-Maker: Check dependencies")
if "python_embed" in sys.executable or "python_embedded" in sys.executable:
pip_install = [sys.executable, '-s', '-m', 'pip', 'install', '-q']
mim_install = [sys.executable, '-s', '-m', 'mim', 'install']
else:
pip_install = [sys.executable, '-m', 'pip', 'install', '-q']
mim_install = [sys.executable, '-m', 'mim', 'install']
subpack_req = os.path.join(root_path, "requirements.txt")
check_and_install_requirements(subpack_req)
if platform.system() != "Windows" :
process_wrap(pip_install + ['mmcv_full'], cwd=root_path)
if sys.argv[0] == 'install.py':
sys.path.append('.') # for portable version
except Exception as e:
log("[ERROR] ComfyUI-Impact-Pack: Dependency installation has failed. Please install manually.")
traceback.print_exc()
+8
View File
@@ -16,6 +16,7 @@ skin_retouching = None
portrait_enhancement = None
psgan_interface = None
real_gan_sr = None
face_recognition = None
def get_retinaface_detection():
global retinaface_detection
@@ -72,3 +73,10 @@ def get_pagan_interface():
makeup_transfer_model_path = os.path.join(models_path, "makeup_transfer.pth")
psgan_interface = PSGAN_Inference("cuda", makeup_transfer_model_path, get_retinaface_detection(), get_face_skin(), face_landmarks_model_path)
return psgan_interface
def get_face_recognition():
global face_recognition
if face_recognition is None:
face_recognition = pipeline("face_recognition", model="bubbliiiing/cv_retinafce_recognition", model_revision="v1.0.3")
return face_recognition
+152 -21
View File
@@ -7,13 +7,15 @@ from .utils.img_utils import img_to_tensor, tensor_to_img, tensor_to_np, np_to_t
from .model_holder import *
# import pydevd_pycharm
#
# pydevd_pycharm.settrace('49.7.62.197', port=10090, stdoutToServer=True, stderrToServer=True)
class RetinaFacePM:
@classmethod
def INPUT_TYPES(s):
return {"required": {"image": ("IMAGE",),
"multi_user_facecrop_ratio": ("FLOAT", {"default": 1, "min": 0, "max": 10, "step": 0.01})
"multi_user_facecrop_ratio": ("FLOAT", {"default": 1, "min": 0, "max": 10, "step": 0.01}),
"face_index": ("INT", {"default": 0, "min": 0, "max": 10, "step": 1})
}}
RETURN_TYPES = ("IMAGE", "MASK", "BOX")
@@ -21,12 +23,14 @@ class RetinaFacePM:
FUNCTION = "retain_face"
CATEGORY = "protrait/model"
def retain_face(self, image, multi_user_facecrop_ratio):
def retain_face(self, image, multi_user_facecrop_ratio, face_index):
np_image = np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8)
image = Image.fromarray(np_image)
retinaface_boxes, retinaface_keypoints, retinaface_masks, retinaface_tensor = call_face_crop(get_retinaface_detection(), image, multi_user_facecrop_ratio)
crop_image = image.crop(retinaface_boxes[0])
return (img_to_tensor(crop_image), retinaface_tensor, retinaface_boxes[0])
retinaface_boxes, retinaface_keypoints, retinaface_masks, retinaface_mask_nps = call_face_crop(get_retinaface_detection(), image, multi_user_facecrop_ratio)
crop_image = image.crop(retinaface_boxes[face_index])
retinaface_mask = np_to_mask(retinaface_mask_nps[face_index])
retinaface_boxe = retinaface_boxes[face_index]
return (img_to_tensor(crop_image), retinaface_mask, retinaface_boxe)
class FaceFusionPM:
@@ -42,6 +46,14 @@ class FaceFusionPM:
CATEGORY = "protrait/model"
def resize(self, tensor):
image = tensor_to_img(tensor)
short_side = max(image.width, image.height)
resize = float(short_side / 640)
new_size = (int(image.width // resize), int(image.height // resize))
resize_image = image.resize(new_size, Image.Resampling.LANCZOS)
return img_to_np(resize_image)
def img_face_fusion(self, source_image, swap_image, mode):
if mode == "ali":
source_image = tensor_to_img(source_image)
@@ -51,12 +63,24 @@ class FaceFusionPM:
result_image = Image.fromarray(cv2.cvtColor(fusion_image, cv2.COLOR_BGR2RGB))
return (img_to_tensor(result_image),)
else:
width, height = source_image.shape[2], source_image.shape[1]
need_resize = False
source_np = tensor_to_np(source_image)
swap_np = tensor_to_np(swap_image)
if source_image.shape[2] > 640 or source_image.shape[1] > 640:
source_np = self.resize(source_image)
need_resize = True
if swap_image.shape[2] > 640 or swap_image.shape[1] > 640:
swap_np = self.resize(swap_image)
get_face_analysis().prepare(ctx_id=0, det_size=(640, 640))
source_image = tensor_to_np(source_image)
faces = get_face_analysis().get(source_image)
swap_image = tensor_to_np(swap_image)
swap_face = get_face_analysis().get(swap_image)
result_image = get_roop().get(source_image, faces[0], swap_face[0], paste_back=True)
faces = get_face_analysis().get(source_np)
swap_faces = get_face_analysis().get(swap_np)
result_image = get_roop().get(source_np, faces[0], swap_faces[0], paste_back=True)
if need_resize:
image = Image.fromarray(result_image)
new_size = width, height
result_image = image.resize(new_size, Image.Resampling.LANCZOS)
result_image = img_to_np(result_image)
return (np_to_tensor(result_image),)
class RatioMerge2ImagePM:
@@ -73,7 +97,7 @@ class RatioMerge2ImagePM:
RETURN_TYPES = ("IMAGE",)
FUNCTION = "image_ratio_merge"
CATEGORY = "protrait/model"
CATEGORY = "protrait/other"
def image_ratio_merge(self, image1, image2, fusion_rate):
rate_fusion_image = image1 * (1 - fusion_rate) + image2 * fusion_rate
@@ -132,7 +156,7 @@ class ExpandMaskFaceWidthPM:
RETURN_TYPES = ("MASK", "BOX")
FUNCTION = "expand_mask_face_width"
CATEGORY = "protrait/model"
CATEGORY = "protrait/other"
def expand_mask_face_width(self, mask, box, expand_width):
h, w = mask.shape[1], mask.shape[2]
@@ -160,7 +184,7 @@ class BoxCropImagePM:
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("crop_image",)
FUNCTION = "box_crop_image"
CATEGORY = "protrait/model"
CATEGORY = "protrait/other"
def box_crop_image(self, image, box):
image = image[:, box[1]:box[3], box[0]:box[2], :]
@@ -178,7 +202,7 @@ class ColorTransferPM:
RETURN_TYPES = ("IMAGE",)
FUNCTION = "color_transfer"
CATEGORY = "protrait/model"
CATEGORY = "protrait/other"
def color_transfer(self, transfer_from, transfer_to):
transfer_result = color_transfer(tensor_to_np(transfer_from), tensor_to_np(transfer_to)) # 进行颜色迁移
@@ -219,7 +243,7 @@ class MaskDilateErodePM:
RETURN_TYPES = ("MASK",)
FUNCTION = "mask_dilate_erode"
CATEGORY = "protrait/model"
CATEGORY = "protrait/other"
def mask_dilate_erode(self, mask):
out_mask = Image.fromarray(np.uint8(cv2.dilate(tensor_to_np(mask), np.ones((96, 96), np.uint8), iterations=1) - cv2.erode(tensor_to_np(mask), np.ones((48, 48), np.uint8), iterations=1)))
@@ -271,13 +295,13 @@ class ImageScaleShortPM:
return {"required": {
"image": ("IMAGE",),
"size": ("INT", {"default": 512, "min": 0, "max": 2048, "step": 1}),
"crop_face": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
"crop_face": ("BOOLEAN", {"default": False}),
}}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "image_scale_short"
CATEGORY = "protrait/model"
CATEGORY = "protrait/other"
def image_scale_short(self, image, size, crop_face):
input_image = tensor_to_img(image)
@@ -304,7 +328,7 @@ class ImageResizeTargetPM:
FUNCTION = "image_resize_target"
CATEGORY = "protrait/model"
CATEGORY = "protrait/other"
def image_resize_target(self, image, width, height):
imagepi = tensor_to_img(image)
@@ -323,7 +347,7 @@ class GetImageInfoPM:
FUNCTION = "get_image_info"
CATEGORY = "protrait/model"
CATEGORY = "protrait/other"
def get_image_info(self, image):
width = image.shape[2]
@@ -345,8 +369,8 @@ class MakeUpTransferPM:
CATEGORY = "protrait/model"
def makeup_transfer(self, source_image, makeup_image):
source_image = tensor_to_img(source_image).resize([256, 256])
makeup_image = tensor_to_img(makeup_image).resize([256, 256])
source_image = tensor_to_img(source_image)
makeup_image = tensor_to_img(makeup_image)
result = get_pagan_interface().transfer(source_image, makeup_image)
return (img_to_tensor(result),)
@@ -386,3 +410,110 @@ class FaceShapMatchPM:
input_image_uint8 = np.array(source_image) * face_skin_mask + np.array(match_image) * (1 - face_skin_mask)
return (np_to_tensor(input_image_uint8),)
class SuperColorTransferPM:
@classmethod
def INPUT_TYPES(s):
return \
{
"required": {
"main_image": ("IMAGE",),
"transfer_image": ("IMAGE",),
},
"optional": {
"avatar_box": ("BOX",),
},
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "super_color_transfer"
CATEGORY = "protrait/super"
def super_color_transfer(self, main_image, transfer_image, avatar_box=None):
origin_np = tensor_to_np(main_image)
result_np = None
if avatar_box is not None:
main_image = main_image[:, avatar_box[1]:avatar_box[3], avatar_box[0]:avatar_box[2], :]
transfer_image = transfer_image[:, avatar_box[1]:avatar_box[3], avatar_box[0]:avatar_box[2], :]
transfer_result = color_transfer(tensor_to_np(main_image), tensor_to_np(transfer_image)) # 进行颜色迁移
face_skin_img = get_face_skin()(Image.fromarray(transfer_result), get_retinaface_detection(), [[1, 2, 3, 4, 5, 10, 12, 13]])[0]
face_skin_np = img_to_np(face_skin_img)
face_skin_np = cv2.blur(face_skin_np, (32, 32)) / 255
masked_img_np = tensor_to_np(main_image) * (1 - face_skin_np) + transfer_result * face_skin_np
result_np = masked_img_np
if avatar_box is not None:
origin_np[avatar_box[1]:avatar_box[3], avatar_box[0]:avatar_box[2], :] = masked_img_np
result_np = origin_np
return (np_to_tensor(result_np),)
class SuperMakeUpTransferPM:
@classmethod
def INPUT_TYPES(s):
return \
{
"required": {
"main_image": ("IMAGE",),
"makeup_image": ("IMAGE",),
},
"optional": {
"avatar_box": ("BOX",),
},
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "super_makeup_transfer"
CATEGORY = "protrait/super"
def super_makeup_transfer(self, main_image, makeup_image, avatar_box=None):
box_width, box_height = avatar_box[2] - avatar_box[0], avatar_box[3] - avatar_box[1]
origin_np = tensor_to_np(main_image)
if avatar_box is not None:
main_image = main_image[:, avatar_box[1]:avatar_box[3], avatar_box[0]:avatar_box[2], :]
makeup_image = makeup_image[:, avatar_box[1]:avatar_box[3], avatar_box[0]:avatar_box[2], :]
resize_source_box_image = tensor_to_img(main_image).resize([256, 256])
resize_makeup_box_image = tensor_to_img(makeup_image).resize([256, 256])
transfer_image = get_pagan_interface().transfer(resize_source_box_image, resize_makeup_box_image)
box_size_transfer = transfer_image.resize([box_width, box_height], Image.Resampling.LANCZOS)
origin_np[avatar_box[1]:avatar_box[3], avatar_box[0]:avatar_box[2], :] = img_to_np(box_size_transfer)
return (np_to_tensor(origin_np),)
class SimilarityPM:
@classmethod
def INPUT_TYPES(s):
return \
{
"required": {
"main_image": ("IMAGE",),
"compare_image": ("IMAGE",),
"model": (["sim"],),
"result_prefix": ("STRING",{"default": ""}),
},
}
RETURN_TYPES = ("STRING",)
FUNCTION = "similarity_compare"
CATEGORY = "protrait/model"
def similarity_compare(self, main_image, compare_image, model, result_prefix):
main_image_copy = tensor_to_img(main_image)
compare_image_copy = tensor_to_img(compare_image)
score = None
result = None
if model == "sim":
root_embedding = get_face_recognition()(dict(user=Image.fromarray(np.uint8(main_image_copy))))[OutputKeys.IMG_EMBEDDING]
compare_embedding = get_face_recognition()(dict(user=Image.fromarray(np.uint8(compare_image_copy))))[OutputKeys.IMG_EMBEDDING]
score = float(np.dot(root_embedding, np.transpose(compare_embedding))[0][0])
if result_prefix is "":
result = str(round(score, 2))
else:
result = f"{result_prefix}_{round(score, 2)}"
return (result,)
+4 -5
View File
@@ -65,9 +65,8 @@ def safe_get_box_mask_keypoints(image, retinaface_result, crop_ratio, face_seg,
retinaface_boxs = [retinaface_boxs[index] for index in argindex]
retinaface_keypoints = [retinaface_keypoints[index] for index in argindex]
retinaface_mask_pils = [retinaface_mask_pils[index] for index in argindex]
retinaface_mask_np = [retinaface_masks[index] for index in argindex]
mask_tensor = np_to_mask(retinaface_mask_np[0])
return retinaface_boxs, retinaface_keypoints, retinaface_mask_pils, mask_tensor
retinaface_mask_nps = [retinaface_masks[index] for index in argindex]
return retinaface_boxs, retinaface_keypoints, retinaface_mask_pils, retinaface_mask_nps
else:
retinaface_box = np.array([])
@@ -120,9 +119,9 @@ def call_face_crop(retinaface_detection, image, crop_ratio, prefix="tmp"):
# retinaface detect
retinaface_result = retinaface_detection(image)
# get mask and keypoints
retinaface_box, retinaface_keypoints, retinaface_mask_pil, retinaface_mask_tensor = safe_get_box_mask_keypoints(image, retinaface_result, crop_ratio, None, "crop")
retinaface_box, retinaface_keypoints, retinaface_mask_pil, retinaface_mask_nps = safe_get_box_mask_keypoints(image, retinaface_result, crop_ratio, None, "crop")
return retinaface_box, retinaface_keypoints, retinaface_mask_pil, retinaface_mask_tensor
return retinaface_box, retinaface_keypoints, retinaface_mask_pil, retinaface_mask_nps
def color_transfer(sc, dc):
"""
+2 -2
View File
@@ -456,7 +456,7 @@ class PreProcess:
lms = lms[:, ::-1]
mask, diff = self.process(mask, lms, device=self.device)
image = image.resize((self.img_size, self.img_size), Image.ANTIALIAS)
image = image.resize((self.img_size, self.img_size), Image.Resampling.NEAREST)
image = self.transform(image)
real = to_var(image.unsqueeze(0))
return [real, mask, diff], face_on_image, crop_face
@@ -830,7 +830,7 @@ class PostProcess:
height, width = source.shape[:2]
small_source = cv2.resize(source, (self.img_size, self.img_size))
laplacian_diff = source.astype(np.float) - cv2.resize(small_source, (width, height)).astype(np.float)
laplacian_diff = source.astype(np.float64) - cv2.resize(small_source, (width, height)).astype(np.float64)
result = (cv2.resize(result, (width, height)) + laplacian_diff).round().clip(0, 255).astype(np.uint8)
if self.denoise:
result = cv2.fastNlMeansDenoisingColored(result)
+3 -1
View File
@@ -1,3 +1,4 @@
aliyun-python-sdk-core-v3==2.13.10
opencv-python
tensorflow-cpu
tensorflow
@@ -7,4 +8,5 @@ modelscope
scikit-image
matplotlib
insightface
diffusers==0.18.2
diffusers==0.18.2
sentencepiece
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff