包含: - hair_service_sd: 主服务(换发型/换发色/生发,端口8801) - photo_service: LoRA调度+训练(端口32678) - hair_grow_service: 调试测试页(端口8888,含4个测试页) - 批量训练脚本(batch_train_hairstyles.py) - 发际线mask自动识别(hairline_mask.py,4种方案) - 手绘mask换发型(hair_swap_manual.py) - 文档:README.md + LARGE_FILES.md + docs/ 大文件(模型权重200G、训练数据123G)已排除,见 LARGE_FILES.md OSS/COS密钥已脱敏为环境变量,原文件备份在本地
148 lines
5.4 KiB
Python
148 lines
5.4 KiB
Python
import torch
|
|
import torch.nn as nn
|
|
import math
|
|
import os
|
|
import cv2
|
|
import numpy as np
|
|
from utils.DATAIMG import DATAIMG
|
|
from utils import landmark_processor
|
|
import glob
|
|
from mathlib.umeyama import umeyama
|
|
|
|
def op_name(op_name, m):
|
|
m.op_name = op_name
|
|
return m
|
|
|
|
class Flatten(nn.Module):
|
|
def __init__(self, axis=1):
|
|
super(Flatten, self).__init__()
|
|
self.axis = axis
|
|
|
|
def forward(self, x):
|
|
assert self.axis == 1
|
|
x = x.reshape(x.shape[0], -1)
|
|
return x
|
|
|
|
def flatten(name, axis=1):
|
|
return op_name(name, Flatten(axis))
|
|
|
|
def conv_relu(name, in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1):
|
|
return nn.Sequential(
|
|
op_name(name, nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, dilation, groups, True)),
|
|
op_name(name.replace('conv', 'relu'), nn.PReLU(out_channels)),
|
|
)
|
|
|
|
class BasicBlock(nn.Module):
|
|
def __init__(self, i, j, inplanes):
|
|
super(BasicBlock, self).__init__()
|
|
|
|
self.conv1 = conv_relu('conv{}_{}'.format(i, 2 * j), inplanes, inplanes, kernel_size=3, stride=1, padding=1)
|
|
self.conv2 = conv_relu('conv{}_{}'.format(i, 2 * j + 1), inplanes, inplanes, kernel_size=3, stride=1, padding=1)
|
|
|
|
def forward(self, x):
|
|
residule = x
|
|
out = self.conv1(x)
|
|
out = self.conv2(out)
|
|
ret = out + residule
|
|
return ret
|
|
|
|
class ResnetBlock(nn.Module):
|
|
def __init__(self, i, inplanes, outplanes, stride=2, n_blocks=1):
|
|
super(ResnetBlock, self).__init__()
|
|
|
|
self.conv = [conv_relu('conv{}_{}'.format(i, 1), inplanes, outplanes, kernel_size=3, stride=stride, padding=1)]
|
|
for m in range(n_blocks):
|
|
self.conv.append(BasicBlock(i, m + 1, outplanes))
|
|
self.conv = nn.Sequential(*self.conv)
|
|
|
|
def forward(self, x):
|
|
ret = self.conv(x)
|
|
return ret
|
|
|
|
class FaceRecognition(nn.Module):
|
|
def __init__(self):
|
|
super(FaceRecognition, self).__init__()
|
|
op_list = []
|
|
ch_num = [3, 32, 64, 128, 128]
|
|
strides = [2, 2, 2, 2]
|
|
n_blocks = [1, 2, 4, 1]
|
|
op_list = []
|
|
op_list += [ResnetBlock(i + 1, ch_num[i], ch_num[i + 1], strides[i], n_blocks[i]) for i in range(len(ch_num) - 1)]
|
|
op_list += [flatten('flatten', 1)]
|
|
op_list += [op_name('fc5', nn.Linear(4608, 256))]
|
|
self.features = nn.Sequential(*op_list)
|
|
|
|
model_path, _ = os.path.split(os.path.realpath(__file__))
|
|
weights = torch.load(os.path.join(model_path, 'Face_ResNet.pth'),
|
|
map_location=lambda storage, loc: storage)
|
|
self.load_state_dict(weights)
|
|
self.eval()
|
|
|
|
def forward(self, x):
|
|
features = self.features(x)
|
|
return features
|
|
|
|
class MomocvFaceRecognition(object):
|
|
def __init__(self, gpu_id=None):
|
|
self.gpu_id = gpu_id
|
|
self.device = torch.device('cuda:{}'.format(gpu_id) if gpu_id is not None else 'cpu')
|
|
self.face_recognition_net = FaceRecognition()
|
|
# self.model_path, _ = os.path.split(os.path.realpath(__file__))
|
|
# weights = torch.load(os.path.join(self.model_path, 'Face_ResNet.pth'),
|
|
# map_location=lambda storage, loc: storage)
|
|
# self.face_recognition_net.load_state_dict(weights)
|
|
self.face_recognition_net.to(self.device)
|
|
# self.face_recognition_net.eval()
|
|
|
|
def forward(self, images, landmarks):
|
|
dst_size = 90
|
|
|
|
with torch.no_grad():
|
|
input_numpy = np.zeros((len(images), 3, dst_size, dst_size), dtype=np.float32)
|
|
assert len(images) == len(landmarks)
|
|
for ix, img in enumerate(images):
|
|
landmark = landmarks[ix]
|
|
|
|
mat = landmark_processor.get_transform_mat_for_face_recognition(landmark, dst_size)[0:2]
|
|
|
|
tmp = cv2.warpAffine(img, mat, (dst_size, dst_size))
|
|
|
|
input_numpy[ix, :, :, :] = (tmp.transpose((2, 0, 1)).astype(np.float32) - 128) / 128
|
|
|
|
# cv2.imshow('tmp', tmp)
|
|
# cv2.waitKey()
|
|
|
|
in_tensor = torch.from_numpy(input_numpy)
|
|
in_tensor = in_tensor.to(self.device)
|
|
features = self.face_recognition_net(in_tensor)
|
|
features = features.cpu().numpy()
|
|
norm_factor = np.sqrt(np.sum(features ** 2, axis=1))
|
|
norm_factor = norm_factor.reshape(-1, 1)
|
|
features /= norm_factor
|
|
return features
|
|
|
|
if __name__ == '__main__':
|
|
all_jpegs = glob.glob(r'E:\deepfacelab_data\expression_dst\7201806132018061208311920180612083119\*.jpg')
|
|
for s_filename_path in all_jpegs:
|
|
img = cv2.imread(s_filename_path)
|
|
|
|
# cv2.imshow('img', img)
|
|
# cv2.waitKey()
|
|
|
|
dflpng = DATAIMG(str(s_filename_path), print_on_no_embedded_data=True)
|
|
if dflpng is None:
|
|
print('ERROR')
|
|
|
|
landmarks = dflpng.get_landmarks()
|
|
|
|
mmcv = MomocvFaceRecognition()
|
|
features = mmcv.forward([img, img], [landmarks, landmarks])
|
|
# fullyconnected1 = fullyconnected1[0]
|
|
# fullyconnected1 = (np.reshape(fullyconnected1, (2, 87)).transpose((1, 0))).astype(np.int32)
|
|
# occlusion_probe = occlusion_probe[0]
|
|
# for ix, pt in enumerate(fullyconnected1):
|
|
# cv2.circle(img, (int(pt[0]), int(pt[1])), 1, (0, 255, 0) if occlusion_probe[ix] > 0.1 else (0, 0, 255), 2)
|
|
# # cv2.putText(img, str(ix), (int(pt[0]), int(pt[1])), cv2.FONT_HERSHEY_SIMPLEX, 0.3, (0, 255, 0), 1)
|
|
# cv2.imshow('img', img)
|
|
# cv2.waitKey()
|