mirror of
https://github.com/modelscope/modelscope.git
synced 2026-07-13 22:08:46 +02:00
add face_reconstruction model
Link: https://code.alibaba-inc.com/Ali-MaaS/MaaS-lib/codereview/11527525
This commit is contained in:
3
data/test/images/face_reconstruction.jpg
Normal file
3
data/test/images/face_reconstruction.jpg
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b3a4f864cee22265fdbb8008719e0e2e36235bd4bb2fdfbc9278b0b964e86eff
|
||||
size 1921140
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 130 B |
@@ -268,6 +268,7 @@ class Pipelines(object):
|
||||
image_object_detection_auto = 'yolox_image-object-detection-auto'
|
||||
hand_detection = 'yolox-pai_hand-detection'
|
||||
skin_retouching = 'unet-skin-retouching'
|
||||
face_reconstruction = 'resnet50-face-reconstruction'
|
||||
tinynas_classification = 'tinynas-classification'
|
||||
easyrobust_classification = 'easyrobust-classification'
|
||||
tinynas_detection = 'tinynas-detection'
|
||||
|
||||
@@ -4,19 +4,19 @@
|
||||
from . import (action_recognition, animal_recognition, body_2d_keypoints,
|
||||
body_3d_keypoints, cartoon, cmdssl_video_embedding,
|
||||
crowd_counting, face_2d_keypoints, face_detection,
|
||||
face_generation, human_wholebody_keypoint, image_classification,
|
||||
image_color_enhance, image_colorization, image_defrcn_fewshot,
|
||||
image_denoise, image_inpainting, image_instance_segmentation,
|
||||
image_matching, image_mvs_depth_estimation,
|
||||
image_panoptic_segmentation, image_portrait_enhancement,
|
||||
image_probing_model, image_quality_assessment_mos,
|
||||
image_reid_person, image_restoration,
|
||||
image_semantic_segmentation, image_to_image_generation,
|
||||
image_to_image_translation, language_guided_video_summarization,
|
||||
movie_scene_segmentation, object_detection,
|
||||
panorama_depth_estimation, pointcloud_sceneflow_estimation,
|
||||
product_retrieval_embedding, realtime_object_detection,
|
||||
referring_video_object_segmentation,
|
||||
face_generation, face_reconstruction, human_wholebody_keypoint,
|
||||
image_classification, image_color_enhance, image_colorization,
|
||||
image_defrcn_fewshot, image_denoise, image_inpainting,
|
||||
image_instance_segmentation, image_matching,
|
||||
image_mvs_depth_estimation, image_panoptic_segmentation,
|
||||
image_portrait_enhancement, image_probing_model,
|
||||
image_quality_assessment_mos, image_reid_person,
|
||||
image_restoration, image_semantic_segmentation,
|
||||
image_to_image_generation, image_to_image_translation,
|
||||
language_guided_video_summarization, movie_scene_segmentation,
|
||||
object_detection, panorama_depth_estimation,
|
||||
pointcloud_sceneflow_estimation, product_retrieval_embedding,
|
||||
realtime_object_detection, referring_video_object_segmentation,
|
||||
robust_image_classification, salient_detection,
|
||||
shop_segmentation, super_resolution, video_frame_interpolation,
|
||||
video_object_segmentation, video_panoptic_segmentation,
|
||||
|
||||
591
modelscope/models/cv/face_reconstruction/models/bfm.py
Normal file
591
modelscope/models/cv/face_reconstruction/models/bfm.py
Normal file
@@ -0,0 +1,591 @@
|
||||
# Part of the implementation is borrowed and modified from Deep3DFaceRecon_pytorch,
|
||||
# publicly available at https://github.com/sicxu/Deep3DFaceRecon_pytorch
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from scipy.io import loadmat
|
||||
|
||||
from ..utils import read_obj, transferBFM09
|
||||
|
||||
|
||||
def perspective_projection(focal, center):
|
||||
# return p.T (N, 3) @ (3, 3)
|
||||
return np.array([focal, 0, center, 0, focal, center, 0, 0,
|
||||
1]).reshape([3, 3]).astype(np.float32).transpose()
|
||||
|
||||
|
||||
class SH:
|
||||
|
||||
def __init__(self):
|
||||
self.a = [np.pi, 2 * np.pi / np.sqrt(3.), 2 * np.pi / np.sqrt(8.)]
|
||||
self.c = [
|
||||
1 / np.sqrt(4 * np.pi),
|
||||
np.sqrt(3.) / np.sqrt(4 * np.pi),
|
||||
3 * np.sqrt(5.) / np.sqrt(12 * np.pi)
|
||||
]
|
||||
|
||||
|
||||
class ParametricFaceModel:
|
||||
|
||||
def __init__(self,
|
||||
bfm_folder='./asset/BFM',
|
||||
recenter=True,
|
||||
camera_distance=10.,
|
||||
init_lit=np.array([0.8, 0, 0, 0, 0, 0, 0, 0, 0]),
|
||||
focal=1015.,
|
||||
center=112.,
|
||||
is_train=True,
|
||||
default_name='BFM_model_front.mat'):
|
||||
|
||||
if not os.path.isfile(os.path.join(bfm_folder, default_name)):
|
||||
transferBFM09(bfm_folder)
|
||||
model = loadmat(os.path.join(bfm_folder, default_name))
|
||||
# mean face shape. [3*N,1]
|
||||
self.mean_shape = model['meanshape'].astype(np.float32)
|
||||
|
||||
# identity basis. [3*N,80]
|
||||
self.id_base = model['idBase'].astype(np.float32)
|
||||
|
||||
# expression basis. [3*N,64]
|
||||
self.exp_base = model['exBase'].astype(np.float32)
|
||||
|
||||
# mean face texture. [3*N,1] (0-255)
|
||||
self.mean_tex = model['meantex'].astype(np.float32)
|
||||
|
||||
# texture basis. [3*N,80]
|
||||
self.tex_base = model['texBase'].astype(np.float32)
|
||||
|
||||
# face indices for each vertex that lies in. starts from 0. [N,8]
|
||||
self.point_buf = model['point_buf'].astype(np.int64) - 1
|
||||
|
||||
# vertex indices for each face. starts from 0. [F,3]
|
||||
self.face_buf = model['tri'].astype(np.int64) - 1
|
||||
|
||||
# vertex indices for 68 landmarks. starts from 0. [68,1]
|
||||
self.keypoints = np.squeeze(model['keypoints']).astype(np.int64) - 1
|
||||
|
||||
self.mean_shape_ori = model['meanshape_ori'].astype(np.float32)
|
||||
self.bfm_keep_inds = model['bfm_keep_inds'][0]
|
||||
self.nose_reduced_part = model['nose_reduced_part'].reshape(
|
||||
(1, -1)) - self.mean_shape
|
||||
self.nonlinear_UVs = model['nonlinear_UVs']
|
||||
|
||||
if default_name == 'head_model_for_maas.mat':
|
||||
self.ours_hair_area_inds = model['hair_area_inds'][0]
|
||||
|
||||
self.mean_tex = self.mean_tex.reshape(1, -1, 3)
|
||||
mean_tex_keep = self.mean_tex[:, self.bfm_keep_inds]
|
||||
self.mean_tex[:, :len(self.bfm_keep_inds)] = mean_tex_keep
|
||||
self.mean_tex[:,
|
||||
len(self.bfm_keep_inds):] = np.array([200, 146,
|
||||
118])[None,
|
||||
None]
|
||||
self.mean_tex[:, self.ours_hair_area_inds] = 40.0
|
||||
self.mean_tex = self.mean_tex.reshape(1, -1)
|
||||
self.mean_tex = np.ascontiguousarray(self.mean_tex)
|
||||
|
||||
self.tex_base = self.tex_base.reshape(-1, 3, 80)
|
||||
tex_base_keep = self.tex_base[self.bfm_keep_inds]
|
||||
self.tex_base[:len(self.bfm_keep_inds)] = tex_base_keep
|
||||
self.tex_base[len(self.bfm_keep_inds):] = 0.0
|
||||
self.tex_base = self.tex_base.reshape(-1, 80)
|
||||
self.tex_base = np.ascontiguousarray(self.tex_base)
|
||||
|
||||
self.point_buf = self.point_buf[:, :8] + 1
|
||||
|
||||
self.neck_adjust_part = model['neck_adjust_part'].reshape(
|
||||
(1, -1)) - self.mean_shape
|
||||
self.eyes_adjust_part = model['eyes_adjust_part'].reshape(
|
||||
(1, -1)) - self.mean_shape
|
||||
|
||||
self.eye_corner_inds = model['eye_corner_inds'][0]
|
||||
self.eye_corner_lines = model['eye_corner_lines']
|
||||
|
||||
if recenter:
|
||||
mean_shape = self.mean_shape.reshape([-1, 3])
|
||||
mean_shape_ori = self.mean_shape_ori.reshape([-1, 3])
|
||||
mean_shape = mean_shape - np.mean(
|
||||
mean_shape_ori[:35709, ...], axis=0, keepdims=True)
|
||||
self.mean_shape = mean_shape.reshape([-1, 1])
|
||||
|
||||
self.center = center
|
||||
self.persc_proj = perspective_projection(focal, self.center)
|
||||
self.device = 'cpu'
|
||||
self.camera_distance = camera_distance
|
||||
self.SH = SH()
|
||||
self.init_lit = init_lit.reshape([1, 1, -1]).astype(np.float32)
|
||||
|
||||
def to(self, device):
|
||||
self.device = device
|
||||
for key, value in self.__dict__.items():
|
||||
if type(value).__module__ == np.__name__:
|
||||
setattr(self, key, torch.tensor(value).to(device))
|
||||
|
||||
def compute_shape(self,
|
||||
id_coeff,
|
||||
exp_coeff,
|
||||
nose_coeff=0.0,
|
||||
neck_coeff=0.0,
|
||||
eyes_coeff=0.0):
|
||||
"""
|
||||
Return:
|
||||
face_shape -- torch.tensor, size (B, N, 3)
|
||||
|
||||
Parameters:
|
||||
id_coeff -- torch.tensor, size (B, 80), identity coeffs
|
||||
exp_coeff -- torch.tensor, size (B, 64), expression coeffs
|
||||
"""
|
||||
batch_size = id_coeff.shape[0]
|
||||
id_part = torch.einsum('ij,aj->ai', self.id_base, id_coeff)
|
||||
exp_part = torch.einsum('ij,aj->ai', self.exp_base, exp_coeff)
|
||||
face_shape = id_part + exp_part + self.mean_shape.reshape([1, -1])
|
||||
|
||||
if nose_coeff != 0:
|
||||
face_shape = face_shape + nose_coeff * self.nose_reduced_part
|
||||
if neck_coeff != 0:
|
||||
face_shape = face_shape + neck_coeff * self.neck_adjust_part
|
||||
if eyes_coeff != 0 and self.eyes_adjust_part is not None:
|
||||
face_shape = face_shape + eyes_coeff * self.eyes_adjust_part
|
||||
|
||||
return face_shape.reshape([batch_size, -1, 3])
|
||||
|
||||
def compute_texture(self, tex_coeff, normalize=True):
|
||||
"""
|
||||
Return:
|
||||
face_texture -- torch.tensor, size (B, N, 3), in RGB order, range (0, 1.)
|
||||
|
||||
Parameters:
|
||||
tex_coeff -- torch.tensor, size (B, 80)
|
||||
"""
|
||||
batch_size = tex_coeff.shape[0]
|
||||
face_texture = torch.einsum('ij,aj->ai', self.tex_base,
|
||||
tex_coeff) + self.mean_tex
|
||||
if normalize:
|
||||
face_texture = face_texture / 255.
|
||||
return face_texture.reshape([batch_size, -1, 3])
|
||||
|
||||
def compute_norm(self, face_shape):
|
||||
"""
|
||||
Return:
|
||||
vertex_norm -- torch.tensor, size (B, N, 3)
|
||||
|
||||
Parameters:
|
||||
face_shape -- torch.tensor, size (B, N, 3)
|
||||
"""
|
||||
|
||||
v1 = face_shape[:, self.face_buf[:, 0]]
|
||||
v2 = face_shape[:, self.face_buf[:, 1]]
|
||||
v3 = face_shape[:, self.face_buf[:, 2]]
|
||||
e1 = v1 - v2
|
||||
e2 = v2 - v3
|
||||
face_norm = torch.cross(e1, e2, dim=-1)
|
||||
face_norm = F.normalize(face_norm, dim=-1, p=2)
|
||||
face_norm = torch.cat(
|
||||
[face_norm,
|
||||
torch.zeros(face_norm.shape[0], 1, 3).to(self.device)],
|
||||
dim=1)
|
||||
|
||||
vertex_norm = torch.sum(face_norm[:, self.point_buf], dim=2)
|
||||
vertex_norm = F.normalize(vertex_norm, dim=-1, p=2)
|
||||
return vertex_norm
|
||||
|
||||
def compute_color(self, face_texture, face_norm, gamma):
|
||||
"""
|
||||
Return:
|
||||
face_color -- torch.tensor, size (B, N, 3), range (0, 1.)
|
||||
|
||||
Parameters:
|
||||
face_texture -- torch.tensor, size (B, N, 3), from texture model, range (0, 1.)
|
||||
face_norm -- torch.tensor, size (B, N, 3), rotated face normal
|
||||
gamma -- torch.tensor, size (B, 27), SH coeffs
|
||||
"""
|
||||
batch_size = gamma.shape[0]
|
||||
a, c = self.SH.a, self.SH.c
|
||||
gamma = gamma.reshape([batch_size, 3, 9])
|
||||
gamma = gamma + self.init_lit
|
||||
gamma = gamma.permute(0, 2, 1)
|
||||
|
||||
y1 = a[0] * c[0] * torch.ones_like(face_norm[..., :1]).to(self.device)
|
||||
y2 = -a[1] * c[1] * face_norm[..., 1:2]
|
||||
y3 = a[1] * c[1] * face_norm[..., 2:]
|
||||
y4 = -a[1] * c[1] * face_norm[..., :1]
|
||||
y5 = a[2] * c[2] * face_norm[..., :1] * face_norm[..., 1:2]
|
||||
y6 = -a[2] * c[2] * face_norm[..., 1:2] * face_norm[..., 2:]
|
||||
y7 = 0.5 * a[2] * c[2] / np.sqrt(3.) * (3 * face_norm[..., 2:]**2 - 1)
|
||||
y8 = -a[2] * c[2] * face_norm[..., :1] * face_norm[..., 2:]
|
||||
y9 = 0.5 * a[2] * c[2] * (
|
||||
face_norm[..., :1]**2 - face_norm[..., 1:2]**2)
|
||||
Y = torch.cat([y1, y2, y3, y4, y5, y6, y7, y8, y9], dim=-1)
|
||||
r = Y @ gamma[..., :1]
|
||||
g = Y @ gamma[..., 1:2]
|
||||
b = Y @ gamma[..., 2:]
|
||||
face_color = torch.cat([r, g, b], dim=-1) * face_texture
|
||||
return face_color
|
||||
|
||||
def compute_rotation(self, angles):
|
||||
"""
|
||||
Return:
|
||||
rot -- torch.tensor, size (B, 3, 3) pts @ trans_mat
|
||||
|
||||
Parameters:
|
||||
angles -- torch.tensor, size (B, 3), radian
|
||||
"""
|
||||
|
||||
batch_size = angles.shape[0]
|
||||
ones = torch.ones([batch_size, 1]).to(self.device)
|
||||
zeros = torch.zeros([batch_size, 1]).to(self.device)
|
||||
x, y, z = angles[:, :1], angles[:, 1:2], angles[:, 2:],
|
||||
|
||||
value_list = [
|
||||
ones, zeros, zeros, zeros,
|
||||
torch.cos(x), -torch.sin(x), zeros,
|
||||
torch.sin(x),
|
||||
torch.cos(x)
|
||||
]
|
||||
rot_x = torch.cat(value_list, dim=1).reshape([batch_size, 3, 3])
|
||||
|
||||
value_list = [
|
||||
torch.cos(y), zeros,
|
||||
torch.sin(y), zeros, ones, zeros, -torch.sin(y), zeros,
|
||||
torch.cos(y)
|
||||
]
|
||||
rot_y = torch.cat(value_list, dim=1).reshape([batch_size, 3, 3])
|
||||
|
||||
value_list = [
|
||||
torch.cos(z), -torch.sin(z), zeros,
|
||||
torch.sin(z),
|
||||
torch.cos(z), zeros, zeros, zeros, ones
|
||||
]
|
||||
rot_z = torch.cat(value_list, dim=1).reshape([batch_size, 3, 3])
|
||||
|
||||
rot = rot_z @ rot_y @ rot_x
|
||||
return rot.permute(0, 2, 1)
|
||||
|
||||
def to_camera(self, face_shape):
|
||||
face_shape[..., -1] = self.camera_distance - face_shape[..., -1]
|
||||
return face_shape
|
||||
|
||||
def to_image(self, face_shape):
|
||||
"""
|
||||
Return:
|
||||
face_proj -- torch.tensor, size (B, N, 2), y direction is opposite to v direction
|
||||
|
||||
Parameters:
|
||||
face_shape -- torch.tensor, size (B, N, 3)
|
||||
"""
|
||||
# to image_plane
|
||||
face_proj = face_shape @ self.persc_proj
|
||||
face_proj = face_proj[..., :2] / face_proj[..., 2:]
|
||||
|
||||
return face_proj
|
||||
|
||||
def transform(self, face_shape, rot, trans):
|
||||
"""
|
||||
Return:
|
||||
face_shape -- torch.tensor, size (B, N, 3) pts @ rot + trans
|
||||
|
||||
Parameters:
|
||||
face_shape -- torch.tensor, size (B, N, 3)
|
||||
rot -- torch.tensor, size (B, 3, 3)
|
||||
trans -- torch.tensor, size (B, 3)
|
||||
"""
|
||||
return face_shape @ rot + trans.unsqueeze(1)
|
||||
|
||||
def get_landmarks(self, face_proj):
|
||||
"""
|
||||
Return:
|
||||
face_lms -- torch.tensor, size (B, 68, 2)
|
||||
|
||||
Parameters:
|
||||
face_proj -- torch.tensor, size (B, N, 2)
|
||||
"""
|
||||
return face_proj[:, self.keypoints]
|
||||
|
||||
def split_coeff(self, coeffs):
|
||||
"""
|
||||
Return:
|
||||
coeffs_dict -- a dict of torch.tensors
|
||||
|
||||
Parameters:
|
||||
coeffs -- torch.tensor, size (B, 256)
|
||||
"""
|
||||
if type(coeffs) == dict and 'id' in coeffs:
|
||||
return coeffs
|
||||
|
||||
id_coeffs = coeffs[:, :80]
|
||||
exp_coeffs = coeffs[:, 80:144]
|
||||
tex_coeffs = coeffs[:, 144:224]
|
||||
angles = coeffs[:, 224:227]
|
||||
gammas = coeffs[:, 227:254]
|
||||
translations = coeffs[:, 254:]
|
||||
return {
|
||||
'id': id_coeffs,
|
||||
'exp': exp_coeffs,
|
||||
'tex': tex_coeffs,
|
||||
'angle': angles,
|
||||
'gamma': gammas,
|
||||
'trans': translations
|
||||
}
|
||||
|
||||
def merge_coeff(self, coeffs):
|
||||
"""
|
||||
Return:
|
||||
coeffs_dict -- a dict of torch.tensors
|
||||
|
||||
Parameters:
|
||||
coeffs -- torch.tensor, size (B, 256)
|
||||
"""
|
||||
names = ['id', 'exp', 'tex', 'angle', 'gamma', 'trans']
|
||||
coeffs_merge = []
|
||||
for name in names:
|
||||
coeffs_merge.append(coeffs[name].detach())
|
||||
coeffs_merge = torch.cat(coeffs_merge, dim=1)
|
||||
|
||||
return coeffs_merge
|
||||
|
||||
def compute_for_render(self, coeffs, coeffs_mvs=None):
|
||||
"""
|
||||
Return:
|
||||
face_vertex -- torch.tensor, size (B, N, 3), in camera coordinate
|
||||
face_color -- torch.tensor, size (B, N, 3), in RGB order
|
||||
landmark -- torch.tensor, size (B, 68, 2), y direction is opposite to v direction
|
||||
Parameters:
|
||||
coeffs -- torch.tensor, size (B, 257)
|
||||
"""
|
||||
if type(coeffs) == dict:
|
||||
coef_dict = coeffs
|
||||
elif type(coeffs) == torch.Tensor:
|
||||
coef_dict = self.split_coeff(coeffs)
|
||||
|
||||
face_shape = self.compute_shape(
|
||||
coef_dict['id'], coef_dict['exp'], nose_coeff=0.4, neck_coeff=0.6)
|
||||
|
||||
rotation = self.compute_rotation(coef_dict['angle'])
|
||||
|
||||
face_shape_transformed = self.transform(face_shape, rotation,
|
||||
coef_dict['trans'])
|
||||
face_vertex = self.to_camera(face_shape_transformed)
|
||||
face_vertex_ori = self.to_camera(face_shape)
|
||||
|
||||
face_proj = self.to_image(face_vertex)
|
||||
landmark = self.get_landmarks(face_proj)
|
||||
|
||||
face_texture = self.compute_texture(coef_dict['tex'])
|
||||
face_norm = self.compute_norm(face_shape)
|
||||
face_norm_roted = face_norm @ rotation
|
||||
face_color = self.compute_color(face_texture, face_norm_roted,
|
||||
coef_dict['gamma'])
|
||||
|
||||
if coeffs_mvs is not None:
|
||||
mvs_face_shape = self.compute_shape(coeffs_mvs['id'],
|
||||
coeffs_mvs['exp'])
|
||||
|
||||
mvs_face_shape_transformed = self.transform(
|
||||
mvs_face_shape, rotation, coef_dict['trans'])
|
||||
mvs_face_vertex = self.to_camera(mvs_face_shape_transformed)
|
||||
return face_vertex, face_texture, face_color, landmark, mvs_face_vertex
|
||||
else:
|
||||
return face_vertex, face_texture, face_color, landmark, face_vertex_ori
|
||||
|
||||
def reverse_recenter(self, face_shape):
|
||||
batch_size = face_shape.shape[0]
|
||||
face_shape = face_shape.reshape([-1, 3])
|
||||
mean_shape_ori = self.mean_shape_ori.reshape([-1, 3])
|
||||
face_shape = face_shape + torch.mean(
|
||||
mean_shape_ori[:35709, ...], dim=0, keepdim=True)
|
||||
face_shape = face_shape.reshape([batch_size, -1, 3])
|
||||
return face_shape
|
||||
|
||||
def add_nonlinear_offset_eyes(self, face_shape, shape_offset):
|
||||
assert face_shape.shape[0] == 1 and shape_offset.shape[0] == 1
|
||||
face_shape = face_shape[0]
|
||||
shape_offset = shape_offset[0]
|
||||
|
||||
corner_inds = self.eye_corner_inds
|
||||
lines = self.eye_corner_lines
|
||||
|
||||
corner_shape = face_shape[-625:, :]
|
||||
corner_offset = shape_offset[corner_inds]
|
||||
for i in range(len(lines)):
|
||||
corner_shape[lines[i]] += corner_offset[i][None, ...]
|
||||
face_shape[-625:, :] = corner_shape
|
||||
|
||||
l_eye_landmarks = [11540, 11541]
|
||||
r_eye_landmarks = [4271, 4272]
|
||||
|
||||
l_eye_offset = torch.mean(
|
||||
shape_offset[l_eye_landmarks], dim=0, keepdim=True)
|
||||
face_shape[37082:37082 + 609] += l_eye_offset
|
||||
|
||||
r_eye_offset = torch.mean(
|
||||
shape_offset[r_eye_landmarks], dim=0, keepdim=True)
|
||||
face_shape[37082 + 609:37082 + 609 + 608] += r_eye_offset
|
||||
|
||||
face_shape = face_shape[None, ...]
|
||||
|
||||
return face_shape
|
||||
|
||||
def add_nonlinear_offset(self, face_shape, shape_offset_uv, UVs):
|
||||
"""
|
||||
|
||||
Args:
|
||||
face_shape: torch.tensor, size (1, N, 3)
|
||||
shape_offset_uv: torch.tensor, size (1, h, w, 3)
|
||||
UVs: torch.tensor, size (N, 2)
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
assert face_shape.shape[0] == 1 and shape_offset_uv.shape[0] == 1
|
||||
face_shape = face_shape[0]
|
||||
shape_offset_uv = shape_offset_uv[0]
|
||||
|
||||
h, w = shape_offset_uv.shape[:2]
|
||||
UVs_coords = UVs.clone()
|
||||
UVs_coords[:, 0] *= w
|
||||
UVs_coords[:, 1] *= h
|
||||
UVs_coords_int = torch.floor(UVs_coords)
|
||||
UVs_coords_float = UVs_coords - UVs_coords_int
|
||||
UVs_coords_int = UVs_coords_int.long()
|
||||
|
||||
shape_lt = shape_offset_uv[(h - 1
|
||||
- UVs_coords_int[:, 1]).clamp(0, h - 1),
|
||||
UVs_coords_int[:, 0].clamp(0, w - 1)]
|
||||
shape_lb = shape_offset_uv[(h - UVs_coords_int[:, 1]).clamp(0, h - 1),
|
||||
UVs_coords_int[:, 0].clamp(0, w - 1)]
|
||||
shape_rt = shape_offset_uv[(h - 1
|
||||
- UVs_coords_int[:, 1]).clamp(0, h - 1),
|
||||
(UVs_coords_int[:, 0] + 1).clamp(0, w - 1)]
|
||||
shape_rb = shape_offset_uv[(h - UVs_coords_int[:, 1]).clamp(0, h - 1),
|
||||
(UVs_coords_int[:, 0] + 1).clamp(0, w - 1)]
|
||||
|
||||
value1 = shape_lt * (
|
||||
1 - UVs_coords_float[:, :1]) * UVs_coords_float[:, 1:]
|
||||
value2 = shape_lb * (1 - UVs_coords_float[:, :1]) * (
|
||||
1 - UVs_coords_float[:, 1:])
|
||||
value3 = shape_rt * UVs_coords_float[:, :1] * UVs_coords_float[:, 1:]
|
||||
value4 = shape_rb * UVs_coords_float[:, :1] * (
|
||||
1 - UVs_coords_float[:, 1:])
|
||||
offset_shape = value1 + value2 + value3 + value4 # (N, 3)
|
||||
|
||||
face_shape = (face_shape + offset_shape)[None, ...]
|
||||
|
||||
return face_shape, offset_shape[None, ...]
|
||||
|
||||
def compute_for_render_train_nonlinear(self,
|
||||
coeffs,
|
||||
shape_offset_uv,
|
||||
tex_offset_uv,
|
||||
UVs,
|
||||
reverse_recenter=True):
|
||||
if type(coeffs) == dict:
|
||||
coef_dict = coeffs
|
||||
elif type(coeffs) == torch.Tensor:
|
||||
coef_dict = self.split_coeff(coeffs)
|
||||
|
||||
face_shape = self.compute_shape(coef_dict['id'],
|
||||
coef_dict['exp']) # (1, n, 3)
|
||||
if reverse_recenter:
|
||||
face_shape_ori_noRecenter = self.reverse_recenter(
|
||||
face_shape.clone())
|
||||
else:
|
||||
face_shape_ori_noRecenter = face_shape.clone()
|
||||
face_vertex_ori = self.to_camera(face_shape_ori_noRecenter)
|
||||
|
||||
face_shape, shape_offset = self.add_nonlinear_offset(
|
||||
face_shape, shape_offset_uv, UVs[:35709, :]) # (1, n, 3)
|
||||
if reverse_recenter:
|
||||
face_shape_offset_noRecenter = self.reverse_recenter(
|
||||
face_shape.clone())
|
||||
else:
|
||||
face_shape_offset_noRecenter = face_shape.clone()
|
||||
face_vertex_offset = self.to_camera(face_shape_offset_noRecenter)
|
||||
|
||||
rotation = self.compute_rotation(coef_dict['angle'])
|
||||
|
||||
face_shape_transformed = self.transform(face_shape, rotation,
|
||||
coef_dict['trans'])
|
||||
face_vertex = self.to_camera(face_shape_transformed)
|
||||
|
||||
face_proj = self.to_image(face_vertex)
|
||||
landmark = self.get_landmarks(face_proj)
|
||||
|
||||
face_texture = self.compute_texture(coef_dict['tex']) # (1, n, 3)
|
||||
face_texture, texture_offset = self.add_nonlinear_offset(
|
||||
face_texture, tex_offset_uv, UVs[:35709, :])
|
||||
face_norm = self.compute_norm(face_shape)
|
||||
face_norm_roted = face_norm @ rotation
|
||||
face_color = self.compute_color(face_texture, face_norm_roted,
|
||||
coef_dict['gamma'])
|
||||
|
||||
return face_vertex, face_texture, face_color, landmark, face_vertex_ori, face_vertex_offset, face_proj
|
||||
|
||||
def compute_for_render_nonlinear_full(self,
|
||||
coeffs,
|
||||
shape_offset_uv,
|
||||
UVs,
|
||||
nose_coeff=0.0,
|
||||
eyes_coeff=0.0):
|
||||
if type(coeffs) == dict:
|
||||
coef_dict = coeffs
|
||||
elif type(coeffs) == torch.Tensor:
|
||||
coef_dict = self.split_coeff(coeffs)
|
||||
|
||||
face_shape = self.compute_shape(
|
||||
coef_dict['id'],
|
||||
coef_dict['exp'],
|
||||
nose_coeff=nose_coeff,
|
||||
neck_coeff=0.6,
|
||||
eyes_coeff=eyes_coeff) # (1, n, 3)
|
||||
face_vertex_ori = self.to_camera(face_shape.clone())
|
||||
|
||||
face_shape[:, :35241, :], shape_offset = self.add_nonlinear_offset(
|
||||
face_shape[:, :35241, :], shape_offset_uv,
|
||||
UVs[:35709, :][self.bfm_keep_inds])
|
||||
face_shape = self.add_nonlinear_offset_eyes(face_shape, shape_offset)
|
||||
face_shape_noRecenter = self.reverse_recenter(face_shape.clone())
|
||||
face_vertex_offset = self.to_camera(face_shape_noRecenter)
|
||||
|
||||
rotation = self.compute_rotation(coef_dict['angle'])
|
||||
|
||||
face_shape_transformed = self.transform(face_shape, rotation,
|
||||
coef_dict['trans'])
|
||||
face_vertex = self.to_camera(face_shape_transformed)
|
||||
|
||||
return face_vertex, face_vertex_ori, face_vertex_offset
|
||||
|
||||
def compute_for_render_train(self, coeffs):
|
||||
"""
|
||||
Return:
|
||||
face_vertex -- torch.tensor, size (B, N, 3), in camera coordinate
|
||||
face_color -- torch.tensor, size (B, N, 3), in RGB order
|
||||
landmark -- torch.tensor, size (B, 68, 2), y direction is opposite to v direction
|
||||
Parameters:
|
||||
coeffs -- torch.tensor, size (B, 257)
|
||||
"""
|
||||
if type(coeffs) == dict:
|
||||
coef_dict = coeffs
|
||||
elif type(coeffs) == torch.Tensor:
|
||||
coef_dict = self.split_coeff(coeffs)
|
||||
|
||||
face_shape = self.compute_shape(coef_dict['id'], coef_dict['exp'])
|
||||
uv_geometry = self.render.world2uv(face_shape)
|
||||
|
||||
rotation = self.compute_rotation(coef_dict['angle'])
|
||||
|
||||
face_shape_transformed = self.transform(face_shape, rotation,
|
||||
coef_dict['trans'])
|
||||
face_vertex = self.to_camera(face_shape_transformed)
|
||||
|
||||
face_proj = self.to_image(face_vertex)
|
||||
landmark = self.get_landmarks(face_proj)
|
||||
|
||||
face_texture = self.compute_texture(coef_dict['tex'])
|
||||
face_norm = self.compute_norm(face_shape)
|
||||
face_norm_roted = face_norm @ rotation
|
||||
face_color = self.compute_color(face_texture, face_norm_roted,
|
||||
coef_dict['gamma'])
|
||||
|
||||
return face_vertex, face_texture, face_color, landmark, uv_geometry
|
||||
@@ -0,0 +1,91 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from .nets.large_base_lmks_net import LargeBaseLmksNet
|
||||
|
||||
BASE_LANDMARK_NUM = 106
|
||||
INPUT_SIZE = 224
|
||||
ENLARGE_RATIO = 1.35
|
||||
|
||||
|
||||
class LargeBaseLmkInfer:
|
||||
|
||||
@staticmethod
|
||||
def model_preload(model_path, use_gpu=True):
|
||||
model = LargeBaseLmksNet(infer=False)
|
||||
# using gpu
|
||||
if use_gpu:
|
||||
model = model.cuda()
|
||||
|
||||
checkpoint = []
|
||||
if use_gpu:
|
||||
checkpoint = torch.load(model_path, map_location='cuda')
|
||||
else:
|
||||
checkpoint = torch.load(model_path, map_location='cpu')
|
||||
|
||||
model.load_state_dict(
|
||||
{
|
||||
k.replace('module.', ''): v
|
||||
for k, v in checkpoint['state_dict'].items()
|
||||
},
|
||||
strict=False)
|
||||
model.eval()
|
||||
return model
|
||||
|
||||
@staticmethod
|
||||
def process_img(model, image, use_gpu=True):
|
||||
img_resize = image
|
||||
|
||||
img_resize = (img_resize
|
||||
- [103.94, 116.78, 123.68]) / 255.0 # important
|
||||
img_resize = img_resize.transpose([2, 0, 1])
|
||||
|
||||
if use_gpu:
|
||||
img_resize = torch.from_numpy(img_resize).cuda()
|
||||
else:
|
||||
img_resize = torch.from_numpy(img_resize)
|
||||
|
||||
w_new = INPUT_SIZE
|
||||
h_new = INPUT_SIZE
|
||||
img_in = torch.zeros([1, 3, h_new, w_new], dtype=torch.float32)
|
||||
if use_gpu:
|
||||
img_in = img_in.cuda()
|
||||
|
||||
img_in[0, :] = img_resize
|
||||
|
||||
with torch.no_grad():
|
||||
output = model(img_in)
|
||||
output = output * INPUT_SIZE
|
||||
|
||||
if use_gpu:
|
||||
output = output.cpu().numpy()
|
||||
else:
|
||||
output = output.numpy()
|
||||
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def smooth(cur_lmks, prev_lmks):
|
||||
smooth_lmks = np.zeros((106, 2))
|
||||
|
||||
cur_rect_x1 = np.min(cur_lmks[:, 0])
|
||||
cur_rect_x2 = np.max(cur_lmks[:, 0])
|
||||
|
||||
smooth_param = 60.0
|
||||
factor = smooth_param / (cur_rect_x1 - cur_rect_x2)
|
||||
for i in range(BASE_LANDMARK_NUM):
|
||||
weightX = np.exp(factor * np.abs(cur_lmks[i][0] - prev_lmks[i][0]))
|
||||
weightY = np.exp(factor * np.abs(cur_lmks[i][1] - prev_lmks[i][1]))
|
||||
|
||||
smooth_lmks[i][0] = (
|
||||
1 - weightX) * cur_lmks[i][0] + weightX * prev_lmks[i][0]
|
||||
smooth_lmks[i][1] = (
|
||||
1 - weightY) * cur_lmks[i][1] + weightY * prev_lmks[i][1]
|
||||
|
||||
return smooth_lmks
|
||||
|
||||
@staticmethod
|
||||
def infer_img(img, model, use_gpu=True):
|
||||
lmks = LargeBaseLmkInfer.process_img(model, img, use_gpu)
|
||||
return lmks
|
||||
@@ -0,0 +1,430 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
import os
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from modelscope.models.cv.skin_retouching.retinaface.predict_single import \
|
||||
Model
|
||||
from ...utils import image_warp_grid1, spread_flow
|
||||
from .large_base_lmks_infer import LargeBaseLmkInfer
|
||||
|
||||
INPUT_SIZE = 224
|
||||
ENLARGE_RATIO = 1.35
|
||||
|
||||
|
||||
def resize_on_long_side(img, long_side=800):
|
||||
src_height = img.shape[0]
|
||||
src_width = img.shape[1]
|
||||
|
||||
if src_height > src_width:
|
||||
scale = long_side * 1.0 / src_height
|
||||
_img = cv2.resize(
|
||||
img, (int(src_width * scale), long_side),
|
||||
interpolation=cv2.INTER_CUBIC)
|
||||
|
||||
else:
|
||||
scale = long_side * 1.0 / src_width
|
||||
_img = cv2.resize(
|
||||
img, (long_side, int(src_height * scale)),
|
||||
interpolation=cv2.INTER_CUBIC)
|
||||
|
||||
return _img, scale
|
||||
|
||||
|
||||
def draw_line(im, points, color, stroke_size=2, closed=False):
|
||||
points = points.astype(np.int32)
|
||||
for i in range(len(points) - 1):
|
||||
cv2.line(im, tuple(points[i]), tuple(points[i + 1]), color,
|
||||
stroke_size)
|
||||
if closed:
|
||||
cv2.line(im, tuple(points[0]), tuple(points[-1]), color, stroke_size)
|
||||
|
||||
|
||||
def enlarged_bbox(bbox, img_width, img_height, enlarge_ratio=0.2):
|
||||
'''
|
||||
:param bbox: [xmin,ymin,xmax,ymax]
|
||||
:return: bbox: [xmin,ymin,xmax,ymax]
|
||||
'''
|
||||
|
||||
left = bbox[0]
|
||||
top = bbox[1]
|
||||
|
||||
right = bbox[2]
|
||||
bottom = bbox[3]
|
||||
|
||||
roi_width = right - left
|
||||
roi_height = bottom - top
|
||||
|
||||
new_left = left - int(roi_width * enlarge_ratio)
|
||||
new_left = 0 if new_left < 0 else new_left
|
||||
|
||||
new_top = top - int(roi_height * enlarge_ratio)
|
||||
new_top = 0 if new_top < 0 else new_top
|
||||
|
||||
new_right = right + int(roi_width * enlarge_ratio)
|
||||
new_right = img_width if new_right > img_width else new_right
|
||||
|
||||
new_bottom = bottom + int(roi_height * enlarge_ratio)
|
||||
new_bottom = img_height if new_bottom > img_height else new_bottom
|
||||
|
||||
bbox = [new_left, new_top, new_right, new_bottom]
|
||||
|
||||
bbox = [int(x) for x in bbox]
|
||||
|
||||
return bbox
|
||||
|
||||
|
||||
class FaceInfo:
|
||||
|
||||
def __init__(self):
|
||||
self.rect = np.asarray([0, 0, 0, 0])
|
||||
self.points_array = np.zeros((106, 2))
|
||||
self.eye_left = np.zeros((22, 2))
|
||||
self.eye_right = np.zeros((22, 2))
|
||||
self.eyebrow_left = np.zeros((13, 2))
|
||||
self.eyebrow_right = np.zeros((13, 2))
|
||||
self.lips = np.zeros((64, 2))
|
||||
|
||||
|
||||
class LargeModelInfer:
|
||||
|
||||
def __init__(self, ckpt, device='cuda'):
|
||||
self.large_base_lmks_model = LargeBaseLmkInfer.model_preload(
|
||||
ckpt,
|
||||
device.lower() == 'cuda')
|
||||
self.device = device.lower()
|
||||
self.detector = Model(max_size=512, device=device)
|
||||
detector_ckpt_name = 'retinaface_resnet50_2020-07-20_old_torch.pth'
|
||||
state_dict = torch.load(
|
||||
os.path.join(os.path.dirname(ckpt), detector_ckpt_name),
|
||||
map_location='cpu')
|
||||
self.detector.load_state_dict(state_dict)
|
||||
self.detector.eval()
|
||||
|
||||
def infer(self, img_bgr):
|
||||
landmarks = []
|
||||
|
||||
rgb_image = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
||||
results = self.detector.predict_jsons(rgb_image)
|
||||
|
||||
boxes = []
|
||||
for anno in results:
|
||||
if anno['score'] == -1:
|
||||
break
|
||||
boxes.append({
|
||||
'x1': anno['bbox'][0],
|
||||
'y1': anno['bbox'][1],
|
||||
'x2': anno['bbox'][2],
|
||||
'y2': anno['bbox'][3]
|
||||
})
|
||||
|
||||
for detect_result in boxes:
|
||||
x1 = detect_result['x1']
|
||||
y1 = detect_result['y1']
|
||||
x2 = detect_result['x2']
|
||||
y2 = detect_result['y2']
|
||||
|
||||
w = x2 - x1 + 1
|
||||
h = y2 - y1 + 1
|
||||
|
||||
cx = (x2 + x1) / 2
|
||||
cy = (y2 + y1) / 2
|
||||
|
||||
sz = max(h, w) * ENLARGE_RATIO
|
||||
|
||||
x1 = cx - sz / 2
|
||||
y1 = cy - sz / 2
|
||||
trans_x1 = x1
|
||||
trans_y1 = y1
|
||||
x2 = x1 + sz
|
||||
y2 = y1 + sz
|
||||
|
||||
height, width, _ = rgb_image.shape
|
||||
dx = max(0, -x1)
|
||||
dy = max(0, -y1)
|
||||
x1 = max(0, x1)
|
||||
y1 = max(0, y1)
|
||||
|
||||
edx = max(0, x2 - width)
|
||||
edy = max(0, y2 - height)
|
||||
x2 = min(width, x2)
|
||||
y2 = min(height, y2)
|
||||
|
||||
crop_img = rgb_image[int(y1):int(y2), int(x1):int(x2)]
|
||||
if dx > 0 or dy > 0 or edx > 0 or edy > 0:
|
||||
crop_img = cv2.copyMakeBorder(
|
||||
crop_img,
|
||||
int(dy),
|
||||
int(edy),
|
||||
int(dx),
|
||||
int(edx),
|
||||
cv2.BORDER_CONSTANT,
|
||||
value=(103.94, 116.78, 123.68))
|
||||
crop_img = cv2.resize(crop_img, (INPUT_SIZE, INPUT_SIZE))
|
||||
|
||||
base_lmks = LargeBaseLmkInfer.infer_img(crop_img,
|
||||
self.large_base_lmks_model,
|
||||
self.device == 'cuda')
|
||||
|
||||
inv_scale = sz / INPUT_SIZE
|
||||
|
||||
affine_base_lmks = np.zeros((106, 2))
|
||||
for idx in range(106):
|
||||
affine_base_lmks[idx][
|
||||
0] = base_lmks[0][idx * 2 + 0] * inv_scale + trans_x1
|
||||
affine_base_lmks[idx][
|
||||
1] = base_lmks[0][idx * 2 + 1] * inv_scale + trans_y1
|
||||
|
||||
x1 = np.min(affine_base_lmks[:, 0])
|
||||
y1 = np.min(affine_base_lmks[:, 1])
|
||||
x2 = np.max(affine_base_lmks[:, 0])
|
||||
y2 = np.max(affine_base_lmks[:, 1])
|
||||
|
||||
w = x2 - x1 + 1
|
||||
h = y2 - y1 + 1
|
||||
|
||||
cx = (x2 + x1) / 2
|
||||
cy = (y2 + y1) / 2
|
||||
|
||||
sz = max(h, w) * ENLARGE_RATIO
|
||||
|
||||
x1 = cx - sz / 2
|
||||
y1 = cy - sz / 2
|
||||
trans_x1 = x1
|
||||
trans_y1 = y1
|
||||
x2 = x1 + sz
|
||||
y2 = y1 + sz
|
||||
|
||||
height, width, _ = rgb_image.shape
|
||||
dx = max(0, -x1)
|
||||
dy = max(0, -y1)
|
||||
x1 = max(0, x1)
|
||||
y1 = max(0, y1)
|
||||
|
||||
edx = max(0, x2 - width)
|
||||
edy = max(0, y2 - height)
|
||||
x2 = min(width, x2)
|
||||
y2 = min(height, y2)
|
||||
|
||||
crop_img = rgb_image[int(y1):int(y2), int(x1):int(x2)]
|
||||
if dx > 0 or dy > 0 or edx > 0 or edy > 0:
|
||||
crop_img = cv2.copyMakeBorder(
|
||||
crop_img,
|
||||
int(dy),
|
||||
int(edy),
|
||||
int(dx),
|
||||
int(edx),
|
||||
cv2.BORDER_CONSTANT,
|
||||
value=(103.94, 116.78, 123.68))
|
||||
crop_img = cv2.resize(crop_img, (INPUT_SIZE, INPUT_SIZE))
|
||||
|
||||
base_lmks = LargeBaseLmkInfer.infer_img(
|
||||
crop_img, self.large_base_lmks_model,
|
||||
self.device.lower() == 'cuda')
|
||||
|
||||
inv_scale = sz / INPUT_SIZE
|
||||
|
||||
affine_base_lmks = np.zeros((106, 2))
|
||||
for idx in range(106):
|
||||
affine_base_lmks[idx][
|
||||
0] = base_lmks[0][idx * 2 + 0] * inv_scale + trans_x1
|
||||
affine_base_lmks[idx][
|
||||
1] = base_lmks[0][idx * 2 + 1] * inv_scale + trans_y1
|
||||
|
||||
landmarks.append(affine_base_lmks)
|
||||
|
||||
return boxes, landmarks
|
||||
|
||||
def find_face_contour(self, image):
|
||||
|
||||
boxes, landmarks = self.infer(image)
|
||||
landmarks = np.array(landmarks)
|
||||
|
||||
args = [[0, 33, False], [33, 38, False], [42, 47, False],
|
||||
[51, 55, False], [57, 64, False], [66, 74, True],
|
||||
[75, 83, True], [84, 96, True]]
|
||||
|
||||
roi_bboxs = []
|
||||
|
||||
for i in range(len(boxes)):
|
||||
roi_bbox = enlarged_bbox([
|
||||
boxes[i]['x1'], boxes[i]['y1'], boxes[i]['x2'], boxes[i]['y2']
|
||||
], image.shape[1], image.shape[0], 0.5)
|
||||
roi_bbox = [int(x) for x in roi_bbox]
|
||||
roi_bboxs.append(roi_bbox)
|
||||
|
||||
people_maps = []
|
||||
|
||||
for i in range(landmarks.shape[0]):
|
||||
landmark = landmarks[i, :, :]
|
||||
maps = []
|
||||
whole_mask = np.zeros((image.shape[0], image.shape[1]), np.uint8)
|
||||
|
||||
roi_box = roi_bboxs[i]
|
||||
roi_box_width = roi_box[2] - roi_box[0]
|
||||
roi_box_height = roi_box[3] - roi_box[1]
|
||||
short_side_length = roi_box_width if roi_box_width < roi_box_height else roi_box_height
|
||||
|
||||
line_width = short_side_length // 10
|
||||
|
||||
if line_width == 0:
|
||||
line_width = 1
|
||||
|
||||
kernel_size = line_width * 2
|
||||
gaussian_kernel = kernel_size if kernel_size % 2 == 1 else kernel_size + 1
|
||||
|
||||
for t, arg in enumerate(args):
|
||||
mask = np.zeros((image.shape[0], image.shape[1]), np.uint8)
|
||||
draw_line(mask, landmark[arg[0]:arg[1]], (255, 255, 255),
|
||||
line_width, arg[2])
|
||||
mask = cv2.GaussianBlur(mask,
|
||||
(gaussian_kernel, gaussian_kernel), 0)
|
||||
if t >= 1:
|
||||
draw_line(whole_mask, landmark[arg[0]:arg[1]],
|
||||
(255, 255, 255), line_width * 2, arg[2])
|
||||
maps.append(mask)
|
||||
whole_mask = cv2.GaussianBlur(whole_mask,
|
||||
(gaussian_kernel, gaussian_kernel),
|
||||
0)
|
||||
maps.append(whole_mask)
|
||||
people_maps.append(maps)
|
||||
|
||||
return people_maps[0], boxes
|
||||
|
||||
def face2contour(self, image, stack_mode='column'):
|
||||
'''
|
||||
|
||||
:param facer:
|
||||
:param image:
|
||||
:param stack_mode:
|
||||
:return: final_maps: [map0, map1,....]
|
||||
roi_bboxs: [bbox0, bbox1, ...]
|
||||
'''
|
||||
|
||||
boxes, landmarks = self.infer(image)
|
||||
landmarks = np.array(landmarks)
|
||||
|
||||
args = [[0, 33, False], [33, 38, False], [42, 47, False],
|
||||
[51, 55, False], [57, 64, False], [66, 74, True],
|
||||
[75, 83, True], [84, 96, True]]
|
||||
|
||||
roi_bboxs = []
|
||||
|
||||
for i in range(len(boxes)):
|
||||
roi_bbox = enlarged_bbox([
|
||||
boxes[i]['x1'], boxes[i]['y1'], boxes[i]['x2'], boxes[i]['y2']
|
||||
], image.shape[1], image.shape[0], 0.5)
|
||||
roi_bbox = [int(x) for x in roi_bbox]
|
||||
roi_bboxs.append(roi_bbox)
|
||||
|
||||
people_maps = []
|
||||
|
||||
for i in range(landmarks.shape[0]):
|
||||
landmark = landmarks[i, :, :]
|
||||
maps = []
|
||||
whole_mask = np.zeros((image.shape[0], image.shape[1]), np.uint8)
|
||||
|
||||
roi_box = roi_bboxs[i]
|
||||
roi_box_width = roi_box[2] - roi_box[0]
|
||||
roi_box_height = roi_box[3] - roi_box[1]
|
||||
short_side_length = roi_box_width if roi_box_width < roi_box_height else roi_box_height
|
||||
|
||||
line_width = short_side_length // 50
|
||||
|
||||
if line_width == 0:
|
||||
line_width = 1
|
||||
|
||||
kernel_size = line_width * 4
|
||||
gaussian_kernel = kernel_size if kernel_size % 2 == 1 else kernel_size + 1
|
||||
|
||||
for arg in args:
|
||||
mask = np.zeros((image.shape[0], image.shape[1]), np.uint8)
|
||||
draw_line(mask, landmark[arg[0]:arg[1]], (255, 255, 255),
|
||||
line_width, arg[2])
|
||||
mask = cv2.GaussianBlur(mask,
|
||||
(gaussian_kernel, gaussian_kernel), 0)
|
||||
draw_line(whole_mask, landmark[arg[0]:arg[1]], (255, 255, 255),
|
||||
line_width, arg[2])
|
||||
maps.append(mask)
|
||||
whole_mask = cv2.GaussianBlur(whole_mask,
|
||||
(gaussian_kernel, gaussian_kernel),
|
||||
0)
|
||||
maps.append(whole_mask)
|
||||
people_maps.append(maps)
|
||||
|
||||
if stack_mode == 'depth':
|
||||
final_maps = []
|
||||
for i, maps in enumerate(people_maps):
|
||||
final_map = np.dstack(maps)
|
||||
final_map = final_map[roi_bboxs[i][1]:roi_bboxs[i][3],
|
||||
roi_bboxs[i][0]:roi_bboxs[i][2], :]
|
||||
final_maps.append(final_map)
|
||||
return final_maps, roi_bboxs
|
||||
|
||||
elif stack_mode == 'column':
|
||||
final_maps = []
|
||||
for i, maps in enumerate(people_maps):
|
||||
joint_maps = [
|
||||
x[roi_bboxs[i][1]:roi_bboxs[i][3],
|
||||
roi_bboxs[i][0]:roi_bboxs[i][2]] for x in maps
|
||||
]
|
||||
final_map = np.column_stack(joint_maps)
|
||||
final_maps.append(final_map)
|
||||
return final_maps, roi_bboxs
|
||||
|
||||
def fat_face(self, img, degree=0.1):
|
||||
|
||||
_img, scale = resize_on_long_side(img, 800)
|
||||
|
||||
contour_maps, boxes = self.find_face_contour(_img)
|
||||
|
||||
contour_map = contour_maps[0]
|
||||
|
||||
boxes = boxes[0]
|
||||
|
||||
Flow = np.zeros(
|
||||
shape=(contour_map.shape[0], contour_map.shape[1], 2),
|
||||
dtype=np.float32)
|
||||
|
||||
box_center = [(boxes['x1'] + boxes['x2']) / 2,
|
||||
(boxes['y1'] + boxes['y2']) / 2]
|
||||
|
||||
box_length = max(
|
||||
abs(boxes['y1'] - boxes['y2']), abs(boxes['x1'] - boxes['x2']))
|
||||
|
||||
value_1 = 2 * (Flow.shape[0] - box_center[1] - 1)
|
||||
value_2 = 2 * (Flow.shape[1] - box_center[0] - 1)
|
||||
value_list = [
|
||||
box_length * 2, 2 * (box_center[0] - 1), 2 * (box_center[1] - 1),
|
||||
value_1, value_2
|
||||
]
|
||||
flow_box_length = min(value_list)
|
||||
flow_box_length = int(flow_box_length)
|
||||
|
||||
sf = spread_flow(100, flow_box_length * degree)
|
||||
sf = cv2.resize(sf, (flow_box_length, flow_box_length))
|
||||
|
||||
Flow[int(box_center[1]
|
||||
- flow_box_length / 2):int(box_center[1]
|
||||
+ flow_box_length / 2),
|
||||
int(box_center[0]
|
||||
- flow_box_length / 2):int(box_center[0]
|
||||
+ flow_box_length / 2)] = sf
|
||||
|
||||
Flow = Flow * np.dstack((contour_map, contour_map)) / 255.0
|
||||
|
||||
inter_face_maps = contour_maps[-1]
|
||||
|
||||
Flow = Flow * (1.0 - np.dstack(
|
||||
(inter_face_maps, inter_face_maps)) / 255.0)
|
||||
|
||||
Flow = cv2.resize(Flow, (img.shape[1], img.shape[0]))
|
||||
|
||||
Flow = Flow / scale
|
||||
|
||||
pred, top_bound, bottom_bound, left_bound, right_bound = image_warp_grid1(
|
||||
Flow[..., 0], Flow[..., 1], img, 1.0, [0, 0, 0, 0])
|
||||
|
||||
return pred
|
||||
@@ -0,0 +1,201 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.nn import functional as F
|
||||
|
||||
INPUT_SIZE = 224
|
||||
|
||||
|
||||
def constant_init(module, val, bias=0):
|
||||
if hasattr(module, 'weight') and module.weight is not None:
|
||||
nn.init.constant_(module.weight, val)
|
||||
if hasattr(module, 'bias') and module.bias is not None:
|
||||
nn.init.constant_(module.bias, bias)
|
||||
|
||||
|
||||
def kaiming_init(module,
|
||||
a=0,
|
||||
mode='fan_out',
|
||||
nonlinearity='relu',
|
||||
bias=0,
|
||||
distribution='normal'):
|
||||
assert distribution in ['uniform', 'normal']
|
||||
if distribution == 'uniform':
|
||||
nn.init.kaiming_uniform_(
|
||||
module.weight, a=a, mode=mode, nonlinearity=nonlinearity)
|
||||
else:
|
||||
nn.init.kaiming_normal_(
|
||||
module.weight, a=a, mode=mode, nonlinearity=nonlinearity)
|
||||
if hasattr(module, 'bias') and module.bias is not None:
|
||||
nn.init.constant_(module.bias, bias)
|
||||
|
||||
|
||||
def conv_bn(inp, oup, kernel, stride, padding=1):
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(inp, oup, kernel, stride, padding, bias=False),
|
||||
nn.BatchNorm2d(oup), nn.PReLU(oup))
|
||||
|
||||
|
||||
def conv_1x1_bn(inp, oup):
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(inp, oup, 1, 1, 0, bias=False), nn.BatchNorm2d(oup),
|
||||
nn.ReLU(inplace=True))
|
||||
|
||||
|
||||
class InvertedResidual(nn.Module):
|
||||
|
||||
def __init__(self,
|
||||
inp,
|
||||
oup,
|
||||
stride,
|
||||
padding,
|
||||
use_res_connect,
|
||||
expand_ratio=6):
|
||||
|
||||
super(InvertedResidual, self).__init__()
|
||||
|
||||
self.stride = stride
|
||||
assert stride in [1, 2]
|
||||
|
||||
self.use_res_connect = use_res_connect
|
||||
hid_channels = inp * expand_ratio
|
||||
self.conv = nn.Sequential(
|
||||
nn.Conv2d(inp, hid_channels, 1, 1, 0, bias=False),
|
||||
nn.BatchNorm2d(hid_channels),
|
||||
nn.PReLU(hid_channels),
|
||||
nn.Conv2d(
|
||||
hid_channels,
|
||||
hid_channels,
|
||||
3,
|
||||
stride,
|
||||
padding,
|
||||
groups=hid_channels,
|
||||
bias=False),
|
||||
nn.BatchNorm2d(hid_channels),
|
||||
nn.PReLU(hid_channels),
|
||||
nn.Conv2d(hid_channels, oup, 1, 1, 0, bias=False),
|
||||
nn.BatchNorm2d(oup),
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
if self.use_res_connect:
|
||||
return x + self.conv(x)
|
||||
else:
|
||||
return self.conv(x)
|
||||
|
||||
|
||||
class SoftArgmax(nn.Module):
|
||||
|
||||
def __init__(self, beta: int = 1, infer=False):
|
||||
if not 0.0 <= beta:
|
||||
raise ValueError(f'Invalid beta: {beta}')
|
||||
super().__init__()
|
||||
self.beta = beta
|
||||
self.infer = infer
|
||||
|
||||
def forward(self, heatmap: torch.Tensor) -> torch.Tensor:
|
||||
heatmap = heatmap.mul(self.beta)
|
||||
batch_size, num_channel, height, width = heatmap.size()
|
||||
device: str = heatmap.device
|
||||
|
||||
if not self.infer:
|
||||
softmax: torch.Tensor = F.softmax(
|
||||
heatmap.view(batch_size, num_channel, height * width),
|
||||
dim=2).view(batch_size, num_channel, height, width)
|
||||
|
||||
xx, yy = torch.meshgrid(list(map(torch.arange, [width, height])))
|
||||
|
||||
approx_x = (
|
||||
softmax.mul(xx.float().to(device)).view(
|
||||
batch_size, num_channel,
|
||||
height * width).sum(2).unsqueeze(2))
|
||||
approx_y = (
|
||||
softmax.mul(yy.float().to(device)).view(
|
||||
batch_size, num_channel,
|
||||
height * width).sum(2).unsqueeze(2))
|
||||
|
||||
output = [approx_x / width, approx_y / height]
|
||||
output = torch.cat(output, 2)
|
||||
output = output.view(-1, output.size(1) * output.size(2))
|
||||
return output
|
||||
else:
|
||||
softmax: torch.Tensor = F.softmax(
|
||||
heatmap.view(batch_size, num_channel, height * width), dim=2)
|
||||
|
||||
return softmax
|
||||
|
||||
|
||||
class LargeBaseLmksNet(nn.Module):
|
||||
|
||||
def __init__(self, er=1.0, infer=False):
|
||||
|
||||
super(LargeBaseLmksNet, self).__init__()
|
||||
|
||||
self.infer = infer
|
||||
|
||||
self.block1 = conv_bn(3, int(64 * er), 3, 2, 1)
|
||||
self.block2 = InvertedResidual(
|
||||
int(64 * er), int(64 * er), 1, 1, False, 2)
|
||||
|
||||
self.block3 = InvertedResidual(
|
||||
int(64 * er), int(64 * er), 2, 1, False, 2)
|
||||
self.block4 = InvertedResidual(
|
||||
int(64 * er), int(64 * er), 1, 1, True, 2)
|
||||
self.block5 = InvertedResidual(
|
||||
int(64 * er), int(64 * er), 1, 1, True, 2)
|
||||
self.block6 = InvertedResidual(
|
||||
int(64 * er), int(64 * er), 1, 1, True, 2)
|
||||
self.block7 = InvertedResidual(
|
||||
int(64 * er), int(64 * er), 1, 1, True, 2)
|
||||
|
||||
self.block8 = InvertedResidual(
|
||||
int(64 * er), int(128 * er), 2, 1, False, 2)
|
||||
|
||||
self.block9 = InvertedResidual(
|
||||
int(128 * er), int(128 * er), 1, 1, False, 4)
|
||||
self.block10 = InvertedResidual(
|
||||
int(128 * er), int(128 * er), 1, 1, True, 4)
|
||||
self.block11 = InvertedResidual(
|
||||
int(128 * er), int(128 * er), 1, 1, True, 4)
|
||||
self.block12 = InvertedResidual(
|
||||
int(128 * er), int(128 * er), 1, 1, True, 4)
|
||||
self.block13 = InvertedResidual(
|
||||
int(128 * er), int(128 * er), 1, 1, True, 4)
|
||||
self.block14 = InvertedResidual(
|
||||
int(128 * er), int(128 * er), 1, 1, True, 4)
|
||||
|
||||
self.block15 = InvertedResidual(
|
||||
int(128 * er), int(128 * er), 1, 1, False, 2) # [128, 14, 14]
|
||||
self.block16 = InvertedResidual(
|
||||
int(128 * er), int(128 * er), 2, 1, False, 2)
|
||||
self.block17 = InvertedResidual(
|
||||
int(128 * er), int(128 * er), 1, 1, False, 2)
|
||||
|
||||
self.block18 = conv_bn(int(128 * er), int(256 * er), 3, 1, 1)
|
||||
self.block19 = nn.Conv2d(int(256 * er), 106, 3, 1, 1, bias=False)
|
||||
self.softargmax = SoftArgmax(infer=infer)
|
||||
|
||||
def forward(self, x): # x: 3, 224, 224
|
||||
|
||||
x = self.block1(x)
|
||||
x = self.block2(x)
|
||||
x = self.block3(x)
|
||||
x = self.block4(x)
|
||||
x = self.block5(x)
|
||||
x = self.block6(x)
|
||||
x = self.block7(x)
|
||||
x = self.block8(x)
|
||||
x = self.block9(x)
|
||||
x = self.block10(x)
|
||||
x = self.block11(x)
|
||||
x = self.block12(x)
|
||||
x = self.block13(x)
|
||||
x = self.block14(x)
|
||||
x = self.block15(x)
|
||||
x = self.block16(x)
|
||||
x = self.block17(x)
|
||||
x = self.block18(x)
|
||||
x = self.block19(x)
|
||||
x = self.softargmax(x)
|
||||
|
||||
return x
|
||||
@@ -0,0 +1,160 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
import torch.nn as nn
|
||||
|
||||
FACE_PART_SIZE = 56
|
||||
|
||||
|
||||
class InvertedResidual(nn.Module):
|
||||
|
||||
def __init__(self,
|
||||
inp,
|
||||
oup,
|
||||
kernel_size,
|
||||
stride,
|
||||
padding,
|
||||
expand_ratio=2,
|
||||
use_connect=False,
|
||||
activation='relu'):
|
||||
super(InvertedResidual, self).__init__()
|
||||
|
||||
hid_channels = int(inp * expand_ratio)
|
||||
if activation == 'relu':
|
||||
self.conv = nn.Sequential(
|
||||
nn.Conv2d(inp, hid_channels, 1, 1, 0, bias=False),
|
||||
nn.BatchNorm2d(hid_channels), nn.ReLU(inplace=True),
|
||||
nn.Conv2d(
|
||||
hid_channels,
|
||||
hid_channels,
|
||||
kernel_size,
|
||||
stride,
|
||||
padding,
|
||||
groups=hid_channels,
|
||||
bias=False), nn.BatchNorm2d(hid_channels),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(hid_channels, oup, 1, 1, 0, bias=False),
|
||||
nn.BatchNorm2d(oup))
|
||||
elif activation == 'prelu':
|
||||
self.conv = nn.Sequential(
|
||||
nn.Conv2d(inp, hid_channels, 1, 1, 0, bias=False),
|
||||
nn.BatchNorm2d(hid_channels), nn.PReLU(hid_channels),
|
||||
nn.Conv2d(
|
||||
hid_channels,
|
||||
hid_channels,
|
||||
kernel_size,
|
||||
stride,
|
||||
padding,
|
||||
groups=hid_channels,
|
||||
bias=False), nn.BatchNorm2d(hid_channels),
|
||||
nn.PReLU(hid_channels),
|
||||
nn.Conv2d(hid_channels, oup, 1, 1, 0, bias=False),
|
||||
nn.BatchNorm2d(oup))
|
||||
self.use_connect = use_connect
|
||||
|
||||
def forward(self, x):
|
||||
if self.use_connect:
|
||||
return x + self.conv(x)
|
||||
else:
|
||||
return self.conv(x)
|
||||
|
||||
|
||||
class Residual(nn.Module):
|
||||
|
||||
def __init__(self,
|
||||
inp,
|
||||
oup,
|
||||
kernel_size,
|
||||
stride,
|
||||
padding,
|
||||
use_connect=False,
|
||||
activation='relu'):
|
||||
super(Residual, self).__init__()
|
||||
|
||||
self.use_connect = use_connect
|
||||
|
||||
if activation == 'relu':
|
||||
self.conv = nn.Sequential(
|
||||
nn.Conv2d(
|
||||
inp,
|
||||
inp,
|
||||
kernel_size,
|
||||
stride,
|
||||
padding,
|
||||
groups=inp,
|
||||
bias=False), nn.BatchNorm2d(inp), nn.ReLU(inplace=True),
|
||||
nn.Conv2d(inp, oup, 1, 1, 0, bias=False), nn.BatchNorm2d(oup),
|
||||
nn.ReLU(inplace=True))
|
||||
elif activation == 'prelu':
|
||||
self.conv = nn.Sequential(
|
||||
nn.Conv2d(
|
||||
inp,
|
||||
inp,
|
||||
kernel_size,
|
||||
stride,
|
||||
padding,
|
||||
groups=inp,
|
||||
bias=False), nn.BatchNorm2d(inp), nn.PReLU(inp),
|
||||
nn.Conv2d(inp, oup, 1, 1, 0, bias=False), nn.BatchNorm2d(oup),
|
||||
nn.PReLU(oup))
|
||||
|
||||
def forward(self, x):
|
||||
if self.use_connect:
|
||||
return x + self.conv(x)
|
||||
else:
|
||||
return self.conv(x)
|
||||
|
||||
|
||||
def conv_bn(inp, oup, kernel, stride, padding=1):
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(inp, oup, kernel, stride, padding, bias=False),
|
||||
nn.BatchNorm2d(oup), nn.PReLU(oup))
|
||||
|
||||
|
||||
def conv_no_relu(inp, oup, kernel, stride, padding=1):
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(inp, oup, kernel, stride, padding, bias=False),
|
||||
nn.BatchNorm2d(oup))
|
||||
|
||||
|
||||
class View(nn.Module):
|
||||
|
||||
def __init__(self, shape):
|
||||
super(View, self).__init__()
|
||||
self.shape = shape
|
||||
|
||||
def forward(self, x):
|
||||
return x.view(*self.shape)
|
||||
|
||||
|
||||
class Softmax(nn.Module):
|
||||
|
||||
def __init__(self, dim):
|
||||
super(Softmax, self).__init__()
|
||||
self.softmax = nn.Softmax(dim)
|
||||
|
||||
def forward(self, x):
|
||||
return self.softmax(x)
|
||||
|
||||
|
||||
class LargeEyeballNet(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super(LargeEyeballNet, self).__init__()
|
||||
|
||||
# v6/v7/v9
|
||||
# iris : -1*2, 3, FACE_PART_SIZE, FACE_PART_SIZE
|
||||
self.net = nn.Sequential(
|
||||
conv_bn(3, 16, 3, 2, 0),
|
||||
InvertedResidual(16, 16, 3, 1, 1, 2, True, activation='prelu'),
|
||||
InvertedResidual(16, 32, 3, 2, 0, 2, False, activation='prelu'),
|
||||
InvertedResidual(32, 32, 3, 1, 1, 2, True, activation='prelu'),
|
||||
InvertedResidual(32, 64, 3, 2, 1, 2, False, activation='prelu'),
|
||||
InvertedResidual(64, 64, 3, 1, 1, 2, True, activation='prelu'),
|
||||
InvertedResidual(64, 64, 3, 2, 0, 2, False, activation='prelu'),
|
||||
InvertedResidual(64, 64, 3, 1, 1, 2, True, activation='prelu'),
|
||||
View((-1, 64 * 3 * 3, 1, 1)), conv_bn(64 * 3 * 3, 64, 1, 1, 0),
|
||||
conv_no_relu(64, 40, 1, 1, 0), View((-1, 40)))
|
||||
|
||||
def forward(self, x): # x: -1, 3, FACE_PART_SIZE, FACE_PART_SIZE
|
||||
iris = self.net(x)
|
||||
|
||||
return iris
|
||||
@@ -0,0 +1,564 @@
|
||||
# Part of the implementation is borrowed and modified from Deep3DFaceRecon_pytorch,
|
||||
# publicly available at https://github.com/sicxu/Deep3DFaceRecon_pytorch
|
||||
import os
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from modelscope.models import MODELS, TorchModel
|
||||
from modelscope.models.cv.face_reconstruction.models import opt
|
||||
from .. import utils
|
||||
from . import networks
|
||||
from .bfm import ParametricFaceModel
|
||||
from .losses import (CLIPLoss_relative, TVLoss, TVLoss_std, landmark_loss,
|
||||
perceptual_loss, photo_loss, points_loss_horizontal,
|
||||
reflectance_loss, reg_loss)
|
||||
from .nv_diffrast import MeshRenderer
|
||||
|
||||
|
||||
@MODELS.register_module('face-reconstruction', 'face_reconstruction')
|
||||
class FaceReconModel(TorchModel):
|
||||
|
||||
def __init__(self,
|
||||
model_dir,
|
||||
w_color=1.92,
|
||||
w_exp=0.8,
|
||||
w_gamma=10.0,
|
||||
w_id=1.0,
|
||||
w_lm=0.0016,
|
||||
w_reg=0.0003,
|
||||
w_tex=0.017,
|
||||
*args,
|
||||
**kwargs):
|
||||
"""The FaceReconModel is implemented based on Deep3DFaceRecon_pytorch, publicly available at
|
||||
https://github.com/sicxu/Deep3DFaceRecon_pytorch
|
||||
|
||||
Args:
|
||||
model_dir: the root directory of the model files
|
||||
w_color: the weight of color loss
|
||||
w_exp: the regularization weight of expression
|
||||
w_gamma: the regularization weight of lighting
|
||||
w_id: the regularization weight of identity
|
||||
w_lm: the weight of landmark loss
|
||||
w_reg: the weight of regularization loss
|
||||
w_tex: the regularization weight of texture
|
||||
"""
|
||||
super().__init__(model_dir, *args, **kwargs)
|
||||
|
||||
opt.bfm_folder = os.path.join(model_dir, 'assets')
|
||||
self.opt = opt
|
||||
self.w_color = w_color
|
||||
self.w_exp = w_exp
|
||||
self.w_gamma = w_gamma
|
||||
self.w_id = w_id
|
||||
self.w_lm = w_lm
|
||||
self.w_reg = w_reg
|
||||
self.w_tex = w_tex
|
||||
self.device = torch.device('cpu')
|
||||
self.isTrain = opt.isTrain
|
||||
self.visual_names = ['output_vis']
|
||||
self.model_names = ['net_recon']
|
||||
self.parallel_names = self.model_names + ['renderer']
|
||||
|
||||
self.net_recon = networks.define_net_recon(
|
||||
net_recon=opt.net_recon,
|
||||
use_last_fc=opt.use_last_fc,
|
||||
init_path=None)
|
||||
|
||||
self.facemodel = ParametricFaceModel(
|
||||
bfm_folder=opt.bfm_folder,
|
||||
camera_distance=opt.camera_d,
|
||||
focal=opt.focal,
|
||||
center=opt.center,
|
||||
is_train=self.isTrain,
|
||||
default_name=opt.bfm_model)
|
||||
|
||||
self.facemodel_front = ParametricFaceModel(
|
||||
bfm_folder=opt.bfm_folder,
|
||||
camera_distance=opt.camera_d,
|
||||
focal=opt.focal,
|
||||
center=opt.center,
|
||||
is_train=self.isTrain,
|
||||
default_name='face_model_for_maas.mat')
|
||||
|
||||
fov = 2 * np.arctan(opt.center / opt.focal) * 180 / np.pi
|
||||
self.renderer = MeshRenderer(
|
||||
rasterize_fov=fov,
|
||||
znear=opt.z_near,
|
||||
zfar=opt.z_far,
|
||||
rasterize_size=int(2 * opt.center))
|
||||
|
||||
self.renderer_fitting = MeshRenderer(
|
||||
rasterize_fov=fov,
|
||||
znear=opt.z_near,
|
||||
zfar=opt.z_far,
|
||||
rasterize_size=int(2 * opt.center))
|
||||
|
||||
self.nonlinear_UVs = self.facemodel.nonlinear_UVs
|
||||
self.nonlinear_UVs = torch.from_numpy(self.nonlinear_UVs).to(
|
||||
torch.device('cuda'))
|
||||
|
||||
template_obj_path = os.path.join(opt.bfm_folder, 'template_mesh.obj')
|
||||
self.template_mesh = utils.read_obj(template_obj_path)
|
||||
|
||||
self.input_imgs = []
|
||||
self.input_img_hds = []
|
||||
self.input_fat_img_hds = []
|
||||
self.atten_masks = []
|
||||
self.gt_lms = []
|
||||
self.gt_lm_hds = []
|
||||
self.trans_ms = []
|
||||
self.img_names = []
|
||||
self.face_masks = []
|
||||
self.head_masks = []
|
||||
self.input_imgs_coeff = []
|
||||
self.gt_lms_coeff = []
|
||||
|
||||
self.loss_names = [
|
||||
'all', 'feat', 'color', 'lm', 'reg', 'gamma', 'reflc'
|
||||
]
|
||||
|
||||
# loss func name: (compute_%s_loss) % loss_name
|
||||
self.compute_feat_loss = perceptual_loss
|
||||
self.comupte_color_loss = photo_loss
|
||||
self.compute_lm_loss = landmark_loss
|
||||
self.compute_reg_loss = reg_loss
|
||||
self.compute_reflc_loss = reflectance_loss
|
||||
|
||||
def load_networks(self, load_path):
|
||||
state_dict = torch.load(load_path, map_location=self.device)
|
||||
print('loading the model from %s' % load_path)
|
||||
|
||||
for name in self.model_names:
|
||||
if isinstance(name, str):
|
||||
net = getattr(self, name)
|
||||
if isinstance(net, torch.nn.DataParallel):
|
||||
net = net.module
|
||||
net.load_state_dict(state_dict[name], strict=False)
|
||||
|
||||
if self.opt.phase != 'test':
|
||||
if self.opt.continue_train:
|
||||
|
||||
try:
|
||||
for i, sched in enumerate(self.schedulers):
|
||||
sched.load_state_dict(state_dict['sched_%02d' % i])
|
||||
except Exception as e:
|
||||
print(e)
|
||||
for i, sched in enumerate(self.schedulers):
|
||||
sched.last_epoch = self.opt.epoch_count - 1
|
||||
|
||||
def setup(self, checkpoint_path):
|
||||
"""Load and print networks; create schedulers
|
||||
|
||||
Parameters:
|
||||
opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions
|
||||
"""
|
||||
self.load_networks(checkpoint_path)
|
||||
|
||||
def parallelize(self, convert_sync_batchnorm=True):
|
||||
if not self.opt.use_ddp:
|
||||
for name in self.parallel_names:
|
||||
if isinstance(name, str):
|
||||
module = getattr(self, name)
|
||||
setattr(self, name, module.to(self.device))
|
||||
else:
|
||||
for name in self.model_names:
|
||||
if isinstance(name, str):
|
||||
module = getattr(self, name)
|
||||
if convert_sync_batchnorm:
|
||||
module = torch.nn.SyncBatchNorm.convert_sync_batchnorm(
|
||||
module)
|
||||
setattr(
|
||||
self, name,
|
||||
torch.nn.parallel.DistributedDataParallel(
|
||||
module.to(self.device),
|
||||
device_ids=[self.device.index],
|
||||
find_unused_parameters=True,
|
||||
broadcast_buffers=True))
|
||||
|
||||
# DistributedDataParallel is not needed when a module doesn't have any parameter that requires a gradient.
|
||||
for name in self.parallel_names:
|
||||
if isinstance(name, str) and name not in self.model_names:
|
||||
module = getattr(self, name)
|
||||
setattr(self, name, module.to(self.device))
|
||||
|
||||
# put state_dict of optimizer to gpu device
|
||||
if self.opt.phase != 'test':
|
||||
if self.opt.continue_train:
|
||||
for optim in self.optimizers:
|
||||
for state in optim.state.values():
|
||||
for k, v in state.items():
|
||||
if isinstance(v, torch.Tensor):
|
||||
state[k] = v.to(self.device)
|
||||
|
||||
def eval(self):
|
||||
"""Make models eval mode"""
|
||||
for name in self.model_names:
|
||||
if isinstance(name, str):
|
||||
net = getattr(self, name)
|
||||
net.eval()
|
||||
|
||||
def set_render(self, image_res):
|
||||
fov = 2 * np.arctan(self.opt.center / self.opt.focal) * 180 / np.pi
|
||||
if image_res is None:
|
||||
image_res = int(2 * self.opt.center)
|
||||
|
||||
self.renderer = MeshRenderer(
|
||||
rasterize_fov=fov,
|
||||
znear=self.opt.z_near,
|
||||
zfar=self.opt.z_far,
|
||||
rasterize_size=image_res)
|
||||
|
||||
def set_input(self, input):
|
||||
"""Unpack input data from the dataloader and perform necessary pre-processing steps.
|
||||
|
||||
Parameters:
|
||||
input: a dictionary that contains the data itself and its metadata information.
|
||||
"""
|
||||
self.input_img = input['imgs'].to(self.device)
|
||||
self.input_img_hd = input['imgs_hd'].to(
|
||||
self.device) if 'imgs_hd' in input else None
|
||||
|
||||
if 'imgs_fat_hd' not in input or input['imgs_fat_hd'] is None:
|
||||
self.input_fat_img_hd = self.input_img_hd
|
||||
else:
|
||||
self.input_fat_img_hd = input['imgs_fat_hd'].to(self.device)
|
||||
|
||||
self.atten_mask = input['msks'].to(
|
||||
self.device) if 'msks' in input else None
|
||||
self.gt_lm = input['lms'].to(self.device) if 'lms' in input else None
|
||||
self.gt_lm_hd = input['lms_hd'].to(
|
||||
self.device) if 'lms_hd' in input else None
|
||||
self.trans_m = input['M'].to(self.device) if 'M' in input else None
|
||||
self.image_paths = input['im_paths'] if 'im_paths' in input else None
|
||||
self.img_name = input['img_name'] if 'img_name' in input else None
|
||||
self.face_mask = input['face_mask'].to(
|
||||
self.device) if 'face_mask' in input else None
|
||||
self.head_mask = input['head_mask'].to(
|
||||
self.device) if 'head_mask' in input else None
|
||||
self.gt_normals = input['normals'].to(
|
||||
self.device) if 'normals' in input else None
|
||||
self.input_img_coeff = input['imgs_coeff'].to(
|
||||
self.device) if 'imgs_coeff' in input else None
|
||||
self.gt_lm_coeff = input['lms_coeff'].to(
|
||||
self.device) if 'lms_coeff' in input else None
|
||||
|
||||
def get_edge_points_horizontal(self):
|
||||
left_points = []
|
||||
right_points = []
|
||||
for i in range(self.face_mask.shape[2]):
|
||||
inds = torch.where(self.face_mask[0, 0, i, :] > 0.5) # 0.9
|
||||
if len(inds[0]) > 0: # i > 112 and len(inds[0]) > 0
|
||||
left_points.append(int(inds[0][0]) + 1)
|
||||
right_points.append(int(inds[0][-1]))
|
||||
else:
|
||||
left_points.append(0)
|
||||
right_points.append(self.face_mask.shape[3] - 1)
|
||||
self.left_points = torch.tensor(left_points).long().to(self.device)
|
||||
self.right_points = torch.tensor(right_points).long().to(self.device)
|
||||
|
||||
def get_edge_points_vertical(self):
|
||||
top_points = []
|
||||
bottom_points = []
|
||||
for i in range(self.face_mask.shape[3]):
|
||||
inds = torch.where(self.face_mask[0, 0, :, i] > 0.9)
|
||||
if len(inds[0]) > 0:
|
||||
top_points.append(int(inds[0][0]))
|
||||
bottom_points.append(int(inds[0][-1]))
|
||||
else:
|
||||
top_points.append(0)
|
||||
bottom_points.append(self.face_mask.shape[2] - 1)
|
||||
self.top_points = torch.tensor(top_points).long().to(self.device)
|
||||
self.bottom_points = torch.tensor(bottom_points).long().to(self.device)
|
||||
|
||||
def blur_shape_offset_uv(self, global_blur=False, blur_size=3):
|
||||
if self.edge_mask is not None:
|
||||
shape_offset_uv_blur = self.shape_offset_uv[0].detach().cpu(
|
||||
).numpy()
|
||||
shape_offset_uv_blur = cv2.blur(shape_offset_uv_blur, (15, 15))
|
||||
shape_offset_uv_blur = torch.from_numpy(
|
||||
shape_offset_uv_blur).float().to(self.device)[None, ...]
|
||||
value_1 = shape_offset_uv_blur * self.edge_mask[None, ..., None]
|
||||
value_2 = self.shape_offset_uv * (
|
||||
1 - self.edge_mask[None, ..., None])
|
||||
self.shape_offset_uv = value_1 + value_2
|
||||
|
||||
self.shape_offset_uv = self.shape_offset_uv * self.fusion_mask[None,
|
||||
...,
|
||||
None]
|
||||
|
||||
if global_blur and blur_size > 0:
|
||||
shape_offset_uv_blur = self.shape_offset_uv[0].detach().cpu(
|
||||
).numpy()
|
||||
shape_offset_uv_blur = cv2.blur(shape_offset_uv_blur,
|
||||
(blur_size, blur_size))
|
||||
shape_offset_uv_blur = torch.from_numpy(
|
||||
shape_offset_uv_blur).float().to(self.device)[None, ...]
|
||||
self.shape_offset_uv = shape_offset_uv_blur
|
||||
|
||||
def get_fusion_mask(self):
|
||||
|
||||
h, w = self.shape_offset_uv.shape[1:3]
|
||||
self.fusion_mask = torch.zeros((h, w)).to(self.device).float()
|
||||
UVs_coords = self.nonlinear_UVs.clone()[:35709]
|
||||
UVs_coords[:, 0] *= w
|
||||
UVs_coords[:, 1] *= h
|
||||
UVs_coords_int = torch.floor(UVs_coords)
|
||||
UVs_coords_int = UVs_coords_int.long()
|
||||
|
||||
self.fusion_mask[h - 1 - UVs_coords_int[:, 1], UVs_coords_int[:,
|
||||
0]] = 1
|
||||
|
||||
# blur mask
|
||||
self.fusion_mask = self.fusion_mask.cpu().numpy()
|
||||
new_kernel1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
|
||||
new_kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (8, 8))
|
||||
self.fusion_mask = cv2.dilate(self.fusion_mask, new_kernel1, 1)
|
||||
self.fusion_mask = cv2.erode(self.fusion_mask, new_kernel2, 1)
|
||||
self.fusion_mask = cv2.blur(self.fusion_mask, (17, 17))
|
||||
self.fusion_mask = torch.from_numpy(self.fusion_mask).float().to(
|
||||
self.device)
|
||||
|
||||
def get_edge_mask(self):
|
||||
|
||||
h, w = self.shape_offset_uv.shape[1:3]
|
||||
self.edge_mask = torch.zeros((h, w)).to(self.device).float()
|
||||
UVs_coords = self.nonlinear_UVs.clone()[self.edge_points_inds]
|
||||
UVs_coords[:, 0] *= w
|
||||
UVs_coords[:, 1] *= h
|
||||
UVs_coords_int = torch.floor(UVs_coords)
|
||||
UVs_coords_int = UVs_coords_int.long()
|
||||
|
||||
self.edge_mask[h - 1 - UVs_coords_int[:, 1], UVs_coords_int[:, 0]] = 1
|
||||
|
||||
# blur mask
|
||||
self.edge_mask = self.edge_mask.cpu().numpy()
|
||||
new_kernel1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (8, 8))
|
||||
self.edge_mask = cv2.dilate(self.edge_mask, new_kernel1, 1)
|
||||
self.edge_mask = cv2.blur(self.edge_mask, (5, 5))
|
||||
self.edge_mask = torch.from_numpy(self.edge_mask).float().to(
|
||||
self.device)
|
||||
|
||||
def fitting_nonlinear(self, coeff, debug=False, n_iters=100, out_dir=None):
|
||||
output_coeff = coeff.detach().clone()
|
||||
|
||||
output_coeff = self.facemodel_front.split_coeff(output_coeff)
|
||||
output_coeff['id'].requires_grad = True
|
||||
output_coeff['exp'].requires_grad = True
|
||||
output_coeff['tex'].requires_grad = True
|
||||
output_coeff['angle'].requires_grad = True
|
||||
output_coeff['gamma'].requires_grad = True
|
||||
output_coeff['trans'].requires_grad = True
|
||||
|
||||
self.shape_offset_uv = torch.zeros(
|
||||
(1, 300, 300, 3),
|
||||
dtype=torch.float32).to(self.device) # (1, 180, 256, 3)
|
||||
self.shape_offset_uv.requires_grad = True
|
||||
|
||||
self.texture_offset_uv = torch.zeros(
|
||||
(1, 300, 300, 3),
|
||||
dtype=torch.float32).to(self.device) # (1, 180, 256, 3)
|
||||
self.texture_offset_uv.requires_grad = True
|
||||
|
||||
value_list = [
|
||||
self.shape_offset_uv, self.texture_offset_uv, output_coeff['id'],
|
||||
output_coeff['exp'], output_coeff['tex'], output_coeff['angle'],
|
||||
output_coeff['gamma'], output_coeff['trans']
|
||||
]
|
||||
optim = torch.optim.Adam(value_list, lr=1e-3)
|
||||
|
||||
self.get_edge_points_horizontal()
|
||||
self.get_edge_points_vertical()
|
||||
|
||||
self.cur_iter = 0
|
||||
for i in range(n_iters): # 500
|
||||
self.pred_vertex, _, self.pred_color, self.pred_lm, _, face_shape_offset, self.verts_proj = \
|
||||
self.facemodel_front.compute_for_render_train_nonlinear(output_coeff, self.shape_offset_uv,
|
||||
self.texture_offset_uv,
|
||||
self.nonlinear_UVs[:35709, ...])
|
||||
self.pred_mask, _, self.pred_face, self.occ = self.renderer_fitting(
|
||||
self.pred_vertex,
|
||||
self.facemodel_front.face_buf,
|
||||
feat=self.pred_color)
|
||||
|
||||
self.pred_coeffs_dict = self.facemodel_front.split_coeff(
|
||||
output_coeff)
|
||||
self.compute_losses_fitting()
|
||||
if debug and i % 10 == 0:
|
||||
print('{}: total loss: {:.6f}'.format(i, self.loss_all.item()))
|
||||
|
||||
optim.zero_grad()
|
||||
self.loss_all.backward()
|
||||
optim.step()
|
||||
|
||||
self.cur_iter += 1
|
||||
|
||||
output_coeff = self.facemodel_front.merge_coeff(output_coeff)
|
||||
|
||||
self.get_edge_mask()
|
||||
self.get_fusion_mask()
|
||||
self.blur_shape_offset_uv()
|
||||
|
||||
self.pred_vertex, _, self.pred_color, self.pred_lm, _, face_shape_offset, self.verts_proj = \
|
||||
self.facemodel_front.compute_for_render_train_nonlinear(output_coeff, self.shape_offset_uv,
|
||||
self.texture_offset_uv,
|
||||
self.nonlinear_UVs[:35709, ...])
|
||||
|
||||
if out_dir is not None:
|
||||
input_img_numpy = 255. * (self.input_img).detach().cpu().permute(
|
||||
0, 2, 3, 1).numpy()
|
||||
input_img_numpy = np.squeeze(input_img_numpy)
|
||||
|
||||
output_vis = self.pred_face
|
||||
output_vis_numpy_raw = 255. * output_vis.detach().cpu().permute(
|
||||
0, 2, 3, 1).numpy()
|
||||
output_vis_numpy_raw = np.squeeze(output_vis_numpy_raw)
|
||||
|
||||
output_vis_numpy = np.concatenate(
|
||||
(input_img_numpy, output_vis_numpy_raw), axis=-2)
|
||||
|
||||
output_vis = np.squeeze(output_vis_numpy)
|
||||
output_vis = output_vis[..., ::-1] # rgb->bgr
|
||||
output_face_mask = self.pred_mask.detach().cpu().permute(
|
||||
0, 2, 3, 1).squeeze().numpy() * 255.0
|
||||
output_vis = np.column_stack(
|
||||
(output_vis, cv2.cvtColor(output_face_mask,
|
||||
cv2.COLOR_GRAY2BGR)))
|
||||
output_input_vis = output_vis[:, :224]
|
||||
output_pred_vis = output_vis[:, 224:448]
|
||||
output_mask_vis = output_vis[:, 448:]
|
||||
|
||||
face_mask_vis = 255. * self.face_mask.detach().cpu()[0, 0].numpy()
|
||||
|
||||
shape_offset_vis = self.shape_offset_uv.detach().cpu().numpy()[0]
|
||||
shape_offset_vis = (shape_offset_vis - shape_offset_vis.min()) / (
|
||||
shape_offset_vis.max() - shape_offset_vis.min()) * 255.0
|
||||
|
||||
cv2.imwrite(
|
||||
os.path.join(out_dir, 'fitting_01_input.jpg'),
|
||||
output_input_vis)
|
||||
cv2.imwrite(
|
||||
os.path.join(out_dir, 'fitting_02_pred.jpg'), output_pred_vis)
|
||||
cv2.imwrite(
|
||||
os.path.join(out_dir, 'fitting_03_mask.jpg'), output_mask_vis)
|
||||
cv2.imwrite(
|
||||
os.path.join(out_dir, 'fitting_04_facemask.jpg'),
|
||||
face_mask_vis)
|
||||
cv2.imwrite(
|
||||
os.path.join(out_dir, 'fitting_05_shape_offset.jpg'),
|
||||
shape_offset_vis)
|
||||
|
||||
recon_shape_offset = face_shape_offset
|
||||
recon_shape_offset[..., -1] = 10 - recon_shape_offset[
|
||||
..., -1] # from camera space to world space
|
||||
recon_shape_offset = recon_shape_offset.detach().cpu().numpy()[0]
|
||||
|
||||
tri = self.facemodel_front.face_buf.cpu().numpy()
|
||||
pred_color = self.pred_color.detach().cpu().numpy()[0].clip(0, 1)
|
||||
|
||||
output = {
|
||||
'coeffs': output_coeff,
|
||||
'face_vertices': recon_shape_offset,
|
||||
'face_faces': tri + 1,
|
||||
'face_colors': pred_color
|
||||
}
|
||||
return output
|
||||
|
||||
def forward(self, out_dir=None):
|
||||
self.facemodel.to(self.device)
|
||||
self.facemodel_front.to(self.device)
|
||||
with torch.no_grad():
|
||||
|
||||
output_coeff = self.net_recon(self.input_img)
|
||||
|
||||
with torch.enable_grad():
|
||||
output = self.fitting_nonlinear(
|
||||
output_coeff, debug=True, out_dir=out_dir)
|
||||
|
||||
output_coeff = output['coeffs']
|
||||
output_coeff = self.facemodel.split_coeff(output_coeff)
|
||||
eye_coeffs = output_coeff['exp'][0, 16] + output_coeff['exp'][
|
||||
0, 17] + output_coeff['exp'][0, 19]
|
||||
if eye_coeffs > 1.0:
|
||||
degree = 0.5
|
||||
else:
|
||||
degree = 1.0
|
||||
output_coeff['exp'][0, 16] += 1 * degree
|
||||
output_coeff['exp'][0, 17] += 1 * degree
|
||||
output_coeff['exp'][0, 19] += 1.5 * degree
|
||||
output_coeff = self.facemodel.merge_coeff(output_coeff)
|
||||
|
||||
self.pred_vertex, face_shape_ori, head_shape = \
|
||||
self.facemodel.compute_for_render_nonlinear_full(output_coeff, self.shape_offset_uv.detach(),
|
||||
self.nonlinear_UVs, nose_coeff=0.1)
|
||||
|
||||
UVs_tensor = torch.tensor(
|
||||
self.template_mesh['uvs'],
|
||||
dtype=torch.float32)[None, ...].to(self.pred_vertex.device)
|
||||
target_img = self.input_fat_img_hd.permute(0, 2, 3, 1)
|
||||
with torch.enable_grad():
|
||||
_, _, _, texture_map, _ = self.renderer.pred_shape_and_texture(
|
||||
self.pred_vertex, self.facemodel.face_buf, UVs_tensor,
|
||||
target_img)
|
||||
|
||||
recon_shape = head_shape
|
||||
recon_shape[
|
||||
...,
|
||||
-1] = 10 - recon_shape[..., -1] # from camera space to world space
|
||||
recon_shape = recon_shape.cpu().numpy()[0]
|
||||
tri = self.facemodel.face_buf.cpu().numpy()
|
||||
normals = utils.estimate_normals(recon_shape, tri)
|
||||
|
||||
output['head_vertices'] = recon_shape
|
||||
output['head_faces'] = tri + 1
|
||||
output['head_tex_map'] = texture_map
|
||||
output['head_UVs'] = self.template_mesh['uvs']
|
||||
output['head_faces_uv'] = self.template_mesh['faces_uv']
|
||||
output['head_normals'] = normals
|
||||
|
||||
return output
|
||||
|
||||
def compute_losses_fitting(self):
|
||||
face_mask = self.pred_mask
|
||||
|
||||
face_mask = face_mask.detach()
|
||||
self.loss_color = self.w_color * self.comupte_color_loss(
|
||||
self.pred_face, self.input_img, face_mask) # 1.0
|
||||
|
||||
self.loss_color_nose = torch.tensor(0.0).float().to(self.device)
|
||||
|
||||
loss_reg, loss_gamma = self.compute_reg_loss(self.pred_coeffs_dict,
|
||||
self.w_id, self.w_exp,
|
||||
self.w_tex)
|
||||
self.loss_reg = self.w_reg * loss_reg # 1.0
|
||||
self.loss_gamma = self.w_gamma * loss_gamma # 1.0
|
||||
|
||||
self.loss_lm = self.w_lm * self.compute_lm_loss(
|
||||
self.pred_lm, self.gt_lm) * 0.1 # 0.1
|
||||
|
||||
self.loss_smooth_offset = TVLoss()(self.shape_offset_uv.permute(
|
||||
0, 3, 1, 2)) * 10000 # 10000
|
||||
|
||||
self.loss_reg_offset = torch.tensor(0.0).float().to(self.device)
|
||||
|
||||
self.loss_reg_textureOff = torch.mean(
|
||||
torch.abs(self.texture_offset_uv)) * 10 # 10
|
||||
|
||||
self.loss_smooth_offset_std = TVLoss_std()(
|
||||
self.shape_offset_uv.permute(0, 3, 1, 2)) * 50000 # 50000
|
||||
|
||||
self.loss_points_horizontal, self.edge_points_inds = points_loss_horizontal(
|
||||
self.verts_proj, self.left_points, self.right_points) # 20
|
||||
self.loss_points_horizontal *= 20
|
||||
self.loss_points_horizontal_jaw = torch.tensor(0.0).float().to(
|
||||
self.device)
|
||||
self.loss_points_vertical = torch.tensor(0.0).float().to(self.device)
|
||||
self.loss_normals = torch.tensor(0.0).float().to(self.device)
|
||||
|
||||
self.loss_all = self.loss_color + self.loss_lm + self.loss_reg + self.loss_gamma + self.loss_smooth_offset
|
||||
self.loss_all += self.loss_reg_offset + self.loss_smooth_offset_std + self.loss_points_horizontal
|
||||
self.loss_all += self.loss_points_vertical + self.loss_reg_textureOff
|
||||
self.loss_all += self.loss_color_nose + self.loss_normals + self.loss_points_horizontal_jaw
|
||||
|
||||
self.loss_mask = torch.tensor(0.0).float().to(self.device)
|
||||
413
modelscope/models/cv/face_reconstruction/models/losses.py
Normal file
413
modelscope/models/cv/face_reconstruction/models/losses.py
Normal file
@@ -0,0 +1,413 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
import clip
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from kornia.geometry import warp_affine
|
||||
|
||||
|
||||
def resize_n_crop(image, M, dsize=112):
|
||||
# image: (b, c, h, w)
|
||||
# M : (b, 2, 3)
|
||||
return warp_affine(image, M, dsize=(dsize, dsize))
|
||||
|
||||
|
||||
class CLIPLoss(torch.nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super(CLIPLoss, self).__init__()
|
||||
self.model, self.preprocess = clip.load('ViT-B/32', device='cuda')
|
||||
|
||||
def forward(self, image, text):
|
||||
similarity = 1 - self.model(image, text)[0] / 100
|
||||
return similarity
|
||||
|
||||
|
||||
class CLIPLoss_relative(torch.nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super(CLIPLoss_relative, self).__init__()
|
||||
self.model, self.preprocess = clip.load('ViT-B/32', device='cuda')
|
||||
|
||||
def forward(self, image, text, image_ori, text_ori):
|
||||
|
||||
image_features = self.model.encode_image(image)
|
||||
text_features = self.model.encode_text(text)
|
||||
|
||||
# normalized features
|
||||
image_features = image_features / image_features.norm(
|
||||
dim=1, keepdim=True)
|
||||
text_features = text_features / text_features.norm(dim=1, keepdim=True)
|
||||
|
||||
image_features_ori = self.model.encode_image(image_ori)
|
||||
text_features_ori = self.model.encode_text(text_ori)
|
||||
|
||||
# normalized features
|
||||
image_features_ori = image_features_ori / image_features_ori.norm(
|
||||
dim=1, keepdim=True)
|
||||
text_features_ori = text_features_ori / text_features_ori.norm(
|
||||
dim=1, keepdim=True)
|
||||
|
||||
delta_image = image_features - image_features_ori
|
||||
delta_text = text_features - text_features_ori
|
||||
|
||||
loss = 1 - torch.sum(delta_image * delta_text) / (
|
||||
torch.norm(delta_image) * torch.norm(delta_text))
|
||||
|
||||
return loss
|
||||
|
||||
|
||||
# perceptual level loss
|
||||
class PerceptualLoss(nn.Module):
|
||||
|
||||
def __init__(self, recog_net, input_size=112):
|
||||
super(PerceptualLoss, self).__init__()
|
||||
self.recog_net = recog_net
|
||||
self.preprocess = lambda x: 2 * x - 1
|
||||
self.input_size = input_size
|
||||
|
||||
def forward(self, imageA, imageB, M):
|
||||
"""
|
||||
1 - cosine distance
|
||||
Parameters:
|
||||
imageA --torch.tensor (B, 3, H, W), range (0, 1) , RGB order
|
||||
imageB --same as imageA
|
||||
"""
|
||||
|
||||
imageA = self.preprocess(resize_n_crop(imageA, M, self.input_size))
|
||||
imageB = self.preprocess(resize_n_crop(imageB, M, self.input_size))
|
||||
|
||||
# freeze bn
|
||||
self.recog_net.eval()
|
||||
|
||||
id_featureA = F.normalize(self.recog_net(imageA), dim=-1, p=2)
|
||||
id_featureB = F.normalize(self.recog_net(imageB), dim=-1, p=2)
|
||||
cosine_d = torch.sum(id_featureA * id_featureB, dim=-1)
|
||||
return torch.sum(1 - cosine_d) / cosine_d.shape[0]
|
||||
|
||||
|
||||
def perceptual_loss(id_featureA, id_featureB):
|
||||
cosine_d = torch.sum(id_featureA * id_featureB, dim=-1)
|
||||
return torch.sum(1 - cosine_d) / cosine_d.shape[0]
|
||||
|
||||
|
||||
# image level loss
|
||||
def photo_loss(imageA, imageB, mask, eps=1e-6):
|
||||
"""
|
||||
l2 norm (with sqrt, to ensure backward stabililty, use eps, otherwise Nan may occur)
|
||||
Parameters:
|
||||
imageA --torch.tensor (B, 3, H, W), range (0, 1), RGB order
|
||||
imageB --same as imageA
|
||||
"""
|
||||
loss = torch.sqrt(eps + torch.sum(
|
||||
(imageA - imageB)**2, dim=1, keepdims=True)) * mask
|
||||
loss = torch.sum(loss) / torch.max(
|
||||
torch.sum(mask),
|
||||
torch.tensor(1.0).to(mask.device))
|
||||
return loss
|
||||
|
||||
|
||||
def landmark_loss(predict_lm, gt_lm, weight=None):
|
||||
"""
|
||||
weighted mse loss
|
||||
Parameters:
|
||||
predict_lm --torch.tensor (B, 68, 2)
|
||||
gt_lm --torch.tensor (B, 68, 2)
|
||||
weight --numpy.array (1, 68)
|
||||
"""
|
||||
if not weight:
|
||||
weight = np.ones([68])
|
||||
weight[28:31] = 20
|
||||
weight[-8:] = 20
|
||||
weight = np.expand_dims(weight, 0)
|
||||
weight = torch.tensor(weight).to(predict_lm.device)
|
||||
loss = torch.sum((predict_lm - gt_lm)**2, dim=-1) * weight
|
||||
loss = torch.sum(loss) / (predict_lm.shape[0] * predict_lm.shape[1])
|
||||
return loss
|
||||
|
||||
|
||||
# regulization
|
||||
def reg_loss(coeffs_dict, w_id=1, w_exp=1, w_tex=1):
|
||||
"""
|
||||
l2 norm without the sqrt, from yu's implementation (mse)
|
||||
tf.nn.l2_loss https://www.tensorflow.org/api_docs/python/tf/nn/l2_loss
|
||||
Parameters:
|
||||
coeffs_dict -- a dict of torch.tensors , keys: id, exp, tex, angle, gamma, trans
|
||||
|
||||
"""
|
||||
# coefficient regularization to ensure plausible 3d faces
|
||||
value_1 = w_id * torch.sum(coeffs_dict['id']**2)
|
||||
value_2 = w_exp * torch.sum(coeffs_dict['exp']**2)
|
||||
value_3 = w_tex * torch.sum(coeffs_dict['tex']**2)
|
||||
creg_loss = value_1 + value_2 + value_3
|
||||
creg_loss = creg_loss / coeffs_dict['id'].shape[0]
|
||||
|
||||
# gamma regularization to ensure a nearly-monochromatic light
|
||||
gamma = coeffs_dict['gamma'].reshape([-1, 3, 9])
|
||||
gamma_mean = torch.mean(gamma, dim=1, keepdims=True)
|
||||
gamma_loss = torch.mean((gamma - gamma_mean)**2)
|
||||
|
||||
return creg_loss, gamma_loss
|
||||
|
||||
|
||||
def reflectance_loss(texture, mask):
|
||||
"""
|
||||
minimize texture variance (mse), albedo regularization to ensure an uniform skin albedo
|
||||
Parameters:
|
||||
texture --torch.tensor, (B, N, 3)
|
||||
mask --torch.tensor, (N), 1 or 0
|
||||
|
||||
"""
|
||||
mask = mask.reshape([1, mask.shape[0], 1])
|
||||
texture_mean = torch.sum(
|
||||
mask * texture, dim=1, keepdims=True) / torch.sum(mask)
|
||||
loss = torch.sum(((texture - texture_mean) * mask)**2) / (
|
||||
texture.shape[0] * torch.sum(mask))
|
||||
return loss
|
||||
|
||||
|
||||
def lm_3d_loss(pred_lm_3d, gt_lm_3d, mask):
|
||||
loss = torch.abs(pred_lm_3d - gt_lm_3d)[mask, :]
|
||||
loss = torch.mean(loss)
|
||||
return loss
|
||||
|
||||
|
||||
class TVLoss(nn.Module):
|
||||
|
||||
def __init__(self, TVLoss_weight=1):
|
||||
super(TVLoss, self).__init__()
|
||||
self.TVLoss_weight = TVLoss_weight
|
||||
|
||||
def forward(self, x):
|
||||
batch_size = x.size()[0]
|
||||
h_x = x.size()[2]
|
||||
w_x = x.size()[3]
|
||||
count_h = self._tensor_size(x[:, :, 1:, :])
|
||||
count_w = self._tensor_size(x[:, :, :, 1:])
|
||||
h_tv = torch.pow((x[:, :, 1:, :] - x[:, :, :h_x - 1, :]), 2).sum()
|
||||
w_tv = torch.pow((x[:, :, :, 1:] - x[:, :, :, :w_x - 1]), 2).sum()
|
||||
return self.TVLoss_weight * 2 * (h_tv / count_h
|
||||
+ w_tv / count_w) / batch_size
|
||||
|
||||
def _tensor_size(self, t):
|
||||
return t.size()[1] * t.size()[2] * t.size()[3]
|
||||
|
||||
|
||||
class TVLoss_std(nn.Module):
|
||||
|
||||
def __init__(self, TVLoss_weight=1):
|
||||
super(TVLoss_std, self).__init__()
|
||||
self.TVLoss_weight = TVLoss_weight
|
||||
|
||||
def forward(self, x):
|
||||
batch_size = x.size()[0]
|
||||
h_x = x.size()[2]
|
||||
w_x = x.size()[3]
|
||||
h_tv = torch.pow((x[:, :, 1:, :] - x[:, :, :h_x - 1, :]), 2)
|
||||
h_tv = ((h_tv - torch.mean(h_tv))**2).sum()
|
||||
w_tv = torch.pow((x[:, :, :, 1:] - x[:, :, :, :w_x - 1]), 2)
|
||||
w_tv = ((w_tv - torch.mean(w_tv))**2).sum()
|
||||
return self.TVLoss_weight * 2 * (h_tv + w_tv) / batch_size
|
||||
|
||||
def _tensor_size(self, t):
|
||||
return t.size()[1] * t.size()[2] * t.size()[3]
|
||||
|
||||
|
||||
def photo_loss_sum(imageA, imageB, mask, eps=1e-6):
|
||||
"""
|
||||
l2 norm (with sqrt, to ensure backward stabililty, use eps, otherwise Nan may occur)
|
||||
Parameters:
|
||||
imageA --torch.tensor (B, 3, H, W), range (0, 1), RGB order
|
||||
imageB --same as imageA
|
||||
"""
|
||||
loss = torch.sqrt(eps + torch.sum(
|
||||
(imageA - imageB)**2, dim=1, keepdims=True)) * mask
|
||||
loss = torch.sum(loss) / (
|
||||
imageA.shape[0] * imageA.shape[2] * imageA.shape[3])
|
||||
return loss
|
||||
|
||||
|
||||
def points_loss_horizontal(verts, left_points, right_points, width=224):
|
||||
verts_int = torch.ceil(verts[0]).long().clamp(0, width - 1) # (n, 2)
|
||||
verts_left = left_points[width - 1 - verts_int[:, 1]].float()
|
||||
verts_right = right_points[width - 1 - verts_int[:, 1]].float()
|
||||
verts_x = verts[0, :, 0]
|
||||
dist = (verts_left - verts_x) / width * (verts_right - verts_x) / width
|
||||
dist /= torch.max(
|
||||
torch.abs((verts_left - verts_x) / width),
|
||||
torch.abs((verts_right - verts_x) / width))
|
||||
edge_inds = torch.where(dist > 0)[0]
|
||||
dist += 0.01
|
||||
dist = torch.nn.functional.relu(dist).clone()
|
||||
dist -= 0.01
|
||||
dist = torch.abs(dist)
|
||||
loss = torch.mean(dist)
|
||||
return loss, edge_inds
|
||||
|
||||
|
||||
class LaplacianLoss(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super(LaplacianLoss, self).__init__()
|
||||
|
||||
def forward(self, x):
|
||||
batch_size, slice_num = x.size()[:2]
|
||||
z_x = x.size()[2]
|
||||
h_x = x.size()[3]
|
||||
w_x = x.size()[4]
|
||||
count_z = self._tensor_size(x[:, :, 1:, :, :])
|
||||
count_h = self._tensor_size(x[:, :, :, 1:, :])
|
||||
count_w = self._tensor_size(x[:, :, :, :, 1:])
|
||||
z_tv = torch.pow((x[:, :, 1:, :, :] - x[:, :, :z_x - 1, :, :]),
|
||||
2).sum()
|
||||
h_tv = torch.pow((x[:, :, :, 1:, :] - x[:, :, :, :h_x - 1, :]),
|
||||
2).sum()
|
||||
w_tv = torch.pow((x[:, :, :, :, 1:] - x[:, :, :, :, :w_x - 1]),
|
||||
2).sum()
|
||||
return 2 * (z_tv / count_z + h_tv / count_h + w_tv / count_w) / (
|
||||
batch_size * slice_num)
|
||||
|
||||
def _tensor_size(self, t):
|
||||
return t.size()[2] * t.size()[3] * t.size()[4]
|
||||
|
||||
|
||||
class LaplacianLoss_L1(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super(LaplacianLoss_L1, self).__init__()
|
||||
|
||||
def forward(self, x):
|
||||
batch_size, slice_num = x.size()[:2]
|
||||
z_x = x.size()[2]
|
||||
h_x = x.size()[3]
|
||||
w_x = x.size()[4]
|
||||
count_z = self._tensor_size(x[:, :, 1:, :, :])
|
||||
count_h = self._tensor_size(x[:, :, :, 1:, :])
|
||||
count_w = self._tensor_size(x[:, :, :, :, 1:])
|
||||
z_tv = torch.abs((x[:, :, 1:, :, :] - x[:, :, :z_x - 1, :, :])).sum()
|
||||
h_tv = torch.abs((x[:, :, :, 1:, :] - x[:, :, :, :h_x - 1, :])).sum()
|
||||
w_tv = torch.abs((x[:, :, :, :, 1:] - x[:, :, :, :, :w_x - 1])).sum()
|
||||
return 2 * (z_tv / count_z + h_tv / count_h + w_tv / count_w) / (
|
||||
batch_size * slice_num)
|
||||
|
||||
def _tensor_size(self, t):
|
||||
return t.size()[2] * t.size()[3] * t.size()[4]
|
||||
|
||||
|
||||
class GANLoss(nn.Module):
|
||||
|
||||
def __init__(self,
|
||||
gan_mode,
|
||||
target_real_label=1.0,
|
||||
target_fake_label=0.0,
|
||||
tensor=torch.FloatTensor):
|
||||
super(GANLoss, self).__init__()
|
||||
self.real_label = target_real_label
|
||||
self.fake_label = target_fake_label
|
||||
self.real_label_tensor = None
|
||||
self.fake_label_tensor = None
|
||||
self.zero_tensor = None
|
||||
self.Tensor = tensor
|
||||
self.gan_mode = gan_mode
|
||||
if gan_mode == 'ls':
|
||||
pass
|
||||
elif gan_mode == 'original':
|
||||
pass
|
||||
elif gan_mode == 'w':
|
||||
pass
|
||||
elif gan_mode == 'hinge':
|
||||
pass
|
||||
else:
|
||||
raise ValueError('Unexpected gan_mode {}'.format(gan_mode))
|
||||
|
||||
def get_target_tensor(self, input, target_is_real):
|
||||
if target_is_real:
|
||||
if self.real_label_tensor is None:
|
||||
self.real_label_tensor = self.Tensor(1).fill_(self.real_label)
|
||||
self.real_label_tensor.requires_grad_(False)
|
||||
return self.real_label_tensor.expand_as(input)
|
||||
else:
|
||||
if self.fake_label_tensor is None:
|
||||
self.fake_label_tensor = self.Tensor(1).fill_(self.fake_label)
|
||||
self.fake_label_tensor.requires_grad_(False)
|
||||
return self.fake_label_tensor.expand_as(input)
|
||||
|
||||
def get_zero_tensor(self, input):
|
||||
if self.zero_tensor is None:
|
||||
self.zero_tensor = self.Tensor(1).fill_(0)
|
||||
self.zero_tensor.requires_grad_(False)
|
||||
return self.zero_tensor.expand_as(input)
|
||||
|
||||
def loss(self, input, target_is_real, for_discriminator=True):
|
||||
if self.gan_mode == 'original': # cross entropy loss
|
||||
target_tensor = self.get_target_tensor(input, target_is_real)
|
||||
loss = F.binary_cross_entropy_with_logits(input, target_tensor)
|
||||
return loss
|
||||
elif self.gan_mode == 'ls':
|
||||
target_tensor = self.get_target_tensor(input, target_is_real)
|
||||
return F.mse_loss(input, target_tensor)
|
||||
elif self.gan_mode == 'hinge':
|
||||
if for_discriminator:
|
||||
if target_is_real:
|
||||
minval = torch.min(input - 1, self.get_zero_tensor(input))
|
||||
loss = -torch.mean(minval)
|
||||
else:
|
||||
minval = torch.min(-input - 1, self.get_zero_tensor(input))
|
||||
loss = -torch.mean(minval)
|
||||
else:
|
||||
assert target_is_real, "The generator's hinge loss must be aiming for real"
|
||||
loss = -torch.mean(input)
|
||||
return loss
|
||||
else:
|
||||
# wgan
|
||||
if target_is_real:
|
||||
return -input.mean()
|
||||
else:
|
||||
return input.mean()
|
||||
|
||||
def __call__(self, input, target_is_real, for_discriminator=True):
|
||||
# computing loss is a bit complicated because |input| may not be
|
||||
# a tensor, but list of tensors in case of multiscale discriminator
|
||||
if isinstance(input, list):
|
||||
loss = 0
|
||||
for pred_i in input:
|
||||
if isinstance(pred_i, list):
|
||||
pred_i = pred_i[-1]
|
||||
loss_tensor = self.loss(pred_i, target_is_real,
|
||||
for_discriminator)
|
||||
bs = 1 if len(loss_tensor.size()) == 0 else loss_tensor.size(0)
|
||||
new_loss = torch.mean(loss_tensor.view(bs, -1), dim=1)
|
||||
loss += new_loss
|
||||
return loss / len(input)
|
||||
else:
|
||||
return self.loss(input, target_is_real, for_discriminator)
|
||||
|
||||
|
||||
class BinaryDiceLoss(nn.Module):
|
||||
|
||||
def __init__(self, smooth=1, p=1, reduction='mean'):
|
||||
super(BinaryDiceLoss, self).__init__()
|
||||
self.smooth = smooth
|
||||
self.p = p
|
||||
self.reduction = reduction
|
||||
|
||||
def forward(self, predict, target):
|
||||
assert predict.shape[0] == target.shape[
|
||||
0], "predict & target batch size don't match"
|
||||
predict = predict.contiguous().view(predict.shape[0], -1)
|
||||
target = target.contiguous().view(target.shape[0], -1)
|
||||
|
||||
num = torch.sum(torch.mul(predict, target), dim=1)
|
||||
den = torch.sum(predict + target, dim=1)
|
||||
|
||||
loss = 1 - (2 * num + self.smooth) / (den + self.smooth)
|
||||
|
||||
if self.reduction == 'mean':
|
||||
return loss.mean()
|
||||
elif self.reduction == 'sum':
|
||||
return loss.sum()
|
||||
elif self.reduction == 'none':
|
||||
return loss
|
||||
else:
|
||||
raise Exception('Unexpected reduction {}'.format(self.reduction))
|
||||
577
modelscope/models/cv/face_reconstruction/models/networks.py
Normal file
577
modelscope/models/cv/face_reconstruction/models/networks.py
Normal file
@@ -0,0 +1,577 @@
|
||||
# Part of the implementation is borrowed and modified from Deep3DFaceRecon_pytorch,
|
||||
# publicly available at https://github.com/sicxu/Deep3DFaceRecon_pytorch
|
||||
import os
|
||||
from typing import Any, Callable, List, Optional, Type, Union
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from kornia.geometry import warp_affine
|
||||
from torch import Tensor
|
||||
from torch.optim import lr_scheduler
|
||||
|
||||
try:
|
||||
from torch.hub import load_state_dict_from_url
|
||||
except ImportError:
|
||||
from torch.utils.model_zoo import load_url as load_state_dict_from_url
|
||||
|
||||
|
||||
def resize_n_crop(image, M, dsize=112):
|
||||
# image: (b, c, h, w)
|
||||
# M : (b, 2, 3)
|
||||
return warp_affine(image, M, dsize=(dsize, dsize))
|
||||
|
||||
|
||||
def filter_state_dict(state_dict, remove_name='fc'):
|
||||
new_state_dict = {}
|
||||
for key in state_dict:
|
||||
if remove_name in key:
|
||||
continue
|
||||
new_state_dict[key] = state_dict[key]
|
||||
return new_state_dict
|
||||
|
||||
|
||||
def define_net_recon(net_recon, use_last_fc=False, init_path=None):
|
||||
return ReconNetWrapper(
|
||||
net_recon, use_last_fc=use_last_fc, init_path=init_path)
|
||||
|
||||
|
||||
def define_net_recon2(net_recon, use_last_fc=False, init_path=None):
|
||||
return ReconNetWrapper2(
|
||||
net_recon, use_last_fc=use_last_fc, init_path=init_path)
|
||||
|
||||
|
||||
class ReconNetWrapper(nn.Module):
|
||||
fc_dim = 257
|
||||
|
||||
def __init__(self, net_recon, use_last_fc=False, init_path=None):
|
||||
super(ReconNetWrapper, self).__init__()
|
||||
self.use_last_fc = use_last_fc
|
||||
if net_recon not in func_dict:
|
||||
return NotImplementedError('network [%s] is not implemented',
|
||||
net_recon)
|
||||
func, last_dim = func_dict[net_recon]
|
||||
backbone = func(use_last_fc=use_last_fc, num_classes=self.fc_dim)
|
||||
if init_path and os.path.isfile(init_path):
|
||||
state_dict = filter_state_dict(
|
||||
torch.load(init_path, map_location='cpu'))
|
||||
backbone.load_state_dict(state_dict)
|
||||
print('loading init net_recon %s from %s' % (net_recon, init_path))
|
||||
self.backbone = backbone
|
||||
if not use_last_fc:
|
||||
self.final_layers = nn.ModuleList([
|
||||
conv1x1(last_dim, 80, bias=True), # id layer
|
||||
conv1x1(last_dim, 64, bias=True), # exp layer
|
||||
conv1x1(last_dim, 80, bias=True), # tex layer
|
||||
conv1x1(last_dim, 3, bias=True), # angle layer
|
||||
conv1x1(last_dim, 27, bias=True), # gamma layer
|
||||
conv1x1(last_dim, 2, bias=True), # tx, ty
|
||||
conv1x1(last_dim, 1, bias=True) # tz
|
||||
])
|
||||
for m in self.final_layers:
|
||||
nn.init.constant_(m.weight, 0.)
|
||||
nn.init.constant_(m.bias, 0.)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.backbone(x)
|
||||
if not self.use_last_fc:
|
||||
output = []
|
||||
for layer in self.final_layers:
|
||||
output.append(layer(x))
|
||||
x = torch.flatten(torch.cat(output, dim=1), 1)
|
||||
return x
|
||||
|
||||
|
||||
class ReconNetWrapper2(nn.Module):
|
||||
fc_dim = 264
|
||||
|
||||
def __init__(self, net_recon, use_last_fc=False, init_path=None):
|
||||
super(ReconNetWrapper2, self).__init__()
|
||||
self.use_last_fc = use_last_fc
|
||||
if net_recon not in func_dict:
|
||||
return NotImplementedError('network [%s] is not implemented',
|
||||
net_recon)
|
||||
func, last_dim = func_dict[net_recon]
|
||||
backbone = func(use_last_fc=use_last_fc, num_classes=self.fc_dim)
|
||||
if init_path and os.path.isfile(init_path):
|
||||
state_dict = filter_state_dict(
|
||||
torch.load(init_path, map_location='cpu'))
|
||||
backbone.load_state_dict(state_dict)
|
||||
print('loading init net_recon %s from %s' % (net_recon, init_path))
|
||||
self.backbone = backbone
|
||||
if not use_last_fc:
|
||||
self.final_layers2 = nn.ModuleList([
|
||||
conv1x1(last_dim, 80, bias=True), # id layer
|
||||
conv1x1(last_dim, 51, bias=True), # exp layer
|
||||
conv1x1(last_dim, 100, bias=True), # tex layer
|
||||
conv1x1(last_dim, 3, bias=True), # angle layer
|
||||
conv1x1(last_dim, 27, bias=True), # gamma layer
|
||||
conv1x1(last_dim, 2, bias=True), # tx, ty
|
||||
conv1x1(last_dim, 1, bias=True) # tz
|
||||
])
|
||||
for m in self.final_layers2:
|
||||
nn.init.constant_(m.weight, 0.)
|
||||
nn.init.constant_(m.bias, 0.)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.backbone(x)
|
||||
if not self.use_last_fc:
|
||||
output = []
|
||||
for layer in self.final_layers2:
|
||||
output.append(layer(x))
|
||||
x = torch.flatten(torch.cat(output, dim=1), 1)
|
||||
return x
|
||||
|
||||
|
||||
# adapted from https://github.com/pytorch/vision/edit/master/torchvision/models/resnet.py
|
||||
__all__ = [
|
||||
'ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101', 'resnet152',
|
||||
'resnext50_32x4d', 'resnext101_32x8d', 'wide_resnet50_2',
|
||||
'wide_resnet101_2'
|
||||
]
|
||||
|
||||
model_urls = {
|
||||
'resnet18':
|
||||
'https://download.pytorch.org/models/resnet18-f37072fd.pth',
|
||||
'resnet34':
|
||||
'https://download.pytorch.org/models/resnet34-b627a593.pth',
|
||||
'resnet50':
|
||||
'https://download.pytorch.org/models/resnet50-0676ba61.pth',
|
||||
'resnet101':
|
||||
'https://download.pytorch.org/models/resnet101-63fe2227.pth',
|
||||
'resnet152':
|
||||
'https://download.pytorch.org/models/resnet152-394f9c45.pth',
|
||||
'resnext50_32x4d':
|
||||
'https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth',
|
||||
'resnext101_32x8d':
|
||||
'https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth',
|
||||
'wide_resnet50_2':
|
||||
'https://download.pytorch.org/models/wide_resnet50_2-95faca4d.pth',
|
||||
'wide_resnet101_2':
|
||||
'https://download.pytorch.org/models/wide_resnet101_2-32ee1156.pth',
|
||||
}
|
||||
|
||||
|
||||
def conv3x3(in_planes: int,
|
||||
out_planes: int,
|
||||
stride: int = 1,
|
||||
groups: int = 1,
|
||||
dilation: int = 1) -> nn.Conv2d:
|
||||
"""3x3 convolution with padding"""
|
||||
return nn.Conv2d(
|
||||
in_planes,
|
||||
out_planes,
|
||||
kernel_size=3,
|
||||
stride=stride,
|
||||
padding=dilation,
|
||||
groups=groups,
|
||||
bias=False,
|
||||
dilation=dilation)
|
||||
|
||||
|
||||
def conv1x1(in_planes: int,
|
||||
out_planes: int,
|
||||
stride: int = 1,
|
||||
bias: bool = False) -> nn.Conv2d:
|
||||
"""1x1 convolution"""
|
||||
return nn.Conv2d(
|
||||
in_planes, out_planes, kernel_size=1, stride=stride, bias=bias)
|
||||
|
||||
|
||||
class BasicBlock(nn.Module):
|
||||
expansion: int = 1
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
inplanes: int,
|
||||
planes: int,
|
||||
stride: int = 1,
|
||||
downsample: Optional[nn.Module] = None,
|
||||
groups: int = 1,
|
||||
base_width: int = 64,
|
||||
dilation: int = 1,
|
||||
norm_layer: Optional[Callable[..., nn.Module]] = None) -> None:
|
||||
super(BasicBlock, self).__init__()
|
||||
if norm_layer is None:
|
||||
norm_layer = nn.BatchNorm2d
|
||||
if groups != 1 or base_width != 64:
|
||||
raise ValueError(
|
||||
'BasicBlock only supports groups=1 and base_width=64')
|
||||
if dilation > 1:
|
||||
raise NotImplementedError(
|
||||
'Dilation > 1 not supported in BasicBlock')
|
||||
# Both self.conv1 and self.downsample layers downsample the input when stride != 1
|
||||
self.conv1 = conv3x3(inplanes, planes, stride)
|
||||
self.bn1 = norm_layer(planes)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.conv2 = conv3x3(planes, planes)
|
||||
self.bn2 = norm_layer(planes)
|
||||
self.downsample = downsample
|
||||
self.stride = stride
|
||||
|
||||
def forward(self, x: Tensor) -> Tensor:
|
||||
identity = x
|
||||
|
||||
out = self.conv1(x)
|
||||
out = self.bn1(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv2(out)
|
||||
out = self.bn2(out)
|
||||
|
||||
if self.downsample is not None:
|
||||
identity = self.downsample(x)
|
||||
|
||||
out += identity
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class Bottleneck(nn.Module):
|
||||
# Bottleneck in torchvision places the stride for downsampling at 3x3 convolution(self.conv2)
|
||||
# while original implementation places the stride at the first 1x1 convolution(self.conv1)
|
||||
# according to "Deep residual learning for image recognition"https://arxiv.org/abs/1512.03385.
|
||||
# This variant is also known as ResNet V1.5 and improves accuracy according to
|
||||
# https://ngc.nvidia.com/catalog/model-scripts/nvidia:resnet_50_v1_5_for_pytorch.
|
||||
|
||||
expansion: int = 4
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
inplanes: int,
|
||||
planes: int,
|
||||
stride: int = 1,
|
||||
downsample: Optional[nn.Module] = None,
|
||||
groups: int = 1,
|
||||
base_width: int = 64,
|
||||
dilation: int = 1,
|
||||
norm_layer: Optional[Callable[..., nn.Module]] = None) -> None:
|
||||
super(Bottleneck, self).__init__()
|
||||
if norm_layer is None:
|
||||
norm_layer = nn.BatchNorm2d
|
||||
width = int(planes * (base_width / 64.)) * groups
|
||||
# Both self.conv2 and self.downsample layers downsample the input when stride != 1
|
||||
self.conv1 = conv1x1(inplanes, width)
|
||||
self.bn1 = norm_layer(width)
|
||||
self.conv2 = conv3x3(width, width, stride, groups, dilation)
|
||||
self.bn2 = norm_layer(width)
|
||||
self.conv3 = conv1x1(width, planes * self.expansion)
|
||||
self.bn3 = norm_layer(planes * self.expansion)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.downsample = downsample
|
||||
self.stride = stride
|
||||
|
||||
def forward(self, x: Tensor) -> Tensor:
|
||||
identity = x
|
||||
|
||||
out = self.conv1(x)
|
||||
out = self.bn1(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv2(out)
|
||||
out = self.bn2(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv3(out)
|
||||
out = self.bn3(out)
|
||||
|
||||
if self.downsample is not None:
|
||||
identity = self.downsample(x)
|
||||
|
||||
out += identity
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class ResNet(nn.Module):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
block: Type[Union[BasicBlock, Bottleneck]],
|
||||
layers: List[int],
|
||||
num_classes: int = 1000,
|
||||
zero_init_residual: bool = False,
|
||||
use_last_fc: bool = False,
|
||||
groups: int = 1,
|
||||
width_per_group: int = 64,
|
||||
replace_stride_with_dilation: Optional[List[bool]] = None,
|
||||
norm_layer: Optional[Callable[..., nn.Module]] = None) -> None:
|
||||
super(ResNet, self).__init__()
|
||||
if norm_layer is None:
|
||||
norm_layer = nn.BatchNorm2d
|
||||
self._norm_layer = norm_layer
|
||||
|
||||
self.inplanes = 64
|
||||
self.dilation = 1
|
||||
if replace_stride_with_dilation is None:
|
||||
# each element in the tuple indicates if we should replace
|
||||
# the 2x2 stride with a dilated convolution instead
|
||||
replace_stride_with_dilation = [False, False, False]
|
||||
if len(replace_stride_with_dilation) != 3:
|
||||
raise ValueError('replace_stride_with_dilation should be None '
|
||||
'or a 3-element tuple, got {}'.format(
|
||||
replace_stride_with_dilation))
|
||||
self.use_last_fc = use_last_fc
|
||||
self.groups = groups
|
||||
self.base_width = width_per_group
|
||||
self.conv1 = nn.Conv2d(
|
||||
3, self.inplanes, kernel_size=7, stride=2, padding=3, bias=False)
|
||||
self.bn1 = norm_layer(self.inplanes)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
||||
self.layer1 = self._make_layer(block, 64, layers[0])
|
||||
self.layer2 = self._make_layer(
|
||||
block,
|
||||
128,
|
||||
layers[1],
|
||||
stride=2,
|
||||
dilate=replace_stride_with_dilation[0])
|
||||
self.layer3 = self._make_layer(
|
||||
block,
|
||||
256,
|
||||
layers[2],
|
||||
stride=2,
|
||||
dilate=replace_stride_with_dilation[1])
|
||||
self.layer4 = self._make_layer(
|
||||
block,
|
||||
512,
|
||||
layers[3],
|
||||
stride=2,
|
||||
dilate=replace_stride_with_dilation[2])
|
||||
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
|
||||
|
||||
if self.use_last_fc:
|
||||
self.fc = nn.Linear(512 * block.expansion, num_classes)
|
||||
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
nn.init.kaiming_normal_(
|
||||
m.weight, mode='fan_out', nonlinearity='relu')
|
||||
elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
|
||||
nn.init.constant_(m.weight, 1)
|
||||
nn.init.constant_(m.bias, 0)
|
||||
|
||||
# Zero-initialize the last BN in each residual branch,
|
||||
# so that the residual branch starts with zeros, and each residual block behaves like an identity.
|
||||
# This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677
|
||||
if zero_init_residual:
|
||||
for m in self.modules():
|
||||
if isinstance(m, Bottleneck):
|
||||
nn.init.constant_(m.bn3.weight,
|
||||
0) # type: ignore[arg-type]
|
||||
elif isinstance(m, BasicBlock):
|
||||
nn.init.constant_(m.bn2.weight,
|
||||
0) # type: ignore[arg-type]
|
||||
|
||||
def _make_layer(self,
|
||||
block: Type[Union[BasicBlock, Bottleneck]],
|
||||
planes: int,
|
||||
blocks: int,
|
||||
stride: int = 1,
|
||||
dilate: bool = False) -> nn.Sequential:
|
||||
norm_layer = self._norm_layer
|
||||
downsample = None
|
||||
previous_dilation = self.dilation
|
||||
if dilate:
|
||||
self.dilation *= stride
|
||||
stride = 1
|
||||
if stride != 1 or self.inplanes != planes * block.expansion:
|
||||
downsample = nn.Sequential(
|
||||
conv1x1(self.inplanes, planes * block.expansion, stride),
|
||||
norm_layer(planes * block.expansion),
|
||||
)
|
||||
|
||||
layers = []
|
||||
layers.append(
|
||||
block(self.inplanes, planes, stride, downsample, self.groups,
|
||||
self.base_width, previous_dilation, norm_layer))
|
||||
self.inplanes = planes * block.expansion
|
||||
for _ in range(1, blocks):
|
||||
layers.append(
|
||||
block(
|
||||
self.inplanes,
|
||||
planes,
|
||||
groups=self.groups,
|
||||
base_width=self.base_width,
|
||||
dilation=self.dilation,
|
||||
norm_layer=norm_layer))
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def _forward_impl(self, x: Tensor) -> Tensor:
|
||||
# See note [TorchScript super()]
|
||||
x = self.conv1(x)
|
||||
x = self.bn1(x)
|
||||
x = self.relu(x)
|
||||
x = self.maxpool(x)
|
||||
|
||||
x = self.layer1(x)
|
||||
x = self.layer2(x)
|
||||
x = self.layer3(x)
|
||||
x = self.layer4(x)
|
||||
|
||||
x = self.avgpool(x)
|
||||
if self.use_last_fc:
|
||||
x = torch.flatten(x, 1)
|
||||
x = self.fc(x)
|
||||
return x
|
||||
|
||||
def forward(self, x: Tensor) -> Tensor:
|
||||
return self._forward_impl(x)
|
||||
|
||||
|
||||
def _resnet(arch: str, block: Type[Union[BasicBlock,
|
||||
Bottleneck]], layers: List[int],
|
||||
pretrained: bool, progress: bool, **kwargs: Any) -> ResNet:
|
||||
model = ResNet(block, layers, **kwargs)
|
||||
if pretrained:
|
||||
state_dict = load_state_dict_from_url(
|
||||
model_urls[arch], progress=progress)
|
||||
model.load_state_dict(state_dict)
|
||||
return model
|
||||
|
||||
|
||||
def resnet18(pretrained: bool = False,
|
||||
progress: bool = True,
|
||||
**kwargs: Any) -> ResNet:
|
||||
r"""ResNet-18 model from
|
||||
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_.
|
||||
|
||||
Args:
|
||||
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
||||
progress (bool): If True, displays a progress bar of the download to stderr
|
||||
"""
|
||||
return _resnet('resnet18', BasicBlock, [2, 2, 2, 2], pretrained, progress,
|
||||
**kwargs)
|
||||
|
||||
|
||||
def resnet34(pretrained: bool = False,
|
||||
progress: bool = True,
|
||||
**kwargs: Any) -> ResNet:
|
||||
r"""ResNet-34 model from
|
||||
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_.
|
||||
|
||||
Args:
|
||||
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
||||
progress (bool): If True, displays a progress bar of the download to stderr
|
||||
"""
|
||||
return _resnet('resnet34', BasicBlock, [3, 4, 6, 3], pretrained, progress,
|
||||
**kwargs)
|
||||
|
||||
|
||||
def resnet50(pretrained: bool = False,
|
||||
progress: bool = True,
|
||||
**kwargs: Any) -> ResNet:
|
||||
r"""ResNet-50 model from
|
||||
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_.
|
||||
|
||||
Args:
|
||||
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
||||
progress (bool): If True, displays a progress bar of the download to stderr
|
||||
"""
|
||||
return _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress,
|
||||
**kwargs)
|
||||
|
||||
|
||||
def resnet101(pretrained: bool = False,
|
||||
progress: bool = True,
|
||||
**kwargs: Any) -> ResNet:
|
||||
r"""ResNet-101 model from
|
||||
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_.
|
||||
|
||||
Args:
|
||||
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
||||
progress (bool): If True, displays a progress bar of the download to stderr
|
||||
"""
|
||||
return _resnet('resnet101', Bottleneck, [3, 4, 23, 3], pretrained,
|
||||
progress, **kwargs)
|
||||
|
||||
|
||||
def resnet152(pretrained: bool = False,
|
||||
progress: bool = True,
|
||||
**kwargs: Any) -> ResNet:
|
||||
r"""ResNet-152 model from
|
||||
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_.
|
||||
|
||||
Args:
|
||||
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
||||
progress (bool): If True, displays a progress bar of the download to stderr
|
||||
"""
|
||||
return _resnet('resnet152', Bottleneck, [3, 8, 36, 3], pretrained,
|
||||
progress, **kwargs)
|
||||
|
||||
|
||||
def resnext50_32x4d(pretrained: bool = False,
|
||||
progress: bool = True,
|
||||
**kwargs: Any) -> ResNet:
|
||||
r"""ResNeXt-50 32x4d model from
|
||||
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_.
|
||||
|
||||
Args:
|
||||
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
||||
progress (bool): If True, displays a progress bar of the download to stderr
|
||||
"""
|
||||
kwargs['groups'] = 32
|
||||
kwargs['width_per_group'] = 4
|
||||
return _resnet('resnext50_32x4d', Bottleneck, [3, 4, 6, 3], pretrained,
|
||||
progress, **kwargs)
|
||||
|
||||
|
||||
def resnext101_32x8d(pretrained: bool = False,
|
||||
progress: bool = True,
|
||||
**kwargs: Any) -> ResNet:
|
||||
r"""ResNeXt-101 32x8d model from
|
||||
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_.
|
||||
|
||||
Args:
|
||||
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
||||
progress (bool): If True, displays a progress bar of the download to stderr
|
||||
"""
|
||||
kwargs['groups'] = 32
|
||||
kwargs['width_per_group'] = 8
|
||||
return _resnet('resnext101_32x8d', Bottleneck, [3, 4, 23, 3], pretrained,
|
||||
progress, **kwargs)
|
||||
|
||||
|
||||
def wide_resnet50_2(pretrained: bool = False,
|
||||
progress: bool = True,
|
||||
**kwargs: Any) -> ResNet:
|
||||
r"""Wide ResNet-50-2 model from
|
||||
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_.
|
||||
|
||||
The model is the same as ResNet except for the bottleneck number of channels
|
||||
which is twice larger in every block. The number of channels in outer 1x1
|
||||
convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048
|
||||
channels, and in Wide ResNet-50-2 has 2048-1024-2048.
|
||||
|
||||
Args:
|
||||
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
||||
progress (bool): If True, displays a progress bar of the download to stderr
|
||||
"""
|
||||
kwargs['width_per_group'] = 64 * 2
|
||||
return _resnet('wide_resnet50_2', Bottleneck, [3, 4, 6, 3], pretrained,
|
||||
progress, **kwargs)
|
||||
|
||||
|
||||
def wide_resnet101_2(pretrained: bool = False,
|
||||
progress: bool = True,
|
||||
**kwargs: Any) -> ResNet:
|
||||
r"""Wide ResNet-101-2 model from
|
||||
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_.
|
||||
|
||||
The model is the same as ResNet except for the bottleneck number of channels
|
||||
which is twice larger in every block. The number of channels in outer 1x1
|
||||
convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048
|
||||
channels, and in Wide ResNet-50-2 has 2048-1024-2048.
|
||||
|
||||
Args:
|
||||
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
||||
progress (bool): If True, displays a progress bar of the download to stderr
|
||||
"""
|
||||
kwargs['width_per_group'] = 64 * 2
|
||||
return _resnet('wide_resnet101_2', Bottleneck, [3, 4, 23, 3], pretrained,
|
||||
progress, **kwargs)
|
||||
|
||||
|
||||
func_dict = {'resnet18': (resnet18, 512), 'resnet50': (resnet50, 2048)}
|
||||
400
modelscope/models/cv/face_reconstruction/models/nv_diffrast.py
Normal file
400
modelscope/models/cv/face_reconstruction/models/nv_diffrast.py
Normal file
@@ -0,0 +1,400 @@
|
||||
# Part of the implementation is borrowed and modified from Deep3DFaceRecon_pytorch,
|
||||
# publicly available at https://github.com/sicxu/Deep3DFaceRecon_pytorch
|
||||
import warnings
|
||||
from typing import List
|
||||
|
||||
import numpy as np
|
||||
import nvdiffrast.torch as dr
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import nn
|
||||
|
||||
from .losses import TVLoss, TVLoss_std
|
||||
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
|
||||
def ndc_projection(x=0.1, n=1.0, f=50.0):
|
||||
return np.array([[n / x, 0, 0, 0], [0, n / -x, 0, 0],
|
||||
[0, 0, -(f + n) / (f - n), -(2 * f * n) / (f - n)],
|
||||
[0, 0, -1, 0]]).astype(np.float32)
|
||||
|
||||
|
||||
def to_image(face_shape):
|
||||
"""
|
||||
Return:
|
||||
face_proj -- torch.tensor, size (B, N, 2), y direction is opposite to v direction
|
||||
|
||||
Parameters:
|
||||
face_shape -- torch.tensor, size (B, N, 3)
|
||||
"""
|
||||
|
||||
focal = 1015.
|
||||
center = 112.
|
||||
persc_proj = np.array([focal, 0, center, 0, focal, center, 0, 0,
|
||||
1]).reshape([3, 3]).astype(np.float32).transpose()
|
||||
|
||||
persc_proj = torch.tensor(persc_proj).to(face_shape.device)
|
||||
|
||||
face_proj = face_shape @ persc_proj
|
||||
face_proj = face_proj[..., :2] / face_proj[..., 2:]
|
||||
|
||||
return face_proj
|
||||
|
||||
|
||||
class MeshRenderer(nn.Module):
|
||||
|
||||
def __init__(self, rasterize_fov, znear=0.1, zfar=10, rasterize_size=224):
|
||||
super(MeshRenderer, self).__init__()
|
||||
|
||||
x = np.tan(np.deg2rad(rasterize_fov * 0.5)) * znear
|
||||
self.ndc_proj = torch.tensor(ndc_projection(
|
||||
x=x, n=znear,
|
||||
f=zfar)).matmul(torch.diag(torch.tensor([1., -1, -1, 1])))
|
||||
self.rasterize_size = rasterize_size
|
||||
self.glctx = None
|
||||
|
||||
def forward(self, vertex, tri, feat=None):
|
||||
"""
|
||||
Return:
|
||||
mask -- torch.tensor, size (B, 1, H, W)
|
||||
depth -- torch.tensor, size (B, 1, H, W)
|
||||
features(optional) -- torch.tensor, size (B, C, H, W) if feat is not None
|
||||
|
||||
Parameters:
|
||||
vertex -- torch.tensor, size (B, N, 3)
|
||||
tri -- torch.tensor, size (B, M, 3) or (M, 3), triangles
|
||||
feat(optional) -- torch.tensor, size (B, C), features
|
||||
"""
|
||||
device = vertex.device
|
||||
rsize = int(self.rasterize_size)
|
||||
ndc_proj = self.ndc_proj.to(device)
|
||||
verts_proj = to_image(vertex)
|
||||
# trans to homogeneous coordinates of 3d vertices, the direction of y is the same as v
|
||||
if vertex.shape[-1] == 3:
|
||||
vertex = torch.cat(
|
||||
[vertex, torch.ones([*vertex.shape[:2], 1]).to(device)],
|
||||
dim=-1)
|
||||
vertex[..., 1] = -vertex[..., 1]
|
||||
|
||||
vertex_ndc = vertex @ ndc_proj.t()
|
||||
if self.glctx is None:
|
||||
self.glctx = dr.RasterizeCudaContext(device=device)
|
||||
|
||||
ranges = None
|
||||
if isinstance(tri, List) or len(tri.shape) == 3:
|
||||
vum = vertex_ndc.shape[1]
|
||||
fnum = torch.tensor([f.shape[0]
|
||||
for f in tri]).unsqueeze(1).to(device)
|
||||
|
||||
print('fnum shape:{}'.format(fnum.shape))
|
||||
|
||||
fstartidx = torch.cumsum(fnum, dim=0) - fnum
|
||||
ranges = torch.cat([fstartidx, fnum],
|
||||
axis=1).type(torch.int32).cpu()
|
||||
for i in range(tri.shape[0]):
|
||||
tri[i] = tri[i] + i * vum
|
||||
vertex_ndc = torch.cat(vertex_ndc, dim=0)
|
||||
tri = torch.cat(tri, dim=0)
|
||||
|
||||
# for range_mode vetex: [B*N, 4], tri: [B*M, 3], for instance_mode vetex: [B, N, 4], tri: [M, 3]
|
||||
tri = tri.type(torch.int32).contiguous()
|
||||
rast_out, _ = dr.rasterize(
|
||||
self.glctx,
|
||||
vertex_ndc.contiguous(),
|
||||
tri,
|
||||
resolution=[rsize, rsize],
|
||||
ranges=ranges)
|
||||
|
||||
depth, _ = dr.interpolate(
|
||||
vertex.reshape([-1, 4])[..., 2].unsqueeze(1).contiguous(),
|
||||
rast_out, tri)
|
||||
depth = depth.permute(0, 3, 1, 2)
|
||||
mask = (rast_out[..., 3] > 0).float().unsqueeze(1)
|
||||
depth = mask * depth
|
||||
|
||||
image = None
|
||||
|
||||
verts_x = verts_proj[0, :, 0]
|
||||
verts_y = 224 - verts_proj[0, :, 1]
|
||||
verts_int = torch.ceil(verts_proj[0]).long() # (n, 2)
|
||||
verts_xr_int = verts_int[:, 0].clamp(1, 224 - 1)
|
||||
verts_yt_int = 224 - verts_int[:, 1].clamp(2, 224)
|
||||
verts_right_float = verts_xr_int - verts_x
|
||||
verts_left_float = 1 - verts_right_float
|
||||
verts_top_float = verts_y - verts_yt_int
|
||||
verts_bottom_float = 1 - verts_top_float
|
||||
|
||||
rast_lt = rast_out[0, verts_yt_int, verts_xr_int - 1, 3]
|
||||
rast_lb = rast_out[0, verts_yt_int + 1, verts_xr_int - 1, 3]
|
||||
rast_rt = rast_out[0, verts_yt_int, verts_xr_int, 3]
|
||||
rast_rb = rast_out[0, verts_yt_int + 1, verts_xr_int, 3]
|
||||
|
||||
occ_feat = (rast_lt > 0) * 1.0 * (verts_left_float + verts_top_float) + \
|
||||
(rast_lb > 0) * 1.0 * (verts_left_float + verts_bottom_float) + \
|
||||
(rast_rt > 0) * 1.0 * (verts_right_float + verts_top_float) + \
|
||||
(rast_rb > 0) * 1.0 * (verts_right_float + verts_bottom_float)
|
||||
occ_feat = occ_feat[None, :, None] / 4.0
|
||||
|
||||
occ, _ = dr.interpolate(occ_feat, rast_out, tri)
|
||||
occ = occ.permute(0, 3, 1, 2)
|
||||
|
||||
if feat is not None:
|
||||
image, _ = dr.interpolate(feat, rast_out, tri)
|
||||
image = image.permute(0, 3, 1, 2)
|
||||
image = mask * image
|
||||
|
||||
return mask, depth, image, occ
|
||||
|
||||
def render_uv_texture(self, vertex, tri, uv, uv_texture):
|
||||
"""
|
||||
Return:
|
||||
mask -- torch.tensor, size (B, 1, H, W)
|
||||
depth -- torch.tensor, size (B, 1, H, W)
|
||||
features(optional) -- torch.tensor, size (B, C, H, W) if feat is not None
|
||||
|
||||
Parameters:
|
||||
vertex -- torch.tensor, size (B, N, 3)
|
||||
tri -- torch.tensor, size (M, 3), triangles
|
||||
uv -- torch.tensor, size (B,N, 2), uv mapping
|
||||
base_tex -- torch.tensor, size (B,H,W,C)
|
||||
"""
|
||||
device = vertex.device
|
||||
rsize = int(self.rasterize_size)
|
||||
ndc_proj = self.ndc_proj.to(device)
|
||||
# trans to homogeneous coordinates of 3d vertices, the direction of y is the same as v
|
||||
if vertex.shape[-1] == 3:
|
||||
vertex = torch.cat(
|
||||
[vertex, torch.ones([*vertex.shape[:2], 1]).to(device)],
|
||||
dim=-1)
|
||||
vertex[..., 1] = -vertex[..., 1]
|
||||
|
||||
vertex_ndc = vertex @ ndc_proj.t()
|
||||
if self.glctx is None:
|
||||
self.glctx = dr.RasterizeCudaContext(device=device)
|
||||
|
||||
ranges = None
|
||||
if isinstance(tri, List) or len(tri.shape) == 3:
|
||||
vum = vertex_ndc.shape[1]
|
||||
fnum = torch.tensor([f.shape[0]
|
||||
for f in tri]).unsqueeze(1).to(device)
|
||||
|
||||
print('fnum shape:{}'.format(fnum.shape))
|
||||
|
||||
fstartidx = torch.cumsum(fnum, dim=0) - fnum
|
||||
ranges = torch.cat([fstartidx, fnum],
|
||||
axis=1).type(torch.int32).cpu()
|
||||
for i in range(tri.shape[0]):
|
||||
tri[i] = tri[i] + i * vum
|
||||
vertex_ndc = torch.cat(vertex_ndc, dim=0)
|
||||
tri = torch.cat(tri, dim=0)
|
||||
|
||||
# for range_mode vetex: [B*N, 4], tri: [B*M, 3], for instance_mode vetex: [B, N, 4], tri: [M, 3]
|
||||
tri = tri.type(torch.int32).contiguous()
|
||||
rast_out, _ = dr.rasterize(
|
||||
self.glctx,
|
||||
vertex_ndc.contiguous(),
|
||||
tri,
|
||||
resolution=[rsize, rsize],
|
||||
ranges=ranges)
|
||||
|
||||
depth, _ = dr.interpolate(
|
||||
vertex.reshape([-1, 4])[..., 2].unsqueeze(1).contiguous(),
|
||||
rast_out, tri)
|
||||
depth = depth.permute(0, 3, 1, 2)
|
||||
mask = (rast_out[..., 3] > 0).float().unsqueeze(1)
|
||||
depth = mask * depth
|
||||
uv[..., -1] = 1.0 - uv[..., -1]
|
||||
|
||||
rast_out, rast_db = dr.rasterize(
|
||||
self.glctx,
|
||||
vertex_ndc.contiguous(),
|
||||
tri,
|
||||
resolution=[rsize, rsize],
|
||||
ranges=ranges)
|
||||
|
||||
interp_out, uv_da = dr.interpolate(
|
||||
uv, rast_out, tri, rast_db, diff_attrs='all')
|
||||
|
||||
uv_texture = uv_texture.permute(0, 2, 3, 1).contiguous()
|
||||
img = dr.texture(
|
||||
uv_texture, interp_out, filter_mode='linear') # , uv_da)
|
||||
img = img * torch.clamp(rast_out[..., -1:], 0,
|
||||
1) # Mask out background.
|
||||
|
||||
tex_map = uv_texture[0].detach().cpu().numpy()[..., ::-1] * 255.0
|
||||
|
||||
image = img.permute(0, 3, 1, 2)
|
||||
|
||||
return mask, depth, image, tex_map
|
||||
|
||||
def pred_shape_and_texture(self,
|
||||
vertex,
|
||||
tri,
|
||||
uv,
|
||||
target_img,
|
||||
base_tex=None):
|
||||
"""
|
||||
Return:
|
||||
mask -- torch.tensor, size (B, 1, H, W)
|
||||
depth -- torch.tensor, size (B, 1, H, W)
|
||||
features(optional) -- torch.tensor, size (B, C, H, W) if feat is not None
|
||||
|
||||
Parameters:
|
||||
vertex -- torch.tensor, size (B, N, 3)
|
||||
tri -- torch.tensor, size (B, M, 3) or (M, 3), triangles
|
||||
uv -- torch.tensor, size (B,N, 2), uv mapping
|
||||
base_tex -- torch.tensor, size (B,H,W,C)
|
||||
"""
|
||||
vertex = torch.cat([vertex[:, :35241, :], vertex[:, 37082:, :]],
|
||||
dim=1) # BFM front
|
||||
tri = torch.cat([tri[:69732, :], tri[73936:, ]], dim=0)
|
||||
uv = torch.cat([uv[:, :35241, :], uv[:, 37082:, :]], dim=1)
|
||||
tri[69732:, :] = tri[69732:, :] - (37082 - 35241)
|
||||
|
||||
device = vertex.device
|
||||
rsize = int(self.rasterize_size)
|
||||
ndc_proj = self.ndc_proj.to(device)
|
||||
# trans to homogeneous coordinates of 3d vertices, the direction of y is the same as v
|
||||
if vertex.shape[-1] == 3:
|
||||
vertex = torch.cat(
|
||||
[vertex, torch.ones([*vertex.shape[:2], 1]).to(device)],
|
||||
dim=-1)
|
||||
vertex[..., 1] = -vertex[..., 1]
|
||||
|
||||
vertex_ndc = vertex @ ndc_proj.t()
|
||||
if self.glctx is None:
|
||||
self.glctx = dr.RasterizeCudaContext(device=device)
|
||||
|
||||
ranges = None
|
||||
if isinstance(tri, List) or len(tri.shape) == 3:
|
||||
vum = vertex_ndc.shape[1]
|
||||
fnum = torch.tensor([f.shape[0]
|
||||
for f in tri]).unsqueeze(1).to(device)
|
||||
|
||||
fstartidx = torch.cumsum(fnum, dim=0) - fnum
|
||||
ranges = torch.cat([fstartidx, fnum],
|
||||
axis=1).type(torch.int32).cpu()
|
||||
for i in range(tri.shape[0]):
|
||||
tri[i] = tri[i] + i * vum
|
||||
vertex_ndc = torch.cat(vertex_ndc, dim=0)
|
||||
tri = torch.cat(tri, dim=0)
|
||||
|
||||
# for range_mode vetex: [B*N, 4], tri: [B*M, 3], for instance_mode vetex: [B, N, 4], tri: [M, 3]
|
||||
tri = tri.type(torch.int32).contiguous()
|
||||
rast_out, _ = dr.rasterize(
|
||||
self.glctx,
|
||||
vertex_ndc.contiguous(),
|
||||
tri,
|
||||
resolution=[rsize, rsize],
|
||||
ranges=ranges)
|
||||
|
||||
depth, _ = dr.interpolate(
|
||||
vertex.reshape([-1, 4])[..., 2].unsqueeze(1).contiguous(),
|
||||
rast_out, tri)
|
||||
depth = depth.permute(0, 3, 1, 2)
|
||||
mask = (rast_out[..., 3] > 0).float().unsqueeze(1)
|
||||
depth = mask * depth
|
||||
uv[..., -1] = 1.0 - uv[..., -1]
|
||||
|
||||
rast_out, rast_db = dr.rasterize(
|
||||
self.glctx,
|
||||
vertex_ndc.contiguous(),
|
||||
tri,
|
||||
resolution=[rsize, rsize],
|
||||
ranges=ranges)
|
||||
|
||||
interp_out, uv_da = dr.interpolate(
|
||||
uv, rast_out, tri, rast_db, diff_attrs='all')
|
||||
|
||||
mask_3c = mask.permute(0, 2, 3, 1)
|
||||
mask_3c = torch.cat((mask_3c, mask_3c, mask_3c), dim=-1)
|
||||
maskout_img = mask_3c * target_img
|
||||
mean_color = torch.sum(maskout_img, dim=(1, 2))
|
||||
valid_pixel_count = torch.sum(mask)
|
||||
|
||||
mean_color = mean_color / valid_pixel_count
|
||||
|
||||
tex = torch.zeros((1, 128 * 5 // 4, 128, 3), dtype=torch.float32)
|
||||
tex[:, :, :, 0] = mean_color[0, 0]
|
||||
tex[:, :, :, 1] = mean_color[0, 1]
|
||||
tex[:, :, :, 2] = mean_color[0, 2]
|
||||
|
||||
tex = tex.cuda()
|
||||
|
||||
tex_mask = torch.zeros((1, 2048 * 5 // 4, 2048, 3),
|
||||
dtype=torch.float32)
|
||||
tex_mask[:, :, :, 1] = 1.0
|
||||
tex_mask = tex_mask.cuda()
|
||||
tex_mask.requires_grad = True
|
||||
tex_mask = tex_mask.contiguous()
|
||||
|
||||
criterionTV = TVLoss()
|
||||
|
||||
if base_tex is not None:
|
||||
base_tex = base_tex.cuda()
|
||||
|
||||
for tex_resolution in [64, 128, 256, 512, 1024, 2048]:
|
||||
tex = tex.detach()
|
||||
tex = tex.permute(0, 3, 1, 2)
|
||||
tex = F.interpolate(tex, (tex_resolution * 5 // 4, tex_resolution))
|
||||
tex = tex.permute(0, 2, 3, 1).contiguous()
|
||||
|
||||
if base_tex is not None:
|
||||
_base_tex = base_tex.permute(0, 3, 1, 2)
|
||||
_base_tex = F.interpolate(
|
||||
_base_tex, (tex_resolution * 5 // 4, tex_resolution))
|
||||
_base_tex = _base_tex.permute(0, 2, 3, 1).contiguous()
|
||||
tex += _base_tex
|
||||
|
||||
tex.requires_grad = True
|
||||
optim = torch.optim.Adam([tex], lr=1e-2)
|
||||
|
||||
texture_opt_iters = 100
|
||||
|
||||
if tex_resolution == 2048:
|
||||
optim_mask = torch.optim.Adam([tex_mask], lr=1e-2)
|
||||
|
||||
for i in range(int(texture_opt_iters)):
|
||||
|
||||
if tex_resolution == 2048:
|
||||
optim_mask.zero_grad()
|
||||
rendered = dr.texture(
|
||||
tex_mask, interp_out, filter_mode='linear') # , uv_da)
|
||||
rendered = rendered * torch.clamp(
|
||||
rast_out[..., -1:], 0, 1) # Mask out background.
|
||||
tex_loss = torch.mean((target_img - rendered)**2)
|
||||
|
||||
tex_loss.backward()
|
||||
optim_mask.step()
|
||||
|
||||
optim.zero_grad()
|
||||
|
||||
img = dr.texture(
|
||||
tex, interp_out, filter_mode='linear') # , uv_da)
|
||||
img = img * torch.clamp(rast_out[..., -1:], 0,
|
||||
1) # Mask out background.
|
||||
recon_loss = torch.mean((target_img - img)**2)
|
||||
|
||||
if tex_resolution < 2048:
|
||||
tv_loss = criterionTV(tex.permute(0, 3, 1, 2))
|
||||
|
||||
total_loss = recon_loss + tv_loss * 0.01
|
||||
else:
|
||||
|
||||
total_loss = recon_loss
|
||||
|
||||
total_loss.backward()
|
||||
optim.step()
|
||||
|
||||
tex_map = tex[0].detach().cpu().numpy()[..., ::-1] * 255.0
|
||||
|
||||
image = img.permute(0, 3, 1, 2)
|
||||
|
||||
tex_mask = tex_mask[0].detach().cpu().numpy() * 255.0
|
||||
tex_mask = np.where(tex_mask[..., 1] > 250, 1.0, 0.0) * np.where(
|
||||
tex_mask[..., 0] < 10, 1.0, 0) * np.where(tex_mask[..., 2] < 10,
|
||||
1.0, 0)
|
||||
tex_mask = 1.0 - tex_mask
|
||||
|
||||
return mask, depth, image, tex_map, tex_mask
|
||||
13
modelscope/models/cv/face_reconstruction/models/opt.py
Normal file
13
modelscope/models/cv/face_reconstruction/models/opt.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
bfm_folder = ''
|
||||
bfm_model = 'head_model_for_maas.mat'
|
||||
camera_d = 10.0
|
||||
center = 112.0
|
||||
focal = 1015.0
|
||||
isTrain = False
|
||||
net_recon = 'resnet50'
|
||||
phase = 'test'
|
||||
use_ddp = False
|
||||
use_last_fc = False
|
||||
z_far = 15.0
|
||||
z_near = 5.0
|
||||
752
modelscope/models/cv/face_reconstruction/utils.py
Normal file
752
modelscope/models/cv/face_reconstruction/utils.py
Normal file
@@ -0,0 +1,752 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
import argparse
|
||||
import math
|
||||
import os
|
||||
import os.path as osp
|
||||
from array import array
|
||||
|
||||
import cv2
|
||||
import numba
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from PIL import Image
|
||||
from scipy.io import loadmat, savemat
|
||||
|
||||
|
||||
def img_value_rescale(img, old_range: list, new_range: list):
|
||||
assert len(old_range) == 2
|
||||
assert len(new_range) == 2
|
||||
img = (img - old_range[0]) / (old_range[1] - old_range[0]) * (
|
||||
new_range[1] - new_range[0]) + new_range[0]
|
||||
return img
|
||||
|
||||
|
||||
def resize_on_long_side(img, long_side=800):
|
||||
src_height = img.shape[0]
|
||||
src_width = img.shape[1]
|
||||
|
||||
if src_height > src_width:
|
||||
scale = long_side * 1.0 / src_height
|
||||
_img = cv2.resize(
|
||||
img, (int(src_width * scale), long_side),
|
||||
interpolation=cv2.INTER_CUBIC)
|
||||
|
||||
else:
|
||||
scale = long_side * 1.0 / src_width
|
||||
_img = cv2.resize(
|
||||
img, (long_side, int(src_height * scale)),
|
||||
interpolation=cv2.INTER_CUBIC)
|
||||
|
||||
return _img, scale
|
||||
|
||||
|
||||
def get_mg_layer(src, gt, skin_mask=None):
|
||||
"""
|
||||
src, gt shape: [h, w, 3] value: [0, 1]
|
||||
return: mg, shape: [h, w, 1] value: [0, 1]
|
||||
"""
|
||||
mg = (src * src - gt + 1e-10) / (2 * src * src - 2 * src + 2e-10)
|
||||
mg[mg < 0] = 0.5
|
||||
mg[mg > 1] = 0.5
|
||||
|
||||
diff_abs = np.abs(gt - src)
|
||||
mg[diff_abs < (1 / 255.0)] = 0.5
|
||||
|
||||
if skin_mask is not None:
|
||||
mg[skin_mask == 0] = 0.5
|
||||
|
||||
return mg
|
||||
|
||||
|
||||
def str2bool(v):
|
||||
if isinstance(v, bool):
|
||||
return v
|
||||
if v.lower() in ('yes', 'true', 't', 'y', '1'):
|
||||
return True
|
||||
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
|
||||
return False
|
||||
else:
|
||||
raise argparse.ArgumentTypeError('Boolean value expected.')
|
||||
|
||||
|
||||
def spread_flow(length, spread_ratio=2):
|
||||
Flow = np.zeros(shape=(length, length, 2), dtype=np.float32)
|
||||
mag = np.zeros(shape=(length, length), dtype=np.float32)
|
||||
|
||||
radius = length * 0.5
|
||||
for h in range(Flow.shape[0]):
|
||||
for w in range(Flow.shape[1]):
|
||||
|
||||
if (h - length // 2)**2 + (w - length // 2)**2 <= radius**2:
|
||||
Flow[h, w, 0] = -(w - length // 2)
|
||||
Flow[h, w, 1] = -(h - length // 2)
|
||||
|
||||
distance = np.sqrt((w - length // 2)**2 + (h - length // 2)**2)
|
||||
|
||||
if distance <= radius / 2.0:
|
||||
mag[h, w] = 2.0 / radius * distance
|
||||
else:
|
||||
mag[h, w] = -2.0 / radius * distance + 2.0
|
||||
|
||||
_, ang = cv2.cartToPolar(Flow[..., 0] + 1e-8, Flow[..., 1] + 1e-8)
|
||||
|
||||
mag *= spread_ratio
|
||||
|
||||
x, y = cv2.polarToCart(mag, ang, angleInDegrees=False)
|
||||
Flow = np.dstack((x, y))
|
||||
|
||||
return Flow
|
||||
|
||||
|
||||
@numba.jit(nopython=True, parallel=True)
|
||||
def bilinear_interp(x, y, v11, v12, v21, v22):
|
||||
t = 0.2
|
||||
|
||||
if x < t and y < t:
|
||||
return v11
|
||||
elif x < t and y > 1 - t:
|
||||
return v12
|
||||
elif x > 1 - t and y < t:
|
||||
return v21
|
||||
elif x > 1 - t and y > 1 - t:
|
||||
return v22
|
||||
else:
|
||||
result = (v11 * (1 - y) + v12 * y) * (1 - x) + \
|
||||
(v21 * (1 - y) + v22 * y) * x
|
||||
if result < 0:
|
||||
result = 0
|
||||
|
||||
if result > 255:
|
||||
result = 255
|
||||
return result
|
||||
|
||||
|
||||
@numba.jit(nopython=True, parallel=True)
|
||||
def image_warp_grid1(rDx, rDy, oriImg, transRatio, pads):
|
||||
# assert oriImg.dtype == np.uint8
|
||||
srcW = oriImg.shape[1]
|
||||
srcH = oriImg.shape[0]
|
||||
|
||||
padTop, padBottom, padLeft, padRight = pads
|
||||
|
||||
left_bound = padLeft + 1
|
||||
right_bound = srcW - padRight
|
||||
bottom_bound = srcH - padBottom
|
||||
top_bound = padTop + 1
|
||||
|
||||
newImg = oriImg.copy()
|
||||
|
||||
for i in range(srcH):
|
||||
for j in range(srcW):
|
||||
_i = i
|
||||
_j = j
|
||||
|
||||
deltaX = rDx[_i, _j]
|
||||
deltaY = rDy[_i, _j]
|
||||
|
||||
if abs(deltaX) < 0.2 and abs(deltaY) < 0.2:
|
||||
continue
|
||||
|
||||
nx = _j + deltaX * transRatio
|
||||
ny = _i + deltaY * transRatio
|
||||
|
||||
if nx >= srcW - padRight:
|
||||
if nx > srcW - 1:
|
||||
nx = srcW - 1
|
||||
|
||||
if _j < right_bound:
|
||||
right_bound = _j
|
||||
|
||||
if ny >= srcH - padBottom:
|
||||
if ny > srcH - 1:
|
||||
ny = srcH - 1
|
||||
|
||||
if _i < bottom_bound:
|
||||
bottom_bound = _i
|
||||
|
||||
if nx < padLeft:
|
||||
if nx < 0:
|
||||
nx = 0
|
||||
|
||||
if _j + 1 > left_bound:
|
||||
left_bound = _j + 1
|
||||
|
||||
if ny < padTop:
|
||||
if ny < 0:
|
||||
ny = 0
|
||||
|
||||
if _i + 1 > top_bound:
|
||||
top_bound = _i + 1
|
||||
|
||||
nxi = int(math.floor(nx))
|
||||
nyi = int(math.floor(ny))
|
||||
nxi1 = int(math.ceil(nx))
|
||||
nyi1 = int(math.ceil(ny))
|
||||
|
||||
if nxi < 0:
|
||||
nxi = 0
|
||||
if nxi > oriImg.shape[1] - 1:
|
||||
nxi = oriImg.shape[1] - 1
|
||||
|
||||
if nxi1 < 0:
|
||||
nxi1 = 0
|
||||
if nxi1 > oriImg.shape[1] - 1:
|
||||
nxi1 = oriImg.shape[1] - 1
|
||||
|
||||
if nyi < 0:
|
||||
nyi = 0
|
||||
if nyi > oriImg.shape[0] - 1:
|
||||
nyi = oriImg.shape[0] - 1
|
||||
|
||||
if nyi1 < 0:
|
||||
nyi1 = 0
|
||||
if nyi1 > oriImg.shape[0] - 1:
|
||||
nyi1 = oriImg.shape[0] - 1
|
||||
|
||||
for ll in range(3):
|
||||
newImg[_i, _j,
|
||||
ll] = bilinear_interp(ny - nyi, nx - nxi,
|
||||
oriImg[nyi, nxi,
|
||||
ll], oriImg[nyi, nxi1, ll],
|
||||
oriImg[nyi1, nxi,
|
||||
ll], oriImg[nyi1, nxi1,
|
||||
ll])
|
||||
|
||||
return newImg, top_bound, bottom_bound, left_bound, right_bound
|
||||
|
||||
|
||||
def warp(x, flow, mode='bilinear', padding_mode='zeros', coff=0.1):
|
||||
"""
|
||||
|
||||
Args:
|
||||
x: [n, c, h, w]
|
||||
flow: [n, h, w, 2]
|
||||
mode:
|
||||
padding_mode:
|
||||
coff:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
n, c, h, w = x.size()
|
||||
yv, xv = torch.meshgrid([torch.arange(h), torch.arange(w)])
|
||||
xv = xv.float() / (w - 1) * 2.0 - 1
|
||||
yv = yv.float() / (h - 1) * 2.0 - 1
|
||||
'''
|
||||
grid[0,:,:,0] =
|
||||
-1, .....1
|
||||
-1, .....1
|
||||
-1, .....1
|
||||
|
||||
grid[0,:,:,1] =
|
||||
-1, -1, -1
|
||||
; ;
|
||||
1, 1, 1
|
||||
|
||||
'''
|
||||
|
||||
if torch.cuda.is_available():
|
||||
grid = torch.cat((xv.unsqueeze(-1), yv.unsqueeze(-1)),
|
||||
-1).unsqueeze(0).cuda()
|
||||
else:
|
||||
grid = torch.cat((xv.unsqueeze(-1), yv.unsqueeze(-1)), -1).unsqueeze(0)
|
||||
grid_x = grid + 2 * flow * coff
|
||||
warp_x = F.grid_sample(x, grid_x, mode=mode, padding_mode=padding_mode)
|
||||
return warp_x
|
||||
|
||||
|
||||
# load expression basis
|
||||
def LoadExpBasis(bfm_folder='asset/BFM'):
|
||||
n_vertex = 53215
|
||||
Expbin = open(osp.join(bfm_folder, 'Exp_Pca.bin'), 'rb')
|
||||
exp_dim = array('i')
|
||||
exp_dim.fromfile(Expbin, 1)
|
||||
expMU = array('f')
|
||||
expPC = array('f')
|
||||
expMU.fromfile(Expbin, 3 * n_vertex)
|
||||
expPC.fromfile(Expbin, 3 * exp_dim[0] * n_vertex)
|
||||
Expbin.close()
|
||||
|
||||
expPC = np.array(expPC)
|
||||
expPC = np.reshape(expPC, [exp_dim[0], -1])
|
||||
expPC = np.transpose(expPC)
|
||||
|
||||
expEV = np.loadtxt(osp.join(bfm_folder, 'std_exp.txt'))
|
||||
|
||||
return expPC, expEV
|
||||
|
||||
|
||||
# transfer original BFM09 to our face model
|
||||
def transferBFM09(bfm_folder='BFM'):
|
||||
print('Transfer BFM09 to BFM_model_front......')
|
||||
original_BFM = loadmat(osp.join(bfm_folder, '01_MorphableModel.mat'))
|
||||
shapePC = original_BFM['shapePC'] # shape basis, 160470*199
|
||||
shapeEV = original_BFM['shapeEV'] # corresponding eigen value, 199*1
|
||||
shapeMU = original_BFM['shapeMU'] # mean face, 160470*1
|
||||
texPC = original_BFM['texPC'] # texture basis, 160470*199
|
||||
texEV = original_BFM['texEV'] # eigen value, 199*1
|
||||
texMU = original_BFM['texMU'] # mean texture, 160470*1
|
||||
|
||||
expPC, expEV = LoadExpBasis()
|
||||
|
||||
# transfer BFM09 to our face model
|
||||
|
||||
idBase = shapePC * np.reshape(shapeEV, [-1, 199])
|
||||
idBase = idBase / 1e5 # unify the scale to decimeter
|
||||
idBase = idBase[:, :80] # use only first 80 basis
|
||||
|
||||
exBase = expPC * np.reshape(expEV, [-1, 79])
|
||||
exBase = exBase / 1e5 # unify the scale to decimeter
|
||||
exBase = exBase[:, :64] # use only first 64 basis
|
||||
|
||||
texBase = texPC * np.reshape(texEV, [-1, 199])
|
||||
texBase = texBase[:, :80] # use only first 80 basis
|
||||
|
||||
# our face model is cropped along face landmarks and contains only 35709 vertex.
|
||||
# original BFM09 contains 53490 vertex, and expression basis provided by Guo et al. contains 53215 vertex.
|
||||
# thus we select corresponding vertex to get our face model.
|
||||
|
||||
index_exp = loadmat(osp.join(bfm_folder, 'BFM_front_idx.mat'))
|
||||
index_exp = index_exp['idx'].astype(
|
||||
np.int32) - 1 # starts from 0 (to 53215)
|
||||
|
||||
index_shape = loadmat(osp.join(bfm_folder, 'BFM_exp_idx.mat'))
|
||||
index_shape = index_shape['trimIndex'].astype(
|
||||
np.int32) - 1 # starts from 0 (to 53490)
|
||||
index_shape = index_shape[index_exp]
|
||||
|
||||
idBase = np.reshape(idBase, [-1, 3, 80])
|
||||
idBase = idBase[index_shape, :, :]
|
||||
idBase = np.reshape(idBase, [-1, 80])
|
||||
|
||||
texBase = np.reshape(texBase, [-1, 3, 80])
|
||||
texBase = texBase[index_shape, :, :]
|
||||
texBase = np.reshape(texBase, [-1, 80])
|
||||
|
||||
exBase = np.reshape(exBase, [-1, 3, 64])
|
||||
exBase = exBase[index_exp, :, :]
|
||||
exBase = np.reshape(exBase, [-1, 64])
|
||||
|
||||
meanshape = np.reshape(shapeMU, [-1, 3]) / 1e5
|
||||
meanshape = meanshape[index_shape, :]
|
||||
meanshape = np.reshape(meanshape, [1, -1])
|
||||
|
||||
meantex = np.reshape(texMU, [-1, 3])
|
||||
meantex = meantex[index_shape, :]
|
||||
meantex = np.reshape(meantex, [1, -1])
|
||||
|
||||
# other info contains triangles, region used for computing photometric loss,
|
||||
# region used for skin texture regularization, and 68 landmarks index etc.
|
||||
other_info = loadmat(osp.join(bfm_folder, 'facemodel_info.mat'))
|
||||
frontmask2_idx = other_info['frontmask2_idx']
|
||||
skinmask = other_info['skinmask']
|
||||
keypoints = other_info['keypoints']
|
||||
point_buf = other_info['point_buf']
|
||||
tri = other_info['tri']
|
||||
tri_mask2 = other_info['tri_mask2']
|
||||
|
||||
# save our face model
|
||||
savemat(
|
||||
osp.join(bfm_folder, 'BFM_model_front.mat'), {
|
||||
'meanshape': meanshape,
|
||||
'meantex': meantex,
|
||||
'idBase': idBase,
|
||||
'exBase': exBase,
|
||||
'texBase': texBase,
|
||||
'tri': tri,
|
||||
'point_buf': point_buf,
|
||||
'tri_mask2': tri_mask2,
|
||||
'keypoints': keypoints,
|
||||
'frontmask2_idx': frontmask2_idx,
|
||||
'skinmask': skinmask
|
||||
})
|
||||
|
||||
|
||||
# load landmarks for standard face, which is used for image preprocessing
|
||||
def load_lm3d(bfm_folder):
|
||||
|
||||
Lm3D = loadmat(osp.join(bfm_folder, 'similarity_Lm3D_all.mat'))
|
||||
Lm3D = Lm3D['lm']
|
||||
|
||||
# calculate 5 facial landmarks using 68 landmarks
|
||||
lm_idx = np.array([31, 37, 40, 43, 46, 49, 55]) - 1
|
||||
value_list = [
|
||||
Lm3D[lm_idx[0], :],
|
||||
np.mean(Lm3D[lm_idx[[1, 2]], :], 0),
|
||||
np.mean(Lm3D[lm_idx[[3, 4]], :], 0), Lm3D[lm_idx[5], :],
|
||||
Lm3D[lm_idx[6], :]
|
||||
]
|
||||
Lm3D = np.stack(value_list, axis=0)
|
||||
Lm3D = Lm3D[[1, 2, 0, 3, 4], :]
|
||||
|
||||
return Lm3D
|
||||
|
||||
|
||||
def write_obj(save_path, mesh):
|
||||
save_dir = os.path.dirname(save_path)
|
||||
save_name = os.path.splitext(os.path.basename(save_path))[0]
|
||||
|
||||
if 'texture_map' in mesh:
|
||||
cv2.imwrite(
|
||||
os.path.join(save_dir, save_name + '.jpg'), mesh['texture_map'])
|
||||
|
||||
with open(os.path.join(save_dir, save_name + '.mtl'), 'w') as wf:
|
||||
wf.write('# Created by ModelScope\n')
|
||||
wf.write('newmtl material_0\n')
|
||||
wf.write('Ka 1.000000 0.000000 0.000000\n')
|
||||
wf.write('Kd 1.000000 1.000000 1.000000\n')
|
||||
wf.write('Ks 0.000000 0.000000 0.000000\n')
|
||||
wf.write('Tr 0.000000\n')
|
||||
wf.write('illum 0\n')
|
||||
wf.write('Ns 0.000000\n')
|
||||
wf.write('map_Kd {}\n'.format(save_name + '.jpg'))
|
||||
|
||||
with open(save_path, 'w') as wf:
|
||||
if 'texture_map' in mesh:
|
||||
wf.write('# Create by ModelScope\n')
|
||||
wf.write('mtllib ./{}.mtl\n'.format(save_name))
|
||||
|
||||
if 'colors' in mesh:
|
||||
for i, v in enumerate(mesh['vertices']):
|
||||
wf.write('v {} {} {} {} {} {}\n'.format(
|
||||
v[0], v[1], v[2], mesh['colors'][i][0],
|
||||
mesh['colors'][i][1], mesh['colors'][i][2]))
|
||||
else:
|
||||
for v in mesh['vertices']:
|
||||
wf.write('v {} {} {}\n'.format(v[0], v[1], v[2]))
|
||||
|
||||
if 'UVs' in mesh:
|
||||
for uv in mesh['UVs']:
|
||||
wf.write('vt {} {}\n'.format(uv[0], uv[1]))
|
||||
|
||||
if 'normals' in mesh:
|
||||
for vn in mesh['normals']:
|
||||
wf.write('vn {} {} {}\n'.format(vn[0], vn[1], vn[2]))
|
||||
|
||||
if 'faces' in mesh:
|
||||
for ind, face in enumerate(mesh['faces']):
|
||||
if 'faces_uv' in mesh or 'faces_normal' in mesh:
|
||||
if 'faces_uv' in mesh:
|
||||
face_uv = mesh['faces_uv'][ind]
|
||||
else:
|
||||
face_uv = face
|
||||
if 'faces_normal' in mesh:
|
||||
face_normal = mesh['faces_normal'][ind]
|
||||
else:
|
||||
face_normal = face
|
||||
row = 'f ' + ' '.join([
|
||||
'{}/{}/{}'.format(face[i], face_uv[i], face_normal[i])
|
||||
for i in range(len(face))
|
||||
]) + '\n'
|
||||
else:
|
||||
row = 'f ' + ' '.join(
|
||||
['{}'.format(face[i])
|
||||
for i in range(len(face))]) + '\n'
|
||||
wf.write(row)
|
||||
|
||||
|
||||
def read_obj(obj_path, print_shape=True):
|
||||
with open(obj_path, 'r') as f:
|
||||
bfm_lines = f.readlines()
|
||||
|
||||
vertices = []
|
||||
faces = []
|
||||
uvs = []
|
||||
vns = []
|
||||
faces_uv = []
|
||||
faces_normal = []
|
||||
max_face_length = 0
|
||||
for line in bfm_lines:
|
||||
if line[:2] == 'v ':
|
||||
vertex = [
|
||||
float(a) for a in line.strip().split(' ')[1:] if len(a) > 0
|
||||
]
|
||||
vertices.append(vertex)
|
||||
|
||||
if line[:2] == 'f ':
|
||||
items = line.strip().split(' ')[1:]
|
||||
face = [int(a.split('/')[0]) for a in items if len(a) > 0]
|
||||
max_face_length = max(max_face_length, len(face))
|
||||
if len(faces) > 0 and len(face) != len(faces[0]):
|
||||
continue
|
||||
faces.append(face)
|
||||
|
||||
if '/' in items[0] and len(items[0].split('/')[1]) > 0:
|
||||
face_uv = [int(a.split('/')[1]) for a in items if len(a) > 0]
|
||||
faces_uv.append(face_uv)
|
||||
|
||||
if '/' in items[0] and len(items[0].split('/')) >= 3 and len(
|
||||
items[0].split('/')[2]) > 0:
|
||||
face_normal = [
|
||||
int(a.split('/')[2]) for a in items if len(a) > 0
|
||||
]
|
||||
faces_normal.append(face_normal)
|
||||
|
||||
if line[:3] == 'vt ':
|
||||
items = line.strip().split(' ')[1:]
|
||||
uv = [float(a) for a in items if len(a) > 0]
|
||||
uvs.append(uv)
|
||||
|
||||
if line[:3] == 'vn ':
|
||||
items = line.strip().split(' ')[1:]
|
||||
vn = [float(a) for a in items if len(a) > 0]
|
||||
vns.append(vn)
|
||||
|
||||
vertices = np.array(vertices).astype(np.float32)
|
||||
if max_face_length <= 3:
|
||||
faces = np.array(faces).astype(np.int32)
|
||||
|
||||
if vertices.shape[1] == 3:
|
||||
mesh = {
|
||||
'vertices': vertices,
|
||||
'faces': faces,
|
||||
}
|
||||
else:
|
||||
mesh = {
|
||||
'vertices': vertices[:, :3],
|
||||
'colors': vertices[:, 3:],
|
||||
'faces': faces,
|
||||
}
|
||||
|
||||
if len(uvs) > 0:
|
||||
uvs = np.array(uvs).astype(np.float32)
|
||||
mesh['uvs'] = uvs
|
||||
|
||||
if len(vns) > 0:
|
||||
vns = np.array(vns).astype(np.float32)
|
||||
mesh['vns'] = vns
|
||||
|
||||
if len(faces_uv) > 0:
|
||||
if max_face_length <= 3:
|
||||
faces_uv = np.array(faces_uv).astype(np.int32)
|
||||
mesh['faces_uv'] = faces_uv
|
||||
|
||||
if len(faces_normal) > 0:
|
||||
if max_face_length <= 3:
|
||||
faces_normal = np.array(faces_normal).astype(np.int32)
|
||||
mesh['faces_normal'] = faces_normal
|
||||
|
||||
return mesh
|
||||
|
||||
|
||||
# calculating least square problem for image alignment
|
||||
def POS(xp, x):
|
||||
npts = xp.shape[1]
|
||||
|
||||
A = np.zeros([2 * npts, 8])
|
||||
|
||||
A[0:2 * npts - 1:2, 0:3] = x.transpose()
|
||||
A[0:2 * npts - 1:2, 3] = 1
|
||||
|
||||
A[1:2 * npts:2, 4:7] = x.transpose()
|
||||
A[1:2 * npts:2, 7] = 1
|
||||
|
||||
b = np.reshape(xp.transpose(), [2 * npts, 1])
|
||||
|
||||
k, _, _, _ = np.linalg.lstsq(A, b)
|
||||
|
||||
R1 = k[0:3]
|
||||
R2 = k[4:7]
|
||||
sTx = k[3]
|
||||
sTy = k[7]
|
||||
s = (np.linalg.norm(R1) + np.linalg.norm(R2)) / 2
|
||||
t = np.stack([sTx, sTy], axis=0)
|
||||
|
||||
return t, s
|
||||
|
||||
|
||||
# bounding box for 68 landmark detection
|
||||
def BBRegression(points, params):
|
||||
w1 = params['W1']
|
||||
b1 = params['B1']
|
||||
w2 = params['W2']
|
||||
b2 = params['B2']
|
||||
data = points.copy()
|
||||
data = data.reshape([5, 2])
|
||||
data_mean = np.mean(data, axis=0)
|
||||
x_mean = data_mean[0]
|
||||
y_mean = data_mean[1]
|
||||
data[:, 0] = data[:, 0] - x_mean
|
||||
data[:, 1] = data[:, 1] - y_mean
|
||||
|
||||
rms = np.sqrt(np.sum(data**2) / 5)
|
||||
data = data / rms
|
||||
data = data.reshape([1, 10])
|
||||
data = np.transpose(data)
|
||||
inputs = np.matmul(w1, data) + b1
|
||||
inputs = 2 / (1 + np.exp(-2 * inputs)) - 1
|
||||
inputs = np.matmul(w2, inputs) + b2
|
||||
inputs = np.transpose(inputs)
|
||||
x = inputs[:, 0] * rms + x_mean
|
||||
y = inputs[:, 1] * rms + y_mean
|
||||
w = 224 / inputs[:, 2] * rms
|
||||
rects = [x, y, w, w]
|
||||
return np.array(rects).reshape([4])
|
||||
|
||||
|
||||
# utils for landmark detection
|
||||
def img_padding(img, box):
|
||||
success = True
|
||||
bbox = box.copy()
|
||||
res = np.zeros([2 * img.shape[0], 2 * img.shape[1], 3])
|
||||
res[img.shape[0] // 2:img.shape[0] + img.shape[0] // 2,
|
||||
img.shape[1] // 2:img.shape[1] + img.shape[1] // 2] = img
|
||||
|
||||
bbox[0] = bbox[0] + img.shape[1] // 2
|
||||
bbox[1] = bbox[1] + img.shape[0] // 2
|
||||
if bbox[0] < 0 or bbox[1] < 0:
|
||||
success = False
|
||||
return res, bbox, success
|
||||
|
||||
|
||||
# utils for landmark detection
|
||||
def crop(img, bbox):
|
||||
padded_img, padded_bbox, flag = img_padding(img, bbox)
|
||||
if flag:
|
||||
crop_img = padded_img[padded_bbox[1]:padded_bbox[1] + padded_bbox[3],
|
||||
padded_bbox[0]:padded_bbox[0] + padded_bbox[2]]
|
||||
crop_img = cv2.resize(
|
||||
crop_img.astype(np.uint8), (224, 224),
|
||||
interpolation=cv2.INTER_CUBIC)
|
||||
scale = 224 / padded_bbox[3]
|
||||
return crop_img, scale
|
||||
else:
|
||||
return padded_img, 0
|
||||
|
||||
|
||||
# utils for landmark detection
|
||||
def scale_trans(img, lm, t, s):
|
||||
imgw = img.shape[1]
|
||||
imgh = img.shape[0]
|
||||
M_s = np.array(
|
||||
[[1, 0, -t[0] + imgw // 2 + 0.5], [0, 1, -imgh // 2 + t[1]]],
|
||||
dtype=np.float32)
|
||||
img = cv2.warpAffine(img, M_s, (imgw, imgh))
|
||||
w = int(imgw / s * 100)
|
||||
h = int(imgh / s * 100)
|
||||
img = cv2.resize(img, (w, h))
|
||||
lm = np.stack([lm[:, 0] - t[0] + imgw // 2, lm[:, 1] - t[1] + imgh // 2],
|
||||
axis=1) / s * 100
|
||||
|
||||
left = w // 2 - 112
|
||||
up = h // 2 - 112
|
||||
bbox = [left, up, 224, 224]
|
||||
cropped_img, scale2 = crop(img, bbox)
|
||||
assert (scale2 != 0)
|
||||
t1 = np.array([bbox[0], bbox[1]])
|
||||
|
||||
# back to raw img s * crop + s * t1 + t2
|
||||
t1 = np.array([w // 2 - 112, h // 2 - 112])
|
||||
scale = s / 100
|
||||
t2 = np.array([t[0] - imgw / 2, t[1] - imgh / 2])
|
||||
inv = (scale / scale2, scale * t1 + t2.reshape([2]))
|
||||
return cropped_img, inv
|
||||
|
||||
|
||||
# utils for landmark detection
|
||||
def align_for_lm(img, five_points, params):
|
||||
five_points = np.array(five_points).reshape([1, 10])
|
||||
bbox = BBRegression(five_points, params)
|
||||
assert (bbox[2] != 0)
|
||||
bbox = np.round(bbox).astype(np.int32)
|
||||
crop_img, scale = crop(img, bbox)
|
||||
return crop_img, scale, bbox
|
||||
|
||||
|
||||
# resize and crop images for face reconstruction
|
||||
def resize_n_crop_img(img, lm, t, s, target_size=224., mask=None):
|
||||
w0, h0 = img.size
|
||||
w = (w0 * s).astype(np.int32)
|
||||
h = (h0 * s).astype(np.int32)
|
||||
left = (w / 2 - target_size / 2 + float(
|
||||
(t[0] - w0 / 2) * s)).astype(np.int32)
|
||||
right = left + target_size
|
||||
up = (h / 2 - target_size / 2 + float(
|
||||
(h0 / 2 - t[1]) * s)).astype(np.int32)
|
||||
below = up + target_size
|
||||
|
||||
new_img = img.resize((w, h), resample=Image.BICUBIC)
|
||||
new_img = new_img.crop((left, up, right, below))
|
||||
|
||||
if mask is not None:
|
||||
mask = mask.resize((w, h), resample=Image.BICUBIC)
|
||||
mask = mask.crop((left, up, right, below))
|
||||
|
||||
new_lm = np.stack([lm[:, 0] - t[0] + w0 / 2, lm[:, 1] - t[1] + h0 / 2],
|
||||
axis=1) * s
|
||||
new_lm = new_lm - np.reshape(
|
||||
np.array([(w / 2 - target_size / 2),
|
||||
(h / 2 - target_size / 2)]), [1, 2])
|
||||
|
||||
return new_img, new_lm, mask
|
||||
|
||||
|
||||
# utils for face reconstruction
|
||||
def extract_5p(lm):
|
||||
lm_idx = np.array([31, 37, 40, 43, 46, 49, 55]) - 1
|
||||
value_list = [
|
||||
lm[lm_idx[0], :],
|
||||
np.mean(lm[lm_idx[[1, 2]], :], 0),
|
||||
np.mean(lm[lm_idx[[3, 4]], :], 0), lm[lm_idx[5], :], lm[lm_idx[6], :]
|
||||
]
|
||||
lm5p = np.stack(value_list, axis=0)
|
||||
lm5p = lm5p[[1, 2, 0, 3, 4], :]
|
||||
return lm5p
|
||||
|
||||
|
||||
# utils for face reconstruction
|
||||
def align_img(img, lm, lm3D, mask=None, target_size=224., rescale_factor=102.):
|
||||
"""
|
||||
Return:
|
||||
transparams --numpy.array (raw_W, raw_H, scale, tx, ty)
|
||||
img_new --PIL.Image (target_size, target_size, 3)
|
||||
lm_new --numpy.array (68, 2), y direction is opposite to v direction
|
||||
mask_new --PIL.Image (target_size, target_size)
|
||||
|
||||
Parameters:
|
||||
img --PIL.Image (raw_H, raw_W, 3)
|
||||
lm --numpy.array (68, 2), y direction is opposite to v direction
|
||||
lm3D --numpy.array (5, 3)
|
||||
mask --PIL.Image (raw_H, raw_W, 3)
|
||||
"""
|
||||
|
||||
w0, h0 = img.size
|
||||
if lm.shape[0] != 5:
|
||||
lm5p = extract_5p(lm)
|
||||
else:
|
||||
lm5p = lm
|
||||
|
||||
# calculate translation and scale factors using 5 facial landmarks and standard landmarks of a 3D face
|
||||
t, s = POS(lm5p.transpose(), lm3D.transpose())
|
||||
s = rescale_factor / s
|
||||
|
||||
# processing the image
|
||||
img_new, lm_new, mask_new = resize_n_crop_img(
|
||||
img, lm, t, s, target_size=target_size, mask=mask)
|
||||
trans_params = np.array([w0, h0, s, t[0], t[1]])
|
||||
|
||||
return trans_params, img_new, lm_new, mask_new
|
||||
|
||||
|
||||
def normalize_v3(arr):
|
||||
''' Normalize a numpy array of 3 component vectors shape=(n,3) '''
|
||||
lens = np.sqrt(arr[:, 0]**2 + arr[:, 1]**2 + arr[:, 2]**2)[:, None]
|
||||
arr /= lens
|
||||
return arr
|
||||
|
||||
|
||||
def estimate_normals(vertices, faces):
|
||||
norm = np.zeros(vertices.shape, dtype=vertices.dtype)
|
||||
tris = vertices[faces]
|
||||
n = np.cross(tris[::, 1] - tris[::, 0], tris[::, 2] - tris[::, 0])
|
||||
n[(n[:, 0] == 0) * (n[:, 1] == 0) * (n[:, 2] == 0)] = [0, 0, 1.0]
|
||||
n = normalize_v3(n)
|
||||
for i in range(3):
|
||||
for j in range(faces.shape[0]):
|
||||
norm[faces[j, i]] += n[j]
|
||||
|
||||
inds = (norm[:, 0] == 0) * (norm[:, 1] == 0) * (norm[:, 2] == 0)
|
||||
norm[inds] = [0, 0, 1.0]
|
||||
result = normalize_v3(norm)
|
||||
return result
|
||||
@@ -390,6 +390,21 @@ TASK_OUTPUTS = {
|
||||
Tasks.body_3d_keypoints:
|
||||
[OutputKeys.KEYPOINTS, OutputKeys.TIMESTAMPS, OutputKeys.OUTPUT_VIDEO],
|
||||
|
||||
# 3D face reconstruction result for single sample
|
||||
# {
|
||||
# "output": {
|
||||
# "vertices": np.array with shape(n, 3),
|
||||
# "faces": np.array with shape(n, 3),
|
||||
# "faces_uv": np.array with shape(n, 3),
|
||||
# "faces_normal": np.array with shape(n, 3),
|
||||
# "colors": np.array with shape(n, 3),
|
||||
# "UVs": np.array with shape(n, 2),
|
||||
# "normals": np.array with shape(n, 3),
|
||||
# "texture_map": np.array with shape(h, w, 3),
|
||||
# }
|
||||
# }
|
||||
Tasks.face_reconstruction: [OutputKeys.OUTPUT],
|
||||
|
||||
# 2D hand keypoints result for single sample
|
||||
# {
|
||||
# "keypoints": [
|
||||
|
||||
@@ -68,6 +68,8 @@ TASK_INPUTS = {
|
||||
InputType.IMAGE,
|
||||
Tasks.face_recognition:
|
||||
InputType.IMAGE,
|
||||
Tasks.face_reconstruction:
|
||||
InputType.IMAGE,
|
||||
Tasks.human_detection:
|
||||
InputType.IMAGE,
|
||||
Tasks.face_image_generation:
|
||||
|
||||
@@ -51,6 +51,7 @@ if TYPE_CHECKING:
|
||||
from .license_plate_detection_pipeline import LicensePlateDetectionPipeline
|
||||
from .table_recognition_pipeline import TableRecognitionPipeline
|
||||
from .skin_retouching_pipeline import SkinRetouchingPipeline
|
||||
from .face_reconstruction_pipeline import FaceReconstructionPipeline
|
||||
from .tinynas_classification_pipeline import TinynasClassificationPipeline
|
||||
from .video_category_pipeline import VideoCategoryPipeline
|
||||
from .virtual_try_on_pipeline import VirtualTryonPipeline
|
||||
@@ -150,6 +151,7 @@ else:
|
||||
'license_plate_detection_pipeline': ['LicensePlateDetectionPipeline'],
|
||||
'table_recognition_pipeline': ['TableRecognitionPipeline'],
|
||||
'skin_retouching_pipeline': ['SkinRetouchingPipeline'],
|
||||
'face_reconstruction_pipeline': ['FaceReconstructionPipeline'],
|
||||
'tinynas_classification_pipeline': ['TinynasClassificationPipeline'],
|
||||
'video_category_pipeline': ['VideoCategoryPipeline'],
|
||||
'virtual_try_on_pipeline': ['VirtualTryonPipeline'],
|
||||
|
||||
370
modelscope/pipelines/cv/face_reconstruction_pipeline.py
Normal file
370
modelscope/pipelines/cv/face_reconstruction_pipeline.py
Normal file
@@ -0,0 +1,370 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
import os
|
||||
import shutil
|
||||
from typing import Any, Dict
|
||||
|
||||
import cv2
|
||||
import face_alignment
|
||||
import numpy as np
|
||||
import PIL.Image
|
||||
import tensorflow as tf
|
||||
import torch
|
||||
from scipy.io import loadmat, savemat
|
||||
|
||||
from modelscope.metainfo import Pipelines
|
||||
from modelscope.models import Model
|
||||
from modelscope.models.cv.face_reconstruction.models.facelandmark.large_model_infer import \
|
||||
LargeModelInfer
|
||||
from modelscope.models.cv.face_reconstruction.utils import (align_for_lm,
|
||||
align_img,
|
||||
load_lm3d,
|
||||
read_obj,
|
||||
write_obj)
|
||||
from modelscope.outputs import OutputKeys
|
||||
from modelscope.pipelines import pipeline
|
||||
from modelscope.pipelines.base import Input, Pipeline
|
||||
from modelscope.pipelines.builder import PIPELINES
|
||||
from modelscope.preprocessors import LoadImage
|
||||
from modelscope.utils.constant import ModelFile, Tasks
|
||||
from modelscope.utils.device import create_device, device_placement
|
||||
from modelscope.utils.logger import get_logger
|
||||
|
||||
if tf.__version__ >= '2.0':
|
||||
tf = tf.compat.v1
|
||||
tf.disable_eager_execution()
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
@PIPELINES.register_module(
|
||||
Tasks.face_reconstruction, module_name=Pipelines.face_reconstruction)
|
||||
class FaceReconstructionPipeline(Pipeline):
|
||||
|
||||
def __init__(self, model: str, device: str):
|
||||
"""The inference pipeline for face reconstruction task.
|
||||
|
||||
Args:
|
||||
model (`str` or `Model` or module instance): A model instance or a model local dir
|
||||
or a model id in the model hub.
|
||||
device ('str'): device str, should be either cpu, cuda, gpu, gpu:X or cuda:X.
|
||||
|
||||
Example:
|
||||
>>> from modelscope.pipelines import pipeline
|
||||
>>> test_image = 'data/test/images/face_reconstruction.jpg'
|
||||
>>> pipeline_faceRecon = pipeline('face-reconstruction',
|
||||
model='damo/cv_resnet50_face-reconstruction')
|
||||
>>> result = pipeline_faceRecon(test_image)
|
||||
>>> write_obj('result_face_reconstruction.obj', result[OutputKeys.OUTPUT])
|
||||
"""
|
||||
super().__init__(model=model, device=device)
|
||||
|
||||
model_root = model
|
||||
bfm_folder = os.path.join(model_root, 'assets')
|
||||
checkpoint_path = os.path.join(model_root, ModelFile.TORCH_MODEL_FILE)
|
||||
|
||||
self.face_mark_model = LargeModelInfer(
|
||||
os.path.join(model_root, 'large_base_net.pth'), device='cuda')
|
||||
|
||||
device = torch.device(0)
|
||||
torch.cuda.set_device(device)
|
||||
self.model.setup(checkpoint_path)
|
||||
self.model.device = device
|
||||
self.model.parallelize()
|
||||
self.model.eval()
|
||||
self.model.set_render(image_res=1024)
|
||||
|
||||
save_ckpt_dir = os.path.join(
|
||||
os.path.expanduser('~'), '.cache/torch/hub/checkpoints')
|
||||
if not os.path.exists(save_ckpt_dir):
|
||||
os.makedirs(save_ckpt_dir)
|
||||
shutil.copy(
|
||||
os.path.join(model_root, 'face_alignment', 's3fd-619a316812.pth'),
|
||||
save_ckpt_dir)
|
||||
shutil.copy(
|
||||
os.path.join(model_root, 'face_alignment',
|
||||
'3DFAN4-4a694010b9.zip'), save_ckpt_dir)
|
||||
shutil.copy(
|
||||
os.path.join(model_root, 'face_alignment', 'depth-6c4283c0e0.zip'),
|
||||
save_ckpt_dir)
|
||||
self.lm_sess = face_alignment.FaceAlignment(
|
||||
face_alignment.LandmarksType._3D, flip_input=False)
|
||||
|
||||
config = tf.ConfigProto(allow_soft_placement=True)
|
||||
config.gpu_options.per_process_gpu_memory_fraction = 0.2
|
||||
config.gpu_options.allow_growth = True
|
||||
g1 = tf.Graph()
|
||||
self.face_sess = tf.Session(graph=g1, config=config)
|
||||
with self.face_sess.as_default():
|
||||
with g1.as_default():
|
||||
with tf.gfile.FastGFile(
|
||||
os.path.join(model_root, 'segment_face.pb'),
|
||||
'rb') as f:
|
||||
graph_def = tf.GraphDef()
|
||||
graph_def.ParseFromString(f.read())
|
||||
self.face_sess.graph.as_default()
|
||||
tf.import_graph_def(graph_def, name='')
|
||||
self.face_sess.run(tf.global_variables_initializer())
|
||||
|
||||
self.tex_size = 4096
|
||||
|
||||
self.bald_tex_bg = cv2.imread(
|
||||
'{}/assets/template_texture.jpg'.format(model_root)).astype(
|
||||
np.float32)
|
||||
|
||||
front_mask = cv2.imread(
|
||||
'{}/assets/face_mask.jpg'.format(model_root)).astype(
|
||||
np.float32) / 255
|
||||
front_mask = cv2.resize(front_mask, (1024, 1024))
|
||||
front_mask = cv2.resize(front_mask, (0, 0), fx=0.1, fy=0.1)
|
||||
front_mask = cv2.erode(front_mask,
|
||||
np.ones(shape=(7, 7), dtype=np.float32))
|
||||
front_mask = cv2.GaussianBlur(front_mask, (13, 13), 0)
|
||||
self.front_mask = cv2.resize(front_mask,
|
||||
(self.tex_size, self.tex_size))
|
||||
self.binary_front_mask = self.front_mask.copy()
|
||||
self.binary_front_mask[(self.front_mask < 0.3)
|
||||
+ (self.front_mask > 0.7)] = 0
|
||||
self.binary_front_mask[self.binary_front_mask != 0] = 1.0
|
||||
self.binary_front_mask_ = self.binary_front_mask.copy()
|
||||
self.binary_front_mask = np.zeros((4096 + 1024, 4096, 3),
|
||||
dtype=np.float32)
|
||||
self.binary_front_mask[:4096, :] = self.binary_front_mask_
|
||||
self.front_mask_ = self.front_mask.copy()
|
||||
self.front_mask = np.zeros((4096 + 1024, 4096, 3), dtype=np.float32)
|
||||
self.front_mask[:4096, :] = self.front_mask_
|
||||
|
||||
l_eye_mask = cv2.imread(
|
||||
'{}/assets/l_eye_mask.png'.format(model_root))[:, :, :1] / 255.0
|
||||
l_eye_mask = cv2.erode(l_eye_mask,
|
||||
np.ones(shape=(5, 5), dtype=np.float32))
|
||||
self.l_eye_mask = cv2.GaussianBlur(l_eye_mask, (7, 7), 0)[..., None]
|
||||
self.l_eye_binary_mask = self.l_eye_mask.copy()
|
||||
self.l_eye_binary_mask[(self.l_eye_mask < 0.3)
|
||||
+ (self.l_eye_mask > 0.7)] = 0
|
||||
self.l_eye_binary_mask[self.l_eye_binary_mask != 0] = 1.0
|
||||
|
||||
r_eye_mask = cv2.imread(
|
||||
'{}/assets/r_eye_mask.png'.format(model_root))[:, :, :1] / 255.0
|
||||
r_eye_mask = cv2.dilate(r_eye_mask,
|
||||
np.ones(shape=(7, 7), dtype=np.float32))
|
||||
self.r_eye_mask = cv2.GaussianBlur(r_eye_mask, (7, 7), 0)[..., None]
|
||||
self.r_eye_binary_mask = self.r_eye_mask.copy()
|
||||
self.r_eye_binary_mask[(self.r_eye_mask < 0.3)
|
||||
+ (self.r_eye_mask > 0.7)] = 0
|
||||
self.r_eye_binary_mask[self.r_eye_binary_mask != 0] = 1.0
|
||||
|
||||
self.lm3d_std = load_lm3d(bfm_folder)
|
||||
self.align_params = loadmat(
|
||||
'{}/assets/BBRegressorParam_r.mat'.format(model_root))
|
||||
|
||||
device = create_device(self.device_name)
|
||||
self.device = device
|
||||
|
||||
def preprocess(self, input: Input) -> Dict[str, Any]:
|
||||
img = LoadImage.convert_to_ndarray(input)
|
||||
if len(img.shape) == 2:
|
||||
img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
|
||||
img = img.astype(np.float)
|
||||
result = {'img': img}
|
||||
return result
|
||||
|
||||
def read_data(self,
|
||||
img,
|
||||
lm,
|
||||
lm3d_std,
|
||||
to_tensor=True,
|
||||
image_res=1024,
|
||||
img_fat=None):
|
||||
# to RGB
|
||||
im = PIL.Image.fromarray(img[..., ::-1])
|
||||
W, H = im.size
|
||||
lm[:, -1] = H - 1 - lm[:, -1]
|
||||
|
||||
im_lr_coeff, lm_lr_coeff = None, None
|
||||
head_mask = None
|
||||
|
||||
_, im_lr, lm_lr, mask_lr_head = align_img(
|
||||
im, lm, lm3d_std, mask=head_mask)
|
||||
_, im_hd, lm_hd, _ = align_img(
|
||||
im,
|
||||
lm,
|
||||
lm3d_std,
|
||||
target_size=image_res,
|
||||
rescale_factor=102.0 * image_res / 224)
|
||||
|
||||
mask_lr = self.face_sess.run(
|
||||
self.face_sess.graph.get_tensor_by_name('output_alpha:0'),
|
||||
feed_dict={'input_image:0': np.array(im_lr)})
|
||||
|
||||
if img_fat is not None:
|
||||
assert img_fat.shape == img.shape
|
||||
im_fat = PIL.Image.fromarray(img_fat[..., ::-1])
|
||||
|
||||
_, im_hd, _, _ = align_img(
|
||||
im_fat,
|
||||
lm,
|
||||
lm3d_std,
|
||||
target_size=image_res,
|
||||
rescale_factor=102.0 * image_res / 224)
|
||||
|
||||
im_hd = np.array(im_hd).astype(np.float32)
|
||||
|
||||
if to_tensor:
|
||||
im_lr = torch.tensor(
|
||||
np.array(im_lr) / 255.,
|
||||
dtype=torch.float32).permute(2, 0, 1).unsqueeze(0)
|
||||
im_hd = torch.tensor(
|
||||
np.array(im_hd) / 255.,
|
||||
dtype=torch.float32).permute(2, 0, 1).unsqueeze(0)
|
||||
mask_lr = torch.tensor(
|
||||
np.array(mask_lr) / 255., dtype=torch.float32)[None,
|
||||
None, :, :]
|
||||
mask_lr_head = torch.tensor(
|
||||
np.array(mask_lr_head) / 255., dtype=torch.float32)[
|
||||
None, None, :, :] if mask_lr_head is not None else None
|
||||
lm_lr = torch.tensor(lm_lr).unsqueeze(0)
|
||||
lm_hd = torch.tensor(lm_hd).unsqueeze(0)
|
||||
return im_lr, lm_lr, im_hd, lm_hd, mask_lr, mask_lr_head, im_lr_coeff, lm_lr_coeff
|
||||
|
||||
def prepare_data(self, img, lm_sess, five_points=None):
|
||||
input_img, scale, bbox = align_for_lm(
|
||||
img, five_points,
|
||||
self.align_params) # align for 68 landmark detection
|
||||
|
||||
if scale == 0:
|
||||
return None
|
||||
|
||||
# detect landmarks
|
||||
input_img = np.reshape(input_img, [1, 224, 224, 3]).astype(np.float32)
|
||||
|
||||
input_img = input_img[0, :, :, ::-1]
|
||||
landmark = lm_sess.get_landmarks_from_image(input_img)[0]
|
||||
|
||||
landmark = landmark[:, :2] / scale
|
||||
landmark[:, 0] = landmark[:, 0] + bbox[0]
|
||||
landmark[:, 1] = landmark[:, 1] + bbox[1]
|
||||
|
||||
return landmark
|
||||
|
||||
def blend_eye_corner(self, tex_map, template_tex):
|
||||
tex_map = tex_map.astype(np.float32)
|
||||
|
||||
x1 = int(288 * 4096 / 758)
|
||||
y1 = int(235 * 4096 / 758)
|
||||
w = int(90 * 4096 / 758)
|
||||
h = int(50 * 4096 / 758)
|
||||
template_tex_l = template_tex[y1:y1 + h, x1:x1 + w]
|
||||
pred_tex_l = tex_map[y1:y1 + h, x1:x1 + w]
|
||||
pred_tex_l_mean_rgb = np.sum(
|
||||
pred_tex_l * self.l_eye_binary_mask, axis=(0, 1))
|
||||
template_tex_l_mean_rgb = np.sum(
|
||||
template_tex_l * self.l_eye_binary_mask, axis=(0, 1))
|
||||
for ch in range(3):
|
||||
template_tex_l[:, :, ch] *= pred_tex_l_mean_rgb[
|
||||
ch] / template_tex_l_mean_rgb[ch]
|
||||
pred_tex_l = pred_tex_l * (
|
||||
1 - self.l_eye_mask) + template_tex_l * self.l_eye_mask
|
||||
|
||||
x2 = 4096 - x1 - w
|
||||
y2 = y1
|
||||
template_tex_r = template_tex[y2:y2 + h, x2:x2 + w]
|
||||
pred_tex_r = tex_map[y2:y2 + h, x2:x2 + w]
|
||||
pred_tex_r_mean_rgb = np.sum(
|
||||
pred_tex_r * self.r_eye_binary_mask, axis=(0, 1))
|
||||
template_tex_r_mean_rgb = np.sum(
|
||||
template_tex_r * self.r_eye_binary_mask, axis=(0, 1))
|
||||
for ch in range(3):
|
||||
template_tex_r[:, :, ch] *= pred_tex_r_mean_rgb[
|
||||
ch] / template_tex_r_mean_rgb[ch]
|
||||
pred_tex_r = pred_tex_r * (
|
||||
1 - self.r_eye_mask) + template_tex_r * self.r_eye_mask
|
||||
|
||||
tex_map[y1:y1 + h, x1:x1 + w] = pred_tex_l
|
||||
tex_map[y2:y2 + h, x2:x2 + w] = pred_tex_r
|
||||
|
||||
return tex_map
|
||||
|
||||
def forward(self, input: Dict[str, Any]) -> Dict[str, Any]:
|
||||
rgb_image = input['img'].cpu().numpy().astype(np.uint8)
|
||||
|
||||
bgr_image = cv2.cvtColor(rgb_image, cv2.COLOR_RGB2BGR)
|
||||
|
||||
img = bgr_image
|
||||
# preprocess
|
||||
flag = 0
|
||||
box, results = self.face_mark_model.infer(img)
|
||||
if results is None or np.array(results).shape[0] == 0:
|
||||
flag = 1 # no face
|
||||
return flag, {}
|
||||
|
||||
fatbgr = self.face_mark_model.fat_face(img, degree=0.02)
|
||||
|
||||
landmarks = []
|
||||
results = results[0]
|
||||
for idx in [74, 83, 54, 84, 90]:
|
||||
landmarks.append([results[idx][0], results[idx][1]])
|
||||
landmarks = np.array(landmarks)
|
||||
|
||||
landmarks = self.prepare_data(img, self.lm_sess, five_points=landmarks)
|
||||
|
||||
im_tensor, lm_tensor, im_hd_tensor, lm_hd_tensor, mask, _, _, _ = self.read_data(
|
||||
img, landmarks, self.lm3d_std, image_res=1024, img_fat=fatbgr)
|
||||
data = {
|
||||
'imgs': im_tensor,
|
||||
'imgs_hd': im_hd_tensor,
|
||||
'lms': lm_tensor,
|
||||
'lms_hd': lm_hd_tensor,
|
||||
'face_mask': mask,
|
||||
'img_name': 'temp',
|
||||
}
|
||||
self.model.set_input(data) # unpack data from data loader
|
||||
|
||||
# reconstruct
|
||||
out_dir = None
|
||||
output = self.model(out_dir=out_dir) # run inference
|
||||
|
||||
# process texture map
|
||||
tex_map = output['head_tex_map'].astype(np.float32)
|
||||
tex_map = cv2.resize(tex_map, (self.tex_size, self.tex_size + 1024))
|
||||
bg_mean_rgb = np.sum(
|
||||
self.bald_tex_bg * self.binary_front_mask, axis=(0, 1))
|
||||
pred_tex_mean_rgb = np.sum(
|
||||
tex_map * self.binary_front_mask, axis=(0, 1)) * 1.05
|
||||
mid_mean_rgb = bg_mean_rgb * 0.8 + pred_tex_mean_rgb * 0.2
|
||||
tex_map += (
|
||||
(mid_mean_rgb - pred_tex_mean_rgb)
|
||||
/ np.sum(self.binary_front_mask, axis=(0, 1)))[None, None] * 0.5
|
||||
pred_tex_mean_rgb = np.sum(
|
||||
tex_map * self.binary_front_mask, axis=(0, 1)) * 1.05
|
||||
_bald_tex_bg = self.bald_tex_bg.copy()
|
||||
for ch in range(3):
|
||||
_bald_tex_bg[:, :, ch] *= pred_tex_mean_rgb[ch] / bg_mean_rgb[ch]
|
||||
tex_map = _bald_tex_bg * (
|
||||
1. - self.front_mask) + tex_map * self.front_mask
|
||||
tex_map = tex_map * 1.05
|
||||
tex_map = self.blend_eye_corner(tex_map, self.bald_tex_bg)
|
||||
|
||||
# export mesh
|
||||
results = {
|
||||
'vertices': output['head_vertices'],
|
||||
'faces': output['head_faces'],
|
||||
'UVs': output['head_UVs'],
|
||||
'faces_uv': output['head_faces_uv'],
|
||||
'normals': output['head_normals'],
|
||||
'texture_map': tex_map,
|
||||
}
|
||||
|
||||
if out_dir is not None:
|
||||
face_mesh = {
|
||||
'vertices': output['face_vertices'],
|
||||
'faces': output['face_faces'],
|
||||
'colors': output['face_colors'],
|
||||
}
|
||||
|
||||
write_obj(os.path.join(out_dir, 'face.obj'), face_mesh)
|
||||
write_obj(os.path.join(out_dir, 'head.obj'), results)
|
||||
|
||||
return {OutputKeys.OUTPUT: results}
|
||||
|
||||
def postprocess(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
|
||||
return inputs
|
||||
@@ -124,6 +124,9 @@ class CVTasks(object):
|
||||
# domain specific object detection
|
||||
domain_specific_object_detection = 'domain-specific-object-detection'
|
||||
|
||||
# 3d reconstruction
|
||||
face_reconstruction = 'face-reconstruction'
|
||||
|
||||
# image quality assessment mos
|
||||
image_quality_assessment_mos = 'image-quality-assessment-mos'
|
||||
# motion generation
|
||||
|
||||
@@ -6,6 +6,7 @@ clip>=1.0
|
||||
ddpm_guided_diffusion
|
||||
easydict
|
||||
easyrobust
|
||||
face_alignment>=1.3.5
|
||||
fairscale>=0.4.1
|
||||
fastai>=1.0.51
|
||||
ffmpeg>=1.4
|
||||
@@ -41,6 +42,7 @@ tensorflow-estimator>=1.15.1
|
||||
tf_slim
|
||||
timm>=0.4.9
|
||||
torchmetrics>=0.6.2
|
||||
torchsummary>=1.5.1
|
||||
torchvision
|
||||
ujson
|
||||
utils
|
||||
|
||||
52
tests/pipelines/test_face_reconstruction.py
Normal file
52
tests/pipelines/test_face_reconstruction.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# Copyright (c) Alibaba, Inc. and its affiliates.
|
||||
import os.path as osp
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from modelscope.hub.snapshot_download import snapshot_download
|
||||
from modelscope.models.cv.face_reconstruction.utils import write_obj
|
||||
from modelscope.outputs import OutputKeys
|
||||
from modelscope.pipelines import pipeline
|
||||
from modelscope.pipelines.base import Pipeline
|
||||
from modelscope.utils.constant import Tasks
|
||||
from modelscope.utils.demo_utils import DemoCompatibilityCheck
|
||||
from modelscope.utils.test_utils import test_level
|
||||
|
||||
sys.path.append('.')
|
||||
|
||||
|
||||
class FaceReconstructionTest(unittest.TestCase, DemoCompatibilityCheck):
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.task = Tasks.face_reconstruction
|
||||
self.model_id = 'damo/cv_resnet50_face-reconstruction'
|
||||
self.test_image = 'data/test/images/face_reconstruction.jpg'
|
||||
|
||||
def pipeline_inference(self, pipeline: Pipeline, input_location: str):
|
||||
result = pipeline(input_location)
|
||||
mesh = result[OutputKeys.OUTPUT]
|
||||
write_obj('result_face_reconstruction.obj', mesh)
|
||||
print(
|
||||
f'Output written to {osp.abspath("result_face_reconstruction.obj")}'
|
||||
)
|
||||
|
||||
@unittest.skipUnless(test_level() >= 2, 'skip test in current test level')
|
||||
def test_run_by_direct_model_download(self):
|
||||
model_dir = snapshot_download(self.model_id)
|
||||
face_reconstruction = pipeline(
|
||||
Tasks.face_reconstruction, model=model_dir)
|
||||
self.pipeline_inference(face_reconstruction, self.test_image)
|
||||
|
||||
@unittest.skipUnless(test_level() >= 0, 'skip test in current test level')
|
||||
def test_run_modelhub(self):
|
||||
face_reconstruction = pipeline(
|
||||
Tasks.face_reconstruction, model=self.model_id)
|
||||
self.pipeline_inference(face_reconstruction, self.test_image)
|
||||
|
||||
@unittest.skip('demo compatibility test is only enabled on a needed-basis')
|
||||
def test_demo_compatibility(self):
|
||||
self.compatibility_check()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user