Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fde067c9f5 | ||
|
|
f233cae900 | ||
|
|
94dcf5b2d9 | ||
|
|
db728d3b70 | ||
|
|
9a7cdbd756 | ||
|
|
74dce3296d | ||
|
|
e30f28d20e | ||
|
|
baa3724ae2 | ||
|
|
9453492f02 | ||
|
|
b2d44fb139 | ||
|
|
1d09d46f16 | ||
|
|
bac4d69fba | ||
|
|
0c06d3e3bf | ||
|
|
7861f289c5 | ||
|
|
fd5f4737fe | ||
|
|
fb2a1e3283 | ||
|
|
75cb3303a9 | ||
|
|
2763361b55 | ||
|
|
3f70c7eb6b | ||
|
|
5beb93db0a | ||
|
|
98d3b8ac13 | ||
|
|
c871825137 | ||
|
|
26245524ea | ||
|
|
2292031e5f | ||
|
|
017dcee81b | ||
|
|
6d6eb679d5 | ||
|
|
2abacd7fde | ||
|
|
6a2d356ed7 | ||
|
|
97c93eff3d | ||
|
|
2598a6efe4 | ||
|
|
dedd32d29e | ||
|
|
a839966e9f | ||
|
|
c8e3535869 | ||
|
|
359e72cd2e | ||
|
|
a11d54294f | ||
|
|
6013eb426c | ||
|
|
0b005db217 | ||
|
|
658f057ccd | ||
|
|
5b77e1cf3d | ||
|
|
a6dbede699 | ||
|
|
dc01281d85 | ||
|
|
948b679e70 | ||
|
|
6335f3c3d4 | ||
|
|
3744780d0b | ||
|
|
425924bcbb | ||
|
|
d364f2874a | ||
|
|
e75c9a402a | ||
|
|
e45b89d111 | ||
|
|
3c61c01ba1 | ||
|
|
a41dfa668c | ||
|
|
e54e9ea579 |
@@ -1,2 +1,3 @@
|
||||
__pycache__
|
||||
.idea
|
||||
*.DS_Store
|
||||
|
||||
@@ -11,11 +11,12 @@ 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
|
||||
@@ -40,10 +41,6 @@ If you have any questions or suggestions, you can reach us through:
|
||||
## Installation
|
||||
**Note: When you start the plugin for the first time, it will download all the models required by EasyPhoto. You can see the download progress in the terminal. Please do not interrupt the download (no hash verification for startup speed). If the download is interrupted, you need to manually delete the files downloaded halfway last time and download them again.**
|
||||
|
||||
### For Windows users
|
||||
|
||||
If you are using a project extracted from a zip package while using ComfyUI, you won't be able to use this plugin. This project relies on ModelScope, but the virtual environment provided in the official ComfyUI zip package cannot install ModelScope. Furthermore, the ComfyUI author has responded, stating that this issue cannot be resolved.[aliyunsdkcor error](https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/223) If Windows users wish to use this plugin for analyzing and composing ComfyUI workflows, they will need to create their own virtual environment. (I am using Python 3.10.6.).Of course, if you know a solution, feel free to submit a pull request (PR).
|
||||
|
||||
### install step
|
||||
1. First, install ComfyUI.
|
||||
|
||||
@@ -131,6 +128,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:
|
||||
|
||||
+4
-5
@@ -13,11 +13,12 @@ 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 增加模糊选项
|
||||
@@ -43,10 +44,6 @@ English | [简体中文](./README_zh-CN.md)
|
||||
|
||||
**注意:初次启动插件的时候会下载EasyPhoto所需要的所有模型,在terminal中可以看到下载进度,请不要中断下载,(为了启动速度,没有做hash校验),如果中断下载,需要手动删除上次下载一半的文件,重新下载。**
|
||||
|
||||
### windows用户
|
||||
|
||||
如果在使用ComfyUI的时候使用zip包解压后的项目,是无法使用本插件的,本项目依赖modelscope,但是ComfyUI官方zip包中的虚拟环境无法安装modelscope,并且ComfyUI作者已经回复了表示无法解决此问题[aliyunsdkcor error](https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/223)如果windows用户想使用本插件来分析、组合ComfyUI的流程,请自己创建虚拟环境。(我使用的是python3.10.6),当然如果您知道解决的方法,欢迎提交pr
|
||||
|
||||
### 步骤
|
||||
1. 首先安装ComfyUI
|
||||
|
||||
@@ -117,6 +114,8 @@ Easyphoto工作位置: [./workflow/easyphoto.json](./workflows/easyphoto.json )
|
||||
* GetImageInfo PM: 提取图片的宽高
|
||||
* FaceShapMatchPM: 扩散后的图片和原图片进行一定的融合,减少脸旁边的差异
|
||||
* MakeUpTransferPM: 使用gan网络模型对妆容进行一定的迁移
|
||||
* SuperMakeUpTransferPM:(多节点的整合)融合两张图片的装扮
|
||||
* SuperColorTransferPM:(多节点的整合)迁移两张图片的颜色
|
||||
|
||||
## 贡献
|
||||
|
||||
|
||||
+5
-29
@@ -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))
|
||||
@@ -79,6 +52,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"PM_FaceShapMatch": FaceShapMatchPM,
|
||||
"PM_SuperColorTransfer": SuperColorTransferPM,
|
||||
"PM_SuperMakeUpTransfer": SuperMakeUpTransferPM,
|
||||
"PM_Similarity": SimilarityPM,
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"PM_RetinaFace": "RetinaFace PM",
|
||||
@@ -100,6 +74,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"PM_FaceShapMatch": "FaceShapMatch PM",
|
||||
"PM_SuperColorTransfer": "SuperColorTransfer PM",
|
||||
"PM_SuperMakeUpTransfer": "SuperMakeUpTransfer PM",
|
||||
"PM_Similarity": "Similarity PM",
|
||||
|
||||
}
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
|
||||
Regular → Executable
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 679 KiB |
Binary file not shown.
+2
-2
@@ -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"),
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import threading
|
||||
import locale
|
||||
import traceback
|
||||
import re
|
||||
|
||||
from .config import root_path
|
||||
|
||||
plugin_name = os.path.basename(root_path)
|
||||
windows_not_install = ['mmcv_full\n']
|
||||
|
||||
def log(msg, end=None, file=None):
|
||||
print(f'{plugin_name} :', 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"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"Failed to retrieve the information of installed pip packages.")
|
||||
return set()
|
||||
|
||||
return pip_list
|
||||
|
||||
def mmcv_install():
|
||||
process_wrap(pip_install + ['-U', 'openmim'], cwd=root_path)
|
||||
process_wrap(mim_install + ['mmcv-full'], cwd=root_path)
|
||||
pass
|
||||
|
||||
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("### : 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', '-q']
|
||||
else:
|
||||
pip_install = [sys.executable, '-m', 'pip', 'install', '-q']
|
||||
mim_install = [sys.executable, '-m', 'mim', 'install', '-q']
|
||||
|
||||
subpack_req = os.path.join(root_path, "requirements.txt")
|
||||
# mmcv_install()
|
||||
check_and_install_requirements(subpack_req)
|
||||
if sys.argv[0] == 'install.py':
|
||||
sys.path.append('..') # for portable version
|
||||
|
||||
except Exception as e:
|
||||
log("Dependency installation has failed. Please install manually.")
|
||||
traceback.print_exc()
|
||||
@@ -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
|
||||
|
||||
|
||||
+92
-28
@@ -6,9 +6,9 @@ from .utils.face_process_utils import call_face_crop, color_transfer, Face_Skin
|
||||
from .utils.img_utils import img_to_tensor, tensor_to_img, tensor_to_np, np_to_tensor, np_to_mask, img_to_mask, img_to_np
|
||||
from .model_holder import *
|
||||
|
||||
import pydevd_pycharm
|
||||
|
||||
pydevd_pycharm.settrace('49.7.62.197', port=10090, stdoutToServer=True, stderrToServer=True)
|
||||
# import pydevd_pycharm
|
||||
#
|
||||
# pydevd_pycharm.settrace('49.7.62.197', port=10090, stdoutToServer=True, stderrToServer=True)
|
||||
|
||||
class RetinaFacePM:
|
||||
@classmethod
|
||||
@@ -46,21 +46,46 @@ 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)
|
||||
swap_image = tensor_to_img(swap_image)
|
||||
fusion_image = get_image_face_fusion()(dict(template=source_image, user=swap_image))[
|
||||
source_image_pil = tensor_to_img(source_image)
|
||||
swap_image_pil = tensor_to_img(swap_image)
|
||||
fusion_image = get_image_face_fusion()(dict(template=source_image_pil, user=swap_image_pil))[
|
||||
OutputKeys.OUTPUT_IMG]
|
||||
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)
|
||||
if len(faces) == 0:
|
||||
raise RuntimeError("No face was recognized in the source image / source image 没有识别到人脸")
|
||||
if len(swap_faces) == 0:
|
||||
print("No face was recognized in the swap faces / swap faces没有识别到人脸, 用原脸替换!!!!!!!!!")
|
||||
return (source_image,)
|
||||
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:
|
||||
@@ -77,7 +102,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
|
||||
@@ -136,7 +161,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]
|
||||
@@ -164,7 +189,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], :]
|
||||
@@ -182,7 +207,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)) # 进行颜色迁移
|
||||
@@ -223,7 +248,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)))
|
||||
@@ -275,13 +300,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)
|
||||
@@ -308,7 +333,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)
|
||||
@@ -327,7 +352,7 @@ class GetImageInfoPM:
|
||||
|
||||
FUNCTION = "get_image_info"
|
||||
|
||||
CATEGORY = "protrait/model"
|
||||
CATEGORY = "protrait/other"
|
||||
|
||||
def get_image_info(self, image):
|
||||
width = image.shape[2]
|
||||
@@ -349,6 +374,8 @@ class MakeUpTransferPM:
|
||||
CATEGORY = "protrait/model"
|
||||
|
||||
def makeup_transfer(self, source_image, makeup_image):
|
||||
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),)
|
||||
|
||||
@@ -370,9 +397,9 @@ class FaceShapMatchPM:
|
||||
|
||||
def faceshap_match(self, source_image, match_image, face_box):
|
||||
# detect face area
|
||||
source_image = tensor_to_img(source_image)
|
||||
match_image = tensor_to_img(match_image)
|
||||
face_skin_mask = get_face_skin()(source_image, get_retinaface_detection(), needs_index=[[1, 2, 3, 4, 5, 7, 8, 10, 11, 12, 13]])[0]
|
||||
source_image_copy = tensor_to_img(source_image)
|
||||
match_image_copy = tensor_to_img(match_image)
|
||||
face_skin_mask = get_face_skin()(source_image_copy, get_retinaface_detection(), needs_index=[[1, 2, 3, 4, 5, 7, 8, 10, 11, 12, 13]])[0]
|
||||
face_width = face_box[2] - face_box[0]
|
||||
kernel_size = np.ones((int(face_width // 10), int(face_width // 10)), np.uint8)
|
||||
|
||||
@@ -385,7 +412,7 @@ class FaceShapMatchPM:
|
||||
|
||||
# paste back to photo, Using I2I generation controlled solely by OpenPose, even with a very small denoise amplitude,
|
||||
# still carries the risk of introducing NSFW and global incoherence.!!! important!!!
|
||||
input_image_uint8 = np.array(source_image) * face_skin_mask + np.array(match_image) * (1 - face_skin_mask)
|
||||
input_image_uint8 = np.array(source_image_copy) * face_skin_mask + np.array(match_image_copy) * (1 - face_skin_mask)
|
||||
|
||||
return (np_to_tensor(input_image_uint8),)
|
||||
|
||||
@@ -407,10 +434,11 @@ class SuperColorTransferPM:
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
|
||||
FUNCTION = "super_color_transfer"
|
||||
CATEGORY = "protrait/model"
|
||||
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], :]
|
||||
@@ -422,10 +450,13 @@ class SuperColorTransferPM:
|
||||
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
|
||||
|
||||
origin_np[avatar_box[1]:avatar_box[3], avatar_box[0]:avatar_box[2], :] = 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(origin_np),)
|
||||
return (np_to_tensor(result_np),)
|
||||
|
||||
class SuperMakeUpTransferPM:
|
||||
|
||||
@@ -444,7 +475,7 @@ class SuperMakeUpTransferPM:
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "super_makeup_transfer"
|
||||
CATEGORY = "protrait/model"
|
||||
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]
|
||||
@@ -458,3 +489,36 @@ class SuperMakeUpTransferPM:
|
||||
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 == "":
|
||||
result = str(round(score, 2))
|
||||
else:
|
||||
result = f"{result_prefix}_{round(score, 2)}"
|
||||
return (result,)
|
||||
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user