From 8d5c84c0eccd5fae1f7b539fcd8b95fdce6a102b Mon Sep 17 00:00:00 2001 From: FuouM Date: Sat, 10 Aug 2024 19:36:06 +0700 Subject: [PATCH 1/4] refactor --- README.md | 6 + fomm_loader.py | 35 --- ...te_inference.py => inference_articulate.py | 0 fomm_inference.py => inference_fomm.py | 45 ++- inference_partswap.py | 274 ++++++++++++++++++ spline_inference.py => inference_spline.py | 2 +- partswap_inference.py | 84 ------ partswap_loader.py | 92 ------ partswap_model.py | 105 ------- pyproject.toml | 2 +- requirements.txt | 2 +- run.py | 12 +- 12 files changed, 328 insertions(+), 331 deletions(-) delete mode 100644 fomm_loader.py rename articulate_inference.py => inference_articulate.py (100%) rename fomm_inference.py => inference_fomm.py (85%) create mode 100644 inference_partswap.py rename spline_inference.py => inference_spline.py (99%) delete mode 100644 partswap_inference.py delete mode 100644 partswap_loader.py delete mode 100644 partswap_model.py diff --git a/README.md b/README.md index a62b4d1..2595756 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Now supports: 1. Face Swapping using [Motion Supervised co-part Segmentation](https://github.com/AliaksandrSiarohin/motion-cosegmentation): 2. [Motion Representations for Articulated Animation](https://github.com/snap-research/articulated-animation) 3. [Thin-Plate Spline Motion Model for Image Animation](https://github.com/yoyo-nb/thin-plate-spline-motion-model) +4. [Learning Motion Refinement for Unsupervised Face Animation](https://github.com/JialeTao/MRFA/) More will come soon @@ -69,6 +70,9 @@ Doesn't need any * `avd`: similar to `relative`, may yield better but more "jittery/jumpy" result * `find_best_frame`: Same as FOMM + +### MRFA + ## Installation 1. Clone the repo to `ComfyUI/custom_nodes/` @@ -129,3 +133,5 @@ resnet18-5c106cde.pth For **Articulate**, download the model from [Pre-trained checkpoints](https://github.com/snap-research/articulated-animation?tab=readme-ov-file#pre-trained-checkpoints) section and place it here: `articulate_module/models/vox256.pth` For **Spline**, download the model from [Pre-trained models](https://github.com/yoyo-nb/thin-plate-spline-motion-model?tab=readme-ov-file#pre-trained-models) section and place it here: `spline_module/models/vox.pth.tar`. To use `find_best_frame`, install `face-alignment`. + +For **MRFA**, download the model from [Pretrained models](https://github.com/JialeTao/MRFA/?tab=readme-ov-file#pretrained-models) section and place it here: `mrfa_module/models/` \ No newline at end of file diff --git a/fomm_loader.py b/fomm_loader.py deleted file mode 100644 index b100543..0000000 --- a/fomm_loader.py +++ /dev/null @@ -1,35 +0,0 @@ -import yaml -import torch - -from .modules.generator import OcclusionAwareGenerator -from .modules.keypoint_detector import KPDetector - -def load_checkpoint( - config_path: str, checkpoint_path: str, cpu=False -) -> tuple[OcclusionAwareGenerator, KPDetector]: - with open(config_path) as f: - config = yaml.full_load(f) - - generator = OcclusionAwareGenerator( - **config["model_params"]["generator_params"], - **config["model_params"]["common_params"], - ) - kp_detector = KPDetector( - **config["model_params"]["kp_detector_params"], - **config["model_params"]["common_params"], - ) - - if not cpu: - generator.cuda() - kp_detector.cuda() - checkpoint = torch.load(checkpoint_path) - else: - checkpoint = torch.load(checkpoint_path, map_location=torch.device("cpu")) - - generator.load_state_dict(checkpoint["generator"]) - kp_detector.load_state_dict(checkpoint["kp_detector"]) - - generator.eval() - kp_detector.eval() - - return generator, kp_detector \ No newline at end of file diff --git a/articulate_inference.py b/inference_articulate.py similarity index 100% rename from articulate_inference.py rename to inference_articulate.py diff --git a/fomm_inference.py b/inference_fomm.py similarity index 85% rename from fomm_inference.py rename to inference_fomm.py index 2665374..150d6c3 100644 --- a/fomm_inference.py +++ b/inference_fomm.py @@ -1,11 +1,12 @@ -import torch import numpy as np +import torch +import yaml from comfy.utils import ProgressBar -from tqdm import tqdm from scipy.spatial import ConvexHull +from tqdm import tqdm + from .modules.generator import OcclusionAwareGenerator from .modules.keypoint_detector import KPDetector - from .utils import tensor_to_numpy try: @@ -24,6 +25,7 @@ def normalize_kp_best_frame(kp): kp[:, :2] = kp[:, :2] / area return kp + def normalize_kp( kp_source, kp_driving, @@ -55,11 +57,12 @@ def normalize_kp( return kp_new + def find_best_frame(source_image, driving_video: list, cpu=False) -> int: if not face_alignment_available: print("Face Alignment module were not found. Best frame set to 0.") return 0 - + print("Initializing Face Alignment...") fa = face_alignment.FaceAlignment( face_alignment.LandmarksType.TWO_D, @@ -93,6 +96,7 @@ def find_best_frame(source_image, driving_video: list, cpu=False) -> int: return frame_num + def inference( source_image, driving_video: list, @@ -182,4 +186,35 @@ def inference_best_frame( predictions_first = predictions_first[::-1] # Reverse the first half back predictions.extend(predictions_first) predictions.extend(predictions_second) - return predictions \ No newline at end of file + return predictions + + +def load_checkpoint( + config_path: str, checkpoint_path: str, cpu=False +) -> tuple[OcclusionAwareGenerator, KPDetector]: + with open(config_path) as f: + config = yaml.full_load(f) + + generator = OcclusionAwareGenerator( + **config["model_params"]["generator_params"], + **config["model_params"]["common_params"], + ) + kp_detector = KPDetector( + **config["model_params"]["kp_detector_params"], + **config["model_params"]["common_params"], + ) + + if not cpu: + generator.cuda() + kp_detector.cuda() + checkpoint = torch.load(checkpoint_path) + else: + checkpoint = torch.load(checkpoint_path, map_location=torch.device("cpu")) + + generator.load_state_dict(checkpoint["generator"]) + kp_detector.load_state_dict(checkpoint["kp_detector"]) + + generator.eval() + kp_detector.eval() + + return generator, kp_detector diff --git a/inference_partswap.py b/inference_partswap.py new file mode 100644 index 0000000..08f7150 --- /dev/null +++ b/inference_partswap.py @@ -0,0 +1,274 @@ +import numpy as np +import torch +import torch.nn.functional as F +import yaml +from comfy.utils import ProgressBar +from tqdm import tqdm + +from .modules.dense_motion import DenseMotionNetwork +from .modules.reconstruction_module import ReconstructionModule +from .modules.segmentation_module import SegmentationModule +from .modules.util import AntiAliasInterpolation2d +from .sync_batchnorm.replicate import DataParallelWithCallback + + +def face_parse_seg( + source_image, face_parser_model, in_size=(512, 512), out_size=(64, 64) +): + seg = F.interpolate(source_image, size=in_size) + seg = (seg - face_parser_model.mean) / face_parser_model.std + seg = torch.softmax(face_parser_model(seg)[0], dim=1) + seg = F.interpolate(seg, size=out_size) + return seg + + +class PartSwapGenerator(ReconstructionModule): + def __init__(self, blend_scale=1, first_order_motion_model=False, **kwargs): + super(PartSwapGenerator, self).__init__(**kwargs) + if blend_scale == 1: + self.blend_downsample = lambda x: x + else: + self.blend_downsample = AntiAliasInterpolation2d(1, blend_scale) + + if first_order_motion_model: + self.dense_motion_network = DenseMotionNetwork( + block_expansion=64, + num_blocks=5, + max_features=1024, + num_kp=10, + num_channels=3, + estimate_occlusion_map=True, + scale_factor=0.25, + ) + else: + self.dense_motion_network = None + + def forward( + self, + source_image, + target_image, + seg_target, + seg_source, + blend_mask, + use_source_segmentation=False, + ): + # Encoding of source image + enc_source = self.first(source_image) + for i in range(len(self.down_blocks)): + enc_source = self.down_blocks[i](enc_source) + + # Encoding of target image + enc_target = self.first(target_image) + for i in range(len(self.down_blocks)): + enc_target = self.down_blocks[i](enc_target) + + output_dict = {} + # Compute flow field for source image + if self.dense_motion_network is None: + segment_motions = self.segment_motion(seg_target, seg_source) + segment_motions = segment_motions.permute(0, 1, 4, 2, 3) + mask = seg_target["segmentation"].unsqueeze(2) + deformation = (segment_motions * mask).sum(dim=1) + deformation = deformation.permute(0, 2, 3, 1) + else: + motion = self.dense_motion_network.forward_partswap( + source_image=source_image, seg_target=seg_target, seg_source=seg_source + ) + deformation = motion["deformation"] + + # Deform source encoding according to the motion + enc_source = self.deform_input(enc_source, deformation) + + if self.estimate_visibility: + if self.dense_motion_network is None: + visibility = seg_source["segmentation"][:, 1:].sum( + dim=1, keepdim=True + ) * ( + 1 + - seg_target["segmentation"][:, 1:] + .sum(dim=1, keepdim=True) + .detach() + ) + visibility = 1 - visibility + else: + visibility = motion["visibility"] + + if ( + enc_source.shape[2] != visibility.shape[2] + or enc_source.shape[3] != visibility.shape[3] + ): + visibility = F.interpolate( + visibility, size=enc_source.shape[2:], mode="bilinear" + ) + enc_source = enc_source * visibility + + blend_mask = self.blend_downsample(blend_mask) + # If source segmentation is provided use it should be deformed before blending + if use_source_segmentation: + blend_mask = self.deform_input(blend_mask, deformation) + + out = enc_target * (1 - blend_mask) + enc_source * blend_mask + + out = self.bottleneck(out) + + for i in range(len(self.up_blocks)): + out = self.up_blocks[i](out) + + out = self.final(out) + out = F.sigmoid(out) + + output_dict["prediction"] = out + + return output_dict + + +def partswap_inference( + swap_indices: list[int], + source_image, + target_video, + reconstruction_module: PartSwapGenerator, + segmentation_module: SegmentationModule, + use_source_seg: bool, + face_parser_model=None, + hard_edges=False, + cpu=False, +) -> list: + with torch.no_grad(): + predictions = [] + seg_targets = [] + if not cpu: + source_image = source_image.cuda() + target_video = target_video.cuda() + print("Using GPU") + + seg_source = segmentation_module(source_image) + # print(f"{seg_source['segmentation'].shape}") + + num_frames = target_video.shape[2] + pbar = ProgressBar(num_frames) + for frame_idx in tqdm(range(num_frames)): + target_frame = target_video[:, :, frame_idx] + seg_target = segmentation_module(target_frame) + seg_targets.append(seg_target) + + if face_parser_model is not None: + blend_mask = face_parse_seg( + source_image if use_source_seg else target_frame, + face_parser_model, + (512, 512), + (64, 64), + ) + + else: + blend_mask = ( + seg_source["segmentation"] + if use_source_seg + else seg_target["segmentation"] + ) + + blend_mask = blend_mask[:, swap_indices].sum(dim=1, keepdim=True) + if hard_edges: + blend_mask = (blend_mask > 0.5).type(blend_mask.type()) + # print(f"{blend_mask.shape=}") + out = reconstruction_module( + source_image, + target_frame, + seg_source=seg_source, + seg_target=seg_target, + blend_mask=blend_mask, + use_source_segmentation=use_source_seg, + ) + + predictions.append( + np.transpose(out["prediction"].data.cpu().numpy(), [0, 2, 3, 1])[0] + ) + + pbar.update_absolute(frame_idx, num_frames) + + return seg_source, seg_targets, predictions + + +def partial_state_dict_load(module, state_dict): + own_state = module.state_dict() + for name, param in state_dict.items(): + if name not in own_state: + continue + + if isinstance(param, torch.nn.Parameter): + # backwards compatibility for serialized parameters + param = param.data + own_state[name].copy_(param) + + +def load_reconstruction_module(module, checkpoint): + if "generator" in checkpoint: + partial_state_dict_load(module, checkpoint["generator"]) + else: + module.load_state_dict(checkpoint["reconstruction_module"]) + + +def load_segmentation_module(module, checkpoint): + if "kp_detector" in checkpoint: + partial_state_dict_load(module, checkpoint["kp_detector"]) + module.state_dict()["affine.weight"].copy_( + checkpoint["kp_detector"]["jacobian.weight"] + ) + module.state_dict()["affine.bias"].copy_( + checkpoint["kp_detector"]["jacobian.bias"] + ) + module.state_dict()["shift.weight"].copy_( + checkpoint["kp_detector"]["kp.weight"] + ) + module.state_dict()["shift.bias"].copy_(checkpoint["kp_detector"]["kp.bias"]) + if "semantic_seg.weight" in checkpoint["kp_detector"]: + module.state_dict()["segmentation.weight"].copy_( + checkpoint["kp_detector"]["semantic_seg.weight"] + ) + module.state_dict()["segmentation.bias"].copy_( + checkpoint["kp_detector"]["semantic_seg.bias"] + ) + else: + print("Segmentation part initialized at random.") + else: + module.load_state_dict(checkpoint["segmentation_module"]) + + +def load_partswap_checkpoint( + config_path: str, + checkpoint_path: str, + blend_scale: float, + use_fomm: bool, + cpu=False, +): + with open(config_path) as f: + config = yaml.full_load(f) + + reconstruction_module = PartSwapGenerator( + blend_scale=blend_scale, + first_order_motion_model=use_fomm, + **config["model_params"]["reconstruction_module_params"], + **config["model_params"]["common_params"], + ) + + segmentation_module = SegmentationModule( + **config["model_params"]["segmentation_module_params"], + **config["model_params"]["common_params"], + ) + + if cpu: + checkpoint = torch.load(checkpoint_path, map_location=torch.device("cpu")) + else: + segmentation_module.cuda() + checkpoint = torch.load(checkpoint_path) + + load_reconstruction_module(reconstruction_module, checkpoint) + load_segmentation_module(segmentation_module, checkpoint) + + if not cpu: + reconstruction_module = DataParallelWithCallback(reconstruction_module) + segmentation_module = DataParallelWithCallback(segmentation_module) + + reconstruction_module.eval() + segmentation_module.eval() + + return reconstruction_module, segmentation_module diff --git a/spline_inference.py b/inference_spline.py similarity index 99% rename from spline_inference.py rename to inference_spline.py index c432318..80bec5f 100644 --- a/spline_inference.py +++ b/inference_spline.py @@ -5,7 +5,7 @@ import yaml import tqdm from comfy.utils import ProgressBar -from .fomm_inference import find_best_frame +from .inference_fomm import find_best_frame from .spline_module.avd_network import AVDNetwork from .spline_module.dense_motion import DenseMotionNetwork diff --git a/partswap_inference.py b/partswap_inference.py deleted file mode 100644 index 012575f..0000000 --- a/partswap_inference.py +++ /dev/null @@ -1,84 +0,0 @@ -import numpy as np -import torch -import torch.nn.functional as F -from tqdm import tqdm - -from comfy.utils import ProgressBar -from .modules.segmentation_module import SegmentationModule -from .partswap_model import PartSwapGenerator - - -def face_parse_seg( - source_image, face_parser_model, in_size=(512, 512), out_size=(64, 64) -): - seg = F.interpolate(source_image, size=in_size) - seg = (seg - face_parser_model.mean) / face_parser_model.std - seg = torch.softmax(face_parser_model(seg)[0], dim=1) - seg = F.interpolate(seg, size=out_size) - return seg - - -def partswap_inference( - swap_indices: list[int], - source_image, - target_video, - reconstruction_module: PartSwapGenerator, - segmentation_module: SegmentationModule, - use_source_seg: bool, - face_parser_model=None, - hard_edges=False, - cpu=False, -) -> list: - with torch.no_grad(): - predictions = [] - seg_targets = [] - if not cpu: - source_image = source_image.cuda() - target_video = target_video.cuda() - print("Using GPU") - - seg_source = segmentation_module(source_image) - # print(f"{seg_source['segmentation'].shape}") - - num_frames = target_video.shape[2] - pbar = ProgressBar(num_frames) - for frame_idx in tqdm(range(num_frames)): - target_frame = target_video[:, :, frame_idx] - seg_target = segmentation_module(target_frame) - seg_targets.append(seg_target) - - if face_parser_model is not None: - blend_mask = face_parse_seg( - source_image if use_source_seg else target_frame, - face_parser_model, - (512, 512), - (64, 64), - ) - - else: - blend_mask = ( - seg_source["segmentation"] - if use_source_seg - else seg_target["segmentation"] - ) - - blend_mask = blend_mask[:, swap_indices].sum(dim=1, keepdim=True) - if hard_edges: - blend_mask = (blend_mask > 0.5).type(blend_mask.type()) - print(f"{blend_mask.shape=}") - out = reconstruction_module( - source_image, - target_frame, - seg_source=seg_source, - seg_target=seg_target, - blend_mask=blend_mask, - use_source_segmentation=use_source_seg, - ) - - predictions.append( - np.transpose(out["prediction"].data.cpu().numpy(), [0, 2, 3, 1])[0] - ) - - pbar.update_absolute(frame_idx, num_frames) - - return seg_source, seg_targets, predictions diff --git a/partswap_loader.py b/partswap_loader.py deleted file mode 100644 index 55c599c..0000000 --- a/partswap_loader.py +++ /dev/null @@ -1,92 +0,0 @@ -import torch -import yaml - -from .modules.segmentation_module import SegmentationModule -from .partswap_model import PartSwapGenerator -from .sync_batchnorm.replicate import DataParallelWithCallback - - -def partial_state_dict_load(module, state_dict): - own_state = module.state_dict() - for name, param in state_dict.items(): - if name not in own_state: - continue - - if isinstance(param, torch.nn.Parameter): - # backwards compatibility for serialized parameters - param = param.data - own_state[name].copy_(param) - - -def load_reconstruction_module(module, checkpoint): - if "generator" in checkpoint: - partial_state_dict_load(module, checkpoint["generator"]) - else: - module.load_state_dict(checkpoint["reconstruction_module"]) - - -def load_segmentation_module(module, checkpoint): - if "kp_detector" in checkpoint: - partial_state_dict_load(module, checkpoint["kp_detector"]) - module.state_dict()["affine.weight"].copy_( - checkpoint["kp_detector"]["jacobian.weight"] - ) - module.state_dict()["affine.bias"].copy_( - checkpoint["kp_detector"]["jacobian.bias"] - ) - module.state_dict()["shift.weight"].copy_( - checkpoint["kp_detector"]["kp.weight"] - ) - module.state_dict()["shift.bias"].copy_(checkpoint["kp_detector"]["kp.bias"]) - if "semantic_seg.weight" in checkpoint["kp_detector"]: - module.state_dict()["segmentation.weight"].copy_( - checkpoint["kp_detector"]["semantic_seg.weight"] - ) - module.state_dict()["segmentation.bias"].copy_( - checkpoint["kp_detector"]["semantic_seg.bias"] - ) - else: - print("Segmentation part initialized at random.") - else: - module.load_state_dict(checkpoint["segmentation_module"]) - - -def load_partswap_checkpoint( - config_path: str, - checkpoint_path: str, - blend_scale: float, - use_fomm: bool, - cpu=False, -): - with open(config_path) as f: - config = yaml.full_load(f) - - reconstruction_module = PartSwapGenerator( - blend_scale=blend_scale, - first_order_motion_model=use_fomm, - **config["model_params"]["reconstruction_module_params"], - **config["model_params"]["common_params"], - ) - - segmentation_module = SegmentationModule( - **config["model_params"]["segmentation_module_params"], - **config["model_params"]["common_params"], - ) - - if cpu: - checkpoint = torch.load(checkpoint_path, map_location=torch.device("cpu")) - else: - segmentation_module.cuda() - checkpoint = torch.load(checkpoint_path) - - load_reconstruction_module(reconstruction_module, checkpoint) - load_segmentation_module(segmentation_module, checkpoint) - - if not cpu: - reconstruction_module = DataParallelWithCallback(reconstruction_module) - segmentation_module = DataParallelWithCallback(segmentation_module) - - reconstruction_module.eval() - segmentation_module.eval() - - return reconstruction_module, segmentation_module diff --git a/partswap_model.py b/partswap_model.py deleted file mode 100644 index ee55dea..0000000 --- a/partswap_model.py +++ /dev/null @@ -1,105 +0,0 @@ -import torch.nn.functional as F - -from .modules.dense_motion import DenseMotionNetwork -from .modules.reconstruction_module import ReconstructionModule -from .modules.util import AntiAliasInterpolation2d - - -class PartSwapGenerator(ReconstructionModule): - def __init__(self, blend_scale=1, first_order_motion_model=False, **kwargs): - super(PartSwapGenerator, self).__init__(**kwargs) - if blend_scale == 1: - self.blend_downsample = lambda x: x - else: - self.blend_downsample = AntiAliasInterpolation2d(1, blend_scale) - - if first_order_motion_model: - self.dense_motion_network = DenseMotionNetwork( - block_expansion=64, - num_blocks=5, - max_features=1024, - num_kp=10, - num_channels=3, - estimate_occlusion_map=True, - scale_factor=0.25, - ) - else: - self.dense_motion_network = None - - def forward( - self, - source_image, - target_image, - seg_target, - seg_source, - blend_mask, - use_source_segmentation=False, - ): - # Encoding of source image - enc_source = self.first(source_image) - for i in range(len(self.down_blocks)): - enc_source = self.down_blocks[i](enc_source) - - # Encoding of target image - enc_target = self.first(target_image) - for i in range(len(self.down_blocks)): - enc_target = self.down_blocks[i](enc_target) - - output_dict = {} - # Compute flow field for source image - if self.dense_motion_network is None: - segment_motions = self.segment_motion(seg_target, seg_source) - segment_motions = segment_motions.permute(0, 1, 4, 2, 3) - mask = seg_target["segmentation"].unsqueeze(2) - deformation = (segment_motions * mask).sum(dim=1) - deformation = deformation.permute(0, 2, 3, 1) - else: - motion = self.dense_motion_network.forward_partswap( - source_image=source_image, seg_target=seg_target, seg_source=seg_source - ) - deformation = motion["deformation"] - - # Deform source encoding according to the motion - enc_source = self.deform_input(enc_source, deformation) - - if self.estimate_visibility: - if self.dense_motion_network is None: - visibility = seg_source["segmentation"][:, 1:].sum( - dim=1, keepdim=True - ) * ( - 1 - - seg_target["segmentation"][:, 1:] - .sum(dim=1, keepdim=True) - .detach() - ) - visibility = 1 - visibility - else: - visibility = motion["visibility"] - - if ( - enc_source.shape[2] != visibility.shape[2] - or enc_source.shape[3] != visibility.shape[3] - ): - visibility = F.interpolate( - visibility, size=enc_source.shape[2:], mode="bilinear" - ) - enc_source = enc_source * visibility - - blend_mask = self.blend_downsample(blend_mask) - # If source segmentation is provided use it should be deformed before blending - if use_source_segmentation: - blend_mask = self.deform_input(blend_mask, deformation) - - out = enc_target * (1 - blend_mask) + enc_source * blend_mask - - out = self.bottleneck(out) - - for i in range(len(self.up_blocks)): - out = self.up_blocks[i](out) - - out = self.final(out) - out = F.sigmoid(out) - - output_dict["prediction"] = out - - return output_dict diff --git a/pyproject.toml b/pyproject.toml index c90ad79..cfa910a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "comfyui-firstordermm" -description = "A ComfyUI-native node to run First Order Motion Model for Image Animation. [a/https://github.com/AliaksandrSiarohin/first-order-model](https://github.com/AliaksandrSiarohin/first-order-model)" +description = "ComfyUI-native nodes to run First Order Motion Model for Image Animation and its non-diffusion-based successors. [a/https://github.com/AliaksandrSiarohin/first-order-model](https://github.com/AliaksandrSiarohin/first-order-model)" version = "1.0.3" license = { file = "LICENSE" } dependencies = ["numpy", "torch", "scipy", "yaml", "matplotlib"] diff --git a/requirements.txt b/requirements.txt index fe18382..5eecb23 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ numpy torch scipy -yaml +pyyaml matplotlib \ No newline at end of file diff --git a/run.py b/run.py index 42c7645..340dae2 100644 --- a/run.py +++ b/run.py @@ -2,14 +2,13 @@ @author: Fuou Marinas @title: ComfyUI-FirstOrderMM @nickname: FOMM -@description: Run First Order Motion Model for Image Animation in ComfyUI. +@description: ComfyUI-native nodes to run First Order Motion Model for Image Animation and its non-diffusion-based successors. """ from pathlib import Path import torch -from .articulate_inference import articulate_inference from .constants import ( ARTICULATE_CFG_PATH, ARTICULATE_MODEL_PATH, @@ -28,12 +27,11 @@ from .constants import ( supported_model_names, ) from .face_parsing.face_parsing_loader import load_face_parser_model -from .fomm_inference import inference, inference_best_frame -from .fomm_loader import load_checkpoint -from .partswap_inference import partswap_inference -from .partswap_loader import load_partswap_checkpoint +from .inference_articulate import articulate_inference +from .inference_fomm import inference, inference_best_frame, load_checkpoint +from .inference_partswap import load_partswap_checkpoint, partswap_inference +from .inference_spline import spline_inference from .seg_viz import visualize_frame -from .spline_inference import spline_inference from .utils import ( build_seg_arguments, get_config_path, From 385effc52674bce14f58fd9860f24dc7e72e4210 Mon Sep 17 00:00:00 2001 From: FuouM Date: Sat, 10 Aug 2024 19:38:18 +0700 Subject: [PATCH 2/4] rename modules folder --- .gitignore | 4 ++-- constants.py | 8 ++++---- inference_articulate.py | 8 ++++---- inference_spline.py | 8 ++++---- {articulate_module => module_articulate}/avd_network.py | 0 {articulate_module => module_articulate}/batchnorm.py | 0 .../bg_motion_predictor.py | 0 {articulate_module => module_articulate}/comm.py | 0 .../config/taichi256.yaml | 0 .../config/ted-youtube384.yaml | 0 .../config/ted384.yaml | 0 .../config/vox256.yaml | 0 {articulate_module => module_articulate}/generator.py | 0 {articulate_module => module_articulate}/models/.gitkeep | 0 .../pixelwise_flow_predictor.py | 0 .../region_predictor.py | 0 {articulate_module => module_articulate}/util.py | 0 {articulate_module => module_articulate}/visualizer.py | 0 {spline_module => module_spline}/avd_network.py | 0 {spline_module => module_spline}/bg_motion_predictor.py | 0 {spline_module => module_spline}/config/mgif-256.yaml | 0 {spline_module => module_spline}/config/taichi-256.yaml | 0 {spline_module => module_spline}/config/ted-384.yaml | 0 {spline_module => module_spline}/config/vox-256.yaml | 0 {spline_module => module_spline}/dense_motion.py | 0 {spline_module => module_spline}/inpainting_network.py | 0 {spline_module => module_spline}/keypoint_detector.py | 0 {spline_module => module_spline}/model.py | 0 {spline_module => module_spline}/models/.gitkeep | 0 {spline_module => module_spline}/util.py | 0 30 files changed, 14 insertions(+), 14 deletions(-) rename {articulate_module => module_articulate}/avd_network.py (100%) rename {articulate_module => module_articulate}/batchnorm.py (100%) rename {articulate_module => module_articulate}/bg_motion_predictor.py (100%) rename {articulate_module => module_articulate}/comm.py (100%) rename {articulate_module => module_articulate}/config/taichi256.yaml (100%) rename {articulate_module => module_articulate}/config/ted-youtube384.yaml (100%) rename {articulate_module => module_articulate}/config/ted384.yaml (100%) rename {articulate_module => module_articulate}/config/vox256.yaml (100%) rename {articulate_module => module_articulate}/generator.py (100%) rename {articulate_module => module_articulate}/models/.gitkeep (100%) rename {articulate_module => module_articulate}/pixelwise_flow_predictor.py (100%) rename {articulate_module => module_articulate}/region_predictor.py (100%) rename {articulate_module => module_articulate}/util.py (100%) rename {articulate_module => module_articulate}/visualizer.py (100%) rename {spline_module => module_spline}/avd_network.py (100%) rename {spline_module => module_spline}/bg_motion_predictor.py (100%) rename {spline_module => module_spline}/config/mgif-256.yaml (100%) rename {spline_module => module_spline}/config/taichi-256.yaml (100%) rename {spline_module => module_spline}/config/ted-384.yaml (100%) rename {spline_module => module_spline}/config/vox-256.yaml (100%) rename {spline_module => module_spline}/dense_motion.py (100%) rename {spline_module => module_spline}/inpainting_network.py (100%) rename {spline_module => module_spline}/keypoint_detector.py (100%) rename {spline_module => module_spline}/model.py (100%) rename {spline_module => module_spline}/models/.gitkeep (100%) rename {spline_module => module_spline}/util.py (100%) diff --git a/.gitignore b/.gitignore index 5c409c1..1a2d0fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Stuff -articulate_module/models/*.pth -spline_module/models/*.tar +module_articulate/models/*.pth +module_spline/models/*.tar checkpoints/* !checkpoints/place_checkpoints_here.txt diff --git a/constants.py b/constants.py index 9876432..93b184b 100644 --- a/constants.py +++ b/constants.py @@ -99,11 +99,11 @@ seg_model_dict = { "vox-adv": seg10_segment_names, } -ARTICULATE_MODEL_PATH = "articulate_module/models/vox256.pth" -ARTICULATE_CFG_PATH = "articulate_module/config/vox256.yaml" +ARTICULATE_MODEL_PATH = "module_articulate/models/vox256.pth" +ARTICULATE_CFG_PATH = "module_articulate/config/vox256.yaml" SPLINE_MODES = ['relative', 'standard', 'avd'] SPLINE_DEFAULT = 'relative' -SPLINE_MODEL_PATH = "spline_module/models/vox.pth.tar" -SPLINE_CFG_PATH = "spline_module/config/vox-256.yaml" \ No newline at end of file +SPLINE_MODEL_PATH = "module_spline/models/vox.pth.tar" +SPLINE_CFG_PATH = "module_spline/config/vox-256.yaml" \ No newline at end of file diff --git a/inference_articulate.py b/inference_articulate.py index 0d2c1a4..c357546 100644 --- a/inference_articulate.py +++ b/inference_articulate.py @@ -5,10 +5,10 @@ import yaml from comfy.utils import ProgressBar from scipy.spatial import ConvexHull -from .articulate_module.avd_network import AVDNetwork -from .articulate_module.bg_motion_predictor import BGMotionPredictor -from .articulate_module.generator import Generator -from .articulate_module.region_predictor import RegionPredictor +from .module_articulate.avd_network import AVDNetwork +from .module_articulate.bg_motion_predictor import BGMotionPredictor +from .module_articulate.generator import Generator +from .module_articulate.region_predictor import RegionPredictor from .sync_batchnorm.replicate import DataParallelWithCallback diff --git a/inference_spline.py b/inference_spline.py index 80bec5f..d2595e9 100644 --- a/inference_spline.py +++ b/inference_spline.py @@ -7,10 +7,10 @@ from comfy.utils import ProgressBar from .inference_fomm import find_best_frame -from .spline_module.avd_network import AVDNetwork -from .spline_module.dense_motion import DenseMotionNetwork -from .spline_module.keypoint_detector import KPDetector -from .spline_module.inpainting_network import InpaintingNetwork +from .module_spline.avd_network import AVDNetwork +from .module_spline.dense_motion import DenseMotionNetwork +from .module_spline.keypoint_detector import KPDetector +from .module_spline.inpainting_network import InpaintingNetwork def spline_inference( diff --git a/articulate_module/avd_network.py b/module_articulate/avd_network.py similarity index 100% rename from articulate_module/avd_network.py rename to module_articulate/avd_network.py diff --git a/articulate_module/batchnorm.py b/module_articulate/batchnorm.py similarity index 100% rename from articulate_module/batchnorm.py rename to module_articulate/batchnorm.py diff --git a/articulate_module/bg_motion_predictor.py b/module_articulate/bg_motion_predictor.py similarity index 100% rename from articulate_module/bg_motion_predictor.py rename to module_articulate/bg_motion_predictor.py diff --git a/articulate_module/comm.py b/module_articulate/comm.py similarity index 100% rename from articulate_module/comm.py rename to module_articulate/comm.py diff --git a/articulate_module/config/taichi256.yaml b/module_articulate/config/taichi256.yaml similarity index 100% rename from articulate_module/config/taichi256.yaml rename to module_articulate/config/taichi256.yaml diff --git a/articulate_module/config/ted-youtube384.yaml b/module_articulate/config/ted-youtube384.yaml similarity index 100% rename from articulate_module/config/ted-youtube384.yaml rename to module_articulate/config/ted-youtube384.yaml diff --git a/articulate_module/config/ted384.yaml b/module_articulate/config/ted384.yaml similarity index 100% rename from articulate_module/config/ted384.yaml rename to module_articulate/config/ted384.yaml diff --git a/articulate_module/config/vox256.yaml b/module_articulate/config/vox256.yaml similarity index 100% rename from articulate_module/config/vox256.yaml rename to module_articulate/config/vox256.yaml diff --git a/articulate_module/generator.py b/module_articulate/generator.py similarity index 100% rename from articulate_module/generator.py rename to module_articulate/generator.py diff --git a/articulate_module/models/.gitkeep b/module_articulate/models/.gitkeep similarity index 100% rename from articulate_module/models/.gitkeep rename to module_articulate/models/.gitkeep diff --git a/articulate_module/pixelwise_flow_predictor.py b/module_articulate/pixelwise_flow_predictor.py similarity index 100% rename from articulate_module/pixelwise_flow_predictor.py rename to module_articulate/pixelwise_flow_predictor.py diff --git a/articulate_module/region_predictor.py b/module_articulate/region_predictor.py similarity index 100% rename from articulate_module/region_predictor.py rename to module_articulate/region_predictor.py diff --git a/articulate_module/util.py b/module_articulate/util.py similarity index 100% rename from articulate_module/util.py rename to module_articulate/util.py diff --git a/articulate_module/visualizer.py b/module_articulate/visualizer.py similarity index 100% rename from articulate_module/visualizer.py rename to module_articulate/visualizer.py diff --git a/spline_module/avd_network.py b/module_spline/avd_network.py similarity index 100% rename from spline_module/avd_network.py rename to module_spline/avd_network.py diff --git a/spline_module/bg_motion_predictor.py b/module_spline/bg_motion_predictor.py similarity index 100% rename from spline_module/bg_motion_predictor.py rename to module_spline/bg_motion_predictor.py diff --git a/spline_module/config/mgif-256.yaml b/module_spline/config/mgif-256.yaml similarity index 100% rename from spline_module/config/mgif-256.yaml rename to module_spline/config/mgif-256.yaml diff --git a/spline_module/config/taichi-256.yaml b/module_spline/config/taichi-256.yaml similarity index 100% rename from spline_module/config/taichi-256.yaml rename to module_spline/config/taichi-256.yaml diff --git a/spline_module/config/ted-384.yaml b/module_spline/config/ted-384.yaml similarity index 100% rename from spline_module/config/ted-384.yaml rename to module_spline/config/ted-384.yaml diff --git a/spline_module/config/vox-256.yaml b/module_spline/config/vox-256.yaml similarity index 100% rename from spline_module/config/vox-256.yaml rename to module_spline/config/vox-256.yaml diff --git a/spline_module/dense_motion.py b/module_spline/dense_motion.py similarity index 100% rename from spline_module/dense_motion.py rename to module_spline/dense_motion.py diff --git a/spline_module/inpainting_network.py b/module_spline/inpainting_network.py similarity index 100% rename from spline_module/inpainting_network.py rename to module_spline/inpainting_network.py diff --git a/spline_module/keypoint_detector.py b/module_spline/keypoint_detector.py similarity index 100% rename from spline_module/keypoint_detector.py rename to module_spline/keypoint_detector.py diff --git a/spline_module/model.py b/module_spline/model.py similarity index 100% rename from spline_module/model.py rename to module_spline/model.py diff --git a/spline_module/models/.gitkeep b/module_spline/models/.gitkeep similarity index 100% rename from spline_module/models/.gitkeep rename to module_spline/models/.gitkeep diff --git a/spline_module/util.py b/module_spline/util.py similarity index 100% rename from spline_module/util.py rename to module_spline/util.py From 32a763e065e9b34e6739f73869aa21f8089e30d2 Mon Sep 17 00:00:00 2001 From: FuouM Date: Sat, 10 Aug 2024 21:47:31 +0700 Subject: [PATCH 3/4] add mrfa node --- .gitignore | 1 + README.md | 16 +- __init__.py | 5 +- constants.py | 14 +- inference_mrfa.py | 131 ++ module_mrfa/bg_motion_predictor.py | 29 + module_mrfa/configs/celebvhq.yaml | 186 +++ module_mrfa/configs/vox1.yaml | 186 +++ module_mrfa/dense_motion.py | 435 +++++++ module_mrfa/generator.py | 88 ++ module_mrfa/kp_detector.py | 213 ++++ module_mrfa/model.py | 294 +++++ module_mrfa/models/.gitkeep | 0 module_mrfa/raft.py | 456 +++++++ module_mrfa/transformer/hr_base.py | 727 +++++++++++ module_mrfa/transformer/pose_tokenpose_b.py | 61 + module_mrfa/transformer/tokenpose_base.py | 1236 +++++++++++++++++++ module_mrfa/util.py | 503 ++++++++ pyproject.toml | 4 +- requirements.txt | 4 +- run.py | 100 +- workflows/MRFA.json | 423 +++++++ workflows/workflow_mrfa.png | Bin 0 -> 258689 bytes 23 files changed, 5105 insertions(+), 7 deletions(-) create mode 100644 inference_mrfa.py create mode 100644 module_mrfa/bg_motion_predictor.py create mode 100644 module_mrfa/configs/celebvhq.yaml create mode 100644 module_mrfa/configs/vox1.yaml create mode 100644 module_mrfa/dense_motion.py create mode 100644 module_mrfa/generator.py create mode 100644 module_mrfa/kp_detector.py create mode 100644 module_mrfa/model.py create mode 100644 module_mrfa/models/.gitkeep create mode 100644 module_mrfa/raft.py create mode 100644 module_mrfa/transformer/hr_base.py create mode 100644 module_mrfa/transformer/pose_tokenpose_b.py create mode 100644 module_mrfa/transformer/tokenpose_base.py create mode 100644 module_mrfa/util.py create mode 100644 workflows/MRFA.json create mode 100644 workflows/workflow_mrfa.png diff --git a/.gitignore b/.gitignore index 1a2d0fd..96c0f1a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ module_articulate/models/*.pth module_spline/models/*.tar +module_mrfa/models/*.pth checkpoints/* !checkpoints/place_checkpoints_here.txt diff --git a/README.md b/README.md index 2595756..f7ce99b 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,11 @@ https://github.com/user-attachments/assets/b2948efb-3b44-440b-bff2-dde7b95a9946 ![Workflow Spline](workflows/workflow_spline.png) +### MRFA + +[MRFA.json](workflows/MRFA.json) + +![Workflow MRFA](workflows/workflow_mrfa.png) ## Arguments @@ -73,6 +78,10 @@ Doesn't need any ### MRFA +* `model_name`: `celebvhq` or `vox`, which is trained on different datasets +* `use_relative`: Whether to use relative mode or not (absolute mode). Absolute mode is similar to FOMM's `adapt_movement_scale` set to False +* `relative_movement`, `relative_jacobian`, `adapt_movement_scale`: Same as FOMM + ## Installation 1. Clone the repo to `ComfyUI/custom_nodes/` @@ -134,4 +143,9 @@ For **Articulate**, download the model from [Pre-trained checkpoints](https://gi For **Spline**, download the model from [Pre-trained models](https://github.com/yoyo-nb/thin-plate-spline-motion-model?tab=readme-ov-file#pre-trained-models) section and place it here: `spline_module/models/vox.pth.tar`. To use `find_best_frame`, install `face-alignment`. -For **MRFA**, download the model from [Pretrained models](https://github.com/JialeTao/MRFA/?tab=readme-ov-file#pretrained-models) section and place it here: `mrfa_module/models/` \ No newline at end of file +For **MRFA**, download `MRFACheckpoints.tar.gz` from [Pretrained models](https://github.com/JialeTao/MRFA/?tab=readme-ov-file#pretrained-models) section and decompress it. Inside there should be two files. Place them here: + +``` +mrfa_module/models/celebvhq.pth +mrfa_module/models/vox.pth +``` \ No newline at end of file diff --git a/__init__.py b/__init__.py index 9edeec0..f59a68f 100644 --- a/__init__.py +++ b/__init__.py @@ -5,7 +5,8 @@ from .run import ( FOMM_Seg10Chooser, FOMM_Seg15Chooser, Articulate_Runner, - Spline_Runner + Spline_Runner, + MRFA_Runner ) NODE_CLASS_MAPPINGS = { @@ -16,6 +17,7 @@ NODE_CLASS_MAPPINGS = { "FOMM_Seg15Chooser": FOMM_Seg15Chooser, "Articulate_Runner": Articulate_Runner, "Spline_Runner": Spline_Runner, + "MRFA_Runner": MRFA_Runner, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -26,6 +28,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "FOMM_Seg15Chooser": "FOMM Seg15 Chooser", "Articulate_Runner": "Articulate Runner", "Spline_Runner": "Spline Runner", + "MRFA_Runner": "MRFA Runner", } diff --git a/constants.py b/constants.py index 93b184b..ad76d08 100644 --- a/constants.py +++ b/constants.py @@ -106,4 +106,16 @@ SPLINE_MODES = ['relative', 'standard', 'avd'] SPLINE_DEFAULT = 'relative' SPLINE_MODEL_PATH = "module_spline/models/vox.pth.tar" -SPLINE_CFG_PATH = "module_spline/config/vox-256.yaml" \ No newline at end of file +SPLINE_CFG_PATH = "module_spline/config/vox-256.yaml" + +MRFA_MODEL_NAMES = ["celebvhq", "vox"] +MRFA_MODEL_PATHS = { + "celebvhq": "module_mrfa/models/celebvhq.pth", + "vox": "module_mrfa/models/vox.pth", +} +MRFA_DEFAULT_MODEL = "celebvhq" + +MRFA_CFG_PATHS = { + "celebvhq": "module_mrfa/configs/celebvhq.yaml", + "vox": "module_mrfa/configs/vox1.yaml", +} \ No newline at end of file diff --git a/inference_mrfa.py b/inference_mrfa.py new file mode 100644 index 0000000..6215144 --- /dev/null +++ b/inference_mrfa.py @@ -0,0 +1,131 @@ +import numpy as np +import torch +import tqdm +import yaml +from comfy.utils import ProgressBar +from scipy.spatial import ConvexHull + +from .module_mrfa.model import MRFA +from .module_mrfa.util import AntiAliasInterpolation2d + + +def mrfa_inference( + source_image: torch.Tensor, + driving_video: torch.Tensor, + config_path: str, + checkpoint_path: str, + use_relative=True, + relative_movement=True, + relative_jacobian=True, + adapt_movement_scale=False, +): + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + with open(config_path) as f: + config = yaml.full_load(f) + + model = MRFA(config) + model = torch.nn.DataParallel(model) + + checkpoint = torch.load(checkpoint_path, map_location=device) + model.load_state_dict(checkpoint["model"], strict=False) + model = model.module + model.eval().to(device) + + kp_detector = model.encoder + dense_motion_network = model.dense_motion + decoder = model.decoder + down = AntiAliasInterpolation2d(3, 0.25).to(device) + + source = source_image.to(device) # 4D + driving = driving_video.to(device) # + + num_frames = driving.shape[2] + pbar = ProgressBar(num_frames) + + predictions = [] + with torch.no_grad(): + # visualizations = [] + + kp_source = kp_detector.forward(source) + kp_driving_initial = kp_detector.forward(driving[:, :, 0]) + + for frame_idx in tqdm.tqdm( + range(num_frames), + desc=f"Generating {'Relative' if use_relative else 'Absolute'} mode", + ): + driving_frame = driving[:, :, frame_idx] + if use_relative: + kp_driving = kp_detector.forward(driving_frame) + kp_norm = normalize_kp( + kp_source=kp_source, + kp_driving=kp_driving, + kp_driving_initial=kp_driving_initial, + use_relative_movement=relative_movement, + use_relative_jacobian=relative_jacobian, + adapt_movement_scale=adapt_movement_scale, + ) + dense_motion = dense_motion_network.forward( + source, kp_norm, kp_source, bg_param=None + ) + + if config["train_params"]["prior_model"] == "tpsm": + kp_s_value = kp_source["kp"].view(source.shape[0], -1, 5, 2).mean(2) + kp_d_value = kp_norm["kp"].view(driving.shape[0], -1, 5, 2).mean(2) + else: + kp_s_value = kp_source["kp"] + kp_d_value = kp_norm["kp"] + + out, _, _ = decoder.forward( + kp_s_value, + kp_d_value, + dense_motion, + img=down(source), + img_full=source, + ) + + # visualization = Visualizer(**config['visualizer_params']).visualize(source=source, driving=driving_frame, out=out) + # visualizations.append(visualization) + + else: + abs_inp = {"source": source, "driving": driving_frame} + out, _, _, _, _ = model.forward(abs_inp, is_train=False) + + prediction = out.data.cpu().numpy() + prediction = np.transpose(prediction, [0, 2, 3, 1]).squeeze(0) + predictions.append(prediction) + + pbar.update_absolute(frame_idx, num_frames) + + return predictions + + +def normalize_kp( + kp_source, + kp_driving, + kp_driving_initial, + adapt_movement_scale=False, + use_relative_movement=False, + use_relative_jacobian=False, +): + if adapt_movement_scale: + source_area = ConvexHull(kp_source["kp"][0].data.cpu().numpy()).volume + driving_area = ConvexHull(kp_driving_initial["kp"][0].data.cpu().numpy()).volume + adapt_movement_scale = np.sqrt(source_area) / np.sqrt(driving_area) + else: + adapt_movement_scale = 1 + + kp_new = {k: v for k, v in kp_driving.items()} + + if use_relative_movement: + kp_value_diff = kp_driving["kp"] - kp_driving_initial["kp"] + kp_value_diff *= adapt_movement_scale + kp_new["kp"] = kp_value_diff + kp_source["kp"] + + if use_relative_jacobian: + jacobian_diff = torch.matmul( + kp_driving["jacobian"], torch.inverse(kp_driving_initial["jacobian"]) + ) + kp_new["jacobian"] = torch.matmul(jacobian_diff, kp_source["jacobian"]) + + return kp_new diff --git a/module_mrfa/bg_motion_predictor.py b/module_mrfa/bg_motion_predictor.py new file mode 100644 index 0000000..272683c --- /dev/null +++ b/module_mrfa/bg_motion_predictor.py @@ -0,0 +1,29 @@ +import torch +from torch import nn +from torchvision import models + + +class BGMotionPredictor(nn.Module): + """ + Module for background estimation, return single transformation, parametrized as 3x3 matrix. The third row is [0 0 1] + """ + + def __init__(self): + super(BGMotionPredictor, self).__init__() + self.bg_encoder = models.resnet18(pretrained=False) + self.bg_encoder.conv1 = nn.Conv2d( + 6, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False + ) + num_features = self.bg_encoder.fc.in_features + self.bg_encoder.fc = nn.Linear(num_features, 6) + self.bg_encoder.fc.weight.data.zero_() + self.bg_encoder.fc.bias.data.copy_( + torch.tensor([1, 0, 0, 0, 1, 0], dtype=torch.float) + ) + + def forward(self, source_image, driving_image): + bs = source_image.shape[0] + out = torch.eye(3).unsqueeze(0).repeat(bs, 1, 1).type(source_image.type()) + prediction = self.bg_encoder(torch.cat([source_image, driving_image], dim=1)) + out[:, :2, :] = prediction.view(bs, 2, 3) + return out diff --git a/module_mrfa/configs/celebvhq.yaml b/module_mrfa/configs/celebvhq.yaml new file mode 100644 index 0000000..02cfa17 --- /dev/null +++ b/module_mrfa/configs/celebvhq.yaml @@ -0,0 +1,186 @@ +dataset_params: + root_dir: ./data/celebvhq256-png/ + split_len: 11 + frame_shape: [256, 256, 3] + id_sampling: True + pairs_list: + augmentation_params: + flip_param: + horizontal_flip: True + time_flip: True + jitter_param: + brightness: 0.1 + contrast: 0.1 + saturation: 0.1 + hue: 0.1 + +dense_motion: + block_expansion: 64 + max_features: 1024 + num_blocks: 5 + scale_factor: 0.25 + num_kp: 10 + num_channels: 3 + estimate_occlusion_map: True +fomm_kp_detector: + block_expansion: 32 + num_kp: 10 + num_channels: 3 + max_features: 1024 + num_blocks: 5 + temperature: 0.1 + scale_factor: 0.25 + estimate_jacobian: True + estimate_occlusion: False +tpsm_kp_detector: + num_tps: 10 +tpsm_dense_motion: + block_expansion: 64 + max_features: 1024 + num_blocks: 5 + scale_factor: 0.25 + num_tps: 10 + num_channels: 3 + +raft_flow: + prior_only: False + num_kp: 10 + dim: 256 + size: 256 + generator: + num_channels: 3 + block_expansion: 64 + max_features: 512 + num_up_blocks: 5 + driving_encoder: + in_features: 10 + block_expansion: 32 + max_features: 512 + num_blocks: 5 + source_encoder: + in_features: 13 + block_expansion: 32 + max_features: 512 + num_blocks: 5 + +train_params: + ## [mtia, tpsm, fomm] + prior_model: mtia + num_epochs: 100 + num_repeats: 5 + epoch_milestones: [60, 90] + # ### enable this for tpsm + # epoch_milestones: [70, 90] + # ### enable this for tpsm + + lr: 2.0e-4 + batch_size: 80 + scales: [1, 0.5, 0.25, 0.125] + clip_grad: True + clip: 10 + bg_start: 0 + checkpoint_freq: 100 + ### for tpsm + dropout_epoch: 35 + dropout_maxp: 0.3 + dropout_startp: 0.1 + dropout_inc_epoch: 10 + ### for tpsm + + transform_params: + sigma_affine: 0.05 + sigma_tps: 0.005 + points_tps: 5 + loss_weights: + perceptual: [10, 10, 10, 10, 10] + equivariance: 10 + equivariance_jacobian: 10 + # ### enable this for tpsm + # equivariance_jacobian: 0 + # ### enable this for tpsm + +reconstruction_params: + num_videos: 1000 + format: '.mp4' + +animate_params: + num_pairs: 50 + format: '.mp4' + normalization_params: + adapt_movement_scale: False + use_relative_movement: True + use_relative_jacobian: True + +visualizer_params: + draw_border: True + colormap: 'gist_rainbow' + +mtia_kp_detector: + MODEL: + ESTIMATE_JACOBIAN: True + DATA_PREPROCESS: False + FIX_IMG2MOTION_ATTENTION: False + TRANSFORMER_DEPTH: 12 + TRANSFORMER_HEADS: 8 + DIM: 192 # 4*4*3 + INIT_WEIGHTS: False + NAME: pose_tokenpose_b + NUM_JOINTS: 10 + PRETRAINED: '' + PATCH_SIZE: [4, 4] + IMAGE_SIZE: [256, 256] + HEATMAP_SIZE: [64, 64] + + ## default + TAG_PER_JOINT: True + HIDDEN_HEATMAP_DIM: -1 + MULTI_TRANSFORMER_DEPTH: [12, 12] + MULTI_TRANSFORMER_HEADS: [16, 16] + MULTI_DIM: [48, 48] + NUM_BRANCHES: 1 + BASE_CHANNEL: 32 + TRANSFORMER_MLP_RATIO: 3 + POS_EMBEDDING_TYPE: 'sine-full' + TEMPERATURE: 0.1 + TARGET_TYPE: gaussian + INIT: True + + SIGMA: 2 + EXTRA: + PRETRAINED_LAYERS: + - 'conv1' + - 'bn1' + - 'conv2' + - 'bn2' + - 'layer1' + - 'transition1' + - 'stage2' + - 'transition2' + - 'stage3' + FINAL_CONV_KERNEL: 1 + STAGE2: + NUM_MODULES: 1 + NUM_BRANCHES: 2 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + NUM_CHANNELS: + - 32 + - 64 + FUSE_METHOD: SUM + STAGE3: + NUM_MODULES: 4 + NUM_BRANCHES: 3 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 32 + - 64 + - 128 + FUSE_METHOD: SUM + ## default + \ No newline at end of file diff --git a/module_mrfa/configs/vox1.yaml b/module_mrfa/configs/vox1.yaml new file mode 100644 index 0000000..e81f029 --- /dev/null +++ b/module_mrfa/configs/vox1.yaml @@ -0,0 +1,186 @@ +dataset_params: + root_dir: ./data/voxceleb1-png/ + split_len: + frame_shape: [256, 256, 3] + id_sampling: True + pairs_list: + augmentation_params: + flip_param: + horizontal_flip: True + time_flip: True + jitter_param: + brightness: 0.1 + contrast: 0.1 + saturation: 0.1 + hue: 0.1 + +dense_motion: + block_expansion: 64 + max_features: 1024 + num_blocks: 5 + scale_factor: 0.25 + num_kp: 10 + num_channels: 3 + estimate_occlusion_map: True +fomm_kp_detector: + block_expansion: 32 + num_kp: 10 + num_channels: 3 + max_features: 1024 + num_blocks: 5 + temperature: 0.1 + scale_factor: 0.25 + estimate_jacobian: True + estimate_occlusion: False +tpsm_kp_detector: + num_tps: 10 +tpsm_dense_motion: + block_expansion: 64 + max_features: 1024 + num_blocks: 5 + scale_factor: 0.25 + num_tps: 10 + num_channels: 3 + +raft_flow: + prior_only: False + num_kp: 10 + dim: 256 + size: 256 + generator: + num_channels: 3 + block_expansion: 64 + max_features: 512 + num_up_blocks: 5 + driving_encoder: + in_features: 10 + block_expansion: 32 + max_features: 512 + num_blocks: 5 + source_encoder: + in_features: 13 + block_expansion: 32 + max_features: 512 + num_blocks: 5 + +train_params: + ## [mtia, tpsm, fomm] + prior_model: mtia + num_epochs: 100 + num_repeats: 150 + epoch_milestones: [60, 90] + # ### enable this for tpsm + # epoch_milestones: [70, 90] + # ### enable this for tpsm + + lr: 2.0e-4 + batch_size: 80 + scales: [1, 0.5, 0.25, 0.125] + clip_grad: True + clip: 10 + bg_start: 1000 + checkpoint_freq: 100 + ### for tpsm + dropout_epoch: 35 + dropout_maxp: 0.3 + dropout_startp: 0.1 + dropout_inc_epoch: 10 + ### for tpsm + + transform_params: + sigma_affine: 0.05 + sigma_tps: 0.005 + points_tps: 5 + loss_weights: + perceptual: [10, 10, 10, 10, 10] + equivariance: 10 + equivariance_jacobian: 10 + # ### enable this for tpsm + # equivariance_jacobian: 0 + # ### enable this for tpsm + +reconstruction_params: + num_videos: 1000 + format: '.mp4' + +animate_params: + num_pairs: 50 + format: '.mp4' + normalization_params: + adapt_movement_scale: False + use_relative_movement: True + use_relative_jacobian: True + +visualizer_params: + draw_border: True + colormap: 'gist_rainbow' + +mtia_kp_detector: + MODEL: + ESTIMATE_JACOBIAN: True + DATA_PREPROCESS: False + FIX_IMG2MOTION_ATTENTION: False + TRANSFORMER_DEPTH: 12 + TRANSFORMER_HEADS: 8 + DIM: 192 # 4*4*3 + INIT_WEIGHTS: False + NAME: pose_tokenpose_b + NUM_JOINTS: 10 + PRETRAINED: '' + PATCH_SIZE: [4, 4] + IMAGE_SIZE: [256, 256] + HEATMAP_SIZE: [64, 64] + + ## default + TAG_PER_JOINT: True + HIDDEN_HEATMAP_DIM: -1 + MULTI_TRANSFORMER_DEPTH: [12, 12] + MULTI_TRANSFORMER_HEADS: [16, 16] + MULTI_DIM: [48, 48] + NUM_BRANCHES: 1 + BASE_CHANNEL: 32 + TRANSFORMER_MLP_RATIO: 3 + POS_EMBEDDING_TYPE: 'sine-full' + TEMPERATURE: 0.1 + TARGET_TYPE: gaussian + INIT: True + + SIGMA: 2 + EXTRA: + PRETRAINED_LAYERS: + - 'conv1' + - 'bn1' + - 'conv2' + - 'bn2' + - 'layer1' + - 'transition1' + - 'stage2' + - 'transition2' + - 'stage3' + FINAL_CONV_KERNEL: 1 + STAGE2: + NUM_MODULES: 1 + NUM_BRANCHES: 2 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + NUM_CHANNELS: + - 32 + - 64 + FUSE_METHOD: SUM + STAGE3: + NUM_MODULES: 4 + NUM_BRANCHES: 3 + BLOCK: BASIC + NUM_BLOCKS: + - 4 + - 4 + - 4 + NUM_CHANNELS: + - 32 + - 64 + - 128 + FUSE_METHOD: SUM + ## default + \ No newline at end of file diff --git a/module_mrfa/dense_motion.py b/module_mrfa/dense_motion.py new file mode 100644 index 0000000..5cf758d --- /dev/null +++ b/module_mrfa/dense_motion.py @@ -0,0 +1,435 @@ +import math + +import torch +import torch.nn.functional as F +from torch import nn + +from .util import ( + TPS, + AntiAliasInterpolation2d, + Hourglass, + UpBlock2d, + from_homogeneous, + kp2gaussian, + make_coordinate_grid, + to_homogeneous, +) + + +class DenseMotionNetwork(nn.Module): + """ + Module that predicting a dense motion from sparse motion representation given by kp_source and kp_driving + """ + + def __init__( + self, + block_expansion, + num_blocks, + max_features, + num_kp, + num_channels, + estimate_occlusion_map=True, + scale_factor=1, + kp_variance=0.01, + ): + super(DenseMotionNetwork, self).__init__() + + infeatures = num_kp + 1 + self.infeatures = infeatures + + self.hourglass = Hourglass( + block_expansion=block_expansion, + in_features=infeatures * (num_channels + 1), + max_features=max_features, + num_blocks=num_blocks, + ) + self.mask = nn.Conv2d( + self.hourglass.out_filters, infeatures, kernel_size=(7, 7), padding=(3, 3) + ) + + if estimate_occlusion_map: + self.occlusion = nn.Conv2d( + self.hourglass.out_filters, 1, kernel_size=(7, 7), padding=(3, 3) + ) + else: + self.occlusion = None + + self.num_kp = num_kp + self.scale_factor = scale_factor + self.kp_variance = kp_variance + + if self.scale_factor != 1: + self.down = AntiAliasInterpolation2d(num_channels, self.scale_factor) + + def create_heatmap_representations(self, source_image, kp_driving, kp_source): + spatial_size = source_image.shape[2:] + gaussian_driving = kp2gaussian( + kp_driving["kp"], spatial_size=spatial_size, kp_variance=self.kp_variance + ) + gaussian_source = kp2gaussian( + kp_source["kp"], spatial_size=spatial_size, kp_variance=self.kp_variance + ) + heatmap = gaussian_driving - gaussian_source + + # adding background feature + zeros = torch.zeros(heatmap.shape[0], 1, spatial_size[0], spatial_size[1]).type( + heatmap.type() + ) + heatmap = torch.cat([zeros, heatmap], dim=1) + heatmap = heatmap.unsqueeze(2) + return heatmap + + def create_sparse_motions(self, source_image, kp_driving, kp_source, bg_param=None): + bs, _, h, w = source_image.shape + identity_grid = make_coordinate_grid((h, w), type=kp_source["kp"].type()) + identity_grid = identity_grid.view(1, 1, h, w, 2) + coordinate_grid = identity_grid - kp_driving["kp"].view( + bs, self.num_kp, 1, 1, 2 + ) + if "jacobian" in kp_driving: + jacobian = torch.matmul( + kp_source["jacobian"], torch.inverse(kp_driving["jacobian"]) + ) + jacobian = jacobian.unsqueeze(-3).unsqueeze(-3) + jacobian = jacobian.repeat(1, 1, h, w, 1, 1) + coordinate_grid = torch.matmul(jacobian, coordinate_grid.unsqueeze(-1)) + coordinate_grid = coordinate_grid.squeeze(-1) + # n * 10 * h * w * 2 + + driving_to_source = coordinate_grid + kp_source["kp"].view( + bs, self.num_kp, 1, 1, 2 + ) + + # #adding background feature + # identity_grid = identity_grid.repeat(bs, 1, 1, 1, 1) + + # adding background feature + if bg_param is None: + bg_grid = identity_grid.repeat(bs, 1, 1, 1, 1) + else: + bg_grid = identity_grid.repeat(bs, 1, 1, 1, 1) + bg_grid = to_homogeneous(bg_grid) + bg_grid = torch.matmul( + bg_param.view(bs, 1, 1, 1, 3, 3), bg_grid.unsqueeze(-1) + ).squeeze(-1) + bg_grid = from_homogeneous(bg_grid) + + sparse_motions = torch.cat([bg_grid, driving_to_source], dim=1) + return sparse_motions + + def create_deformed_source_image(self, source_image, sparse_motions): + bs, _, h, w = source_image.shape + source_repeat = ( + source_image.unsqueeze(1) + .unsqueeze(1) + .repeat(1, self.num_kp + 1, 1, 1, 1, 1) + ) + source_repeat = source_repeat.view(bs * (self.num_kp + 1), -1, h, w) + sparse_motions = sparse_motions.view((bs * (self.num_kp + 1), h, w, -1)) + sparse_deformed = F.grid_sample(source_repeat, sparse_motions) + sparse_deformed = sparse_deformed.view((bs, self.num_kp + 1, -1, h, w)) + return sparse_deformed + + def dropout_softmax(self, X, P): + """ + Dropout for TPS transformations. Eq(7) and Eq(8) in the paper. + """ + drop = ( + (torch.rand(X.shape[0], X.shape[1]) < (1 - P)).type(X.type()).to(X.device) + ) + drop[..., 0] = 1 + drop = drop.repeat(X.shape[2], X.shape[3], 1, 1).permute(2, 3, 0, 1) + + maxx = X.max(1).values.unsqueeze_(1) + X = X - maxx + X_exp = X.exp() + X[:, 1:, ...] /= 1 - P + mask_bool = drop == 0 + X_exp = X_exp.masked_fill(mask_bool, 0) + partition = X_exp.sum(dim=1, keepdim=True) + 1e-6 + return X_exp / partition + + def forward( + self, + source_image, + kp_driving, + kp_source, + bg_param=None, + dropout_flag=False, + dropout_p=0, + ): + if self.scale_factor != 1: + source_image = self.down(source_image) + + bs, _, h, w = source_image.shape + + out_dict = dict() + heatmap_representation = self.create_heatmap_representations( + source_image, kp_driving, kp_source + ) + sparse_motion = self.create_sparse_motions( + source_image, kp_driving, kp_source, bg_param=bg_param + ) + # n * 11 * h * w * 2 + deformed_source = self.create_deformed_source_image(source_image, sparse_motion) + # n * 11 * 3 * h * w + out_dict["sparse_deformed"] = deformed_source + + input = torch.cat([heatmap_representation, deformed_source], dim=2) + # n * 11(101,22) * 4 * h * w + input = input.view(bs, -1, h, w) + + prediction = self.hourglass(input) + + mask = self.mask(prediction) + out_dict["logit_mask"] = mask + if dropout_flag: + mask = self.dropout_softmax(mask, dropout_p) + else: + mask = F.softmax(mask, dim=1) + # mask = F.softmax(mask, dim=1) + out_dict["mask"] = mask + mask = mask.unsqueeze(2) + sparse_motion = sparse_motion.permute(0, 1, 4, 2, 3) + # n * 11 * 2 * h * w + deformation = (sparse_motion * mask).sum(dim=1) + deformation = deformation.permute(0, 2, 3, 1) + # n * h * w * 2 + + out_dict["deformation"] = deformation + + if self.occlusion is not None: + # occlusion_map = torch.sigmoid(self.occlusion(prediction)) + occlusion_map = self.occlusion(prediction) + out_dict["occlusion"] = occlusion_map + + return out_dict + + +class TPSDenseMotionNetwork(nn.Module): + """ + Module that estimating an optical flow and multi-resolution occlusion masks + from K TPS transformations and an affine transformation. + """ + + def __init__( + self, + block_expansion, + num_blocks, + max_features, + num_tps, + num_channels, + scale_factor=0.25, + bg=False, + multi_mask=False, + kp_variance=0.01, + ): + super(TPSDenseMotionNetwork, self).__init__() + + if scale_factor != 1: + self.down = AntiAliasInterpolation2d(num_channels, scale_factor) + self.scale_factor = scale_factor + self.multi_mask = multi_mask + + self.hourglass = Hourglass( + block_expansion=block_expansion, + in_features=(num_channels * (num_tps + 1) + num_tps * 5 + 1), + max_features=max_features, + num_blocks=num_blocks, + ) + + hourglass_output_size = self.hourglass.out_filters + # self.maps = nn.Conv2d(hourglass_output_size[-1], num_tps + 1, kernel_size=(7, 7), padding=(3, 3)) + self.maps = nn.Conv2d( + hourglass_output_size, num_tps + 1, kernel_size=(7, 7), padding=(3, 3) + ) + + if multi_mask: + up = [] + self.up_nums = int(math.log(1 / scale_factor, 2)) + self.occlusion_num = 4 + + channel = [hourglass_output_size[-1] // (2**i) for i in range(self.up_nums)] + for i in range(self.up_nums): + up.append( + UpBlock2d(channel[i], channel[i] // 2, kernel_size=3, padding=1) + ) + self.up = nn.ModuleList(up) + + channel = [ + hourglass_output_size[-i - 1] + for i in range(self.occlusion_num - self.up_nums)[::-1] + ] + for i in range(self.up_nums): + channel.append(hourglass_output_size[-1] // (2 ** (i + 1))) + occlusion = [] + + for i in range(self.occlusion_num): + occlusion.append( + nn.Conv2d(channel[i], 1, kernel_size=(7, 7), padding=(3, 3)) + ) + self.occlusion = nn.ModuleList(occlusion) + else: + # occlusion = [nn.Conv2d(hourglass_output_size[-1], 1, kernel_size=(7, 7), padding=(3, 3))] + occlusion = [ + nn.Conv2d(hourglass_output_size, 1, kernel_size=(7, 7), padding=(3, 3)) + ] + self.occlusion = nn.ModuleList(occlusion) + + self.num_tps = num_tps + self.bg = bg + self.kp_variance = kp_variance + + def create_heatmap_representations(self, source_image, kp_driving, kp_source): + spatial_size = source_image.shape[2:] + gaussian_driving = kp2gaussian( + kp_driving["kp"], spatial_size=spatial_size, kp_variance=self.kp_variance + ) + gaussian_source = kp2gaussian( + kp_source["kp"], spatial_size=spatial_size, kp_variance=self.kp_variance + ) + heatmap = gaussian_driving - gaussian_source + + zeros = ( + torch.zeros(heatmap.shape[0], 1, spatial_size[0], spatial_size[1]) + .type(heatmap.type()) + .to(heatmap.device) + ) + heatmap = torch.cat([zeros, heatmap], dim=1) + + return heatmap + + def create_transformations(self, source_image, kp_driving, kp_source, bg_param): + # K TPS transformaions + bs, _, h, w = source_image.shape + kp_1 = kp_driving["kp"] + kp_2 = kp_source["kp"] + kp_1 = kp_1.view(bs, -1, 5, 2) + kp_2 = kp_2.view(bs, -1, 5, 2) + trans = TPS(mode="kp", bs=bs, kp_1=kp_1, kp_2=kp_2) + driving_to_source = trans.transform_frame(source_image) + + identity_grid = make_coordinate_grid((h, w), type=kp_1.type()).to(kp_1.device) + identity_grid = identity_grid.view(1, 1, h, w, 2) + identity_grid = identity_grid.repeat(bs, 1, 1, 1, 1) + + # affine background transformation + if bg_param is not None: + identity_grid = to_homogeneous(identity_grid) + identity_grid = torch.matmul( + bg_param.view(bs, 1, 1, 1, 3, 3), identity_grid.unsqueeze(-1) + ).squeeze(-1) + identity_grid = from_homogeneous(identity_grid) + + transformations = torch.cat([identity_grid, driving_to_source], dim=1) + return transformations + + def create_deformed_source_image(self, source_image, transformations): + bs, _, h, w = source_image.shape + source_repeat = ( + source_image.unsqueeze(1) + .unsqueeze(1) + .repeat(1, self.num_tps + 1, 1, 1, 1, 1) + ) + source_repeat = source_repeat.view(bs * (self.num_tps + 1), -1, h, w) + transformations = transformations.view((bs * (self.num_tps + 1), h, w, -1)) + deformed = F.grid_sample(source_repeat, transformations, align_corners=True) + deformed = deformed.view((bs, self.num_tps + 1, -1, h, w)) + return deformed + + def dropout_softmax(self, X, P): + """ + Dropout for TPS transformations. Eq(7) and Eq(8) in the paper. + """ + drop = ( + (torch.rand(X.shape[0], X.shape[1]) < (1 - P)).type(X.type()).to(X.device) + ) + drop[..., 0] = 1 + drop = drop.repeat(X.shape[2], X.shape[3], 1, 1).permute(2, 3, 0, 1) + + maxx = X.max(1).values.unsqueeze_(1) + X = X - maxx + X_exp = X.exp() + X[:, 1:, ...] /= 1 - P + mask_bool = drop == 0 + X_exp = X_exp.masked_fill(mask_bool, 0) + partition = X_exp.sum(dim=1, keepdim=True) + 1e-6 + return X_exp / partition + + def forward( + self, + source_image, + kp_driving, + kp_source, + bg_param=None, + dropout_flag=False, + dropout_p=0, + ): + if self.scale_factor != 1: + source_image = self.down(source_image) + + bs, _, h, w = source_image.shape + + out_dict = dict() + heatmap_representation = self.create_heatmap_representations( + source_image, kp_driving, kp_source + ) + transformations = self.create_transformations( + source_image, kp_driving, kp_source, bg_param + ) + deformed_source = self.create_deformed_source_image( + source_image, transformations + ) + out_dict["deformed_source"] = deformed_source + # out_dict['transformations'] = transformations + deformed_source = deformed_source.view(bs, -1, h, w) + input = torch.cat([heatmap_representation, deformed_source], dim=1) + input = input.view(bs, -1, h, w) + + # prediction = self.hourglass(input, mode = 1) + prediction = self.hourglass(input) + + # contribution_maps = self.maps(prediction[-1]) + contribution_maps = self.maps(prediction) + if dropout_flag: + contribution_maps = self.dropout_softmax(contribution_maps, dropout_p) + else: + contribution_maps = F.softmax(contribution_maps, dim=1) + out_dict["contribution_maps"] = contribution_maps + out_dict["mask"] = contribution_maps + + # Combine the K+1 transformations + # Eq(6) in the paper + contribution_maps = contribution_maps.unsqueeze(2) + transformations = transformations.permute(0, 1, 4, 2, 3) + deformation = (transformations * contribution_maps).sum(dim=1) + deformation = deformation.permute(0, 2, 3, 1) + + out_dict["deformation"] = deformation # Optical Flow + + occlusion_map = [] + if self.multi_mask: + for i in range(self.occlusion_num - self.up_nums): + occlusion_map.append( + torch.sigmoid( + self.occlusion[i]( + prediction[self.up_nums - self.occlusion_num + i] + ) + ) + ) + prediction = prediction[-1] + for i in range(self.up_nums): + prediction = self.up[i](prediction) + occlusion_map.append( + torch.sigmoid( + self.occlusion[i + self.occlusion_num - self.up_nums]( + prediction + ) + ) + ) + else: + # occlusion_map.append(torch.sigmoid(self.occlusion[0](prediction[-1]))) + occlusion_map = self.occlusion[0](prediction) + + out_dict["occlusion"] = occlusion_map # Multi-resolution Occlusion Masks + return out_dict diff --git a/module_mrfa/generator.py b/module_mrfa/generator.py new file mode 100644 index 0000000..f632cc4 --- /dev/null +++ b/module_mrfa/generator.py @@ -0,0 +1,88 @@ +# import math + +import torch +# import torch.nn.functional as F +from torch import nn + +from .util import ChannelBlock2d, DownBlock2d, ResBlock2d, SameBlock2d, UpBlock2d + + +class OcclusionAwareGenerator(nn.Module): + def __init__(self, num_channels, block_expansion, max_features, num_up_blocks): + super(OcclusionAwareGenerator, self).__init__() + + self.num_up_blocks = num_up_blocks + self.first = SameBlock2d( + num_channels, block_expansion, kernel_size=(7, 7), padding=(3, 3) + ) + down_blocks = [] + up_blocks = [] + resblock = [] + channel_block = [] + for i in range(num_up_blocks): + in_features = min(max_features, block_expansion * (2**i)) + out_features = min(max_features, block_expansion * (2 ** (i + 1))) + down_blocks.append( + DownBlock2d( + in_features, out_features, kernel_size=(3, 3), padding=(1, 1) + ) + ) + decoder_in_feature = out_features + if i == num_up_blocks - 1: + decoder_in_feature = out_features + up_blocks.append( + UpBlock2d( + decoder_in_feature, in_features, kernel_size=(3, 3), padding=(1, 1) + ) + ) + resblock.append( + ResBlock2d(decoder_in_feature, kernel_size=(3, 3), padding=(1, 1)) + ) + channel_block.append( + ChannelBlock2d( + decoder_in_feature * 2, kernel_size=(3, 3), padding=(1, 1) + ) + ) + self.down_blocks = nn.ModuleList(down_blocks) + self.up_blocks = nn.ModuleList(up_blocks[::-1]) + self.resblock = nn.ModuleList(resblock[::-1]) + self.channel_block = nn.ModuleList(channel_block[::-1]) + self.final = nn.Conv2d( + block_expansion, num_channels, kernel_size=(7, 7), padding=(3, 3) + ) + + def encode(self, x): + f = [] + out = self.first(x) + f.append(out) + for i in range(len(self.down_blocks)): + out = self.down_blocks[i](out) + f.append(out) + # 256 128 64 32,...,16,8 + return f[::-1] + + def decode(self, warp_f, warp_img, occlusion, warp_f_c=None, occlusion_c=None): + # warp_f 32 64 128 256 + out = warp_f[0] * occlusion[0] + # out = warp_f[0] + if warp_f_c is not None: + out_c = warp_f_c[0] + out = torch.cat([out, out_c], dim=1) + for i in range(self.num_up_blocks): + if warp_f_c is not None: + out = self.channel_block[i](out) + out = self.resblock[i](out) + out = self.up_blocks[i](out) + out = warp_f[i + 1] * occlusion[i + 1] + out * (1 - occlusion[i + 1]) + if warp_f_c is not None and i != self.num_up_blocks - 1: + out_c = warp_f_c[i + 1] + out = torch.cat([out, out_c], dim=1) + out = self.final(out) + out = torch.sigmoid(out) + out = out * (1 - occlusion[-1]) + warp_img * occlusion[-1] + return out + + def forward(self, x): + x = self.encode(x) + x = self.decode(x) + return x diff --git a/module_mrfa/kp_detector.py b/module_mrfa/kp_detector.py new file mode 100644 index 0000000..95f69bb --- /dev/null +++ b/module_mrfa/kp_detector.py @@ -0,0 +1,213 @@ +# from collections import OrderedDict + +import torch +import torch.nn.functional as F +from torch import nn + +# from torch.jit.annotations import Dict +# from torch.nn import BatchNorm2d +from torchvision import models + +# from torchvision.models._utils import IntermediateLayerGetter +from .util import AntiAliasInterpolation2d, DownBlock2d, Hourglass, make_coordinate_grid + + +class KPDetector(nn.Module): + """ + Detecting a keypoints. Return keypoint position and jacobian near each keypoint. + """ + + def __init__( + self, + block_expansion=32, + num_kp=15, + num_channels=3, + max_features=1024, + num_blocks=5, + temperature=0.1, + scale_factor=0.25, + estimate_jacobian=False, + estimate_occlusion=False, + ): + super(KPDetector, self).__init__() + + self.predictor = Hourglass( + block_expansion, + in_features=num_channels, + max_features=max_features, + num_blocks=num_blocks, + ) + + self.kp = nn.Conv2d( + in_channels=self.predictor.out_filters, + out_channels=num_kp, + kernel_size=(7, 7), + padding=0, + ) + self.num_kp = num_kp + + self.estimate_jacobian = estimate_jacobian + if estimate_jacobian: + self.num_jacobian_maps = 1 + self.jacobian = nn.Conv2d( + in_channels=self.predictor.out_filters, + out_channels=4 * self.num_jacobian_maps, + kernel_size=(7, 7), + padding=0, + ) + self.jacobian.weight.data.zero_() + self.jacobian.bias.data.copy_( + torch.tensor([1, 0, 0, 1] * self.num_jacobian_maps, dtype=torch.float) + ) + + self.estimate_occlusion = estimate_occlusion + if estimate_occlusion: + kp_occlusion = [ + DownBlock2d( + self.predictor.out_filters, + block_expansion, + kernel_size=3, + padding=1, + ) + ] + kp_occlusion.append( + DownBlock2d( + block_expansion, block_expansion * 2, kernel_size=3, padding=1 + ) + ) + kp_occlusion.append( + DownBlock2d( + block_expansion * 2, block_expansion * 3, kernel_size=3, padding=1 + ) + ) + kp_occlusion.append( + DownBlock2d( + block_expansion * 3, block_expansion * 4, kernel_size=3, padding=1 + ) + ) + kp_occlusion.append( + nn.Conv2d( + block_expansion * 4, num_kp, kernel_size=(4, 4), padding=0, stride=4 + ) + ) + self.kp_occlusion = nn.Sequential(*kp_occlusion) + + self.temperature = temperature + self.scale_factor = scale_factor + if self.scale_factor != 1: + self.down = AntiAliasInterpolation2d(num_channels, self.scale_factor) + + def create_part_flows(self, source_image, kp_driving, kp_source, bg_params=None): + """ + Eq 4. in the paper T_{s<-d}(z) + """ + bs, _, h, w = source_image.shape + num_kp = kp_driving["kp"].shape[1] + identity_grid = make_coordinate_grid((h, w), type=kp_source["kp"].type()) + identity_grid = identity_grid.view(1, 1, h, w, 2) + coordinate_grid = identity_grid - kp_driving["kp"].view(bs, num_kp, 1, 1, 2) + if "jacobian" in kp_driving: + jacobian = torch.matmul( + kp_source["jacobian"], torch.inverse(kp_driving["jacobian"]) + ) + jacobian = jacobian.unsqueeze(-3).unsqueeze(-3) + jacobian = jacobian.repeat(1, 1, h, w, 1, 1) + coordinate_grid = torch.matmul(jacobian, coordinate_grid.unsqueeze(-1)) + coordinate_grid = coordinate_grid.squeeze(-1) + # n * 10 * h * w * 2 + driving_to_source = coordinate_grid + kp_source["kp"].view(bs, num_kp, 1, 1, 2) + flow = driving_to_source - identity_grid + flow = (h - 1) * flow / 2.0 + return flow.view(bs, num_kp * 2, h, w) + + # # #adding background feature + # # identity_grid = identity_grid.repeat(bs, 1, 1, 1, 1) + + # # adding background feature + # if bg_params is None: + # bg_grid = identity_grid.repeat(bs, 1, 1, 1, 1) + # else: + # bg_grid = identity_grid.repeat(bs, 1, 1, 1, 1) + # bg_grid = to_homogeneous(bg_grid) + # bg_grid = torch.matmul(bg_params.view(bs, 1, 1, 1, 3, 3), bg_grid.unsqueeze(-1)).squeeze(-1) + # bg_grid = from_homogeneous(bg_grid) + # sparse_motions = torch.cat([bg_grid, driving_to_source], dim=1) + # return sparse_motions + + def gaussian2kp(self, heatmap): + """ + Extract the mean and from a heatmap + """ + shape = heatmap.shape + heatmap = heatmap.unsqueeze(-1) + grid = ( + make_coordinate_grid(shape[2:], heatmap.type()).unsqueeze_(0).unsqueeze_(0) + ) + kp = (heatmap * grid).sum(dim=(2, 3)) # N * 10 * 2 + # kp = {'value': value} + + return kp + + def forward(self, x): + if self.scale_factor != 1: + x = self.down(x) + feature_map = self.predictor(x) + prediction = self.kp(feature_map) + final_shape = prediction.shape + heatmap = prediction.view(final_shape[0], final_shape[1], -1) + heatmap = F.softmax(heatmap / self.temperature, dim=2) + heatmap = heatmap.view(*final_shape) + out = self.gaussian2kp(heatmap) + if self.estimate_jacobian: + jacobian_map = self.jacobian(feature_map) + jacobian_map = jacobian_map.reshape( + final_shape[0], + self.num_jacobian_maps, + 4, + final_shape[2], + final_shape[3], + ) + heatmap = heatmap.unsqueeze(2) + jacobian = heatmap * jacobian_map + jacobian = jacobian.view(final_shape[0], final_shape[1], 4, -1) + jacobian = jacobian.sum(dim=-1) + jacobian = jacobian.view(jacobian.shape[0], jacobian.shape[1], 2, 2) + # N * 10 *2 *2 + out = {"kp": out, "jacobian": jacobian} + else: + out = {"kp": out} + + if self.estimate_occlusion: + # N*10*1*1 + kp_occlusion = self.kp_occlusion(feature_map) + kp_occlusion = torch.sigmoid(kp_occlusion) + out["kp_occlusion"] = kp_occlusion + # out['heatmap'] = heatmap + # out['feature_map'] = feature_map + return out + + +class TPSKPDetector(nn.Module): + """ + Predict K*5 keypoints. + """ + + def __init__(self, num_tps, **kwargs): + super(TPSKPDetector, self).__init__() + self.num_tps = num_tps + + self.fg_encoder = models.resnet18(pretrained=False) + num_features = self.fg_encoder.fc.in_features + self.fg_encoder.fc = nn.Linear(num_features, num_tps * 5 * 2) + + def forward(self, image): + fg_kp = self.fg_encoder(image) + ( + bs, + _, + ) = fg_kp.shape + fg_kp = torch.sigmoid(fg_kp) + fg_kp = fg_kp * 2 - 1 + out = {"kp": fg_kp.view(bs, self.num_tps * 5, -1)} + + return out diff --git a/module_mrfa/model.py b/module_mrfa/model.py new file mode 100644 index 0000000..3c5a1a1 --- /dev/null +++ b/module_mrfa/model.py @@ -0,0 +1,294 @@ +# from __future__ import absolute_import, division, print_function + +# import logging + +# import timm +# import math +# import os + +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.autograd import grad +from torchvision import models + +from .bg_motion_predictor import BGMotionPredictor +from .dense_motion import DenseMotionNetwork, TPSDenseMotionNetwork + +# from .generator import OcclusionAwareGenerator +from .kp_detector import KPDetector, TPSKPDetector +from .raft import RaftFlow +from .transformer.pose_tokenpose_b import get_pose_net +from .util import AntiAliasInterpolation2d, make_coordinate_grid + + +class Transform: + """ + Random tps transformation for equivariance constraints. See Sec 3.3 + """ + + def __init__(self, bs, **kwargs): + noise = torch.normal( + mean=0, std=kwargs["sigma_affine"] * torch.ones([bs, 2, 3]) + ) + self.theta = noise + torch.eye(2, 3).view(1, 2, 3) + self.bs = bs + + if ("sigma_tps" in kwargs) and ("points_tps" in kwargs): + self.tps = True + self.control_points = make_coordinate_grid( + (kwargs["points_tps"], kwargs["points_tps"]), type=noise.type() + ) + self.control_points = self.control_points.unsqueeze(0) + self.control_params = torch.normal( + mean=0, + std=kwargs["sigma_tps"] + * torch.ones([bs, 1, kwargs["points_tps"] ** 2]), + ) + else: + self.tps = False + + def transform_frame(self, frame): + grid = make_coordinate_grid(frame.shape[2:], type=frame.type()).unsqueeze(0) + grid = grid.view(1, frame.shape[2] * frame.shape[3], 2) + grid = self.warp_coordinates(grid).view( + self.bs, frame.shape[2], frame.shape[3], 2 + ) + return F.grid_sample(frame, grid, padding_mode="reflection") + + def warp_coordinates(self, coordinates): + theta = self.theta.type(coordinates.type()) + theta = theta.unsqueeze(1) + # N * (HW) * 2 * 1 + transformed = ( + torch.matmul(theta[:, :, :, :2], coordinates.unsqueeze(-1)) + + theta[:, :, :, 2:] + ) + transformed = transformed.squeeze(-1) + + if self.tps: + control_points = self.control_points.type(coordinates.type()) + control_params = self.control_params.type(coordinates.type()) + # 1 * (HW) * 1 * 2 - 1 * 1 * 25 *2 + distances = coordinates.view( + coordinates.shape[0], -1, 1, 2 + ) - control_points.view(1, 1, -1, 2) + distances = torch.abs(distances).sum(-1) + + result = distances**2 + result = result * torch.log(distances + 1e-6) + result = result * control_params + result = result.sum(dim=2).view(self.bs, coordinates.shape[1], 1) + transformed = transformed + result + + return transformed + + def jacobian(self, coordinates): + new_coordinates = self.warp_coordinates(coordinates) + grad_x = grad(new_coordinates[..., 0].sum(), coordinates, create_graph=True) + grad_y = grad(new_coordinates[..., 1].sum(), coordinates, create_graph=True) + jacobian = torch.cat([grad_x[0].unsqueeze(-2), grad_y[0].unsqueeze(-2)], dim=-2) + return jacobian + + +class Vgg19(torch.nn.Module): + """ + Vgg19 network for perceptual loss. See Sec 3.3. + """ + + def __init__(self, requires_grad=False): + super(Vgg19, self).__init__() + vgg_pretrained_features = models.vgg19(pretrained=True).features + self.slice1 = torch.nn.Sequential() + self.slice2 = torch.nn.Sequential() + self.slice3 = torch.nn.Sequential() + self.slice4 = torch.nn.Sequential() + self.slice5 = torch.nn.Sequential() + for x in range(2): + self.slice1.add_module(str(x), vgg_pretrained_features[x]) + for x in range(2, 7): + self.slice2.add_module(str(x), vgg_pretrained_features[x]) + for x in range(7, 12): + self.slice3.add_module(str(x), vgg_pretrained_features[x]) + for x in range(12, 21): + self.slice4.add_module(str(x), vgg_pretrained_features[x]) + for x in range(21, 30): + self.slice5.add_module(str(x), vgg_pretrained_features[x]) + + self.mean = torch.nn.Parameter( + data=torch.Tensor(np.array([0.485, 0.456, 0.406]).reshape((1, 3, 1, 1))), + requires_grad=False, + ) + self.std = torch.nn.Parameter( + data=torch.Tensor(np.array([0.229, 0.224, 0.225]).reshape((1, 3, 1, 1))), + requires_grad=False, + ) + + if not requires_grad: + for param in self.parameters(): + param.requires_grad = False + + def forward(self, X): + # X = X.clamp(-1, 1) + # X = X / 2 + 0.5 + X = (X - self.mean) / self.std + h_relu1 = self.slice1(X) + h_relu2 = self.slice2(h_relu1) + h_relu3 = self.slice3(h_relu2) + h_relu4 = self.slice4(h_relu3) + h_relu5 = self.slice5(h_relu4) + out = [h_relu1, h_relu2, h_relu3, h_relu4, h_relu5] + return out + + +class ImagePyramide(torch.nn.Module): + """ + Create image pyramide for computing pyramide perceptual loss. See Sec 3.3 + """ + + def __init__(self, scales, num_channels=3): + super(ImagePyramide, self).__init__() + downs = {} + for scale in scales: + downs[str(scale).replace(".", "-")] = AntiAliasInterpolation2d( + num_channels, scale + ) + self.downs = nn.ModuleDict(downs) + + def forward(self, x): + out_dict = {} + for scale, down_module in self.downs.items(): + out_dict["prediction_" + str(scale).replace("-", ".")] = down_module(x) + return out_dict + + +class MRFA(nn.Module): + def __init__(self, cfg, **kwargs): + super(MRFA, self).__init__() + + self.cfg = cfg + train_params = cfg["train_params"] + self.train_params = train_params + self.scales = train_params["scales"] + self.loss_weights = train_params["loss_weights"] + self.pyramid = ImagePyramide(self.scales).cuda() + if sum(self.loss_weights["perceptual"]) != 0: + self.vgg = Vgg19().cuda() + + ################################################## + if train_params["prior_model"] == "fomm": + self.encoder = KPDetector(**cfg["fomm_kp_detector"]) + self.dense_motion = DenseMotionNetwork(**cfg["dense_motion"]) + elif train_params["prior_model"] == "tpsm": + self.encoder = TPSKPDetector(**cfg["tpsm_kp_detector"]) + self.dense_motion = TPSDenseMotionNetwork(**cfg["tpsm_dense_motion"]) + self.dropout_epoch = train_params["dropout_epoch"] + self.dropout_maxp = train_params["dropout_maxp"] + self.dropout_startp = train_params["dropout_startp"] + self.dropout_inc_epoch = train_params["dropout_inc_epoch"] + elif train_params["prior_model"] == "mtia": + self.encoder = get_pose_net(cfg["mtia_kp_detector"], is_train=True) + self.dense_motion = DenseMotionNetwork(**cfg["dense_motion"]) + else: + print("please specify the prior motion model") + + if train_params["bg_start"] < train_params["num_epochs"]: + self.bg_predictor = BGMotionPredictor() + self.bg_start = train_params["bg_start"] + + self.decoder = RaftFlow(**cfg["raft_flow"]) + self.down = AntiAliasInterpolation2d(3, 0.25) + + def forward(self, x, epoch=100, is_train=True): + kp_s = self.encoder(x["source"]) + kp_d = self.encoder(x["driving"]) + + img_down = self.down(x["source"]) + if epoch >= self.bg_start: + bg_param = self.bg_predictor(x["source"], x["driving"]) + else: + bg_param = None + + if self.train_params["prior_model"] == "tpsm" and epoch < self.dropout_epoch: + # dropout_p will linearly increase from dropout_startp to dropout_maxp + dropout_flag = True + dropout_p = min( + epoch / self.dropout_inc_epoch * self.dropout_maxp + + self.dropout_startp, + self.dropout_maxp, + ) + else: + dropout_flag = False + dropout_p = 0 + dense_motion = self.dense_motion( + x["source"], + kp_d, + kp_s, + bg_param=bg_param, + dropout_flag=dropout_flag, + dropout_p=dropout_p, + ) + + if self.train_params["prior_model"] == "tpsm": + kp_s_value = kp_s["kp"].view(x["source"].shape[0], -1, 5, 2).mean(2) + kp_d_value = kp_d["kp"].view(x["driving"].shape[0], -1, 5, 2).mean(2) + else: + kp_s_value = kp_s["kp"] + kp_d_value = kp_d["kp"] + + gen, warp_img, occlusion = self.decoder( + kp_s_value, kp_d_value, dense_motion, img=img_down, img_full=x["source"] + ) + warp_img = torch.cat([warp_img, occlusion.repeat(1, 3, 1, 1)], dim=3) + + loss_values = {} + + if not is_train: + return gen, warp_img, loss_values, kp_s["kp"], kp_d["kp"] + + pyramide_real = self.pyramid(x["driving"]) + pyramide_generated = self.pyramid(gen) + value_total = 0 + for scale in self.scales: + x_vgg = self.vgg(pyramide_generated["prediction_" + str(scale)]) + y_vgg = self.vgg(pyramide_real["prediction_" + str(scale)]) + + for i, weight in enumerate(self.loss_weights["perceptual"]): + value = torch.abs(x_vgg[i] - y_vgg[i].detach()).mean() + value_total += self.loss_weights["perceptual"][i] * value + loss_values["perceptual"] = value_total + + if self.loss_weights["equivariance"] != 0: + transform = Transform( + x["driving"].shape[0], **self.train_params["transform_params"] + ) + transformed_frame = transform.transform_frame(x["driving"]) + transformed_kp = self.encoder(transformed_frame) + value = torch.abs( + kp_d["kp"] - transform.warp_coordinates(transformed_kp["kp"]) + ) + value = value.mean() + loss_values["equivariance"] = self.loss_weights["equivariance"] * value + + if self.loss_weights["equivariance_jacobian"] != 0: + jacobian_transformed = torch.matmul( + transform.jacobian(transformed_kp["kp"]), transformed_kp["jacobian"] + ) + normed_driving = torch.inverse(kp_d["jacobian"]) + normed_transformed = jacobian_transformed + value = torch.matmul(normed_driving, normed_transformed) + eye = torch.eye(2).view(1, 1, 2, 2).type(value.type()) + value = torch.abs(eye - value) + loss_values["equivariance_jacobian"] = ( + self.loss_weights["equivariance_jacobian"] * value + ) + + if epoch >= self.bg_start: + bg_param_reverse = self.bg_predictor(x["driving"], x["source"]) + value = torch.matmul(bg_param, bg_param_reverse) + eye = torch.eye(3).view(1, 1, 3, 3).type(value.type()) + value = torch.abs(eye - value).mean() + loss_values["bg"] = 10 * value + + return gen, warp_img, loss_values, kp_s["kp"], kp_d["kp"] diff --git a/module_mrfa/models/.gitkeep b/module_mrfa/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/module_mrfa/raft.py b/module_mrfa/raft.py new file mode 100644 index 0000000..6ee3b6b --- /dev/null +++ b/module_mrfa/raft.py @@ -0,0 +1,456 @@ +import math + +import torch +import torch.nn.functional as F +from einops import rearrange +from timm.models.layers.weight_init import trunc_normal_ +from torch import nn + +from .generator import OcclusionAwareGenerator +from .util import ( + # AntiAliasInterpolation2d, + Hourglass, + batch_bilinear_sampler, + bilinear_sampler, + coords_grid, + kp2gaussian, + # make_coordinate_grid, +) + + +class CorrBlock: + def __init__(self, corr, num_levels=2, radius=3): + self.num_levels = num_levels + self.radius = radius + self.corr_pyramid = [] + + self.corr_pyramid.append(corr) + for i in range(self.num_levels - 1): + corr = F.avg_pool2d(corr, 2, stride=2) + self.corr_pyramid.append(corr) + + def __call__(self, coords): + r = self.radius + coords = coords.permute(0, 2, 3, 1) + batch, h1, w1, _ = coords.shape + + out_pyramid = [] + for i in range(self.num_levels): + corr = self.corr_pyramid[i] + dx = torch.linspace(-r, r, 2 * r + 1, device=coords.device) + dy = torch.linspace(-r, r, 2 * r + 1, device=coords.device) + delta = torch.stack(torch.meshgrid(dy, dx), axis=-1) + + centroid_lvl = coords.reshape(batch * h1 * w1, 1, 1, 2) / 2**i + delta_lvl = delta.view(1, 2 * r + 1, 2 * r + 1, 2) + coords_lvl = centroid_lvl + delta_lvl + + if batch > 1 and h1 >= 128: + corr = batch_bilinear_sampler( + corr, coords_lvl, h=h1, w=w1, mini_batch=1 + ) + else: + corr = bilinear_sampler(corr, coords_lvl) + # corr = bilinear_sampler(corr, coords_lvl) + corr = corr.view(batch, h1, w1, -1) + out_pyramid.append(corr) + + out = torch.cat(out_pyramid, dim=-1) + return out.permute(0, 3, 1, 2).contiguous().float() + + +class BasicMotionEncoder(nn.Module): + def __init__(self, num_levels=2, radius=3): + super(BasicMotionEncoder, self).__init__() + cor_planes = num_levels * (2 * radius + 1) ** 2 + self.convc1 = nn.Conv2d(cor_planes, 128, 1, padding=0) + self.convc2 = nn.Conv2d(128, 96, 3, padding=1) + self.convf1 = nn.Conv2d(2, 128, 7, padding=3) + self.convf2 = nn.Conv2d(128, 64, 3, padding=1) + self.conv = nn.Conv2d(64 + 96, 128 - 2, 3, padding=1) + + def forward(self, delta_flow, corr): + cor = F.relu(self.convc1(corr)) + cor = F.relu(self.convc2(cor)) + flo = F.relu(self.convf1(delta_flow)) + flo = F.relu(self.convf2(flo)) + + cor_flo = torch.cat([cor, flo], dim=1) + out = F.relu(self.conv(cor_flo)) + return torch.cat([out, delta_flow], dim=1) + + +class RefineFlow(nn.Module): + def __init__(self): + super(RefineFlow, self).__init__() + self.convc1 = nn.Conv2d(192, 128, 3, padding=1) + # self.convc2 = nn.Conv2d(128, 64, 3, padding=1) + self.conv1 = nn.Conv2d(256, 128, 3, padding=1) + self.conv2 = nn.Conv2d(128, 2, 3, padding=1) + self.convo1 = nn.Conv2d(256, 128, 3, padding=1) + self.convo2 = nn.Conv2d(128, 1, 3, padding=1) + + def forward(self, m_f, warp_f): + c = F.relu(self.convc1(warp_f)) + ## c = F.relu(self.convc2(c)) + inp = torch.cat([m_f, c], dim=1) + flow = self.conv2(F.relu(self.conv1(inp))) + occ = self.convo2(F.relu(self.convo1(inp))) + out = torch.cat([flow, occ], dim=1) + return out, inp + # return flow, inp + + +class RaftFlow(nn.Module): + def __init__( + self, + prior_only=False, + num_kp=10, + dim=256, + size=256, + generator=None, + driving_encoder=None, + source_encoder=None, + ): + super(RaftFlow, self).__init__() + + self.scale = dim**-0.5 + self.size = size + ## basic flow resolution of the prior motion model + self.h = size // 4 + self.w = size // 4 + self.prior_only = prior_only + self.generator = OcclusionAwareGenerator(**generator) + # self.down = AntiAliasInterpolation2d(3, 0.25) + + ## start iteration from resolution of size//2**5 + channels = { + size // 32: 512, + size // 16: 512, + size // 8: 512, + size // 4: 256, + size // 2: 128, + size: 64, + # 512: 32, + } + self.total_iter = int(math.log(2**5, 2)) + 1 + self.num_iter = int(math.log(2**5, 2)) + 1 + self.basic_res_index = int(math.log(self.h // (size // 32), 2)) + + if not self.prior_only: + ### driving and source structure encoder + self.kp = Hourglass(**driving_encoder) + self.kp_img = Hourglass(**source_encoder) + self.kp_head = nn.Conv2d( + in_channels=self.kp.out_filters, + out_channels=dim, + kernel_size=1, + padding=0, + ) + self.kp_img_head = nn.Conv2d( + in_channels=self.kp_img.out_filters, + out_channels=dim, + kernel_size=1, + padding=0, + ) + # self._make_position_embedding(64, 64, num_kp, 'sine-full') + # self.pos_embedding = rearrange(self.pos_embedding, 'b (h w) c -> b c h w', h=self.h, w=self.w) + self.pos_embedding = nn.Parameter(torch.zeros(1, num_kp, self.h, self.w)) + trunc_normal_(self.pos_embedding, std=0.02) + ### driving and source structure encoder + + ### flow updater + self.corr_enc = BasicMotionEncoder() + self.refine = RefineFlow() + ## project different-scale warped features to the same dimension + self.to_context = nn.ModuleList() + for i in range(self.num_iter): + f_channel = channels[(size // 32) * (2**i)] + self.to_context.append(nn.Conv2d(f_channel, 192, 1, padding=0)) + ### flow updater + + def forward(self, kp_s, kp_d, dense_motion, img, img_full): + feature = self.generator.encode(img_full) + if img is None: + img = self.down(img_full) + b, _, h, w = img.shape + + out_warp_f = [] + out_occlusion = [] + out_flow = [] + + out_warp_f_c = [] + out_occlusion_c = [] + + ### prior only + if self.prior_only: + flow = dense_motion["deformation"] + occlusion = dense_motion["occlusion"] + for i in range(self.total_iter): + if flow.shape[2] != feature[i].shape[2]: + flow_res = F.interpolate( + flow.permute(0, 3, 1, 2), + size=feature[i].shape[2:], + mode="bilinear", + align_corners=True, + ) + occlusion_res = F.interpolate( + occlusion, + size=feature[i].shape[2:], + mode="bilinear", + align_corners=True, + ) + else: + flow_res = flow.permute(0, 3, 1, 2) + occlusion_res = occlusion + out_warp_f.append( + F.grid_sample(feature[i], flow_res.permute(0, 2, 3, 1)) + ) + out_occlusion.append(F.sigmoid(occlusion_res)) + warp_img = F.grid_sample(img_full, flow_res.permute(0, 2, 3, 1)) + out = self.generator.decode(out_warp_f, warp_img, out_occlusion) + for i in range(len(out_occlusion)): + out_occlusion[i] = F.interpolate( + out_occlusion[i], + size=self.size, + mode="bilinear", + align_corners=True, + ) + occlusion = torch.cat(out_occlusion, dim=3) + return out, warp_img, occlusion + ### prior only + + ### compute correlation volume at basic resolution + kp_s = kp2gaussian(kp_s, (h, w), 0.1) + self.pos_embedding + kp_d = kp2gaussian(kp_d, (h, w), 0.1) + self.pos_embedding + fe_s = self.kp_img(torch.cat([kp_s, img], dim=1)) + fe_d = self.kp(kp_d) + k_s = self.kp_img_head(fe_s) + q_d = self.kp_head(fe_d) + f_s = rearrange(k_s, "b c h w -> b (h w) c", h=self.h, w=self.w) + f_d = rearrange(q_d, "b c h w -> b (h w) c", h=self.h, w=self.w) + corr_volume = torch.einsum("bic,bjc->bij", f_d, f_s) * self.scale + ### compute correlation volume at basic resolution + + ### prior motion initialization + id_grid = coords_grid(b, self.h, self.w, corr_volume.device) + init_flow = (self.h - 1) * ( + dense_motion["deformation"].permute(0, 3, 1, 2) + 1 + ) / 2.0 - id_grid + init_occlusion = dense_motion["occlusion"] + ### prior motion initialization + + # ### NPMR only initialization + # id_grid = coords_grid(b, self.h, self.w, corr_volume.device) + # id_grid = rearrange(id_grid, 'b c h w -> b (h w) c', h=self.h, w=self.w) + # init_flow = torch.einsum('bij,bjc->bic', corr_volume.softmax(-1), id_grid) + # init_flow = rearrange(init_flow, 'b (h w) c-> b c h w', h=self.h, w=self.w) + # id_grid = rearrange(id_grid, 'b (h w) c -> b c h w', h=self.h, w=self.w) + # init_flow = init_flow - id_grid + # init_occlusion = None + # ### NPMR only initialization + + ## start iter resolution: size//32 = (size//4) // 8, where size//4 defines the basic flow resolution + flow = ( + F.interpolate( + init_flow, scale_factor=1.0 / 8.0, mode="bilinear", align_corners=True + ) + / 8.0 + ) + occlusion = ( + F.interpolate( + init_occlusion, + scale_factor=1.0 / 8.0, + mode="bilinear", + align_corners=True, + ) + if init_occlusion is not None + else init_occlusion + ) + ## reshape for pooling the driving image dimension, fitting different resolutions + corr_volume = rearrange( + corr_volume, "b (h w) n -> (b n) h w", h=self.h, w=self.w + ).unsqueeze(1) + + for i in range(self.total_iter): + ## identity grid with current-iter resolution + id_grid = coords_grid( + b, + self.size // 32 * (2**i), + self.size // 32 * (2**i), + corr_volume.device, + ) + flow_sample = flow + id_grid_sample = id_grid + + ### computing upsampled or downsampled structure correlation volume + if i < self.basic_res_index: + corr_volume_res = F.avg_pool2d( + corr_volume, + 2 ** (self.basic_res_index - i), + stride=2 ** (self.basic_res_index - i), + ) + scale = 2 ** (self.basic_res_index - i) + elif i == self.basic_res_index: + corr_volume_res = corr_volume + scale = 1 + elif i > self.basic_res_index: + ## for resolution larger than basic flow resolution, sampling with the basic flow resolution + corr_volume_res = corr_volume + scale = 0.5 ** (i - self.basic_res_index) + flow_sample = ( + F.interpolate( + flow, size=self.h, mode="bilinear", align_corners=True + ) + * scale + ) + id_grid_sample = coords_grid(b, self.h, self.w, corr_volume.device) + scale = 1 + ### computing upsampled or downsampled structure correlation volume + + if i < self.num_iter: + ## reshape for pooling the source image dimension, thus to obtain correlation pyramids + corr_volume_res = rearrange( + corr_volume_res, "(b n) c h w -> (b h w) c n", n=self.h * self.w + ) + corr_volume_res = rearrange( + corr_volume_res, "b c (p q) -> b c p q", p=self.h, q=self.w + ) + ## building correlation pyramids + corr_fn = CorrBlock(corr_volume_res) + ## sampling correlation features from the pyramids + corr = corr_fn((flow_sample + id_grid_sample) * scale) + if i > self.basic_res_index: + ## reshape from basic resolution to current resolution + corr = F.interpolate( + corr, size=flow.shape[2], mode="bilinear", align_corners=True + ) + ## non-prior-based motion feature + m_f = self.corr_enc(flow, corr) + + warp_f = bilinear_sampler( + feature[i], (flow + id_grid).permute(0, 2, 3, 1) + ) + warp_f = F.relu(self.to_context[i](warp_f)) + + d_flow, _ = self.refine(m_f, warp_f) + flow_w = flow + d_flow[:, 0:2, :, :] + d_occ = d_flow[:, 2:, :, :] + if occlusion is not None: + occlusion = occlusion + d_occ + else: + occlusion = d_occ + else: + flow_w = flow + + out = bilinear_sampler(feature[i], (flow_w + id_grid).permute(0, 2, 3, 1)) + out_occlusion.append(F.sigmoid(occlusion)) + out_warp_f.append(out) + + ## coarse warping (prior motion flow) + if i != self.basic_res_index: + flow_res = F.interpolate( + dense_motion["deformation"].permute(0, 3, 1, 2), + size=feature[i].shape[2:], + mode="bilinear", + align_corners=True, + ) + occlusion_res = F.interpolate( + dense_motion["occlusion"], + size=feature[i].shape[2:], + mode="bilinear", + align_corners=True, + ) + else: + flow_res = dense_motion["deformation"].permute(0, 3, 1, 2) + occlusion_res = dense_motion["occlusion"] + out_warp_f_c.append(F.grid_sample(feature[i], flow_res.permute(0, 2, 3, 1))) + out_occlusion_c.append(F.sigmoid(occlusion_res)) + ## coarse warping (prior motion flow) + + ### updating flow by directly scaling initial flow + if i < self.num_iter - 1: + ## scale between basic resolution and updated resolution + scale = 2 ** (self.basic_res_index - i) / 2.0 + d_f = ( + F.interpolate( + d_flow[:, 0:2, :, :], + scale_factor=2, + mode="bilinear", + align_corners=True, + ) + * 2 + ) + ## flow refinement of current iteration + flow = ( + d_f + + F.interpolate( + init_flow, + size=self.size // 32 * (2 ** (i + 1)), + mode="bilinear", + align_corners=True, + ) + / scale + ) + ## flow refinement of previous iterations + if i == 0: + d_f_pre = d_f + else: + flow = ( + flow + + F.interpolate( + d_f_pre, scale_factor=2, mode="bilinear", align_corners=True + ) + * 2 + ) + d_f_pre = ( + d_f + + F.interpolate( + d_f_pre, scale_factor=2, mode="bilinear", align_corners=True + ) + * 2 + ) + ## occlusion refinement + d_occ = F.interpolate( + d_occ, scale_factor=2, mode="bilinear", align_corners=True + ) + occlusion = d_occ + F.interpolate( + init_occlusion, + size=self.size // 32 * (2 ** (i + 1)), + mode="bilinear", + align_corners=True, + ) + if i == 0: + d_occ_pre = d_occ + else: + occlusion = occlusion + F.interpolate( + d_occ_pre, scale_factor=2, mode="bilinear", align_corners=True + ) + d_occ_pre = d_occ + F.interpolate( + d_occ_pre, scale_factor=2, mode="bilinear", align_corners=True + ) + ## for num_iter < total_iter + # elif i < self.total_iter-1: + # ## without refinement + # flow = F.interpolate(flow, scale_factor=2, mode='bilinear',align_corners=True) * 2 + # occlusion = F.interpolate(occlusion, scale_factor=2, mode='bilinear',align_corners=True) + + warp_img = bilinear_sampler(img_full, (flow + id_grid).permute(0, 2, 3, 1)) + out = self.generator.decode( + out_warp_f, warp_img, out_occlusion, out_warp_f_c, out_occlusion_c + ) + + out_occlusion.append(F.sigmoid(init_occlusion)) + out_occlusion_vis = [] + for i in range(len(out_occlusion)): + out_occlusion_vis.append( + F.interpolate( + out_occlusion[i], + size=self.size, + mode="bilinear", + align_corners=True, + ) + ) + occlusion = torch.cat(out_occlusion_vis, dim=3) + + return out, warp_img, occlusion diff --git a/module_mrfa/transformer/hr_base.py b/module_mrfa/transformer/hr_base.py new file mode 100644 index 0000000..39f0500 --- /dev/null +++ b/module_mrfa/transformer/hr_base.py @@ -0,0 +1,727 @@ +# ------------------------------------------------------------------------------ +# Copyright (c) Microsoft +# Licensed under the MIT License. +# Written by Bin Xiao (Bin.Xiao@microsoft.com) +# Modified by Sen Yang (yangsenius@seu.edu.cn) +# ------------------------------------------------------------------------------ + +import copy +import logging +import os + +import torch +import torch.nn.functional as F +from torch import nn + +BN_MOMENTUM = 0.1 +logger = logging.getLogger(__name__) + + +def conv3x3(in_planes, out_planes, stride=1): + """3x3 convolution with padding""" + return nn.Conv2d( + in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False + ) + + +class BasicBlock(nn.Module): + expansion = 1 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(BasicBlock, self).__init__() + self.conv1 = conv3x3(inplanes, planes, stride) + self.bn1 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) + self.relu = nn.ReLU(inplace=True) + self.conv2 = conv3x3(planes, planes) + self.bn2 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(Bottleneck, self).__init__() + self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) + self.bn1 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) + self.conv2 = nn.Conv2d( + planes, planes, kernel_size=3, stride=stride, padding=1, bias=False + ) + self.bn2 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) + self.conv3 = nn.Conv2d( + planes, planes * self.expansion, kernel_size=1, bias=False + ) + self.bn3 = nn.BatchNorm2d(planes * self.expansion, momentum=BN_MOMENTUM) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +def _get_clones(module, N): + return nn.ModuleList([copy.deepcopy(module) for i in range(N)]) + + +def _get_activation_fn(activation): + """Return an activation function given a string""" + if activation == "relu": + return F.relu + if activation == "gelu": + return F.gelu + if activation == "glu": + return F.glu + raise RuntimeError(f"activation should be relu/gelu, not {activation}.") + + +class HighResolutionModule(nn.Module): + def __init__( + self, + num_branches, + blocks, + num_blocks, + num_inchannels, + num_channels, + fuse_method, + multi_scale_output=True, + ): + super(HighResolutionModule, self).__init__() + self._check_branches( + num_branches, blocks, num_blocks, num_inchannels, num_channels + ) + + self.num_inchannels = num_inchannels + self.fuse_method = fuse_method + self.num_branches = num_branches + + self.multi_scale_output = multi_scale_output + + self.branches = self._make_branches( + num_branches, blocks, num_blocks, num_channels + ) + self.fuse_layers = self._make_fuse_layers() + self.relu = nn.ReLU(True) + + def _check_branches( + self, num_branches, blocks, num_blocks, num_inchannels, num_channels + ): + if num_branches != len(num_blocks): + error_msg = "NUM_BRANCHES({}) <> NUM_BLOCKS({})".format( + num_branches, len(num_blocks) + ) + logger.error(error_msg) + raise ValueError(error_msg) + + if num_branches != len(num_channels): + error_msg = "NUM_BRANCHES({}) <> NUM_CHANNELS({})".format( + num_branches, len(num_channels) + ) + logger.error(error_msg) + raise ValueError(error_msg) + + if num_branches != len(num_inchannels): + error_msg = "NUM_BRANCHES({}) <> NUM_INCHANNELS({})".format( + num_branches, len(num_inchannels) + ) + logger.error(error_msg) + raise ValueError(error_msg) + + def _make_one_branch(self, branch_index, block, num_blocks, num_channels, stride=1): + downsample = None + if ( + stride != 1 + or self.num_inchannels[branch_index] + != num_channels[branch_index] * block.expansion + ): + downsample = nn.Sequential( + nn.Conv2d( + self.num_inchannels[branch_index], + num_channels[branch_index] * block.expansion, + kernel_size=1, + stride=stride, + bias=False, + ), + nn.BatchNorm2d( + num_channels[branch_index] * block.expansion, momentum=BN_MOMENTUM + ), + ) + + layers = [] + layers.append( + block( + self.num_inchannels[branch_index], + num_channels[branch_index], + stride, + downsample, + ) + ) + self.num_inchannels[branch_index] = num_channels[branch_index] * block.expansion + for i in range(1, num_blocks[branch_index]): + layers.append( + block(self.num_inchannels[branch_index], num_channels[branch_index]) + ) + + return nn.Sequential(*layers) + + def _make_branches(self, num_branches, block, num_blocks, num_channels): + branches = [] + + for i in range(num_branches): + branches.append(self._make_one_branch(i, block, num_blocks, num_channels)) + + return nn.ModuleList(branches) + + def _make_fuse_layers(self): + if self.num_branches == 1: + return None + + num_branches = self.num_branches + num_inchannels = self.num_inchannels + fuse_layers = [] + for i in range(num_branches if self.multi_scale_output else 1): + fuse_layer = [] + for j in range(num_branches): + if j > i: + fuse_layer.append( + nn.Sequential( + nn.Conv2d( + num_inchannels[j], + num_inchannels[i], + 1, + 1, + 0, + bias=False, + ), + nn.BatchNorm2d(num_inchannels[i]), + nn.Upsample(scale_factor=2 ** (j - i), mode="nearest"), + ) + ) + elif j == i: + fuse_layer.append(None) + else: + conv3x3s = [] + for k in range(i - j): + if k == i - j - 1: + num_outchannels_conv3x3 = num_inchannels[i] + conv3x3s.append( + nn.Sequential( + nn.Conv2d( + num_inchannels[j], + num_outchannels_conv3x3, + 3, + 2, + 1, + bias=False, + ), + nn.BatchNorm2d(num_outchannels_conv3x3), + ) + ) + else: + num_outchannels_conv3x3 = num_inchannels[j] + conv3x3s.append( + nn.Sequential( + nn.Conv2d( + num_inchannels[j], + num_outchannels_conv3x3, + 3, + 2, + 1, + bias=False, + ), + nn.BatchNorm2d(num_outchannels_conv3x3), + nn.ReLU(True), + ) + ) + fuse_layer.append(nn.Sequential(*conv3x3s)) + fuse_layers.append(nn.ModuleList(fuse_layer)) + + return nn.ModuleList(fuse_layers) + + def get_num_inchannels(self): + return self.num_inchannels + + def forward(self, x): + if self.num_branches == 1: + return [self.branches[0](x[0])] + + for i in range(self.num_branches): + x[i] = self.branches[i](x[i]) + + x_fuse = [] + + for i in range(len(self.fuse_layers)): + y = x[0] if i == 0 else self.fuse_layers[i][0](x[0]) + for j in range(1, self.num_branches): + if i == j: + y = y + x[j] + else: + y = y + self.fuse_layers[i][j](x[j]) + x_fuse.append(self.relu(y)) + + return x_fuse + + +blocks_dict = {"BASIC": BasicBlock, "BOTTLENECK": Bottleneck} + + +class HRNET_base(nn.Module): + def __init__(self, cfg, **kwargs): + self.inplanes = 64 + extra = cfg["MODEL"]["EXTRA"] + super(HRNET_base, self).__init__() + + # stem net + self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1, bias=False) + self.bn1 = nn.BatchNorm2d(64, momentum=BN_MOMENTUM) + self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1, bias=False) + self.bn2 = nn.BatchNorm2d(64, momentum=BN_MOMENTUM) + self.relu = nn.ReLU(inplace=True) + self.layer1 = self._make_layer(Bottleneck, 64, 4) + + self.stage2_cfg = extra["STAGE2"] + num_channels = self.stage2_cfg["NUM_CHANNELS"] + block = blocks_dict[self.stage2_cfg["BLOCK"]] + num_channels = [ + num_channels[i] * block.expansion for i in range(len(num_channels)) + ] + self.transition1 = self._make_transition_layer([256], num_channels) + self.stage2, pre_stage_channels = self._make_stage( + self.stage2_cfg, num_channels + ) + + self.stage3_cfg = extra["STAGE3"] + num_channels = self.stage3_cfg["NUM_CHANNELS"] + block = blocks_dict[self.stage3_cfg["BLOCK"]] + num_channels = [ + num_channels[i] * block.expansion for i in range(len(num_channels)) + ] + self.transition2 = self._make_transition_layer(pre_stage_channels, num_channels) + self.stage3, pre_stage_channels = self._make_stage( + self.stage3_cfg, num_channels, multi_scale_output=False + ) + + self.pretrained_layers = extra["PRETRAINED_LAYERS"] + + def _make_transition_layer(self, num_channels_pre_layer, num_channels_cur_layer): + num_branches_cur = len(num_channels_cur_layer) + num_branches_pre = len(num_channels_pre_layer) + + transition_layers = [] + for i in range(num_branches_cur): + if i < num_branches_pre: + if num_channels_cur_layer[i] != num_channels_pre_layer[i]: + transition_layers.append( + nn.Sequential( + nn.Conv2d( + num_channels_pre_layer[i], + num_channels_cur_layer[i], + 3, + 1, + 1, + bias=False, + ), + nn.BatchNorm2d(num_channels_cur_layer[i]), + nn.ReLU(inplace=True), + ) + ) + else: + transition_layers.append(None) + else: + conv3x3s = [] + for j in range(i + 1 - num_branches_pre): + inchannels = num_channels_pre_layer[-1] + outchannels = ( + num_channels_cur_layer[i] + if j == i - num_branches_pre + else inchannels + ) + conv3x3s.append( + nn.Sequential( + nn.Conv2d(inchannels, outchannels, 3, 2, 1, bias=False), + nn.BatchNorm2d(outchannels), + nn.ReLU(inplace=True), + ) + ) + transition_layers.append(nn.Sequential(*conv3x3s)) + + return nn.ModuleList(transition_layers) + + def _make_layer(self, block, planes, blocks, stride=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d( + self.inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + bias=False, + ), + nn.BatchNorm2d(planes * block.expansion, momentum=BN_MOMENTUM), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def _make_stage(self, layer_config, num_inchannels, multi_scale_output=True): + num_modules = layer_config["NUM_MODULES"] + num_branches = layer_config["NUM_BRANCHES"] + num_blocks = layer_config["NUM_BLOCKS"] + num_channels = layer_config["NUM_CHANNELS"] + block = blocks_dict[layer_config["BLOCK"]] + fuse_method = layer_config["FUSE_METHOD"] + + modules = [] + for i in range(num_modules): + # multi_scale_output is only used last module + if not multi_scale_output and i == num_modules - 1: + reset_multi_scale_output = False + else: + reset_multi_scale_output = True + + modules.append( + HighResolutionModule( + num_branches, + block, + num_blocks, + num_inchannels, + num_channels, + fuse_method, + reset_multi_scale_output, + ) + ) + num_inchannels = modules[-1].get_num_inchannels() + + return nn.Sequential(*modules), num_inchannels + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.conv2(x) + x = self.bn2(x) + x = self.relu(x) + x = self.layer1(x) + + x_list = [] + for i in range(self.stage2_cfg["NUM_BRANCHES"]): + if self.transition1[i] is not None: + x_list.append(self.transition1[i](x)) + else: + x_list.append(x) + y_list = self.stage2(x_list) + + x_list = [] + for i in range(self.stage3_cfg["NUM_BRANCHES"]): + if self.transition2[i] is not None: + x_list.append(self.transition2[i](y_list[-1])) + else: + x_list.append(y_list[i]) + y_list = self.stage3(x_list) + return y_list[0] + + def init_weights(self, pretrained="", print_load_info=False): + logger.info("=> init weights from normal distribution") + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.normal_(m.weight, std=0.001) + for name, _ in m.named_parameters(): + if name in ["bias"]: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.BatchNorm2d): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.ConvTranspose2d): + nn.init.normal_(m.weight, std=0.001) + for name, _ in m.named_parameters(): + if name in ["bias"]: + nn.init.constant_(m.bias, 0) + + if os.path.isfile(pretrained): + pretrained_state_dict = torch.load( + pretrained, map_location=torch.device("cpu") + ) + logger.info("=> loading pretrained model {}".format(pretrained)) + + existing_state_dict = {} + for name, m in pretrained_state_dict.items(): + if ( + name.split(".")[0] in self.pretrained_layers + and name in self.state_dict() + or self.pretrained_layers[0] is "*" + ): + existing_state_dict[name] = m + if print_load_info: + print(":: {} is loaded from {}".format(name, pretrained)) + self.load_state_dict(existing_state_dict, strict=False) + elif pretrained: + logger.error("=> please download pre-trained models first!") + raise ValueError("{} is not exist!".format(pretrained)) + + +class HRNET4_base(nn.Module): + def __init__(self, cfg, **kwargs): + self.inplanes = 64 + extra = cfg["MODEL"]["EXTRA"] + super(HRNET4_base, self).__init__() + + # stem net + self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1, bias=False) + self.bn1 = nn.BatchNorm2d(64, momentum=BN_MOMENTUM) + self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1, bias=False) + self.bn2 = nn.BatchNorm2d(64, momentum=BN_MOMENTUM) + self.relu = nn.ReLU(inplace=True) + self.layer1 = self._make_layer(Bottleneck, 64, 4) + + # stage 2 + self.stage2_cfg = extra["STAGE2"] + num_channels = self.stage2_cfg["NUM_CHANNELS"] + block = blocks_dict[self.stage2_cfg["BLOCK"]] + num_channels = [ + num_channels[i] * block.expansion for i in range(len(num_channels)) + ] + self.transition1 = self._make_transition_layer([256], num_channels) + self.stage2, pre_stage_channels = self._make_stage( + self.stage2_cfg, num_channels + ) + + # stage 3 + self.stage3_cfg = extra["STAGE3"] + num_channels = self.stage3_cfg["NUM_CHANNELS"] + block = blocks_dict[self.stage3_cfg["BLOCK"]] + num_channels = [ + num_channels[i] * block.expansion for i in range(len(num_channels)) + ] + self.transition2 = self._make_transition_layer(pre_stage_channels, num_channels) + self.stage3, pre_stage_channels = self._make_stage( + self.stage3_cfg, num_channels + ) + + # stage 4 + self.stage4_cfg = extra["STAGE4"] + num_channels = self.stage4_cfg["NUM_CHANNELS"] + block = blocks_dict[self.stage4_cfg["BLOCK"]] + num_channels = [ + num_channels[i] * block.expansion for i in range(len(num_channels)) + ] + self.transition3 = self._make_transition_layer(pre_stage_channels, num_channels) + self.stage4, pre_stage_channels = self._make_stage( + self.stage4_cfg, num_channels, multi_scale_output=False + ) + + self.pretrained_layers = extra["PRETRAINED_LAYERS"] + + def _make_transition_layer(self, num_channels_pre_layer, num_channels_cur_layer): + num_branches_cur = len(num_channels_cur_layer) + num_branches_pre = len(num_channels_pre_layer) + + transition_layers = [] + for i in range(num_branches_cur): + if i < num_branches_pre: + if num_channels_cur_layer[i] != num_channels_pre_layer[i]: + transition_layers.append( + nn.Sequential( + nn.Conv2d( + num_channels_pre_layer[i], + num_channels_cur_layer[i], + 3, + 1, + 1, + bias=False, + ), + nn.BatchNorm2d(num_channels_cur_layer[i]), + nn.ReLU(inplace=True), + ) + ) + else: + transition_layers.append(None) + else: + conv3x3s = [] + for j in range(i + 1 - num_branches_pre): + inchannels = num_channels_pre_layer[-1] + outchannels = ( + num_channels_cur_layer[i] + if j == i - num_branches_pre + else inchannels + ) + conv3x3s.append( + nn.Sequential( + nn.Conv2d(inchannels, outchannels, 3, 2, 1, bias=False), + nn.BatchNorm2d(outchannels), + nn.ReLU(inplace=True), + ) + ) + transition_layers.append(nn.Sequential(*conv3x3s)) + + return nn.ModuleList(transition_layers) + + def _make_layer(self, block, planes, blocks, stride=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d( + self.inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + bias=False, + ), + nn.BatchNorm2d(planes * block.expansion, momentum=BN_MOMENTUM), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def _make_stage(self, layer_config, num_inchannels, multi_scale_output=True): + num_modules = layer_config["NUM_MODULES"] + num_branches = layer_config["NUM_BRANCHES"] + num_blocks = layer_config["NUM_BLOCKS"] + num_channels = layer_config["NUM_CHANNELS"] + block = blocks_dict[layer_config["BLOCK"]] + fuse_method = layer_config["FUSE_METHOD"] + + modules = [] + for i in range(num_modules): + # multi_scale_output is only used last module + if not multi_scale_output and i == num_modules - 1: + reset_multi_scale_output = False + else: + reset_multi_scale_output = True + + modules.append( + HighResolutionModule( + num_branches, + block, + num_blocks, + num_inchannels, + num_channels, + fuse_method, + reset_multi_scale_output, + ) + ) + num_inchannels = modules[-1].get_num_inchannels() + + return nn.Sequential(*modules), num_inchannels + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.conv2(x) + x = self.bn2(x) + x = self.relu(x) + x = self.layer1(x) + + # stage 2 + x_list = [] + for i in range(self.stage2_cfg["NUM_BRANCHES"]): + if self.transition1[i] is not None: + x_list.append(self.transition1[i](x)) + else: + x_list.append(x) + y_list = self.stage2(x_list) + + # stage 3 + x_list = [] + for i in range(self.stage3_cfg["NUM_BRANCHES"]): + if self.transition2[i] is not None: + x_list.append(self.transition2[i](y_list[-1])) + else: + x_list.append(y_list[i]) + y_list = self.stage3(x_list) + + # stage 4 + x_list = [] + for i in range(self.stage4_cfg["NUM_BRANCHES"]): + if self.transition3[i] is not None: + x_list.append(self.transition3[i](y_list[-1])) + else: + x_list.append(y_list[i]) + y_list = self.stage4(x_list) + + return y_list[0] + + def init_weights(self, pretrained="", print_load_info=False): + logger.info("=> init weights from normal distribution") + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.normal_(m.weight, std=0.001) + for name, _ in m.named_parameters(): + if name in ["bias"]: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.BatchNorm2d): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.ConvTranspose2d): + nn.init.normal_(m.weight, std=0.001) + for name, _ in m.named_parameters(): + if name in ["bias"]: + nn.init.constant_(m.bias, 0) + + if os.path.isfile(pretrained): + pretrained_state_dict = torch.load(pretrained) + logger.info("=> loading pretrained model {}".format(pretrained)) + + existing_state_dict = {} + for name, m in pretrained_state_dict.items(): + if ( + name.split(".")[0] in self.pretrained_layers + and name in self.state_dict() + or self.pretrained_layers[0] is "*" + ): + existing_state_dict[name] = m + if print_load_info: + print(":: {} is loaded from {}".format(name, pretrained)) + self.load_state_dict(existing_state_dict, strict=False) + elif pretrained: + logger.error("=> please download pre-trained models first!") + raise ValueError("{} is not exist!".format(pretrained)) diff --git a/module_mrfa/transformer/pose_tokenpose_b.py b/module_mrfa/transformer/pose_tokenpose_b.py new file mode 100644 index 0000000..cc554fd --- /dev/null +++ b/module_mrfa/transformer/pose_tokenpose_b.py @@ -0,0 +1,61 @@ +import torch +import torch.nn as nn + +from .hr_base import HRNET_base +from .tokenpose_base import TokenPose_TB_base + + +class TokenPose_B(nn.Module): + def __init__(self, cfg, **kwargs): + super(TokenPose_B, self).__init__() + + extra = cfg["MODEL"]["EXTRA"] + self.cfg = cfg + print(cfg["MODEL"]) + ################################################## + self.pre_feature = HRNET_base(cfg, **kwargs) + self.transformer = TokenPose_TB_base( + feature_size=[cfg["MODEL"]["IMAGE_SIZE"][1] // 4, cfg["MODEL"]["IMAGE_SIZE"][0] // 4], + patch_size=[cfg["MODEL"]["PATCH_SIZE"][1], cfg["MODEL"]["PATCH_SIZE"][0]], + num_keypoints=cfg["MODEL"]["NUM_JOINTS"], + dim=cfg["MODEL"]["DIM"], + channels=cfg["MODEL"]["BASE_CHANNEL"], + depth=cfg["MODEL"]["TRANSFORMER_DEPTH"], + heads=cfg["MODEL"]["TRANSFORMER_HEADS"], + mlp_dim=cfg["MODEL"]["DIM"] * cfg["MODEL"]["TRANSFORMER_MLP_RATIO"], + apply_init=cfg["MODEL"]["INIT"], + hidden_heatmap_dim=cfg["MODEL"]["HEATMAP_SIZE"][1] + * cfg["MODEL"]["HEATMAP_SIZE"][0] + // 8, + heatmap_dim=cfg["MODEL"]["HEATMAP_SIZE"][1] * cfg["MODEL"]["HEATMAP_SIZE"][0], + heatmap_size=[cfg["MODEL"]["HEATMAP_SIZE"][1], cfg["MODEL"]["HEATMAP_SIZE"][0]], + pos_embedding_type=cfg["MODEL"]["POS_EMBEDDING_TYPE"], + estimate_jacobian=cfg["MODEL"]["ESTIMATE_JACOBIAN"], + temperature=cfg["MODEL"]["TEMPERATURE"], + fix_img2motion_attention=cfg["MODEL"]["FIX_IMG2MOTION_ATTENTION"], + ) + ################################################### + + def forward(self, x): + if self.cfg["MODEL"]["DATA_PREPROCESS"]: + mean = torch.tensor([0.485, 0.456, 0.406]).to(x.device) + std = torch.tensor([0.229, 0.224, 0.225]).to(x.device) + mean = mean.view(1, 3, 1, 1) + std = std.view(1, 3, 1, 1) + x = x - mean + x = x / std + x = self.pre_feature(x) + x = self.transformer(x) + return x + + def init_weights(self, pretrained=""): + self.pre_feature.init_weights(pretrained) + + +def get_pose_net(cfg, is_train, **kwargs): + # cfg = convert_dict_to_attrit_dict(cfg) + model = TokenPose_B(cfg, **kwargs) + if is_train and cfg["MODEL"]["INIT_WEIGHTS"]: + model.init_weights(cfg["MODEL"]["PRETRAINED"]) + + return model diff --git a/module_mrfa/transformer/tokenpose_base.py b/module_mrfa/transformer/tokenpose_base.py new file mode 100644 index 0000000..5531d7a --- /dev/null +++ b/module_mrfa/transformer/tokenpose_base.py @@ -0,0 +1,1236 @@ +import math + +import torch +import torch.nn.functional as F +from einops import rearrange, repeat +from timm.models.layers.weight_init import trunc_normal_ +from torch import nn + +from ..util import make_coordinate_grid + +MIN_NUM_PATCHES = 16 +BN_MOMENTUM = 0.1 + + +class Residual(nn.Module): + def __init__(self, fn, num_keypoints=10): + super().__init__() + self.fn = fn + self.num_keypoints = num_keypoints + + def forward(self, x, **kwargs): + return self.fn(x, **kwargs) + x + + +class PreNorm(nn.Module): + def __init__(self, dim, fn, fusion_factor=1): + super().__init__() + self.norm = nn.LayerNorm(dim * fusion_factor) + self.fn = fn + + def forward(self, x, **kwargs): + return self.fn(self.norm(x), **kwargs) + + +class QKVPreNorm(nn.Module): + def __init__(self, dim, fn, fusion_factor=1): + super().__init__() + self.norm = nn.LayerNorm(dim * fusion_factor) + self.fn = fn + + def forward(self, q, k, v, **kwargs): + return self.fn(self.norm(q), self.norm(k), self.norm(v), **kwargs) + + +class FeedForward(nn.Module): + def __init__(self, dim, hidden_dim, dropout=0.0): + super().__init__() + self.net = nn.Sequential( + nn.Linear(dim, hidden_dim), + nn.GELU(), + nn.Dropout(dropout), + nn.Linear(hidden_dim, dim), + nn.Dropout(dropout), + ) + + def forward(self, x): + return self.net(x) + + +class Attention(nn.Module): + def __init__( + self, + dim, + heads=8, + dropout=0.0, + num_keypoints=None, + scale_with_head=False, + fix_img2motion_attention=False, + num_img_tokens=None, + ): + super().__init__() + self.heads = heads + self.scale = (dim // heads) ** -0.5 if scale_with_head else dim**-0.5 + + self.to_qkv = nn.Linear(dim, dim * 3, bias=False) + self.to_out = nn.Sequential(nn.Linear(dim, dim), nn.Dropout(dropout)) + self.num_keypoints = num_keypoints + self.fix_img2motion_attention = fix_img2motion_attention + if fix_img2motion_attention: + num_tokens = num_keypoints + num_img_tokens + mask = torch.zeros(num_tokens, num_tokens) + mask[self.num_keypoints :, 0 : self.num_keypoints] += -1000.0 + mask = mask.unsqueeze(0).unsqueeze(0) + self.register_buffer("attn_mask", mask) + + # @get_local('dots') + # @get_local('attn') + def forward(self, x, mask=None): + b, n, _, h = *x.shape, self.heads + qkv = self.to_qkv(x).chunk(3, dim=-1) + q, k, v = map(lambda t: rearrange(t, "b n (h d) -> b h n d", h=h), qkv) + + dots = torch.einsum("bhid,bhjd->bhij", q, k) * self.scale + mask_value = -torch.finfo(dots.dtype).max + + if mask is not None: + mask = F.pad(mask.flatten(1), (1, 0), value=True) + assert mask.shape[-1] == dots.shape[-1], "mask has incorrect dimensions" + mask = mask[:, None, :] * mask[:, :, None] + dots.masked_fill_(~mask, mask_value) + del mask + + if self.fix_img2motion_attention: + dots += self.attn_mask + attn = dots.softmax(dim=-1) + # print(attn[0,0,-1,-1]) + out = torch.einsum("bhij,bhjd->bhid", attn, v) + out = rearrange(out, "b h n d -> b n (h d)") + out = self.to_out(out) + return out + + +class QKVAttention(nn.Module): + def __init__( + self, dim, heads=8, dropout=0.0, num_keypoints=None, scale_with_head=False + ): + super().__init__() + self.heads = heads + self.scale = (dim // heads) ** -0.5 if scale_with_head else dim**-0.5 + + self.to_k = nn.Linear(dim, dim, bias=False) + self.to_v = nn.Linear(dim, dim, bias=False) + self.to_q = nn.Linear(dim, dim, bias=False) + self.to_out = nn.Sequential(nn.Linear(dim, dim), nn.Dropout(dropout)) + self.num_keypoints = num_keypoints + + # @get_local('dots') + # @get_local('attn') + def forward(self, x, k=None, v=None, mask=None): + b, n, _, h = *x.shape, self.heads + q = self.to_q(x) + k = self.to_k(k) + v = self.to_v(v) + q = rearrange(q, "b n (h d) -> b h n d", h=h) + k = rearrange(k, "b n (h d) -> b h n d", h=h) + v = rearrange(v, "b n (h d) -> b h n d", h=h) + + dots = torch.einsum("bhid,bhjd->bhij", q, k) * self.scale + mask_value = -torch.finfo(dots.dtype).max + + if mask is not None: + mask = F.pad(mask.flatten(1), (1, 0), value=True) + assert mask.shape[-1] == dots.shape[-1], "mask has incorrect dimensions" + mask = mask[:, None, :] * mask[:, :, None] + dots.masked_fill_(~mask, mask_value) + del mask + attn = dots.softmax(dim=-1) + # print(attn[0,0,-1,-1]) + out = torch.einsum("bhij,bhjd->bhid", attn, v) + out = rearrange(out, "b h n d -> b n (h d)") + out = self.to_out(out) + return out + + +class Transformer(nn.Module): + def __init__( + self, + dim, + depth, + heads, + mlp_dim, + dropout, + num_keypoints=None, + all_attn=False, + scale_with_head=False, + fix_img2motion_attention=False, + num_patches=256, + ): + super().__init__() + self.layers = nn.ModuleList([]) + self.all_attn = all_attn + self.num_keypoints = num_keypoints + self.fix_img2motion_attention = fix_img2motion_attention + for d in range(depth): + # if not d < depth // 2: + # fix_img2motion_attention = False + self.layers.append( + nn.ModuleList( + [ + Residual( + PreNorm( + dim, + Attention( + dim, + heads=heads, + dropout=dropout, + num_keypoints=num_keypoints, + scale_with_head=scale_with_head, + fix_img2motion_attention=fix_img2motion_attention, + num_img_tokens=num_patches, + ), + ), + num_keypoints=self.num_keypoints, + ), + Residual( + PreNorm(dim, FeedForward(dim, mlp_dim, dropout=dropout)) + ), + ] + ) + ) + + def forward(self, x, mask=None, pos=None): + img_token = x[:, self.num_keypoints :] + for idx, (attn, ff) in enumerate(self.layers): + if idx > 0 and self.all_attn: + x[:, self.num_keypoints :] += pos + x = attn(x, mask=mask) + x = ff(x) + return x + + +class TransformerEncoder(nn.Module): + def __init__( + self, + dim, + depth, + heads, + mlp_dim, + dropout, + num_keypoints=None, + all_attn=False, + scale_with_head=False, + num_patches=256, + v_pos=False, + ): + super().__init__() + self.encoder = nn.ModuleList([]) + self.all_attn = all_attn + self.num_keypoints = num_keypoints + self.v_pos = v_pos + for d in range(depth): + self.encoder.append( + nn.ModuleList( + [ + Residual( + QKVPreNorm( + dim, + QKVAttention( + dim, + heads=heads, + dropout=dropout, + num_keypoints=num_keypoints, + scale_with_head=scale_with_head, + ), + ), + num_keypoints=self.num_keypoints, + ), + Residual( + PreNorm(dim, FeedForward(dim, mlp_dim, dropout=dropout)) + ), + ] + ) + ) + + def forward(self, x, mask=None, pos=None): + for idx, (attn, ff) in enumerate(self.encoder): + if self.all_attn: + if self.v_pos: + x[:, self.num_keypoints :] += pos + q = x + k = x + v = x + else: + v = x.clone() + x[:, self.num_keypoints :] += pos + q = x + k = x + x = attn(q, k=k, v=v, mask=mask) + x = ff(x) + return x + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(Bottleneck, self).__init__() + self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) + self.bn1 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) + self.conv2 = nn.Conv2d( + planes, planes, kernel_size=3, stride=stride, padding=1, bias=False + ) + self.bn2 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) + self.conv3 = nn.Conv2d( + planes, planes * self.expansion, kernel_size=1, bias=False + ) + self.bn3 = nn.BatchNorm2d(planes * self.expansion, momentum=BN_MOMENTUM) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class TokenPose_TB_base(nn.Module): + def __init__( + self, + *, + feature_size, + patch_size, + num_keypoints, + dim, + depth, + heads, + mlp_dim, + apply_init=False, + apply_multi=True, + hidden_heatmap_dim=64 * 6, + heatmap_dim=64 * 64, + heatmap_size=[64, 64], + channels=3, + dropout=0.0, + emb_dropout=0.0, + pos_embedding_type="sine-full", + estimate_jacobian=True, + temperature=0.1, + spatial_kp_head=False, + jacobian_token=True, + hidden_dim=False, + affine_jacobian=False, + fix_img2motion_attention=False, + ): + super().__init__() + assert isinstance(feature_size, list) and isinstance( + patch_size, list + ), "image_size and patch_size should be list" + assert ( + feature_size[0] % patch_size[0] == 0 + and feature_size[1] % patch_size[1] == 0 + ), "Image dimensions must be divisible by the patch size." + num_patches = (feature_size[0] // (patch_size[0])) * ( + feature_size[1] // (patch_size[1]) + ) + patch_dim = channels * patch_size[0] * patch_size[1] + # assert pos_embedding_type in ['sine','learnable','sine-full'] + + self.inplanes = 64 + self.patch_size = patch_size + self.heatmap_size = heatmap_size + self.num_keypoints = num_keypoints + self.num_patches = num_patches + self.pos_embedding_type = pos_embedding_type + self.all_attn = self.pos_embedding_type == "sine-full" + + self.jacobian_token = jacobian_token + if jacobian_token: + ## additional token for jacobian + num_keypoints = 2 * num_keypoints + self.num_keypoints = num_keypoints + self.keypoint_token = nn.Parameter(torch.zeros(1, self.num_keypoints, dim)) + h, w = ( + feature_size[0] // (self.patch_size[0]), + feature_size[1] // (self.patch_size[1]), + ) + self._make_position_embedding(w, h, dim, pos_embedding_type) + + self.patch_to_embedding = nn.Linear(patch_dim, dim) + self.dropout = nn.Dropout(emb_dropout) + + # transformer + self.transformer = Transformer( + dim, + depth, + heads, + mlp_dim, + dropout, + num_keypoints=num_keypoints, + all_attn=self.all_attn, + scale_with_head=True, + fix_img2motion_attention=fix_img2motion_attention, + num_patches=num_patches, + ) + + self.to_keypoint_token = nn.Identity() + ## original mlphead for keypoint heatmap prediction + self.spatial_kp_head = spatial_kp_head + if spatial_kp_head: + self.mlp_head = ( + nn.Sequential( + nn.LayerNorm(dim), + nn.Linear(dim, hidden_heatmap_dim), + nn.LayerNorm(hidden_heatmap_dim), + nn.Linear(hidden_heatmap_dim, heatmap_dim), + ) + if (dim <= hidden_heatmap_dim * 0.5 and apply_multi) + else nn.Sequential(nn.LayerNorm(dim), nn.Linear(dim, heatmap_dim)) + ) + self.temperature = temperature + ## original mlphead for keypoint heatmap prediction + else: + if hidden_dim: + self.mlp_head = nn.Sequential( + nn.LayerNorm(dim), + nn.Linear(dim, dim), + nn.LayerNorm(dim), + nn.Linear(dim, 2), + ) + else: + self.mlp_head = nn.Sequential( + nn.LayerNorm(dim), + nn.Linear(dim, 2), + ) + trunc_normal_(self.keypoint_token, std=0.02) + if apply_init: + self.apply(self._init_weights) + + ## light-weight head for direct jacobian regression + if estimate_jacobian: + if hidden_dim: + self.mlp_head_jacobian = nn.Sequential( + nn.LayerNorm(dim), + nn.Linear(dim, dim), + nn.LayerNorm(dim), + nn.Linear(dim, 4), + ) + self._init_weights(self.mlp_head_jacobian[0]) + self._init_weights(self.mlp_head_jacobian[1]) + self._init_weights(self.mlp_head_jacobian[2]) + else: + self.mlp_head_jacobian = nn.Sequential( + nn.LayerNorm(dim), + nn.Linear(dim, 4), + ) + self._init_weights(self.mlp_head_jacobian[0]) + self.mlp_head_jacobian[-1].weight.data.zero_() + self.mlp_head_jacobian[-1].bias.data.copy_( + torch.tensor([1, 0, 0, 1], dtype=torch.float) + ) + self.affine_jacobian = affine_jacobian + ## light-weight head for direct keypoint and jacobian regression + + def _make_position_embedding(self, w, h, d_model, pe_type="sine"): + """ + d_model: embedding size in transformer encoder + """ + assert pe_type in ["none", "learnable", "sine", "sine-full"] + if pe_type == "none": + self.pos_embedding = None + print("==> Without any PositionEmbedding~") + else: + with torch.no_grad(): + self.pe_h = h + self.pe_w = w + length = self.pe_h * self.pe_w + if pe_type == "learnable": + self.pos_embedding = nn.Parameter( + torch.zeros(1, self.num_patches + self.num_keypoints, d_model) + ) + trunc_normal_(self.pos_embedding, std=0.02) + print("==> Add Learnable PositionEmbedding~") + else: + self.pos_embedding = nn.Parameter( + self._make_sine_position_embedding(d_model), requires_grad=False + ) + print("==> Add Sine PositionEmbedding~") + + def _make_sine_position_embedding( + self, d_model, temperature=10000, scale=2 * math.pi + ): + h, w = self.pe_h, self.pe_w + area = torch.ones(1, h, w) # [b, h, w] + y_embed = area.cumsum(1, dtype=torch.float32) + x_embed = area.cumsum(2, dtype=torch.float32) + + one_direction_feats = d_model // 2 + + eps = 1e-6 + y_embed = y_embed / (y_embed[:, -1:, :] + eps) * scale + x_embed = x_embed / (x_embed[:, :, -1:] + eps) * scale + + dim_t = torch.arange(one_direction_feats, dtype=torch.float32) + dim_t = temperature ** (2 * (dim_t // 2) / one_direction_feats) + + pos_x = x_embed[:, :, :, None] / dim_t + pos_y = y_embed[:, :, :, None] / dim_t + pos_x = torch.stack( + (pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4 + ).flatten(3) + pos_y = torch.stack( + (pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4 + ).flatten(3) + pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2) + pos = pos.flatten(2).permute(0, 2, 1) + return pos + + def _make_layer(self, block, planes, blocks, stride=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d( + self.inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + bias=False, + ), + nn.BatchNorm2d(planes * block.expansion, momentum=BN_MOMENTUM), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def _init_weights(self, m): + # print("Initialization...") + if isinstance(m, nn.Linear): + trunc_normal_(m.weight, std=0.02) + if isinstance(m, nn.Linear) and m.bias is not None: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.LayerNorm): + nn.init.constant_(m.bias, 0) + nn.init.constant_(m.weight, 1.0) + + def gaussian2kp(self, heatmap): + """ + Extract the mean and from a heatmap + """ + shape = heatmap.shape + heatmap = heatmap.unsqueeze(-1) + grid = ( + make_coordinate_grid(shape[2:], heatmap.type()).unsqueeze_(0).unsqueeze_(0) + ) + value = (heatmap * grid).sum(dim=(2, 3)) # N * 10 * 2 + kp = {"value": value} + + return kp + + def forward(self, feature, mask=None): + p = self.patch_size + # transformer + x = rearrange( + feature, "b c (h p1) (w p2) -> b (h w) (p1 p2 c)", p1=p[0], p2=p[1] + ) + # print(x.shape) + x = self.patch_to_embedding(x) + + b, n, _ = x.shape + + keypoint_tokens = repeat(self.keypoint_token, "() n d -> b n d", b=b) + if self.pos_embedding_type in ["sine", "sine-full"]: + x += self.pos_embedding[:, :n] + x = torch.cat((keypoint_tokens, x), dim=1) + elif self.pos_embedding_type == "none": + x = torch.cat((keypoint_tokens, x), dim=1) + else: + x = torch.cat((keypoint_tokens, x), dim=1) + x += self.pos_embedding[:, : (n + self.num_keypoints)] + x = self.dropout(x) + + x = self.transformer(x, mask, self.pos_embedding) + if self.jacobian_token: + x_keypoint = self.to_keypoint_token(x[:, 0 : self.num_keypoints // 2]) + else: + x_keypoint = self.to_keypoint_token(x[:, 0 : self.num_keypoints]) + if self.spatial_kp_head: + heatmap = self.mlp_head(x_keypoint) + heatmap = F.softmax(heatmap / self.temperature, dim=2) + heatmap = rearrange( + heatmap, + "b c (p1 p2) -> b c p1 p2", + p1=self.heatmap_size[0], + p2=self.heatmap_size[1], + ) + out = self.gaussian2kp(heatmap) + out["heatmap"] = heatmap + else: + # keypoint = torch.tanh(self.mlp_head(x_keypoint)) + keypoint = 2 * F.sigmoid(self.mlp_head(x_keypoint)) - 1 + out = {"kp": keypoint} + if self.mlp_head_jacobian is not None: + if self.jacobian_token: + x_jacobian = self.to_keypoint_token( + x[:, self.num_keypoints // 2 : self.num_keypoints] + ) + else: + x_jacobian = x_keypoint + jacobian = self.mlp_head_jacobian(x_jacobian) + if self.affine_jacobian: + theta = jacobian[:, :, 0:2] + theta = theta / (torch.norm(theta, p=2, dim=-1, keepdim=True) + 1e-10) + cos_theta = theta[:, :, 0:1] + sin_theta = theta[:, :, 1:2] + rotate = torch.cat( + (cos_theta, -sin_theta, sin_theta, cos_theta), dim=-1 + ) + rotate = rearrange(rotate, "b c (p1 p2) -> b c p1 p2", p1=2, p2=2) + + scale = jacobian[:, :, 2:] + # scale = torch.abs(jacobian[:,:,2:]) + 0.1 + scale = torch.tanh(scale) * 0.9 + 1 + scale = 1 / scale + scale_x = scale[:, :, 0:1] + scale_y = scale[:, :, 1:2] + scale = torch.cat( + ( + scale_x, + torch.zeros(scale_x.shape).to(x.device), + torch.zeros(scale_x.shape).to(x.device), + scale_y, + ), + dim=-1, + ) + scale = rearrange(scale, "b c (p1 p2) -> b c p1 p2", p1=2, p2=2) + + jacobian = torch.matmul(rotate, scale) + else: + jacobian = rearrange(jacobian, "b c (p1 p2) -> b c p1 p2", p1=2, p2=2) + out["jacobian"] = jacobian + return out + + +class TokenPose_S_base(nn.Module): + def __init__( + self, + *, + image_size, + patch_size, + num_keypoints, + dim, + depth, + heads, + mlp_dim, + apply_init=False, + apply_multi=True, + hidden_heatmap_dim=64 * 6, + heatmap_dim=64 * 48, + heatmap_size=[64, 48], + channels=3, + dropout=0.0, + emb_dropout=0.0, + pos_embedding_type="learnable", + estimate_jacobian=False, + temperature=0.1, + jacobian_token=True, + hidden_dim=False, + affine_jacobian=False, + ): + super().__init__() + assert isinstance(image_size, list) and isinstance( + patch_size, list + ), "image_size and patch_size should be list" + assert ( + image_size[0] % patch_size[0] == 0 and image_size[1] % patch_size[1] == 0 + ), "Image dimensions must be divisible by the patch size." + num_patches = (image_size[0] // (4 * patch_size[0])) * ( + image_size[1] // (4 * patch_size[1]) + ) + patch_dim = channels * patch_size[0] * patch_size[1] + assert ( + num_patches > MIN_NUM_PATCHES + ), f"your number of patches ({num_patches}) is way too small for attention to be effective (at least 16). Try decreasing your patch size" + assert pos_embedding_type in ["sine", "none", "learnable", "sine-full"] + + self.inplanes = 64 + self.patch_size = patch_size + self.heatmap_size = heatmap_size + self.num_keypoints = num_keypoints + self.num_patches = num_patches + self.pos_embedding_type = pos_embedding_type + self.all_attn = self.pos_embedding_type == "sine-full" + + self.jacobian_token = jacobian_token + if jacobian_token: + ## additional token for jacobian + num_keypoints = 2 * num_keypoints + self.num_keypoints = num_keypoints + + self.keypoint_token = nn.Parameter(torch.zeros(1, self.num_keypoints, dim)) + h, w = ( + image_size[0] // (4 * self.patch_size[0]), + image_size[1] // (4 * self.patch_size[1]), + ) + self._make_position_embedding(w, h, dim, pos_embedding_type) + + self.patch_to_embedding = nn.Linear(patch_dim, dim) + self.dropout = nn.Dropout(emb_dropout) + + # stem net + self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1, bias=False) + self.bn1 = nn.BatchNorm2d(64, momentum=BN_MOMENTUM) + self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1, bias=False) + self.bn2 = nn.BatchNorm2d(64, momentum=BN_MOMENTUM) + self.relu = nn.ReLU(inplace=True) + self.layer1 = self._make_layer(Bottleneck, 64, 4) + + # transformer + self.transformer = Transformer( + dim, + depth, + heads, + mlp_dim, + dropout, + num_keypoints=num_keypoints, + all_attn=self.all_attn, + ) + + self.to_keypoint_token = nn.Identity() + + if hidden_dim: + self.mlp_head = nn.Sequential( + nn.LayerNorm(dim), + nn.Linear(dim, dim), + nn.LayerNorm(dim), + nn.Linear(dim, 2), + ) + else: + self.mlp_head = nn.Sequential( + nn.LayerNorm(dim), + nn.Linear(dim, 2), + ) + trunc_normal_(self.keypoint_token, std=0.02) + if apply_init: + self.apply(self._init_weights) + + ## light-weight head for direct jacobian regression + if estimate_jacobian: + if hidden_dim: + self.mlp_head_jacobian = nn.Sequential( + nn.LayerNorm(dim), + nn.Linear(dim, dim), + nn.LayerNorm(dim), + nn.Linear(dim, 4), + ) + self._init_weights(self.mlp_head_jacobian[0]) + self._init_weights(self.mlp_head_jacobian[1]) + self._init_weights(self.mlp_head_jacobian[2]) + else: + self.mlp_head_jacobian = nn.Sequential( + nn.LayerNorm(dim), + nn.Linear(dim, 4), + ) + self._init_weights(self.mlp_head_jacobian[0]) + self.mlp_head_jacobian[-1].weight.data.zero_() + self.mlp_head_jacobian[-1].bias.data.copy_( + torch.tensor([1, 0, 0, 1], dtype=torch.float) + ) + self.temperature = temperature + self.affine_jacobian = affine_jacobian + ## light-weight head for direct keypoint and jacobian regression + + def _make_position_embedding(self, w, h, d_model, pe_type="sine"): + """ + d_model: embedding size in transformer encoder + """ + assert pe_type in ["none", "learnable", "sine", "sine-full"] + if pe_type == "none": + self.pos_embedding = None + print("==> Without any PositionEmbedding~") + else: + with torch.no_grad(): + self.pe_h = h + self.pe_w = w + length = self.pe_h * self.pe_w + if pe_type == "learnable": + self.pos_embedding = nn.Parameter( + torch.zeros(1, self.num_patches + self.num_keypoints, d_model) + ) + trunc_normal_(self.pos_embedding, std=0.02) + print("==> Add Learnable PositionEmbedding~") + else: + self.pos_embedding = nn.Parameter( + self._make_sine_position_embedding(d_model), requires_grad=False + ) + print("==> Add Sine PositionEmbedding~") + + def _make_sine_position_embedding( + self, d_model, temperature=10000, scale=2 * math.pi + ): + h, w = self.pe_h, self.pe_w + area = torch.ones(1, h, w) # [b, h, w] + y_embed = area.cumsum(1, dtype=torch.float32) + x_embed = area.cumsum(2, dtype=torch.float32) + + one_direction_feats = d_model // 2 + + eps = 1e-6 + y_embed = y_embed / (y_embed[:, -1:, :] + eps) * scale + x_embed = x_embed / (x_embed[:, :, -1:] + eps) * scale + + dim_t = torch.arange(one_direction_feats, dtype=torch.float32) + dim_t = temperature ** (2 * (dim_t // 2) / one_direction_feats) + + pos_x = x_embed[:, :, :, None] / dim_t + pos_y = y_embed[:, :, :, None] / dim_t + pos_x = torch.stack( + (pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4 + ).flatten(3) + pos_y = torch.stack( + (pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4 + ).flatten(3) + pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2) + pos = pos.flatten(2).permute(0, 2, 1) + return pos + + def _make_layer(self, block, planes, blocks, stride=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d( + self.inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + bias=False, + ), + nn.BatchNorm2d(planes * block.expansion, momentum=BN_MOMENTUM), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def _init_weights(self, m): + # print("Initialization...") + if isinstance(m, nn.Linear): + trunc_normal_(m.weight, std=0.02) + if isinstance(m, nn.Linear) and m.bias is not None: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.LayerNorm): + nn.init.constant_(m.bias, 0) + nn.init.constant_(m.weight, 1.0) + + def forward(self, img, mask=None): + p = self.patch_size + # stem net + x = self.conv1(img) + x = self.bn1(x) + x = self.relu(x) + x = self.conv2(x) + x = self.bn2(x) + x = self.relu(x) + x = self.layer1(x) + + # transformer + x = rearrange(x, "b c (h p1) (w p2) -> b (h w) (p1 p2 c)", p1=p[0], p2=p[1]) + x = self.patch_to_embedding(x) + b, n, _ = x.shape + + keypoint_tokens = repeat(self.keypoint_token, "() n d -> b n d", b=b) + if self.pos_embedding_type in ["sine", "sine-full"]: # + x += self.pos_embedding[:, :n] + x = torch.cat((keypoint_tokens, x), dim=1) + elif self.pos_embedding_type == "learnable": + x = torch.cat((keypoint_tokens, x), dim=1) + x += self.pos_embedding[:, : (n + self.num_keypoints)] + x = self.dropout(x) + + x = self.transformer(x, mask, self.pos_embedding) + x_keypoint = self.to_keypoint_token(x[:, 0 : self.num_keypoints // 2]) + keypoint = 2 * F.sigmoid(self.mlp_head(x_keypoint)) - 1 + out = {"value": keypoint} + if self.mlp_head_jacobian is not None: + x_jacobian = self.to_keypoint_token( + x[:, self.num_keypoints // 2 : self.num_keypoints] + ) + # x_jacobian = x_keypoint + jacobian = self.mlp_head_jacobian(x_jacobian) + if self.affine_jacobian: + theta = jacobian[:, :, 0:2] + theta = theta / (torch.norm(theta, p=2, dim=-1, keepdim=True) + 1e-10) + cos_theta = theta[:, :, 0:1] + sin_theta = theta[:, :, 1:2] + rotate = torch.cat( + (cos_theta, -sin_theta, sin_theta, cos_theta), dim=-1 + ) + rotate = rearrange(rotate, "b c (p1 p2) -> b c p1 p2", p1=2, p2=2) + + scale = torch.abs(jacobian[:, :, 2:]) + 0.1 + # scale = torch.tanh(scale)*0.9+1 + # scale = 1/scale + scale_x = scale[:, :, 0:1] + scale_y = scale[:, :, 1:2] + scale = torch.cat( + ( + scale_x, + torch.zeros(scale_x.shape).to(x.device), + torch.zeros(scale_x.shape).to(x.device), + scale_y, + ), + dim=-1, + ) + scale = rearrange(scale, "b c (p1 p2) -> b c p1 p2", p1=2, p2=2) + + jacobian = torch.matmul(rotate, scale) + else: + # jacobian = rearrange(jacobian,'b c (p0 p1 p2) -> b c p0 p1 p2',p0=4,p1=self.heatmap_size[0],p2=self.heatmap_size[1]) + # jacobian = heatmap.unsqueeze(2) * jacobian + # jacobian = jacobian.sum(-1).sum(-1) + jacobian = rearrange(jacobian, "b c (p1 p2) -> b c p1 p2", p1=2, p2=2) + out["jacobian"] = jacobian + return out + + +class TokenPose_L_base(nn.Module): + def __init__( + self, + *, + feature_size, + patch_size, + num_keypoints, + dim, + depth, + heads, + mlp_dim, + apply_init=False, + hidden_heatmap_dim=64 * 6, + heatmap_dim=64 * 48, + heatmap_size=[64, 48], + channels=3, + dropout=0.0, + emb_dropout=0.0, + pos_embedding_type="learnable", + estimate_jacobian=False, + temperature=0.1, + jacobian_token=True, + hidden_dim=False, + affine_jacobian=False, + ): + super().__init__() + assert isinstance(feature_size, list) and isinstance( + patch_size, list + ), "image_size and patch_size should be list" + assert ( + feature_size[0] % patch_size[0] == 0 + and feature_size[1] % patch_size[1] == 0 + ), "Image dimensions must be divisible by the patch size." + num_patches = (feature_size[0] // (patch_size[0])) * ( + feature_size[1] // (patch_size[1]) + ) + patch_dim = channels * patch_size[0] * patch_size[1] + assert pos_embedding_type in ["sine", "learnable", "sine-full"] + + self.inplanes = 64 + self.patch_size = patch_size + self.heatmap_size = heatmap_size + self.num_keypoints = num_keypoints + self.num_patches = num_patches + self.pos_embedding_type = pos_embedding_type + self.all_attn = self.pos_embedding_type == "sine-full" + + self.jacobian_token = jacobian_token + if jacobian_token: + ## additional token for jacobian + num_keypoints = 2 * num_keypoints + self.num_keypoints = num_keypoints + self.keypoint_token = nn.Parameter(torch.zeros(1, self.num_keypoints, dim)) + h, w = ( + feature_size[0] // (self.patch_size[0]), + feature_size[1] // (self.patch_size[1]), + ) + + # for normal + self._make_position_embedding(w, h, dim, pos_embedding_type) + + self.patch_to_embedding = nn.Linear(patch_dim, dim) + self.dropout = nn.Dropout(emb_dropout) + + # transformer + self.transformer1 = Transformer( + dim, + depth, + heads, + mlp_dim, + dropout, + num_keypoints=num_keypoints, + all_attn=self.all_attn, + scale_with_head=True, + ) + self.transformer2 = Transformer( + dim, + depth, + heads, + mlp_dim, + dropout, + num_keypoints=num_keypoints, + all_attn=self.all_attn, + scale_with_head=True, + ) + self.transformer3 = Transformer( + dim, + depth, + heads, + mlp_dim, + dropout, + num_keypoints=num_keypoints, + all_attn=self.all_attn, + scale_with_head=True, + ) + + self.to_keypoint_token = nn.Identity() + if hidden_dim: + self.mlp_head = nn.Sequential( + # nn.LayerNorm(dim), + # nn.Linear(dim, dim), + # nn.LayerNorm(dim), + # nn.Linear(dim, 2), + nn.LayerNorm(dim * 3), + nn.Linear(dim * 3, dim * 3), + nn.LayerNorm(dim * 3), + nn.Linear(dim * 3, 2), + ) + else: + self.mlp_head = nn.Sequential( + # nn.LayerNorm(dim), + # nn.Linear(dim, 2), + nn.LayerNorm(dim * 3), + nn.Linear(dim * 3, 2), + ) + trunc_normal_(self.keypoint_token, std=0.02) + if apply_init: + self.apply(self._init_weights) + + ## light-weight head for direct jacobian regression + if estimate_jacobian: + if hidden_dim: + self.mlp_head_jacobian = nn.Sequential( + # nn.LayerNorm(dim), + # nn.Linear(dim, dim), + # nn.LayerNorm(dim), + # nn.Linear(dim, 4), + nn.LayerNorm(dim * 3), + nn.Linear(dim * 3, dim * 3), + nn.LayerNorm(dim * 3), + nn.Linear(dim * 3, 4), + ) + self._init_weights(self.mlp_head_jacobian[0]) + self._init_weights(self.mlp_head_jacobian[1]) + self._init_weights(self.mlp_head_jacobian[2]) + else: + self.mlp_head_jacobian = nn.Sequential( + # nn.LayerNorm(dim), + # nn.Linear(dim, 4), + nn.LayerNorm(dim * 3), + nn.Linear(dim * 3, 4), + ) + self._init_weights(self.mlp_head_jacobian[0]) + self.mlp_head_jacobian[-1].weight.data.zero_() + self.mlp_head_jacobian[-1].bias.data.copy_( + torch.tensor([1, 0, 0, 1], dtype=torch.float) + ) + self.temperature = temperature + self.affine_jacobian = affine_jacobian + ## light-weight head for direct keypoint and jacobian regression + + def _make_position_embedding(self, w, h, d_model, pe_type="sine"): + """ + d_model: embedding size in transformer encoder + """ + assert pe_type in ["none", "learnable", "sine", "sine-full"] + if pe_type == "none": + self.pos_embedding = None + print("==> Without any PositionEmbedding~") + else: + with torch.no_grad(): + self.pe_h = h + self.pe_w = w + length = self.pe_h * self.pe_w + if pe_type == "learnable": + self.pos_embedding = nn.Parameter( + torch.zeros(1, self.num_patches + self.num_keypoints, d_model) + ) + trunc_normal_(self.pos_embedding, std=0.02) + print("==> Add Learnable PositionEmbedding~") + else: + self.pos_embedding = nn.Parameter( + self._make_sine_position_embedding(d_model), requires_grad=False + ) + print("==> Add Sine PositionEmbedding~") + + def _make_sine_position_embedding( + self, d_model, temperature=10000, scale=2 * math.pi + ): + h, w = self.pe_h, self.pe_w + area = torch.ones(1, h, w) # [b, h, w] + y_embed = area.cumsum(1, dtype=torch.float32) + x_embed = area.cumsum(2, dtype=torch.float32) + + one_direction_feats = d_model // 2 + + eps = 1e-6 + y_embed = y_embed / (y_embed[:, -1:, :] + eps) * scale + x_embed = x_embed / (x_embed[:, :, -1:] + eps) * scale + + dim_t = torch.arange(one_direction_feats, dtype=torch.float32) + dim_t = temperature ** (2 * (dim_t // 2) / one_direction_feats) + + pos_x = x_embed[:, :, :, None] / dim_t + pos_y = y_embed[:, :, :, None] / dim_t + pos_x = torch.stack( + (pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4 + ).flatten(3) + pos_y = torch.stack( + (pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4 + ).flatten(3) + pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2) + pos = pos.flatten(2).permute(0, 2, 1) + return pos + + def _make_layer(self, block, planes, blocks, stride=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d( + self.inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + bias=False, + ), + nn.BatchNorm2d(planes * block.expansion, momentum=BN_MOMENTUM), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def _init_weights(self, m): + # print("Initialization...") + if isinstance(m, nn.Linear): + trunc_normal_(m.weight, std=0.02) + if isinstance(m, nn.Linear) and m.bias is not None: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.LayerNorm): + nn.init.constant_(m.bias, 0) + nn.init.constant_(m.weight, 1.0) + + def forward(self, feature, mask=None): + p = self.patch_size + # transformer + x = rearrange( + feature, "b c (h p1) (w p2) -> b (h w) (p1 p2 c)", p1=p[0], p2=p[1] + ) + x = self.patch_to_embedding(x) + + b, n, _ = x.shape + + keypoint_tokens = repeat(self.keypoint_token, "() n d -> b n d", b=b) + if self.pos_embedding_type in ["sine", "sine-full"]: + x += self.pos_embedding[:, :n] + x = torch.cat((keypoint_tokens, x), dim=1) + else: + x = torch.cat((keypoint_tokens, x), dim=1) + x += self.pos_embedding[:, : (n + self.num_keypoints)] + x = self.dropout(x) + + x1 = self.transformer1(x, mask, self.pos_embedding) + x2 = self.transformer2(x1, mask, self.pos_embedding) + x3 = self.transformer3(x2, mask, self.pos_embedding) + + x1_out = self.to_keypoint_token(x1[:, 0 : self.num_keypoints]) + x2_out = self.to_keypoint_token(x2[:, 0 : self.num_keypoints]) + x3_out = self.to_keypoint_token(x3[:, 0 : self.num_keypoints]) + + x = torch.cat((x1_out, x2_out, x3_out), dim=2) + # x = x3 + + if self.jacobian_token: + x_keypoint = self.to_keypoint_token(x[:, 0 : self.num_keypoints // 2]) + else: + x_keypoint = self.to_keypoint_token(x[:, 0 : self.num_keypoints]) + keypoint = 2 * F.sigmoid(self.mlp_head(x_keypoint)) - 1 + out = {"value": keypoint} + if self.mlp_head_jacobian is not None: + if self.jacobian_token: + x_jacobian = self.to_keypoint_token( + x[:, self.num_keypoints // 2 : self.num_keypoints] + ) + else: + x_jacobian = x_keypoint + jacobian = self.mlp_head_jacobian(x_jacobian) + if self.affine_jacobian: + theta = jacobian[:, :, 0:2] + theta = theta / (torch.norm(theta, p=2, dim=-1, keepdim=True) + 1e-10) + cos_theta = theta[:, :, 0:1] + sin_theta = theta[:, :, 1:2] + rotate = torch.cat( + (cos_theta, -sin_theta, sin_theta, cos_theta), dim=-1 + ) + rotate = rearrange(rotate, "b c (p1 p2) -> b c p1 p2", p1=2, p2=2) + + scale = torch.abs(jacobian[:, :, 2:]) + 0.1 + # scale = torch.tanh(scale)*0.9+1 + # scale = 1/scale + scale_x = scale[:, :, 0:1] + scale_y = scale[:, :, 1:2] + scale = torch.cat( + ( + scale_x, + torch.zeros(scale_x.shape).to(x.device), + torch.zeros(scale_x.shape).to(x.device), + scale_y, + ), + dim=-1, + ) + scale = rearrange(scale, "b c (p1 p2) -> b c p1 p2", p1=2, p2=2) + + jacobian = torch.matmul(rotate, scale) + else: + # jacobian = rearrange(jacobian,'b c (p0 p1 p2) -> b c p0 p1 p2',p0=4,p1=self.heatmap_size[0],p2=self.heatmap_size[1]) + # jacobian = heatmap.unsqueeze(2) * jacobian + # jacobian = jacobian.sum(-1).sum(-1) + jacobian = rearrange(jacobian, "b c (p1 p2) -> b c p1 p2", p1=2, p2=2) + out["jacobian"] = jacobian + return out diff --git a/module_mrfa/util.py b/module_mrfa/util.py new file mode 100644 index 0000000..f999e96 --- /dev/null +++ b/module_mrfa/util.py @@ -0,0 +1,503 @@ +import torch +import torch.nn.functional as F +from torch import nn +from torch.nn import BatchNorm2d + + +class AttributeDict(dict): + def __getattr__(self, attr): + try: + return self[attr] + except AttributeError as e: + raise AttributeError(f"{e}") + + def __setattr__(self, attr, value): + self[attr] = value + + +def convert_dict_to_attrit_dict(dict_instance): + att_dict = AttributeDict() + for key, value in dict_instance.items(): + if isinstance(value, dict): + value = convert_dict_to_attrit_dict(value) + att_dict.__setattr__(key, value) + return att_dict + + +def bilinear_sampler(img, coords, mode="bilinear", mask=False): + """Wrapper for grid_sample, uses pixel coordinates""" + H, W = img.shape[-2:] + xgrid, ygrid = coords.split([1, 1], dim=-1) + xgrid = 2 * xgrid / (W - 1) - 1 + ygrid = 2 * ygrid / (H - 1) - 1 + + grid = torch.cat([xgrid, ygrid], dim=-1) + img = F.grid_sample(img, grid, align_corners=True) + if mask: + mask = (xgrid > -1) & (ygrid > -1) & (xgrid < 1) & (ygrid < 1) + return img, mask.float() + return img + + +def batch_bilinear_sampler( + img, coords, mode="bilinear", mask=False, h=256, w=256, mini_batch=4 +): + """Wrapper for grid_sample, uses pixel coordinates""" + batch = img.shape[0] // (h * w) + # img = img.view(batch,h,w,img.shape[1],img.shape[2],img.shape[3]) + # coords = coords.view(batch,h,w,coords.shape[1],coords.shape[2],coords.shape[3]) + num_iter = batch // mini_batch + sampled = [] + for i in range(num_iter): + img_i = img[i * mini_batch * h * w : (i + 1) * mini_batch * h * w, :, :, :] + coords_i = coords[ + i * mini_batch * h * w : (i + 1) * mini_batch * h * w, :, :, : + ] + sampled.append(bilinear_sampler(img_i, coords_i)) + return torch.cat(sampled, dim=0) + + +def coords_grid(batch, ht, wd, device): + coords = torch.meshgrid( + torch.arange(ht, device=device), torch.arange(wd, device=device) + ) + coords = torch.stack(coords[::-1], dim=0).float() + return coords[None].repeat(batch, 1, 1, 1) + + +def kp2gaussian(kp, spatial_size, kp_variance): + """ + Transform a keypoint into gaussian like representation + """ + # mean = kp['value'] + mean = kp + # N * 10 * 2 + + coordinate_grid = make_coordinate_grid(spatial_size, mean.type()) + number_of_leading_dimensions = len(mean.shape) - 1 + shape = (1,) * number_of_leading_dimensions + coordinate_grid.shape + # 1,1,h,w,2 + coordinate_grid = coordinate_grid.view(*shape) + repeats = mean.shape[:number_of_leading_dimensions] + (1, 1, 1) + # N * 10 * 1 *1 *1 + coordinate_grid = coordinate_grid.repeat(*repeats) + # N,10,h,w,2 + + # Preprocess kp shape + shape = mean.shape[:number_of_leading_dimensions] + (1, 1, 2) + # N * 10 * 1 * 1 * 2 + mean = mean.view(*shape) + + mean_sub = coordinate_grid - mean + + out = torch.exp(-0.5 * (mean_sub**2).sum(-1) / kp_variance) + # N * 10 * h * w + + return out + + +def make_coordinate_grid(spatial_size, type): + """ + Create a meshgrid [-1,1] x [-1,1] of given spatial_size. + """ + h, w = spatial_size + x = torch.arange(w).type(type) + y = torch.arange(h).type(type) + + x = 2 * (x / (w - 1)) - 1 + y = 2 * (y / (h - 1)) - 1 + + yy = y.view(-1, 1).repeat(1, w) + xx = x.view(1, -1).repeat(h, 1) + # xx [[-1,-1/3,1/3,1],[-1,-1/3,1/3,1],[-1,-1/3,1/3,1],[-1,-1/3,1/3,1]] + # yy [[-1,-1,-1,-1],[-1/3,-1/3,-1/3,-1/3],[1/3,1/3,1/3,1/3],[1,1,1,1]] + + meshed = torch.cat([xx.unsqueeze_(2), yy.unsqueeze_(2)], 2) + + return meshed + + +class ChannelBlock2d(nn.Module): + """ + Res block, preserve spatial resolution. + """ + + def __init__(self, in_features, kernel_size, padding): + super(ChannelBlock2d, self).__init__() + self.conv1 = nn.Conv2d( + in_channels=in_features, + out_channels=in_features // 2, + kernel_size=kernel_size, + padding=padding, + ) + # self.conv2 = nn.Conv2d(in_channels=in_features//2, out_channels=in_features//2, kernel_size=kernel_size, + # padding=padding) + self.norm1 = BatchNorm2d(in_features, affine=True) + # self.norm2 = BatchNorm2d(in_features, affine=True) + + def forward(self, x): + out = self.norm1(x) + out = F.relu(out) + out = self.conv1(out) + # out = self.norm2(out) + # out = F.relu(out) + # out = self.conv2(out) + # out += x + return out + + +class ResBlock2d(nn.Module): + """ + Res block, preserve spatial resolution. + """ + + def __init__(self, in_features, kernel_size, padding): + super(ResBlock2d, self).__init__() + self.conv1 = nn.Conv2d( + in_channels=in_features, + out_channels=in_features, + kernel_size=kernel_size, + padding=padding, + ) + self.conv2 = nn.Conv2d( + in_channels=in_features, + out_channels=in_features, + kernel_size=kernel_size, + padding=padding, + ) + self.norm1 = BatchNorm2d(in_features, affine=True) + self.norm2 = BatchNorm2d(in_features, affine=True) + + def forward(self, x): + out = self.norm1(x) + out = F.relu(out) + out = self.conv1(out) + out = self.norm2(out) + out = F.relu(out) + out = self.conv2(out) + out += x + return out + + +class UpBlock2d(nn.Module): + """ + Upsampling block for use in decoder. + """ + + def __init__(self, in_features, out_features, kernel_size=3, padding=1, groups=1): + super(UpBlock2d, self).__init__() + + self.conv = nn.Conv2d( + in_channels=in_features, + out_channels=out_features, + kernel_size=kernel_size, + padding=padding, + groups=groups, + ) + self.norm = BatchNorm2d(out_features, affine=True) + + def forward(self, x): + out = F.interpolate(x, scale_factor=2) + out = self.conv(out) + out = self.norm(out) + out = F.relu(out) + return out + + +class DownBlock2d(nn.Module): + """ + Downsampling block for use in encoder. + """ + + def __init__(self, in_features, out_features, kernel_size=3, padding=1, groups=1): + super(DownBlock2d, self).__init__() + self.conv = nn.Conv2d( + in_channels=in_features, + out_channels=out_features, + kernel_size=kernel_size, + padding=padding, + groups=groups, + ) + self.norm = BatchNorm2d(out_features, affine=True) + self.pool = nn.AvgPool2d(kernel_size=(2, 2)) + + def forward(self, x): + out = self.conv(x) + out = self.norm(out) + out = F.relu(out) + out = self.pool(out) + return out + + +class SameBlock2d(nn.Module): + """ + Simple block, preserve spatial resolution. + """ + + def __init__(self, in_features, out_features, groups=1, kernel_size=3, padding=1): + super(SameBlock2d, self).__init__() + self.conv = nn.Conv2d( + in_channels=in_features, + out_channels=out_features, + kernel_size=kernel_size, + padding=padding, + groups=groups, + ) + self.norm = BatchNorm2d(out_features, affine=True) + + def forward(self, x): + out = self.conv(x) + out = self.norm(out) + out = F.relu(out) + return out + + +class Encoder(nn.Module): + """ + Hourglass Encoder + """ + + def __init__(self, block_expansion, in_features, num_blocks=3, max_features=256): + super(Encoder, self).__init__() + + down_blocks = [] + for i in range(num_blocks): + down_blocks.append( + DownBlock2d( + 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, + ) + ) + self.down_blocks = nn.ModuleList(down_blocks) + + def forward(self, x): + outs = [x] + for down_block in self.down_blocks: + outs.append(down_block(outs[-1])) + return outs + + +class Decoder(nn.Module): + """ + Hourglass Decoder + """ + + def __init__(self, block_expansion, in_features, num_blocks=3, max_features=256): + super(Decoder, self).__init__() + + up_blocks = [] + + for i in range(num_blocks)[::-1]: + in_filters = (1 if i == num_blocks - 1 else 2) * min( + max_features, block_expansion * (2 ** (i + 1)) + ) + out_filters = min(max_features, block_expansion * (2**i)) + up_blocks.append( + UpBlock2d(in_filters, out_filters, kernel_size=3, padding=1) + ) + + self.up_blocks = nn.ModuleList(up_blocks) + self.out_filters = block_expansion + in_features + + def forward(self, x): + out = x.pop() + for up_block in self.up_blocks: + out = up_block(out) + skip = x.pop() + out = torch.cat([out, skip], dim=1) + return out + + +class Hourglass(nn.Module): + """ + Hourglass architecture. + """ + + def __init__(self, block_expansion, in_features, num_blocks=3, max_features=256): + super(Hourglass, self).__init__() + self.encoder = Encoder(block_expansion, in_features, num_blocks, max_features) + self.decoder = Decoder(block_expansion, in_features, num_blocks, max_features) + self.out_filters = self.decoder.out_filters + + def forward(self, x): + return self.decoder(self.encoder(x)) + + +class AntiAliasInterpolation2d(nn.Module): + """ + Band-limited downsampling, for better preservation of the input signal. + """ + + def __init__(self, channels, scale): + super(AntiAliasInterpolation2d, self).__init__() + sigma = (1 / scale - 1) / 2 + kernel_size = 2 * round(sigma * 4) + 1 + self.ka = kernel_size // 2 + self.kb = self.ka - 1 if kernel_size % 2 == 0 else self.ka + + kernel_size = [kernel_size, kernel_size] + sigma = [sigma, sigma] + # The gaussian kernel is the product of the + # gaussian function of each dimension. + kernel = 1 + meshgrids = torch.meshgrid( + [torch.arange(size, dtype=torch.float32) for size in kernel_size] + ) + for size, std, mgrid in zip(kernel_size, sigma, meshgrids): + mean = (size - 1) / 2 + kernel *= torch.exp(-((mgrid - mean) ** 2) / (2 * std**2)) + + # Make sure sum of values in gaussian kernel equals 1. + kernel = kernel / torch.sum(kernel) + # Reshape to depthwise convolutional weight + kernel = kernel.view(1, 1, *kernel.size()) + kernel = kernel.repeat(channels, *[1] * (kernel.dim() - 1)) + + self.register_buffer("weight", kernel) + self.groups = channels + self.scale = scale + + def forward(self, input): + if self.scale == 1.0: + return input + + out = F.pad(input, (self.ka, self.kb, self.ka, self.kb)) + out = F.conv2d(out, weight=self.weight, groups=self.groups) + out = F.interpolate(out, scale_factor=(self.scale, self.scale)) + + return out + + +def to_homogeneous(coordinates): + ones_shape = list(coordinates.shape) + ones_shape[-1] = 1 + ones = torch.ones(ones_shape).type(coordinates.type()) + + return torch.cat([coordinates, ones], dim=-1) + + +def from_homogeneous(coordinates): + return coordinates[..., :2] / coordinates[..., 2:3] + + +class TPS: + """ + TPS transformation, mode 'kp' for Eq(2) in the paper, mode 'random' for equivariance loss. + """ + + def __init__(self, mode, bs, **kwargs): + self.bs = bs + self.mode = mode + if mode == "random": + noise = torch.normal( + mean=0, std=kwargs["sigma_affine"] * torch.ones([bs, 2, 3]) + ) + self.theta = noise + torch.eye(2, 3).view(1, 2, 3) + self.control_points = make_coordinate_grid( + (kwargs["points_tps"], kwargs["points_tps"]), type=noise.type() + ) + self.control_points = self.control_points.unsqueeze(0) + self.control_params = torch.normal( + mean=0, + std=kwargs["sigma_tps"] + * torch.ones([bs, 1, kwargs["points_tps"] ** 2]), + ) + elif mode == "kp": + kp_1 = kwargs["kp_1"] + kp_2 = kwargs["kp_2"] + device = kp_1.device + kp_type = kp_1.type() + self.gs = kp_1.shape[1] + n = kp_1.shape[2] + K = torch.norm(kp_1[:, :, :, None] - kp_1[:, :, None, :], dim=4, p=2) + K = K**2 + K = K * torch.log(K + 1e-9) + + one1 = ( + torch.ones(self.bs, kp_1.shape[1], kp_1.shape[2], 1) + .to(device) + .type(kp_type) + ) + kp_1p = torch.cat([kp_1, one1], 3) + + zero = torch.zeros(self.bs, kp_1.shape[1], 3, 3).to(device).type(kp_type) + P = torch.cat([kp_1p, zero], 2) + L = torch.cat([K, kp_1p.permute(0, 1, 3, 2)], 2) + L = torch.cat([L, P], 3) + + zero = torch.zeros(self.bs, kp_1.shape[1], 3, 2).to(device).type(kp_type) + Y = torch.cat([kp_2, zero], 2) + one = torch.eye(L.shape[2]).expand(L.shape).to(device).type(kp_type) * 0.01 + L = L + one + + param = torch.matmul(torch.inverse(L), Y) + self.theta = param[:, :, n:, :].permute(0, 1, 3, 2) + + self.control_points = kp_1 + self.control_params = param[:, :, :n, :] + else: + raise Exception("Error TPS mode") + + def transform_frame(self, frame): + grid = ( + make_coordinate_grid(frame.shape[2:], type=frame.type()) + .unsqueeze(0) + .to(frame.device) + ) + grid = grid.view(1, frame.shape[2] * frame.shape[3], 2) + shape = [self.bs, frame.shape[2], frame.shape[3], 2] + if self.mode == "kp": + shape.insert(1, self.gs) + grid = self.warp_coordinates(grid).view(*shape) + return grid + + def warp_coordinates(self, coordinates): + theta = self.theta.type(coordinates.type()).to(coordinates.device) + control_points = self.control_points.type(coordinates.type()).to( + coordinates.device + ) + control_params = self.control_params.type(coordinates.type()).to( + coordinates.device + ) + + if self.mode == "kp": + transformed = ( + torch.matmul(theta[:, :, :, :2], coordinates.permute(0, 2, 1)) + + theta[:, :, :, 2:] + ) + + distances = coordinates.view( + coordinates.shape[0], 1, 1, -1, 2 + ) - control_points.view(self.bs, control_points.shape[1], -1, 1, 2) + + distances = distances**2 + result = distances.sum(-1) + result = result * torch.log(result + 1e-9) + result = torch.matmul(result.permute(0, 1, 3, 2), control_params) + transformed = transformed.permute(0, 1, 3, 2) + result + + elif self.mode == "random": + theta = theta.unsqueeze(1) + transformed = ( + torch.matmul(theta[:, :, :, :2], coordinates.unsqueeze(-1)) + + theta[:, :, :, 2:] + ) + transformed = transformed.squeeze(-1) + ances = coordinates.view( + coordinates.shape[0], -1, 1, 2 + ) - control_points.view(1, 1, -1, 2) + distances = ances**2 + + result = distances.sum(-1) + result = result * torch.log(result + 1e-9) + result = result * control_params + result = result.sum(dim=2).view(self.bs, coordinates.shape[1], 1) + transformed = transformed + result + else: + raise Exception("Error TPS mode") + + return transformed diff --git a/pyproject.toml b/pyproject.toml index cfa910a..89191c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,9 @@ [project] name = "comfyui-firstordermm" description = "ComfyUI-native nodes to run First Order Motion Model for Image Animation and its non-diffusion-based successors. [a/https://github.com/AliaksandrSiarohin/first-order-model](https://github.com/AliaksandrSiarohin/first-order-model)" -version = "1.0.3" +version = "1.0.4" license = { file = "LICENSE" } -dependencies = ["numpy", "torch", "scipy", "yaml", "matplotlib"] +dependencies = ["numpy", "torch", "scipy", "pyyaml", "matplotlib", "einops", "timm"] [project.urls] Repository = "https://github.com/FuouM/ComfyUI-FirstOrderMM" diff --git a/requirements.txt b/requirements.txt index 5eecb23..7443ff8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,6 @@ numpy torch scipy pyyaml -matplotlib \ No newline at end of file +matplotlib +einops +timm \ No newline at end of file diff --git a/run.py b/run.py index 340dae2..9fc0491 100644 --- a/run.py +++ b/run.py @@ -12,6 +12,10 @@ import torch from .constants import ( ARTICULATE_CFG_PATH, ARTICULATE_MODEL_PATH, + MRFA_CFG_PATHS, + MRFA_DEFAULT_MODEL, + MRFA_MODEL_NAMES, + MRFA_MODEL_PATHS, SPLINE_CFG_PATH, SPLINE_DEFAULT, SPLINE_MODEL_PATH, @@ -29,6 +33,7 @@ from .constants import ( from .face_parsing.face_parsing_loader import load_face_parser_model from .inference_articulate import articulate_inference from .inference_fomm import inference, inference_best_frame, load_checkpoint +from .inference_mrfa import mrfa_inference from .inference_partswap import load_partswap_checkpoint, partswap_inference from .inference_spline import spline_inference from .seg_viz import visualize_frame @@ -449,7 +454,7 @@ class Spline_Runner: find_best_frame: bool, audio=None, ): - print(f"{type(source_image)=}") + print(f"{type(source_image)=}") # [B, H, W, C] print(f"{type(driving_video_input)=}") print(f"{source_image.shape=}") print(f"{driving_video_input.shape=}") @@ -486,6 +491,99 @@ class Spline_Runner: ) +class MRFA_Runner: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "source_image": ("IMAGE",), + "driving_video_input": ("IMAGE",), + "model_name": (MRFA_MODEL_NAMES, {"default": MRFA_DEFAULT_MODEL}), + "frame_rate": ("FLOAT", {"default": 30.0}), + "use_relative": ( + "BOOLEAN", + {"default": True}, + ), + "relative_movement": ( + "BOOLEAN", + {"default": True}, + ), + "relative_jacobian": ( + "BOOLEAN", + {"default": True}, + ), + "adapt_movement_scale": ( + "BOOLEAN", + {"default": False}, + ), + }, + "optional": {"audio": ("AUDIO",)}, + } + + RETURN_TYPES = ( + "IMAGE", + "AUDIO", + "FLOAT", + ) + RETURN_NAMES = ( + "images", + "audio", + "frame_rate", + ) + FUNCTION = "todo" + CATEGORY = "FirstOrderMM" + + def todo( + self, + source_image, + driving_video_input, + model_name: str, + frame_rate: float, + use_relative: bool, + relative_movement: bool, + relative_jacobian: bool, + adapt_movement_scale: bool, + audio=None, + ): + print(f"{type(source_image)=}") # [B, H, W, C] + print(f"{type(driving_video_input)=}") + print(f"{source_image.shape=}") + print(f"{driving_video_input.shape=}") + print(f"{type(audio)=}") + print(base_dir) + + config_path = f"{base_dir}/{MRFA_CFG_PATHS[model_name]}" + checkpoint_path = f"{base_dir}/{MRFA_MODEL_PATHS[model_name]}" + + source_image = reshape_image(source_image, (256, 256)) + driving_video = reshape_image(driving_video_input, (256, 256)).unsqueeze(0) + driving_video = driving_video.permute(0, 2, 1, 3, 4) + + print("After reshaping") + print(f"{source_image.shape=}") + print(f"{driving_video.shape=}") + params = { + "source_image": source_image, + "driving_video": driving_video, + "config_path": config_path, + "checkpoint_path": checkpoint_path, + "use_relative": use_relative, + "relative_movement": relative_movement, + "relative_jacobian": relative_jacobian, + "adapt_movement_scale": adapt_movement_scale, + } + + predictions = mrfa_inference(**params) + + output_images = out_video(predictions) + + return ( + output_images, + audio, + frame_rate, + ) + + def serialize_integers(int_list): return "_".join(map(str, int_list)) diff --git a/workflows/MRFA.json b/workflows/MRFA.json new file mode 100644 index 0000000..7b12dbf --- /dev/null +++ b/workflows/MRFA.json @@ -0,0 +1,423 @@ +{ + "last_node_id": 7, + "last_link_id": 18, + "nodes": [ + { + "id": 2, + "type": "LoadImage", + "pos": [ + 83, + 105 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 13 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "Rick1.png", + "image" + ] + }, + { + "id": 5, + "type": "VHS_VideoInfoLoaded", + "pos": [ + 753, + 383 + ], + "size": { + "0": 304.79998779296875, + "1": 106 + }, + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "video_info", + "type": "VHS_VIDEOINFO", + "link": 7 + } + ], + "outputs": [ + { + "name": "fps🟦", + "type": "FLOAT", + "links": [ + 18 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "frame_count🟦", + "type": "INT", + "links": null, + "shape": 3 + }, + { + "name": "duration🟦", + "type": "FLOAT", + "links": null, + "shape": 3 + }, + { + "name": "width🟦", + "type": "INT", + "links": null, + "shape": 3 + }, + { + "name": "height🟦", + "type": "INT", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "VHS_VideoInfoLoaded" + }, + "widgets_values": {} + }, + { + "id": 6, + "type": "VHS_VideoCombine", + "pos": [ + 1236, + 163 + ], + "size": [ + 315, + 615 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 15 + }, + { + "name": "audio", + "type": "AUDIO", + "link": 16 + }, + { + "name": "meta_batch", + "type": "VHS_BatchManager", + "link": null + }, + { + "name": "vae", + "type": "VAE", + "link": null + }, + { + "name": "frame_rate", + "type": "FLOAT", + "link": 17, + "widget": { + "name": "frame_rate" + } + } + ], + "outputs": [ + { + "name": "Filenames", + "type": "VHS_FILENAMES", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "VHS_VideoCombine" + }, + "widgets_values": { + "frame_rate": 8, + "loop_count": 0, + "filename_prefix": "MRFA", + "format": "video/h264-mp4", + "pix_fmt": "yuv420p", + "crf": 0, + "save_metadata": true, + "pingpong": false, + "save_output": true, + "videopreview": { + "hidden": false, + "paused": false, + "params": { + "filename": "MRFA_00003.mp4", + "subfolder": "", + "type": "output", + "format": "video/h264-mp4", + "frame_rate": 29.970029970029966 + } + } + } + }, + { + "id": 3, + "type": "VHS_LoadVideo", + "pos": [ + 430, + 196 + ], + "size": [ + 235.1999969482422, + 491.1999969482422 + ], + "flags": { + "collapsed": false + }, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "meta_batch", + "type": "VHS_BatchManager", + "link": null + }, + { + "name": "vae", + "type": "VAE", + "link": null + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 14 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "frame_count", + "type": "INT", + "links": null, + "shape": 3 + }, + { + "name": "audio", + "type": "AUDIO", + "links": [], + "shape": 3, + "slot_index": 2 + }, + { + "name": "video_info", + "type": "VHS_VIDEOINFO", + "links": [ + 7 + ], + "shape": 3, + "slot_index": 3 + } + ], + "properties": { + "Node name for S&R": "VHS_LoadVideo" + }, + "widgets_values": { + "video": "damedane.mp4", + "force_rate": 0, + "force_size": "Disabled", + "custom_width": 512, + "custom_height": 512, + "frame_load_cap": 0, + "skip_first_frames": 0, + "select_every_nth": 1, + "choose video to upload": "image", + "videopreview": { + "hidden": false, + "paused": false, + "params": { + "frame_load_cap": 0, + "skip_first_frames": 0, + "force_rate": 0, + "filename": "damedane.mp4", + "type": "input", + "format": "video/mp4", + "select_every_nth": 1 + } + } + } + }, + { + "id": 7, + "type": "MRFA_Runner", + "pos": [ + 779, + 107 + ], + "size": { + "0": 380.4000244140625, + "1": 218 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "source_image", + "type": "IMAGE", + "link": 13 + }, + { + "name": "driving_video_input", + "type": "IMAGE", + "link": 14 + }, + { + "name": "audio", + "type": "AUDIO", + "link": null + }, + { + "name": "frame_rate", + "type": "FLOAT", + "link": 18, + "widget": { + "name": "frame_rate" + } + } + ], + "outputs": [ + { + "name": "images", + "type": "IMAGE", + "links": [ + 15 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "audio", + "type": "AUDIO", + "links": [ + 16 + ], + "shape": 3, + "slot_index": 1 + }, + { + "name": "frame_rate", + "type": "FLOAT", + "links": [ + 17 + ], + "shape": 3, + "slot_index": 2 + } + ], + "properties": { + "Node name for S&R": "MRFA_Runner" + }, + "widgets_values": [ + "celebvhq", + 30, + true, + true, + true, + true + ] + } + ], + "links": [ + [ + 7, + 3, + 3, + 5, + 0, + "VHS_VIDEOINFO" + ], + [ + 13, + 2, + 0, + 7, + 0, + "IMAGE" + ], + [ + 14, + 3, + 0, + 7, + 1, + "IMAGE" + ], + [ + 15, + 7, + 0, + 6, + 0, + "IMAGE" + ], + [ + 16, + 7, + 1, + 6, + 1, + "AUDIO" + ], + [ + 17, + 7, + 2, + 6, + 4, + "FLOAT" + ], + [ + 18, + 5, + 0, + 7, + 3, + "FLOAT" + ] + ], + "groups": [], + "config": {}, + "extra": { + "ds": { + "scale": 1, + "offset": [ + -17, + 60 + ] + } + }, + "version": 0.4 +} \ No newline at end of file diff --git a/workflows/workflow_mrfa.png b/workflows/workflow_mrfa.png new file mode 100644 index 0000000000000000000000000000000000000000..6d075e6c59ca17c2acfe078212123d5fce9fb905 GIT binary patch literal 258689 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 literal 0 HcmV?d00001 From ec0758b28bb43cca57e7c0741594ad30b2563e01 Mon Sep 17 00:00:00 2001 From: FuouM Date: Sat, 10 Aug 2024 21:59:00 +0700 Subject: [PATCH 4/4] update readme --- README.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f7ce99b..54df587 100644 --- a/README.md +++ b/README.md @@ -100,13 +100,16 @@ pip install -r requirements.txt pip install face-alignment ``` -## Model +## Models -FOMM currently supporting `vox` and `vox-adv`. Models can and must be manually downloaded from: +### FOMM and Part Swap + +**FOMM**: `vox` and `vox-adv` from * [AliaksandrSiarohin/first-order-model](https://github.com/AliaksandrSiarohin/first-order-model) * [graphemecluster/first-order-model-demo](https://github.com/graphemecluster/first-order-model-demo) -Part Swap currently supporting Seg-based models and FOMM (`vox` and `vox-adv`) models. +**Part Swap** + * `vox-5segments` * `vox-10segments` * `vox-15segments` @@ -139,13 +142,56 @@ resnet18-5c106cde.pth 79999_iter.pth ``` -For **Articulate**, download the model from [Pre-trained checkpoints](https://github.com/snap-research/articulated-animation?tab=readme-ov-file#pre-trained-checkpoints) section and place it here: `articulate_module/models/vox256.pth` +### Other -For **Spline**, download the model from [Pre-trained models](https://github.com/yoyo-nb/thin-plate-spline-motion-model?tab=readme-ov-file#pre-trained-models) section and place it here: `spline_module/models/vox.pth.tar`. To use `find_best_frame`, install `face-alignment`. +| Model Arch | File Path | Source | +|------------|-----------|--------| +| **Articulate** | `articulate_module/models/vox256.pth` | [Articulated Animation (Pre-trained checkpoints)](https://github.com/snap-research/articulated-animation?tab=readme-ov-file#pre-trained-checkpoints) | +| **Spline** | `spline_module/models/vox.pth.tar` | [Thin Plate Spline Motion Model (Pre-trained models)](https://github.com/yoyo-nb/thin-plate-spline-motion-model?tab=readme-ov-file#pre-trained-models) | +| **MRFA** (celebvhq) | `mrfa_module/models/celebvhq.pth` | [MRFA (Pre-trained checkpoints)](https://github.com/JialeTao/MRFA/?tab=readme-ov-file#pretrained-models) | +| **MRFA** (vox) | `mrfa_module/models/vox.pth` | [MRFA (Pre-trained checkpoints)](https://github.com/JialeTao/MRFA/?tab=readme-ov-file#pretrained-models) | -For **MRFA**, download `MRFACheckpoints.tar.gz` from [Pretrained models](https://github.com/JialeTao/MRFA/?tab=readme-ov-file#pretrained-models) section and decompress it. Inside there should be two files. Place them here: +Notes: +- For **Spline**, to use `find_best_frame`, follow above instructions to install `face-alignment` with its models. + +## Credits ``` -mrfa_module/models/celebvhq.pth -mrfa_module/models/vox.pth +@InProceedings{Siarohin_2019_NeurIPS, + author={Siarohin, Aliaksandr and Lathuilière, Stéphane and Tulyakov, Sergey and Ricci, Elisa and Sebe, Nicu}, + title={First Order Motion Model for Image Animation}, + booktitle = {Conference on Neural Information Processing Systems (NeurIPS)}, + month = {December}, + year = {2019} +} +``` + +``` +@InProceedings{Siarohin_2019_NeurIPS, + author={Siarohin, Aliaksandr and Lathuilière, Stéphane and Tulyakov, Sergey and Ricci, Elisa and Sebe, Nicu}, + title={First Order Motion Model for Image Animation}, + booktitle = {Conference on Neural Information Processing Systems (NeurIPS)}, + month = {December}, + year = {2019} +} +``` + +``` +@inproceedings{siarohin2021motion, + author={Siarohin, Aliaksandr and Woodford, Oliver and Ren, Jian and Chai, Menglei and Tulyakov, Sergey}, + title={Motion Representations for Articulated Animation}, + booktitle = {CVPR}, + year = {2021} +} +``` + +``` +@inproceedings{ +tao2023learning, +title={Learning Motion Refinement for Unsupervised Face Animation}, +author={Jiale Tao and Shuhang Gu and Wen Li and Lixin Duan}, +booktitle={Thirty-seventh Conference on Neural Information Processing Systems}, +year={2023}, +url={https://openreview.net/forum?id=m9uHv1Pxq7} +} ``` \ No newline at end of file