Spaces:
Running
on
Zero
Running
on
Zero
Upload 115 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +1 -0
- difpoint/src/__pycache__/live_portrait_pipeline.cpython-310.pyc +0 -0
- difpoint/src/__pycache__/live_portrait_wrapper.cpython-310.pyc +0 -0
- difpoint/src/config/__init__.py +0 -0
- difpoint/src/config/__pycache__/__init__.cpython-310.pyc +0 -0
- difpoint/src/config/__pycache__/argument_config.cpython-310.pyc +0 -0
- difpoint/src/config/__pycache__/base_config.cpython-310.pyc +0 -0
- difpoint/src/config/__pycache__/crop_config.cpython-310.pyc +0 -0
- difpoint/src/config/__pycache__/inference_config.cpython-310.pyc +0 -0
- difpoint/src/config/argument_config.py +48 -0
- difpoint/src/config/base_config.py +29 -0
- difpoint/src/config/crop_config.py +29 -0
- difpoint/src/config/inference_config.py +52 -0
- difpoint/src/config/models.yaml +43 -0
- difpoint/src/gradio_pipeline.py +117 -0
- difpoint/src/live_portrait_pipeline.py +285 -0
- difpoint/src/live_portrait_wrapper.py +318 -0
- difpoint/src/modules/__init__.py +0 -0
- difpoint/src/modules/__pycache__/__init__.cpython-310.pyc +0 -0
- difpoint/src/modules/__pycache__/appearance_feature_extractor.cpython-310.pyc +0 -0
- difpoint/src/modules/__pycache__/convnextv2.cpython-310.pyc +0 -0
- difpoint/src/modules/__pycache__/dense_motion.cpython-310.pyc +0 -0
- difpoint/src/modules/__pycache__/motion_extractor.cpython-310.pyc +0 -0
- difpoint/src/modules/__pycache__/spade_generator.cpython-310.pyc +0 -0
- difpoint/src/modules/__pycache__/stitching_retargeting_network.cpython-310.pyc +0 -0
- difpoint/src/modules/__pycache__/util.cpython-310.pyc +0 -0
- difpoint/src/modules/__pycache__/warping_network.cpython-310.pyc +0 -0
- difpoint/src/modules/appearance_feature_extractor.py +48 -0
- difpoint/src/modules/convnextv2.py +149 -0
- difpoint/src/modules/dense_motion.py +104 -0
- difpoint/src/modules/motion_extractor.py +35 -0
- difpoint/src/modules/spade_generator.py +59 -0
- difpoint/src/modules/stitching_retargeting_network.py +38 -0
- difpoint/src/modules/util.py +441 -0
- difpoint/src/modules/warping_network.py +77 -0
- difpoint/src/utils/__init__.py +0 -5
- difpoint/src/utils/__pycache__/__init__.cpython-310.pyc +0 -0
- difpoint/src/utils/__pycache__/camera.cpython-310.pyc +0 -0
- difpoint/src/utils/__pycache__/crop.cpython-310.pyc +0 -0
- difpoint/src/utils/__pycache__/cropper.cpython-310.pyc +0 -0
- difpoint/src/utils/__pycache__/face_analysis_diy.cpython-310.pyc +0 -0
- difpoint/src/utils/__pycache__/helper.cpython-310.pyc +0 -0
- difpoint/src/utils/__pycache__/hparams.cpython-310.pyc +0 -0
- difpoint/src/utils/__pycache__/io.cpython-310.pyc +0 -0
- difpoint/src/utils/__pycache__/landmark_runner.cpython-310.pyc +0 -0
- difpoint/src/utils/__pycache__/retargeting_utils.cpython-310.pyc +0 -0
- difpoint/src/utils/__pycache__/rprint.cpython-310.pyc +0 -0
- difpoint/src/utils/__pycache__/timer.cpython-310.pyc +0 -0
- difpoint/src/utils/__pycache__/video.cpython-310.pyc +0 -0
- difpoint/src/utils/camera.py +73 -0
.gitattributes
CHANGED
@@ -37,3 +37,4 @@ difpoint/assets/docs/inference.gif filter=lfs diff=lfs merge=lfs -text
|
|
37 |
difpoint/assets/docs/showcase.gif filter=lfs diff=lfs merge=lfs -text
|
38 |
difpoint/assets/docs/showcase2.gif filter=lfs diff=lfs merge=lfs -text
|
39 |
src/utils/dependencies/insightface/data/images/t1.jpg filter=lfs diff=lfs merge=lfs -text
|
|
|
|
37 |
difpoint/assets/docs/showcase.gif filter=lfs diff=lfs merge=lfs -text
|
38 |
difpoint/assets/docs/showcase2.gif filter=lfs diff=lfs merge=lfs -text
|
39 |
src/utils/dependencies/insightface/data/images/t1.jpg filter=lfs diff=lfs merge=lfs -text
|
40 |
+
difpoint/src/utils/dependencies/insightface/data/images/t1.jpg filter=lfs diff=lfs merge=lfs -text
|
difpoint/src/__pycache__/live_portrait_pipeline.cpython-310.pyc
ADDED
Binary file (8.08 kB). View file
|
|
difpoint/src/__pycache__/live_portrait_wrapper.cpython-310.pyc
ADDED
Binary file (10.2 kB). View file
|
|
difpoint/src/config/__init__.py
ADDED
File without changes
|
difpoint/src/config/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (137 Bytes). View file
|
|
difpoint/src/config/__pycache__/argument_config.cpython-310.pyc
ADDED
Binary file (1.82 kB). View file
|
|
difpoint/src/config/__pycache__/base_config.cpython-310.pyc
ADDED
Binary file (1.08 kB). View file
|
|
difpoint/src/config/__pycache__/crop_config.cpython-310.pyc
ADDED
Binary file (1.14 kB). View file
|
|
difpoint/src/config/__pycache__/inference_config.cpython-310.pyc
ADDED
Binary file (2.39 kB). View file
|
|
difpoint/src/config/argument_config.py
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
|
3 |
+
"""
|
4 |
+
All configs for user
|
5 |
+
"""
|
6 |
+
|
7 |
+
from dataclasses import dataclass
|
8 |
+
import tyro
|
9 |
+
from typing_extensions import Annotated
|
10 |
+
from typing import Optional
|
11 |
+
from .base_config import PrintableConfig, make_abs_path
|
12 |
+
|
13 |
+
|
14 |
+
@dataclass(repr=False) # use repr from PrintableConfig
|
15 |
+
class ArgumentConfig(PrintableConfig):
|
16 |
+
########## input arguments ##########
|
17 |
+
source_image: Annotated[str, tyro.conf.arg(aliases=["-s"])] = make_abs_path('../../assets/examples/source/s6.jpg') # path to the source portrait
|
18 |
+
driving_info: Annotated[str, tyro.conf.arg(aliases=["-d"])] = make_abs_path('../../assets/examples/driving/d12.mp4') # path to driving video or template (.pkl format)
|
19 |
+
output_dir: Annotated[str, tyro.conf.arg(aliases=["-o"])] = 'animations/' # directory to save output video
|
20 |
+
|
21 |
+
########## inference arguments ##########
|
22 |
+
flag_use_half_precision: bool = False # whether to use half precision (FP16). If black boxes appear, it might be due to GPU incompatibility; set to False.
|
23 |
+
flag_crop_driving_video: bool = False # whether to crop the driving video, if the given driving info is a video
|
24 |
+
device_id: int = 0 # gpu device id
|
25 |
+
flag_force_cpu: bool = False # force cpu inference, WIP!
|
26 |
+
flag_lip_zero: bool = False # whether let the lip to close state before animation, only take effect when flag_eye_retargeting and flag_lip_retargeting is False
|
27 |
+
flag_eye_retargeting: bool = False # not recommend to be True, WIP
|
28 |
+
flag_lip_retargeting: bool = False # not recommend to be True, WIP
|
29 |
+
flag_stitching: bool = False # recommend to True if head movement is small, False if head movement is large
|
30 |
+
flag_relative_motion: bool = False # whether to use relative motion
|
31 |
+
flag_pasteback: bool = False # whether to paste-back/stitch the animated face cropping from the face-cropping space to the original image space
|
32 |
+
flag_do_crop: bool = False # whether to crop the source portrait to the face-cropping space
|
33 |
+
flag_do_rot: bool = False # whether to conduct the rotation when flag_do_crop is True
|
34 |
+
|
35 |
+
########## crop arguments ##########
|
36 |
+
scale: float = 2.3 # the ratio of face area is smaller if scale is larger
|
37 |
+
vx_ratio: float = 0 # the ratio to move the face to left or right in cropping space
|
38 |
+
vy_ratio: float = -0.125 # the ratio to move the face to up or down in cropping space
|
39 |
+
|
40 |
+
scale_crop_video: float = 2.2 # scale factor for cropping video
|
41 |
+
vx_ratio_crop_video: float = 0. # adjust y offset
|
42 |
+
vy_ratio_crop_video: float = -0.1 # adjust x offset
|
43 |
+
|
44 |
+
########## gradio arguments ##########
|
45 |
+
server_port: Annotated[int, tyro.conf.arg(aliases=["-p"])] = 8890 # port for gradio server
|
46 |
+
share: bool = False # whether to share the server to public
|
47 |
+
server_name: Optional[str] = "127.0.0.1" # set the local server name, "0.0.0.0" to broadcast all
|
48 |
+
flag_do_torch_compile: bool = False # whether to use torch.compile to accelerate generation
|
difpoint/src/config/base_config.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
|
3 |
+
"""
|
4 |
+
pretty printing class
|
5 |
+
"""
|
6 |
+
|
7 |
+
from __future__ import annotations
|
8 |
+
import os.path as osp
|
9 |
+
from typing import Tuple
|
10 |
+
|
11 |
+
|
12 |
+
def make_abs_path(fn):
|
13 |
+
return osp.join(osp.dirname(osp.realpath(__file__)), fn)
|
14 |
+
|
15 |
+
|
16 |
+
class PrintableConfig: # pylint: disable=too-few-public-methods
|
17 |
+
"""Printable Config defining str function"""
|
18 |
+
|
19 |
+
def __repr__(self):
|
20 |
+
lines = [self.__class__.__name__ + ":"]
|
21 |
+
for key, val in vars(self).items():
|
22 |
+
if isinstance(val, Tuple):
|
23 |
+
flattened_val = "["
|
24 |
+
for item in val:
|
25 |
+
flattened_val += str(item) + "\n"
|
26 |
+
flattened_val = flattened_val.rstrip("\n")
|
27 |
+
val = flattened_val + "]"
|
28 |
+
lines += f"{key}: {str(val)}".split("\n")
|
29 |
+
return "\n ".join(lines)
|
difpoint/src/config/crop_config.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
|
3 |
+
"""
|
4 |
+
parameters used for crop faces
|
5 |
+
"""
|
6 |
+
|
7 |
+
from dataclasses import dataclass
|
8 |
+
|
9 |
+
from .base_config import PrintableConfig
|
10 |
+
|
11 |
+
|
12 |
+
@dataclass(repr=False) # use repr from PrintableConfig
|
13 |
+
class CropConfig(PrintableConfig):
|
14 |
+
insightface_root: str = "../../dataset_process/pretrained_weights/insightface"
|
15 |
+
landmark_ckpt_path: str = "../../dataset_process/pretrained_weights/liveportrait/landmark.onnx"
|
16 |
+
device_id: int = 0 # gpu device id
|
17 |
+
flag_force_cpu: bool = False # force cpu inference, WIP
|
18 |
+
########## source image cropping option ##########
|
19 |
+
dsize: int = 512 # crop size
|
20 |
+
scale: float = 2.0 # scale factor
|
21 |
+
vx_ratio: float = 0 # vx ratio
|
22 |
+
vy_ratio: float = -0.125 # vy ratio +up, -down
|
23 |
+
max_face_num: int = 0 # max face number, 0 mean no limit
|
24 |
+
|
25 |
+
########## driving video auto cropping option ##########
|
26 |
+
scale_crop_video: float = 2.2 # 2.0 # scale factor for cropping video
|
27 |
+
vx_ratio_crop_video: float = 0.0 # adjust y offset
|
28 |
+
vy_ratio_crop_video: float = -0.1 # adjust x offset
|
29 |
+
direction: str = "large-small" # direction of cropping
|
difpoint/src/config/inference_config.py
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
|
3 |
+
"""
|
4 |
+
config dataclass used for inference
|
5 |
+
"""
|
6 |
+
|
7 |
+
import os.path as osp
|
8 |
+
import cv2
|
9 |
+
from numpy import ndarray
|
10 |
+
from dataclasses import dataclass
|
11 |
+
from typing import Literal, Tuple
|
12 |
+
from .base_config import PrintableConfig, make_abs_path
|
13 |
+
|
14 |
+
|
15 |
+
@dataclass(repr=False) # use repr from PrintableConfig
|
16 |
+
class InferenceConfig(PrintableConfig):
|
17 |
+
# MODEL CONFIG, NOT EXPORTED PARAMS
|
18 |
+
models_config: str = make_abs_path('./models.yaml') # portrait animation config
|
19 |
+
checkpoint_F: str = make_abs_path('../../dataset_process/pretrained_weights/liveportrait/base_models/appearance_feature_extractor.pth') # path to checkpoint of F
|
20 |
+
checkpoint_M: str = make_abs_path('../../dataset_process/pretrained_weights/liveportrait/base_models/motion_extractor.pth') # path to checkpoint pf M
|
21 |
+
checkpoint_G: str = make_abs_path('../../dataset_process/pretrained_weights/liveportrait/base_models/spade_generator.pth') # path to checkpoint of G
|
22 |
+
checkpoint_W: str = make_abs_path('../../dataset_process/pretrained_weights/liveportrait/base_models/warping_module.pth') # path to checkpoint of W
|
23 |
+
checkpoint_S: str = make_abs_path('../../dataset_process/pretrained_weights/liveportrait/retargeting_models/stitching_retargeting_module.pth') # path to checkpoint to S and R_eyes, R_lip
|
24 |
+
|
25 |
+
# EXPORTED PARAMS
|
26 |
+
flag_use_half_precision: bool = True
|
27 |
+
flag_crop_driving_video: bool = False
|
28 |
+
device_id: int = 0
|
29 |
+
flag_lip_zero: bool = False
|
30 |
+
flag_eye_retargeting: bool = False
|
31 |
+
flag_lip_retargeting: bool = False
|
32 |
+
flag_stitching: bool = False
|
33 |
+
flag_relative_motion: bool = False
|
34 |
+
flag_pasteback: bool = False
|
35 |
+
flag_do_crop: bool = False
|
36 |
+
flag_do_rot: bool = False
|
37 |
+
flag_force_cpu: bool = False
|
38 |
+
flag_do_torch_compile: bool = False
|
39 |
+
|
40 |
+
# NOT EXPORTED PARAMS
|
41 |
+
lip_zero_threshold: float = 0.03 # threshold for flag_lip_zero
|
42 |
+
anchor_frame: int = 0 # TO IMPLEMENT
|
43 |
+
|
44 |
+
input_shape: Tuple[int, int] = (256, 256) # input shape
|
45 |
+
output_format: Literal['mp4', 'gif'] = 'mp4' # output video format
|
46 |
+
crf: int = 15 # crf for output video
|
47 |
+
output_fps: int = 25 # default output fps
|
48 |
+
|
49 |
+
mask_crop: ndarray = cv2.imread(make_abs_path('../utils/resources/mask_template.png'), cv2.IMREAD_COLOR)
|
50 |
+
size_gif: int = 256 # default gif size, TO IMPLEMENT
|
51 |
+
source_max_dim: int = 1280 # the max dim of height and width of source image
|
52 |
+
source_division: int = 2 # make sure the height and width of source image can be divided by this number
|
difpoint/src/config/models.yaml
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
model_params:
|
2 |
+
appearance_feature_extractor_params: # the F in the paper
|
3 |
+
image_channel: 3
|
4 |
+
block_expansion: 64
|
5 |
+
num_down_blocks: 2
|
6 |
+
max_features: 512
|
7 |
+
reshape_channel: 32
|
8 |
+
reshape_depth: 16
|
9 |
+
num_resblocks: 6
|
10 |
+
motion_extractor_params: # the M in the paper
|
11 |
+
num_kp: 21
|
12 |
+
backbone: convnextv2_tiny
|
13 |
+
warping_module_params: # the W in the paper
|
14 |
+
num_kp: 21
|
15 |
+
block_expansion: 64
|
16 |
+
max_features: 512
|
17 |
+
num_down_blocks: 2
|
18 |
+
reshape_channel: 32
|
19 |
+
estimate_occlusion_map: True
|
20 |
+
dense_motion_params:
|
21 |
+
block_expansion: 32
|
22 |
+
max_features: 1024
|
23 |
+
num_blocks: 5
|
24 |
+
reshape_depth: 16
|
25 |
+
compress: 4
|
26 |
+
spade_generator_params: # the G in the paper
|
27 |
+
upscale: 2 # represents upsample factor 256x256 -> 512x512
|
28 |
+
block_expansion: 64
|
29 |
+
max_features: 512
|
30 |
+
num_down_blocks: 2
|
31 |
+
stitching_retargeting_module_params: # the S in the paper
|
32 |
+
stitching:
|
33 |
+
input_size: 126 # (21*3)*2
|
34 |
+
hidden_sizes: [128, 128, 64]
|
35 |
+
output_size: 65 # (21*3)+2(tx,ty)
|
36 |
+
lip:
|
37 |
+
input_size: 65 # (21*3)+2
|
38 |
+
hidden_sizes: [128, 128, 64]
|
39 |
+
output_size: 63 # (21*3)
|
40 |
+
eye:
|
41 |
+
input_size: 66 # (21*3)+3
|
42 |
+
hidden_sizes: [256, 256, 128, 128, 64]
|
43 |
+
output_size: 63 # (21*3)
|
difpoint/src/gradio_pipeline.py
ADDED
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
|
3 |
+
"""
|
4 |
+
Pipeline for gradio
|
5 |
+
"""
|
6 |
+
import gradio as gr
|
7 |
+
|
8 |
+
from .config.argument_config import ArgumentConfig
|
9 |
+
from .live_portrait_pipeline import LivePortraitPipeline
|
10 |
+
from .utils.io import load_img_online
|
11 |
+
from .utils.rprint import rlog as log
|
12 |
+
from .utils.crop import prepare_paste_back, paste_back
|
13 |
+
from .utils.camera import get_rotation_matrix
|
14 |
+
|
15 |
+
|
16 |
+
def update_args(args, user_args):
|
17 |
+
"""update the args according to user inputs
|
18 |
+
"""
|
19 |
+
for k, v in user_args.items():
|
20 |
+
if hasattr(args, k):
|
21 |
+
setattr(args, k, v)
|
22 |
+
return args
|
23 |
+
|
24 |
+
|
25 |
+
class GradioPipeline(LivePortraitPipeline):
|
26 |
+
|
27 |
+
def __init__(self, inference_cfg, crop_cfg, args: ArgumentConfig):
|
28 |
+
super().__init__(inference_cfg, crop_cfg)
|
29 |
+
# self.live_portrait_wrapper = self.live_portrait_wrapper
|
30 |
+
self.args = args
|
31 |
+
|
32 |
+
def execute_video(
|
33 |
+
self,
|
34 |
+
input_image_path,
|
35 |
+
input_video_path,
|
36 |
+
flag_relative_input,
|
37 |
+
flag_do_crop_input,
|
38 |
+
flag_remap_input,
|
39 |
+
flag_crop_driving_video_input
|
40 |
+
):
|
41 |
+
""" for video driven potrait animation
|
42 |
+
"""
|
43 |
+
if input_image_path is not None and input_video_path is not None:
|
44 |
+
args_user = {
|
45 |
+
'source_image': input_image_path,
|
46 |
+
'driving_info': input_video_path,
|
47 |
+
'flag_relative': flag_relative_input,
|
48 |
+
'flag_do_crop': flag_do_crop_input,
|
49 |
+
'flag_pasteback': flag_remap_input,
|
50 |
+
'flag_crop_driving_video': flag_crop_driving_video_input
|
51 |
+
}
|
52 |
+
# update config from user input
|
53 |
+
self.args = update_args(self.args, args_user)
|
54 |
+
self.live_portrait_wrapper.update_config(self.args.__dict__)
|
55 |
+
self.cropper.update_config(self.args.__dict__)
|
56 |
+
# video driven animation
|
57 |
+
video_path, video_path_concat = self.execute(self.args)
|
58 |
+
gr.Info("Run successfully!", duration=2)
|
59 |
+
return video_path, video_path_concat,
|
60 |
+
else:
|
61 |
+
raise gr.Error("The input source portrait or driving video hasn't been prepared yet 💥!", duration=5)
|
62 |
+
|
63 |
+
def execute_image(self, input_eye_ratio: float, input_lip_ratio: float, input_image, flag_do_crop=True):
|
64 |
+
""" for single image retargeting
|
65 |
+
"""
|
66 |
+
# disposable feature
|
67 |
+
f_s_user, x_s_user, source_lmk_user, crop_M_c2o, mask_ori, img_rgb = \
|
68 |
+
self.prepare_retargeting(input_image, flag_do_crop)
|
69 |
+
|
70 |
+
if input_eye_ratio is None or input_lip_ratio is None:
|
71 |
+
raise gr.Error("Invalid ratio input 💥!", duration=5)
|
72 |
+
else:
|
73 |
+
inference_cfg = self.live_portrait_wrapper.inference_cfg
|
74 |
+
x_s_user = x_s_user.to(self.live_portrait_wrapper.device)
|
75 |
+
f_s_user = f_s_user.to(self.live_portrait_wrapper.device)
|
76 |
+
# ∆_eyes,i = R_eyes(x_s; c_s,eyes, c_d,eyes,i)
|
77 |
+
combined_eye_ratio_tensor = self.live_portrait_wrapper.calc_combined_eye_ratio([[input_eye_ratio]], source_lmk_user)
|
78 |
+
eyes_delta = self.live_portrait_wrapper.retarget_eye(x_s_user, combined_eye_ratio_tensor)
|
79 |
+
# ∆_lip,i = R_lip(x_s; c_s,lip, c_d,lip,i)
|
80 |
+
combined_lip_ratio_tensor = self.live_portrait_wrapper.calc_combined_lip_ratio([[input_lip_ratio]], source_lmk_user)
|
81 |
+
lip_delta = self.live_portrait_wrapper.retarget_lip(x_s_user, combined_lip_ratio_tensor)
|
82 |
+
num_kp = x_s_user.shape[1]
|
83 |
+
# default: use x_s
|
84 |
+
x_d_new = x_s_user + eyes_delta.reshape(-1, num_kp, 3) + lip_delta.reshape(-1, num_kp, 3)
|
85 |
+
# D(W(f_s; x_s, x′_d))
|
86 |
+
out = self.live_portrait_wrapper.warp_decode(f_s_user, x_s_user, x_d_new)
|
87 |
+
out = self.live_portrait_wrapper.parse_output(out['out'])[0]
|
88 |
+
out_to_ori_blend = paste_back(out, crop_M_c2o, img_rgb, mask_ori)
|
89 |
+
gr.Info("Run successfully!", duration=2)
|
90 |
+
return out, out_to_ori_blend
|
91 |
+
|
92 |
+
def prepare_retargeting(self, input_image, flag_do_crop=True):
|
93 |
+
""" for single image retargeting
|
94 |
+
"""
|
95 |
+
if input_image is not None:
|
96 |
+
# gr.Info("Upload successfully!", duration=2)
|
97 |
+
inference_cfg = self.live_portrait_wrapper.inference_cfg
|
98 |
+
######## process source portrait ########
|
99 |
+
img_rgb = load_img_online(input_image, mode='rgb', max_dim=1280, n=16)
|
100 |
+
log(f"Load source image from {input_image}.")
|
101 |
+
crop_info = self.cropper.crop_source_image(img_rgb, self.cropper.crop_cfg)
|
102 |
+
if flag_do_crop:
|
103 |
+
I_s = self.live_portrait_wrapper.prepare_source(crop_info['img_crop_256x256'])
|
104 |
+
else:
|
105 |
+
I_s = self.live_portrait_wrapper.prepare_source(img_rgb)
|
106 |
+
x_s_info = self.live_portrait_wrapper.get_kp_info(I_s)
|
107 |
+
R_s = get_rotation_matrix(x_s_info['pitch'], x_s_info['yaw'], x_s_info['roll'])
|
108 |
+
############################################
|
109 |
+
f_s_user = self.live_portrait_wrapper.extract_feature_3d(I_s)
|
110 |
+
x_s_user = self.live_portrait_wrapper.transform_keypoint(x_s_info)
|
111 |
+
source_lmk_user = crop_info['lmk_crop']
|
112 |
+
crop_M_c2o = crop_info['M_c2o']
|
113 |
+
mask_ori = prepare_paste_back(inference_cfg.mask_crop, crop_info['M_c2o'], dsize=(img_rgb.shape[1], img_rgb.shape[0]))
|
114 |
+
return f_s_user, x_s_user, source_lmk_user, crop_M_c2o, mask_ori, img_rgb
|
115 |
+
else:
|
116 |
+
# when press the clear button, go here
|
117 |
+
raise gr.Error("The retargeting input hasn't been prepared yet 💥!", duration=5)
|
difpoint/src/live_portrait_pipeline.py
ADDED
@@ -0,0 +1,285 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
|
3 |
+
"""
|
4 |
+
Pipeline of LivePortrait
|
5 |
+
"""
|
6 |
+
|
7 |
+
import torch
|
8 |
+
torch.backends.cudnn.benchmark = True # disable CUDNN_BACKEND_EXECUTION_PLAN_DESCRIPTOR warning
|
9 |
+
|
10 |
+
import cv2; cv2.setNumThreads(0); cv2.ocl.setUseOpenCL(False)
|
11 |
+
import numpy as np
|
12 |
+
import os
|
13 |
+
import os.path as osp
|
14 |
+
from rich.progress import track
|
15 |
+
|
16 |
+
from .config.argument_config import ArgumentConfig
|
17 |
+
from .config.inference_config import InferenceConfig
|
18 |
+
from .config.crop_config import CropConfig
|
19 |
+
from .utils.cropper import Cropper
|
20 |
+
from .utils.camera import get_rotation_matrix
|
21 |
+
from .utils.video import images2video, concat_frames, get_fps, add_audio_to_video, has_audio_stream
|
22 |
+
from .utils.crop import prepare_paste_back, paste_back
|
23 |
+
from .utils.io import load_image_rgb, load_driving_info, resize_to_limit, dump, load
|
24 |
+
from .utils.helper import mkdir, basename, dct2device, is_video, is_template, remove_suffix
|
25 |
+
from .utils.rprint import rlog as log
|
26 |
+
# from .utils.viz import viz_lmk
|
27 |
+
from .live_portrait_wrapper import LivePortraitWrapper
|
28 |
+
|
29 |
+
|
30 |
+
def make_abs_path(fn):
|
31 |
+
return osp.join(osp.dirname(osp.realpath(__file__)), fn)
|
32 |
+
|
33 |
+
|
34 |
+
class LivePortraitPipeline(object):
|
35 |
+
|
36 |
+
def __init__(self, inference_cfg: InferenceConfig, crop_cfg: CropConfig):
|
37 |
+
self.live_portrait_wrapper: LivePortraitWrapper = LivePortraitWrapper(inference_cfg=inference_cfg)
|
38 |
+
self.cropper: Cropper = Cropper(crop_cfg=crop_cfg)
|
39 |
+
|
40 |
+
def execute(self, args: ArgumentConfig):
|
41 |
+
# for convenience
|
42 |
+
inf_cfg = self.live_portrait_wrapper.inference_cfg
|
43 |
+
device = self.live_portrait_wrapper.device
|
44 |
+
crop_cfg = self.cropper.crop_cfg
|
45 |
+
|
46 |
+
######## process source portrait ########
|
47 |
+
img_rgb = load_image_rgb(args.source_image)
|
48 |
+
img_rgb = resize_to_limit(img_rgb, inf_cfg.source_max_dim, inf_cfg.source_division)
|
49 |
+
log(f"Load source image from {args.source_image}")
|
50 |
+
|
51 |
+
crop_info = self.cropper.crop_source_image(img_rgb, crop_cfg)
|
52 |
+
if crop_info is None:
|
53 |
+
raise Exception("No face detected in the source image!")
|
54 |
+
source_lmk = crop_info['lmk_crop']
|
55 |
+
img_crop, img_crop_256x256 = crop_info['img_crop'], crop_info['img_crop_256x256']
|
56 |
+
|
57 |
+
if inf_cfg.flag_do_crop:
|
58 |
+
I_s = self.live_portrait_wrapper.prepare_source(img_crop_256x256)
|
59 |
+
else:
|
60 |
+
img_crop_256x256 = cv2.resize(img_rgb, (256, 256)) # force to resize to 256x256
|
61 |
+
I_s = self.live_portrait_wrapper.prepare_source(img_crop_256x256)
|
62 |
+
x_s_info = self.live_portrait_wrapper.get_kp_info(I_s)
|
63 |
+
x_c_s = x_s_info['kp']
|
64 |
+
R_s = get_rotation_matrix(x_s_info['pitch'], x_s_info['yaw'], x_s_info['roll'])
|
65 |
+
f_s = self.live_portrait_wrapper.extract_feature_3d(I_s)
|
66 |
+
x_s = self.live_portrait_wrapper.transform_keypoint(x_s_info)
|
67 |
+
|
68 |
+
flag_lip_zero = inf_cfg.flag_lip_zero # not overwrite
|
69 |
+
if flag_lip_zero:
|
70 |
+
# let lip-open scalar to be 0 at first
|
71 |
+
c_d_lip_before_animation = [0.]
|
72 |
+
combined_lip_ratio_tensor_before_animation = self.live_portrait_wrapper.calc_combined_lip_ratio(c_d_lip_before_animation, source_lmk)
|
73 |
+
if combined_lip_ratio_tensor_before_animation[0][0] < inf_cfg.lip_zero_threshold:
|
74 |
+
flag_lip_zero = False
|
75 |
+
else:
|
76 |
+
lip_delta_before_animation = self.live_portrait_wrapper.retarget_lip(x_s, combined_lip_ratio_tensor_before_animation)
|
77 |
+
############################################
|
78 |
+
|
79 |
+
######## process driving info ########
|
80 |
+
flag_load_from_template = is_template(args.driving_info)
|
81 |
+
driving_rgb_crop_256x256_lst = None
|
82 |
+
wfp_template = None
|
83 |
+
|
84 |
+
if flag_load_from_template:
|
85 |
+
# NOTE: load from template, it is fast, but the cropping video is None
|
86 |
+
log(f"Load from template: {args.driving_info}, NOT the video, so the cropping video and audio are both NULL.", style='bold green')
|
87 |
+
template_dct = load(args.driving_info)
|
88 |
+
n_frames = template_dct['n_frames']
|
89 |
+
|
90 |
+
# set output_fps
|
91 |
+
output_fps = template_dct.get('output_fps', inf_cfg.output_fps)
|
92 |
+
log(f'The FPS of template: {output_fps}')
|
93 |
+
|
94 |
+
if args.flag_crop_driving_video:
|
95 |
+
log("Warning: flag_crop_driving_video is True, but the driving info is a template, so it is ignored.")
|
96 |
+
|
97 |
+
elif osp.exists(args.driving_info) and is_video(args.driving_info):
|
98 |
+
# load from video file, AND make motion template
|
99 |
+
log(f"Load video: {args.driving_info}")
|
100 |
+
if osp.isdir(args.driving_info):
|
101 |
+
output_fps = inf_cfg.output_fps
|
102 |
+
else:
|
103 |
+
output_fps = int(get_fps(args.driving_info))
|
104 |
+
log(f'The FPS of {args.driving_info} is: {output_fps}')
|
105 |
+
|
106 |
+
log(f"Load video file (mp4 mov avi etc...): {args.driving_info}")
|
107 |
+
driving_rgb_lst = load_driving_info(args.driving_info)
|
108 |
+
|
109 |
+
######## make motion template ########
|
110 |
+
log("Start making motion template...")
|
111 |
+
if inf_cfg.flag_crop_driving_video:
|
112 |
+
ret = self.cropper.crop_driving_video(driving_rgb_lst)
|
113 |
+
log(f'Driving video is cropped, {len(ret["frame_crop_lst"])} frames are processed.')
|
114 |
+
driving_rgb_crop_lst, driving_lmk_crop_lst = ret['frame_crop_lst'], ret['lmk_crop_lst']
|
115 |
+
driving_rgb_crop_256x256_lst = [cv2.resize(_, (256, 256)) for _ in driving_rgb_crop_lst]
|
116 |
+
else:
|
117 |
+
driving_lmk_crop_lst = self.cropper.calc_lmks_from_cropped_video(driving_rgb_lst)
|
118 |
+
driving_rgb_crop_256x256_lst = [cv2.resize(_, (256, 256)) for _ in driving_rgb_lst] # force to resize to 256x256
|
119 |
+
|
120 |
+
c_d_eyes_lst, c_d_lip_lst = self.live_portrait_wrapper.calc_driving_ratio(driving_lmk_crop_lst)
|
121 |
+
# save the motion template
|
122 |
+
I_d_lst = self.live_portrait_wrapper.prepare_driving_videos(driving_rgb_crop_256x256_lst)
|
123 |
+
template_dct = self.make_motion_template(I_d_lst, c_d_eyes_lst, c_d_lip_lst, output_fps=output_fps)
|
124 |
+
|
125 |
+
wfp_template = remove_suffix(args.driving_info) + '.pkl'
|
126 |
+
dump(wfp_template, template_dct)
|
127 |
+
log(f"Dump motion template to {wfp_template}")
|
128 |
+
|
129 |
+
n_frames = I_d_lst.shape[0]
|
130 |
+
else:
|
131 |
+
raise Exception(f"{args.driving_info} not exists or unsupported driving info types!")
|
132 |
+
#########################################
|
133 |
+
|
134 |
+
######## prepare for pasteback ########
|
135 |
+
I_p_pstbk_lst = None
|
136 |
+
if inf_cfg.flag_pasteback and inf_cfg.flag_do_crop and inf_cfg.flag_stitching:
|
137 |
+
mask_ori_float = prepare_paste_back(inf_cfg.mask_crop, crop_info['M_c2o'], dsize=(img_rgb.shape[1], img_rgb.shape[0]))
|
138 |
+
I_p_pstbk_lst = []
|
139 |
+
log("Prepared pasteback mask done.")
|
140 |
+
#########################################
|
141 |
+
|
142 |
+
I_p_lst = []
|
143 |
+
R_d_0, x_d_0_info = None, None
|
144 |
+
|
145 |
+
for i in track(range(n_frames), description='🚀Animating...', total=n_frames):
|
146 |
+
x_d_i_info = template_dct['motion'][i]
|
147 |
+
x_d_i_info = dct2device(x_d_i_info, device)
|
148 |
+
R_d_i = x_d_i_info['R_d']
|
149 |
+
|
150 |
+
if i == 0:
|
151 |
+
R_d_0 = R_d_i
|
152 |
+
x_d_0_info = x_d_i_info
|
153 |
+
|
154 |
+
if inf_cfg.flag_relative_motion:
|
155 |
+
R_new = (R_d_i @ R_d_0.permute(0, 2, 1)) @ R_s
|
156 |
+
delta_new = x_s_info['exp'] + (x_d_i_info['exp'] - x_d_0_info['exp'])
|
157 |
+
scale_new = x_s_info['scale'] * (x_d_i_info['scale'] / x_d_0_info['scale'])
|
158 |
+
t_new = x_s_info['t'] + (x_d_i_info['t'] - x_d_0_info['t'])
|
159 |
+
else:
|
160 |
+
R_new = R_d_i
|
161 |
+
delta_new = x_d_i_info['exp']
|
162 |
+
scale_new = x_s_info['scale']
|
163 |
+
t_new = x_d_i_info['t']
|
164 |
+
|
165 |
+
t_new[..., 2].fill_(0) # zero tz
|
166 |
+
x_d_i_new = scale_new * (x_c_s @ R_new + delta_new) + t_new
|
167 |
+
|
168 |
+
# Algorithm 1:
|
169 |
+
if not inf_cfg.flag_stitching and not inf_cfg.flag_eye_retargeting and not inf_cfg.flag_lip_retargeting:
|
170 |
+
# without stitching or retargeting
|
171 |
+
if flag_lip_zero:
|
172 |
+
x_d_i_new += lip_delta_before_animation.reshape(-1, x_s.shape[1], 3)
|
173 |
+
else:
|
174 |
+
pass
|
175 |
+
elif inf_cfg.flag_stitching and not inf_cfg.flag_eye_retargeting and not inf_cfg.flag_lip_retargeting:
|
176 |
+
# with stitching and without retargeting
|
177 |
+
if flag_lip_zero:
|
178 |
+
x_d_i_new = self.live_portrait_wrapper.stitching(x_s, x_d_i_new) + lip_delta_before_animation.reshape(-1, x_s.shape[1], 3)
|
179 |
+
else:
|
180 |
+
x_d_i_new = self.live_portrait_wrapper.stitching(x_s, x_d_i_new)
|
181 |
+
else:
|
182 |
+
eyes_delta, lip_delta = None, None
|
183 |
+
if inf_cfg.flag_eye_retargeting:
|
184 |
+
c_d_eyes_i = c_d_eyes_lst[i]
|
185 |
+
combined_eye_ratio_tensor = self.live_portrait_wrapper.calc_combined_eye_ratio(c_d_eyes_i, source_lmk)
|
186 |
+
# ∆_eyes,i = R_eyes(x_s; c_s,eyes, c_d,eyes,i)
|
187 |
+
eyes_delta = self.live_portrait_wrapper.retarget_eye(x_s, combined_eye_ratio_tensor)
|
188 |
+
if inf_cfg.flag_lip_retargeting:
|
189 |
+
c_d_lip_i = c_d_lip_lst[i]
|
190 |
+
combined_lip_ratio_tensor = self.live_portrait_wrapper.calc_combined_lip_ratio(c_d_lip_i, source_lmk)
|
191 |
+
# ∆_lip,i = R_lip(x_s; c_s,lip, c_d,lip,i)
|
192 |
+
lip_delta = self.live_portrait_wrapper.retarget_lip(x_s, combined_lip_ratio_tensor)
|
193 |
+
|
194 |
+
if inf_cfg.flag_relative_motion: # use x_s
|
195 |
+
x_d_i_new = x_s + \
|
196 |
+
(eyes_delta.reshape(-1, x_s.shape[1], 3) if eyes_delta is not None else 0) + \
|
197 |
+
(lip_delta.reshape(-1, x_s.shape[1], 3) if lip_delta is not None else 0)
|
198 |
+
else: # use x_d,i
|
199 |
+
x_d_i_new = x_d_i_new + \
|
200 |
+
(eyes_delta.reshape(-1, x_s.shape[1], 3) if eyes_delta is not None else 0) + \
|
201 |
+
(lip_delta.reshape(-1, x_s.shape[1], 3) if lip_delta is not None else 0)
|
202 |
+
|
203 |
+
if inf_cfg.flag_stitching:
|
204 |
+
x_d_i_new = self.live_portrait_wrapper.stitching(x_s, x_d_i_new)
|
205 |
+
|
206 |
+
out = self.live_portrait_wrapper.warp_decode(f_s, x_s, x_d_i_new)
|
207 |
+
I_p_i = self.live_portrait_wrapper.parse_output(out['out'])[0]
|
208 |
+
I_p_lst.append(I_p_i)
|
209 |
+
|
210 |
+
if inf_cfg.flag_pasteback and inf_cfg.flag_do_crop and inf_cfg.flag_stitching:
|
211 |
+
# TODO: pasteback is slow, considering optimize it using multi-threading or GPU
|
212 |
+
I_p_pstbk = paste_back(I_p_i, crop_info['M_c2o'], img_rgb, mask_ori_float)
|
213 |
+
I_p_pstbk_lst.append(I_p_pstbk)
|
214 |
+
|
215 |
+
mkdir(args.output_dir)
|
216 |
+
wfp_concat = None
|
217 |
+
flag_has_audio = (not flag_load_from_template) and has_audio_stream(args.driving_info)
|
218 |
+
|
219 |
+
######### build final concact result #########
|
220 |
+
# driving frame | source image | generation, or source image | generation
|
221 |
+
frames_concatenated = concat_frames(driving_rgb_crop_256x256_lst, img_crop_256x256, I_p_lst)
|
222 |
+
wfp_concat = osp.join(args.output_dir, f'{basename(args.source_image)}--{basename(args.driving_info)}_concat.mp4')
|
223 |
+
images2video(frames_concatenated, wfp=wfp_concat, fps=output_fps)
|
224 |
+
|
225 |
+
if flag_has_audio:
|
226 |
+
# final result with concact
|
227 |
+
wfp_concat_with_audio = osp.join(args.output_dir, f'{basename(args.source_image)}--{basename(args.driving_info)}_concat_with_audio.mp4')
|
228 |
+
add_audio_to_video(wfp_concat, args.driving_info, wfp_concat_with_audio)
|
229 |
+
os.replace(wfp_concat_with_audio, wfp_concat)
|
230 |
+
log(f"Replace {wfp_concat} with {wfp_concat_with_audio}")
|
231 |
+
|
232 |
+
# save drived result
|
233 |
+
wfp = osp.join(args.output_dir, f'{basename(args.source_image)}--{basename(args.driving_info)}.mp4')
|
234 |
+
if I_p_pstbk_lst is not None and len(I_p_pstbk_lst) > 0:
|
235 |
+
images2video(I_p_pstbk_lst, wfp=wfp, fps=output_fps)
|
236 |
+
else:
|
237 |
+
images2video(I_p_lst, wfp=wfp, fps=output_fps)
|
238 |
+
|
239 |
+
######### build final result #########
|
240 |
+
if flag_has_audio:
|
241 |
+
wfp_with_audio = osp.join(args.output_dir, f'{basename(args.source_image)}--{basename(args.driving_info)}_with_audio.mp4')
|
242 |
+
add_audio_to_video(wfp, args.driving_info, wfp_with_audio)
|
243 |
+
os.replace(wfp_with_audio, wfp)
|
244 |
+
log(f"Replace {wfp} with {wfp_with_audio}")
|
245 |
+
|
246 |
+
# final log
|
247 |
+
if wfp_template not in (None, ''):
|
248 |
+
log(f'Animated template: {wfp_template}, you can specify `-d` argument with this template path next time to avoid cropping video, motion making and protecting privacy.', style='bold green')
|
249 |
+
log(f'Animated video: {wfp}')
|
250 |
+
log(f'Animated video with concact: {wfp_concat}')
|
251 |
+
|
252 |
+
return wfp, wfp_concat
|
253 |
+
|
254 |
+
def make_motion_template(self, I_d_lst, c_d_eyes_lst, c_d_lip_lst, **kwargs):
|
255 |
+
n_frames = I_d_lst.shape[0]
|
256 |
+
template_dct = {
|
257 |
+
'n_frames': n_frames,
|
258 |
+
'output_fps': kwargs.get('output_fps', 25),
|
259 |
+
'motion': [],
|
260 |
+
'c_d_eyes_lst': [],
|
261 |
+
'c_d_lip_lst': [],
|
262 |
+
}
|
263 |
+
|
264 |
+
for i in track(range(n_frames), description='Making motion templates...', total=n_frames):
|
265 |
+
# collect s_d, R_d, δ_d and t_d for inference
|
266 |
+
I_d_i = I_d_lst[i]
|
267 |
+
x_d_i_info = self.live_portrait_wrapper.get_kp_info(I_d_i)
|
268 |
+
R_d_i = get_rotation_matrix(x_d_i_info['pitch'], x_d_i_info['yaw'], x_d_i_info['roll'])
|
269 |
+
|
270 |
+
item_dct = {
|
271 |
+
'scale': x_d_i_info['scale'].cpu().numpy().astype(np.float32),
|
272 |
+
'R_d': R_d_i.cpu().numpy().astype(np.float32),
|
273 |
+
'exp': x_d_i_info['exp'].cpu().numpy().astype(np.float32),
|
274 |
+
't': x_d_i_info['t'].cpu().numpy().astype(np.float32),
|
275 |
+
}
|
276 |
+
|
277 |
+
template_dct['motion'].append(item_dct)
|
278 |
+
|
279 |
+
c_d_eyes = c_d_eyes_lst[i].astype(np.float32)
|
280 |
+
template_dct['c_d_eyes_lst'].append(c_d_eyes)
|
281 |
+
|
282 |
+
c_d_lip = c_d_lip_lst[i].astype(np.float32)
|
283 |
+
template_dct['c_d_lip_lst'].append(c_d_lip)
|
284 |
+
|
285 |
+
return template_dct
|
difpoint/src/live_portrait_wrapper.py
ADDED
@@ -0,0 +1,318 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
|
3 |
+
"""
|
4 |
+
Wrapper for LivePortrait core functions
|
5 |
+
"""
|
6 |
+
|
7 |
+
import os.path as osp
|
8 |
+
import numpy as np
|
9 |
+
import cv2
|
10 |
+
import torch
|
11 |
+
import yaml
|
12 |
+
|
13 |
+
from .utils.timer import Timer
|
14 |
+
from .utils.helper import load_model, concat_feat
|
15 |
+
from .utils.camera import headpose_pred_to_degree, get_rotation_matrix
|
16 |
+
from .utils.retargeting_utils import calc_eye_close_ratio, calc_lip_close_ratio
|
17 |
+
from .config.inference_config import InferenceConfig
|
18 |
+
from .utils.rprint import rlog as log
|
19 |
+
|
20 |
+
|
21 |
+
class LivePortraitWrapper(object):
|
22 |
+
|
23 |
+
def __init__(self, inference_cfg: InferenceConfig):
|
24 |
+
|
25 |
+
self.inference_cfg = inference_cfg
|
26 |
+
self.device_id = inference_cfg.device_id
|
27 |
+
self.compile = inference_cfg.flag_do_torch_compile
|
28 |
+
if inference_cfg.flag_force_cpu:
|
29 |
+
self.device = 'cpu'
|
30 |
+
else:
|
31 |
+
self.device = 'cuda:' + str(self.device_id)
|
32 |
+
|
33 |
+
model_config = yaml.load(open(inference_cfg.models_config, 'r'), Loader=yaml.SafeLoader)
|
34 |
+
# init F
|
35 |
+
self.appearance_feature_extractor = load_model(inference_cfg.checkpoint_F, model_config, self.device, 'appearance_feature_extractor')
|
36 |
+
log(f'Load appearance_feature_extractor done.')
|
37 |
+
# init M
|
38 |
+
self.motion_extractor = load_model(inference_cfg.checkpoint_M, model_config, self.device, 'motion_extractor')
|
39 |
+
log(f'Load motion_extractor done.')
|
40 |
+
# init W
|
41 |
+
self.warping_module = load_model(inference_cfg.checkpoint_W, model_config, self.device, 'warping_module')
|
42 |
+
log(f'Load warping_module done.')
|
43 |
+
# init G
|
44 |
+
self.spade_generator = load_model(inference_cfg.checkpoint_G, model_config, self.device, 'spade_generator')
|
45 |
+
log(f'Load spade_generator done.')
|
46 |
+
# init S and R
|
47 |
+
if inference_cfg.checkpoint_S is not None and osp.exists(inference_cfg.checkpoint_S):
|
48 |
+
self.stitching_retargeting_module = load_model(inference_cfg.checkpoint_S, model_config, self.device, 'stitching_retargeting_module')
|
49 |
+
log(f'Load stitching_retargeting_module done.')
|
50 |
+
else:
|
51 |
+
self.stitching_retargeting_module = None
|
52 |
+
# Optimize for inference
|
53 |
+
if self.compile:
|
54 |
+
self.warping_module = torch.compile(self.warping_module, mode='max-autotune')
|
55 |
+
self.spade_generator = torch.compile(self.spade_generator, mode='max-autotune')
|
56 |
+
|
57 |
+
self.timer = Timer()
|
58 |
+
|
59 |
+
def update_config(self, user_args):
|
60 |
+
for k, v in user_args.items():
|
61 |
+
if hasattr(self.inference_cfg, k):
|
62 |
+
setattr(self.inference_cfg, k, v)
|
63 |
+
|
64 |
+
def prepare_source(self, img: np.ndarray) -> torch.Tensor:
|
65 |
+
""" construct the input as standard
|
66 |
+
img: HxWx3, uint8, 256x256
|
67 |
+
"""
|
68 |
+
h, w = img.shape[:2]
|
69 |
+
if h != self.inference_cfg.input_shape[0] or w != self.inference_cfg.input_shape[1]:
|
70 |
+
x = cv2.resize(img, (self.inference_cfg.input_shape[0], self.inference_cfg.input_shape[1]))
|
71 |
+
else:
|
72 |
+
x = img.copy()
|
73 |
+
|
74 |
+
if x.ndim == 3:
|
75 |
+
x = x[np.newaxis].astype(np.float32) / 255. # HxWx3 -> 1xHxWx3, normalized to 0~1
|
76 |
+
elif x.ndim == 4:
|
77 |
+
x = x.astype(np.float32) / 255. # BxHxWx3, normalized to 0~1
|
78 |
+
else:
|
79 |
+
raise ValueError(f'img ndim should be 3 or 4: {x.ndim}')
|
80 |
+
x = np.clip(x, 0, 1) # clip to 0~1
|
81 |
+
x = torch.from_numpy(x).permute(0, 3, 1, 2) # 1xHxWx3 -> 1x3xHxW
|
82 |
+
x = x.to(self.device)
|
83 |
+
return x
|
84 |
+
|
85 |
+
def prepare_driving_videos(self, imgs) -> torch.Tensor:
|
86 |
+
""" construct the input as standard
|
87 |
+
imgs: NxBxHxWx3, uint8
|
88 |
+
"""
|
89 |
+
if isinstance(imgs, list):
|
90 |
+
_imgs = np.array(imgs)[..., np.newaxis] # TxHxWx3x1
|
91 |
+
elif isinstance(imgs, np.ndarray):
|
92 |
+
_imgs = imgs
|
93 |
+
else:
|
94 |
+
raise ValueError(f'imgs type error: {type(imgs)}')
|
95 |
+
|
96 |
+
y = _imgs.astype(np.float32) / 255.
|
97 |
+
y = np.clip(y, 0, 1) # clip to 0~1
|
98 |
+
y = torch.from_numpy(y).permute(0, 4, 3, 1, 2) # TxHxWx3x1 -> Tx1x3xHxW
|
99 |
+
y = y.to(self.device)
|
100 |
+
|
101 |
+
return y
|
102 |
+
|
103 |
+
def extract_feature_3d(self, x: torch.Tensor) -> torch.Tensor:
|
104 |
+
""" get the appearance feature of the image by F
|
105 |
+
x: Bx3xHxW, normalized to 0~1
|
106 |
+
"""
|
107 |
+
with torch.no_grad():
|
108 |
+
with torch.autocast(device_type=self.device[:4], dtype=torch.float16, enabled=self.inference_cfg.flag_use_half_precision):
|
109 |
+
feature_3d = self.appearance_feature_extractor(x)
|
110 |
+
|
111 |
+
return feature_3d.float()
|
112 |
+
|
113 |
+
def get_kp_info(self, x: torch.Tensor, **kwargs) -> dict:
|
114 |
+
""" get the implicit keypoint information
|
115 |
+
x: Bx3xHxW, normalized to 0~1
|
116 |
+
flag_refine_info: whether to trandform the pose to degrees and the dimention of the reshape
|
117 |
+
return: A dict contains keys: 'pitch', 'yaw', 'roll', 't', 'exp', 'scale', 'kp'
|
118 |
+
"""
|
119 |
+
with torch.no_grad():
|
120 |
+
with torch.autocast(device_type=self.device[:4], dtype=torch.float16, enabled=self.inference_cfg.flag_use_half_precision):
|
121 |
+
kp_info = self.motion_extractor(x)
|
122 |
+
|
123 |
+
if self.inference_cfg.flag_use_half_precision:
|
124 |
+
# float the dict
|
125 |
+
for k, v in kp_info.items():
|
126 |
+
if isinstance(v, torch.Tensor):
|
127 |
+
kp_info[k] = v.float()
|
128 |
+
|
129 |
+
flag_refine_info: bool = kwargs.get('flag_refine_info', True)
|
130 |
+
if flag_refine_info:
|
131 |
+
bs = kp_info['kp'].shape[0]
|
132 |
+
kp_info['pitch'] = headpose_pred_to_degree(kp_info['pitch'])[:, None] # Bx1
|
133 |
+
kp_info['yaw'] = headpose_pred_to_degree(kp_info['yaw'])[:, None] # Bx1
|
134 |
+
kp_info['roll'] = headpose_pred_to_degree(kp_info['roll'])[:, None] # Bx1
|
135 |
+
kp_info['kp'] = kp_info['kp'].reshape(bs, -1) # B,Nx3
|
136 |
+
kp_info['exp'] = kp_info['exp'].reshape(bs, -1) # B,Nx3
|
137 |
+
|
138 |
+
return kp_info
|
139 |
+
|
140 |
+
def get_pose_dct(self, kp_info: dict) -> dict:
|
141 |
+
pose_dct = dict(
|
142 |
+
pitch=headpose_pred_to_degree(kp_info['pitch']).item(),
|
143 |
+
yaw=headpose_pred_to_degree(kp_info['yaw']).item(),
|
144 |
+
roll=headpose_pred_to_degree(kp_info['roll']).item(),
|
145 |
+
)
|
146 |
+
return pose_dct
|
147 |
+
|
148 |
+
def get_fs_and_kp_info(self, source_prepared, driving_first_frame):
|
149 |
+
|
150 |
+
# get the canonical keypoints of source image by M
|
151 |
+
source_kp_info = self.get_kp_info(source_prepared, flag_refine_info=True)
|
152 |
+
source_rotation = get_rotation_matrix(source_kp_info['pitch'], source_kp_info['yaw'], source_kp_info['roll'])
|
153 |
+
|
154 |
+
# get the canonical keypoints of first driving frame by M
|
155 |
+
driving_first_frame_kp_info = self.get_kp_info(driving_first_frame, flag_refine_info=True)
|
156 |
+
driving_first_frame_rotation = get_rotation_matrix(
|
157 |
+
driving_first_frame_kp_info['pitch'],
|
158 |
+
driving_first_frame_kp_info['yaw'],
|
159 |
+
driving_first_frame_kp_info['roll']
|
160 |
+
)
|
161 |
+
|
162 |
+
# get feature volume by F
|
163 |
+
source_feature_3d = self.extract_feature_3d(source_prepared)
|
164 |
+
|
165 |
+
return source_kp_info, source_rotation, source_feature_3d, driving_first_frame_kp_info, driving_first_frame_rotation
|
166 |
+
|
167 |
+
def transform_keypoint(self, kp_info: dict):
|
168 |
+
"""
|
169 |
+
transform the implicit keypoints with the pose, shift, and expression deformation
|
170 |
+
kp: BxNx3
|
171 |
+
"""
|
172 |
+
kp = kp_info['kp'] # (bs, k, 3)
|
173 |
+
pitch, yaw, roll = kp_info['pitch'], kp_info['yaw'], kp_info['roll']
|
174 |
+
|
175 |
+
t, exp = kp_info['t'], kp_info['exp']
|
176 |
+
scale = kp_info['scale']
|
177 |
+
|
178 |
+
pitch = headpose_pred_to_degree(pitch)
|
179 |
+
yaw = headpose_pred_to_degree(yaw)
|
180 |
+
roll = headpose_pred_to_degree(roll)
|
181 |
+
|
182 |
+
bs = kp.shape[0]
|
183 |
+
if kp.ndim == 2:
|
184 |
+
num_kp = kp.shape[1] // 3 # Bx(num_kpx3)
|
185 |
+
else:
|
186 |
+
num_kp = kp.shape[1] # Bxnum_kpx3
|
187 |
+
|
188 |
+
rot_mat = get_rotation_matrix(pitch, yaw, roll) # (bs, 3, 3)
|
189 |
+
|
190 |
+
# Eqn.2: s * (R * x_c,s + exp) + t
|
191 |
+
kp_transformed = kp.view(bs, num_kp, 3) @ rot_mat + exp.view(bs, num_kp, 3)
|
192 |
+
kp_transformed *= scale[..., None] # (bs, k, 3) * (bs, 1, 1) = (bs, k, 3)
|
193 |
+
kp_transformed[:, :, 0:2] += t[:, None, 0:2] # remove z, only apply tx ty
|
194 |
+
# kp_transformed[:, :, :] += t[:, None, :]
|
195 |
+
|
196 |
+
return kp_transformed
|
197 |
+
|
198 |
+
def retarget_eye(self, kp_source: torch.Tensor, eye_close_ratio: torch.Tensor) -> torch.Tensor:
|
199 |
+
"""
|
200 |
+
kp_source: BxNx3
|
201 |
+
eye_close_ratio: Bx3
|
202 |
+
Return: Bx(3*num_kp+2)
|
203 |
+
"""
|
204 |
+
feat_eye = concat_feat(kp_source, eye_close_ratio)
|
205 |
+
|
206 |
+
with torch.no_grad():
|
207 |
+
delta = self.stitching_retargeting_module['eye'](feat_eye)
|
208 |
+
|
209 |
+
return delta
|
210 |
+
|
211 |
+
def retarget_lip(self, kp_source: torch.Tensor, lip_close_ratio: torch.Tensor) -> torch.Tensor:
|
212 |
+
"""
|
213 |
+
kp_source: BxNx3
|
214 |
+
lip_close_ratio: Bx2
|
215 |
+
"""
|
216 |
+
feat_lip = concat_feat(kp_source, lip_close_ratio)
|
217 |
+
|
218 |
+
with torch.no_grad():
|
219 |
+
delta = self.stitching_retargeting_module['lip'](feat_lip)
|
220 |
+
|
221 |
+
return delta
|
222 |
+
|
223 |
+
def stitch(self, kp_source: torch.Tensor, kp_driving: torch.Tensor) -> torch.Tensor:
|
224 |
+
"""
|
225 |
+
kp_source: BxNx3
|
226 |
+
kp_driving: BxNx3
|
227 |
+
Return: Bx(3*num_kp+2)
|
228 |
+
"""
|
229 |
+
feat_stiching = concat_feat(kp_source, kp_driving)
|
230 |
+
|
231 |
+
with torch.no_grad():
|
232 |
+
delta = self.stitching_retargeting_module['stitching'](feat_stiching)
|
233 |
+
|
234 |
+
return delta
|
235 |
+
|
236 |
+
def stitching(self, kp_source: torch.Tensor, kp_driving: torch.Tensor) -> torch.Tensor:
|
237 |
+
""" conduct the stitching
|
238 |
+
kp_source: Bxnum_kpx3
|
239 |
+
kp_driving: Bxnum_kpx3
|
240 |
+
"""
|
241 |
+
|
242 |
+
if self.stitching_retargeting_module is not None:
|
243 |
+
|
244 |
+
bs, num_kp = kp_source.shape[:2]
|
245 |
+
|
246 |
+
kp_driving_new = kp_driving.clone()
|
247 |
+
delta = self.stitch(kp_source, kp_driving_new)
|
248 |
+
|
249 |
+
delta_exp = delta[..., :3*num_kp].reshape(bs, num_kp, 3) # 1x20x3
|
250 |
+
delta_tx_ty = delta[..., 3*num_kp:3*num_kp+2].reshape(bs, 1, 2) # 1x1x2
|
251 |
+
|
252 |
+
kp_driving_new += delta_exp
|
253 |
+
kp_driving_new[..., :2] += delta_tx_ty
|
254 |
+
|
255 |
+
return kp_driving_new
|
256 |
+
|
257 |
+
return kp_driving
|
258 |
+
|
259 |
+
def warp_decode(self, feature_3d: torch.Tensor, kp_source: torch.Tensor, kp_driving: torch.Tensor) -> torch.Tensor:
|
260 |
+
""" get the image after the warping of the implicit keypoints
|
261 |
+
feature_3d: Bx32x16x64x64, feature volume
|
262 |
+
kp_source: BxNx3
|
263 |
+
kp_driving: BxNx3
|
264 |
+
"""
|
265 |
+
# The line 18 in Algorithm 1: D(W(f_s; x_s, x′_d,i))
|
266 |
+
with torch.no_grad():
|
267 |
+
with torch.autocast(device_type=self.device[:4], dtype=torch.float16, enabled=self.inference_cfg.flag_use_half_precision):
|
268 |
+
if self.compile:
|
269 |
+
# Mark the beginning of a new CUDA Graph step
|
270 |
+
torch.compiler.cudagraph_mark_step_begin()
|
271 |
+
# get decoder input
|
272 |
+
ret_dct = self.warping_module(feature_3d, kp_source=kp_source, kp_driving=kp_driving)
|
273 |
+
# decode
|
274 |
+
ret_dct['out'] = self.spade_generator(feature=ret_dct['out'])
|
275 |
+
|
276 |
+
# float the dict
|
277 |
+
if self.inference_cfg.flag_use_half_precision:
|
278 |
+
for k, v in ret_dct.items():
|
279 |
+
if isinstance(v, torch.Tensor):
|
280 |
+
ret_dct[k] = v.float()
|
281 |
+
|
282 |
+
return ret_dct
|
283 |
+
|
284 |
+
def parse_output(self, out: torch.Tensor) -> np.ndarray:
|
285 |
+
""" construct the output as standard
|
286 |
+
return: 1xHxWx3, uint8
|
287 |
+
"""
|
288 |
+
out = np.transpose(out.data.cpu().numpy(), [0, 2, 3, 1]) # 1x3xHxW -> 1xHxWx3
|
289 |
+
out = np.clip(out, 0, 1) # clip to 0~1
|
290 |
+
out = np.clip(out * 255, 0, 255).astype(np.uint8) # 0~1 -> 0~255
|
291 |
+
|
292 |
+
return out
|
293 |
+
|
294 |
+
def calc_driving_ratio(self, driving_lmk_lst):
|
295 |
+
input_eye_ratio_lst = []
|
296 |
+
input_lip_ratio_lst = []
|
297 |
+
for lmk in driving_lmk_lst:
|
298 |
+
# for eyes retargeting
|
299 |
+
input_eye_ratio_lst.append(calc_eye_close_ratio(lmk[None]))
|
300 |
+
# for lip retargeting
|
301 |
+
input_lip_ratio_lst.append(calc_lip_close_ratio(lmk[None]))
|
302 |
+
return input_eye_ratio_lst, input_lip_ratio_lst
|
303 |
+
|
304 |
+
def calc_combined_eye_ratio(self, c_d_eyes_i, source_lmk):
|
305 |
+
c_s_eyes = calc_eye_close_ratio(source_lmk[None])
|
306 |
+
c_s_eyes_tensor = torch.from_numpy(c_s_eyes).float().to(self.device)
|
307 |
+
c_d_eyes_i_tensor = torch.Tensor([c_d_eyes_i[0][0]]).reshape(1, 1).to(self.device)
|
308 |
+
# [c_s,eyes, c_d,eyes,i]
|
309 |
+
combined_eye_ratio_tensor = torch.cat([c_s_eyes_tensor, c_d_eyes_i_tensor], dim=1)
|
310 |
+
return combined_eye_ratio_tensor
|
311 |
+
|
312 |
+
def calc_combined_lip_ratio(self, c_d_lip_i, source_lmk):
|
313 |
+
c_s_lip = calc_lip_close_ratio(source_lmk[None])
|
314 |
+
c_s_lip_tensor = torch.from_numpy(c_s_lip).float().to(self.device)
|
315 |
+
c_d_lip_i_tensor = torch.Tensor([c_d_lip_i[0]]).to(self.device).reshape(1, 1) # 1x1
|
316 |
+
# [c_s,lip, c_d,lip,i]
|
317 |
+
combined_lip_ratio_tensor = torch.cat([c_s_lip_tensor, c_d_lip_i_tensor], dim=1) # 1x2
|
318 |
+
return combined_lip_ratio_tensor
|
difpoint/src/modules/__init__.py
ADDED
File without changes
|
difpoint/src/modules/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (138 Bytes). View file
|
|
difpoint/src/modules/__pycache__/appearance_feature_extractor.cpython-310.pyc
ADDED
Binary file (1.82 kB). View file
|
|
difpoint/src/modules/__pycache__/convnextv2.cpython-310.pyc
ADDED
Binary file (4.75 kB). View file
|
|
difpoint/src/modules/__pycache__/dense_motion.cpython-310.pyc
ADDED
Binary file (3.61 kB). View file
|
|
difpoint/src/modules/__pycache__/motion_extractor.cpython-310.pyc
ADDED
Binary file (1.62 kB). View file
|
|
difpoint/src/modules/__pycache__/spade_generator.cpython-310.pyc
ADDED
Binary file (2.01 kB). View file
|
|
difpoint/src/modules/__pycache__/stitching_retargeting_network.cpython-310.pyc
ADDED
Binary file (1.95 kB). View file
|
|
difpoint/src/modules/__pycache__/util.cpython-310.pyc
ADDED
Binary file (14.4 kB). View file
|
|
difpoint/src/modules/__pycache__/warping_network.cpython-310.pyc
ADDED
Binary file (2.16 kB). View file
|
|
difpoint/src/modules/appearance_feature_extractor.py
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
|
3 |
+
"""
|
4 |
+
Appearance extractor(F) defined in paper, which maps the source image s to a 3D appearance feature volume.
|
5 |
+
"""
|
6 |
+
|
7 |
+
import torch
|
8 |
+
from torch import nn
|
9 |
+
from .util import SameBlock2d, DownBlock2d, ResBlock3d
|
10 |
+
|
11 |
+
|
12 |
+
class AppearanceFeatureExtractor(nn.Module):
|
13 |
+
|
14 |
+
def __init__(self, image_channel, block_expansion, num_down_blocks, max_features, reshape_channel, reshape_depth, num_resblocks):
|
15 |
+
super(AppearanceFeatureExtractor, self).__init__()
|
16 |
+
self.image_channel = image_channel
|
17 |
+
self.block_expansion = block_expansion
|
18 |
+
self.num_down_blocks = num_down_blocks
|
19 |
+
self.max_features = max_features
|
20 |
+
self.reshape_channel = reshape_channel
|
21 |
+
self.reshape_depth = reshape_depth
|
22 |
+
|
23 |
+
self.first = SameBlock2d(image_channel, block_expansion, kernel_size=(3, 3), padding=(1, 1))
|
24 |
+
|
25 |
+
down_blocks = []
|
26 |
+
for i in range(num_down_blocks):
|
27 |
+
in_features = min(max_features, block_expansion * (2 ** i))
|
28 |
+
out_features = min(max_features, block_expansion * (2 ** (i + 1)))
|
29 |
+
down_blocks.append(DownBlock2d(in_features, out_features, kernel_size=(3, 3), padding=(1, 1)))
|
30 |
+
self.down_blocks = nn.ModuleList(down_blocks)
|
31 |
+
|
32 |
+
self.second = nn.Conv2d(in_channels=out_features, out_channels=max_features, kernel_size=1, stride=1)
|
33 |
+
|
34 |
+
self.resblocks_3d = torch.nn.Sequential()
|
35 |
+
for i in range(num_resblocks):
|
36 |
+
self.resblocks_3d.add_module('3dr' + str(i), ResBlock3d(reshape_channel, kernel_size=3, padding=1))
|
37 |
+
|
38 |
+
def forward(self, source_image):
|
39 |
+
out = self.first(source_image) # Bx3x256x256 -> Bx64x256x256
|
40 |
+
|
41 |
+
for i in range(len(self.down_blocks)):
|
42 |
+
out = self.down_blocks[i](out)
|
43 |
+
out = self.second(out)
|
44 |
+
bs, c, h, w = out.shape # ->Bx512x64x64
|
45 |
+
|
46 |
+
f_s = out.view(bs, self.reshape_channel, self.reshape_depth, h, w) # ->Bx32x16x64x64
|
47 |
+
f_s = self.resblocks_3d(f_s) # ->Bx32x16x64x64
|
48 |
+
return f_s
|
difpoint/src/modules/convnextv2.py
ADDED
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
|
3 |
+
"""
|
4 |
+
This moudle is adapted to the ConvNeXtV2 version for the extraction of implicit keypoints, poses, and expression deformation.
|
5 |
+
"""
|
6 |
+
|
7 |
+
import torch
|
8 |
+
import torch.nn as nn
|
9 |
+
# from timm.models.layers import trunc_normal_, DropPath
|
10 |
+
from .util import LayerNorm, DropPath, trunc_normal_, GRN
|
11 |
+
|
12 |
+
__all__ = ['convnextv2_tiny']
|
13 |
+
|
14 |
+
|
15 |
+
class Block(nn.Module):
|
16 |
+
""" ConvNeXtV2 Block.
|
17 |
+
|
18 |
+
Args:
|
19 |
+
dim (int): Number of input channels.
|
20 |
+
drop_path (float): Stochastic depth rate. Default: 0.0
|
21 |
+
"""
|
22 |
+
|
23 |
+
def __init__(self, dim, drop_path=0.):
|
24 |
+
super().__init__()
|
25 |
+
self.dwconv = nn.Conv2d(dim, dim, kernel_size=7, padding=3, groups=dim) # depthwise conv
|
26 |
+
self.norm = LayerNorm(dim, eps=1e-6)
|
27 |
+
self.pwconv1 = nn.Linear(dim, 4 * dim) # pointwise/1x1 convs, implemented with linear layers
|
28 |
+
self.act = nn.GELU()
|
29 |
+
self.grn = GRN(4 * dim)
|
30 |
+
self.pwconv2 = nn.Linear(4 * dim, dim)
|
31 |
+
self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
|
32 |
+
|
33 |
+
def forward(self, x):
|
34 |
+
input = x
|
35 |
+
x = self.dwconv(x)
|
36 |
+
x = x.permute(0, 2, 3, 1) # (N, C, H, W) -> (N, H, W, C)
|
37 |
+
x = self.norm(x)
|
38 |
+
x = self.pwconv1(x)
|
39 |
+
x = self.act(x)
|
40 |
+
x = self.grn(x)
|
41 |
+
x = self.pwconv2(x)
|
42 |
+
x = x.permute(0, 3, 1, 2) # (N, H, W, C) -> (N, C, H, W)
|
43 |
+
|
44 |
+
x = input + self.drop_path(x)
|
45 |
+
return x
|
46 |
+
|
47 |
+
|
48 |
+
class ConvNeXtV2(nn.Module):
|
49 |
+
""" ConvNeXt V2
|
50 |
+
|
51 |
+
Args:
|
52 |
+
in_chans (int): Number of input image channels. Default: 3
|
53 |
+
num_classes (int): Number of classes for classification head. Default: 1000
|
54 |
+
depths (tuple(int)): Number of blocks at each stage. Default: [3, 3, 9, 3]
|
55 |
+
dims (int): Feature dimension at each stage. Default: [96, 192, 384, 768]
|
56 |
+
drop_path_rate (float): Stochastic depth rate. Default: 0.
|
57 |
+
head_init_scale (float): Init scaling value for classifier weights and biases. Default: 1.
|
58 |
+
"""
|
59 |
+
|
60 |
+
def __init__(
|
61 |
+
self,
|
62 |
+
in_chans=3,
|
63 |
+
depths=[3, 3, 9, 3],
|
64 |
+
dims=[96, 192, 384, 768],
|
65 |
+
drop_path_rate=0.,
|
66 |
+
**kwargs
|
67 |
+
):
|
68 |
+
super().__init__()
|
69 |
+
self.depths = depths
|
70 |
+
self.downsample_layers = nn.ModuleList() # stem and 3 intermediate downsampling conv layers
|
71 |
+
stem = nn.Sequential(
|
72 |
+
nn.Conv2d(in_chans, dims[0], kernel_size=4, stride=4),
|
73 |
+
LayerNorm(dims[0], eps=1e-6, data_format="channels_first")
|
74 |
+
)
|
75 |
+
self.downsample_layers.append(stem)
|
76 |
+
for i in range(3):
|
77 |
+
downsample_layer = nn.Sequential(
|
78 |
+
LayerNorm(dims[i], eps=1e-6, data_format="channels_first"),
|
79 |
+
nn.Conv2d(dims[i], dims[i+1], kernel_size=2, stride=2),
|
80 |
+
)
|
81 |
+
self.downsample_layers.append(downsample_layer)
|
82 |
+
|
83 |
+
self.stages = nn.ModuleList() # 4 feature resolution stages, each consisting of multiple residual blocks
|
84 |
+
dp_rates = [x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))]
|
85 |
+
cur = 0
|
86 |
+
for i in range(4):
|
87 |
+
stage = nn.Sequential(
|
88 |
+
*[Block(dim=dims[i], drop_path=dp_rates[cur + j]) for j in range(depths[i])]
|
89 |
+
)
|
90 |
+
self.stages.append(stage)
|
91 |
+
cur += depths[i]
|
92 |
+
|
93 |
+
self.norm = nn.LayerNorm(dims[-1], eps=1e-6) # final norm layer
|
94 |
+
|
95 |
+
# NOTE: the output semantic items
|
96 |
+
num_bins = kwargs.get('num_bins', 66)
|
97 |
+
num_kp = kwargs.get('num_kp', 24) # the number of implicit keypoints
|
98 |
+
self.fc_kp = nn.Linear(dims[-1], 3 * num_kp) # implicit keypoints
|
99 |
+
|
100 |
+
# print('dims[-1]: ', dims[-1])
|
101 |
+
self.fc_scale = nn.Linear(dims[-1], 1) # scale
|
102 |
+
self.fc_pitch = nn.Linear(dims[-1], num_bins) # pitch bins
|
103 |
+
self.fc_yaw = nn.Linear(dims[-1], num_bins) # yaw bins
|
104 |
+
self.fc_roll = nn.Linear(dims[-1], num_bins) # roll bins
|
105 |
+
self.fc_t = nn.Linear(dims[-1], 3) # translation
|
106 |
+
self.fc_exp = nn.Linear(dims[-1], 3 * num_kp) # expression / delta
|
107 |
+
|
108 |
+
def _init_weights(self, m):
|
109 |
+
if isinstance(m, (nn.Conv2d, nn.Linear)):
|
110 |
+
trunc_normal_(m.weight, std=.02)
|
111 |
+
nn.init.constant_(m.bias, 0)
|
112 |
+
|
113 |
+
def forward_features(self, x):
|
114 |
+
for i in range(4):
|
115 |
+
x = self.downsample_layers[i](x)
|
116 |
+
x = self.stages[i](x)
|
117 |
+
return self.norm(x.mean([-2, -1])) # global average pooling, (N, C, H, W) -> (N, C)
|
118 |
+
|
119 |
+
def forward(self, x):
|
120 |
+
x = self.forward_features(x)
|
121 |
+
|
122 |
+
# implicit keypoints
|
123 |
+
kp = self.fc_kp(x)
|
124 |
+
|
125 |
+
# pose and expression deformation
|
126 |
+
pitch = self.fc_pitch(x)
|
127 |
+
yaw = self.fc_yaw(x)
|
128 |
+
roll = self.fc_roll(x)
|
129 |
+
t = self.fc_t(x)
|
130 |
+
exp = self.fc_exp(x)
|
131 |
+
scale = self.fc_scale(x)
|
132 |
+
|
133 |
+
ret_dct = {
|
134 |
+
'pitch': pitch,
|
135 |
+
'yaw': yaw,
|
136 |
+
'roll': roll,
|
137 |
+
't': t,
|
138 |
+
'exp': exp,
|
139 |
+
'scale': scale,
|
140 |
+
|
141 |
+
'kp': kp, # canonical keypoint
|
142 |
+
}
|
143 |
+
|
144 |
+
return ret_dct
|
145 |
+
|
146 |
+
|
147 |
+
def convnextv2_tiny(**kwargs):
|
148 |
+
model = ConvNeXtV2(depths=[3, 3, 9, 3], dims=[96, 192, 384, 768], **kwargs)
|
149 |
+
return model
|
difpoint/src/modules/dense_motion.py
ADDED
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
|
3 |
+
"""
|
4 |
+
The module that predicting a dense motion from sparse motion representation given by kp_source and kp_driving
|
5 |
+
"""
|
6 |
+
|
7 |
+
from torch import nn
|
8 |
+
import torch.nn.functional as F
|
9 |
+
import torch
|
10 |
+
from .util import Hourglass, make_coordinate_grid, kp2gaussian
|
11 |
+
|
12 |
+
|
13 |
+
class DenseMotionNetwork(nn.Module):
|
14 |
+
def __init__(self, block_expansion, num_blocks, max_features, num_kp, feature_channel, reshape_depth, compress, estimate_occlusion_map=True):
|
15 |
+
super(DenseMotionNetwork, self).__init__()
|
16 |
+
self.hourglass = Hourglass(block_expansion=block_expansion, in_features=(num_kp+1)*(compress+1), max_features=max_features, num_blocks=num_blocks) # ~60+G
|
17 |
+
|
18 |
+
self.mask = nn.Conv3d(self.hourglass.out_filters, num_kp + 1, kernel_size=7, padding=3) # 65G! NOTE: computation cost is large
|
19 |
+
self.compress = nn.Conv3d(feature_channel, compress, kernel_size=1) # 0.8G
|
20 |
+
self.norm = nn.BatchNorm3d(compress, affine=True)
|
21 |
+
self.num_kp = num_kp
|
22 |
+
self.flag_estimate_occlusion_map = estimate_occlusion_map
|
23 |
+
|
24 |
+
if self.flag_estimate_occlusion_map:
|
25 |
+
self.occlusion = nn.Conv2d(self.hourglass.out_filters*reshape_depth, 1, kernel_size=7, padding=3)
|
26 |
+
else:
|
27 |
+
self.occlusion = None
|
28 |
+
|
29 |
+
def create_sparse_motions(self, feature, kp_driving, kp_source):
|
30 |
+
bs, _, d, h, w = feature.shape # (bs, 4, 16, 64, 64)
|
31 |
+
identity_grid = make_coordinate_grid((d, h, w), ref=kp_source) # (16, 64, 64, 3)
|
32 |
+
identity_grid = identity_grid.view(1, 1, d, h, w, 3) # (1, 1, d=16, h=64, w=64, 3)
|
33 |
+
coordinate_grid = identity_grid - kp_driving.view(bs, self.num_kp, 1, 1, 1, 3)
|
34 |
+
|
35 |
+
k = coordinate_grid.shape[1]
|
36 |
+
|
37 |
+
# NOTE: there lacks an one-order flow
|
38 |
+
driving_to_source = coordinate_grid + kp_source.view(bs, self.num_kp, 1, 1, 1, 3) # (bs, num_kp, d, h, w, 3)
|
39 |
+
|
40 |
+
# adding background feature
|
41 |
+
identity_grid = identity_grid.repeat(bs, 1, 1, 1, 1, 1)
|
42 |
+
sparse_motions = torch.cat([identity_grid, driving_to_source], dim=1) # (bs, 1+num_kp, d, h, w, 3)
|
43 |
+
return sparse_motions
|
44 |
+
|
45 |
+
def create_deformed_feature(self, feature, sparse_motions):
|
46 |
+
bs, _, d, h, w = feature.shape
|
47 |
+
feature_repeat = feature.unsqueeze(1).unsqueeze(1).repeat(1, self.num_kp+1, 1, 1, 1, 1, 1) # (bs, num_kp+1, 1, c, d, h, w)
|
48 |
+
feature_repeat = feature_repeat.view(bs * (self.num_kp+1), -1, d, h, w) # (bs*(num_kp+1), c, d, h, w)
|
49 |
+
sparse_motions = sparse_motions.view((bs * (self.num_kp+1), d, h, w, -1)) # (bs*(num_kp+1), d, h, w, 3)
|
50 |
+
sparse_deformed = F.grid_sample(feature_repeat, sparse_motions, align_corners=False)
|
51 |
+
sparse_deformed = sparse_deformed.view((bs, self.num_kp+1, -1, d, h, w)) # (bs, num_kp+1, c, d, h, w)
|
52 |
+
|
53 |
+
return sparse_deformed
|
54 |
+
|
55 |
+
def create_heatmap_representations(self, feature, kp_driving, kp_source):
|
56 |
+
spatial_size = feature.shape[3:] # (d=16, h=64, w=64)
|
57 |
+
gaussian_driving = kp2gaussian(kp_driving, spatial_size=spatial_size, kp_variance=0.01) # (bs, num_kp, d, h, w)
|
58 |
+
gaussian_source = kp2gaussian(kp_source, spatial_size=spatial_size, kp_variance=0.01) # (bs, num_kp, d, h, w)
|
59 |
+
heatmap = gaussian_driving - gaussian_source # (bs, num_kp, d, h, w)
|
60 |
+
|
61 |
+
# adding background feature
|
62 |
+
zeros = torch.zeros(heatmap.shape[0], 1, spatial_size[0], spatial_size[1], spatial_size[2]).type(heatmap.type()).to(heatmap.device)
|
63 |
+
heatmap = torch.cat([zeros, heatmap], dim=1)
|
64 |
+
heatmap = heatmap.unsqueeze(2) # (bs, 1+num_kp, 1, d, h, w)
|
65 |
+
return heatmap
|
66 |
+
|
67 |
+
def forward(self, feature, kp_driving, kp_source):
|
68 |
+
bs, _, d, h, w = feature.shape # (bs, 32, 16, 64, 64)
|
69 |
+
|
70 |
+
feature = self.compress(feature) # (bs, 4, 16, 64, 64)
|
71 |
+
feature = self.norm(feature) # (bs, 4, 16, 64, 64)
|
72 |
+
feature = F.relu(feature) # (bs, 4, 16, 64, 64)
|
73 |
+
|
74 |
+
out_dict = dict()
|
75 |
+
|
76 |
+
# 1. deform 3d feature
|
77 |
+
sparse_motion = self.create_sparse_motions(feature, kp_driving, kp_source) # (bs, 1+num_kp, d, h, w, 3)
|
78 |
+
deformed_feature = self.create_deformed_feature(feature, sparse_motion) # (bs, 1+num_kp, c=4, d=16, h=64, w=64)
|
79 |
+
|
80 |
+
# 2. (bs, 1+num_kp, d, h, w)
|
81 |
+
heatmap = self.create_heatmap_representations(deformed_feature, kp_driving, kp_source) # (bs, 1+num_kp, 1, d, h, w)
|
82 |
+
|
83 |
+
input = torch.cat([heatmap, deformed_feature], dim=2) # (bs, 1+num_kp, c=5, d=16, h=64, w=64)
|
84 |
+
input = input.view(bs, -1, d, h, w) # (bs, (1+num_kp)*c=105, d=16, h=64, w=64)
|
85 |
+
|
86 |
+
prediction = self.hourglass(input)
|
87 |
+
|
88 |
+
mask = self.mask(prediction)
|
89 |
+
mask = F.softmax(mask, dim=1) # (bs, 1+num_kp, d=16, h=64, w=64)
|
90 |
+
out_dict['mask'] = mask
|
91 |
+
mask = mask.unsqueeze(2) # (bs, num_kp+1, 1, d, h, w)
|
92 |
+
sparse_motion = sparse_motion.permute(0, 1, 5, 2, 3, 4) # (bs, num_kp+1, 3, d, h, w)
|
93 |
+
deformation = (sparse_motion * mask).sum(dim=1) # (bs, 3, d, h, w) mask take effect in this place
|
94 |
+
deformation = deformation.permute(0, 2, 3, 4, 1) # (bs, d, h, w, 3)
|
95 |
+
|
96 |
+
out_dict['deformation'] = deformation
|
97 |
+
|
98 |
+
if self.flag_estimate_occlusion_map:
|
99 |
+
bs, _, d, h, w = prediction.shape
|
100 |
+
prediction_reshape = prediction.view(bs, -1, h, w)
|
101 |
+
occlusion_map = torch.sigmoid(self.occlusion(prediction_reshape)) # Bx1x64x64
|
102 |
+
out_dict['occlusion_map'] = occlusion_map
|
103 |
+
|
104 |
+
return out_dict
|
difpoint/src/modules/motion_extractor.py
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
|
3 |
+
"""
|
4 |
+
Motion extractor(M), which directly predicts the canonical keypoints, head pose and expression deformation of the input image
|
5 |
+
"""
|
6 |
+
|
7 |
+
from torch import nn
|
8 |
+
import torch
|
9 |
+
|
10 |
+
from .convnextv2 import convnextv2_tiny
|
11 |
+
from .util import filter_state_dict
|
12 |
+
|
13 |
+
model_dict = {
|
14 |
+
'convnextv2_tiny': convnextv2_tiny,
|
15 |
+
}
|
16 |
+
|
17 |
+
|
18 |
+
class MotionExtractor(nn.Module):
|
19 |
+
def __init__(self, **kwargs):
|
20 |
+
super(MotionExtractor, self).__init__()
|
21 |
+
|
22 |
+
# default is convnextv2_base
|
23 |
+
backbone = kwargs.get('backbone', 'convnextv2_tiny')
|
24 |
+
self.detector = model_dict.get(backbone)(**kwargs)
|
25 |
+
|
26 |
+
def load_pretrained(self, init_path: str):
|
27 |
+
if init_path not in (None, ''):
|
28 |
+
state_dict = torch.load(init_path, map_location=lambda storage, loc: storage)['model']
|
29 |
+
state_dict = filter_state_dict(state_dict, remove_name='head')
|
30 |
+
ret = self.detector.load_state_dict(state_dict, strict=False)
|
31 |
+
print(f'Load pretrained model from {init_path}, ret: {ret}')
|
32 |
+
|
33 |
+
def forward(self, x):
|
34 |
+
out = self.detector(x)
|
35 |
+
return out
|
difpoint/src/modules/spade_generator.py
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
|
3 |
+
"""
|
4 |
+
Spade decoder(G) defined in the paper, which input the warped feature to generate the animated image.
|
5 |
+
"""
|
6 |
+
|
7 |
+
import torch
|
8 |
+
from torch import nn
|
9 |
+
import torch.nn.functional as F
|
10 |
+
from .util import SPADEResnetBlock
|
11 |
+
|
12 |
+
|
13 |
+
class SPADEDecoder(nn.Module):
|
14 |
+
def __init__(self, upscale=1, max_features=256, block_expansion=64, out_channels=64, num_down_blocks=2):
|
15 |
+
for i in range(num_down_blocks):
|
16 |
+
input_channels = min(max_features, block_expansion * (2 ** (i + 1)))
|
17 |
+
self.upscale = upscale
|
18 |
+
super().__init__()
|
19 |
+
norm_G = 'spadespectralinstance'
|
20 |
+
label_num_channels = input_channels # 256
|
21 |
+
|
22 |
+
self.fc = nn.Conv2d(input_channels, 2 * input_channels, 3, padding=1)
|
23 |
+
self.G_middle_0 = SPADEResnetBlock(2 * input_channels, 2 * input_channels, norm_G, label_num_channels)
|
24 |
+
self.G_middle_1 = SPADEResnetBlock(2 * input_channels, 2 * input_channels, norm_G, label_num_channels)
|
25 |
+
self.G_middle_2 = SPADEResnetBlock(2 * input_channels, 2 * input_channels, norm_G, label_num_channels)
|
26 |
+
self.G_middle_3 = SPADEResnetBlock(2 * input_channels, 2 * input_channels, norm_G, label_num_channels)
|
27 |
+
self.G_middle_4 = SPADEResnetBlock(2 * input_channels, 2 * input_channels, norm_G, label_num_channels)
|
28 |
+
self.G_middle_5 = SPADEResnetBlock(2 * input_channels, 2 * input_channels, norm_G, label_num_channels)
|
29 |
+
self.up_0 = SPADEResnetBlock(2 * input_channels, input_channels, norm_G, label_num_channels)
|
30 |
+
self.up_1 = SPADEResnetBlock(input_channels, out_channels, norm_G, label_num_channels)
|
31 |
+
self.up = nn.Upsample(scale_factor=2)
|
32 |
+
|
33 |
+
if self.upscale is None or self.upscale <= 1:
|
34 |
+
self.conv_img = nn.Conv2d(out_channels, 3, 3, padding=1)
|
35 |
+
else:
|
36 |
+
self.conv_img = nn.Sequential(
|
37 |
+
nn.Conv2d(out_channels, 3 * (2 * 2), kernel_size=3, padding=1),
|
38 |
+
nn.PixelShuffle(upscale_factor=2)
|
39 |
+
)
|
40 |
+
|
41 |
+
def forward(self, feature):
|
42 |
+
seg = feature # Bx256x64x64
|
43 |
+
x = self.fc(feature) # Bx512x64x64
|
44 |
+
x = self.G_middle_0(x, seg)
|
45 |
+
x = self.G_middle_1(x, seg)
|
46 |
+
x = self.G_middle_2(x, seg)
|
47 |
+
x = self.G_middle_3(x, seg)
|
48 |
+
x = self.G_middle_4(x, seg)
|
49 |
+
x = self.G_middle_5(x, seg)
|
50 |
+
|
51 |
+
x = self.up(x) # Bx512x64x64 -> Bx512x128x128
|
52 |
+
x = self.up_0(x, seg) # Bx512x128x128 -> Bx256x128x128
|
53 |
+
x = self.up(x) # Bx256x128x128 -> Bx256x256x256
|
54 |
+
x = self.up_1(x, seg) # Bx256x256x256 -> Bx64x256x256
|
55 |
+
|
56 |
+
x = self.conv_img(F.leaky_relu(x, 2e-1)) # Bx64x256x256 -> Bx3xHxW
|
57 |
+
x = torch.sigmoid(x) # Bx3xHxW
|
58 |
+
|
59 |
+
return x
|
difpoint/src/modules/stitching_retargeting_network.py
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
|
3 |
+
"""
|
4 |
+
Stitching module(S) and two retargeting modules(R) defined in the paper.
|
5 |
+
|
6 |
+
- The stitching module pastes the animated portrait back into the original image space without pixel misalignment, such as in
|
7 |
+
the stitching region.
|
8 |
+
|
9 |
+
- The eyes retargeting module is designed to address the issue of incomplete eye closure during cross-id reenactment, especially
|
10 |
+
when a person with small eyes drives a person with larger eyes.
|
11 |
+
|
12 |
+
- The lip retargeting module is designed similarly to the eye retargeting module, and can also normalize the input by ensuring that
|
13 |
+
the lips are in a closed state, which facilitates better animation driving.
|
14 |
+
"""
|
15 |
+
from torch import nn
|
16 |
+
|
17 |
+
|
18 |
+
class StitchingRetargetingNetwork(nn.Module):
|
19 |
+
def __init__(self, input_size, hidden_sizes, output_size):
|
20 |
+
super(StitchingRetargetingNetwork, self).__init__()
|
21 |
+
layers = []
|
22 |
+
for i in range(len(hidden_sizes)):
|
23 |
+
if i == 0:
|
24 |
+
layers.append(nn.Linear(input_size, hidden_sizes[i]))
|
25 |
+
else:
|
26 |
+
layers.append(nn.Linear(hidden_sizes[i - 1], hidden_sizes[i]))
|
27 |
+
layers.append(nn.ReLU(inplace=True))
|
28 |
+
layers.append(nn.Linear(hidden_sizes[-1], output_size))
|
29 |
+
self.mlp = nn.Sequential(*layers)
|
30 |
+
|
31 |
+
def initialize_weights_to_zero(self):
|
32 |
+
for m in self.modules():
|
33 |
+
if isinstance(m, nn.Linear):
|
34 |
+
nn.init.zeros_(m.weight)
|
35 |
+
nn.init.zeros_(m.bias)
|
36 |
+
|
37 |
+
def forward(self, x):
|
38 |
+
return self.mlp(x)
|
difpoint/src/modules/util.py
ADDED
@@ -0,0 +1,441 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
|
3 |
+
"""
|
4 |
+
This file defines various neural network modules and utility functions, including convolutional and residual blocks,
|
5 |
+
normalizations, and functions for spatial transformation and tensor manipulation.
|
6 |
+
"""
|
7 |
+
|
8 |
+
from torch import nn
|
9 |
+
import torch.nn.functional as F
|
10 |
+
import torch
|
11 |
+
import torch.nn.utils.spectral_norm as spectral_norm
|
12 |
+
import math
|
13 |
+
import warnings
|
14 |
+
|
15 |
+
|
16 |
+
def kp2gaussian(kp, spatial_size, kp_variance):
|
17 |
+
"""
|
18 |
+
Transform a keypoint into gaussian like representation
|
19 |
+
"""
|
20 |
+
mean = kp
|
21 |
+
|
22 |
+
coordinate_grid = make_coordinate_grid(spatial_size, mean)
|
23 |
+
number_of_leading_dimensions = len(mean.shape) - 1
|
24 |
+
shape = (1,) * number_of_leading_dimensions + coordinate_grid.shape
|
25 |
+
coordinate_grid = coordinate_grid.view(*shape)
|
26 |
+
repeats = mean.shape[:number_of_leading_dimensions] + (1, 1, 1, 1)
|
27 |
+
coordinate_grid = coordinate_grid.repeat(*repeats)
|
28 |
+
|
29 |
+
# Preprocess kp shape
|
30 |
+
shape = mean.shape[:number_of_leading_dimensions] + (1, 1, 1, 3)
|
31 |
+
mean = mean.view(*shape)
|
32 |
+
|
33 |
+
mean_sub = (coordinate_grid - mean)
|
34 |
+
|
35 |
+
out = torch.exp(-0.5 * (mean_sub ** 2).sum(-1) / kp_variance)
|
36 |
+
|
37 |
+
return out
|
38 |
+
|
39 |
+
|
40 |
+
def make_coordinate_grid(spatial_size, ref, **kwargs):
|
41 |
+
d, h, w = spatial_size
|
42 |
+
x = torch.arange(w).type(ref.dtype).to(ref.device)
|
43 |
+
y = torch.arange(h).type(ref.dtype).to(ref.device)
|
44 |
+
z = torch.arange(d).type(ref.dtype).to(ref.device)
|
45 |
+
|
46 |
+
# NOTE: must be right-down-in
|
47 |
+
x = (2 * (x / (w - 1)) - 1) # the x axis faces to the right
|
48 |
+
y = (2 * (y / (h - 1)) - 1) # the y axis faces to the bottom
|
49 |
+
z = (2 * (z / (d - 1)) - 1) # the z axis faces to the inner
|
50 |
+
|
51 |
+
yy = y.view(1, -1, 1).repeat(d, 1, w)
|
52 |
+
xx = x.view(1, 1, -1).repeat(d, h, 1)
|
53 |
+
zz = z.view(-1, 1, 1).repeat(1, h, w)
|
54 |
+
|
55 |
+
meshed = torch.cat([xx.unsqueeze_(3), yy.unsqueeze_(3), zz.unsqueeze_(3)], 3)
|
56 |
+
|
57 |
+
return meshed
|
58 |
+
|
59 |
+
|
60 |
+
class ConvT2d(nn.Module):
|
61 |
+
"""
|
62 |
+
Upsampling block for use in decoder.
|
63 |
+
"""
|
64 |
+
|
65 |
+
def __init__(self, in_features, out_features, kernel_size=3, stride=2, padding=1, output_padding=1):
|
66 |
+
super(ConvT2d, self).__init__()
|
67 |
+
|
68 |
+
self.convT = nn.ConvTranspose2d(in_features, out_features, kernel_size=kernel_size, stride=stride,
|
69 |
+
padding=padding, output_padding=output_padding)
|
70 |
+
self.norm = nn.InstanceNorm2d(out_features)
|
71 |
+
|
72 |
+
def forward(self, x):
|
73 |
+
out = self.convT(x)
|
74 |
+
out = self.norm(out)
|
75 |
+
out = F.leaky_relu(out)
|
76 |
+
return out
|
77 |
+
|
78 |
+
|
79 |
+
class ResBlock3d(nn.Module):
|
80 |
+
"""
|
81 |
+
Res block, preserve spatial resolution.
|
82 |
+
"""
|
83 |
+
|
84 |
+
def __init__(self, in_features, kernel_size, padding):
|
85 |
+
super(ResBlock3d, self).__init__()
|
86 |
+
self.conv1 = nn.Conv3d(in_channels=in_features, out_channels=in_features, kernel_size=kernel_size, padding=padding)
|
87 |
+
self.conv2 = nn.Conv3d(in_channels=in_features, out_channels=in_features, kernel_size=kernel_size, padding=padding)
|
88 |
+
self.norm1 = nn.BatchNorm3d(in_features, affine=True)
|
89 |
+
self.norm2 = nn.BatchNorm3d(in_features, affine=True)
|
90 |
+
|
91 |
+
def forward(self, x):
|
92 |
+
out = self.norm1(x)
|
93 |
+
out = F.relu(out)
|
94 |
+
out = self.conv1(out)
|
95 |
+
out = self.norm2(out)
|
96 |
+
out = F.relu(out)
|
97 |
+
out = self.conv2(out)
|
98 |
+
out += x
|
99 |
+
return out
|
100 |
+
|
101 |
+
|
102 |
+
class UpBlock3d(nn.Module):
|
103 |
+
"""
|
104 |
+
Upsampling block for use in decoder.
|
105 |
+
"""
|
106 |
+
|
107 |
+
def __init__(self, in_features, out_features, kernel_size=3, padding=1, groups=1):
|
108 |
+
super(UpBlock3d, self).__init__()
|
109 |
+
|
110 |
+
self.conv = nn.Conv3d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size,
|
111 |
+
padding=padding, groups=groups)
|
112 |
+
self.norm = nn.BatchNorm3d(out_features, affine=True)
|
113 |
+
|
114 |
+
def forward(self, x):
|
115 |
+
out = F.interpolate(x, scale_factor=(1, 2, 2))
|
116 |
+
out = self.conv(out)
|
117 |
+
out = self.norm(out)
|
118 |
+
out = F.relu(out)
|
119 |
+
return out
|
120 |
+
|
121 |
+
|
122 |
+
class DownBlock2d(nn.Module):
|
123 |
+
"""
|
124 |
+
Downsampling block for use in encoder.
|
125 |
+
"""
|
126 |
+
|
127 |
+
def __init__(self, in_features, out_features, kernel_size=3, padding=1, groups=1):
|
128 |
+
super(DownBlock2d, self).__init__()
|
129 |
+
self.conv = nn.Conv2d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size, padding=padding, groups=groups)
|
130 |
+
self.norm = nn.BatchNorm2d(out_features, affine=True)
|
131 |
+
self.pool = nn.AvgPool2d(kernel_size=(2, 2))
|
132 |
+
|
133 |
+
def forward(self, x):
|
134 |
+
out = self.conv(x)
|
135 |
+
out = self.norm(out)
|
136 |
+
out = F.relu(out)
|
137 |
+
out = self.pool(out)
|
138 |
+
return out
|
139 |
+
|
140 |
+
|
141 |
+
class DownBlock3d(nn.Module):
|
142 |
+
"""
|
143 |
+
Downsampling block for use in encoder.
|
144 |
+
"""
|
145 |
+
|
146 |
+
def __init__(self, in_features, out_features, kernel_size=3, padding=1, groups=1):
|
147 |
+
super(DownBlock3d, self).__init__()
|
148 |
+
'''
|
149 |
+
self.conv = nn.Conv3d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size,
|
150 |
+
padding=padding, groups=groups, stride=(1, 2, 2))
|
151 |
+
'''
|
152 |
+
self.conv = nn.Conv3d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size,
|
153 |
+
padding=padding, groups=groups)
|
154 |
+
self.norm = nn.BatchNorm3d(out_features, affine=True)
|
155 |
+
self.pool = nn.AvgPool3d(kernel_size=(1, 2, 2))
|
156 |
+
|
157 |
+
def forward(self, x):
|
158 |
+
out = self.conv(x)
|
159 |
+
out = self.norm(out)
|
160 |
+
out = F.relu(out)
|
161 |
+
out = self.pool(out)
|
162 |
+
return out
|
163 |
+
|
164 |
+
|
165 |
+
class SameBlock2d(nn.Module):
|
166 |
+
"""
|
167 |
+
Simple block, preserve spatial resolution.
|
168 |
+
"""
|
169 |
+
|
170 |
+
def __init__(self, in_features, out_features, groups=1, kernel_size=3, padding=1, lrelu=False):
|
171 |
+
super(SameBlock2d, self).__init__()
|
172 |
+
self.conv = nn.Conv2d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size, padding=padding, groups=groups)
|
173 |
+
self.norm = nn.BatchNorm2d(out_features, affine=True)
|
174 |
+
if lrelu:
|
175 |
+
self.ac = nn.LeakyReLU()
|
176 |
+
else:
|
177 |
+
self.ac = nn.ReLU()
|
178 |
+
|
179 |
+
def forward(self, x):
|
180 |
+
out = self.conv(x)
|
181 |
+
out = self.norm(out)
|
182 |
+
out = self.ac(out)
|
183 |
+
return out
|
184 |
+
|
185 |
+
|
186 |
+
class Encoder(nn.Module):
|
187 |
+
"""
|
188 |
+
Hourglass Encoder
|
189 |
+
"""
|
190 |
+
|
191 |
+
def __init__(self, block_expansion, in_features, num_blocks=3, max_features=256):
|
192 |
+
super(Encoder, self).__init__()
|
193 |
+
|
194 |
+
down_blocks = []
|
195 |
+
for i in range(num_blocks):
|
196 |
+
down_blocks.append(DownBlock3d(in_features if i == 0 else min(max_features, block_expansion * (2 ** i)), min(max_features, block_expansion * (2 ** (i + 1))), kernel_size=3, padding=1))
|
197 |
+
self.down_blocks = nn.ModuleList(down_blocks)
|
198 |
+
|
199 |
+
def forward(self, x):
|
200 |
+
outs = [x]
|
201 |
+
for down_block in self.down_blocks:
|
202 |
+
outs.append(down_block(outs[-1]))
|
203 |
+
return outs
|
204 |
+
|
205 |
+
|
206 |
+
class Decoder(nn.Module):
|
207 |
+
"""
|
208 |
+
Hourglass Decoder
|
209 |
+
"""
|
210 |
+
|
211 |
+
def __init__(self, block_expansion, in_features, num_blocks=3, max_features=256):
|
212 |
+
super(Decoder, self).__init__()
|
213 |
+
|
214 |
+
up_blocks = []
|
215 |
+
|
216 |
+
for i in range(num_blocks)[::-1]:
|
217 |
+
in_filters = (1 if i == num_blocks - 1 else 2) * min(max_features, block_expansion * (2 ** (i + 1)))
|
218 |
+
out_filters = min(max_features, block_expansion * (2 ** i))
|
219 |
+
up_blocks.append(UpBlock3d(in_filters, out_filters, kernel_size=3, padding=1))
|
220 |
+
|
221 |
+
self.up_blocks = nn.ModuleList(up_blocks)
|
222 |
+
self.out_filters = block_expansion + in_features
|
223 |
+
|
224 |
+
self.conv = nn.Conv3d(in_channels=self.out_filters, out_channels=self.out_filters, kernel_size=3, padding=1)
|
225 |
+
self.norm = nn.BatchNorm3d(self.out_filters, affine=True)
|
226 |
+
|
227 |
+
def forward(self, x):
|
228 |
+
out = x.pop()
|
229 |
+
for up_block in self.up_blocks:
|
230 |
+
out = up_block(out)
|
231 |
+
skip = x.pop()
|
232 |
+
out = torch.cat([out, skip], dim=1)
|
233 |
+
out = self.conv(out)
|
234 |
+
out = self.norm(out)
|
235 |
+
out = F.relu(out)
|
236 |
+
return out
|
237 |
+
|
238 |
+
|
239 |
+
class Hourglass(nn.Module):
|
240 |
+
"""
|
241 |
+
Hourglass architecture.
|
242 |
+
"""
|
243 |
+
|
244 |
+
def __init__(self, block_expansion, in_features, num_blocks=3, max_features=256):
|
245 |
+
super(Hourglass, self).__init__()
|
246 |
+
self.encoder = Encoder(block_expansion, in_features, num_blocks, max_features)
|
247 |
+
self.decoder = Decoder(block_expansion, in_features, num_blocks, max_features)
|
248 |
+
self.out_filters = self.decoder.out_filters
|
249 |
+
|
250 |
+
def forward(self, x):
|
251 |
+
return self.decoder(self.encoder(x))
|
252 |
+
|
253 |
+
|
254 |
+
class SPADE(nn.Module):
|
255 |
+
def __init__(self, norm_nc, label_nc):
|
256 |
+
super().__init__()
|
257 |
+
|
258 |
+
self.param_free_norm = nn.InstanceNorm2d(norm_nc, affine=False)
|
259 |
+
nhidden = 128
|
260 |
+
|
261 |
+
self.mlp_shared = nn.Sequential(
|
262 |
+
nn.Conv2d(label_nc, nhidden, kernel_size=3, padding=1),
|
263 |
+
nn.ReLU())
|
264 |
+
self.mlp_gamma = nn.Conv2d(nhidden, norm_nc, kernel_size=3, padding=1)
|
265 |
+
self.mlp_beta = nn.Conv2d(nhidden, norm_nc, kernel_size=3, padding=1)
|
266 |
+
|
267 |
+
def forward(self, x, segmap):
|
268 |
+
normalized = self.param_free_norm(x)
|
269 |
+
segmap = F.interpolate(segmap, size=x.size()[2:], mode='nearest')
|
270 |
+
actv = self.mlp_shared(segmap)
|
271 |
+
gamma = self.mlp_gamma(actv)
|
272 |
+
beta = self.mlp_beta(actv)
|
273 |
+
out = normalized * (1 + gamma) + beta
|
274 |
+
return out
|
275 |
+
|
276 |
+
|
277 |
+
class SPADEResnetBlock(nn.Module):
|
278 |
+
def __init__(self, fin, fout, norm_G, label_nc, use_se=False, dilation=1):
|
279 |
+
super().__init__()
|
280 |
+
# Attributes
|
281 |
+
self.learned_shortcut = (fin != fout)
|
282 |
+
fmiddle = min(fin, fout)
|
283 |
+
self.use_se = use_se
|
284 |
+
# create conv layers
|
285 |
+
self.conv_0 = nn.Conv2d(fin, fmiddle, kernel_size=3, padding=dilation, dilation=dilation)
|
286 |
+
self.conv_1 = nn.Conv2d(fmiddle, fout, kernel_size=3, padding=dilation, dilation=dilation)
|
287 |
+
if self.learned_shortcut:
|
288 |
+
self.conv_s = nn.Conv2d(fin, fout, kernel_size=1, bias=False)
|
289 |
+
# apply spectral norm if specified
|
290 |
+
if 'spectral' in norm_G:
|
291 |
+
self.conv_0 = spectral_norm(self.conv_0)
|
292 |
+
self.conv_1 = spectral_norm(self.conv_1)
|
293 |
+
if self.learned_shortcut:
|
294 |
+
self.conv_s = spectral_norm(self.conv_s)
|
295 |
+
# define normalization layers
|
296 |
+
self.norm_0 = SPADE(fin, label_nc)
|
297 |
+
self.norm_1 = SPADE(fmiddle, label_nc)
|
298 |
+
if self.learned_shortcut:
|
299 |
+
self.norm_s = SPADE(fin, label_nc)
|
300 |
+
|
301 |
+
def forward(self, x, seg1):
|
302 |
+
x_s = self.shortcut(x, seg1)
|
303 |
+
dx = self.conv_0(self.actvn(self.norm_0(x, seg1)))
|
304 |
+
dx = self.conv_1(self.actvn(self.norm_1(dx, seg1)))
|
305 |
+
out = x_s + dx
|
306 |
+
return out
|
307 |
+
|
308 |
+
def shortcut(self, x, seg1):
|
309 |
+
if self.learned_shortcut:
|
310 |
+
x_s = self.conv_s(self.norm_s(x, seg1))
|
311 |
+
else:
|
312 |
+
x_s = x
|
313 |
+
return x_s
|
314 |
+
|
315 |
+
def actvn(self, x):
|
316 |
+
return F.leaky_relu(x, 2e-1)
|
317 |
+
|
318 |
+
|
319 |
+
def filter_state_dict(state_dict, remove_name='fc'):
|
320 |
+
new_state_dict = {}
|
321 |
+
for key in state_dict:
|
322 |
+
if remove_name in key:
|
323 |
+
continue
|
324 |
+
new_state_dict[key] = state_dict[key]
|
325 |
+
return new_state_dict
|
326 |
+
|
327 |
+
|
328 |
+
class GRN(nn.Module):
|
329 |
+
""" GRN (Global Response Normalization) layer
|
330 |
+
"""
|
331 |
+
|
332 |
+
def __init__(self, dim):
|
333 |
+
super().__init__()
|
334 |
+
self.gamma = nn.Parameter(torch.zeros(1, 1, 1, dim))
|
335 |
+
self.beta = nn.Parameter(torch.zeros(1, 1, 1, dim))
|
336 |
+
|
337 |
+
def forward(self, x):
|
338 |
+
Gx = torch.norm(x, p=2, dim=(1, 2), keepdim=True)
|
339 |
+
Nx = Gx / (Gx.mean(dim=-1, keepdim=True) + 1e-6)
|
340 |
+
return self.gamma * (x * Nx) + self.beta + x
|
341 |
+
|
342 |
+
|
343 |
+
class LayerNorm(nn.Module):
|
344 |
+
r""" LayerNorm that supports two data formats: channels_last (default) or channels_first.
|
345 |
+
The ordering of the dimensions in the inputs. channels_last corresponds to inputs with
|
346 |
+
shape (batch_size, height, width, channels) while channels_first corresponds to inputs
|
347 |
+
with shape (batch_size, channels, height, width).
|
348 |
+
"""
|
349 |
+
|
350 |
+
def __init__(self, normalized_shape, eps=1e-6, data_format="channels_last"):
|
351 |
+
super().__init__()
|
352 |
+
self.weight = nn.Parameter(torch.ones(normalized_shape))
|
353 |
+
self.bias = nn.Parameter(torch.zeros(normalized_shape))
|
354 |
+
self.eps = eps
|
355 |
+
self.data_format = data_format
|
356 |
+
if self.data_format not in ["channels_last", "channels_first"]:
|
357 |
+
raise NotImplementedError
|
358 |
+
self.normalized_shape = (normalized_shape, )
|
359 |
+
|
360 |
+
def forward(self, x):
|
361 |
+
if self.data_format == "channels_last":
|
362 |
+
return F.layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps)
|
363 |
+
elif self.data_format == "channels_first":
|
364 |
+
u = x.mean(1, keepdim=True)
|
365 |
+
s = (x - u).pow(2).mean(1, keepdim=True)
|
366 |
+
x = (x - u) / torch.sqrt(s + self.eps)
|
367 |
+
x = self.weight[:, None, None] * x + self.bias[:, None, None]
|
368 |
+
return x
|
369 |
+
|
370 |
+
|
371 |
+
def _no_grad_trunc_normal_(tensor, mean, std, a, b):
|
372 |
+
# Cut & paste from PyTorch official master until it's in a few official releases - RW
|
373 |
+
# Method based on https://people.sc.fsu.edu/~jburkardt/presentations/truncated_normal.pdf
|
374 |
+
def norm_cdf(x):
|
375 |
+
# Computes standard normal cumulative distribution function
|
376 |
+
return (1. + math.erf(x / math.sqrt(2.))) / 2.
|
377 |
+
|
378 |
+
if (mean < a - 2 * std) or (mean > b + 2 * std):
|
379 |
+
warnings.warn("mean is more than 2 std from [a, b] in nn.init.trunc_normal_. "
|
380 |
+
"The distribution of values may be incorrect.",
|
381 |
+
stacklevel=2)
|
382 |
+
|
383 |
+
with torch.no_grad():
|
384 |
+
# Values are generated by using a truncated uniform distribution and
|
385 |
+
# then using the inverse CDF for the normal distribution.
|
386 |
+
# Get upper and lower cdf values
|
387 |
+
l = norm_cdf((a - mean) / std)
|
388 |
+
u = norm_cdf((b - mean) / std)
|
389 |
+
|
390 |
+
# Uniformly fill tensor with values from [l, u], then translate to
|
391 |
+
# [2l-1, 2u-1].
|
392 |
+
tensor.uniform_(2 * l - 1, 2 * u - 1)
|
393 |
+
|
394 |
+
# Use inverse cdf transform for normal distribution to get truncated
|
395 |
+
# standard normal
|
396 |
+
tensor.erfinv_()
|
397 |
+
|
398 |
+
# Transform to proper mean, std
|
399 |
+
tensor.mul_(std * math.sqrt(2.))
|
400 |
+
tensor.add_(mean)
|
401 |
+
|
402 |
+
# Clamp to ensure it's in the proper range
|
403 |
+
tensor.clamp_(min=a, max=b)
|
404 |
+
return tensor
|
405 |
+
|
406 |
+
|
407 |
+
def drop_path(x, drop_prob=0., training=False, scale_by_keep=True):
|
408 |
+
""" Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).
|
409 |
+
|
410 |
+
This is the same as the DropConnect impl I created for EfficientNet, etc networks, however,
|
411 |
+
the original name is misleading as 'Drop Connect' is a different form of dropout in a separate paper...
|
412 |
+
See discussion: https://github.com/tensorflow/tpu/issues/494#issuecomment-532968956 ... I've opted for
|
413 |
+
changing the layer and argument names to 'drop path' rather than mix DropConnect as a layer name and use
|
414 |
+
'survival rate' as the argument.
|
415 |
+
|
416 |
+
"""
|
417 |
+
if drop_prob == 0. or not training:
|
418 |
+
return x
|
419 |
+
keep_prob = 1 - drop_prob
|
420 |
+
shape = (x.shape[0],) + (1,) * (x.ndim - 1) # work with diff dim tensors, not just 2D ConvNets
|
421 |
+
random_tensor = x.new_empty(shape).bernoulli_(keep_prob)
|
422 |
+
if keep_prob > 0.0 and scale_by_keep:
|
423 |
+
random_tensor.div_(keep_prob)
|
424 |
+
return x * random_tensor
|
425 |
+
|
426 |
+
|
427 |
+
class DropPath(nn.Module):
|
428 |
+
""" Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).
|
429 |
+
"""
|
430 |
+
|
431 |
+
def __init__(self, drop_prob=None, scale_by_keep=True):
|
432 |
+
super(DropPath, self).__init__()
|
433 |
+
self.drop_prob = drop_prob
|
434 |
+
self.scale_by_keep = scale_by_keep
|
435 |
+
|
436 |
+
def forward(self, x):
|
437 |
+
return drop_path(x, self.drop_prob, self.training, self.scale_by_keep)
|
438 |
+
|
439 |
+
|
440 |
+
def trunc_normal_(tensor, mean=0., std=1., a=-2., b=2.):
|
441 |
+
return _no_grad_trunc_normal_(tensor, mean, std, a, b)
|
difpoint/src/modules/warping_network.py
ADDED
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
|
3 |
+
"""
|
4 |
+
Warping field estimator(W) defined in the paper, which generates a warping field using the implicit
|
5 |
+
keypoint representations x_s and x_d, and employs this flow field to warp the source feature volume f_s.
|
6 |
+
"""
|
7 |
+
|
8 |
+
from torch import nn
|
9 |
+
import torch.nn.functional as F
|
10 |
+
from .util import SameBlock2d
|
11 |
+
from .dense_motion import DenseMotionNetwork
|
12 |
+
|
13 |
+
|
14 |
+
class WarpingNetwork(nn.Module):
|
15 |
+
def __init__(
|
16 |
+
self,
|
17 |
+
num_kp,
|
18 |
+
block_expansion,
|
19 |
+
max_features,
|
20 |
+
num_down_blocks,
|
21 |
+
reshape_channel,
|
22 |
+
estimate_occlusion_map=False,
|
23 |
+
dense_motion_params=None,
|
24 |
+
**kwargs
|
25 |
+
):
|
26 |
+
super(WarpingNetwork, self).__init__()
|
27 |
+
|
28 |
+
self.upscale = kwargs.get('upscale', 1)
|
29 |
+
self.flag_use_occlusion_map = kwargs.get('flag_use_occlusion_map', True)
|
30 |
+
|
31 |
+
if dense_motion_params is not None:
|
32 |
+
self.dense_motion_network = DenseMotionNetwork(
|
33 |
+
num_kp=num_kp,
|
34 |
+
feature_channel=reshape_channel,
|
35 |
+
estimate_occlusion_map=estimate_occlusion_map,
|
36 |
+
**dense_motion_params
|
37 |
+
)
|
38 |
+
else:
|
39 |
+
self.dense_motion_network = None
|
40 |
+
|
41 |
+
self.third = SameBlock2d(max_features, block_expansion * (2 ** num_down_blocks), kernel_size=(3, 3), padding=(1, 1), lrelu=True)
|
42 |
+
self.fourth = nn.Conv2d(in_channels=block_expansion * (2 ** num_down_blocks), out_channels=block_expansion * (2 ** num_down_blocks), kernel_size=1, stride=1)
|
43 |
+
|
44 |
+
self.estimate_occlusion_map = estimate_occlusion_map
|
45 |
+
|
46 |
+
def deform_input(self, inp, deformation):
|
47 |
+
return F.grid_sample(inp, deformation, align_corners=False)
|
48 |
+
|
49 |
+
def forward(self, feature_3d, kp_driving, kp_source):
|
50 |
+
if self.dense_motion_network is not None:
|
51 |
+
# Feature warper, Transforming feature representation according to deformation and occlusion
|
52 |
+
dense_motion = self.dense_motion_network(
|
53 |
+
feature=feature_3d, kp_driving=kp_driving, kp_source=kp_source
|
54 |
+
)
|
55 |
+
if 'occlusion_map' in dense_motion:
|
56 |
+
occlusion_map = dense_motion['occlusion_map'] # Bx1x64x64
|
57 |
+
else:
|
58 |
+
occlusion_map = None
|
59 |
+
|
60 |
+
deformation = dense_motion['deformation'] # Bx16x64x64x3
|
61 |
+
out = self.deform_input(feature_3d, deformation) # Bx32x16x64x64
|
62 |
+
|
63 |
+
bs, c, d, h, w = out.shape # Bx32x16x64x64
|
64 |
+
out = out.view(bs, c * d, h, w) # -> Bx512x64x64
|
65 |
+
out = self.third(out) # -> Bx256x64x64
|
66 |
+
out = self.fourth(out) # -> Bx256x64x64
|
67 |
+
|
68 |
+
if self.flag_use_occlusion_map and (occlusion_map is not None):
|
69 |
+
out = out * occlusion_map
|
70 |
+
|
71 |
+
ret_dct = {
|
72 |
+
'occlusion_map': occlusion_map,
|
73 |
+
'deformation': deformation,
|
74 |
+
'out': out,
|
75 |
+
}
|
76 |
+
|
77 |
+
return ret_dct
|
difpoint/src/utils/__init__.py
CHANGED
@@ -1,5 +0,0 @@
|
|
1 |
-
# -*- coding: utf-8 -*-
|
2 |
-
# @Author : wenshao
|
3 |
-
# @Email : [email protected]
|
4 |
-
# @Project : FasterLivePortrait
|
5 |
-
# @FileName: __init__.py.py
|
|
|
|
|
|
|
|
|
|
|
|
difpoint/src/utils/__pycache__/__init__.cpython-310.pyc
CHANGED
Binary files a/difpoint/src/utils/__pycache__/__init__.cpython-310.pyc and b/difpoint/src/utils/__pycache__/__init__.cpython-310.pyc differ
|
|
difpoint/src/utils/__pycache__/camera.cpython-310.pyc
ADDED
Binary file (1.74 kB). View file
|
|
difpoint/src/utils/__pycache__/crop.cpython-310.pyc
CHANGED
Binary files a/difpoint/src/utils/__pycache__/crop.cpython-310.pyc and b/difpoint/src/utils/__pycache__/crop.cpython-310.pyc differ
|
|
difpoint/src/utils/__pycache__/cropper.cpython-310.pyc
ADDED
Binary file (5.49 kB). View file
|
|
difpoint/src/utils/__pycache__/face_analysis_diy.cpython-310.pyc
ADDED
Binary file (3.26 kB). View file
|
|
difpoint/src/utils/__pycache__/helper.cpython-310.pyc
ADDED
Binary file (4.42 kB). View file
|
|
difpoint/src/utils/__pycache__/hparams.cpython-310.pyc
CHANGED
Binary files a/difpoint/src/utils/__pycache__/hparams.cpython-310.pyc and b/difpoint/src/utils/__pycache__/hparams.cpython-310.pyc differ
|
|
difpoint/src/utils/__pycache__/io.cpython-310.pyc
ADDED
Binary file (3.87 kB). View file
|
|
difpoint/src/utils/__pycache__/landmark_runner.cpython-310.pyc
ADDED
Binary file (2.77 kB). View file
|
|
difpoint/src/utils/__pycache__/retargeting_utils.cpython-310.pyc
ADDED
Binary file (1.19 kB). View file
|
|
difpoint/src/utils/__pycache__/rprint.cpython-310.pyc
ADDED
Binary file (348 Bytes). View file
|
|
difpoint/src/utils/__pycache__/timer.cpython-310.pyc
ADDED
Binary file (992 Bytes). View file
|
|
difpoint/src/utils/__pycache__/video.cpython-310.pyc
ADDED
Binary file (6.72 kB). View file
|
|
difpoint/src/utils/camera.py
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# coding: utf-8
|
2 |
+
|
3 |
+
"""
|
4 |
+
functions for processing and transforming 3D facial keypoints
|
5 |
+
"""
|
6 |
+
|
7 |
+
import numpy as np
|
8 |
+
import torch
|
9 |
+
import torch.nn.functional as F
|
10 |
+
|
11 |
+
PI = np.pi
|
12 |
+
|
13 |
+
|
14 |
+
def headpose_pred_to_degree(pred):
|
15 |
+
"""
|
16 |
+
pred: (bs, 66) or (bs, 1) or others
|
17 |
+
"""
|
18 |
+
if pred.ndim > 1 and pred.shape[1] == 66:
|
19 |
+
# NOTE: note that the average is modified to 97.5
|
20 |
+
device = pred.device
|
21 |
+
idx_tensor = [idx for idx in range(0, 66)]
|
22 |
+
idx_tensor = torch.FloatTensor(idx_tensor).to(device)
|
23 |
+
pred = F.softmax(pred, dim=1)
|
24 |
+
degree = torch.sum(pred*idx_tensor, axis=1) * 3 - 97.5
|
25 |
+
|
26 |
+
return degree
|
27 |
+
|
28 |
+
return pred
|
29 |
+
|
30 |
+
|
31 |
+
def get_rotation_matrix(pitch_, yaw_, roll_):
|
32 |
+
""" the input is in degree
|
33 |
+
"""
|
34 |
+
# transform to radian
|
35 |
+
pitch = pitch_ / 180 * PI
|
36 |
+
yaw = yaw_ / 180 * PI
|
37 |
+
roll = roll_ / 180 * PI
|
38 |
+
|
39 |
+
device = pitch.device
|
40 |
+
|
41 |
+
if pitch.ndim == 1:
|
42 |
+
pitch = pitch.unsqueeze(1)
|
43 |
+
if yaw.ndim == 1:
|
44 |
+
yaw = yaw.unsqueeze(1)
|
45 |
+
if roll.ndim == 1:
|
46 |
+
roll = roll.unsqueeze(1)
|
47 |
+
|
48 |
+
# calculate the euler matrix
|
49 |
+
bs = pitch.shape[0]
|
50 |
+
ones = torch.ones([bs, 1]).to(device)
|
51 |
+
zeros = torch.zeros([bs, 1]).to(device)
|
52 |
+
x, y, z = pitch, yaw, roll
|
53 |
+
|
54 |
+
rot_x = torch.cat([
|
55 |
+
ones, zeros, zeros,
|
56 |
+
zeros, torch.cos(x), -torch.sin(x),
|
57 |
+
zeros, torch.sin(x), torch.cos(x)
|
58 |
+
], dim=1).reshape([bs, 3, 3])
|
59 |
+
|
60 |
+
rot_y = torch.cat([
|
61 |
+
torch.cos(y), zeros, torch.sin(y),
|
62 |
+
zeros, ones, zeros,
|
63 |
+
-torch.sin(y), zeros, torch.cos(y)
|
64 |
+
], dim=1).reshape([bs, 3, 3])
|
65 |
+
|
66 |
+
rot_z = torch.cat([
|
67 |
+
torch.cos(z), -torch.sin(z), zeros,
|
68 |
+
torch.sin(z), torch.cos(z), zeros,
|
69 |
+
zeros, zeros, ones
|
70 |
+
], dim=1).reshape([bs, 3, 3])
|
71 |
+
|
72 |
+
rot = rot_z @ rot_y @ rot_x
|
73 |
+
return rot.permute(0, 2, 1) # transpose
|