179 lines
8.0 KiB
Python
179 lines
8.0 KiB
Python
import cv2
|
|
import numpy as np
|
|
from modelscope.outputs import OutputKeys
|
|
from skimage import transform
|
|
|
|
from facechain.model_holder import *
|
|
from facechain.utils.convert_utils import *
|
|
|
|
def debug(*args):
|
|
print(f"==== face chain debug ====", *args)
|
|
|
|
def facechain_detect_crop(source_image_pil, face_index=0, crop_ratio=1, mode='normal', error_when_no_face=False):
|
|
det_result = get_face_detection()(source_image_pil)
|
|
mask = np.zeros_like(source_image_pil)
|
|
bboxes = det_result['boxes']
|
|
bboxes = np.array(bboxes).astype(np.int16)
|
|
keypoints = det_result['keypoints']
|
|
area = 0
|
|
# for i in range(len(bboxes)):
|
|
# bbox = bboxes[i]
|
|
# area_tmp = (bbox[2] - bbox[0]) * (bbox[3] - bbox[1])
|
|
# if area_tmp > area:
|
|
# area = area_tmp
|
|
# idx = i
|
|
try:
|
|
bbox = bboxes[face_index]
|
|
if error_when_no_face:
|
|
return source_image_pil, None, None, None
|
|
except:
|
|
raise RuntimeError('No face detected or face index error/没有检测到人脸或者人脸的索引错误')
|
|
|
|
keypoint = keypoints[face_index]
|
|
points_array = np.zeros((5, 2))
|
|
w, h = source_image_pil.size
|
|
debug(f'bbox', bbox[0], bbox[1], bbox[2], bbox[3])
|
|
debug('w = ', w, 'h = ', h)
|
|
face_w = bbox[2] - bbox[0]
|
|
face_h = bbox[3] - bbox[1]
|
|
for k in range(5):
|
|
points_array[k, 0] = keypoint[2 * k]
|
|
points_array[k, 1] = keypoint[2 * k + 1]
|
|
debug(f'mode = ', mode)
|
|
if mode == "normal":
|
|
bbox[0] = np.clip(np.array(bbox[0], np.int32) - face_w * (crop_ratio - 1) / 2, 0, w - 1)
|
|
bbox[1] = np.clip(np.array(bbox[1], np.int32) - face_h * (crop_ratio - 1) / 2, 0, h - 1)
|
|
bbox[2] = np.clip(np.array(bbox[2], np.int32) + face_w * (crop_ratio - 1) / 2, 0, w - 1)
|
|
bbox[3] = np.clip(np.array(bbox[3], np.int32) + face_h * (crop_ratio - 1) / 2, 0, h - 1)
|
|
bbox = np.array(bbox, np.int32)
|
|
mask[bbox[1]: bbox[3], bbox[0]:bbox[2]] = 1
|
|
debug('mask', mask.shape)
|
|
corp_img_pil = source_image_pil.crop(bbox)
|
|
return corp_img_pil, mask, bbox, points_array
|
|
elif mode == "square 512 width height":
|
|
np_image = image_to_np(source_image_pil)
|
|
face_ratio = 0.45
|
|
crop_l = int(max(face_h, face_w) / face_ratio / 2)
|
|
cx = int((bbox[2] + bbox[0]) / 2)
|
|
cy = int((bbox[1] + bbox[3]) / 2)
|
|
crop_up = min(cy, crop_l)
|
|
crop_bo = min(h - cy, crop_l)
|
|
crop_le = min(cx, crop_l)
|
|
crop_ri = min(w - cx, crop_l)
|
|
debug(crop_l, cx, cy, crop_up, crop_bo, crop_le, crop_ri)
|
|
inpaint_img = np.pad(np_image[cy - crop_up:cy + crop_bo, cx - crop_le:cx + crop_ri],
|
|
((crop_l - crop_up, crop_l - crop_bo), (crop_l - crop_le, crop_l - crop_ri), (0, 0)), 'constant')
|
|
inpaint_img = cv2.resize(inpaint_img, (512, 512))
|
|
inpaint_img = Image.fromarray(cv2.cvtColor(inpaint_img[:, :, ::-1], cv2.COLOR_BGR2RGB))
|
|
mask[cy - crop_up:cy + crop_bo, cx - crop_le:cx + crop_ri] = 1
|
|
return inpaint_img, mask, bbox, points_array,
|
|
else:
|
|
raise RuntimeError('模式错误')
|
|
|
|
def segment(img, ksize=0, eyeh=0, ksize1=0, include_neck=False, warp_mask=None, return_human=True):
|
|
print('ksize', ksize, "ksize1", ksize1, 'warp_mask', warp_mask)
|
|
seg_image = get_segmentation()(img)
|
|
masks = seg_image['masks']
|
|
scores = seg_image['scores']
|
|
labels = seg_image['labels']
|
|
if len(masks) == 0:
|
|
return
|
|
h, w = masks[0].shape
|
|
mask_face = np.zeros((h, w))
|
|
mask_hair = np.zeros((h, w))
|
|
mask_neck = np.zeros((h, w))
|
|
mask_cloth = np.zeros((h, w))
|
|
mask_human = np.zeros((h, w))
|
|
for i in range(len(labels)):
|
|
if scores[i] > 0.8:
|
|
if labels[i] == 'Torso-skin':
|
|
mask_neck += masks[i]
|
|
elif labels[i] == 'Face':
|
|
mask_face += masks[i]
|
|
elif labels[i] == 'Human':
|
|
mask_human += masks[i]
|
|
elif labels[i] == 'Hair':
|
|
mask_hair += masks[i]
|
|
elif labels[i] == 'UpperClothes' or labels[i] == 'Coat':
|
|
mask_cloth += masks[i]
|
|
mask_face = np.clip(mask_face, 0, 1)
|
|
mask_hair = np.clip(mask_hair, 0, 1)
|
|
mask_neck = np.clip(mask_neck, 0, 1)
|
|
mask_cloth = np.clip(mask_cloth, 0, 1)
|
|
mask_human = np.clip(mask_human, 0, 1)
|
|
soft_mask = 0
|
|
if np.sum(mask_face) > 0:
|
|
soft_mask = np.clip(mask_face, 0, 1)
|
|
if ksize1 > 0:
|
|
kernel_size1 = int(np.sqrt(np.sum(soft_mask)) * ksize1)
|
|
kernel1 = np.ones((kernel_size1, kernel_size1))
|
|
soft_mask = cv2.dilate(soft_mask, kernel1, iterations=1)
|
|
if ksize > 0:
|
|
kernel_size = int(np.sqrt(np.sum(soft_mask)) * ksize)
|
|
kernel = np.ones((kernel_size, kernel_size))
|
|
soft_mask_dilate = cv2.dilate(soft_mask, kernel, iterations=1)
|
|
if warp_mask is not None:
|
|
soft_mask_dilate = soft_mask_dilate * (np.clip(soft_mask + warp_mask[:, :, 0], 0, 1))
|
|
if eyeh > 0:
|
|
soft_mask = np.concatenate((soft_mask[:eyeh], soft_mask_dilate[eyeh:]), axis=0)
|
|
else:
|
|
soft_mask = soft_mask_dilate
|
|
else:
|
|
if ksize1 > 0:
|
|
kernel_size1 = int(np.sqrt(np.sum(soft_mask)) * ksize1)
|
|
kernel1 = np.ones((kernel_size1, kernel_size1))
|
|
soft_mask = cv2.dilate(mask_face, kernel1, iterations=1)
|
|
else:
|
|
soft_mask = mask_face
|
|
if include_neck:
|
|
soft_mask = np.clip(soft_mask + mask_neck, 0, 1)
|
|
np_image = image_to_np(img)
|
|
img.crop()
|
|
seg_image_np = np_image * soft_mask[:, :, None]
|
|
seg_image = Image.fromarray(seg_image_np.astype(np.uint8))
|
|
if return_human:
|
|
mask_human = cv2.GaussianBlur(mask_human, (21, 21), 0) * mask_human
|
|
return seg_image, soft_mask, mask_human
|
|
else:
|
|
# 返回一个是PIL,一个是np的二维数组
|
|
return seg_image, soft_mask,
|
|
|
|
def face_fusion(image, fusion_image):
|
|
cv_fusion_result = get_image_face_fusion()(dict(template=image, user=fusion_image))[OutputKeys.OUTPUT_IMG]
|
|
np_fusion_result = cv2.cvtColor(cv_fusion_result, cv2.COLOR_BGR2RGB)
|
|
debug("cv_fusion_result", cv_fusion_result.shape)
|
|
return np_fusion_result
|
|
|
|
def face_fusing_seg_replace(image, replace_image):
|
|
np_fusion_result = face_fusion(image, replace_image)
|
|
_, face_mask, _ = segment(image, ksize=0.1)
|
|
cv_replace_result = (np_fusion_result * face_mask[:, :, None] + np.array(image) * (1 - face_mask[:, :, None])).astype(np.uint8)
|
|
debug("cv_replace_result", cv_replace_result.shape)
|
|
return np_fusion_result, cv_replace_result
|
|
|
|
def crop_and_paste(Source_image, Source_image_mask, Target_image, Source_Five_Point, Target_Five_Point, Source_box, use_warp=True):
|
|
debug(f"crop and paste", Source_image, Source_image_mask, Target_image, Source_Five_Point, Target_Five_Point, Source_box)
|
|
if use_warp:
|
|
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()
|
|
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)
|
|
debug('target shape', np.float32(Target_image).shape)
|
|
debug('warped shape', np.float32(warped).shape)
|
|
output = mask * np.float32(Target_image) + (1 - mask) * np.float32(warped)
|
|
else:
|
|
mask = np.float32(np.array(Source_image_mask) == 0)
|
|
output = mask * np.float32(Target_image) + (1 - mask) * np.float32(Source_image)
|
|
return output, mask
|