first update

This commit is contained in:
TaylorGoulding
2023-10-13 10:24:38 +00:00
parent 427f9d397a
commit c219a7aa67
10 changed files with 1003 additions and 7 deletions
+1
View File
@@ -0,0 +1 @@
__pycache__
+66
View File
@@ -0,0 +1,66 @@
import sys
import requests
from tqdm import tqdm
from .config import *
# import pydevd_pycharm
# pydevd_pycharm.settrace('49.7.62.197', port=10090, stdoutToServer=True, stderrToServer=True)
sys.path.append(utils_path)
from .node import *
def urldownload_progressbar(url, file_path):
response = requests.get(url, stream=True)
total_size = int(response.headers.get('content-length', 0))
progress_bar = tqdm(total=total_size, unit='B', unit_scale=True)
with open(file_path, 'wb') as f:
for chunk in response.iter_content(1024):
if chunk:
f.write(chunk)
progress_bar.update(len(chunk))
progress_bar.close()
print("Start Setting weights")
for url, filename in zip(urls, filenames):
if os.path.exists(filename):
continue
print(f"Start Downloading: {url}")
os.makedirs(os.path.dirname(filename), exist_ok=True)
urldownload_progressbar(url, filename)
NODE_CLASS_MAPPINGS = {
"RetainFace": RetainFace,
"FaceFusion": FaceFusion,
"RatioMerge2Image": RatioMerge2Image,
"MaskMerge2Image": MaskMerge2Image,
"ReplaceBoxImg": ReplaceBoxImg,
"ExpandMaskBox": ExpandMaskFaceWidth,
"BoxCropImage": BoxCropImage,
"ColorTransfer": ColorTransfer,
"FaceSkin": FaceSkin,
"MaskDilateErode": MaskDilateErode,
"SkinRetouching": SkinRetouching,
"PortraitEnhancement": PortraitEnhancement,
"ResizeImage": ResizeImage,
"GetImageInfo": GetImageInfo,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RetainFace": "RetainFace",
"FaceFusion": "FaceFusion",
"RatioMerge2Image": "RatioMerge2Image",
"MaskMerge2Image": "MaskMerge2Image",
"ReplaceBoxImg": "ReplaceBoxImg",
"ExpandMaskBox": "ExpandMaskBox",
"BoxCropImage": "BoxCropImage",
"ColorTransfer": "ColorTransfer",
"FaceSkin": "FaceSkin",
"MaskDilateErode": "MaskDilateErode",
"SkinRetouching": "SkinRetouching",
"PortraitEnhancement": "PortraitEnhancement",
"ResizeImage": "ResizeImage",
"GetImageInfo": "GetImageInfo",
}
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
+35
View File
@@ -0,0 +1,35 @@
import os, glob
from folder_paths import folder_names_and_paths
root_path = os.path.dirname(__file__)
utils_path = os.path.join(os.path.dirname(__file__), "utils")
models_path = os.path.join(os.path.dirname(__file__), "models")
# save_dirs
urls = [
"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",
"https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/webui/control_sd15_random_color.pth",
"https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/webui/FilmVelvia3.safetensors",
"https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/webui/body_pose_model.pth",
"https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/webui/facenet.pth",
"https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/webui/hand_pose_model.pth",
"https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/webui/vae-ft-mse-840000-ema-pruned.ckpt",
"https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/webui/face_skin.pth",
]
filenames = [
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"),
os.path.join(folder_names_and_paths['controlnet'][0][0], "control_sd15_random_color.pth"),
os.path.join(folder_names_and_paths['loras'][0][0], "FilmVelvia3.safetensors"),
os.path.join(folder_names_and_paths['controlnet'][0][0], "body_pose_model.pth"),
os.path.join(folder_names_and_paths['controlnet'][0][0], "facenet.pth"),
os.path.join(folder_names_and_paths['controlnet'][0][0], "hand_pose_model.pth"),
os.path.join(folder_names_and_paths['vae'][0][0], "VAE/vae-ft-mse-840000-ema-pruned.ckpt"),
os.path.join(models_path, "face_skin.pth"),
]
# prompts
validation_prompt = "easyphoto_face, easyphoto, 1person"
DEFAULT_POSITIVE = '(cloth:1.7), (best quality), (realistic, photo-realistic:1.2), detailed skin, beautiful, cool, finely detail, light smile, extremely detailed CG unity 8k wallpaper, huge filesize, best quality, realistic, photo-realistic, ultra high res, raw photo, put on makeup'
DEFAULT_NEGATIVE = '(bags under the eyes:1.5), (Bags under eyes:1.5), (glasses:1.5), (naked:2.0), nude, (nsfw:2.0), breasts, penis, cum, (worst quality:2), (low quality:2), (normal quality:2), over red lips, hair, teeth, lowres, watermark, badhand, (normal quality:2), lowres, bad anatomy, bad hands, normal quality, mural,'
+536
View File
@@ -0,0 +1,536 @@
import cv2
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision.transforms as transforms
from PIL import Image
from skimage import transform
from protrait.img_utils import np_to_mask
import pydevd_pycharm
pydevd_pycharm.settrace('49.7.62.197', port=10090, stdoutToServer=True, stderrToServer=True)
def safe_get_box_mask_keypoints(image, retinaface_result, crop_ratio, face_seg, mask_type):
'''
Inputs:
image Input image
retinaface_result The detection results of retinaface
crop_ratio The proportion of facial clipping and expansion
face_seg Facial segmentation model
mask_type The type of facial segmentation methods, one is loop and the other is skin, and the result of facial segmentation is the skin or frame of the face
Outputs:
retinaface_box After box amplification, the box relative to the original image
retinaface_keypoints Points relative to the original image
retinaface_mask_pil Segmentation Results
'''
h, w, c = np.shape(image)
if len(retinaface_result['boxes']) != 0:
retinaface_boxs = []
retinaface_keypoints = []
retinaface_mask_pils = []
retinaface_masks = []
for index in range(len(retinaface_result['boxes'])):
# 获得retinaface的box并做扩充
retinaface_box = np.array(retinaface_result['boxes'][index])
face_width = retinaface_box[2] - retinaface_box[0]
face_height = retinaface_box[3] - retinaface_box[1]
retinaface_box[0] = np.clip(np.array(retinaface_box[0], np.int32) - face_width * (crop_ratio - 1) / 2, 0, w - 1)
retinaface_box[1] = np.clip(np.array(retinaface_box[1], np.int32) - face_height * (crop_ratio - 1) / 2, 0, h - 1)
retinaface_box[2] = np.clip(np.array(retinaface_box[2], np.int32) + face_width * (crop_ratio - 1) / 2, 0, w - 1)
retinaface_box[3] = np.clip(np.array(retinaface_box[3], np.int32) + face_height * (crop_ratio - 1) / 2, 0, h - 1)
retinaface_box = np.array(retinaface_box, np.int32)
retinaface_boxs.append(retinaface_box)
# 检测关键点
retinaface_keypoint = np.reshape(retinaface_result['keypoints'][index], [5, 2])
retinaface_keypoint = np.array(retinaface_keypoint, np.float32)
retinaface_keypoints.append(retinaface_keypoint)
# mask部分
retinaface_crop = image.crop(np.int32(retinaface_box))
retinaface_mask = np.zeros_like(np.array(image, np.uint8))
if mask_type == "skin":
retinaface_sub_mask = face_seg(retinaface_crop)
retinaface_mask[retinaface_box[1]:retinaface_box[3], retinaface_box[0]:retinaface_box[2]] = np.expand_dims(retinaface_sub_mask, -1)
else:
retinaface_mask[retinaface_box[1]:retinaface_box[3], retinaface_box[0]:retinaface_box[2]] = 255
retinaface_masks.append(retinaface_mask)
retinaface_mask_pil = Image.fromarray(np.uint8(retinaface_mask))
retinaface_mask_pils.append(retinaface_mask_pil)
retinaface_boxs = np.array(retinaface_boxs)
argindex = np.argsort(retinaface_boxs[:, 0])
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
else:
retinaface_box = np.array([])
retinaface_keypoints = np.array([])
retinaface_mask = np.zeros_like(np.array(image, np.uint8))
retinaface_mask_pil = Image.fromarray(np.uint8(retinaface_mask))
return retinaface_box, retinaface_keypoints, retinaface_mask_pil, retinaface_mask
def crop_and_paste(source_image, source_image_mask, target_image, source_five_point, target_five_point, source_box):
"""
Applies a face replacement by cropping and pasting one face onto another image.
Args:
source_image (PIL.Image): The source image containing the face to be pasted.
source_image_mask (PIL.Image): The mask representing the face in the source image.
target_image (PIL.Image): The target image where the face will be pasted.
source_five_point (numpy.ndarray): Five key points of the face in the source image.
target_five_point (numpy.ndarray): Five key points of the corresponding face in the target image.
source_box (list): Coordinates of the bounding box around the face in the source image.
Returns:
PIL.Image: The resulting image with the pasted face.
Notes:
The function takes a source image, its corresponding mask, a target image, key points, and the bounding box
around the face in the source image. It then aligns and pastes the face from the source image onto the
corresponding location in the target image, taking into account the key points and bounding box.
"""
source_five_point = np.reshape(source_five_point, [5, 2]) - np.array(source_box[:2])
target_five_point = np.reshape(target_five_point, [5, 2])
crop_source_image = source_image.crop(np.int32(source_box))
crop_source_image_mask = source_image_mask.crop(np.int32(source_box))
source_five_point, target_five_point = np.array(source_five_point), np.array(target_five_point)
tform = transform.SimilarityTransform()
# 程序直接估算出转换矩阵M
tform.estimate(source_five_point, target_five_point)
M = tform.params[0:2, :]
warped = cv2.warpAffine(np.array(crop_source_image), M, np.shape(target_image)[:2][::-1], borderValue=0.0)
warped_mask = cv2.warpAffine(np.array(crop_source_image_mask), M, np.shape(target_image)[:2][::-1], borderValue=0.0)
mask = np.float32(warped_mask == 0)
output = mask * np.float32(target_image) + (1 - mask) * np.float32(warped)
return output
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")
return retinaface_box, retinaface_keypoints, retinaface_mask_pil, retinaface_mask_tensor
def color_transfer(sc, dc):
"""
Transfer color distribution from of sc, referred to dc.
Args:
sc (numpy.ndarray): input image to be transfered.
dc (numpy.ndarray): reference image
Returns:
numpy.ndarray: Transferred color distribution on the sc.
"""
def get_mean_and_std(img):
x_mean, x_std = cv2.meanStdDev(img)
x_mean = np.hstack(np.around(x_mean, 2))
x_std = np.hstack(np.around(x_std, 2))
return x_mean, x_std
sc = cv2.cvtColor(sc, cv2.COLOR_BGR2LAB) # 转换颜色空间为clelab
s_mean, s_std = get_mean_and_std(sc)
dc = cv2.cvtColor(dc, cv2.COLOR_BGR2LAB) # 转换颜色空间为clelab
t_mean, t_std = get_mean_and_std(dc)
img_n = ((sc - s_mean) * (t_std / s_std)) + t_mean
np.putmask(img_n, img_n > 255, 255)
np.putmask(img_n, img_n < 0, 0)
dst = cv2.cvtColor(cv2.convertScaleAbs(img_n), cv2.COLOR_LAB2BGR)
return dst
def conv3x3(in_planes, out_planes, stride=1):
"""3x3 convolution with padding"""
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=1, bias=False)
class BasicBlock(nn.Module):
def __init__(self, in_chan, out_chan, stride=1):
super(BasicBlock, self).__init__()
self.conv1 = conv3x3(in_chan, out_chan, stride)
self.bn1 = nn.BatchNorm2d(out_chan)
self.conv2 = conv3x3(out_chan, out_chan)
self.bn2 = nn.BatchNorm2d(out_chan)
self.relu = nn.ReLU(inplace=True)
self.downsample = None
if in_chan != out_chan or stride != 1:
self.downsample = nn.Sequential(
nn.Conv2d(in_chan, out_chan,
kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(out_chan),
)
def forward(self, x):
residual = self.conv1(x)
residual = F.relu(self.bn1(residual))
residual = self.conv2(residual)
residual = self.bn2(residual)
shortcut = x
if self.downsample is not None:
shortcut = self.downsample(x)
out = shortcut + residual
out = self.relu(out)
return out
def create_layer_basic(in_chan, out_chan, bnum, stride=1):
layers = [BasicBlock(in_chan, out_chan, stride=stride)]
for i in range(bnum - 1):
layers.append(BasicBlock(out_chan, out_chan, stride=1))
return nn.Sequential(*layers)
class Resnet18(nn.Module):
def __init__(self):
super(Resnet18, self).__init__()
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = create_layer_basic(64, 64, bnum=2, stride=1)
self.layer2 = create_layer_basic(64, 128, bnum=2, stride=2)
self.layer3 = create_layer_basic(128, 256, bnum=2, stride=2)
self.layer4 = create_layer_basic(256, 512, bnum=2, stride=2)
def forward(self, x):
x = self.conv1(x)
x = F.relu(self.bn1(x))
x = self.maxpool(x)
x = self.layer1(x)
feat8 = self.layer2(x) # 1/8
feat16 = self.layer3(feat8) # 1/16
feat32 = self.layer4(feat16) # 1/32
return feat8, feat16, feat32
def get_params(self):
wd_params, nowd_params = [], []
for name, module in self.named_modules():
if isinstance(module, (nn.Linear, nn.Conv2d)):
wd_params.append(module.weight)
if not module.bias is None:
nowd_params.append(module.bias)
elif isinstance(module, nn.BatchNorm2d):
nowd_params += list(module.parameters())
return wd_params, nowd_params
class ConvBNReLU(nn.Module):
def __init__(self, in_chan, out_chan, ks=3, stride=1, padding=1, *args, **kwargs):
super(ConvBNReLU, self).__init__()
self.conv = nn.Conv2d(in_chan,
out_chan,
kernel_size=ks,
stride=stride,
padding=padding,
bias=False)
self.bn = nn.BatchNorm2d(out_chan)
self.init_weight()
def forward(self, x):
x = self.conv(x)
x = F.relu(self.bn(x))
return x
def init_weight(self):
for ly in self.children():
if isinstance(ly, nn.Conv2d):
nn.init.kaiming_normal_(ly.weight, a=1)
if not ly.bias is None: nn.init.constant_(ly.bias, 0)
class BiSeNetOutput(nn.Module):
def __init__(self, in_chan, mid_chan, n_classes, *args, **kwargs):
super(BiSeNetOutput, self).__init__()
self.conv = ConvBNReLU(in_chan, mid_chan, ks=3, stride=1, padding=1)
self.conv_out = nn.Conv2d(mid_chan, n_classes, kernel_size=1, bias=False)
self.init_weight()
def forward(self, x):
x = self.conv(x)
x = self.conv_out(x)
return x
def init_weight(self):
for ly in self.children():
if isinstance(ly, nn.Conv2d):
nn.init.kaiming_normal_(ly.weight, a=1)
if not ly.bias is None: nn.init.constant_(ly.bias, 0)
def get_params(self):
wd_params, nowd_params = [], []
for name, module in self.named_modules():
if isinstance(module, nn.Linear) or isinstance(module, nn.Conv2d):
wd_params.append(module.weight)
if not module.bias is None:
nowd_params.append(module.bias)
elif isinstance(module, nn.BatchNorm2d):
nowd_params += list(module.parameters())
return wd_params, nowd_params
class AttentionRefinementModule(nn.Module):
def __init__(self, in_chan, out_chan, *args, **kwargs):
super(AttentionRefinementModule, self).__init__()
self.conv = ConvBNReLU(in_chan, out_chan, ks=3, stride=1, padding=1)
self.conv_atten = nn.Conv2d(out_chan, out_chan, kernel_size=1, bias=False)
self.bn_atten = nn.BatchNorm2d(out_chan)
self.sigmoid_atten = nn.Sigmoid()
self.init_weight()
def forward(self, x):
feat = self.conv(x)
atten = F.avg_pool2d(feat, feat.size()[2:])
atten = self.conv_atten(atten)
atten = self.bn_atten(atten)
atten = self.sigmoid_atten(atten)
out = torch.mul(feat, atten)
return out
def init_weight(self):
for ly in self.children():
if isinstance(ly, nn.Conv2d):
nn.init.kaiming_normal_(ly.weight, a=1)
if not ly.bias is None: nn.init.constant_(ly.bias, 0)
class ContextPath(nn.Module):
def __init__(self, *args, **kwargs):
super(ContextPath, self).__init__()
self.resnet = Resnet18()
self.arm16 = AttentionRefinementModule(256, 128)
self.arm32 = AttentionRefinementModule(512, 128)
self.conv_head32 = ConvBNReLU(128, 128, ks=3, stride=1, padding=1)
self.conv_head16 = ConvBNReLU(128, 128, ks=3, stride=1, padding=1)
self.conv_avg = ConvBNReLU(512, 128, ks=1, stride=1, padding=0)
self.init_weight()
def forward(self, x):
H0, W0 = x.size()[2:]
feat8, feat16, feat32 = self.resnet(x)
H8, W8 = feat8.size()[2:]
H16, W16 = feat16.size()[2:]
H32, W32 = feat32.size()[2:]
avg = F.avg_pool2d(feat32, feat32.size()[2:])
avg = self.conv_avg(avg)
avg_up = F.interpolate(avg, (H32, W32), mode='nearest')
feat32_arm = self.arm32(feat32)
feat32_sum = feat32_arm + avg_up
feat32_up = F.interpolate(feat32_sum, (H16, W16), mode='nearest')
feat32_up = self.conv_head32(feat32_up)
feat16_arm = self.arm16(feat16)
feat16_sum = feat16_arm + feat32_up
feat16_up = F.interpolate(feat16_sum, (H8, W8), mode='nearest')
feat16_up = self.conv_head16(feat16_up)
return feat8, feat16_up, feat32_up # x8, x8, x16
def init_weight(self):
for ly in self.children():
if isinstance(ly, nn.Conv2d):
nn.init.kaiming_normal_(ly.weight, a=1)
if not ly.bias is None: nn.init.constant_(ly.bias, 0)
def get_params(self):
wd_params, nowd_params = [], []
for name, module in self.named_modules():
if isinstance(module, (nn.Linear, nn.Conv2d)):
wd_params.append(module.weight)
if not module.bias is None:
nowd_params.append(module.bias)
elif isinstance(module, nn.BatchNorm2d):
nowd_params += list(module.parameters())
return wd_params, nowd_params
### This is not used, since I replace this with the resnet feature with the same size
class SpatialPath(nn.Module):
def __init__(self, *args, **kwargs):
super(SpatialPath, self).__init__()
self.conv1 = ConvBNReLU(3, 64, ks=7, stride=2, padding=3)
self.conv2 = ConvBNReLU(64, 64, ks=3, stride=2, padding=1)
self.conv3 = ConvBNReLU(64, 64, ks=3, stride=2, padding=1)
self.conv_out = ConvBNReLU(64, 128, ks=1, stride=1, padding=0)
self.init_weight()
def forward(self, x):
feat = self.conv1(x)
feat = self.conv2(feat)
feat = self.conv3(feat)
feat = self.conv_out(feat)
return feat
def init_weight(self):
for ly in self.children():
if isinstance(ly, nn.Conv2d):
nn.init.kaiming_normal_(ly.weight, a=1)
if not ly.bias is None: nn.init.constant_(ly.bias, 0)
def get_params(self):
wd_params, nowd_params = [], []
for name, module in self.named_modules():
if isinstance(module, nn.Linear) or isinstance(module, nn.Conv2d):
wd_params.append(module.weight)
if not module.bias is None:
nowd_params.append(module.bias)
elif isinstance(module, nn.BatchNorm2d):
nowd_params += list(module.parameters())
return wd_params, nowd_params
class FeatureFusionModule(nn.Module):
def __init__(self, in_chan, out_chan, *args, **kwargs):
super(FeatureFusionModule, self).__init__()
self.convblk = ConvBNReLU(in_chan, out_chan, ks=1, stride=1, padding=0)
self.conv1 = nn.Conv2d(out_chan,
out_chan // 4,
kernel_size=1,
stride=1,
padding=0,
bias=False)
self.conv2 = nn.Conv2d(out_chan // 4,
out_chan,
kernel_size=1,
stride=1,
padding=0,
bias=False)
self.relu = nn.ReLU(inplace=True)
self.sigmoid = nn.Sigmoid()
self.init_weight()
def forward(self, fsp, fcp):
fcat = torch.cat([fsp, fcp], dim=1)
feat = self.convblk(fcat)
atten = F.avg_pool2d(feat, feat.size()[2:])
atten = self.conv1(atten)
atten = self.relu(atten)
atten = self.conv2(atten)
atten = self.sigmoid(atten)
feat_atten = torch.mul(feat, atten)
feat_out = feat_atten + feat
return feat_out
def init_weight(self):
for ly in self.children():
if isinstance(ly, nn.Conv2d):
nn.init.kaiming_normal_(ly.weight, a=1)
if not ly.bias is None: nn.init.constant_(ly.bias, 0)
def get_params(self):
wd_params, nowd_params = [], []
for name, module in self.named_modules():
if isinstance(module, nn.Linear) or isinstance(module, nn.Conv2d):
wd_params.append(module.weight)
if not module.bias is None:
nowd_params.append(module.bias)
elif isinstance(module, nn.BatchNorm2d):
nowd_params += list(module.parameters())
return wd_params, nowd_params
class BiSeNet(nn.Module):
def __init__(self, n_classes, *args, **kwargs):
super(BiSeNet, self).__init__()
self.cp = ContextPath()
## here self.sp is deleted
self.ffm = FeatureFusionModule(256, 256)
self.conv_out = BiSeNetOutput(256, 256, n_classes)
self.conv_out16 = BiSeNetOutput(128, 64, n_classes)
self.conv_out32 = BiSeNetOutput(128, 64, n_classes)
self.init_weight()
def forward(self, x):
H, W = x.size()[2:]
feat_res8, feat_cp8, feat_cp16 = self.cp(x) # here return res3b1 feature
feat_sp = feat_res8 # use res3b1 feature to replace spatial path feature
feat_fuse = self.ffm(feat_sp, feat_cp8)
feat_out = self.conv_out(feat_fuse)
feat_out16 = self.conv_out16(feat_cp8)
feat_out32 = self.conv_out32(feat_cp16)
feat_out = F.interpolate(feat_out, (H, W), mode='bilinear', align_corners=True)
feat_out16 = F.interpolate(feat_out16, (H, W), mode='bilinear', align_corners=True)
feat_out32 = F.interpolate(feat_out32, (H, W), mode='bilinear', align_corners=True)
return feat_out, feat_out16, feat_out32
def init_weight(self):
for ly in self.children():
if isinstance(ly, nn.Conv2d):
nn.init.kaiming_normal_(ly.weight, a=1)
if not ly.bias is None: nn.init.constant_(ly.bias, 0)
def get_params(self):
wd_params, nowd_params, lr_mul_wd_params, lr_mul_nowd_params = [], [], [], []
for name, child in self.named_children():
child_wd_params, child_nowd_params = child.get_params()
if isinstance(child, FeatureFusionModule) or isinstance(child, BiSeNetOutput):
lr_mul_wd_params += child_wd_params
lr_mul_nowd_params += child_nowd_params
else:
wd_params += child_wd_params
nowd_params += child_nowd_params
return wd_params, nowd_params, lr_mul_wd_params, lr_mul_nowd_params
class Face_Skin(object):
'''
Inputs:
image input image.
Outputs:
mask output mask.
'''
def __init__(self, model_path) -> None:
n_classes = 19
self.model = BiSeNet(n_classes=n_classes)
self.model.load_state_dict(torch.load(model_path, map_location='cpu'))
self.model.eval()
# transform for input image
self.trans = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
])
def detect(self, image, retinaface_detection, needs_index=[12, 13]):
# needs_index 12, 13 means seg the lip
with torch.no_grad():
total_mask = np.zeros_like(np.uint8(image))
# detect image
retinaface_boxes, _, _, _ = call_face_crop(retinaface_detection, image, 13, prefix="tmp")
retinaface_box = retinaface_boxes[0]
# sub_face for seg skin
sub_image = image.crop(retinaface_box)
image_h, image_w, c = np.shape(np.uint8(sub_image))
PIL_img = Image.fromarray(np.uint8(sub_image))
PIL_img = PIL_img.resize((512, 512), Image.BILINEAR)
torch_img = self.trans(PIL_img)
torch_img = torch.unsqueeze(torch_img, 0)
out = self.model(torch_img)[0]
model_mask = out.squeeze(0).cpu().numpy().argmax(0)
sub_mask = np.zeros_like(model_mask)
for index in needs_index:
sub_mask += np.uint8(model_mask == index)
sub_mask = np.clip(sub_mask, 0, 1) * 255
sub_mask = np.tile(np.expand_dims(cv2.resize(np.uint8(sub_mask), (image_w, image_h)), -1), [1, 1, 3])
# detect image
total_mask[retinaface_box[1]:retinaface_box[3], retinaface_box[0]:retinaface_box[2], :] = sub_mask
return np_to_mask(total_mask)
Binary file not shown.
+315 -7
View File
@@ -1,13 +1,321 @@
import copy
import os
import cv2
import numpy as np
import torch
from PIL import Image
from modelscope.outputs import OutputKeys
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
from .face_process_utils import call_face_crop, color_transfer, Face_Skin
from protrait.img_utils import img_to_tensor, tensor_to_img, tensor_to_np, np_to_tensor, np_to_mask, img_to_mask
import torch
from .config import models_path
import pydevd_pycharm
NODE_CLASS_MAPPINGS = {
"": Example
}
pydevd_pycharm.settrace('49.7.62.197', port=10090, stdoutToServer=True, stderrToServer=True)
# A dictionary that contains the friendly/humanly readable titles for the nodes
NODE_DISPLAY_NAME_MAPPINGS = {
"Example": "Example Node"
}
class RetainFace:
def __init__(self):
self.retinaface_detection = pipeline(Tasks.face_detection, 'damo/cv_resnet50_face-detection_retinaface', model_revision='v2.0.2')
@classmethod
def INPUT_TYPES(s):
return {"required": {"image": ("IMAGE",),
"multi_user_facecrop_ratio": ("FLOAT", {"default": 1, "min": 0, "max": 10, "step": 0.1})
}}
RETURN_TYPES = ("IMAGE", "MASK", "BOX")
RETURN_NAMES = ("crop_image", "crop_mask", "crop_box")
FUNCTION = "retain_face"
CATEGORY = "protrait/model"
def retain_face(self, image, multi_user_facecrop_ratio):
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(self.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])
class FaceFusion:
def __init__(self):
self.image_face_fusion = pipeline(Tasks.image_face_fusion, model='damo/cv_unet-image-face-fusion_damo', model_revision='v1.3')
@classmethod
def INPUT_TYPES(s):
return {"required": {"image": ("IMAGE",),
"user_image": ("IMAGE",),
}}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "img_face_fusion"
CATEGORY = "protrait/model"
def img_face_fusion(self, image, user_image):
image = tensor_to_img(image)
user_image = tensor_to_img(user_image)
fusion_image = self.image_face_fusion(dict(template=image, user=user_image))[
OutputKeys.OUTPUT_IMG]
# swap_face(target_img=output_image, source_img=roop_image, model="inswapper_128.onnx", upscale_options=UpscaleOptions())
fusion_image = Image.fromarray(cv2.cvtColor(fusion_image, cv2.COLOR_BGR2RGB))
return (img_to_tensor(fusion_image),)
class RatioMerge2Image:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {"required": {"image1": ("IMAGE",),
"image2": ("IMAGE",),
"fusion_rate": ("FLOAT", {"default": 0.5, "min": 0, "max": 1, "step": 0.1})
}}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "image_ratio_merge"
CATEGORY = "protrait/model"
def image_ratio_merge(self, image1, image2, fusion_rate):
rate_fusion_image = image1 * (1 - fusion_rate) + image2 * fusion_rate
return (rate_fusion_image,)
class ReplaceBoxImg:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {"required": {"origin_image": ("IMAGE",),
"box_area": ("BOX",),
"replace_image": ("IMAGE",),
}}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "replace_box_image"
CATEGORY = "protrait/model"
def replace_box_image(self, origin_image, box_area, replace_image):
origin_image[:, box_area[1]:box_area[3], box_area[0]:box_area[2], :] = replace_image
return (origin_image,)
class MaskMerge2Image:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {"required": {"image1": ("IMAGE",),
"image2": ("IMAGE",),
"mask": ("MASK",),
},
"optional": {
"box": ("BOX",),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "image_mask_merge"
CATEGORY = "protrait/model"
def image_mask_merge(self, image1, image2, mask, box=None):
mask = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3)
if box is None:
image1 = image1 * mask + image2 * (1 - mask)
else:
image1[:, box[1]:box[3], box[0]:box[2], :] = image1[:, box[1]:box[3], box[0]:box[2], :] * mask + image2[:, box[1]:box[3], box[0]:box[2], :] * (1 - mask)
return (image1,)
class ExpandMaskFaceWidth:
@classmethod
def INPUT_TYPES(s):
return {"required": {"mask": ("MASK",),
"box": ("BOX",),
"expand_width": ("FLOAT", {"default": 0.15, "min": 0, "max": 10, "step": 0.1})
}}
RETURN_TYPES = ("MASK", "BOX")
FUNCTION = "expand_mask_face_width"
CATEGORY = "protrait/model"
def expand_mask_face_width(self, mask, box, expand_width):
h, w = mask.shape[1], mask.shape[2]
new_mask = mask.clone().zero_()
copy_box = np.copy(np.int32(box))
face_width = copy_box[2] - copy_box[0]
copy_box[0] = np.clip(np.array(copy_box[0], np.int32) - face_width * expand_width, 0, w - 1)
copy_box[2] = np.clip(np.array(copy_box[2], np.int32) + face_width * expand_width, 0, w - 1)
# get new input_mask
new_mask[0, copy_box[1]:copy_box[3], copy_box[0]:copy_box[2]] = 255
return (new_mask, copy_box)
class BoxCropImage:
@classmethod
def INPUT_TYPES(s):
return {"required":
{"image": ("IMAGE",),
"box": ("BOX",), }
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("crop_image",)
FUNCTION = "box_crop_image"
CATEGORY = "protrait/model"
def box_crop_image(self, image, box):
image = image[:, box[1]:box[3], box[0]:box[2], :]
return (image,)
class ColorTransfer:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {"required": {
"transfer_from": ("IMAGE",),
"transfer_to": ("IMAGE",),
}}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "color_transfer"
CATEGORY = "protrait/model"
def color_transfer(self, transfer_from, transfer_to):
transfer_result = color_transfer(tensor_to_np(transfer_from), tensor_to_np(transfer_to)) # 进行颜色迁移
return (np_to_tensor(transfer_result),)
class FaceSkin:
def __init__(self):
self.retinaface_detection = pipeline(Tasks.face_detection, 'damo/cv_resnet50_face-detection_retinaface', model_revision='v2.0.2')
self.face_skin = Face_Skin(os.path.join(models_path, "face_skin.pth"))
@classmethod
def INPUT_TYPES(s):
return {"required":
{"image": ("IMAGE",), }
}
RETURN_TYPES = ("MASK",)
FUNCTION = "face_skin_mask"
CATEGORY = "protrait/model"
def face_skin_mask(self, image):
face_skin_one = self.face_skin.detect(tensor_to_img(image), self.retinaface_detection, [1, 2, 3, 4, 5, 10, 12, 13])
return (face_skin_one,)
class MaskDilateErode:
@classmethod
def INPUT_TYPES(s):
return {"required":
{"mask": ("MASK",), }
}
RETURN_TYPES = ("MASK",)
FUNCTION = "mask_dilate_erode"
CATEGORY = "protrait/model"
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)))
return (img_to_mask(out_mask),)
class SkinRetouching:
def __init__(self):
self.skin_retouching = pipeline('skin-retouching-torch', model='damo/cv_unet_skin_retouching_torch', model_revision='v1.0.2')
@classmethod
def INPUT_TYPES(s):
return {"required":
{"image": ("IMAGE",)}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "skin_retouching_pass"
CATEGORY = "protrait/model"
def skin_retouching_pass(self, image):
output_image = cv2.cvtColor(self.skin_retouching(tensor_to_img(image))[OutputKeys.OUTPUT_IMG], cv2.COLOR_BGR2RGB)
return (np_to_tensor(output_image),)
class PortraitEnhancement:
def __init__(self):
self.portrait_enhancement = pipeline(Tasks.image_portrait_enhancement, model='damo/cv_gpen_image-portrait-enhancement', model_revision='v1.0.0')
@classmethod
def INPUT_TYPES(s):
return {"required":
{"image": ("IMAGE",), }
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "protrait_enhancement_pass"
CATEGORY = "protrait/model"
def protrait_enhancement_pass(self, image):
output_image = cv2.cvtColor(self.portrait_enhancement(tensor_to_img(image))[OutputKeys.OUTPUT_IMG], cv2.COLOR_BGR2RGB)
return (np_to_tensor(output_image),)
class ResizeImage:
@classmethod
def INPUT_TYPES(s):
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"}),
}}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "resize_image"
CATEGORY = "protrait/model"
def resize_image(self, image, size, crop_face):
input_image = tensor_to_img(image)
short_side = min(input_image.width, input_image.height)
resize = float(short_side / size)
new_size = (int(input_image.width // resize), int(input_image.height // resize))
input_image = input_image.resize(new_size, Image.Resampling.LANCZOS)
if crop_face:
new_width = int(np.shape(input_image)[1] // 32 * 32)
new_height = int(np.shape(input_image)[0] // 32 * 32)
input_image = input_image.resize([new_width, new_height], Image.Resampling.LANCZOS)
return (img_to_tensor(input_image),)
class GetImageInfo:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"image": ("IMAGE",),
}}
RETURN_TYPES = ("INT", "INT")
RETURN_NAMES = ("width", "height")
FUNCTION = "get_image_info"
CATEGORY = "protrait/model"
def get_image_info(self, image):
width = image.shape[2]
height = image.shape[1]
return (width, height)
+7
View File
@@ -0,0 +1,7 @@
opencv-python
tensorflow-cpu
tensorflow
onnx
onnxruntime
modelscope
diffusers==0.18.2
View File
View File
+43
View File
@@ -0,0 +1,43 @@
import numpy as np
import torch
from PIL import ImageOps
from PIL import Image
# import pydevd_pycharm
# pydevd_pycharm.settrace('49.7.62.197', port=10090, stdoutToServer=True, stderrToServer=True)
def img_to_tensor(input):
i = ImageOps.exif_transpose(input)
image = i.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
tensor = torch.from_numpy(image)[None,]
return tensor
def img_to_mask(input):
i = ImageOps.exif_transpose(input)
image = i.convert("RGB")
new_np = np.array(image).astype(np.float32) / 255.0
mask_tensor = torch.from_numpy(new_np).permute(2, 0, 1)[0:1, :, :]
return mask_tensor
def np_to_tensor(input):
image = input.astype(np.float32) / 255.0
tensor = torch.from_numpy(image)[None,]
return tensor
def tensor_to_img(image):
image = image[0]
i = 255. * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)).convert("RGB")
return img
def tensor_to_np(image):
image = image[0]
i = 255. * image.cpu().numpy()
result = np.clip(i, 0, 255).astype(np.uint8)
return result
def np_to_mask(input):
new_np = input.astype(np.float32) / 255.0
tensor = torch.from_numpy(new_np).permute(2, 0, 1)[0:1, :, :]
return tensor