CoolFace
Apppublic

Shellbrady/LivePortrait5

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
live_portrait_wrapper.py308 linesDownload Raw Back to src
1# coding: utf-82 3"""4Wrapper for LivePortrait core functions5"""6 7import os.path as osp8import numpy as np9import cv210import torch11import yaml12 13from .utils.timer import Timer14from .utils.helper import load_model, concat_feat15from .utils.camera import headpose_pred_to_degree, get_rotation_matrix16from .utils.retargeting_utils import calc_eye_close_ratio, calc_lip_close_ratio17from .config.inference_config import InferenceConfig18from .utils.rprint import rlog as log19 20 21class LivePortraitWrapper(object):22 23    def __init__(self, cfg: InferenceConfig):24 25        model_config = yaml.load(open(cfg.models_config, 'r'), Loader=yaml.SafeLoader)26 27        # init F28        self.appearance_feature_extractor = load_model(cfg.checkpoint_F, model_config, cfg.device_id, 'appearance_feature_extractor')29        log(f'Load appearance_feature_extractor done.')30        # init M31        self.motion_extractor = load_model(cfg.checkpoint_M, model_config, cfg.device_id, 'motion_extractor')32        log(f'Load motion_extractor done.')33        # init W34        self.warping_module = load_model(cfg.checkpoint_W, model_config, cfg.device_id, 'warping_module')35        log(f'Load warping_module done.')36        # init G37        self.spade_generator = load_model(cfg.checkpoint_G, model_config, cfg.device_id, 'spade_generator')38        log(f'Load spade_generator done.')39        # init S and R40        if cfg.checkpoint_S is not None and osp.exists(cfg.checkpoint_S):41            self.stitching_retargeting_module = load_model(cfg.checkpoint_S, model_config, cfg.device_id, 'stitching_retargeting_module')42            log(f'Load stitching_retargeting_module done.')43        else:44            self.stitching_retargeting_module = None45 46        self.cfg = cfg47        self.device_id = cfg.device_id48        self.timer = Timer()49 50    def update_config(self, user_args):51        for k, v in user_args.items():52            if hasattr(self.cfg, k):53                setattr(self.cfg, k, v)54 55    def prepare_source(self, img: np.ndarray) -> torch.Tensor:56        """ construct the input as standard57        img: HxWx3, uint8, 256x25658        """59        h, w = img.shape[:2]60        if h != self.cfg.input_shape[0] or w != self.cfg.input_shape[1]:61            x = cv2.resize(img, (self.cfg.input_shape[0], self.cfg.input_shape[1]))62        else:63            x = img.copy()64 65        if x.ndim == 3:66            x = x[np.newaxis].astype(np.float32) / 255.  # HxWx3 -> 1xHxWx3, normalized to 0~167        elif x.ndim == 4:68            x = x.astype(np.float32) / 255.  # BxHxWx3, normalized to 0~169        else:70            raise ValueError(f'img ndim should be 3 or 4: {x.ndim}')71        x = np.clip(x, 0, 1)  # clip to 0~172        x = torch.from_numpy(x).permute(0, 3, 1, 2)  # 1xHxWx3 -> 1x3xHxW73        x = x.cuda(self.device_id)74        return x75 76    def prepare_driving_videos(self, imgs) -> torch.Tensor:77        """ construct the input as standard78        imgs: NxBxHxWx3, uint879        """80        if isinstance(imgs, list):81            _imgs = np.array(imgs)[..., np.newaxis]  # TxHxWx3x182        elif isinstance(imgs, np.ndarray):83            _imgs = imgs84        else:85            raise ValueError(f'imgs type error: {type(imgs)}')86 87        y = _imgs.astype(np.float32) / 255.88        y = np.clip(y, 0, 1)  # clip to 0~189        y = torch.from_numpy(y).permute(0, 4, 3, 1, 2)  # TxHxWx3x1 -> Tx1x3xHxW90        y = y.cuda(self.device_id)91 92        return y93 94    def extract_feature_3d(self, x: torch.Tensor) -> torch.Tensor:95        """ get the appearance feature of the image by F96        x: Bx3xHxW, normalized to 0~197        """98        with torch.no_grad():99            with torch.autocast(device_type='cuda', dtype=torch.float16, enabled=self.cfg.flag_use_half_precision):100                feature_3d = self.appearance_feature_extractor(x)101 102        return feature_3d.float()103 104    def get_kp_info(self, x: torch.Tensor, **kwargs) -> dict:105        """ get the implicit keypoint information106        x: Bx3xHxW, normalized to 0~1107        flag_refine_info: whether to trandform the pose to degrees and the dimention of the reshape108        return: A dict contains keys: 'pitch', 'yaw', 'roll', 't', 'exp', 'scale', 'kp'109        """110        with torch.no_grad():111            with torch.autocast(device_type='cuda', dtype=torch.float16, enabled=self.cfg.flag_use_half_precision):112                kp_info = self.motion_extractor(x)113 114            if self.cfg.flag_use_half_precision:115                # float the dict116                for k, v in kp_info.items():117                    if isinstance(v, torch.Tensor):118                        kp_info[k] = v.float()119 120        flag_refine_info: bool = kwargs.get('flag_refine_info', True)121        if flag_refine_info:122            bs = kp_info['kp'].shape[0]123            kp_info['pitch'] = headpose_pred_to_degree(kp_info['pitch'])[:, None]  # Bx1124            kp_info['yaw'] = headpose_pred_to_degree(kp_info['yaw'])[:, None]  # Bx1125            kp_info['roll'] = headpose_pred_to_degree(kp_info['roll'])[:, None]  # Bx1126            kp_info['kp'] = kp_info['kp'].reshape(bs, -1, 3)  # BxNx3127            kp_info['exp'] = kp_info['exp'].reshape(bs, -1, 3)  # BxNx3128 129        return kp_info130 131    def get_pose_dct(self, kp_info: dict) -> dict:132        pose_dct = dict(133            pitch=headpose_pred_to_degree(kp_info['pitch']).item(),134            yaw=headpose_pred_to_degree(kp_info['yaw']).item(),135            roll=headpose_pred_to_degree(kp_info['roll']).item(),136        )137        return pose_dct138 139    def get_fs_and_kp_info(self, source_prepared, driving_first_frame):140 141        # get the canonical keypoints of source image by M142        source_kp_info = self.get_kp_info(source_prepared, flag_refine_info=True)143        source_rotation = get_rotation_matrix(source_kp_info['pitch'], source_kp_info['yaw'], source_kp_info['roll'])144 145        # get the canonical keypoints of first driving frame by M146        driving_first_frame_kp_info = self.get_kp_info(driving_first_frame, flag_refine_info=True)147        driving_first_frame_rotation = get_rotation_matrix(148            driving_first_frame_kp_info['pitch'],149            driving_first_frame_kp_info['yaw'],150            driving_first_frame_kp_info['roll']151        )152 153        # get feature volume by F154        source_feature_3d = self.extract_feature_3d(source_prepared)155 156        return source_kp_info, source_rotation, source_feature_3d, driving_first_frame_kp_info, driving_first_frame_rotation157 158    def transform_keypoint(self, kp_info: dict):159        """160        transform the implicit keypoints with the pose, shift, and expression deformation161        kp: BxNx3162        """163        kp = kp_info['kp']    # (bs, k, 3)164        pitch, yaw, roll = kp_info['pitch'], kp_info['yaw'], kp_info['roll']165 166        t, exp = kp_info['t'], kp_info['exp']167        scale = kp_info['scale']168 169        pitch = headpose_pred_to_degree(pitch)170        yaw = headpose_pred_to_degree(yaw)171        roll = headpose_pred_to_degree(roll)172 173        bs = kp.shape[0]174        if kp.ndim == 2:175            num_kp = kp.shape[1] // 3  # Bx(num_kpx3)176        else:177            num_kp = kp.shape[1]  # Bxnum_kpx3178 179        rot_mat = get_rotation_matrix(pitch, yaw, roll)    # (bs, 3, 3)180 181        # Eqn.2: s * (R * x_c,s + exp) + t182        kp_transformed = kp.view(bs, num_kp, 3) @ rot_mat + exp.view(bs, num_kp, 3)183        kp_transformed *= scale[..., None]  # (bs, k, 3) * (bs, 1, 1) = (bs, k, 3)184        kp_transformed[:, :, 0:2] += t[:, None, 0:2]  # remove z, only apply tx ty185 186        return kp_transformed187 188    def retarget_eye(self, kp_source: torch.Tensor, eye_close_ratio: torch.Tensor) -> torch.Tensor:189        """190        kp_source: BxNx3191        eye_close_ratio: Bx3192        Return: Bx(3*num_kp+2)193        """194        feat_eye = concat_feat(kp_source, eye_close_ratio)195 196        with torch.no_grad():197            delta = self.stitching_retargeting_module['eye'](feat_eye)198 199        return delta200 201    def retarget_lip(self, kp_source: torch.Tensor, lip_close_ratio: torch.Tensor) -> torch.Tensor:202        """203        kp_source: BxNx3204        lip_close_ratio: Bx2205        """206        feat_lip = concat_feat(kp_source, lip_close_ratio)207 208        with torch.no_grad():209            delta = self.stitching_retargeting_module['lip'](feat_lip)210 211        return delta212 213    def stitch(self, kp_source: torch.Tensor, kp_driving: torch.Tensor) -> torch.Tensor:214        """215        kp_source: BxNx3216        kp_driving: BxNx3217        Return: Bx(3*num_kp+2)218        """219        feat_stiching = concat_feat(kp_source, kp_driving)220 221        with torch.no_grad():222            delta = self.stitching_retargeting_module['stitching'](feat_stiching)223 224        return delta225 226    def stitching(self, kp_source: torch.Tensor, kp_driving: torch.Tensor) -> torch.Tensor:227        """ conduct the stitching228        kp_source: Bxnum_kpx3229        kp_driving: Bxnum_kpx3230        """231 232        if self.stitching_retargeting_module is not None:233 234            bs, num_kp = kp_source.shape[:2]235 236            kp_driving_new = kp_driving.clone()237            delta = self.stitch(kp_source, kp_driving_new)238 239            delta_exp = delta[..., :3*num_kp].reshape(bs, num_kp, 3)  # 1x20x3240            delta_tx_ty = delta[..., 3*num_kp:3*num_kp+2].reshape(bs, 1, 2)  # 1x1x2241 242            kp_driving_new += delta_exp243            kp_driving_new[..., :2] += delta_tx_ty244 245            return kp_driving_new246 247        return kp_driving248 249    def warp_decode(self, feature_3d: torch.Tensor, kp_source: torch.Tensor, kp_driving: torch.Tensor) -> torch.Tensor:250        """ get the image after the warping of the implicit keypoints251        feature_3d: Bx32x16x64x64, feature volume252        kp_source: BxNx3253        kp_driving: BxNx3254        """255        # The line 18 in Algorithm 1: D(W(f_s; x_s, x′_d,i))256        with torch.no_grad():257            with torch.autocast(device_type='cuda', dtype=torch.float16, enabled=self.cfg.flag_use_half_precision):258                # get decoder input259                ret_dct = self.warping_module(feature_3d, kp_source=kp_source, kp_driving=kp_driving)260                # decode261                ret_dct['out'] = self.spade_generator(feature=ret_dct['out'])262 263            # float the dict264            if self.cfg.flag_use_half_precision:265                for k, v in ret_dct.items():266                    if isinstance(v, torch.Tensor):267                        ret_dct[k] = v.float()268 269        return ret_dct270 271    def parse_output(self, out: torch.Tensor) -> np.ndarray:272        """ construct the output as standard273        return: 1xHxWx3, uint8274        """275        out = np.transpose(out.data.cpu().numpy(), [0, 2, 3, 1])  # 1x3xHxW -> 1xHxWx3276        out = np.clip(out, 0, 1)  # clip to 0~1277        out = np.clip(out * 255, 0, 255).astype(np.uint8)  # 0~1 -> 0~255278 279        return out280 281    def calc_retargeting_ratio(self, source_lmk, driving_lmk_lst):282        input_eye_ratio_lst = []283        input_lip_ratio_lst = []284        for lmk in driving_lmk_lst:285            # for eyes retargeting286            input_eye_ratio_lst.append(calc_eye_close_ratio(lmk[None]))287            # for lip retargeting288            input_lip_ratio_lst.append(calc_lip_close_ratio(lmk[None]))289        return input_eye_ratio_lst, input_lip_ratio_lst290 291    def calc_combined_eye_ratio(self, input_eye_ratio, source_lmk):292        eye_close_ratio = calc_eye_close_ratio(source_lmk[None])293        eye_close_ratio_tensor = torch.from_numpy(eye_close_ratio).float().cuda(self.device_id)294        input_eye_ratio_tensor = torch.Tensor([input_eye_ratio[0][0]]).reshape(1, 1).cuda(self.device_id)295        # [c_s,eyes, c_d,eyes,i]296        combined_eye_ratio_tensor = torch.cat([eye_close_ratio_tensor, input_eye_ratio_tensor], dim=1)297        return combined_eye_ratio_tensor298 299    def calc_combined_lip_ratio(self, input_lip_ratio, source_lmk):300        lip_close_ratio = calc_lip_close_ratio(source_lmk[None])301        lip_close_ratio_tensor = torch.from_numpy(lip_close_ratio).float().cuda(self.device_id)302        # [c_s,lip, c_d,lip,i]303        input_lip_ratio_tensor = torch.Tensor([input_lip_ratio[0]]).cuda(self.device_id)304        if input_lip_ratio_tensor.shape != [1, 1]:305            input_lip_ratio_tensor = input_lip_ratio_tensor.reshape(1, 1)306        combined_lip_ratio_tensor = torch.cat([lip_close_ratio_tensor, input_lip_ratio_tensor], dim=1)307        return combined_lip_ratio_tensor308