diff --git a/FONT/.idea/.gitignore b/FONT/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..64820cdaf0b5e0e19ee9b1d601645ace646e3f89 --- /dev/null +++ b/FONT/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/../../../../../:\PG5\projects\EAMM-main\.idea/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/FONT/.idea/EAMM-main.iml b/FONT/.idea/EAMM-main.iml new file mode 100644 index 0000000000000000000000000000000000000000..c2bba40ac51e7294b8d4f53de16579785c753660 --- /dev/null +++ b/FONT/.idea/EAMM-main.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/FONT/.idea/deployment.xml b/FONT/.idea/deployment.xml new file mode 100644 index 0000000000000000000000000000000000000000..7186b57e233240094faebd8a207bd9683feed88d --- /dev/null +++ b/FONT/.idea/deployment.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FONT/.idea/inspectionProfiles/Project_Default.xml b/FONT/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000000000000000000000000000000000000..106b4c03f32884bede24b7c87ef9d6210607a120 --- /dev/null +++ b/FONT/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/FONT/.idea/inspectionProfiles/profiles_settings.xml b/FONT/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..105ce2da2d6447d11dfe32bfb846c3d5b199fc99 --- /dev/null +++ b/FONT/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/FONT/.idea/misc.xml b/FONT/.idea/misc.xml new file mode 100644 index 0000000000000000000000000000000000000000..c3334deb12560ce440f1ea76b110debbb57d10d3 --- /dev/null +++ b/FONT/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/FONT/.idea/modules.xml b/FONT/.idea/modules.xml new file mode 100644 index 0000000000000000000000000000000000000000..a595c3106207ccbab2a8e785870e1230612fa0cb --- /dev/null +++ b/FONT/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/FONT/.idea/sshConfigs.xml b/FONT/.idea/sshConfigs.xml new file mode 100644 index 0000000000000000000000000000000000000000..fada610e6007b9633797ed98d3bc7f4185e2713b --- /dev/null +++ b/FONT/.idea/sshConfigs.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/FONT/3DDFA_V2/demo.py b/FONT/3DDFA_V2/demo.py new file mode 100644 index 0000000000000000000000000000000000000000..0a2f6e009ea6b6ee84ca9e547d1b2f62b40b8e5c --- /dev/null +++ b/FONT/3DDFA_V2/demo.py @@ -0,0 +1,246 @@ +# coding: utf-8 + +__author__ = 'cleardusk' + +import sys +import argparse +import cv2 +import yaml +import os +import time +from FaceBoxes import FaceBoxes +from TDDFA import TDDFA +from utils.render import render +#from utils.render_ctypes import render # faster +from utils.depth import depth +from utils.pncc import pncc +from utils.uv import uv_tex +from utils.pose import viz_pose, get_pose +from utils.serialization import ser_to_ply, ser_to_obj +from utils.functions import draw_landmarks, get_suffix +from utils.tddfa_util import str2bool +import numpy as np +from tqdm import tqdm +import copy + +import concurrent.futures +from multiprocessing import Pool + +def main(args,img, save_path, pose_path): + # begin = time.time() + cfg = yaml.load(open(args.config), Loader=yaml.SafeLoader) + + # Init FaceBoxes and TDDFA, recommend using onnx flag + if args.onnx: + import os + os.environ['KMP_DUPLICATE_LIB_OK'] = 'True' + os.environ['OMP_NUM_THREADS'] = '4' + + from FaceBoxes.FaceBoxes_ONNX import FaceBoxes_ONNX + from TDDFA_ONNX import TDDFA_ONNX + + face_boxes = FaceBoxes_ONNX() + tddfa = TDDFA_ONNX(**cfg) + else: + gpu_mode = args.mode == 'gpu' + tddfa = TDDFA(gpu_mode=gpu_mode, **cfg) + face_boxes = FaceBoxes() + + # Given a still image path and load to BGR channel + # img = cv2.imread(img_path) #args.img_fp + + # Detect faces, get 3DMM params and roi boxes + boxes = face_boxes(img) + n = len(boxes) + if n == 0: + print(f'No face detected, exit') + # sys.exit(-1) + return None + print(f'Detect {n} faces') + + param_lst, roi_box_lst = tddfa(img, boxes) + #detection time + # detect_time = time.time()-begin + # print('detection time: '+str(detect_time), file=open('/mnt/lustre/jixinya/Home/3DDFA_V2/pose.txt', 'a')) + # Visualization and serialization + dense_flag = args.opt in ('2d_dense', '3d', 'depth', 'pncc', 'uv_tex', 'ply', 'obj') + # old_suffix = get_suffix(img_path) + old_suffix = 'png' + new_suffix = f'.{args.opt}' if args.opt in ('ply', 'obj') else '.jpg' + + wfp = f'examples/results/{args.img_fp.split("/")[-1].replace(old_suffix, "")}_{args.opt}' + new_suffix + + ver_lst = tddfa.recon_vers(param_lst, roi_box_lst, dense_flag=dense_flag) + + if args.opt == '2d_sparse': + draw_landmarks(img, ver_lst, show_flag=args.show_flag, dense_flag=dense_flag, wfp=wfp) + elif args.opt == '2d_dense': + draw_landmarks(img, ver_lst, show_flag=args.show_flag, dense_flag=dense_flag, wfp=wfp) + elif args.opt == '3d': + render(img, ver_lst, tddfa.tri, alpha=0.6, show_flag=args.show_flag, wfp=wfp) + elif args.opt == 'depth': + + # if `with_bf_flag` is False, the background is black + depth(img, ver_lst, tddfa.tri, show_flag=args.show_flag, wfp=wfp, with_bg_flag=True) + elif args.opt == 'pncc': + pncc(img, ver_lst, tddfa.tri, show_flag=args.show_flag, wfp=wfp, with_bg_flag=True) + elif args.opt == 'uv_tex': + uv_tex(img, ver_lst, tddfa.tri, show_flag=args.show_flag, wfp=wfp) + elif args.opt == 'pose': + all_pose = get_pose(img, param_lst, ver_lst, show_flag=args.show_flag, wfp=save_path, wnp = pose_path) + elif args.opt == 'ply': + ser_to_ply(ver_lst, tddfa.tri, height=img.shape[0], wfp=wfp) + elif args.opt == 'obj': + ser_to_obj(img, ver_lst, tddfa.tri, height=img.shape[0], wfp=wfp) + else: + raise ValueError(f'Unknown opt {args.opt}') + + return all_pose + + + +def process_word(i): + path = '/media/xinya/Backup Plus/sense_shixi_data/new_crop/MEAD_fomm_video_6/' + save = '/media/xinya/Backup Plus/sense_shixi_data/new_crop/MEAD_fomm_pose_im/' + pose = '/media/xinya/Backup Plus/sense_shixi_data/new_crop/MEAD_fomm_pose/' + start = time.time() + Dir = os.listdir(path) + Dir.sort() + word = Dir[i] + wpath = os.path.join(path, word) + print(wpath) + pathDir = os.listdir(wpath) + pose_file = os.path.join(pose,word) + if not os.path.exists(pose_file): + os.makedirs(pose_file) + + for j in range(len(pathDir)): + name = pathDir[j] + # save_file = os.path.join(save,word,name) + # if not os.path.exists(save_file): + # os.makedirs(save_file) + fpath = os.path.join(wpath,name) + image_all = [] + videoCapture = cv2.VideoCapture(fpath) + + success, frame = videoCapture.read() + + n = 0 + while success : + image_all.append(frame) + n = n + 1 + success, frame = videoCapture.read() + + # fDir = os.listdir(fpath) + pose_all = np.zeros((len(image_all),7)) + for k in range(len(image_all)): + # index = fDir[k].split('.')[0] + # img_path = os.path.join(fpath,str(k)+'.png') + + # pose_all[k] = main(args,image_all[k], os.path.join(save_file,str(k)+'.jpg'), None) + pose_all[k] = main(args,image_all[k], None, None) + np.save(os.path.join(pose,word,name.split('.')[0]+'.npy'),pose_all) + st = time.time()-start + print(str(i)+' '+word+' '+str(j)+' '+name+' '+str(k)+'time: '+str(st), file=open('/media/thea/Backup Plus/sense_shixi_data/new_crop/pose_mead6.txt', 'a')) + print(i,word,j,name,k) + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='The demo of still image of 3DDFA_V2') + parser.add_argument('-c', '--config', type=str, default='configs/mb1_120x120.yml') + parser.add_argument('-f', '--img_fp', type=str, default='examples/inputs/0.png') + parser.add_argument('-m', '--mode', type=str, default='cpu', help='gpu or cpu mode') + parser.add_argument('-o', '--opt', type=str, default='pose', + choices=['2d_sparse', '2d_dense', '3d', 'depth', 'pncc', 'uv_tex', 'pose', 'ply', 'obj']) + parser.add_argument('--show_flag', type=str2bool, default='False', help='whether to show the visualization result') + parser.add_argument('--onnx', action='store_true', default=False) + + args = parser.parse_args() + + + + # filepath = 'test/image/' + # pathDir = os.listdir(filepath) + # for i in range(len(pathDir)): + # image= cv2.imread(os.path.join(filepath,pathDir[i])) + # pose = main(args,image, None, None).reshape(1,7) + # + # np.save('test/pose/'+pathDir[i].split('.')[0]+'.npy',pose) + # print(i,pathDir[i]) + + test_image_path = "/data/liujin/dataset/LRW/lipread_frames/ABOUT/train/ABOUT_00001/000000.jpg" + image = cv2.imread(test_image_path) + pose = main(args, image, None, None).reshape(1, 7) + print(pose) + + + + +''' + + + + + +def main(args): + cfg = yaml.load(open(args.config), Loader=yaml.SafeLoader) + + # Init FaceBoxes and TDDFA, recommend using onnx flag + if args.onnx: + import os + os.environ['KMP_DUPLICATE_LIB_OK'] = 'True' + os.environ['OMP_NUM_THREADS'] = '4' + + from FaceBoxes.FaceBoxes_ONNX import FaceBoxes_ONNX + from TDDFA_ONNX import TDDFA_ONNX + + face_boxes = FaceBoxes_ONNX() + tddfa = TDDFA_ONNX(**cfg) + else: + gpu_mode = args.mode == 'gpu' + tddfa = TDDFA(gpu_mode=gpu_mode, **cfg) + face_boxes = FaceBoxes() + + # Given a still image path and load to BGR channel + img = cv2.imread(args.img_fp) + + # Detect faces, get 3DMM params and roi boxes + boxes = face_boxes(img) + n = len(boxes) + if n == 0: + print(f'No face detected, exit') + sys.exit(-1) + print(f'Detect {n} faces') + + param_lst, roi_box_lst = tddfa(img, boxes) + + # Visualization and serialization + dense_flag = args.opt in ('2d_dense', '3d', 'depth', 'pncc', 'uv_tex', 'ply', 'obj') + old_suffix = get_suffix(args.img_fp) + new_suffix = f'.{args.opt}' if args.opt in ('ply', 'obj') else '.jpg' + + wfp = f'examples/results/{args.img_fp.split("/")[-1].replace(old_suffix, "")}_{args.opt}' + new_suffix + + ver_lst = tddfa.recon_vers(param_lst, roi_box_lst, dense_flag=dense_flag) + + if args.opt == '2d_sparse': + draw_landmarks(img, ver_lst, show_flag=args.show_flag, dense_flag=dense_flag, wfp=wfp) + elif args.opt == '2d_dense': + draw_landmarks(img, ver_lst, show_flag=args.show_flag, dense_flag=dense_flag, wfp=wfp) + elif args.opt == '3d': + render(img, ver_lst, tddfa.tri, alpha=0.6, show_flag=args.show_flag, wfp=wfp) + elif args.opt == 'depth': + # if `with_bf_flag` is False, the background is black + depth(img, ver_lst, tddfa.tri, show_flag=args.show_flag, wfp=wfp, with_bg_flag=True) + elif args.opt == 'pncc': + pncc(img, ver_lst, tddfa.tri, show_flag=args.show_flag, wfp=wfp, with_bg_flag=True) + elif args.opt == 'uv_tex': + uv_tex(img, ver_lst, tddfa.tri, show_flag=args.show_flag, wfp=wfp) + elif args.opt == 'pose': + viz_pose(img, param_lst, ver_lst, show_flag=args.show_flag, wfp=wfp) + elif args.opt == 'ply': + ser_to_ply(ver_lst, tddfa.tri, height=img.shape[0], wfp=wfp) + elif args.opt == 'obj': + ser_to_obj(img, ver_lst, tddfa.tri, height=img.shape[0], wfp=wfp) + else: + raise ValueError(f'Unknown opt {args.opt}') +''' \ No newline at end of file diff --git a/FONT/3DDFA_V2/utils/pose.py b/FONT/3DDFA_V2/utils/pose.py new file mode 100644 index 0000000000000000000000000000000000000000..55d1da6006e394578619697ca7217e9f86e4f975 --- /dev/null +++ b/FONT/3DDFA_V2/utils/pose.py @@ -0,0 +1,284 @@ +# coding: utf-8 + +""" +Reference: https://github.com/YadiraF/PRNet/blob/master/utils/estimate_pose.py + +Calculating pose from the output 3DMM parameters, you can also try to use solvePnP to perform estimation +""" + +__author__ = 'cleardusk' + +import cv2 +import numpy as np +from math import cos, sin, atan2, asin, sqrt + +from .functions import calc_hypotenuse, plot_image + + +def P2sRt(P): + """ decompositing camera matrix P. + Args: + P: (3, 4). Affine Camera Matrix. + Returns: + s: scale factor. + R: (3, 3). rotation matrix. + t2d: (2,). 2d translation. + """ + t3d = P[:, 3] + R1 = P[0:1, :3] + R2 = P[1:2, :3] + s = (np.linalg.norm(R1) + np.linalg.norm(R2)) / 2.0 + r1 = R1 / np.linalg.norm(R1) + r2 = R2 / np.linalg.norm(R2) + r3 = np.cross(r1, r2) + + R = np.concatenate((r1, r2, r3), 0) + return s, R, t3d + + +def matrix2angle(R): + """ compute three Euler angles from a Rotation Matrix. Ref: http://www.gregslabaugh.net/publications/euler.pdf + refined by: https://stackoverflow.com/questions/43364900/rotation-matrix-to-euler-angles-with-opencv + todo: check and debug + Args: + R: (3,3). rotation matrix + Returns: + x: yaw + y: pitch + z: roll + """ + if R[2, 0] > 0.998: + z = 0 + x = np.pi / 2 + y = z + atan2(-R[0, 1], -R[0, 2]) + elif R[2, 0] < -0.998: + z = 0 + x = -np.pi / 2 + y = -z + atan2(R[0, 1], R[0, 2]) + else: + x = asin(R[2, 0]) + y = atan2(R[2, 1] / cos(x), R[2, 2] / cos(x)) + z = atan2(R[1, 0] / cos(x), R[0, 0] / cos(x)) + + return x, y, z + +def angle2matrix(theta): + """ compute three Euler angles from a Rotation Matrix. Ref: http://www.gregslabaugh.net/publications/euler.pdf + refined by: https://stackoverflow.com/questions/43364900/rotation-matrix-to-euler-angles-with-opencv + todo: check and debug + Args: + R: (3,3). rotation matrix + Returns: + x: yaw + y: pitch + z: roll + """ + R_x = np.array([[1, 0, 0 ], + + [0, cos(theta[1]), -sin(theta[1]) ], + + [0, sin(theta[1]), cos(theta[1]) ] + + ]) + + + + R_y = np.array([[cos(theta[0]), 0, sin(-theta[0]) ], + + [0, 1, 0 ], + + [-sin(-theta[0]), 0, cos(theta[0]) ] + + ]) + + + + R_z = np.array([[cos(theta[2]), -sin(theta[2]), 0], + + [sin(theta[2]), cos(theta[2]), 0], + + [0, 0, 1] + + ]) + + + + R = np.dot(R_z, np.dot( R_y, R_x )) + + + + return R + +def angle2matrix_3ddfa(angles): + ''' get rotation matrix from three rotation angles(radian). The same as in 3DDFA. + Args: + angles: [3,]. x, y, z angles + x: pitch. + y: yaw. + z: roll. + Returns: + R: 3x3. rotation matrix. + ''' + # x, y, z = np.deg2rad(angles[0]), np.deg2rad(angles[1]), np.deg2rad(angles[2]) + x, y, z = angles[1], angles[0], angles[2] + + # x + Rx=np.array([[1, 0, 0], + [0, cos(x), sin(x)], + [0, -sin(x), cos(x)]]) + # y + Ry=np.array([[ cos(y), 0, -sin(y)], + [ 0, 1, 0], + [sin(y), 0, cos(y)]]) + # z + Rz=np.array([[cos(z), sin(z), 0], + [-sin(z), cos(z), 0], + [ 0, 0, 1]]) + R = Rx.dot(Ry).dot(Rz) + return R.astype(np.float32) + +def calc_pose(param): + P = param[:12].reshape(3, -1) # camera matrix + s, R, t3d = P2sRt(P) + P = np.concatenate((R, t3d.reshape(3, -1)), axis=1) # without scale + pose = matrix2angle(R) + pose = [p * 180 / np.pi for p in pose] + + return P, pose + + +def build_camera_box(rear_size=90): + point_3d = [] + rear_depth = 0 + point_3d.append((-rear_size, -rear_size, rear_depth)) + point_3d.append((-rear_size, rear_size, rear_depth)) + point_3d.append((rear_size, rear_size, rear_depth)) + point_3d.append((rear_size, -rear_size, rear_depth)) + point_3d.append((-rear_size, -rear_size, rear_depth)) + + front_size = int(4 / 3 * rear_size) + front_depth = int(4 / 3 * rear_size) + point_3d.append((-front_size, -front_size, front_depth)) + point_3d.append((-front_size, front_size, front_depth)) + point_3d.append((front_size, front_size, front_depth)) + point_3d.append((front_size, -front_size, front_depth)) + point_3d.append((-front_size, -front_size, front_depth)) + point_3d = np.array(point_3d, dtype=np.float32).reshape(-1, 3) + + return point_3d + + +def plot_pose_box(img, P, ver, color=(40, 255, 0), line_width=2): + """ Draw a 3D box as annotation of pose. + Ref:https://github.com/yinguobing/head-pose-estimation/blob/master/pose_estimator.py + Args: + img: the input image + P: (3, 4). Affine Camera Matrix. + kpt: (2, 68) or (3, 68) + """ + llength = calc_hypotenuse(ver) + point_3d = build_camera_box(llength) + # Map to 2d image points + point_3d_homo = np.hstack((point_3d, np.ones([point_3d.shape[0], 1]))) # n x 4 + point_2d = point_3d_homo.dot(P.T)[:, :2] + + point_2d[:, 1] = - point_2d[:, 1] + point_2d[:, :2] = point_2d[:, :2] - np.mean(point_2d[:4, :2], 0) + np.mean(ver[:2, :27], 1) + point_2d = np.int32(point_2d.reshape(-1, 2)) + + # Draw all the lines + cv2.polylines(img, [point_2d], True, color, line_width, cv2.LINE_AA) + cv2.line(img, tuple(point_2d[1]), tuple( + point_2d[6]), color, line_width, cv2.LINE_AA) + cv2.line(img, tuple(point_2d[2]), tuple( + point_2d[7]), color, line_width, cv2.LINE_AA) + cv2.line(img, tuple(point_2d[3]), tuple( + point_2d[8]), color, line_width, cv2.LINE_AA) + + return img + + +def viz_pose(img, param_lst, ver_lst, show_flag=False, wfp=None): + for param, ver in zip(param_lst, ver_lst): + P, pose = calc_pose(param) + img = plot_pose_box(img, P, ver) + # print(P[:, :3]) + print(f'yaw: {pose[0]:.1f}, pitch: {pose[1]:.1f}, roll: {pose[2]:.1f}') + + if wfp is not None: + cv2.imwrite(wfp, img) + print(f'Save visualization result to {wfp}') + + if show_flag: + plot_image(img) + + return img + +def pose_6(param): + P = param[:12].reshape(3, -1) # camera matrix + s, R, t3d = P2sRt(P) + P = np.concatenate((R, t3d.reshape(3, -1)), axis=1) # without scale + pose = matrix2angle(R) + print(t3d) + R1 = angle2matrix(pose) + print(R) + print(R1) + pose = [p * 180 / np.pi for p in pose] + + return s, pose, t3d, P + + +def smooth_pose(img, param_lst, ver_lst, pose_new, show_flag=False, wfp=None, wnp = None): + for param, ver in zip(param_lst, ver_lst): + t3d = np.array([pose_new[4],pose_new[5],pose_new[6]]) + + theta = np.array([pose_new[0],pose_new[1],pose_new[2]]) + theta = [p * np.pi / 180 for p in theta] + R = angle2matrix(theta) + P = np.concatenate((R, t3d.reshape(3, -1)), axis=1) + img = plot_pose_box(img, P, ver) + # print(P,P.shape,t3d) + print(P,pose_new) + print(f'yaw: {theta[0]:.1f}, pitch: {theta[1]:.1f}, roll: {theta[2]:.1f}') + all_pose = [0] + all_pose = np.array(all_pose) + + if wfp is not None: + cv2.imwrite(wfp, img) + print(f'Save visualization result to {wfp}') + + if wnp is not None: + np.save(wnp, all_pose) + print(f'Save visualization result to {wfp}') + + if show_flag: + plot_image(img) + + return img + + + + + +def get_pose(img, param_lst, ver_lst, show_flag=False, wfp=None, wnp = None): + for param, ver in zip(param_lst, ver_lst): + s, pose, t3d, P = pose_6(param) + img = plot_pose_box(img, P, ver) + # print(P,P.shape,t3d) + print(f'yaw: {pose[0]:.1f}, pitch: {pose[1]:.1f}, roll: {pose[2]:.1f}') + all_pose = [pose[0],pose[1],pose[2],s,t3d[0],t3d[1],t3d[2]] + all_pose = np.array(all_pose) + + if wfp is not None: + cv2.imwrite(wfp, img) + print(f'Save visualization result to {wfp}') + + if wnp is not None: + np.save(wnp, all_pose) + print(f'Save visualization result to {wfp}') + + if show_flag: + plot_image(img) + + return all_pose + diff --git a/FONT/LICENSE b/FONT/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..b706c8caa515f46043f25ebc03c29555ea246b3f --- /dev/null +++ b/FONT/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 jixinya + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/FONT/M003_template.npy b/FONT/M003_template.npy new file mode 100644 index 0000000000000000000000000000000000000000..8437319ababe98179daf4e28b740bbfabbe4cb17 --- /dev/null +++ b/FONT/M003_template.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7d9af24074def1fa92e8e83c2dc2a926f7d1d40054580e84472711797b3918d +size 1216 diff --git a/FONT/README.md b/FONT/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d21ac0f93cf192b636d08ae2c622dfe0d92737bf --- /dev/null +++ b/FONT/README.md @@ -0,0 +1,67 @@ +# EAMM: One-Shot Emotional Talking Face via Audio-Based Emotion-Aware Motion Model [SIGGRAPH 2022 Conference] + +Xinya Ji, [Hang Zhou](https://hangz-nju-cuhk.github.io/), Kaisiyuan Wang, [Qianyi Wu](https://wuqianyi.top/), [Wayne Wu](http://wywu.github.io/), [Feng Xu](http://xufeng.site/), [Xun Cao](https://cite.nju.edu.cn/People/Faculty/20190621/i5054.html) + +[[Project]](https://jixinya.github.io/projects/EAMM/) [[Paper]](https://arxiv.org/abs/2205.15278) + +![visualization](demo/teaser-1.png) + +Given a single portrait image, we can synthesize emotional talking faces, where mouth movements match the input audio and facial emotion dynamics follow the emotion source video. + +## Installation + +We train and test based on Python3.6 and Pytorch. To install the dependencies run: + +``` +pip install -r requirements.txt +``` + +## Testing + +- Download the pre-trained models and data under the following link: [google-drive](https://drive.google.com/file/d/1IL9LjH3JegyMqJABqMxrX3StAq_v8Gtp/view?usp=sharing) and put the file in corresponding places. + +- Run the demo: + + `python demo.py --source_image path/to/image --driving_video path/to/emotion_video --pose_file path/to/pose --in_file path/to/audio --emotion emotion_type` + +- Prepare testing data: + + prepare source_image -- crop_image in process_data.py + + prepare driving_video -- crop_image_tem in process_data.py + + prepare pose -- detect pose using [3DDFA_V2](https://github.com/cleardusk/3DDFA_V2) + +## Training + +- Dataset process: Coming soon. + +- Step 1 : Train the Audio2Facial-Dynamics Module using LRW dataset + + `python run.py --config config/train_part1.yaml --mode train_part1 --checkpoint log/124_52000.pth.tar ` + +- Step 2 : Fine-tune the Audio2Facial-Dynamics Module after getting stable results from step1 + + `python run.py --config config/train_part1_fine_tune.yaml --mode train_part1_fine_tune --checkpoint log/124_52000.pth.tar --audio_chechpoint checkpoint/from/step_1` + +- Setp 3 : Train the Implicit Emotion Displacement Learner + + `python run.py --config config/train_part2.yaml --mode train_part2 --checkpoint log/124_52000.pth.tar --audio_chechpoint checkpoint/from/step_2` + +## Citation + +``` +@inproceedings{10.1145/3528233.3530745, +author = {Ji, Xinya and Zhou, Hang and Wang, Kaisiyuan and Wu, Qianyi and Wu, Wayne and Xu, Feng and Cao, Xun}, +title = {EAMM: One-Shot Emotional Talking Face via Audio-Based Emotion-Aware Motion Model}, +year = {2022}, +isbn = {9781450393379}, +url = {https://doi.org/10.1145/3528233.3530745}, +doi = {10.1145/3528233.3530745}, +booktitle = {ACM SIGGRAPH 2022 Conference Proceedings}, +series = {SIGGRAPH '22} +} + + +``` + diff --git a/FONT/audio.py b/FONT/audio.py new file mode 100644 index 0000000000000000000000000000000000000000..af29a63f1d122a994fdb661bb7686c6f8ba694f8 --- /dev/null +++ b/FONT/audio.py @@ -0,0 +1,145 @@ +import librosa +import librosa.filters +import numpy as np +# import tensorflow as tf +from scipy import signal +from scipy.io import wavfile +# from dataset.hparams import hparams as hp +from hparams import hparams as hp +import scipy, cv2, os, sys, argparse + +def load_wav(path, sr): + return librosa.core.load(path, sr=sr)[0] + +def save_wav(wav, path, sr): + wav *= 32767 / max(0.01, np.max(np.abs(wav))) + #proposed by @dsmiller + wavfile.write(path, sr, wav.astype(np.int16)) + +def save_wavenet_wav(wav, path, sr): + librosa.output.write_wav(path, wav, sr=sr) + +def preemphasis(wav, k, preemphasize=True): + if preemphasize: + return signal.lfilter([1, -k], [1], wav) + return wav + +def inv_preemphasis(wav, k, inv_preemphasize=True): + if inv_preemphasize: + return signal.lfilter([1], [1, -k], wav) + return wav + +def get_hop_size(): + hop_size = hp.hop_size + if hop_size is None: + assert hp.frame_shift_ms is not None + hop_size = int(hp.frame_shift_ms / 1000 * hp.sample_rate) + return hop_size + +def linearspectrogram(wav): + D = _stft(preemphasis(wav, hp.preemphasis, hp.preemphasize)) + S = _amp_to_db(np.abs(D)) - hp.ref_level_db + + if hp.signal_normalization: + return _normalize(S) + return S + +def melspectrogram(wav): + D = _stft(preemphasis(wav, hp.preemphasis, hp.preemphasize)) + S = _amp_to_db(_linear_to_mel(np.abs(D))) - hp.ref_level_db + + if hp.signal_normalization: + return _normalize(S) + return S + +def _lws_processor(): + import lws + return lws.lws(hp.n_fft, get_hop_size(), fftsize=hp.win_size, mode="speech") + +def _stft(y): + if hp.use_lws: + return _lws_processor(hp).stft(y).T + else: + return librosa.stft(y=y, n_fft=hp.n_fft, hop_length=get_hop_size(), win_length=hp.win_size) + +########################################################## +#Those are only correct when using lws!!! (This was messing with Wavenet quality for a long time!) +def num_frames(length, fsize, fshift): + """Compute number of time frames of spectrogram + """ + pad = (fsize - fshift) + if length % fshift == 0: + M = (length + pad * 2 - fsize) // fshift + 1 + else: + M = (length + pad * 2 - fsize) // fshift + 2 + return M + + +def pad_lr(x, fsize, fshift): + """Compute left and right padding + """ + M = num_frames(len(x), fsize, fshift) + pad = (fsize - fshift) + T = len(x) + 2 * pad + r = (M - 1) * fshift + fsize - T + return pad, pad + r +########################################################## +#Librosa correct padding +def librosa_pad_lr(x, fsize, fshift): + return 0, (x.shape[0] // fshift + 1) * fshift - x.shape[0] + +# Conversions +_mel_basis = None + +def _linear_to_mel(spectogram): + global _mel_basis + if _mel_basis is None: + _mel_basis = _build_mel_basis() + return np.dot(_mel_basis, spectogram) + +def _build_mel_basis(): + assert hp.fmax <= hp.sample_rate // 2 + return librosa.filters.mel(hp.sample_rate, hp.n_fft, n_mels=hp.num_mels, + fmin=hp.fmin, fmax=hp.fmax) + +def _amp_to_db(x): + min_level = np.exp(hp.min_level_db / 20 * np.log(10)) + return 20 * np.log10(np.maximum(min_level, x)) + +def _db_to_amp(x): + return np.power(10.0, (x) * 0.05) + +def _normalize(S): + if hp.allow_clipping_in_normalization: + if hp.symmetric_mels: + return np.clip((2 * hp.max_abs_value) * ((S - hp.min_level_db) / (-hp.min_level_db)) - hp.max_abs_value, + -hp.max_abs_value, hp.max_abs_value) + else: + return np.clip(hp.max_abs_value * ((S - hp.min_level_db) / (-hp.min_level_db)), 0, hp.max_abs_value) + + assert S.max() <= 0 and S.min() - hp.min_level_db >= 0 + if hp.symmetric_mels: + return (2 * hp.max_abs_value) * ((S - hp.min_level_db) / (-hp.min_level_db)) - hp.max_abs_value + else: + return hp.max_abs_value * ((S - hp.min_level_db) / (-hp.min_level_db)) + +def _denormalize(D): + if hp.allow_clipping_in_normalization: + if hp.symmetric_mels: + return (((np.clip(D, -hp.max_abs_value, + hp.max_abs_value) + hp.max_abs_value) * -hp.min_level_db / (2 * hp.max_abs_value)) + + hp.min_level_db) + else: + return ((np.clip(D, 0, hp.max_abs_value) * -hp.min_level_db / hp.max_abs_value) + hp.min_level_db) + + if hp.symmetric_mels: + return (((D + hp.max_abs_value) * -hp.min_level_db / (2 * hp.max_abs_value)) + hp.min_level_db) + else: + return ((D * -hp.min_level_db / hp.max_abs_value) + hp.min_level_db) + + +if __name__ == "__main__": + audio_path = "/data/liujin/dataset/LRW/lipread_wav/ABOUT/train/ABOUT_00001.wav" + wav = audio.load_wav(audio_path, 16000) + mel = audio.melspectrogram(wav) + print(mel.shape) diff --git a/FONT/augmentation.py b/FONT/augmentation.py new file mode 100644 index 0000000000000000000000000000000000000000..2c45db9fc831b86ca99e5f2ceed4bd38deb10d28 --- /dev/null +++ b/FONT/augmentation.py @@ -0,0 +1,430 @@ +""" +Code from https://github.com/hassony2/torch_videovision +""" + +import numbers +import math +import random +import numpy as np +import PIL +import cv2 +from skimage.transform import resize, rotate, AffineTransform, warp +from skimage.util import pad +import torchvision + +import warnings + +from skimage import img_as_ubyte, img_as_float + + +def crop_clip(clip, min_h, min_w, h, w): + if isinstance(clip[0], np.ndarray): + cropped = [img[min_h:min_h + h, min_w:min_w + w, :] for img in clip] + + elif isinstance(clip[0], PIL.Image.Image): + cropped = [ + img.crop((min_w, min_h, min_w + w, min_h + h)) for img in clip + ] + else: + raise TypeError('Expected numpy.ndarray or PIL.Image' + + 'but got list of {0}'.format(type(clip[0]))) + return cropped + + +def pad_clip(clip, h, w): + im_h, im_w = clip[0].shape[:2] + pad_h = (0, 0) if h < im_h else ((h - im_h) // 2, (h - im_h + 1) // 2) + pad_w = (0, 0) if w < im_w else ((w - im_w) // 2, (w - im_w + 1) // 2) + + return pad(clip, ((0, 0), pad_h, pad_w, (0, 0)), mode='edge') + + +def resize_clip(clip, size, interpolation='bilinear'): + if isinstance(clip[0], np.ndarray): + if isinstance(size, numbers.Number): + im_h, im_w, im_c = clip[0].shape + # Min spatial dim already matches minimal size + if (im_w <= im_h and im_w == size) or (im_h <= im_w + and im_h == size): + return clip + new_h, new_w = get_resize_sizes(im_h, im_w, size) + size = (new_w, new_h) + else: + size = size[1], size[0] + + scaled = [ + resize(img, size, order=1 if interpolation == 'bilinear' else 0, preserve_range=True, + mode='constant', anti_aliasing=True) for img in clip + ] + elif isinstance(clip[0], PIL.Image.Image): + if isinstance(size, numbers.Number): + im_w, im_h = clip[0].size + # Min spatial dim already matches minimal size + if (im_w <= im_h and im_w == size) or (im_h <= im_w + and im_h == size): + return clip + new_h, new_w = get_resize_sizes(im_h, im_w, size) + size = (new_w, new_h) + else: + size = size[1], size[0] + if interpolation == 'bilinear': + pil_inter = PIL.Image.NEAREST + else: + pil_inter = PIL.Image.BILINEAR + scaled = [img.resize(size, pil_inter) for img in clip] + else: + raise TypeError('Expected numpy.ndarray or PIL.Image' + + 'but got list of {0}'.format(type(clip[0]))) + return scaled + + +def get_resize_sizes(im_h, im_w, size): + if im_w < im_h: + ow = size + oh = int(size * im_h / im_w) + else: + oh = size + ow = int(size * im_w / im_h) + return oh, ow + + +class RandomFlip(object): + def __init__(self, time_flip=False, horizontal_flip=False): + self.time_flip = time_flip + self.horizontal_flip = horizontal_flip + + def __call__(self, clip): + if random.random() < 0.5 and self.time_flip: + return clip[::-1] + if random.random() < 0.5 and self.horizontal_flip: + return [np.fliplr(img) for img in clip] + + return clip + + +class RandomResize(object): + """Resizes a list of (H x W x C) numpy.ndarray to the final size + The larger the original image is, the more times it takes to + interpolate + Args: + interpolation (str): Can be one of 'nearest', 'bilinear' + defaults to nearest + size (tuple): (widht, height) + """ + + def __init__(self, ratio=(3. / 4., 4. / 3.), interpolation='nearest'): + self.ratio = ratio + self.interpolation = interpolation + + def __call__(self, clip): + scaling_factor = random.uniform(self.ratio[0], self.ratio[1]) + + if isinstance(clip[0], np.ndarray): + im_h, im_w, im_c = clip[0].shape + elif isinstance(clip[0], PIL.Image.Image): + im_w, im_h = clip[0].size + + new_w = int(im_w * scaling_factor) + new_h = int(im_h * scaling_factor) + new_size = (new_w, new_h) + resized = resize_clip( + clip, new_size, interpolation=self.interpolation) + + return resized + + +class RandomCrop(object): + """Extract random crop at the same location for a list of videos + Args: + size (sequence or int): Desired output size for the + crop in format (h, w) + """ + + def __init__(self, size): + if isinstance(size, numbers.Number): + size = (size, size) + + self.size = size + + def __call__(self, clip): + """ + Args: + img (PIL.Image or numpy.ndarray): List of videos to be cropped + in format (h, w, c) in numpy.ndarray + Returns: + PIL.Image or numpy.ndarray: Cropped list of videos + """ + h, w = self.size + if isinstance(clip[0], np.ndarray): + im_h, im_w, im_c = clip[0].shape + elif isinstance(clip[0], PIL.Image.Image): + im_w, im_h = clip[0].size + else: + raise TypeError('Expected numpy.ndarray or PIL.Image' + + 'but got list of {0}'.format(type(clip[0]))) + + clip = pad_clip(clip, h, w) + im_h, im_w = clip.shape[1:3] + x1 = 0 if h == im_h else random.randint(0, im_w - w) + y1 = 0 if w == im_w else random.randint(0, im_h - h) + cropped = crop_clip(clip, y1, x1, h, w) + + return cropped + + +class MouthCrop(object): + """Extract random crop at the same location for a list of videos + Args: + size (sequence or int): Desired output size for the + crop in format (h, w) + """ + + def __init__(self, center_x, center_y, mask_width, mask_height): + + + self.center_x = center_x + self.center_y = center_y + self.mask_width = mask_width + self.mask_height = mask_height + + def __call__(self, clip): + """ + Args: + img (PIL.Image or numpy.ndarray): List of videos to be cropped + in format (h, w, c) in numpy.ndarray + Returns: + PIL.Image or numpy.ndarray: Cropped list of videos + """ + start_x = self.center_x - int(self.mask_width/2) + start_y = self.center_y - int(self.mask_height/2) + end_x = start_x + self.mask_width + end_y = start_y + self.mask_height + # mask is all white + # mask = 255*np.ones((mask_height, mask_width, 3), dtype=np.uint8) + # mask is uniform noise + cropped = [] + for i in range(len(clip)): + mask = np.random.rand(self.mask_height, self.mask_width, 3) + img = clip[i].copy() + img[start_y:end_y, start_x:end_x, :] = mask + + cropped.append(img) + cropped = np.array(cropped) + return cropped + +class RandomRotation(object): + """Rotate entire clip randomly by a random angle within + given bounds + Args: + degrees (sequence or int): Range of degrees to select from + If degrees is a number instead of sequence like (min, max), + the range of degrees, will be (-degrees, +degrees). + """ + + def __init__(self, degrees): + if isinstance(degrees, numbers.Number): + if degrees < 0: + raise ValueError('If degrees is a single number,' + 'must be positive') + degrees = (-degrees, degrees) + else: + if len(degrees) != 2: + raise ValueError('If degrees is a sequence,' + 'it must be of len 2.') + + self.degrees = degrees + + def __call__(self, clip): + """ + Args: + img (PIL.Image or numpy.ndarray): List of videos to be cropped + in format (h, w, c) in numpy.ndarray + Returns: + PIL.Image or numpy.ndarray: Cropped list of videos + """ + angle = random.uniform(self.degrees[0], self.degrees[1]) + if isinstance(clip[0], np.ndarray): + rotated = [rotate(image=img, angle=angle, preserve_range=True) for img in clip] + elif isinstance(clip[0], PIL.Image.Image): + rotated = [img.rotate(angle) for img in clip] + else: + raise TypeError('Expected numpy.ndarray or PIL.Image' + + 'but got list of {0}'.format(type(clip[0]))) + + return rotated + +class RandomPerspective(object): + """Rotate entire clip randomly by a random angle within + given bounds + Args: + degrees (sequence or int): Range of degrees to select from + If degrees is a number instead of sequence like (min, max), + the range of degrees, will be (-degrees, +degrees). + """ + + def __init__(self, pers_num, enlarge_num): + self.pers_num = pers_num + self.enlarge_num = enlarge_num + + def __call__(self, clip): + """ + Args: + img (PIL.Image or numpy.ndarray): List of videos to be cropped + in format (h, w, c) in numpy.ndarray + Returns: + PIL.Image or numpy.ndarray: Cropped list of videos + """ + out = clip + for i in range(len(clip)): + self.pers_size = np.random.randint(20, self.pers_num) * pow(-1, np.random.randint(2)) + self.enlarge_size = np.random.randint(20, self.enlarge_num) * pow(-1, np.random.randint(2)) + h, w, c = clip[i].shape + crop_size=256 + dst = np.array([ + [-self.enlarge_size, -self.enlarge_size], + [-self.enlarge_size + self.pers_size, w + self.enlarge_size], + [h + self.enlarge_size, -self.enlarge_size], + [h + self.enlarge_size - self.pers_size, w + self.enlarge_size],], dtype=np.float32) + src = np.array([[-self.enlarge_size, -self.enlarge_size], [-self.enlarge_size, w + self.enlarge_size], + [h + self.enlarge_size, -self.enlarge_size], [h + self.enlarge_size, w + self.enlarge_size]]).astype(np.float32()) + M = cv2.getPerspectiveTransform(src, dst) + warped = cv2.warpPerspective(clip[i], M, (crop_size, crop_size), borderMode=cv2.BORDER_REPLICATE) + out[i] = warped + + return out + + +class ColorJitter(object): + """Randomly change the brightness, contrast and saturation and hue of the clip + Args: + brightness (float): How much to jitter brightness. brightness_factor + is chosen uniformly from [max(0, 1 - brightness), 1 + brightness]. + contrast (float): How much to jitter contrast. contrast_factor + is chosen uniformly from [max(0, 1 - contrast), 1 + contrast]. + saturation (float): How much to jitter saturation. saturation_factor + is chosen uniformly from [max(0, 1 - saturation), 1 + saturation]. + hue(float): How much to jitter hue. hue_factor is chosen uniformly from + [-hue, hue]. Should be >=0 and <= 0.5. + """ + + def __init__(self, brightness=0, contrast=0, saturation=0, hue=0): + self.brightness = brightness + self.contrast = contrast + self.saturation = saturation + self.hue = hue + + def get_params(self, brightness, contrast, saturation, hue): + if brightness > 0: + brightness_factor = random.uniform( + max(0, 1 - brightness), 1 + brightness) + else: + brightness_factor = None + + if contrast > 0: + contrast_factor = random.uniform( + max(0, 1 - contrast), 1 + contrast) + else: + contrast_factor = None + + if saturation > 0: + saturation_factor = random.uniform( + max(0, 1 - saturation), 1 + saturation) + else: + saturation_factor = None + + if hue > 0: + hue_factor = random.uniform(-hue, hue) + else: + hue_factor = None + return brightness_factor, contrast_factor, saturation_factor, hue_factor + + def __call__(self, clip): + """ + Args: + clip (list): list of PIL.Image + Returns: + list PIL.Image : list of transformed PIL.Image + """ + if isinstance(clip[0], np.ndarray): + brightness, contrast, saturation, hue = self.get_params( + self.brightness, self.contrast, self.saturation, self.hue) + + # Create img transform function sequence + img_transforms = [] + if brightness is not None: + img_transforms.append(lambda img: torchvision.transforms.functional.adjust_brightness(img, brightness)) + if saturation is not None: + img_transforms.append(lambda img: torchvision.transforms.functional.adjust_saturation(img, saturation)) + if hue is not None: + img_transforms.append(lambda img: torchvision.transforms.functional.adjust_hue(img, hue)) + if contrast is not None: + img_transforms.append(lambda img: torchvision.transforms.functional.adjust_contrast(img, contrast)) + random.shuffle(img_transforms) + img_transforms = [img_as_ubyte, torchvision.transforms.ToPILImage()] + img_transforms + [np.array, + img_as_float] + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jittered_clip = [] + for img in clip: + jittered_img = img + for func in img_transforms: + jittered_img = func(jittered_img) + jittered_clip.append(jittered_img.astype('float32')) + elif isinstance(clip[0], PIL.Image.Image): + brightness, contrast, saturation, hue = self.get_params( + self.brightness, self.contrast, self.saturation, self.hue) + + # Create img transform function sequence + img_transforms = [] + if brightness is not None: + img_transforms.append(lambda img: torchvision.transforms.functional.adjust_brightness(img, brightness)) + if saturation is not None: + img_transforms.append(lambda img: torchvision.transforms.functional.adjust_saturation(img, saturation)) + if hue is not None: + img_transforms.append(lambda img: torchvision.transforms.functional.adjust_hue(img, hue)) + if contrast is not None: + img_transforms.append(lambda img: torchvision.transforms.functional.adjust_contrast(img, contrast)) + random.shuffle(img_transforms) + + # Apply to all videos + jittered_clip = [] + for img in clip: + for func in img_transforms: + jittered_img = func(img) + jittered_clip.append(jittered_img) + + else: + raise TypeError('Expected numpy.ndarray or PIL.Image' + + 'but got list of {0}'.format(type(clip[0]))) + return jittered_clip + + +class AllAugmentationTransform: + def __init__(self, crop_mouth_param = None, resize_param=None, rotation_param=None, perspective_param=None, flip_param=None, crop_param=None, jitter_param=None): + self.transforms = [] + if crop_mouth_param is not None: + self.transforms.append(MouthCrop(**crop_mouth_param)) + + if flip_param is not None: + self.transforms.append(RandomFlip(**flip_param)) + + if rotation_param is not None: + self.transforms.append(RandomRotation(**rotation_param)) + + if perspective_param is not None: + self.transforms.append(RandomPerspective(**perspective_param)) + + if resize_param is not None: + self.transforms.append(RandomResize(**resize_param)) + + if crop_param is not None: + self.transforms.append(RandomCrop(**crop_param)) + + if jitter_param is not None: + self.transforms.append(ColorJitter(**jitter_param)) + + def __call__(self, clip): + for t in self.transforms: + clip = t(clip) + return clip diff --git a/FONT/config/MEAD_emo_video_aug_delta_4_crop_random_crop.yaml b/FONT/config/MEAD_emo_video_aug_delta_4_crop_random_crop.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2207228fa0d6519f27c33176f18f6d59b81ab683 --- /dev/null +++ b/FONT/config/MEAD_emo_video_aug_delta_4_crop_random_crop.yaml @@ -0,0 +1,105 @@ +dataset_params: + root_dir: /mnt/lustre/share_data/jixinya/MEAD/ + frame_shape: [256, 256, 3] + id_sampling: False + pairs_list: Random_choice + augmentation_params: + crop_mouth_param: + center_x: 135 + center_y: 190 + mask_width: 100 + mask_height: 60 + rotation_param: + degrees: 30 + perspective_param: + pers_num: 30 + enlarge_num: 40 + flip_param: + horizontal_flip: True + time_flip: False + jitter_param: + brightness: 0 + contrast: 0 + saturation: 0 + hue: 0 + +model_params: + common_params: + num_kp: 10 + num_channels: 3 + estimate_jacobian: True + audio_params: + num_kp: 10 + num_channels : 3 + num_channels_a : 3 + estimate_jacobian: True + kp_detector_params: + temperature: 0.1 + block_expansion: 32 + max_features: 1024 + scale_factor: 0.25 + num_blocks: 5 + generator_params: + block_expansion: 64 + max_features: 512 + num_down_blocks: 2 + num_bottleneck_blocks: 6 + estimate_occlusion_map: True + dense_motion_params: + block_expansion: 64 + max_features: 1024 + num_blocks: 5 + scale_factor: 0.25 + discriminator_params: + scales: [1] + block_expansion: 32 + max_features: 512 + num_blocks: 4 + sn: True + +train_params: + type: linear_4 + smooth: False + jaco_net: cnn + ldmark: fake + generator: not + train_generator: False + num_epochs: 300 + num_repeats: 1 + epoch_milestones: [60, 90] + lr_generator: 2.0e-4 + lr_discriminator: 2.0e-4 + lr_kp_detector: 2.0e-4 + lr_audio_feature: 2.0e-4 + batch_size: 16 + scales: [1, 0.5, 0.25, 0.125] + checkpoint_freq: 1 + transform_params: + sigma_affine: 0.05 + sigma_tps: 0.005 + points_tps: 5 + loss_weights: + generator_gan: 0 + discriminator_gan: 1 + feature_matching: [10, 10, 10, 10] + perceptual: [10, 10, 10, 10, 10] + equivariance_value: 0 + equivariance_jacobian: 0 + emo: 10 + +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: + kp_size: 5 + draw_border: True + colormap: 'gist_rainbow' diff --git a/FONT/config/train_part1.yaml b/FONT/config/train_part1.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8be210e1bdab59e8f3773aa6e4fdb26b82293740 --- /dev/null +++ b/FONT/config/train_part1.yaml @@ -0,0 +1,83 @@ +dataset_params: + name: Lrw + root_dir: dataset/LRW/ + frame_shape: [256, 256, 3] + id_sampling: False + augmentation_params: + flip_param: + horizontal_flip: False + time_flip: False + jitter_param: + brightness: 0.1 + contrast: 0.1 + saturation: 0.1 + hue: 0.1 + + +model_params: + common_params: + num_kp: 10 + num_channels: 3 + estimate_jacobian: True + audio_params: + num_kp: 10 + num_channels : 3 + num_channels_a : 3 + estimate_jacobian: True + kp_detector_params: + temperature: 0.1 + block_expansion: 32 + max_features: 1024 + scale_factor: 0.25 + num_blocks: 5 + generator_params: + block_expansion: 64 + max_features: 512 + num_down_blocks: 2 + num_bottleneck_blocks: 6 + estimate_occlusion_map: True + dense_motion_params: + block_expansion: 64 + max_features: 1024 + num_blocks: 5 + scale_factor: 0.25 + discriminator_params: + scales: [1] + block_expansion: 32 + max_features: 512 + num_blocks: 4 + sn: True + +train_params: + jaco_net: cnn + ldmark: fake + generator: not + num_epochs: 600 + num_repeats: 1 + epoch_milestones: [60, 90] + lr_generator: 2.0e-4 + lr_discriminator: 2.0e-4 + lr_kp_detector: 2.0e-4 + lr_audio_feature: 2.0e-4 + batch_size: 16 + scales: [1, 0.5, 0.25, 0.125] + checkpoint_freq: 1 + transform_params: + sigma_affine: 0.05 + sigma_tps: 0.005 + points_tps: 5 + loss_weights: + generator_gan: 0 + discriminator_gan: 0 + feature_matching: [10, 10, 10, 10] + perceptual: [10, 10, 10, 10, 10] + equivariance_value: 0 + equivariance_jacobian: 0 + audio: 10 + + + +visualizer_params: + kp_size: 5 + draw_border: True + colormap: 'gist_rainbow' diff --git a/FONT/config/train_part1_fine_tune.yaml b/FONT/config/train_part1_fine_tune.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d9ae915d6fff1c1fdebb651bfc208c31656317eb --- /dev/null +++ b/FONT/config/train_part1_fine_tune.yaml @@ -0,0 +1,81 @@ +dataset_params: + name: Lrw + root_dir: dataset/LRW/ + frame_shape: [256, 256, 3] + id_sampling: False + augmentation_params: + flip_param: + horizontal_flip: False + time_flip: False + jitter_param: + brightness: 0.1 + contrast: 0.1 + saturation: 0.1 + hue: 0.1 + + +model_params: + common_params: + num_kp: 10 + num_channels: 3 + estimate_jacobian: True + audio_params: + num_kp: 10 + num_channels : 3 + num_channels_a : 3 + estimate_jacobian: True + kp_detector_params: + temperature: 0.1 + block_expansion: 32 + max_features: 1024 + scale_factor: 0.25 + num_blocks: 5 + generator_params: + block_expansion: 64 + max_features: 512 + num_down_blocks: 2 + num_bottleneck_blocks: 6 + estimate_occlusion_map: True + dense_motion_params: + block_expansion: 64 + max_features: 1024 + num_blocks: 5 + scale_factor: 0.25 + discriminator_params: + scales: [1] + block_expansion: 32 + max_features: 512 + num_blocks: 4 + sn: True + +train_params: + jaco_net: cnn + ldmark: fake + generator: audio + num_epochs: 600 + num_repeats: 1 + epoch_milestones: [60, 90] + lr_generator: 2.0e-4 + lr_discriminator: 2.0e-4 + lr_kp_detector: 2.0e-4 + lr_audio_feature: 2.0e-4 + batch_size: 6 + scales: [1, 0.5, 0.25, 0.125] + checkpoint_freq: 1 + transform_params: + sigma_affine: 0.05 + sigma_tps: 0.005 + points_tps: 5 + loss_weights: + generator_gan: 0 + discriminator_gan: 0 + feature_matching: [10, 10, 10, 10] + perceptual: [0.1, 0.1, 0.1, 0.1, 0.1] + equivariance_value: 0 + equivariance_jacobian: 0 + audio: 10 + +visualizer_params: + kp_size: 5 + draw_border: True + colormap: 'gist_rainbow' diff --git a/FONT/config/train_part1_fine_tune_hdtf.yaml b/FONT/config/train_part1_fine_tune_hdtf.yaml new file mode 100644 index 0000000000000000000000000000000000000000..538140e2cc9ec458c0393c68ea59820e7e27ee4a --- /dev/null +++ b/FONT/config/train_part1_fine_tune_hdtf.yaml @@ -0,0 +1,81 @@ +dataset_params: + name: hdtf + root_dir: dataset/LRW/ + frame_shape: [256, 256, 3] + id_sampling: False + augmentation_params: + flip_param: + horizontal_flip: False + time_flip: False + jitter_param: + brightness: 0.1 + contrast: 0.1 + saturation: 0.1 + hue: 0.1 + + +model_params: + common_params: + num_kp: 10 + num_channels: 3 + estimate_jacobian: True + audio_params: + num_kp: 10 + num_channels : 3 + num_channels_a : 3 + estimate_jacobian: True + kp_detector_params: + temperature: 0.1 + block_expansion: 32 + max_features: 1024 + scale_factor: 0.25 + num_blocks: 5 + generator_params: + block_expansion: 64 + max_features: 512 + num_down_blocks: 2 + num_bottleneck_blocks: 6 + estimate_occlusion_map: True + dense_motion_params: + block_expansion: 64 + max_features: 1024 + num_blocks: 5 + scale_factor: 0.25 + discriminator_params: + scales: [1] + block_expansion: 32 + max_features: 512 + num_blocks: 4 + sn: True + +train_params: + jaco_net: cnn + ldmark: fake + generator: audio + num_epochs: 7000 + num_repeats: 1 + epoch_milestones: [60, 90] + lr_generator: 2.0e-4 + lr_discriminator: 2.0e-4 + lr_kp_detector: 2.0e-4 + lr_audio_feature: 2.0e-4 + batch_size: 6 + scales: [1, 0.5, 0.25, 0.125] + checkpoint_freq: 1 + transform_params: + sigma_affine: 0.05 + sigma_tps: 0.005 + points_tps: 5 + loss_weights: + generator_gan: 0 + discriminator_gan: 0 + feature_matching: [10, 10, 10, 10] + perceptual: [0.1, 0.1, 0.1, 0.1, 0.1] + equivariance_value: 0 + equivariance_jacobian: 0 + audio: 10 + +visualizer_params: + kp_size: 5 + draw_border: True + colormap: 'gist_rainbow' diff --git a/FONT/config/train_part1_hdtf.yaml b/FONT/config/train_part1_hdtf.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1f18b843e5b7e3f9a8e256049329ddb38f4b2758 --- /dev/null +++ b/FONT/config/train_part1_hdtf.yaml @@ -0,0 +1,83 @@ +dataset_params: + name: hdtf + root_dir: dataset/LRW/ + frame_shape: [256, 256, 3] + id_sampling: False + augmentation_params: + flip_param: + horizontal_flip: False + time_flip: False + jitter_param: + brightness: 0.1 + contrast: 0.1 + saturation: 0.1 + hue: 0.1 + + +model_params: + common_params: + num_kp: 10 + num_channels: 3 + estimate_jacobian: True + audio_params: + num_kp: 10 + num_channels : 3 + num_channels_a : 3 + estimate_jacobian: True + kp_detector_params: + temperature: 0.1 + block_expansion: 32 + max_features: 1024 + scale_factor: 0.25 + num_blocks: 5 + generator_params: + block_expansion: 64 + max_features: 512 + num_down_blocks: 2 + num_bottleneck_blocks: 6 + estimate_occlusion_map: True + dense_motion_params: + block_expansion: 64 + max_features: 1024 + num_blocks: 5 + scale_factor: 0.25 + discriminator_params: + scales: [1] + block_expansion: 32 + max_features: 512 + num_blocks: 4 + sn: True + +train_params: + jaco_net: cnn + ldmark: fake + generator: not + num_epochs: 100000 + num_repeats: 1 + epoch_milestones: [60, 90] + lr_generator: 2.0e-4 + lr_discriminator: 2.0e-4 + lr_kp_detector: 2.0e-4 + lr_audio_feature: 2.0e-4 + batch_size: 16 + scales: [1, 0.5, 0.25, 0.125] + checkpoint_freq: 1 + transform_params: + sigma_affine: 0.05 + sigma_tps: 0.005 + points_tps: 5 + loss_weights: + generator_gan: 0 + discriminator_gan: 0 + feature_matching: [10, 10, 10, 10] + perceptual: [10, 10, 10, 10, 10] + equivariance_value: 0 + equivariance_jacobian: 0 + audio: 10 + + + +visualizer_params: + kp_size: 5 + draw_border: True + colormap: 'gist_rainbow' diff --git a/FONT/config/train_part2.yaml b/FONT/config/train_part2.yaml new file mode 100644 index 0000000000000000000000000000000000000000..cd3757bf5151d00b0da2224a65db830ff8562b68 --- /dev/null +++ b/FONT/config/train_part2.yaml @@ -0,0 +1,93 @@ +dataset_params: + name: MEAD + root_dir: dataset/MEAD/ + frame_shape: [256, 256, 3] + id_sampling: False + augmentation_params: + crop_mouth_param: + center_x: 135 + center_y: 190 + mask_width: 100 + mask_height: 60 + rotation_param: + degrees: 30 + perspective_param: + pers_num: 30 + enlarge_num: 40 + flip_param: + horizontal_flip: True + time_flip: False + jitter_param: + brightness: 0 + contrast: 0 + saturation: 0 + hue: 0 + +model_params: + common_params: + num_kp: 10 + num_channels: 3 + estimate_jacobian: True + audio_params: + num_kp: 10 + num_channels : 3 + num_channels_a : 3 + estimate_jacobian: True + kp_detector_params: + temperature: 0.1 + block_expansion: 32 + max_features: 1024 + scale_factor: 0.25 + num_blocks: 5 + generator_params: + block_expansion: 64 + max_features: 512 + num_down_blocks: 2 + num_bottleneck_blocks: 6 + estimate_occlusion_map: True + dense_motion_params: + block_expansion: 64 + max_features: 1024 + num_blocks: 5 + scale_factor: 0.25 + discriminator_params: + scales: [1] + block_expansion: 32 + max_features: 512 + num_blocks: 4 + sn: True + +train_params: + type: linear_4 + smooth: False + jaco_net: cnn + ldmark: fake + generator: not + num_epochs: 300 + num_repeats: 1 + epoch_milestones: [60, 90] + lr_generator: 2.0e-4 + lr_discriminator: 2.0e-4 + lr_kp_detector: 2.0e-4 + lr_audio_feature: 2.0e-4 + batch_size: 16 + scales: [1, 0.5, 0.25, 0.125] + checkpoint_freq: 1 + transform_params: + sigma_affine: 0.05 + sigma_tps: 0.005 + points_tps: 5 + loss_weights: + generator_gan: 0 + discriminator_gan: 0 + feature_matching: [10, 10, 10, 10] + perceptual: [10, 10, 10, 10, 10] + equivariance_value: 0 + equivariance_jacobian: 0 + emo: 10 + + +visualizer_params: + kp_size: 5 + draw_border: True + colormap: 'gist_rainbow' diff --git a/FONT/demo.py b/FONT/demo.py new file mode 100644 index 0000000000000000000000000000000000000000..e86e898f53464abf0c58e886b6eeb2efc18ef191 --- /dev/null +++ b/FONT/demo.py @@ -0,0 +1,632 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Oct 6 20:57:27 2021 + +@author: thea +""" + +import matplotlib +matplotlib.use('Agg') +import os,sys +import yaml +from argparse import ArgumentParser +from tqdm import tqdm +from skimage import io, img_as_float32 +import imageio +import numpy as np +from skimage.transform import resize +from skimage import img_as_ubyte +import torch +from filter1 import OneEuroFilter +import torch.utils + +from torch.autograd import Variable +from modules.generator import OcclusionAwareGenerator +from modules.keypoint_detector import KPDetector, KPDetector_a +from modules.util import AT_net, Emotion_k, Emotion_map, AT_net2 +from augmentation import AllAugmentationTransform + +from scipy.spatial import ConvexHull +import audio +import random +import python_speech_features +from pathlib import Path +import dlib +import cv2 +from skimage.draw import circle +import matplotlib.pyplot as plt +import librosa +from skimage import transform as tf +import torch.nn.functional as F + + + +detector = dlib.get_frontal_face_detector() +predictor = dlib.shape_predictor('./ckpt/shape_predictor_68_face_landmarks.dat') + + + + +def load_checkpoints(opt, checkpoint_path, audio_checkpoint_path, emo_checkpoint_path, kp_checkpoint_path, cpu=False): + + with open(opt.config) as f: + config = yaml.load(f, Loader=yaml.FullLoader) + + generator = OcclusionAwareGenerator(**config['model_params']['generator_params'], + **config['model_params']['common_params']) + if not cpu: + generator.cuda() + + kp_detector = KPDetector(**config['model_params']['kp_detector_params'], + **config['model_params']['common_params']) + if not cpu: + kp_detector.cuda() + + kp_detector_a = KPDetector_a(**config['model_params']['kp_detector_params'], + **config['model_params']['audio_params']) + + audio_feature = AT_net2() + # audio_feature = AT_net() + # audio_feature = AT_net_ori() + if opt.type.startswith('linear'): + emo_detector = Emotion_k(block_expansion=32, num_channels=3, max_features=1024, + num_blocks=5, scale_factor=0.25, num_classes=8) + elif opt.type.startswith('map'): + emo_detector = Emotion_map(block_expansion=32, num_channels=3, max_features=1024, + num_blocks=5, scale_factor=0.25, num_classes=8) + if not cpu: + kp_detector_a.cuda() + audio_feature.cuda() + emo_detector.cuda() + + + + + if cpu: + checkpoint = torch.load(checkpoint_path, map_location=torch.device('cpu')) + audio_checkpoint = torch.load(audio_checkpoint_path, map_location=torch.device('cpu')) + emo_checkpoint = torch.load(emo_checkpoint_path, map_location=torch.device('cpu')) + kp_checkpoint = torch.load(kp_checkpoint_path, map_location=torch.device('cpu')) + else: + checkpoint = torch.load(checkpoint_path) + audio_checkpoint = torch.load(audio_checkpoint_path) + emo_checkpoint = torch.load(emo_checkpoint_path) + kp_checkpoint = torch.load(kp_checkpoint_path) + + generator.load_state_dict(checkpoint['generator']) + kp_detector.load_state_dict(kp_checkpoint['kp_detector']) + audio_feature.load_state_dict(audio_checkpoint['audio_feature'], strict=False) + + + + + kp_detector_a.load_state_dict(audio_checkpoint['kp_detector_a']) + emo_detector.load_state_dict(emo_checkpoint['emo_detector']) + + + if not cpu: + generator = generator.cuda() + kp_detector = kp_detector.cuda() + audio_feature = audio_feature.cuda() + kp_detector_a = kp_detector_a.cuda() + emo_detector = emo_detector.cuda() + + generator.eval() + kp_detector.eval() + audio_feature.eval() + kp_detector_a.eval() + emo_detector.eval() + return generator, kp_detector, kp_detector_a, audio_feature, emo_detector + +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['value'][0].data.cpu().numpy()).volume + driving_area = ConvexHull(kp_driving_initial['value'][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['value'] - kp_driving_initial['value']) + kp_value_diff *= adapt_movement_scale + kp_new['value'] = kp_value_diff + kp_source['value'] + + 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 + +def shape_to_np(shape, dtype="int"): + # initialize the list of (x, y)-coordinates + coords = np.zeros((shape.num_parts, 2), dtype=dtype) + + # loop over all facial landmarks and convert them + # to a 2-tuple of (x, y)-coordinates + for i in range(0, shape.num_parts): + coords[i] = (shape.part(i).x, shape.part(i).y) + + # return the list of (x, y)-coordinates + return coords + +def get_aligned_image(driving_video, opt): + aligned_array = [] + + video_array = np.array(driving_video) + source_image=video_array[0] + # aligned_array.append(source_image) + source_image = np.array(source_image * 255, dtype=np.uint8) + gray = cv2.cvtColor(source_image, cv2.COLOR_BGR2GRAY) + rects = detector(gray, 1) #detect human face + for (i, rect) in enumerate(rects): + template = predictor(gray, rect) #detect 68 points + template = shape_to_np(template) + + if opt.emotion == 'surprised' or opt.emotion == 'fear': + template = template-[0,10] + for i in range(len(video_array)): + image=np.array(video_array[i] * 255, dtype=np.uint8) + gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + rects = detector(gray, 1) #detect human face + for (j, rect) in enumerate(rects): + shape = predictor(gray, rect) #detect 68 points + shape = shape_to_np(shape) + + pts2 = np.float32(template[:35,:]) + pts1 = np.float32(shape[:35,:]) #eye and nose + + # pts2 = np.float32(np.concatenate((template[:16,:],template[27:36,:]),axis = 0)) + # pts1 = np.float32(np.concatenate((shape[:16,:],shape[27:36,:]),axis = 0)) #eye and nose + # pts1 = np.float32(landmark[17:35,:]) + tform = tf.SimilarityTransform() + tform.estimate( pts2, pts1) #Set the transformation matrix with the explicit parameters. + dst = tf.warp(image, tform, output_shape=(256, 256)) + + dst = np.array(dst, dtype=np.float32) + aligned_array.append(dst) + + return aligned_array + +def get_transformed_image(driving_video, opt): + video_array = np.array(driving_video) + with open(opt.config) as f: + config = yaml.load(f, Loader=yaml.FullLoader) + transformations = AllAugmentationTransform(**config['dataset_params']['augmentation_params']) + transformed_array = transformations(video_array) + return transformed_array + + + +def make_animation_smooth(source_image, driving_video, transformed_video, deco_out, kp_loss, generator, kp_detector, kp_detector_a, emo_detector, opt, relative=True, adapt_movement_scale=True, cpu=False): + with torch.no_grad(): + predictions = [] + + + source = torch.tensor(source_image[np.newaxis].astype(np.float32)).permute(0, 3, 1, 2) + if not cpu: + source = source.cuda() + + + driving = torch.tensor(np.array(driving_video)[np.newaxis].astype(np.float32)).permute(0, 4, 1, 2, 3) + transformed_driving = torch.tensor(np.array(transformed_video)[np.newaxis].astype(np.float32)).permute(0, 4, 1, 2, 3) + + + kp_source = kp_detector(source) + + save_dpi = (100, 100) + # save kp source image + # source = kp_source['value'].data.cpu().numpy() + # kp_array = source[0] + # image = np.zeros((256,256,3)) + # spatial_size = np.array(image.shape[:2][::-1])[np.newaxis] + # kp_array = spatial_size * (kp_array + 1) / 2 + # num_kp = kp_array.shape[0] + colormap = plt.get_cmap('gist_rainbow') + # for kp_ind, kp in enumerate(kp_array): + # rr, cc = circle(kp[1], kp[0], 5, shape=image.shape[:2]) + # image[rr, cc] = np.array(colormap(kp_ind / num_kp))[:3] + # imageio.imsave('./result/kp_source.png', image) + + + + kp_driving_initial = kp_detector_a(deco_out[:,0]) + + + emo_driving_all = [] + features = [] + kp_driving_all = [] + for frame_idx in tqdm(range(len(deco_out[0]))): + + driving_frame = driving[:, :, frame_idx] + transformed_frame = transformed_driving[:, :, frame_idx] + if not cpu: + driving_frame = driving_frame.cuda() + transformed_frame = transformed_frame.cuda() + kp_driving = kp_detector_a(deco_out[:,frame_idx]) + kp_driving_all.append(kp_driving) + if opt.add_emo: + value = kp_driving['value'] + jacobian = kp_driving['jacobian'] + if opt.type == 'linear_3': + emo_driving,_ = emo_detector(transformed_frame,value,jacobian) + features.append(emo_detector.feature(transformed_frame).data.cpu().numpy()) + + emo_driving_all.append(emo_driving) + features = np.array(features) + if opt.add_emo: + one_euro_filter_v = OneEuroFilter(mincutoff=1, beta=0.2, dcutoff=1.0, freq=100)#1 0.4 + one_euro_filter_j = OneEuroFilter(mincutoff=1, beta=0.2, dcutoff=1.0, freq=100)#1 0.4 + + for j in range(len(emo_driving_all)): + emo_driving_all[j]['value']=one_euro_filter_v.process(emo_driving_all[j]['value'].cpu()*100)/100 + emo_driving_all[j]['value'] = emo_driving_all[j]['value'].cuda() + emo_driving_all[j]['jacobian']=one_euro_filter_j.process(emo_driving_all[j]['jacobian'].cpu()*100)/100 + emo_driving_all[j]['jacobian'] = emo_driving_all[j]['jacobian'].cuda() + + + one_euro_filter_v = OneEuroFilter(mincutoff=0.05, beta=8, dcutoff=1.0, freq=100) + one_euro_filter_j = OneEuroFilter(mincutoff=0.05, beta=8, dcutoff=1.0, freq=100) + + for j in range(len(kp_driving_all)): + kp_driving_all[j]['value']=one_euro_filter_v.process(kp_driving_all[j]['value'].cpu()*10)/10 + kp_driving_all[j]['value'] = kp_driving_all[j]['value'].cuda() + kp_driving_all[j]['jacobian']=one_euro_filter_j.process(kp_driving_all[j]['jacobian'].cpu()*10)/10 + kp_driving_all[j]['jacobian'] = kp_driving_all[j]['jacobian'].cuda() + + + for frame_idx in tqdm(range(len(deco_out[0]))): + + if opt.check_add: + kp_driving = kp_detector_a(deco_out[:,0]) + else: + kp_driving = kp_driving_all[frame_idx] + + # kp_driving_real = kp_detector(driving_frame) + + # kp_driving['value'] = (1-opt.weight)*kp_driving['value'] + opt.weight*kp_driving_real['value'] + # kp_driving['jacobian'] = (1-opt.weight)*kp_driving['jacobian'] + opt.weight*kp_driving_real['jacobian'] + + if opt.add_emo: + emo_driving = emo_driving_all[frame_idx] + if opt.type == 'linear_3': + kp_driving['value'][:,1] = kp_driving['value'][:,1] + emo_driving['value'][:,0]*0.2 + kp_driving['jacobian'][:,1] = kp_driving['jacobian'][:,1] + emo_driving['jacobian'][:,0]*0.2 + kp_driving['value'][:,4] = kp_driving['value'][:,4] + emo_driving['value'][:,1] + kp_driving['jacobian'][:,4] = kp_driving['jacobian'][:,4] + emo_driving['jacobian'][:,1] + kp_driving['value'][:,6] = kp_driving['value'][:,6] + emo_driving['value'][:,2] + kp_driving['jacobian'][:,6] = kp_driving['jacobian'][:,6] + emo_driving['jacobian'][:,2] + # kp_driving['value'][:,8] = kp_driving['value'][:,8] + emo_driving['value'][:,3] + # kp_driving['jacobian'][:,8] = kp_driving['jacobian'][:,8] + emo_driving['jacobian'][:,3] + + + kp_norm = normalize_kp(kp_source=kp_source, kp_driving=kp_driving, + kp_driving_initial=kp_driving_initial, use_relative_movement=relative, + use_relative_jacobian=relative, adapt_movement_scale=adapt_movement_scale) + out = generator(source, kp_source=kp_source, kp_driving=kp_norm) + + # occlusion_map = out['occlusion_map'].data.cpu().repeat(1, 3, 1, 1) + # occlusion_map = F.interpolate(occlusion_map, size=(256,256)).numpy() + # occlusion_map = np.transpose(occlusion_map, [0, 2, 3, 1]) + # print(occlusion_map.shape) + # imageio.imsave('./result/occlusion.png', occlusion_map[0], dpi=save_dpi) + # + # prediction = out['prediction'].data.cpu().numpy() + # prediction = np.transpose(prediction, [0, 2, 3, 1]) + # print(prediction.shape) + # imageio.imsave('./result/prediction.png', prediction[0], dpi=save_dpi) + # + # full_mask = [] + # for i in range(out['sparse_deformed'].shape[1]): + # mask = out['mask'][:, i:(i + 1)].data.cpu().repeat(1, 3, 1, 1) + # mask = F.interpolate(mask, size=(256,256)) + # mask = np.transpose(mask.numpy(), (0, 2, 3, 1)) + # if i != 0: + # color = np.array(colormap((i - 1) / (out['sparse_deformed'].shape[1] - 1)))[:3] + # else: + # color = np.array((0, 0, 0)) + # + # color = color.reshape((1, 1, 1, 3)) + # + # full_mask.append(mask * color) + # motion_flow = sum(full_mask) + # print(motion_flow.shape) + # imageio.imsave('./result/motion_flow.png', motion_flow[0], dpi=save_dpi) + # quit() + + + + predictions.append(np.transpose(out['prediction'].data.cpu().numpy(), [0, 2, 3, 1])[0]) + return predictions, features + + + +def test_auido(example_image, audio_feature, all_pose, opt): + with open(opt.config) as f: + para = yaml.load(f, Loader=yaml.FullLoader) + + # encoder = audio_feature() + if not opt.cpu: + audio_feature = audio_feature.cuda() + + audio_feature.eval() + # decoder.eval() + test_file = opt.in_file + pose = all_pose[:,:6] + if len(pose) == 1: + pose = np.repeat(pose,100,0) + + elif opt.smooth_pose: + one_euro_filter = OneEuroFilter(mincutoff=0.004, beta=0.7, dcutoff=1.0, freq=100) + + + for j in range(len(pose)): + pose[j]=one_euro_filter.process(pose[j]) + # pose[j]=pose[0] + + example_image = np.array(example_image, dtype='float32').transpose((2, 0, 1)) + + + + # # get wav2lip audio feature + # wav = audio.load_wav(test_file, 16000) + # mel = audio.melspectrogram(wav) + # fps = 30 + # mel_step_size = 16 + # mel_chunks = [] + # mel_idx_multiplier = 80. / fps + # i = 0 + # while 1: + # start_idx = int(i * mel_idx_multiplier) + # if start_idx + mel_step_size > len(mel[0]): + # mel_chunks.append(mel[:, len(mel[0]) - mel_step_size:]) + # break + # mel_chunks.append(mel[:, start_idx: start_idx + mel_step_size]) + # i += 1 + # mel_chunks = np.stack(mel_chunks, axis=0) + # input_mfcc = torch.FloatTensor(mel_chunks).cuda() + + + # get atvg audio feature + speech, sr = librosa.load(test_file, sr=16000) + # mfcc = python_speech_features.mfcc(speech ,16000,winstep=0.01) + speech = np.insert(speech, 0, np.zeros(1920)) + speech = np.append(speech, np.zeros(1920)) + mfcc = python_speech_features.mfcc(speech,16000,winstep=0.01) + ind = 3 + fake_lmark = [] + input_mfcc = [] + while ind <= int(mfcc.shape[0] / 4) - 4: + t_mfcc = mfcc[(ind - 3) * 4: (ind + 4) * 4, 1:] + t_mfcc = torch.FloatTensor(t_mfcc).cuda() + input_mfcc.append(t_mfcc) + ind += 1 + input_mfcc = torch.stack(input_mfcc, dim=0) # N,28,12 + + + print ('=======================================') + print ('Start to generate images') + + + with torch.no_grad(): + if (len(pose)len(input_mfcc)): + pose = pose[:len(input_mfcc),:] + + + if not opt.cpu: + example_image = Variable(torch.FloatTensor(example_image.astype(float)) ).cuda() + example_image = torch.unsqueeze(example_image,0) + pose = Variable(torch.FloatTensor(pose.astype(float)) ).cuda() + + pose = pose.unsqueeze(0) + + input_mfcc = input_mfcc.unsqueeze(0) + + deco_out = audio_feature(example_image,input_mfcc,pose,para['train_params']['jaco_net'],1.6) + + # ATNET + # deco_out = audio_feature(example_image, input_mfcc, pose, para['train_params']['jaco_net']) + + return deco_out + + +def save(path, frames, format): + + if format == '.png': + if not os.path.exists(path): + + os.makedirs(path) + for j, frame in enumerate(frames): + imageio.imsave(path+'/'+str(j)+'.png',frame) + # imageio.imsave(os.path.join(path, str(j) + '.png'), frames[j]) + else: + print ("Unknown format %s" % format) + exit() + +class VideoWriter(object): + def __init__(self, path, width, height, fps): + fourcc = cv2.VideoWriter_fourcc(*'XVID') + self.path = path + self.out = cv2.VideoWriter(self.path, fourcc, fps, (width, height)) + + def write_frame(self, frame): + self.out.write(frame) + + def end(self): + self.out.release() + +def concatenate(number, imgs, save_path): + width, height = imgs.shape[-3:-1] + imgs = imgs.reshape(number,-1,width,height,3) + if number == 2: + left = imgs[0] + right = imgs[1] + + im_all = [] + for i in range(len(left)): + im = np.concatenate((left[i],right[i]),axis = 1) + im_all.append(im) + if number == 3: + left = imgs[0] + middle = imgs[1] + right = imgs[2] + + im_all = [] + for i in range(len(left)): + im = np.concatenate((left[i],middle[i],right[i]),axis = 1) + im_all.append(im) + if number == 4: + left = imgs[0] + left2 = imgs[1] + right = imgs[2] + right2 = imgs[3] + + im_all = [] + for i in range(len(left)): + im = np.concatenate((left[i],left2[i],right[i],right2[i]),axis = 1) + im_all.append(im) + if number == 5: + left = imgs[0] + left2 = imgs[1] + middle = imgs[2] + right = imgs[3] + right2 = imgs[4] + + im_all = [] + for i in range(len(left)): + im = np.concatenate((left[i],left2[i],middle[i],right[i],right2[i]),axis = 1) + im_all.append(im) + + + imageio.mimsave(save_path, [img_as_ubyte(frame) for frame in im_all], fps=25) + +def add_audio(video_name=None, audio_dir = None): + + command = 'ffmpeg -i ' + video_name + ' -i ' + audio_dir + ' -vcodec copy -acodec copy -y ' + video_name.replace('.mp4','.mov') + print (command) + os.system(command) + +def smooth_pose(pose_file, pose_long): + start = np.load(pose_file) + video_pose = np.load(pose_long) + if video_pose.shape[-1] == 6: + start = start[:, :6] + + + delta = video_pose - video_pose[0,:] + # print(len(delta)) + + pose = np.repeat(start,len(delta),axis = 0) + all_pose = pose + delta + + return all_pose + +def test(opt, name): + pose_dim = np.load(opt.pose_file).shape[-1] + all_pose = np.load(opt.pose_file).reshape(-1,pose_dim) + if opt.pose_long: + all_pose = smooth_pose(opt.pose_file,opt.pose_given) + + + source_image = img_as_float32(io.imread(opt.source_image)) + source_image = resize(source_image, (256, 256))[..., :3] + + reader = imageio.get_reader(opt.driving_video) + fps = reader.get_meta_data()['fps'] + driving_video = [] + try: + for im in reader: + driving_video.append(im) + except RuntimeError: + pass + reader.close() + + + driving_video = [resize(frame, (256, 256))[..., :3] for frame in driving_video] + driving_video = get_aligned_image(driving_video, opt) + transformed_video = get_transformed_image(driving_video, opt) + transformed_video = np.array(transformed_video) + + generator, kp_detector,kp_detector_a, audio_feature, emo_detector = \ + load_checkpoints(opt=opt, checkpoint_path=opt.checkpoint, audio_checkpoint_path=opt.audio_checkpoint, emo_checkpoint_path = opt.emo_checkpoint, kp_checkpoint_path=opt.kp_checkpoint, cpu=opt.cpu) + + deco_out = test_auido(source_image, audio_feature, all_pose, opt) #1,N,32+3,64,64 + + + if len(driving_video) < len(deco_out[0]): + driving_video = np.resize(driving_video,(len(deco_out[0]),256,256,3)) + transformed_video = np.resize(transformed_video,(len(deco_out[0]),256,256,3)) + + else: + driving_video = driving_video[:len(deco_out[0])] + opt.add_emo = False + predictions, _ = make_animation_smooth(source_image, driving_video, transformed_video, deco_out, opt.kp_loss, generator, kp_detector, kp_detector_a, emo_detector, opt, relative=opt.relative, adapt_movement_scale=opt.adapt_scale, cpu=opt.cpu) + + imageio.mimsave(os.path.join(opt.result_path,'neutral.mp4'), [img_as_ubyte(frame) for frame in predictions], fps=fps) + predictions = np.array(predictions) + + opt.add_emo = True + predictions1,_ = make_animation_smooth(source_image, driving_video, transformed_video, deco_out, opt.kp_loss, generator, kp_detector, kp_detector_a, emo_detector, opt, relative=opt.relative, adapt_movement_scale=opt.adapt_scale, cpu=opt.cpu) + + imageio.mimsave(os.path.join(opt.result_path,'emotion.mp4'), [img_as_ubyte(frame) for frame in predictions1], fps=fps) + add_audio(os.path.join(opt.result_path,'emotion.mp4'),opt.in_file) + predictions1 = np.array(predictions1) + all_imgs = np.concatenate((driving_video,predictions,predictions1),axis = 0) + save_path = os.path.join(opt.result_path, 'all.mp4') + concatenate(3, all_imgs, save_path) + add_audio(save_path,opt.in_file) + + + +if __name__ == "__main__": + + + + parser = ArgumentParser() + parser.add_argument("--config", default ='config/MEAD_emo_video_aug_delta_4_crop_random_crop.yaml', help="path to config")#required=True default ='config/vox-256.yaml' + + parser.add_argument("--audio_checkpoint", default='ckpt/1-6000.pth.tar', help="path to checkpoint to restore") + parser.add_argument("--checkpoint", default='ckpt/124_52000.pth.tar', help="path to checkpoint to restore") + parser.add_argument("--kp_checkpoint", default='ckpt/124_52000.pth.tar', help="path to checkpoint to restore") + # parser.add_argument("--emo_checkpoint", default='ablation/ablation/ten/10-6000.pth.tar', help="path to checkpoint to restore") + parser.add_argument("--emo_checkpoint", default='ckpt/5-3000.pth.tar', help="path to checkpoint to restore") + + parser.add_argument("--source_image", default='test/image/21.png', help="path to source image") + + parser.add_argument("--driving_video", default='test/video/disgusted.mp4', help="path to driving video")#data/M030/video/M030_angry_ + parser.add_argument('--in_file', type=str, default='test/audio/sample1.mov') + parser.add_argument('--pose_file', type=str, default='test/pose/60.npy') + parser.add_argument('--pose_given', type=str, default='test/pose_long/50IAfJCypFI_Alex_Kingston_50IAfJCypFI_0001.npy') + + parser.add_argument("--result_path", default='result/', help="path to output")#'/media/thea/新加卷/fomm/Exp/'+emotion+'.mp4' + + parser.add_argument("--relative", dest="relative", action="store_true", help="use relative or absolute keypoint coordinates") + parser.add_argument("--adapt_scale", dest="adapt_scale", action="store_true", help="adapt movement scale based on convex hull of keypoints") + + parser.add_argument("--cpu", dest="cpu", action="store_true", help="cpu mode.") + parser.add_argument("--kp_loss", default=0, help="keypoint loss.") + + parser.add_argument("--smooth_pose", default=True, help="cpu mode.") + parser.add_argument("--pose_long", default=False, help="use given long poses.") + parser.add_argument("--weight", default=0, help="cpu mode.") + parser.add_argument("--add_emo", default=False, help="add emotion.") + parser.add_argument("--check_add", default=False, help="check emotion displacement.") + parser.add_argument("--type", default='linear_3', help="add emotion type.") + parser.add_argument("--emotion", default='disgusted', help="emotion category, 'angry', 'contempt','disgusted','fear','happy','neutral','sad','surprised'.") + parser.set_defaults(relative=False) + parser.set_defaults(adapt_scale=False) + + opt = parser.parse_args() + # opt.cpu = True + + test(opt,'test') + + diff --git a/FONT/filter1.py b/FONT/filter1.py new file mode 100644 index 0000000000000000000000000000000000000000..ca2342475adb2465a0d86e7d6f033a48facaed40 --- /dev/null +++ b/FONT/filter1.py @@ -0,0 +1,48 @@ +import cv2 +#import pickle +import time +import numpy as np +import copy + +from matplotlib import pyplot as plt +from tqdm import tqdm + + + + +class LowPassFilter: + def __init__(self): + self.prev_raw_value = None + self.prev_filtered_value = None + + def process(self, value, alpha): + if self.prev_raw_value is None: + s = value + else: + s = alpha * value + (1.0 - alpha) * self.prev_filtered_value + self.prev_raw_value = value + self.prev_filtered_value = s + return s + + +class OneEuroFilter: + def __init__(self, mincutoff=1.0, beta=0.0, dcutoff=1.0, freq=30): + self.freq = freq + self.mincutoff = mincutoff + self.beta = beta + self.dcutoff = dcutoff + self.x_filter = LowPassFilter() + self.dx_filter = LowPassFilter() + + def compute_alpha(self, cutoff): + te = 1.0 / self.freq + tau = 1.0 / (2 * np.pi * cutoff) + return 1.0 / (1.0 + tau / te) + + def process(self, x): + prev_x = self.x_filter.prev_raw_value + dx = 0.0 if prev_x is None else (x - prev_x) * self.freq + edx = self.dx_filter.process(dx, self.compute_alpha(self.dcutoff)) + cutoff = self.mincutoff + self.beta * np.abs(edx) + return self.x_filter.process(x, self.compute_alpha(cutoff)) + diff --git a/FONT/frames_dataset.py b/FONT/frames_dataset.py new file mode 100644 index 0000000000000000000000000000000000000000..85eda8ca1a26e36594d021cf638e001a720e7f11 --- /dev/null +++ b/FONT/frames_dataset.py @@ -0,0 +1,539 @@ +import os +from skimage import io, img_as_float32, transform +from skimage.color import gray2rgb +from sklearn.model_selection import train_test_split +from imageio import mimread + +import numpy as np +from torch.utils.data import Dataset +import pandas as pd +from augmentation import AllAugmentationTransform +import glob +import pickle +import random +from filter1 import OneEuroFilter +def read_video(name, frame_shape): + """ + Read video which can be: + - an image of concatenated frames + - '.mp4' and'.gif' + - folder with videos + """ + + if os.path.isdir(name): + frames = sorted(os.listdir(name)) + num_frames = len(frames) + video_array = np.array( + [img_as_float32(io.imread(os.path.join(name, frames[idx]))) for idx in range(num_frames)]) + elif name.lower().endswith('.png') or name.lower().endswith('.jpg'): + image = io.imread(name) + + if len(image.shape) == 2 or image.shape[2] == 1: + image = gray2rgb(image) + + if image.shape[2] == 4: + image = image[..., :3] + + image = img_as_float32(image) + + video_array = np.moveaxis(image, 1, 0) + + video_array = video_array.reshape((-1,) + frame_shape) + video_array = np.moveaxis(video_array, 1, 2) + elif name.lower().endswith('.gif') or name.lower().endswith('.mp4') or name.lower().endswith('.mov'): + video = np.array(mimread(name)) + if len(video.shape) == 3: + video = np.array([gray2rgb(frame) for frame in video]) + if video.shape[-1] == 4: + video = video[..., :3] + video_array = img_as_float32(video) + else: + raise Exception("Unknown file extensions %s" % name) + + return video_array + +def get_list(ipath,base_name): +#ipath = '/mnt/lustre/share/jixinya/LRW/pose/train_fo/' + ipath = os.path.join(ipath,base_name) + name_list = os.listdir(ipath) + image_path = os.path.join('/mnt/lustre/share/jixinya/LRW/Image/',base_name) + all = [] + for k in range(len(name_list)): + name = name_list[k] + path_ = os.path.join(ipath,name) + Dir = os.listdir(path_) + for i in range(len(Dir)): + word = Dir[i] + path = os.path.join(path_, word) + if os.path.exists(os.path.join(image_path,name,word.split('.')[0])): + all.append(name+'/'+word.split('.')[0]) + #print(k,name,i,word) + print('get list '+os.path.basename(ipath)) + return all + + +class AudioDataset(Dataset): + """ + Dataset of videos, each video can be represented as: + - an image of concatenated frames + - '.mp4' or '.gif' + - folder with all frames + """ + + def __init__(self, name, root_dir, frame_shape=(256, 256, 3), id_sampling=False, is_train=True, + random_seed=0, augmentation_params=None): + self.root_dir = root_dir + self.audio_dir = os.path.join(root_dir,'MFCC') + self.image_dir = os.path.join(root_dir,'Image') + self.pose_dir = os.path.join(root_dir,'pose') + # assert len(os.listdir(self.audio_dir)) == len(os.listdir(self.image_dir)), 'audio and image length not equal' + + # self.videos=np.load('../LRW/list/train_fo.npy') + # self.videos = os.listdir(self.landmark_dir) + self.frame_shape = tuple(frame_shape) + + self.id_sampling = id_sampling + + if os.path.exists(os.path.join(self.pose_dir, 'train_fo')): + assert os.path.exists(os.path.join(self.pose_dir, 'test_fo')) + print("Use predefined train-test split.") + if id_sampling: + train_videos = {os.path.basename(video).split('#')[0] for video in + os.listdir(os.path.join(self.image_dir, 'train'))} + train_videos = list(train_videos) + else: + train_videos = np.load('../LRW/list/train_fo.npy')# get_list(self.pose_dir, 'train_fo') + # df=open('../LRW/list/test_fo.txt','rb') + test_videos=np.load('../LRW/list/test_fo.npy') + # df.close() + # test_videos = np.load('../LRW/list/train_fo.npy') + #get_list(self.pose_dir, 'test_fo') + # self.root_dir = os.path.join(self.root_dir, 'train' if is_train else 'test') + + self.image_dir = os.path.join(self.image_dir, 'train_fo' if is_train else 'test_fo') + self.audio_dir = os.path.join(self.audio_dir, 'train' if is_train else 'test') + self.pose_dir = os.path.join(self.pose_dir, 'train_fo' if is_train else 'test_fo') + else: + print("Use random train-test split.") + train_videos, test_videos = train_test_split(self.videos, random_state=random_seed, test_size=0.2) + + if is_train: + self.videos = train_videos + else: + self.videos = test_videos + + self.is_train = is_train + + if self.is_train: + self.transform = AllAugmentationTransform(**augmentation_params) + else: + self.transform = None + + def __len__(self): + return len(self.videos) + + def __getitem__(self, idx): + if self.is_train and self.id_sampling: + name = self.videos[idx].split('.')[0] + path = np.random.choice(glob.glob(os.path.join(self.root_dir, name + '*.mp4'))) + else: + name = self.videos[idx].split('.')[0] + + audio_path = os.path.join(self.audio_dir, name) + pose_path = os.path.join(self.pose_dir,name) + path = os.path.join(self.image_dir, name) + + video_name = os.path.basename(path) + + if os.path.isdir(path): + # if self.is_train and os.path.isdir(path): + + # mfcc loading + r = random.choice([x for x in range(3, 8)]) + + example_image = img_as_float32(io.imread(os.path.join(path, str(r)+'.png'))) + + mfccs = [] + for ind in range(1, 17): + # t_mfcc = mfcc[(r + ind - 3) * 4: (r + ind + 4) * 4, 1:] + t_mfcc = np.load(os.path.join(audio_path,str(r + ind)+'.npy'),allow_pickle=True)[:, 1:] + mfccs.append(t_mfcc) + mfccs = np.array(mfccs) + + poses = [] + video_array = [] + for ind in range(1, 17): + + t_pose = np.load(os.path.join(self.pose_dir,name+'.npy'))[r+ind,:-1] + + poses.append(t_pose) + image = img_as_float32(io.imread(os.path.join(path, str(r + ind)+'.png'))) + video_array.append(image) + poses = np.array(poses) + video_array = np.array(video_array) + + else: + print('Wrong, data path not an existing file.') + + if self.transform is not None: + video_array = self.transform(video_array) + + out = {} + + driving = np.array(video_array, dtype='float32') + spatial_size = np.array(driving.shape[1:3][::-1])[np.newaxis] + driving_pose = np.array(poses, dtype='float32') + example_image = np.array(example_image, dtype='float32') + + out['example_image'] = example_image.transpose((2, 0, 1)) + out['driving_pose'] = driving_pose + out['driving'] = driving.transpose((0, 3, 1, 2)) + out['driving_audio'] = np.array(mfccs, dtype='float32') + # out['name'] = video_name + + return out + +class VoxDataset(Dataset): + """ + Dataset of videos, each video can be represented as: + - an image of concatenated frames + - '.mp4' or '.gif' + - folder with all frames + """ + + def __init__(self, root_dir, frame_shape=(256, 256, 3), id_sampling=False, is_train=True, + random_seed=0, pairs_list=None, augmentation_params=None): + self.root_dir = root_dir + self.audio_dir = os.path.join(root_dir,'MFCC') + self.image_dir = os.path.join(root_dir,'align_img') + + self.pose_dir = os.path.join(root_dir,'align_pose') + # assert len(os.listdir(self.audio_dir)) == len(os.listdir(self.image_dir)), 'audio and image length not equal' + + + # df=open('../LRW/list/test_fo.txt','rb') + # self.videos=pickle.load(df) + # df.close() + self.videos=np.load('/mnt/lustre/share_data/jixinya/VoxCeleb1_Cut/right.npy') + # self.videos = os.listdir(self.landmark_dir) + self.frame_shape = tuple(frame_shape) + self.pairs_list = pairs_list + self.id_sampling = id_sampling + + if os.path.exists(os.path.join(self.pose_dir, 'train_fo')): + assert os.path.exists(os.path.join(self.pose_dir, 'test_fo')) + print("Use predefined train-test split.") + if id_sampling: + train_videos = {os.path.basename(video).split('#')[0] for video in + os.listdir(os.path.join(self.image_dir, 'train'))} + train_videos = list(train_videos) + else: + train_videos = np.load('/mnt/lustre/share_data/jixinya/VoxCeleb1_Cut/right.npy')# get_list(self.pose_dir, 'train_fo') + + self.image_dir = os.path.join(self.image_dir, 'train_fo' if is_train else 'test_fo') + self.audio_dir = os.path.join(self.audio_dir, 'train' if is_train else 'test') + self.pose_dir = os.path.join(self.pose_dir, 'train_fo' if is_train else 'test_fo') + else: + print("Use random train-test split.") + train_videos, test_videos = train_test_split(self.videos, random_state=random_seed, test_size=0.2) + + if is_train: + self.videos = train_videos + else: + self.videos = test_videos + + self.is_train = is_train + + if self.is_train: + self.transform = AllAugmentationTransform(**augmentation_params) + else: + self.transform = None + + def __len__(self): + return len(self.videos) + + def __getitem__(self, idx): + if self.is_train and self.id_sampling: + name = self.videos[idx].split('.')[0] + path = np.random.choice(glob.glob(os.path.join(self.root_dir, name + '*.mp4'))) + else: + name = self.videos[idx].split('.')[0] + + audio_path = os.path.join(self.audio_dir, name+'.npy') + pose_path = os.path.join(self.pose_dir,name+'.npy') + path = os.path.join(self.image_dir, name) + + video_name = os.path.basename(path) + + if os.path.isdir(path): + # if self.is_train and os.path.isdir(path): + frames = os.listdir(path) + num_frames = len(frames) + frame_idx = np.sort(np.random.choice(num_frames, replace=True, size=2)) + video_array = [img_as_float32(io.imread(os.path.join(path, frames[idx]))) for idx in frame_idx] + mfcc = np.load(audio_path) + pose = np.load(pose_path) + + # print(audio_path,pose_path,len(mfcc)) + + try: + len(mfcc) > 16 + except: + print('wrongmfcc len:',audio_path) + if 16 < len(mfcc) < 24 : + r = 0 + else: + + r = random.choice([x for x in range(3, len(mfcc)-20)]) + + mfccs = [] + poses = [] + video_array = [] + for ind in range(1, 17): + t_mfcc = mfcc[r+ind][:, 1:] + mfccs.append(t_mfcc) + t_pose = pose[r+ind,:-1] + poses.append(t_pose) + image = img_as_float32(io.imread(os.path.join(path, str(r + ind)+'.png'))) + video_array.append(image) + mfccs = np.array(mfccs) + poses = np.array(poses) + video_array = np.array(video_array) + + example_image = img_as_float32(io.imread(os.path.join(path, str(r)+'.png'))) + + + else: + print('Wrong, data path not an existing file.') + + if self.transform is not None: + video_array = self.transform(video_array) + + out = {} + + driving = np.array(video_array, dtype='float32') + + spatial_size = np.array(driving.shape[1:3][::-1])[np.newaxis] + driving_pose = np.array(poses, dtype='float32') + example_image = np.array(example_image, dtype='float32') + out['example_image'] = example_image.transpose((2, 0, 1)) + out['driving_pose'] = driving_pose + out['driving'] = driving.transpose((0, 3, 1, 2)) + + out['driving_audio'] = np.array(mfccs, dtype='float32') + # out['name'] = video_name + + return out + +class MeadDataset(Dataset): + """ + Dataset of videos, each video can be represented as: + - an image of concatenated frames + - '.mp4' or '.gif' + - folder with all frames + """ + + def __init__(self, root_dir, frame_shape=(256, 256, 3), id_sampling=False, is_train=True, + random_seed=0, augmentation_params=None): + self.root_dir = root_dir + + self.audio_dir = os.path.join(root_dir,'MEAD_MFCC') + self.image_dir = os.path.join(root_dir,'MEAD_fomm_crop') + + self.pose_dir = os.path.join(root_dir,'MEAD_fomm_pose_crop') + + self.videos = np.load('/mnt/lustre/share_data/jixinya/MEAD/MEAD_fomm_audio_less_crop.npy') + self.dict = np.load('/mnt/lustre/share_data/jixinya/MEAD/MEAD_fomm_neu_dic_crop.npy',allow_pickle=True).item() + # self.videos = os.listdir(root_dir) + self.frame_shape = tuple(frame_shape) + + self.id_sampling = id_sampling + if os.path.exists(os.path.join(root_dir, 'train')): + assert os.path.exists(os.path.join(root_dir, 'test')) + print("Use predefined train-test split.") + if id_sampling: + train_videos = {os.path.basename(video).split('#')[0] for video in + os.listdir(os.path.join(root_dir, 'train'))} + train_videos = list(train_videos) + else: + train_videos = os.listdir(os.path.join(root_dir, 'train')) + test_videos = os.listdir(os.path.join(root_dir, 'test')) + self.root_dir = os.path.join(self.root_dir, 'train' if is_train else 'test') + else: + print("Use random train-test split.") + train_videos, test_videos = train_test_split(self.videos, random_state=random_seed, test_size=0.2) + + if is_train: + self.videos = train_videos + else: + self.videos = test_videos + + self.is_train = is_train + + if self.is_train: + self.transform = AllAugmentationTransform(**augmentation_params) + else: + self.transform = None + + def __len__(self): + return len(self.videos) + + def __getitem__(self, idx): + if self.is_train and self.id_sampling: + name = self.videos[idx] + path = np.random.choice(glob.glob(os.path.join(self.root_dir, name + '*.mp4'))) + else: + name = self.videos[idx] + path = os.path.join(self.image_dir, name) + + video_name = os.path.basename(path) + id_name = path.split('/')[-2] + neu_list = self.dict[id_name] + neu_path = os.path.join(self.image_dir, np.random.choice(neu_list)) + + audio_path = os.path.join(self.audio_dir, name+'.npy') + pose_path = os.path.join(self.pose_dir,name+'.npy') + + + if self.is_train and os.path.isdir(path): + + mfcc = np.load(audio_path) + pose_raw = np.load(pose_path) + one_euro_filter = OneEuroFilter(mincutoff=0.01, beta=0.7, dcutoff=1.0, freq=100) + pose = np.zeros((len(pose_raw),7)) + + for j in range(len(pose_raw)): + pose[j]=one_euro_filter.process(pose_raw[j]) + # print(audio_path,pose_path,len(mfcc)) + + neu_frames = os.listdir(neu_path) + num_neu_frames = len(neu_frames) + frame_idx = np.random.choice(num_neu_frames) + example_image = img_as_float32(io.imread(os.path.join(neu_path, neu_frames[frame_idx]))) + try: + len(mfcc) > 16 + except: + print('wrongmfcc len:',audio_path) + if 16 < len(mfcc) < 24 : + r = 0 + else: + + r = random.choice([x for x in range(3, len(mfcc)-20)]) + + mfccs = [] + poses = [] + video_array = [] + for ind in range(1, 17): + t_mfcc = mfcc[r+ind][:, 1:] + mfccs.append(t_mfcc) + t_pose = pose[r+ind,:-1] + poses.append(t_pose) + image = img_as_float32(io.imread(os.path.join(path, str(r + ind)+'.png'))) + video_array.append(image) + mfccs = np.array(mfccs) + poses = np.array(poses) + video_array = np.array(video_array) + + else: + print('Wrong, data path not an existing file.') + + # if self.transform is not None: + # video_array = self.transform(video_array) + + out = {} + if self.is_train: + + driving = np.array(video_array, dtype='float32') + driving_pose = np.array(poses, dtype='float32') + example_image = np.array(example_image, dtype='float32') + + + out['example_image'] = example_image.transpose((2, 0, 1)) + out['driving_pose'] = driving_pose + out['driving'] = driving.transpose((0, 3, 1, 2)) + out['driving_audio'] = np.array(mfccs, dtype='float32') + + # out['name'] = id_name+'/'+video_name + + return out + + +class DatasetRepeater(Dataset): + """ + Pass several times over the same dataset for better i/o performance + """ + + def __init__(self, dataset, num_repeats=100): + self.dataset = dataset + # self.dataset2 = dataset2 + self.num_repeats = num_repeats + + def __len__(self): + return self.num_repeats * self.dataset.__len__() + + def __getitem__(self, idx): + # if idx % 5 == 0: + # return self.dataset2[idx % self.dataset2.__len__()]#% self.dataset.__len__() + # else: + # return self.dataset[idx % self.dataset.__len__()] + return self.dataset[idx % self.dataset.__len__()] + +class TestsetRepeater(Dataset): + """ + Pass several times over the same dataset for better i/o performance + """ + + def __init__(self, dataset, num_repeats=100): + self.dataset = dataset + + self.num_repeats = num_repeats + + def __len__(self): + return self.num_repeats * self.dataset.__len__() + + def __getitem__(self, idx): + + return self.dataset[idx % self.dataset.__len__()]#% self.dataset.__len__() + + +class PairedDataset(Dataset): + """ + Dataset of pairs for animation. + """ + + def __init__(self, initial_dataset, number_of_pairs, seed=0): + self.initial_dataset = initial_dataset + pairs_list = self.initial_dataset.pairs_list + + np.random.seed(seed) + + if pairs_list is None: + max_idx = min(number_of_pairs, len(initial_dataset)) + nx, ny = max_idx, max_idx + xy = np.mgrid[:nx, :ny].reshape(2, -1).T + number_of_pairs = min(xy.shape[0], number_of_pairs) + self.pairs = xy.take(np.random.choice(xy.shape[0], number_of_pairs, replace=False), axis=0) + else: + videos = self.initial_dataset.videos + name_to_index = {name: index for index, name in enumerate(videos)} + pairs = pd.read_csv(pairs_list) + pairs = pairs[np.logical_and(pairs['source'].isin(videos), pairs['driving'].isin(videos))] + + number_of_pairs = min(pairs.shape[0], number_of_pairs) + self.pairs = [] + self.start_frames = [] + for ind in range(number_of_pairs): + self.pairs.append( + (name_to_index[pairs['driving'].iloc[ind]], name_to_index[pairs['source'].iloc[ind]])) + + def __len__(self): + return len(self.pairs) + + def __getitem__(self, idx): + pair = self.pairs[idx] + first = self.initial_dataset[pair[0]] + second = self.initial_dataset[pair[1]] + first = {'driving_' + key: value for key, value in first.items()} + second = {'source_' + key: value for key, value in second.items()} + + return {**first, **second} diff --git a/FONT/frames_dataset_liujin.py b/FONT/frames_dataset_liujin.py new file mode 100644 index 0000000000000000000000000000000000000000..c017adbd1a7b80f86d25e3ecda5ee1445c9abaed --- /dev/null +++ b/FONT/frames_dataset_liujin.py @@ -0,0 +1,727 @@ +import os +from skimage import io, img_as_float32, transform +from skimage.color import gray2rgb +from sklearn.model_selection import train_test_split +from imageio import mimread + +import numpy as np +from torch.utils.data import Dataset +import pandas as pd +from augmentation import AllAugmentationTransform +import glob +import pickle +import random +from filter1 import OneEuroFilter +import pickle +from tqdm import tqdm +from scipy.io import loadmat +import time +import random + + + +def read_video(name, frame_shape): + """ + Read video which can be: + - an image of concatenated frames + - '.mp4' and'.gif' + - folder with videos + """ + + if os.path.isdir(name): + frames = sorted(os.listdir(name)) + num_frames = len(frames) + video_array = np.array( + [img_as_float32(io.imread(os.path.join(name, frames[idx]))) for idx in range(num_frames)]) + elif name.lower().endswith('.png') or name.lower().endswith('.jpg'): + image = io.imread(name) + + if len(image.shape) == 2 or image.shape[2] == 1: + image = gray2rgb(image) + + if image.shape[2] == 4: + image = image[..., :3] + + image = img_as_float32(image) + + video_array = np.moveaxis(image, 1, 0) + + video_array = video_array.reshape((-1,) + frame_shape) + video_array = np.moveaxis(video_array, 1, 2) + elif name.lower().endswith('.gif') or name.lower().endswith('.mp4') or name.lower().endswith('.mov'): + video = np.array(mimread(name)) + if len(video.shape) == 3: + video = np.array([gray2rgb(frame) for frame in video]) + if video.shape[-1] == 4: + video = video[..., :3] + video_array = img_as_float32(video) + else: + raise Exception("Unknown file extensions %s" % name) + + return video_array + +def get_list(ipath,base_name): +#ipath = '/mnt/lustre/share/jixinya/LRW/pose/train_fo/' + ipath = os.path.join(ipath,base_name) + name_list = os.listdir(ipath) + image_path = os.path.join('/mnt/lustre/share/jixinya/LRW/Image/',base_name) + all = [] + for k in range(len(name_list)): + name = name_list[k] + path_ = os.path.join(ipath,name) + Dir = os.listdir(path_) + for i in range(len(Dir)): + word = Dir[i] + path = os.path.join(path_, word) + if os.path.exists(os.path.join(image_path,name,word.split('.')[0])): + all.append(name+'/'+word.split('.')[0]) + #print(k,name,i,word) + print('get list '+os.path.basename(ipath)) + return all + + +class AudioDataset(Dataset): + """ + Dataset of videos, each video can be represented as: + - an image of concatenated frames + - '.mp4' or '.gif' + - folder with all frames + """ + + def __init__(self, name, root_dir, frame_shape=(256, 256, 3), id_sampling=False, is_train=True, + random_seed=0, augmentation_params=None): + + self.root_dir = "/data/liujin/dataset/LRW/" + # self.audio_dir = os.path.join(root_dir,'lipread_atvg_audio_feature') + + + self.frame_shape = tuple(frame_shape) + + self.is_train = is_train + self.split = 'train' if is_train else 'test' + + self.used_video_frames_paths = self.get_used_frames_paths() + + self.videos = self.get_video_items(self.used_video_frames_paths) + + + print('length of LRW {} dataset is {}'.format(self.split, str(len(self.videos))) ) + + + if self.is_train: + self.transform = AllAugmentationTransform(**augmentation_params) + else: + self.transform = None + + def get_video_items(self, all_video_frames_paths): + print('total video items length is {}'.format(len(all_video_frames_paths))) + video_items = [] + cur_lengh = 0 + init_start_time = time.time() + + for video_frames_path in tqdm(all_video_frames_paths): + video_items.append(self.construct_Video_Info(video_frames_path)) + if len(video_items) - cur_lengh > 5000: + init_end_time = time.time() + + print(len(video_items)) + print('Cost {} seconds'.format(str(init_end_time - init_start_time))) + cur_lengh = len(video_items) + init_start_time = init_end_time + return video_items + + def get_used_frames_paths(self): + used_video_frames_paths = [] + # used_words = ['ABOUT', 'ABSOLUTELY', 'ABUSE', 'ACCESS', 'ACCORDING', + # 'COUNCIL', 'COUNTRIES', 'COUNTRY', 'COUPLE', 'COURSE', + # 'HISTORY', 'HOMES', 'HOSPITAL', 'HOURS', 'HOUSE', + # 'OPPOSITION', 'ORDER', 'OTHER', 'OTHERS', 'OUTSIDE', + # 'SOMETHING', 'SOUTH', 'SOUTHERN', 'SPEAKING'] + + lrw_word_dir = "/data/liujin/dataset/LRW/lipread_frames/" + all_words = sorted(os.listdir(lrw_word_dir)) + # used_words = all_words[0:500:5] + + used_words = sorted(os.listdir("/data/liujin/dataset/LRW/lipread_3DDFA_pose/")) + + for word in (used_words): + cur_videos_path = os.path.join('/data/liujin/dataset/LRW/lipread_frames/', word, self.split) + length = len(os.listdir(cur_videos_path)) + for video_name in sorted(os.listdir(cur_videos_path))[:int(0.05 * length)]: + used_video_frames_paths.append(os.path.join(cur_videos_path, video_name)) + + + return used_video_frames_paths[:] + + def construct_Video_Info(self, cur_frames_path): + info = {} + + cor_mfcc_path = cur_frames_path.replace('lipread_frames', 'lipread_atvg_audio_feature') + '.pkl' + with open(cor_mfcc_path, 'rb') as fp: + info['mfccs'] = pickle.load(fp) #N,28,12 ATVG / N,80,16,wav2lip + + # cor_pose_path = cur_frames_path.replace('lipread_frames', 'lipread_3dmm') + cor_pose_path = cur_frames_path.replace('lipread_frames', 'lipread_3DDFA_pose') + cur_video_pose = [] + for pose_name in sorted(os.listdir(cor_pose_path)): + frame_pose_path = os.path.join(cor_pose_path, pose_name) + cur_pose_data = None + if frame_pose_path.endswith('npy'): + cur_pose_data = np.load(frame_pose_path) + elif frame_pose_path.endswith('mat'): + file_mat = loadmat(frame_pose_path) + coeff_3dmm = file_mat['coeff'] + angles = coeff_3dmm[:, 224:227] # euler angles for pose + translation = coeff_3dmm[:, 254:257] # translation + cur_pose_data = np.concatenate([angles, translation], 1) + + + cur_video_pose.append(cur_pose_data) + cur_video_pose_array = np.concatenate(cur_video_pose, axis=0) #N,7 + info['poses'] = cur_video_pose_array + + + frames = [] + for frame_name in sorted(os.listdir(cur_frames_path)): + cur_frame_path = os.path.join(cur_frames_path, frame_name) + # frames.append(img_as_float32(io.imread(cur_frame_path))) + frames.append(cur_frame_path) + info['frames'] = frames + + return info + + + def __len__(self): + return len(self.videos) + + def __getitem__(self, idx): + + video_info = self.videos[idx] + # mfcc loading + r = random.choice([x for x in range(3, 8)]) + + example_image = video_info['frames'][r] + + + mfccs = video_info['mfccs'][r:r+16] + + poses = [] + video_array = [] + for ind in range(1, 17): + + t_pose = video_info['poses'][r+ind,:6] + poses.append(t_pose) + + image = video_info['frames'][r + ind] + image_data = img_as_float32(io.imread(image)) + video_array.append(image_data) + poses = np.array(poses) + video_array = np.array(video_array) + + + + # if self.transform is not None: + # video_array = self.transform(video_array) + + out = {} + + driving = np.array(video_array, dtype='float32') + spatial_size = np.array(driving.shape[1:3][::-1])[np.newaxis] + driving_pose = np.array(poses, dtype='float32') + + example_image_data = img_as_float32(io.imread(example_image)) + example_image = np.array(example_image_data, dtype='float32') + + out['example_image'] = example_image.transpose((2, 0, 1)) + out['driving_pose'] = driving_pose + out['driving'] = driving.transpose((0, 3, 1, 2)) + # out['driving_audio'] = np.array(mfccs, dtype='float32') + # out['driving_audio'] = np.array(mfccs, dtype='float32') + out['driving_audio'] = mfccs + # out['name'] = video_name + out['transformed_driving'] = out['driving'] + + return out + + +class HDTFDataset(Dataset): + """ + Dataset of videos, each video can be represented as: + - an image of concatenated frames + - '.mp4' or '.gif' + - folder with all frames + """ + + def __init__(self, name, root_dir, frame_shape=(256, 256, 3), id_sampling=False, is_train=True, + random_seed=0, augmentation_params=None): + + self.root_dir = "/data/liujin/dataset/HDTF/" + # self.audio_dir = os.path.join(root_dir,'lipread_atvg_audio_feature') + + self.frame_shape = tuple(frame_shape) + + self.is_train = is_train + self.split = 'train' if is_train else 'test' + + self.used_video_frames_paths = self.get_used_frames_paths() + + self.videos = self.get_video_items(self.used_video_frames_paths) + + print('length of HDTF {} dataset is {}'.format(self.split, str(len(self.videos)))) + + if self.is_train: + self.transform = AllAugmentationTransform(**augmentation_params) + else: + self.transform = None + + def get_video_items(self, all_video_frames_paths): + print('total video items length is {}'.format(len(all_video_frames_paths))) + video_items = [] + cur_lengh = 0 + init_start_time = time.time() + + for video_frames_path in tqdm(all_video_frames_paths): + video_items.append(self.construct_Video_Info(video_frames_path)) + if len(video_items) - cur_lengh > 5000: + init_end_time = time.time() + + print(len(video_items)) + print('Cost {} seconds'.format(str(init_end_time - init_start_time))) + cur_lengh = len(video_items) + init_start_time = init_end_time + return video_items + + def get_used_frames_paths(self): + + + video_frames_dir = os.path.join(self.root_dir, 'frames_256') + video_names = sorted(os.listdir(video_frames_dir)) + length = len(video_names) + used_video_names = video_names[:int(0.9 * length)] if self.is_train else video_names[int(0.9 * length):] + + used_frames_paths = [os.path.join(video_frames_dir, video_name) for video_name in used_video_names] + + return used_frames_paths + + def construct_Video_Info(self, cur_frames_path): + info = {} + + cor_mfcc_path = cur_frames_path.replace('frames_256', 'atvg_audio_feature') + '.pkl' + with open(cor_mfcc_path, 'rb') as fp: + info['mfccs'] = pickle.load(fp) # N,28,12 ATVG / N,80,16,wav2lip + + # cor_pose_path = cur_frames_path.replace('frames_256', '3dmm_256') + cor_pose_path = cur_frames_path.replace('frames_256', 'pose_3DDFA_256') + cur_video_pose = [] + for pose_name in sorted(os.listdir(cor_pose_path)): + frame_pose_path = os.path.join(cor_pose_path, pose_name) + cur_pose_data = None + if frame_pose_path.endswith('npy'): + cur_pose_data = np.load(frame_pose_path) + elif frame_pose_path.endswith('mat'): + file_mat = loadmat(frame_pose_path) + coeff_3dmm = file_mat['coeff'] + angles = coeff_3dmm[:, 224:227] # euler angles for pose + translation = coeff_3dmm[:, 254:257] # translation + cur_pose_data = np.concatenate([angles, translation], 1) + + cur_video_pose.append(cur_pose_data) + cur_video_pose_array = np.concatenate(cur_video_pose, axis=0) # N,7 + info['poses'] = cur_video_pose_array + + frames = [] + for frame_name in sorted(os.listdir(cur_frames_path)): + cur_frame_path = os.path.join(cur_frames_path, frame_name) + # frames.append(img_as_float32(io.imread(cur_frame_path))) + frames.append(cur_frame_path) + info['frames'] = frames + + return info + + def __len__(self): + return len(self.videos) + + def __getitem__(self, idx): + + video_info = self.videos[idx] + # mfcc loading + r = random.choice([x for x in range(3, 8)]) + + example_image = video_info['frames'][r] + + mfccs = video_info['mfccs'][r:r + 16] + + poses = [] + video_array = [] + for ind in range(1, 17): + t_pose = video_info['poses'][r + ind, :6] + poses.append(t_pose) + + image = video_info['frames'][r + ind] + image_data = img_as_float32(io.imread(image)) + video_array.append(image_data) + poses = np.array(poses) + video_array = np.array(video_array) + + # if self.transform is not None: + # video_array = self.transform(video_array) + + out = {} + + driving = np.array(video_array, dtype='float32') + spatial_size = np.array(driving.shape[1:3][::-1])[np.newaxis] + driving_pose = np.array(poses, dtype='float32') + + example_image_data = img_as_float32(io.imread(example_image)) + example_image = np.array(example_image_data, dtype='float32') + + out['example_image'] = example_image.transpose((2, 0, 1)) + out['driving_pose'] = driving_pose + out['driving'] = driving.transpose((0, 3, 1, 2)) + # out['driving_audio'] = np.array(mfccs, dtype='float32') + # out['driving_audio'] = np.array(mfccs, dtype='float32') + out['driving_audio'] = mfccs + # out['name'] = video_name + out['transformed_driving'] = out['driving'] + + return out + +class VoxDataset(Dataset): + """ + Dataset of videos, each video can be represented as: + - an image of concatenated frames + - '.mp4' or '.gif' + - folder with all frames + """ + + def __init__(self, root_dir, frame_shape=(256, 256, 3), id_sampling=False, is_train=True, + random_seed=0, pairs_list=None, augmentation_params=None): + self.root_dir = root_dir + self.audio_dir = os.path.join(root_dir,'MFCC') + self.image_dir = os.path.join(root_dir,'align_img') + + self.pose_dir = os.path.join(root_dir,'align_pose') + # assert len(os.listdir(self.audio_dir)) == len(os.listdir(self.image_dir)), 'audio and image length not equal' + + + self.frame_shape = tuple(frame_shape) + self.pairs_list = pairs_list + self.id_sampling = id_sampling + + if os.path.exists(os.path.join(self.pose_dir, 'train_fo')): + assert os.path.exists(os.path.join(self.pose_dir, 'test_fo')) + print("Use predefined train-test split.") + if id_sampling: + train_videos = {os.path.basename(video).split('#')[0] for video in + os.listdir(os.path.join(self.image_dir, 'train'))} + train_videos = list(train_videos) + else: + train_videos = np.load('/mnt/lustre/share_data/jixinya/VoxCeleb1_Cut/right.npy')# get_list(self.pose_dir, 'train_fo') + + self.image_dir = os.path.join(self.image_dir, 'train_fo' if is_train else 'test_fo') + self.audio_dir = os.path.join(self.audio_dir, 'train' if is_train else 'test') + self.pose_dir = os.path.join(self.pose_dir, 'train_fo' if is_train else 'test_fo') + else: + print("Use random train-test split.") + train_videos, test_videos = train_test_split(self.videos, random_state=random_seed, test_size=0.2) + + if is_train: + self.videos = train_videos + else: + self.videos = test_videos + + self.is_train = is_train + + if self.is_train: + self.transform = AllAugmentationTransform(**augmentation_params) + else: + self.transform = None + + def __len__(self): + return len(self.videos) + + def __getitem__(self, idx): + if self.is_train and self.id_sampling: + name = self.videos[idx].split('.')[0] + path = np.random.choice(glob.glob(os.path.join(self.root_dir, name + '*.mp4'))) + else: + name = self.videos[idx].split('.')[0] + + audio_path = os.path.join(self.audio_dir, name+'.npy') + pose_path = os.path.join(self.pose_dir,name+'.npy') + path = os.path.join(self.image_dir, name) + + video_name = os.path.basename(path) + + if os.path.isdir(path): + # if self.is_train and os.path.isdir(path): + frames = os.listdir(path) + num_frames = len(frames) + frame_idx = np.sort(np.random.choice(num_frames, replace=True, size=2)) + video_array = [img_as_float32(io.imread(os.path.join(path, frames[idx]))) for idx in frame_idx] + mfcc = np.load(audio_path) + pose = np.load(pose_path) + + # print(audio_path,pose_path,len(mfcc)) + + try: + len(mfcc) > 16 + except: + print('wrongmfcc len:',audio_path) + if 16 < len(mfcc) < 24 : + r = 0 + else: + + r = random.choice([x for x in range(3, len(mfcc)-20)]) + + mfccs = [] + poses = [] + video_array = [] + for ind in range(1, 17): + t_mfcc = mfcc[r+ind][:, 1:] + mfccs.append(t_mfcc) + t_pose = pose[r+ind,:-1] + poses.append(t_pose) + image = img_as_float32(io.imread(os.path.join(path, str(r + ind)+'.png'))) + video_array.append(image) + mfccs = np.array(mfccs) + poses = np.array(poses) + video_array = np.array(video_array) + + example_image = img_as_float32(io.imread(os.path.join(path, str(r)+'.png'))) + + + else: + print('Wrong, data path not an existing file.') + + if self.transform is not None: + video_array = self.transform(video_array) + + out = {} + + driving = np.array(video_array, dtype='float32') + + spatial_size = np.array(driving.shape[1:3][::-1])[np.newaxis] + driving_pose = np.array(poses, dtype='float32') + example_image = np.array(example_image, dtype='float32') + out['example_image'] = example_image.transpose((2, 0, 1)) + out['driving_pose'] = driving_pose + out['driving'] = driving.transpose((0, 3, 1, 2)) + + out['driving_audio'] = np.array(mfccs, dtype='float32') + # out['name'] = video_name + + return out + +class MeadDataset(Dataset): + """ + Dataset of videos, each video can be represented as: + - an image of concatenated frames + - '.mp4' or '.gif' + - folder with all frames + """ + + def __init__(self, root_dir, frame_shape=(256, 256, 3), id_sampling=False, is_train=True, + random_seed=0, augmentation_params=None): + self.root_dir = root_dir + + self.audio_dir = os.path.join(root_dir,'MEAD_MFCC') + self.image_dir = os.path.join(root_dir,'MEAD_fomm_crop') + + self.pose_dir = os.path.join(root_dir,'MEAD_fomm_pose_crop') + + self.videos = np.load('/mnt/lustre/share_data/jixinya/MEAD/MEAD_fomm_audio_less_crop.npy') + self.dict = np.load('/mnt/lustre/share_data/jixinya/MEAD/MEAD_fomm_neu_dic_crop.npy',allow_pickle=True).item() + # self.videos = os.listdir(root_dir) + self.frame_shape = tuple(frame_shape) + + self.id_sampling = id_sampling + if os.path.exists(os.path.join(root_dir, 'train')): + assert os.path.exists(os.path.join(root_dir, 'test')) + print("Use predefined train-test split.") + if id_sampling: + train_videos = {os.path.basename(video).split('#')[0] for video in + os.listdir(os.path.join(root_dir, 'train'))} + train_videos = list(train_videos) + else: + train_videos = os.listdir(os.path.join(root_dir, 'train')) + test_videos = os.listdir(os.path.join(root_dir, 'test')) + self.root_dir = os.path.join(self.root_dir, 'train' if is_train else 'test') + else: + print("Use random train-test split.") + train_videos, test_videos = train_test_split(self.videos, random_state=random_seed, test_size=0.2) + + if is_train: + self.videos = train_videos + else: + self.videos = test_videos + + self.is_train = is_train + + if self.is_train: + self.transform = AllAugmentationTransform(**augmentation_params) + else: + self.transform = None + + def __len__(self): + return len(self.videos) + + def __getitem__(self, idx): + if self.is_train and self.id_sampling: + name = self.videos[idx] + path = np.random.choice(glob.glob(os.path.join(self.root_dir, name + '*.mp4'))) + else: + name = self.videos[idx] + path = os.path.join(self.image_dir, name) + + video_name = os.path.basename(path) + id_name = path.split('/')[-2] + neu_list = self.dict[id_name] + neu_path = os.path.join(self.image_dir, np.random.choice(neu_list)) + + audio_path = os.path.join(self.audio_dir, name+'.npy') + pose_path = os.path.join(self.pose_dir,name+'.npy') + + + if self.is_train and os.path.isdir(path): + + mfcc = np.load(audio_path) + pose_raw = np.load(pose_path) + one_euro_filter = OneEuroFilter(mincutoff=0.01, beta=0.7, dcutoff=1.0, freq=100) + pose = np.zeros((len(pose_raw),7)) + + for j in range(len(pose_raw)): + pose[j]=one_euro_filter.process(pose_raw[j]) + # print(audio_path,pose_path,len(mfcc)) + + neu_frames = os.listdir(neu_path) + num_neu_frames = len(neu_frames) + frame_idx = np.random.choice(num_neu_frames) + example_image = img_as_float32(io.imread(os.path.join(neu_path, neu_frames[frame_idx]))) + try: + len(mfcc) > 16 + except: + print('wrongmfcc len:',audio_path) + if 16 < len(mfcc) < 24 : + r = 0 + else: + + r = random.choice([x for x in range(3, len(mfcc)-20)]) + + mfccs = [] + poses = [] + video_array = [] + for ind in range(1, 17): + t_mfcc = mfcc[r+ind][:, 1:] + mfccs.append(t_mfcc) + t_pose = pose[r+ind,:-1] + poses.append(t_pose) + image = img_as_float32(io.imread(os.path.join(path, str(r + ind)+'.png'))) + video_array.append(image) + mfccs = np.array(mfccs) + poses = np.array(poses) + video_array = np.array(video_array) + + else: + print('Wrong, data path not an existing file.') + + if self.transform is not None: + video_array = self.transform(video_array) + + out = {} + if self.is_train: + + driving = np.array(video_array, dtype='float32') + driving_pose = np.array(poses, dtype='float32') + example_image = np.array(example_image, dtype='float32') + + + out['example_image'] = example_image.transpose((2, 0, 1)) + out['driving_pose'] = driving_pose + out['driving'] = driving.transpose((0, 3, 1, 2)) + out['driving_audio'] = np.array(mfccs, dtype='float32') + + # out['name'] = id_name+'/'+video_name + + return out + + +class DatasetRepeater(Dataset): + """ + Pass several times over the same dataset for better i/o performance + """ + + def __init__(self, dataset, num_repeats=100): + self.dataset = dataset + # self.dataset2 = dataset2 + self.num_repeats = num_repeats + + def __len__(self): + return self.num_repeats * self.dataset.__len__() + + def __getitem__(self, idx): + # if idx % 5 == 0: + # return self.dataset2[idx % self.dataset2.__len__()]#% self.dataset.__len__() + # else: + # return self.dataset[idx % self.dataset.__len__()] + return self.dataset[idx % self.dataset.__len__()] + +class TestsetRepeater(Dataset): + """ + Pass several times over the same dataset for better i/o performance + """ + + def __init__(self, dataset, num_repeats=100): + self.dataset = dataset + + self.num_repeats = num_repeats + + def __len__(self): + return self.num_repeats * self.dataset.__len__() + + def __getitem__(self, idx): + + return self.dataset[idx % self.dataset.__len__()]#% self.dataset.__len__() + + +class PairedDataset(Dataset): + """ + Dataset of pairs for animation. + """ + + def __init__(self, initial_dataset, number_of_pairs, seed=0): + self.initial_dataset = initial_dataset + pairs_list = self.initial_dataset.pairs_list + + np.random.seed(seed) + + if pairs_list is None: + max_idx = min(number_of_pairs, len(initial_dataset)) + nx, ny = max_idx, max_idx + xy = np.mgrid[:nx, :ny].reshape(2, -1).T + number_of_pairs = min(xy.shape[0], number_of_pairs) + self.pairs = xy.take(np.random.choice(xy.shape[0], number_of_pairs, replace=False), axis=0) + else: + videos = self.initial_dataset.videos + name_to_index = {name: index for index, name in enumerate(videos)} + pairs = pd.read_csv(pairs_list) + pairs = pairs[np.logical_and(pairs['source'].isin(videos), pairs['driving'].isin(videos))] + + number_of_pairs = min(pairs.shape[0], number_of_pairs) + self.pairs = [] + self.start_frames = [] + for ind in range(number_of_pairs): + self.pairs.append( + (name_to_index[pairs['driving'].iloc[ind]], name_to_index[pairs['source'].iloc[ind]])) + + def __len__(self): + return len(self.pairs) + + def __getitem__(self, idx): + pair = self.pairs[idx] + first = self.initial_dataset[pair[0]] + second = self.initial_dataset[pair[1]] + first = {'driving_' + key: value for key, value in first.items()} + second = {'source_' + key: value for key, value in second.items()} + + return {**first, **second} diff --git a/FONT/gradio_demo.py b/FONT/gradio_demo.py new file mode 100644 index 0000000000000000000000000000000000000000..3a43cd8be9b5f45e54ec30870ba13f679f337f2e --- /dev/null +++ b/FONT/gradio_demo.py @@ -0,0 +1,560 @@ +import matplotlib +matplotlib.use('Agg') +import os,sys +import yaml +from argparse import ArgumentParser +from tqdm import tqdm +from skimage import io, img_as_float32 +import imageio +import numpy as np +from skimage.transform import resize +from skimage import img_as_ubyte +import torch +from FONT.filter1 import OneEuroFilter +import torch.utils + +from torch.autograd import Variable +from FONT.modules.generator import OcclusionAwareGenerator +from FONT.modules.keypoint_detector import KPDetector, KPDetector_a +from FONT.modules.util import AT_net, Emotion_k, Emotion_map, AT_net2 +from . import augmentation + +from scipy.spatial import ConvexHull +import random +import python_speech_features +from pathlib import Path +import dlib +import cv2 +from skimage.draw import circle +import matplotlib.pyplot as plt +import librosa +from skimage import transform as tf +import torch.nn.functional as F + +detector = dlib.get_frontal_face_detector() +predictor = dlib.shape_predictor('./FONT/ckpt/shape_predictor_68_face_landmarks.dat') + + + +class FONT(): + + def __init__(self, checkpoint_path='./FONT/ckpt', config_path='./FONT/config/MEAD_emo_video_aug_delta_4_crop_random_crop.yaml'): + + if torch.cuda.is_available(): + device = "cuda" + else: + device = "cpu" + + self.device = device + + os.environ['TORCH_HOME'] = checkpoint_path + + self.checkpoint_path = checkpoint_path + self.config_path = config_path + self.result_path = './results' + + def load_checkpoints(self, checkpoint_path, audio_checkpoint_path, emo_checkpoint_path, kp_checkpoint_path): + + with open(self.config_path) as f: + config = yaml.load(f, Loader=yaml.FullLoader) + + generator = OcclusionAwareGenerator(**config['model_params']['generator_params'], + **config['model_params']['common_params']) + + generator.to(self.device) + + kp_detector = KPDetector(**config['model_params']['kp_detector_params'], + **config['model_params']['common_params']) + + kp_detector.to(self.device) + + kp_detector_a = KPDetector_a(**config['model_params']['kp_detector_params'], + **config['model_params']['audio_params']) + + audio_feature = AT_net2() + + emo_detector = Emotion_k(block_expansion=32, num_channels=3, max_features=1024, + num_blocks=5, scale_factor=0.25, num_classes=8) + + + kp_detector_a.to(self.device) + audio_feature.to(self.device) + emo_detector.to(self.device) + + if self.device == 'cpu': + checkpoint = torch.load(checkpoint_path, map_location=torch.device('cpu')) + audio_checkpoint = torch.load(audio_checkpoint_path, map_location=torch.device('cpu')) + emo_checkpoint = torch.load(emo_checkpoint_path, map_location=torch.device('cpu')) + kp_checkpoint = torch.load(kp_checkpoint_path, map_location=torch.device('cpu')) + else: + checkpoint = torch.load(checkpoint_path) + audio_checkpoint = torch.load(audio_checkpoint_path) + emo_checkpoint = torch.load(emo_checkpoint_path) + kp_checkpoint = torch.load(kp_checkpoint_path) + + generator.load_state_dict(checkpoint['generator']) + kp_detector.load_state_dict(kp_checkpoint['kp_detector']) + audio_feature.load_state_dict(audio_checkpoint['audio_feature'], strict=False) + + kp_detector_a.load_state_dict(audio_checkpoint['kp_detector_a']) + emo_detector.load_state_dict(emo_checkpoint['emo_detector']) + + + generator.eval() + kp_detector.eval() + audio_feature.eval() + kp_detector_a.eval() + emo_detector.eval() + return generator, kp_detector, kp_detector_a, audio_feature, emo_detector + + def normalize_kp(self, 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['value'][0].data.cpu().numpy()).volume + driving_area = ConvexHull(kp_driving_initial['value'][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['value'] - kp_driving_initial['value']) + kp_value_diff *= adapt_movement_scale + kp_new['value'] = kp_value_diff + kp_source['value'] + + 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 + + def shape_to_np(self, shape, dtype="int"): + # initialize the list of (x, y)-coordinates + coords = np.zeros((shape.num_parts, 2), dtype=dtype) + + # loop over all facial landmarks and convert them + # to a 2-tuple of (x, y)-coordinates + for i in range(0, shape.num_parts): + coords[i] = (shape.part(i).x, shape.part(i).y) + + # return the list of (x, y)-coordinates + return coords + + def get_aligned_image(self, driving_video): + aligned_array = [] + + video_array = np.array(driving_video) + source_image = video_array[0] + # aligned_array.append(source_image) + source_image = np.array(source_image * 255, dtype=np.uint8) + gray = cv2.cvtColor(source_image, cv2.COLOR_BGR2GRAY) + rects = detector(gray, 1) # detect human face + for (i, rect) in enumerate(rects): + template = predictor(gray, rect) # detect 68 points + template = shape_to_np(template) + + # if opt.emotion == 'surprised' or opt.emotion == 'fear': + # template = template - [0, 10] + for i in range(len(video_array)): + image = np.array(video_array[i] * 255, dtype=np.uint8) + gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + rects = detector(gray, 1) # detect human face + for (j, rect) in enumerate(rects): + shape = predictor(gray, rect) # detect 68 points + shape = shape_to_np(shape) + + pts2 = np.float32(template[:35, :]) + pts1 = np.float32(shape[:35, :]) # eye and nose + + # pts2 = np.float32(np.concatenate((template[:16,:],template[27:36,:]),axis = 0)) + # pts1 = np.float32(np.concatenate((shape[:16,:],shape[27:36,:]),axis = 0)) #eye and nose + # pts1 = np.float32(landmark[17:35,:]) + tform = tf.SimilarityTransform() + tform.estimate(pts2, pts1) # Set the transformation matrix with the explicit parameters. + dst = tf.warp(image, tform, output_shape=(256, 256)) + + dst = np.array(dst, dtype=np.float32) + aligned_array.append(dst) + + return aligned_array + + def get_transformed_image(self, driving_video): + video_array = np.array(driving_video) + with open(self.config_path) as f: + config = yaml.load(f, Loader=yaml.FullLoader) + transformations = AllAugmentationTransform(**config['dataset_params']['augmentation_params']) + transformed_array = transformations(video_array) + return transformed_array + + def make_animation_smooth(self, source_image, deco_out, kp_loss, generator, kp_detector, + kp_detector_a, emo_detector, relative=True, adapt_movement_scale=True): + with torch.no_grad(): + predictions = [] + + source = torch.tensor(source_image[np.newaxis].astype(np.float32)).permute(0, 3, 1, 2) + if self.device == 'cuda': + source = source.cuda() + + + kp_source = kp_detector(source) + + save_dpi = (100, 100) + # save kp source image + # source = kp_source['value'].data.cpu().numpy() + # kp_array = source[0] + # image = np.zeros((256,256,3)) + # spatial_size = np.array(image.shape[:2][::-1])[np.newaxis] + # kp_array = spatial_size * (kp_array + 1) / 2 + # num_kp = kp_array.shape[0] + colormap = plt.get_cmap('gist_rainbow') + # for kp_ind, kp in enumerate(kp_array): + # rr, cc = circle(kp[1], kp[0], 5, shape=image.shape[:2]) + # image[rr, cc] = np.array(colormap(kp_ind / num_kp))[:3] + # imageio.imsave('./result/kp_source.png', image) + + kp_driving_initial = kp_detector_a(deco_out[:, 0]) + + emo_driving_all = [] + features = [] + kp_driving_all = [] + for frame_idx in tqdm(range(len(deco_out[0]))): + + + kp_driving = kp_detector_a(deco_out[:, frame_idx]) + kp_driving_all.append(kp_driving) + # if opt.add_emo: + # value = kp_driving['value'] + # jacobian = kp_driving['jacobian'] + # if opt.type == 'linear_3': + # emo_driving, _ = emo_detector(transformed_frame, value, jacobian) + # features.append(emo_detector.feature(transformed_frame).data.cpu().numpy()) + # + # emo_driving_all.append(emo_driving) + features = np.array(features) + # if opt.add_emo: + # one_euro_filter_v = OneEuroFilter(mincutoff=1, beta=0.2, dcutoff=1.0, freq=100) # 1 0.4 + # one_euro_filter_j = OneEuroFilter(mincutoff=1, beta=0.2, dcutoff=1.0, freq=100) # 1 0.4 + # + # for j in range(len(emo_driving_all)): + # emo_driving_all[j]['value'] = one_euro_filter_v.process( + # emo_driving_all[j]['value'].cpu() * 100) / 100 + # emo_driving_all[j]['value'] = emo_driving_all[j]['value'].cuda() + # emo_driving_all[j]['jacobian'] = one_euro_filter_j.process( + # emo_driving_all[j]['jacobian'].cpu() * 100) / 100 + # emo_driving_all[j]['jacobian'] = emo_driving_all[j]['jacobian'].cuda() + + one_euro_filter_v = OneEuroFilter(mincutoff=0.05, beta=8, dcutoff=1.0, freq=100) + one_euro_filter_j = OneEuroFilter(mincutoff=0.05, beta=8, dcutoff=1.0, freq=100) + + for j in range(len(kp_driving_all)): + kp_driving_all[j]['value'] = one_euro_filter_v.process(kp_driving_all[j]['value'].cpu() * 10) / 10 + kp_driving_all[j]['value'] = kp_driving_all[j]['value'].cuda() + kp_driving_all[j]['jacobian'] = one_euro_filter_j.process(kp_driving_all[j]['jacobian'].cpu() * 10) / 10 + kp_driving_all[j]['jacobian'] = kp_driving_all[j]['jacobian'].cuda() + + for frame_idx in tqdm(range(len(deco_out[0]))): + + + kp_driving = kp_driving_all[frame_idx] + + # kp_driving_real = kp_detector(driving_frame) + + # kp_driving['value'] = (1-opt.weight)*kp_driving['value'] + opt.weight*kp_driving_real['value'] + # kp_driving['jacobian'] = (1-opt.weight)*kp_driving['jacobian'] + opt.weight*kp_driving_real['jacobian'] + + # if opt.add_emo: + # emo_driving = emo_driving_all[frame_idx] + # if opt.type == 'linear_3': + # kp_driving['value'][:, 1] = kp_driving['value'][:, 1] + emo_driving['value'][:, 0] * 0.2 + # kp_driving['jacobian'][:, 1] = kp_driving['jacobian'][:, 1] + emo_driving['jacobian'][:, + # 0] * 0.2 + # kp_driving['value'][:, 4] = kp_driving['value'][:, 4] + emo_driving['value'][:, 1] + # kp_driving['jacobian'][:, 4] = kp_driving['jacobian'][:, 4] + emo_driving['jacobian'][:, 1] + # kp_driving['value'][:, 6] = kp_driving['value'][:, 6] + emo_driving['value'][:, 2] + # kp_driving['jacobian'][:, 6] = kp_driving['jacobian'][:, 6] + emo_driving['jacobian'][:, 2] + # # kp_driving['value'][:,8] = kp_driving['value'][:,8] + emo_driving['value'][:,3] + # # kp_driving['jacobian'][:,8] = kp_driving['jacobian'][:,8] + emo_driving['jacobian'][:,3] + + kp_norm = self.normalize_kp(kp_source=kp_source, kp_driving=kp_driving, + kp_driving_initial=kp_driving_initial, use_relative_movement=relative, + use_relative_jacobian=relative, adapt_movement_scale=adapt_movement_scale) + out = generator(source, kp_source=kp_source, kp_driving=kp_norm) + + # occlusion_map = out['occlusion_map'].data.cpu().repeat(1, 3, 1, 1) + # occlusion_map = F.interpolate(occlusion_map, size=(256,256)).numpy() + # occlusion_map = np.transpose(occlusion_map, [0, 2, 3, 1]) + # print(occlusion_map.shape) + # imageio.imsave('./result/occlusion.png', occlusion_map[0], dpi=save_dpi) + # + # prediction = out['prediction'].data.cpu().numpy() + # prediction = np.transpose(prediction, [0, 2, 3, 1]) + # print(prediction.shape) + # imageio.imsave('./result/prediction.png', prediction[0], dpi=save_dpi) + # + # full_mask = [] + # for i in range(out['sparse_deformed'].shape[1]): + # mask = out['mask'][:, i:(i + 1)].data.cpu().repeat(1, 3, 1, 1) + # mask = F.interpolate(mask, size=(256,256)) + # mask = np.transpose(mask.numpy(), (0, 2, 3, 1)) + # if i != 0: + # color = np.array(colormap((i - 1) / (out['sparse_deformed'].shape[1] - 1)))[:3] + # else: + # color = np.array((0, 0, 0)) + # + # color = color.reshape((1, 1, 1, 3)) + # + # full_mask.append(mask * color) + # motion_flow = sum(full_mask) + # print(motion_flow.shape) + # imageio.imsave('./result/motion_flow.png', motion_flow[0], dpi=save_dpi) + # quit() + + predictions.append(np.transpose(out['prediction'].data.cpu().numpy(), [0, 2, 3, 1])[0]) + return predictions, features + + def test_auido(self, example_image, audio_feature, all_pose, driving_audio_path): + with open(self.config_path) as f: + para = yaml.load(f, Loader=yaml.FullLoader) + + # encoder = audio_feature() + + audio_feature = audio_feature.to(self.device) + audio_feature.eval() + # decoder.eval() + test_file = driving_audio_path + pose = all_pose[:, :6] + + one_euro_filter = OneEuroFilter(mincutoff=0.004, beta=0.7, dcutoff=1.0, freq=100) + + for j in range(len(pose)): + pose[j] = one_euro_filter.process(pose[j]) + # pose[j]=pose[0] + + example_image = np.array(example_image, dtype='float32').transpose((2, 0, 1)) + + + + # get atvg audio feature + speech, sr = librosa.load(test_file, sr=16000) + # mfcc = python_speech_features.mfcc(speech ,16000,winstep=0.01) + speech = np.insert(speech, 0, np.zeros(1920)) + speech = np.append(speech, np.zeros(1920)) + mfcc = python_speech_features.mfcc(speech, 16000, winstep=0.01) + ind = 3 + fake_lmark = [] + input_mfcc = [] + while ind <= int(mfcc.shape[0] / 4) - 4: + t_mfcc = mfcc[(ind - 3) * 4: (ind + 4) * 4, 1:] + t_mfcc = torch.FloatTensor(t_mfcc).cuda() + input_mfcc.append(t_mfcc) + ind += 1 + input_mfcc = torch.stack(input_mfcc, dim=0) # N,28,12 + + print('=======================================') + print('Start to generate images') + + with torch.no_grad(): + if (len(pose) < len(input_mfcc)): + gap = len(input_mfcc) - len(pose) + n = int((gap / len(pose) / 2)) + 2 + pose = np.concatenate((pose, pose[::-1, :]), axis=0) + pose = np.tile(pose, (n, 1)) + if (len(pose) > len(input_mfcc)): + pose = pose[:len(input_mfcc), :] + + pose = Variable(torch.FloatTensor(pose.astype(float))) + example_image = Variable(torch.FloatTensor(example_image.astype(float))) + + if self.device == 'cuda': + pose = pose.cuda() + example_image = example_image.cuda() + input_mfcc = input_mfcc.cuda() + + pose = pose.unsqueeze(0) + example_image = example_image.unsqueeze(0) + input_mfcc = input_mfcc.unsqueeze(0) + + deco_out = audio_feature(example_image, input_mfcc, pose, para['train_params']['jaco_net'], 1.6) + + # ATNET + # deco_out = audio_feature(example_image, input_mfcc, pose, para['train_params']['jaco_net']) + + return deco_out + + def save(self, path, frames, format): + + if format == '.png': + if not os.path.exists(path): + os.makedirs(path) + for j, frame in enumerate(frames): + imageio.imsave(path + '/' + str(j) + '.png', frame) + # imageio.imsave(os.path.join(path, str(j) + '.png'), frames[j]) + else: + print("Unknown format %s" % format) + exit() + + class VideoWriter(object): + def __init__(self, path, width, height, fps): + fourcc = cv2.VideoWriter_fourcc(*'XVID') + self.path = path + self.out = cv2.VideoWriter(self.path, fourcc, fps, (width, height)) + + def write_frame(self, frame): + self.out.write(frame) + + def end(self): + self.out.release() + + def get_pose_from_audio(self, driving_audio): + pose = './FONT/test/pose/7.npy' + pose_long = './FONT/test/pose_long/0zn70Ak8lRc_Daniel_Auteuil_0zn70Ak8lRc_0002.npy' + return pose, pose_long + + def concatenate(self, number, imgs, save_path): + width, height = imgs.shape[-3:-1] + imgs = imgs.reshape(number, -1, width, height, 3) + if number == 2: + left = imgs[0] + right = imgs[1] + + im_all = [] + for i in range(len(left)): + im = np.concatenate((left[i], right[i]), axis=1) + im_all.append(im) + if number == 3: + left = imgs[0] + middle = imgs[1] + right = imgs[2] + + im_all = [] + for i in range(len(left)): + im = np.concatenate((left[i], middle[i], right[i]), axis=1) + im_all.append(im) + if number == 4: + left = imgs[0] + left2 = imgs[1] + right = imgs[2] + right2 = imgs[3] + + im_all = [] + for i in range(len(left)): + im = np.concatenate((left[i], left2[i], right[i], right2[i]), axis=1) + im_all.append(im) + if number == 5: + left = imgs[0] + left2 = imgs[1] + middle = imgs[2] + right = imgs[3] + right2 = imgs[4] + + im_all = [] + for i in range(len(left)): + im = np.concatenate((left[i], left2[i], middle[i], right[i], right2[i]), axis=1) + im_all.append(im) + + imageio.mimsave(save_path, [img_as_ubyte(frame) for frame in im_all], fps=25) + + def add_audio(self, video_name=None, audio_dir=None): + + command = 'ffmpeg -i ' + video_name + ' -i ' + audio_dir + ' -vcodec copy -acodec copy -y ' + video_name.replace( + '.mp4', '.mov') + print(command) + os.system(command) + + def smooth_pose(self, pose_file, pose_long): + start = np.load(pose_file) + video_pose = np.load(pose_long) + if video_pose.shape[-1] == 6: + start = start[:, :6] + + delta = video_pose - video_pose[0, :] + # print(len(delta)) + + pose = np.repeat(start, len(delta), axis=0) + all_pose = pose + delta + + return all_pose + + + + def test(self, source_image_path, driving_audio_path, result_dir='./results/'): + + pose_file, pose_given = self.get_pose_from_audio(driving_audio_path) + + + pose_dim = np.load(pose_file).shape[-1] + all_pose = np.load(pose_file).reshape(-1, pose_dim) + all_pose = self.smooth_pose(pose_file, pose_given) + + source_image = img_as_float32(io.imread(source_image_path)) + source_image = resize(source_image, (256, 256))[..., :3] + + # reader = imageio.get_reader(driving_video) + # fps = reader.get_meta_data()['fps'] + # driving_video = [] + # try: + # for im in reader: + # driving_video.append(im) + # except RuntimeError: + # pass + # reader.close() + + # driving_video = [resize(frame, (256, 256))[..., :3] for frame in driving_video] + # driving_video = self.get_aligned_image(driving_video) + # transformed_video = self.get_transformed_image(driving_video) + # transformed_video = np.array(transformed_video) + + checkpoint = os.path.join(self.checkpoint_path, "124_52000.pth.tar") + audio_checkpoint = os.path.join(self.checkpoint_path, "1-6000.pth.tar") + emo_checkpoint = os.path.join(self.checkpoint_path, "5-3000.pth.tar") + + generator, kp_detector, kp_detector_a, audio_feature, emo_detector = \ + self.load_checkpoints(checkpoint_path=checkpoint, audio_checkpoint_path=audio_checkpoint, + emo_checkpoint_path=emo_checkpoint, kp_checkpoint_path=checkpoint) + + deco_out = self.test_auido(source_image, audio_feature, all_pose, driving_audio_path) # 1,N,32+3,64,64 + + # if len(driving_video) < len(deco_out[0]): + # driving_video = np.resize(driving_video, (len(deco_out[0]), 256, 256, 3)) + # transformed_video = np.resize(transformed_video, (len(deco_out[0]), 256, 256, 3)) + # + # else: + # driving_video = driving_video[:len(deco_out[0])] + predictions, _ = self.make_animation_smooth(source_image, deco_out, 0, + generator, kp_detector, kp_detector_a, emo_detector) + + imageio.mimsave(os.path.join(self.result_path, 'neutral.mp4'), [img_as_ubyte(frame) for frame in predictions], + fps=30) + predictions = np.array(predictions) + + # opt.add_emo = True + # predictions1, _ = make_animation_smooth(source_image, driving_video, transformed_video, deco_out, opt.kp_loss, + # generator, kp_detector, kp_detector_a, emo_detector, opt, + # relative=opt.relative, adapt_movement_scale=opt.adapt_scale, + # cpu=opt.cpu) + + # imageio.mimsave(os.path.join(self.result_path, 'emotion.mp4'), [img_as_ubyte(frame) for frame in predictions1], + # fps=fps) + # add_audio(os.path.join(self.result_path, 'emotion.mp4'), opt.in_file) + # predictions1 = np.array(predictions1) + # all_imgs = np.concatenate((driving_video, predictions, predictions1), axis=0) + save_path = os.path.join(self.result_path, 'neutral.mp4') + # concatenate(3, all_imgs, save_path) + self.add_audio(save_path, driving_audio_path) + + + + # del self.preprocess_model + # del self.audio_to_coeff + # del self.animate_from_coeff + + if torch.cuda.is_available(): + torch.cuda.empty_cache() + torch.cuda.synchronize() + + import gc; + gc.collect() + + return save_path + diff --git a/FONT/hparams.py b/FONT/hparams.py new file mode 100644 index 0000000000000000000000000000000000000000..1c019046279f497e4eae3f839f683bc0b1193c6b --- /dev/null +++ b/FONT/hparams.py @@ -0,0 +1,101 @@ +from glob import glob +import os + +def get_image_list(data_root, split): + filelist = [] + + with open('filelists/{}.txt'.format(split)) as f: + for line in f: + line = line.strip() + if ' ' in line: line = line.split()[0] + filelist.append(os.path.join(data_root, line)) + + return filelist + +class HParams: + def __init__(self, **kwargs): + self.data = {} + + for key, value in kwargs.items(): + self.data[key] = value + + def __getattr__(self, key): + if key not in self.data: + raise AttributeError("'HParams' object has no attribute %s" % key) + return self.data[key] + + def set_hparam(self, key, value): + self.data[key] = value + + +# Default hyperparameters +hparams = HParams( + num_mels=80, # Number of mel-spectrogram channels and local conditioning dimensionality + # network + rescale=True, # Whether to rescale audio prior to preprocessing + rescaling_max=0.9, # Rescaling value + + # Use LWS (https://github.com/Jonathan-LeRoux/lws) for STFT and phase reconstruction + # It"s preferred to set True to use with https://github.com/r9y9/wavenet_vocoder + # Does not work if n_ffit is not multiple of hop_size!! + use_lws=False, + + n_fft=800, # Extra window size is filled with 0 paddings to match this parameter + hop_size=200, # For 16000Hz, 200 = 12.5 ms (0.0125 * sample_rate) + win_size=800, # For 16000Hz, 800 = 50 ms (If None, win_size = n_fft) (0.05 * sample_rate) + sample_rate=16000, # 16000Hz (corresponding to librispeech) (sox --i ) + + frame_shift_ms=None, # Can replace hop_size parameter. (Recommended: 12.5) + + # Mel and Linear spectrograms normalization/scaling and clipping + signal_normalization=True, + # Whether to normalize mel spectrograms to some predefined range (following below parameters) + allow_clipping_in_normalization=True, # Only relevant if mel_normalization = True + symmetric_mels=True, + # Whether to scale the data to be symmetric around 0. (Also multiplies the output range by 2, + # faster and cleaner convergence) + max_abs_value=4., + # max absolute value of data. If symmetric, data will be [-max, max] else [0, max] (Must not + # be too big to avoid gradient explosion, + # not too small for fast convergence) + # Contribution by @begeekmyfriend + # Spectrogram Pre-Emphasis (Lfilter: Reduce spectrogram noise and helps model certitude + # levels. Also allows for better G&L phase reconstruction) + preemphasize=True, # whether to apply filter + preemphasis=0.97, # filter coefficient. + + # Limits + min_level_db=-100, + ref_level_db=20, + fmin=55, + # Set this to 55 if your speaker is male! if female, 95 should help taking off noise. (To + # test depending on dataset. Pitch info: male~[65, 260], female~[100, 525]) + fmax=7600, # To be increased/reduced depending on data. + + ###################### Our training parameters ################################# + img_size=96, + fps=25, + + batch_size=16, + initial_learning_rate=1e-4, + nepochs=200000000000000000, ### ctrl + c, stop whenever eval loss is consistently greater than train loss for ~10 epochs + num_workers=16, + checkpoint_interval=3000, + eval_interval=3000, + save_optimizer_state=True, + + syncnet_wt=0.0, # is initially zero, will be set automatically to 0.03 later. Leads to faster convergence. + syncnet_batch_size=64, + syncnet_lr=1e-4, + syncnet_eval_interval=10000, + syncnet_checkpoint_interval=10000, + + disc_wt=0.07, + disc_initial_learning_rate=1e-4, +) + + +def hparams_debug_string(): + values = hparams.values() + hp = [" %s: %s" % (name, values[name]) for name in sorted(values) if name != "sentences"] + return "Hyperparameters:\n" + "\n".join(hp) diff --git a/FONT/logger.py b/FONT/logger.py new file mode 100644 index 0000000000000000000000000000000000000000..9810ebcf938fa75cca17db79d48753944b928b84 --- /dev/null +++ b/FONT/logger.py @@ -0,0 +1,225 @@ +import numpy as np +import torch +import torch.nn.functional as F +import imageio + +import os +from skimage.draw import circle + +import matplotlib.pyplot as plt +import collections + + +class Logger: + def __init__(self, log_dir, checkpoint_freq=50, visualizer_params=None, zfill_num=8, log_file_name='log.txt'): + + self.loss_list = [] + self.cpk_dir = log_dir + self.visualizations_dir = os.path.join(log_dir, 'train-vis') + if not os.path.exists(self.visualizations_dir): + os.makedirs(self.visualizations_dir) + self.log_file = open(os.path.join(log_dir, log_file_name), 'a') + self.zfill_num = zfill_num + self.visualizer = Visualizer(**visualizer_params) + self.checkpoint_freq = checkpoint_freq + self.epoch = 0 + self.best_loss = float('inf') + self.names = None + + def log_scores(self, loss_names): + loss_mean = np.array(self.loss_list).mean(axis=0) + + loss_string = "; ".join(["%s - %.5f" % (name, value) for name, value in zip(loss_names, loss_mean)]) + loss_string = str(str(self.epoch)+str(self.step).zfill(self.zfill_num)) + ") " + loss_string + + print(loss_string, file=self.log_file) + self.loss_list = [] + self.log_file.flush() + + def visualize_rec(self, inp, out): + # image = self.visualizer.visualize(inp['driving'], inp['source'], out) + image = self.visualizer.visualize(inp['driving'][:,-1], inp['transformed_driving'][:,-1], inp['example_image'], out) + imageio.imsave(os.path.join(self.visualizations_dir, "%s-%s-rec.png" % (str(self.epoch),str(self.step).zfill(self.zfill_num))), image) + + def save_cpk(self, emergent=False): + cpk = {k: v.state_dict() for k, v in self.models.items()} + cpk['epoch'] = self.epoch + cpk['step'] = self.step + cpk_path = os.path.join(self.cpk_dir, '%s-%s-checkpoint.pth.tar' % (str(self.epoch),str(self.step).zfill(self.zfill_num))) + if not (os.path.exists(cpk_path) and emergent): + torch.save(cpk, cpk_path) + + @staticmethod + def load_cpk(checkpoint_path, generator=None, discriminator=None, kp_detector=None, audio_feature=None, + optimizer_generator=None, optimizer_discriminator=None, optimizer_kp_detector=None, optimizer_audio_feature = None): + checkpoint = torch.load(checkpoint_path) + if generator is not None: + generator.load_state_dict(checkpoint['generator']) + if kp_detector is not None: + kp_detector.load_state_dict(checkpoint['kp_detector']) + if discriminator is not None: + try: + discriminator.load_state_dict(checkpoint['discriminator']) + except: + print ('No discriminator in the state-dict. Dicriminator will be randomly initialized') + # if audio_feature is not None: + # audio_feature.load_state_dict(checkpoint['audio_feature']) + if optimizer_generator is not None: + optimizer_generator.load_state_dict(checkpoint['optimizer_generator']) + if optimizer_discriminator is not None: + try: + optimizer_discriminator.load_state_dict(checkpoint['optimizer_discriminator']) + except RuntimeError as e: + print ('No discriminator optimizer in the state-dict. Optimizer will be not initialized') + if optimizer_kp_detector is not None: + optimizer_kp_detector.load_state_dict(checkpoint['optimizer_kp_detector']) + # if optimizer_audio_feature is not None: + # a = checkpoint['optimizer_kp_detector']['param_groups'] + # a[0].pop('params') + # optimizer_audio_feature.load_state_dict(checkpoint['optimizer_audio_feature']) + + return checkpoint['epoch'] + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if 'models' in self.__dict__: + self.save_cpk() + self.log_file.close() + + def log_iter(self, losses): + losses = collections.OrderedDict(losses.items()) + if self.names is None: + self.names = list(losses.keys()) + self.loss_list.append(list(losses.values())) + + def log_epoch(self, epoch, step, models, inp, out): + self.epoch = epoch + self.step = step + self.models = models + if (self.epoch + 1) % self.checkpoint_freq == 0: + self.save_cpk() + self.log_scores(self.names) + self.visualize_rec(inp, out) + + +class Visualizer: + def __init__(self, kp_size=5, draw_border=False, colormap='gist_rainbow'): + self.kp_size = kp_size + self.draw_border = draw_border + self.colormap = plt.get_cmap(colormap) + + def draw_image_with_kp(self, image, kp_array): + image = np.copy(image) + spatial_size = np.array(image.shape[:2][::-1])[np.newaxis] + kp_array = spatial_size * (kp_array + 1) / 2 + num_kp = kp_array.shape[0] + for kp_ind, kp in enumerate(kp_array): + rr, cc = circle(kp[1], kp[0], self.kp_size, shape=image.shape[:2]) + image[rr, cc] = np.array(self.colormap(kp_ind / num_kp))[:3] + return image + + def create_image_column_with_kp(self, images, kp): + image_array = np.array([self.draw_image_with_kp(v, k) for v, k in zip(images, kp)]) + return self.create_image_column(image_array) + + def create_image_column(self, images): + if self.draw_border: + images = np.copy(images) + images[:, :, [0, -1]] = (1, 1, 1) + images[:, :, [0, -1]] = (1, 1, 1) + return np.concatenate(list(images), axis=0) + + def create_image_grid(self, *args): + out = [] + for arg in args: + if type(arg) == tuple: + out.append(self.create_image_column_with_kp(arg[0], arg[1])) + else: + out.append(self.create_image_column(arg)) + return np.concatenate(out, axis=1) + + def visualize(self, driving, transformed_driving, source, out): + images = [] + + + # Source image with keypoints + source = source.data.cpu() + kp_source = out['kp_source']['value'].data.cpu().numpy() + source = np.transpose(source, [0, 2, 3, 1]) + images.append((source, kp_source)) + + # Equivariance visualization + if 'transformed_frame' in out: + transformed = out['transformed_frame'].data.cpu().numpy() + transformed = np.transpose(transformed, [0, 2, 3, 1]) + transformed_kp = out['transformed_kp']['value'].data.cpu().numpy() + images.append((transformed, transformed_kp)) + + # Equivariance visualization + transformed_driving = transformed_driving.data.cpu().numpy() + transformed_driving = np.transpose(transformed_driving, [0, 2, 3, 1]) + images.append(transformed_driving) + + # Driving image with keypoints + kp_driving = out['kp_driving'][-1]['value'].data.cpu().numpy() #[-1]['value'] + driving = driving.data.cpu().numpy() + driving = np.transpose(driving, [0, 2, 3, 1]) + images.append((driving, kp_driving)) + + + + # Deformed image + if 'deformed' in out: + deformed = out['deformed'].data.cpu().numpy() + deformed = np.transpose(deformed, [0, 2, 3, 1]) + images.append(deformed) + + # Result with and without keypoints + prediction = out['prediction'].data.cpu().numpy() + prediction = np.transpose(prediction, [0, 2, 3, 1]) + if 'kp_norm' in out: + kp_norm = out['kp_norm']['value'].data.cpu().numpy() + images.append((prediction, kp_norm)) + images.append(prediction) + + + ## Occlusion map + if 'occlusion_map' in out: + occlusion_map = out['occlusion_map'].data.cpu().repeat(1, 3, 1, 1) + occlusion_map = F.interpolate(occlusion_map, size=source.shape[1:3]).numpy() + occlusion_map = np.transpose(occlusion_map, [0, 2, 3, 1]) + images.append(occlusion_map) + + # Deformed images according to each individual transform + if 'sparse_deformed' in out: + full_mask = [] + for i in range(out['sparse_deformed'].shape[1]): + image = out['sparse_deformed'][:, i].data.cpu() + image = F.interpolate(image, size=source.shape[1:3]) + mask = out['mask'][:, i:(i+1)].data.cpu().repeat(1, 3, 1, 1) + mask = F.interpolate(mask, size=source.shape[1:3]) + image = np.transpose(image.numpy(), (0, 2, 3, 1)) + mask = np.transpose(mask.numpy(), (0, 2, 3, 1)) + + if i != 0: + color = np.array(self.colormap((i - 1) / (out['sparse_deformed'].shape[1] - 1)))[:3] + else: + color = np.array((0, 0, 0)) + + color = color.reshape((1, 1, 1, 3)) + + images.append(image) + if i != 0: + images.append(mask * color) + else: + images.append(mask) + + full_mask.append(mask * color) + + images.append(sum(full_mask)) + + image = self.create_image_grid(*images) + image = (255 * image).astype(np.uint8) + return image diff --git a/FONT/modules/conv.py b/FONT/modules/conv.py new file mode 100644 index 0000000000000000000000000000000000000000..ed83da00cb199e027ef217fd360352d91a7891ff --- /dev/null +++ b/FONT/modules/conv.py @@ -0,0 +1,44 @@ +import torch +from torch import nn +from torch.nn import functional as F + +class Conv2d(nn.Module): + def __init__(self, cin, cout, kernel_size, stride, padding, residual=False, *args, **kwargs): + super().__init__(*args, **kwargs) + self.conv_block = nn.Sequential( + nn.Conv2d(cin, cout, kernel_size, stride, padding), + nn.BatchNorm2d(cout) + ) + self.act = nn.ReLU() + self.residual = residual + + def forward(self, x): + out = self.conv_block(x) + if self.residual: + out += x + return self.act(out) + +class nonorm_Conv2d(nn.Module): + def __init__(self, cin, cout, kernel_size, stride, padding, residual=False, *args, **kwargs): + super().__init__(*args, **kwargs) + self.conv_block = nn.Sequential( + nn.Conv2d(cin, cout, kernel_size, stride, padding), + ) + self.act = nn.LeakyReLU(0.01, inplace=True) + + def forward(self, x): + out = self.conv_block(x) + return self.act(out) + +class Conv2dTranspose(nn.Module): + def __init__(self, cin, cout, kernel_size, stride, padding, output_padding=0, *args, **kwargs): + super().__init__(*args, **kwargs) + self.conv_block = nn.Sequential( + nn.ConvTranspose2d(cin, cout, kernel_size, stride, padding, output_padding), + nn.BatchNorm2d(cout) + ) + self.act = nn.ReLU() + + def forward(self, x): + out = self.conv_block(x) + return self.act(out) diff --git a/FONT/modules/dense_motion.py b/FONT/modules/dense_motion.py new file mode 100644 index 0000000000000000000000000000000000000000..30b08d6d218bf758fe62ed307f0e058b6c12c23d --- /dev/null +++ b/FONT/modules/dense_motion.py @@ -0,0 +1,113 @@ +from torch import nn +import torch.nn.functional as F +import torch +from .util import Hourglass, AntiAliasInterpolation2d, make_coordinate_grid, kp2gaussian + + +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=False, + scale_factor=1, kp_variance=0.01): + super(DenseMotionNetwork, self).__init__() + self.hourglass = Hourglass(block_expansion=block_expansion, in_features=(num_kp + 1) * (num_channels + 1), + max_features=max_features, num_blocks=num_blocks) + + self.mask = nn.Conv2d(self.hourglass.out_filters, num_kp + 1, 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): + """ + Eq 6. in the paper H_k(z) + """ + spatial_size = source_image.shape[2:] + gaussian_driving = kp2gaussian(kp_driving, spatial_size=spatial_size, kp_variance=self.kp_variance) + gaussian_source = kp2gaussian(kp_source, spatial_size=spatial_size, kp_variance=self.kp_variance) + heatmap = gaussian_driving - gaussian_source #[4,10,H,W] + + #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) #[4,11,1,h,w] + return heatmap + + def create_sparse_motions(self, source_image, kp_driving, kp_source): + """ + Eq 4. in the paper T_{s<-d}(z) + """ + bs, _, h, w = source_image.shape + identity_grid = make_coordinate_grid((h, w), type=kp_source['value'].type()) + identity_grid = identity_grid.view(1, 1, h, w, 2) + coordinate_grid = identity_grid - kp_driving['value'].view(bs, self.num_kp, 1, 1, 2) #[4,10,64,64,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) + + driving_to_source = coordinate_grid + kp_source['value'].view(bs, self.num_kp, 1, 1, 2) + + #adding background feature + identity_grid = identity_grid.repeat(bs, 1, 1, 1, 1) + sparse_motions = torch.cat([identity_grid, driving_to_source], dim=1) + return sparse_motions + + def create_deformed_source_image(self, source_image, sparse_motions): + """ + Eq 7. in the paper \hat{T}_{s<-d}(z) + """ + 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 forward(self, source_image, kp_driving, kp_source): + if self.scale_factor != 1: + source_image = self.down(source_image) #[4,3,H*scale,W*scale] + + bs, _, h, w = source_image.shape + + out_dict = dict() + heatmap_representation = self.create_heatmap_representations(source_image, kp_driving, kp_source) #[4,11,1,64,64] + sparse_motion = self.create_sparse_motions(source_image, kp_driving, kp_source) #[4,11,64,64,2] + deformed_source = self.create_deformed_source_image(source_image, sparse_motion) #[4,11,3,64,64] + out_dict['sparse_deformed'] = deformed_source + + input = torch.cat([heatmap_representation, deformed_source], dim=2) + input = input.view(bs, -1, h, w) #[4,11*4,64,64] + + prediction = self.hourglass(input) #[4,108,64,64] + + mask = self.mask(prediction) + mask = F.softmax(mask, dim=1) #[4,11,64,64] + out_dict['mask'] = mask + mask = mask.unsqueeze(2) + sparse_motion = sparse_motion.permute(0, 1, 4, 2, 3) + deformation = (sparse_motion * mask).sum(dim=1) + deformation = deformation.permute(0, 2, 3, 1) #[4,64,64,2] + + out_dict['deformation'] = deformation + + # Sec. 3.2 in the paper + if self.occlusion: + occlusion_map = torch.sigmoid(self.occlusion(prediction)) + out_dict['occlusion_map'] = occlusion_map #[4,1,64,64] + + return out_dict diff --git a/FONT/modules/discriminator.py b/FONT/modules/discriminator.py new file mode 100644 index 0000000000000000000000000000000000000000..8356493e49cb9d5723f52d2ac24f8b5d244d3a0f --- /dev/null +++ b/FONT/modules/discriminator.py @@ -0,0 +1,95 @@ +from torch import nn +import torch.nn.functional as F +from modules.util import kp2gaussian +import torch + + +class DownBlock2d(nn.Module): + """ + Simple block for processing video (encoder). + """ + + def __init__(self, in_features, out_features, norm=False, kernel_size=4, pool=False, sn=False): + super(DownBlock2d, self).__init__() + self.conv = nn.Conv2d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size) + + if sn: + self.conv = nn.utils.spectral_norm(self.conv) + + if norm: + self.norm = nn.InstanceNorm2d(out_features, affine=True) + else: + self.norm = None + self.pool = pool + + def forward(self, x): + out = x + out = self.conv(out) + if self.norm: + out = self.norm(out) + out = F.leaky_relu(out, 0.2) + if self.pool: + out = F.avg_pool2d(out, (2, 2)) + return out + + +class Discriminator(nn.Module): + """ + Discriminator similar to Pix2Pix + """ + + def __init__(self, num_channels=3, block_expansion=64, num_blocks=4, max_features=512, + sn=False, use_kp=False, num_kp=10, kp_variance=0.01, **kwargs): + super(Discriminator, self).__init__() + + down_blocks = [] + for i in range(num_blocks): + down_blocks.append( + DownBlock2d(num_channels + num_kp * use_kp if i == 0 else min(max_features, block_expansion * (2 ** i)), + min(max_features, block_expansion * (2 ** (i + 1))), + norm=(i != 0), kernel_size=4, pool=(i != num_blocks - 1), sn=sn)) + + self.down_blocks = nn.ModuleList(down_blocks) + self.conv = nn.Conv2d(self.down_blocks[-1].conv.out_channels, out_channels=1, kernel_size=1) + if sn: + self.conv = nn.utils.spectral_norm(self.conv) + self.use_kp = use_kp + self.kp_variance = kp_variance + + def forward(self, x, kp=None): + feature_maps = [] + out = x + if self.use_kp: + heatmap = kp2gaussian(kp, x.shape[2:], self.kp_variance) + out = torch.cat([out, heatmap], dim=1) + + for down_block in self.down_blocks: + feature_maps.append(down_block(out)) + out = feature_maps[-1] + prediction_map = self.conv(out) + + return feature_maps, prediction_map + + +class MultiScaleDiscriminator(nn.Module): + """ + Multi-scale (scale) discriminator + """ + + def __init__(self, scales=(), **kwargs): + super(MultiScaleDiscriminator, self).__init__() + self.scales = scales + discs = {} + for scale in scales: + discs[str(scale).replace('.', '-')] = Discriminator(**kwargs) + self.discs = nn.ModuleDict(discs) + + def forward(self, x, kp=None): + out_dict = {} + for scale, disc in self.discs.items(): + scale = str(scale).replace('-', '.') + key = 'prediction_' + scale + feature_maps, prediction_map = disc(x[key], kp) + out_dict['feature_maps_' + scale] = feature_maps + out_dict['prediction_map_' + scale] = prediction_map + return out_dict diff --git a/FONT/modules/frames_dataset.py b/FONT/modules/frames_dataset.py new file mode 100644 index 0000000000000000000000000000000000000000..3dac34aa7a48422a5c241d8708893fabe69d17de --- /dev/null +++ b/FONT/modules/frames_dataset.py @@ -0,0 +1,451 @@ +import os +from skimage import io, img_as_float32, transform +from skimage.color import gray2rgb +from sklearn.model_selection import train_test_split +from imageio import mimread + +import numpy as np +from torch.utils.data import Dataset +import pandas as pd +from augmentation import AllAugmentationTransform +import glob +import pickle +import random +def read_video(name, frame_shape): + """ + Read video which can be: + - an image of concatenated frames + - '.mp4' and'.gif' + - folder with videos + """ + + if os.path.isdir(name): + frames = sorted(os.listdir(name)) + num_frames = len(frames) + video_array = np.array( + [img_as_float32(io.imread(os.path.join(name, frames[idx]))) for idx in range(num_frames)]) + elif name.lower().endswith('.png') or name.lower().endswith('.jpg'): + image = io.imread(name) + + if len(image.shape) == 2 or image.shape[2] == 1: + image = gray2rgb(image) + + if image.shape[2] == 4: + image = image[..., :3] + + image = img_as_float32(image) + + video_array = np.moveaxis(image, 1, 0) + + video_array = video_array.reshape((-1,) + frame_shape) + video_array = np.moveaxis(video_array, 1, 2) + elif name.lower().endswith('.gif') or name.lower().endswith('.mp4') or name.lower().endswith('.mov'): + video = np.array(mimread(name)) + if len(video.shape) == 3: + video = np.array([gray2rgb(frame) for frame in video]) + if video.shape[-1] == 4: + video = video[..., :3] + video_array = img_as_float32(video) + else: + raise Exception("Unknown file extensions %s" % name) + + return video_array + +def get_list(ipath,base_name): +#ipath = '/mnt/lustre/share/jixinya/LRW/pose/train_fo/' + ipath = os.path.join(ipath,base_name) + name_list = os.listdir(ipath) + image_path = os.path.join('/mnt/lustre/share/jixinya/LRW/Image/',base_name) + all = [] + for k in range(len(name_list)): + name = name_list[k] + path_ = os.path.join(ipath,name) + Dir = os.listdir(path_) + for i in range(len(Dir)): + word = Dir[i] + path = os.path.join(path_, word) + if os.path.exists(os.path.join(image_path,name,word.split('.')[0])): + all.append(name+'/'+word.split('.')[0]) + #print(k,name,i,word) + print('get list '+os.path.basename(ipath)) + return all + + +class AudioDataset(Dataset): + """ + Dataset of videos, each video can be represented as: + - an image of concatenated frames + - '.mp4' or '.gif' + - folder with all frames + """ + + def __init__(self, root_dir, frame_shape=(256, 256, 3), id_sampling=False, is_train=True, + random_seed=0, pairs_list=None, augmentation_params=None): + self.root_dir = root_dir + self.audio_dir = os.path.join(root_dir,'MFCC') + self.image_dir = os.path.join(root_dir,'Image') + self.landmark_dir = os.path.join(root_dir,'Landmark') + self.pose_dir = os.path.join(root_dir,'pose') + # assert len(os.listdir(self.audio_dir)) == len(os.listdir(self.image_dir)), 'audio and image length not equal' + + + df=open('../LRW/list/test_fo.txt','rb') + self.videos=pickle.load(df) + df.close() + # self.videos=np.load('../LRW/list/train_fo.npy') + # self.videos = os.listdir(self.landmark_dir) + self.frame_shape = tuple(frame_shape) + self.pairs_list = pairs_list + self.id_sampling = id_sampling + self.pca = np.load('../LRW/list/U_106.npy')[:, :16] + self.mean = np.load('../LRW/list/mean_106.npy') + + if os.path.exists(os.path.join(self.pose_dir, 'train_fo')): + assert os.path.exists(os.path.join(self.pose_dir, 'test_fo')) + print("Use predefined train-test split.") + if id_sampling: + train_videos = {os.path.basename(video).split('#')[0] for video in + os.listdir(os.path.join(self.image_dir, 'train'))} + train_videos = list(train_videos) + else: + train_videos = np.load('../LRW/list/train_fo.npy')# get_list(self.pose_dir, 'train_fo') + df=open('../LRW/list/test_fo.txt','rb') + test_videos=pickle.load(df) + df.close() + # test_videos = np.load('../LRW/list/train_fo.npy') + #get_list(self.pose_dir, 'test_fo') + # self.root_dir = os.path.join(self.root_dir, 'train' if is_train else 'test') + self.landmark_dir = os.path.join(self.landmark_dir, 'train_fo' if is_train else 'test_fo') + self.image_dir = os.path.join(self.image_dir, 'train_fo' if is_train else 'test_fo') + self.audio_dir = os.path.join(self.audio_dir, 'train' if is_train else 'test') + self.pose_dir = os.path.join(self.pose_dir, 'train_fo' if is_train else 'test_fo') + else: + print("Use random train-test split.") + train_videos, test_videos = train_test_split(self.videos, random_state=random_seed, test_size=0.2) + + if is_train: + self.videos = train_videos + else: + self.videos = test_videos + + self.is_train = is_train + + if self.is_train: + self.transform = AllAugmentationTransform(**augmentation_params) + else: + self.transform = None + + def __len__(self): + return len(self.videos) + + def __getitem__(self, idx): + if self.is_train and self.id_sampling: + name = self.videos[idx].split('.')[0] + path = np.random.choice(glob.glob(os.path.join(self.root_dir, name + '*.mp4'))) + else: + name = self.videos[idx].split('.')[0] + landmark_path = os.path.join(self.landmark_dir, name+'.npy') + + audio_path = os.path.join(self.audio_dir, name) + pose_path = os.path.join(self.pose_dir,name) + path = os.path.join(self.image_dir, name) + + video_name = os.path.basename(path) + + if os.path.isdir(path): + # if self.is_train and os.path.isdir(path): + + lmark = np.load(landmark_path).reshape(-1,212)/255 + if np.isnan(lmark).sum() or np.isinf(lmark).sum(): + print('Wrong lmark '+ video_name, file=open('log/wrong.txt', 'a')) + lmark = np.zeros((29,212)) + lmark = lmark - self.mean + lmark = np.dot(lmark, self.pca) + + # mfcc loading + + r = random.choice([x for x in range(3, 8)]) + example_landmark = lmark[r, :] + example_image = img_as_float32(io.imread(os.path.join(path, str(r)+'.png'))) + # example_mfcc = mfcc[(r - 3) * 4: (r + 4) * 4, 1:] + + mfccs = [] + for ind in range(1, 17): + # t_mfcc = mfcc[(r + ind - 3) * 4: (r + ind + 4) * 4, 1:] + try: + t_mfcc = np.load(os.path.join(audio_path,str(r + ind)+'.npy'),allow_pickle=True)[:, 1:] + if np.isnan(t_mfcc).sum() or np.isinf(t_mfcc).sum(): + print('Wrong mfcc '+ video_name+str(r+ind), file=open('log/wrong.txt', 'a')) + t_mfcc = np.zeros((28,13))[:,1:] + except: + t_mfcc = np.zeros((28,13))[:,1:] + mfccs.append(t_mfcc) + mfccs = np.array(mfccs) + if not self.is_train: + poses = [] + video_array = [] + for ind in range(1, 17): + # t_mfcc = mfcc[(r + ind - 3) * 4: (r + ind + 4) * 4, 1:] + t_pose = np.load(os.path.join(pose_path,str(r + ind)+'.npy'))[:-1] + poses.append(t_pose) + image = img_as_float32(io.imread(os.path.join(path, str(r + ind)+'.png'))) + video_array.append(image) + poses = np.array(poses) + video_array = np.array(video_array) + else: + poses = [] + video_array = [] + for ind in range(1, 17): + # t_mfcc = mfcc[(r + ind - 3) * 4: (r + ind + 4) * 4, 1:] + t_pose = np.load(os.path.join(self.pose_dir,name+'.npy'))[r+ind,:-1] + if np.isnan(t_pose).sum() or np.isinf(t_pose).sum(): + print('Wrong pose '+ video_name, file=open('log/wrong.txt', 'a')) + t_pose = np.zeros((6,)) + poses.append(t_pose) + image = img_as_float32(io.imread(os.path.join(path, str(r + ind)+'.png'))) + video_array.append(image) + poses = np.array(poses) + video_array = np.array(video_array) + + #mfccs = torch.FloatTensor(mfccs) + landmark = lmark[r + 1: r + 17, :] + index_32 = [0,4,8,12,16,20,24,28,32,33,35,67,68,40,42,52,55,72,73,58,61,75,76,46,47,51,84,87,90,93,98,102] + driving_landmark = np.load(landmark_path)[r + 1: r + 17, :][:,index_32] + source_landmark = np.load(landmark_path)[r, :][index_32] + else: + video_array = read_video(path, frame_shape=self.frame_shape) + num_frames = len(video_array) + frame_idx = np.sort(np.random.choice(num_frames, replace=True, size=2)) if self.is_train else range( + num_frames) + video_array = video_array[frame_idx] + + if self.transform is not None: + video_array = self.transform(video_array) + + out = {} + if True:#self.is_train: + # a = img_as_float32(io.imread('/media/thea/Data/first-order-model/images_512/102.jpg')) + # source = np.array(a, dtype='float32') + + driving = np.array(video_array, dtype='float32') + + spatial_size = np.array(driving.shape[1:3][::-1])[np.newaxis] + # example_landmark = np.array(2*example_landmark / spatial_size -1, dtype='float32') + driving_landmark = np.array(2*driving_landmark / spatial_size -1, dtype='float32') + source_landmark = np.array(2*source_landmark / spatial_size -1, dtype='float32') + driving_pose = np.array(poses, dtype='float32') + example_landmark = np.array(example_landmark, dtype='float32') + example_image = np.array(example_image, dtype='float32') + # source_cube = np.array(transform.resize(cube_array[0], (64,64)), dtype='float32') + # driving_cube = np.array(transform.resize(cube_array[1], (64,64)), dtype='float32') + # source_heatmap = np.array(heatmap_array[0] , dtype='float32') + # driving_heatmap = np.array(heatmap_array[1] , dtype='float32') + # out['source_cube'] = source_cube + # out['driving_cube'] = driving_cube + out['example_landmark'] = example_landmark + out['example_image'] = example_image.transpose((2, 0, 1)) + out['driving_landmark'] = driving_landmark + out['source_landmark'] = source_landmark + out['driving_pose'] = driving_pose + # out['source_heatmap'] = source_heatmap + # out['driving_heatmap'] = driving_heatmap + out['driving'] = driving.transpose((0, 3, 1, 2)) + # out['source'] = source.transpose((2, 0, 1)) + + # out['source_audio'] = np.array(audio_array[0], dtype='float32') + out['driving_audio'] = np.array(mfccs, dtype='float32') + out['gt_landmark'] = np.array(landmark, dtype='float32') + out['pca'] = np.array(self.pca, dtype='float32') + out['mean'] = np.array(self.mean, dtype='float32') + + + out['name'] = video_name + + return out + +class FramesDataset(Dataset): + """ + Dataset of videos, each video can be represented as: + - an image of concatenated frames + - '.mp4' or '.gif' + - folder with all frames + """ + + def __init__(self, root_dir, frame_shape=(256, 256, 3), id_sampling=False, is_train=True, + random_seed=0, pairs_list=None, augmentation_params=None): + self.root_dir = root_dir + self.audio_dir = os.path.join(root_dir,'audio/') + self.image_dir = os.path.join(root_dir,'image/') + self.landmark_dir = os.path.join(root_dir,'cube/') + # assert len(os.listdir(self.audio_dir)) == len(os.listdir(self.image_dir)), 'audio and image length not equal' + + + df=open('/media/thea/新加卷/MEAD/neutral/train.txt','rb') + self.videos=pickle.load(df) + df.close() + # self.videos = os.listdir(self.landmark_dir) + self.frame_shape = tuple(frame_shape) + self.pairs_list = pairs_list + self.id_sampling = id_sampling + if os.path.exists(os.path.join(self.image_dir, 'train')): + assert os.path.exists(os.path.join(self.image_dir, 'test')) + print("Use predefined train-test split.") + if id_sampling: + train_videos = {os.path.basename(video).split('#')[0] for video in + os.listdir(os.path.join(self.image_dir, 'train'))} + train_videos = list(train_videos) + else: + train_videos = os.listdir(os.path.join(self.image_dir, 'train')) + test_videos = os.listdir(os.path.join(self.image_dir, 'test')) + self.root_dir = os.path.join(self.root_dir, 'train' if is_train else 'test') + self.landmark_dir = os.path.join(self.landmark_dir, 'train' if is_train else 'test') + self.image_dir = os.path.join(self.image_dir, 'train' if is_train else 'test') + self.audio_dir = os.path.join(self.audio_dir, 'train' if is_train else 'test') + + else: + print("Use random train-test split.") + train_videos, test_videos = train_test_split(self.videos, random_state=random_seed, test_size=0.2) + + if is_train: + self.videos = train_videos + else: + self.videos = test_videos + + self.is_train = is_train + + if self.is_train: + self.transform = AllAugmentationTransform(**augmentation_params) + else: + self.transform = None + + def __len__(self): + return len(self.videos) + + def __getitem__(self, idx): + if self.is_train and self.id_sampling: + name = self.videos[idx].split('.')[0] + path = np.random.choice(glob.glob(os.path.join(self.root_dir, name + '*.mp4'))) + else: + name = self.videos[idx].split('.')[0] + landmark_path = os.path.join(self.landmark_dir, name) + + audio_path = os.path.join(self.audio_dir, name) + path = os.path.join(self.image_dir, name) + + video_name = os.path.basename(path) + + if self.is_train and os.path.isdir(path): + frames = os.listdir(audio_path) + num_frames = len(frames) + frame_idx = np.sort(np.random.choice(num_frames-1, replace=True, size=2)) + # landmark = np.load(landmark_path)#+'.npy' + # assert len(os.listdir(path)) == len(landmark), video_name+' length not equal' + video_array = [img_as_float32(io.imread(os.path.join(path, str(idx)+'.png'))) for idx in frame_idx] + cube_array = [img_as_float32(io.imread(os.path.join(landmark_path, str(idx)+'.jpg'))) for idx in frame_idx] + audio_array = [np.load(os.path.join(audio_path, str(idx)+'.npy'))[:,1:] for idx in frame_idx] + index_20 = [0,16,32,35,40,52,55,58,61,46,72,73,75,76,84,87,90,93,98,102] + index_32 = [0,4,8,12,16,20,24,28,32,33,35,67,68,40,42,52,55,72,73,58,61,75,76,46,47,51,84,87,90,93,98,102] + # landmark_array = [landmark[idx] for idx in frame_idx] + # landmark_array = [landmark[idx][index_32] for idx in frame_idx] + else: + video_array = read_video(path, frame_shape=self.frame_shape) + num_frames = len(video_array) + frame_idx = np.sort(np.random.choice(num_frames, replace=True, size=2)) if self.is_train else range( + num_frames) + video_array = video_array[frame_idx] + + if self.transform is not None: + video_array = self.transform(video_array) + + out = {} + if self.is_train: + # a = img_as_float32(io.imread('/media/thea/Data/first-order-model/images_512/102.jpg')) + # source = np.array(a, dtype='float32') + source = np.array(video_array[0], dtype='float32') + driving = np.array(video_array[1], dtype='float32') + + spatial_size = np.array(source.shape[:2][::-1])[np.newaxis] + # source_landmark = np.array(2*landmark_array[0] / spatial_size -1, dtype='float32') + # driving_landmark = np.array(2*landmark_array[1] / spatial_size -1, dtype='float32') + source_cube = np.array(transform.resize(cube_array[0], (64,64)), dtype='float32') + driving_cube = np.array(transform.resize(cube_array[1], (64,64)), dtype='float32') + # source_heatmap = np.array(heatmap_array[0] , dtype='float32') + # driving_heatmap = np.array(heatmap_array[1] , dtype='float32') + out['source_cube'] = source_cube + out['driving_cube'] = driving_cube + # out['source_landmark'] = source_landmark + # out['driving_landmark'] = driving_landmark + # out['source_heatmap'] = source_heatmap + # out['driving_heatmap'] = driving_heatmap + out['driving'] = driving.transpose((2, 0, 1)) + out['source'] = source.transpose((2, 0, 1)) + + out['source_audio'] = np.array(audio_array[0], dtype='float32') + out['driving_audio'] = np.array(audio_array[1], dtype='float32') + + else: + video = np.array(video_array, dtype='float32') + out['video'] = video.transpose((3, 0, 1, 2)) + + out['name'] = video_name + + return out + + +class DatasetRepeater(Dataset): + """ + Pass several times over the same dataset for better i/o performance + """ + + def __init__(self, dataset, num_repeats=100): + self.dataset = dataset + self.num_repeats = num_repeats + + def __len__(self): + return self.num_repeats * self.dataset.__len__() + + def __getitem__(self, idx): + return self.dataset[idx % self.dataset.__len__()]#% self.dataset.__len__() + + +class PairedDataset(Dataset): + """ + Dataset of pairs for animation. + """ + + def __init__(self, initial_dataset, number_of_pairs, seed=0): + self.initial_dataset = initial_dataset + pairs_list = self.initial_dataset.pairs_list + + np.random.seed(seed) + + if pairs_list is None: + max_idx = min(number_of_pairs, len(initial_dataset)) + nx, ny = max_idx, max_idx + xy = np.mgrid[:nx, :ny].reshape(2, -1).T + number_of_pairs = min(xy.shape[0], number_of_pairs) + self.pairs = xy.take(np.random.choice(xy.shape[0], number_of_pairs, replace=False), axis=0) + else: + videos = self.initial_dataset.videos + name_to_index = {name: index for index, name in enumerate(videos)} + pairs = pd.read_csv(pairs_list) + pairs = pairs[np.logical_and(pairs['source'].isin(videos), pairs['driving'].isin(videos))] + + number_of_pairs = min(pairs.shape[0], number_of_pairs) + self.pairs = [] + self.start_frames = [] + for ind in range(number_of_pairs): + self.pairs.append( + (name_to_index[pairs['driving'].iloc[ind]], name_to_index[pairs['source'].iloc[ind]])) + + def __len__(self): + return len(self.pairs) + + def __getitem__(self, idx): + pair = self.pairs[idx] + first = self.initial_dataset[pair[0]] + second = self.initial_dataset[pair[1]] + first = {'driving_' + key: value for key, value in first.items()} + second = {'source_' + key: value for key, value in second.items()} + + return {**first, **second} diff --git a/FONT/modules/function.py b/FONT/modules/function.py new file mode 100644 index 0000000000000000000000000000000000000000..d7ce0f4c6d21660bc22e318687015cfd26c36be5 --- /dev/null +++ b/FONT/modules/function.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Thu Sep 30 17:45:24 2021 + +@author: SENSETIME\jixinya1 +""" + +import torch + + +def calc_mean_std(feat, eps=1e-5): + # eps is a small value added to the variance to avoid divide-by-zero. + size = feat.size() + assert (len(size) == 4) + N, C = size[:2] + feat_var = feat.view(N, C, -1).var(dim=2) + eps + feat_std = feat_var.sqrt().view(N, C, 1, 1) + feat_mean = feat.view(N, C, -1).mean(dim=2).view(N, C, 1, 1) + return feat_mean, feat_std + + +def adaptive_instance_normalization(content_feat, style_feat): + assert (content_feat.size()[:2] == style_feat.size()[:2]) + size = content_feat.size() + style_mean, style_std = calc_mean_std(style_feat) + content_mean, content_std = calc_mean_std(content_feat) + + normalized_feat = (content_feat - content_mean.expand( + size)) / content_std.expand(size) + return normalized_feat * style_std.expand(size) + style_mean.expand(size) + + +def _calc_feat_flatten_mean_std(feat): + # takes 3D feat (C, H, W), return mean and std of array within channels + assert (feat.size()[0] == 3) + assert (isinstance(feat, torch.FloatTensor)) + feat_flatten = feat.view(3, -1) + mean = feat_flatten.mean(dim=-1, keepdim=True) + std = feat_flatten.std(dim=-1, keepdim=True) + return feat_flatten, mean, std + + +def _mat_sqrt(x): + U, D, V = torch.svd(x) + return torch.mm(torch.mm(U, D.pow(0.5).diag()), V.t()) + + +def coral(source, target): + # assume both source and target are 3D array (C, H, W) + # Note: flatten -> f + + source_f, source_f_mean, source_f_std = _calc_feat_flatten_mean_std(source) + source_f_norm = (source_f - source_f_mean.expand_as( + source_f)) / source_f_std.expand_as(source_f) + source_f_cov_eye = \ + torch.mm(source_f_norm, source_f_norm.t()) + torch.eye(3) + + target_f, target_f_mean, target_f_std = _calc_feat_flatten_mean_std(target) + target_f_norm = (target_f - target_f_mean.expand_as( + target_f)) / target_f_std.expand_as(target_f) + target_f_cov_eye = \ + torch.mm(target_f_norm, target_f_norm.t()) + torch.eye(3) + + source_f_norm_transfer = torch.mm( + _mat_sqrt(target_f_cov_eye), + torch.mm(torch.inverse(_mat_sqrt(source_f_cov_eye)), + source_f_norm) + ) + + source_f_transfer = source_f_norm_transfer * \ + target_f_std.expand_as(source_f_norm) + \ + target_f_mean.expand_as(source_f_norm) + + return source_f_transfer.view(source.size()) \ No newline at end of file diff --git a/FONT/modules/generator.py b/FONT/modules/generator.py new file mode 100644 index 0000000000000000000000000000000000000000..a31155d685013ac24ef5fa0e12569b46c9c74ae0 --- /dev/null +++ b/FONT/modules/generator.py @@ -0,0 +1,97 @@ +import torch +from torch import nn +import torch.nn.functional as F +from .util import ResBlock2d, SameBlock2d, UpBlock2d, DownBlock2d +from .dense_motion import DenseMotionNetwork + + +class OcclusionAwareGenerator(nn.Module): + """ + Generator that given source image and and keypoints try to transform image according to movement trajectories + induced by keypoints. Generator follows Johnson architecture. + """ + + def __init__(self, num_channels, num_kp, block_expansion, max_features, num_down_blocks, + num_bottleneck_blocks, estimate_occlusion_map=False, dense_motion_params=None, estimate_jacobian=False): + super(OcclusionAwareGenerator, self).__init__() + + if dense_motion_params is not None: + self.dense_motion_network = DenseMotionNetwork(num_kp=num_kp, num_channels=num_channels, + estimate_occlusion_map=estimate_occlusion_map, + **dense_motion_params) + else: + self.dense_motion_network = None + + self.first = SameBlock2d(num_channels, block_expansion, kernel_size=(7, 7), padding=(3, 3)) + + down_blocks = [] + for i in range(num_down_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))) + self.down_blocks = nn.ModuleList(down_blocks) + + up_blocks = [] + for i in range(num_down_blocks): + in_features = min(max_features, block_expansion * (2 ** (num_down_blocks - i))) + out_features = min(max_features, block_expansion * (2 ** (num_down_blocks - i - 1))) + up_blocks.append(UpBlock2d(in_features, out_features, kernel_size=(3, 3), padding=(1, 1))) + self.up_blocks = nn.ModuleList(up_blocks) + + self.bottleneck = torch.nn.Sequential() + in_features = min(max_features, block_expansion * (2 ** num_down_blocks)) + for i in range(num_bottleneck_blocks): + self.bottleneck.add_module('r' + str(i), ResBlock2d(in_features, kernel_size=(3, 3), padding=(1, 1))) + + self.final = nn.Conv2d(block_expansion, num_channels, kernel_size=(7, 7), padding=(3, 3)) + self.estimate_occlusion_map = estimate_occlusion_map + self.num_channels = num_channels + + def deform_input(self, inp, deformation): + _, h_old, w_old, _ = deformation.shape + _, _, h, w = inp.shape + if h_old != h or w_old != w: + deformation = deformation.permute(0, 3, 1, 2) + deformation = F.interpolate(deformation, size=(h, w), mode='bilinear') + deformation = deformation.permute(0, 2, 3, 1) + return F.grid_sample(inp, deformation) + + def forward(self, source_image, kp_driving, kp_source): + # Encoding (downsampling) part + out = self.first(source_image) #[4,64,H,W] + for i in range(len(self.down_blocks)): + out = self.down_blocks[i](out) #[4,256,H/4,W/4] + + # Transforming feature representation according to deformation and occlusion + output_dict = {} + if self.dense_motion_network is not None: + dense_motion = self.dense_motion_network(source_image=source_image, kp_driving=kp_driving, + kp_source=kp_source) + output_dict['mask'] = dense_motion['mask'] + output_dict['sparse_deformed'] = dense_motion['sparse_deformed'] + + if 'occlusion_map' in dense_motion: + occlusion_map = dense_motion['occlusion_map'] + output_dict['occlusion_map'] = occlusion_map + else: + occlusion_map = None + deformation = dense_motion['deformation'] + out = self.deform_input(out, deformation) + + if occlusion_map is not None: + if out.shape[2] != occlusion_map.shape[2] or out.shape[3] != occlusion_map.shape[3]: + occlusion_map = F.interpolate(occlusion_map, size=out.shape[2:], mode='bilinear') + out = out * occlusion_map + + output_dict["deformed"] = self.deform_input(source_image, deformation) + + # Decoding part + out = self.bottleneck(out) #[4,256,64,64] + for i in range(len(self.up_blocks)): + out = self.up_blocks[i](out) + out = self.final(out) + out = torch.sigmoid(out) #[4,3,256,256] + + output_dict["prediction"] = out + + return output_dict diff --git a/FONT/modules/keypoint_detector.py b/FONT/modules/keypoint_detector.py new file mode 100644 index 0000000000000000000000000000000000000000..c92b0bb77bd08b6612ff11c8820afe5e5ee115eb --- /dev/null +++ b/FONT/modules/keypoint_detector.py @@ -0,0 +1,260 @@ +from torch import nn +import torch +import torch.nn.functional as F +from .util import Hourglass, make_coordinate_grid, AntiAliasInterpolation2d, Ct_encoder, EmotionNet, AF2F, AF2F_s, draw_heatmap + + +class KPDetector(nn.Module): + """ + Detecting a keypoints. Return keypoint position and jacobian near each keypoint. + """ + + def __init__(self, block_expansion, num_kp, num_channels, max_features, + num_blocks, temperature, estimate_jacobian=False, scale_factor=1, + single_jacobian_map=False, pad=0): + 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=pad) + + if estimate_jacobian: + self.num_jacobian_maps = 1 if single_jacobian_map else num_kp + self.jacobian = nn.Conv2d(in_channels=self.predictor.out_filters, + out_channels=4 * self.num_jacobian_maps, kernel_size=(7, 7), padding=pad) + self.jacobian.weight.data.zero_() + self.jacobian.bias.data.copy_(torch.tensor([1, 0, 0, 1] * self.num_jacobian_maps, dtype=torch.float)) + else: + self.jacobian = None + + self.temperature = temperature + self.scale_factor = scale_factor + if self.scale_factor != 1: + self.down = AntiAliasInterpolation2d(num_channels, self.scale_factor) + + + + + def gaussian2kp(self, heatmap): + """ + Extract the mean and from a heatmap + """ + shape = heatmap.shape + heatmap = heatmap.unsqueeze(-1) #[4,10,58,58,1] + grid = make_coordinate_grid(shape[2:], heatmap.type()).unsqueeze_(0).unsqueeze_(0) #[1,1,58,58,2] + value = (heatmap * grid).sum(dim=(2, 3)) #[4,10,2] + kp = {'value': value} + + return kp + + def audio_feature(self, x, heatmap): + + # prediction = self.kp(x) #[4,10,H/4-6, W/4-6] + + # final_shape = prediction.shape + # heatmap = prediction.view(final_shape[0], final_shape[1], -1) #[4, 10, 58*58] + # heatmap = F.softmax(heatmap / self.temperature, dim=2) + # heatmap = heatmap.view(*final_shape) #[4,10,58,58] + + # out = self.gaussian2kp(heatmap) + final_shape = heatmap.squeeze(2).shape + + if self.jacobian is not None: + jacobian_map = self.jacobian(x) ##[4,40,H/4-6, W/4-6] + 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 #[4,10,4,H/4-6, W/4-6] + jacobian = jacobian.view(final_shape[0], final_shape[1], 4, -1) + jacobian = jacobian.sum(dim=-1) #[4,10,4] + jacobian = jacobian.view(jacobian.shape[0], jacobian.shape[1], 2, 2) #[4,10,2,2] + + return jacobian + + def forward(self, x): #torch.Size([4, 3, H, W]) + if self.scale_factor != 1: + x = self.down(x) # 0.25 [4, 3, H/4, W/4] + + feature_map = self.predictor(x) #[4,3+32,H/4, W/4] + prediction = self.kp(feature_map) #[4,10,H/4-6, W/4-6] + + final_shape = prediction.shape + + heatmap = prediction.view(final_shape[0], final_shape[1], -1) #[4, 10, 58*58] + heatmap = F.softmax(heatmap / self.temperature, dim=2) + heatmap = heatmap.view(*final_shape) #[4,10,58,58] + + out = self.gaussian2kp(heatmap) + out['heatmap'] = heatmap + + if self.jacobian is not None: + jacobian_map = self.jacobian(feature_map) ##[4,40,H/4-6, W/4-6] + 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 #[4,10,4,H/4-6, W/4-6] + jacobian = jacobian.view(final_shape[0], final_shape[1], 4, -1) + jacobian = jacobian.sum(dim=-1) #[4,10,4] + jacobian = jacobian.view(jacobian.shape[0], jacobian.shape[1], 2, 2) #[4,10,2,2] + out['jacobian'] = jacobian + + return out + + + + +class KPDetector_a(nn.Module): + """ + Detecting a keypoints. Return keypoint position and jacobian near each keypoint. + """ + + def __init__(self, block_expansion, num_kp, num_channels,num_channels_a, max_features, + num_blocks, temperature, estimate_jacobian=False, scale_factor=1, + single_jacobian_map=False, pad=0): + super(KPDetector_a, self).__init__() + + self.predictor = Hourglass(block_expansion, in_features=num_channels_a, + 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=pad) + + if estimate_jacobian: + self.num_jacobian_maps = 1 if single_jacobian_map else num_kp + self.jacobian = nn.Conv2d(in_channels=self.predictor.out_filters, + out_channels=4 * self.num_jacobian_maps, kernel_size=(7, 7), padding=pad) + self.jacobian.weight.data.zero_() + self.jacobian.bias.data.copy_(torch.tensor([1, 0, 0, 1] * self.num_jacobian_maps, dtype=torch.float)) + else: + self.jacobian = None + + self.temperature = temperature + self.scale_factor = scale_factor + if self.scale_factor != 1: + self.down = AntiAliasInterpolation2d(num_channels, self.scale_factor) + + + + + def gaussian2kp(self, heatmap): + """ + Extract the mean and from a heatmap + """ + shape = heatmap.shape + heatmap = heatmap.unsqueeze(-1) #[4,10,58,58,1] + grid = make_coordinate_grid(shape[2:], heatmap.type()).unsqueeze_(0).unsqueeze_(0) #[1,1,58,58,2] + value = (heatmap * grid).sum(dim=(2, 3)) #[4,10,2] + kp = {'value': value} + + return kp + + def audio_feature(self, x, heatmap): + + # prediction = self.kp(x) #[4,10,H/4-6, W/4-6] + + # final_shape = prediction.shape + # heatmap = prediction.view(final_shape[0], final_shape[1], -1) #[4, 10, 58*58] + # heatmap = F.softmax(heatmap / self.temperature, dim=2) + # heatmap = heatmap.view(*final_shape) #[4,10,58,58] + + # out = self.gaussian2kp(heatmap) + final_shape = heatmap.squeeze(2).shape + + if self.jacobian is not None: + jacobian_map = self.jacobian(x) ##[4,40,H/4-6, W/4-6] + 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 #[4,10,4,H/4-6, W/4-6] + jacobian = jacobian.view(final_shape[0], final_shape[1], 4, -1) + jacobian = jacobian.sum(dim=-1) #[4,10,4] + jacobian = jacobian.view(jacobian.shape[0], jacobian.shape[1], 2, 2) #[4,10,2,2] + + return jacobian + + def forward(self, feature_map): #torch.Size([4, 3, H, W]) + + + prediction = self.kp(feature_map) #[4,10,H/4-6, W/4-6] + + final_shape = prediction.shape + + heatmap = prediction.view(final_shape[0], final_shape[1], -1) #[4, 10, 58*58] + heatmap = F.softmax(heatmap / self.temperature, dim=2) + heatmap = heatmap.view(*final_shape) #[4,10,58,58] + + out = self.gaussian2kp(heatmap) + out['heatmap'] = heatmap #B,10,58,58 + + if self.jacobian is not None: + jacobian_map = self.jacobian(feature_map) ##[4,40,H/4-6, W/4-6] + 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 #[4,10,4,H/4-6, W/4-6] + jacobian = jacobian.view(final_shape[0], final_shape[1], 4, -1) + jacobian = jacobian.sum(dim=-1) #[4,10,4] + jacobian = jacobian.view(jacobian.shape[0], jacobian.shape[1], 2, 2) #[4,10,2,2] + out['jacobian'] = jacobian #B,10,2,2 + + return out + + +class Audio_Feature(nn.Module): + def __init__(self): + super(Audio_Feature, self).__init__() + + self.con_encoder = Ct_encoder() + self.emo_encoder = EmotionNet() + self.decoder = AF2F_s() + + + + def forward(self, x): + x = x.unsqueeze(1) + + c = self.con_encoder(x) + e = self.emo_encoder(x) + + # d = torch.cat([c, e], dim=1) + d = self.decoder(c) + + + return d +''' +def forward(self, x, cube, audio): #torch.Size([4, 3, H, W]) + if self.scale_factor != 1: + x = self.down(x) # 0.25 [4, 3, H/4, W/4] + + cube = cube.unsqueeze(1) + feature = torch.cat([x,cube,audio],dim=1) + feature_map = self.predictor(feature) #[4,3+32,H/4, W/4] + prediction = self.kp(feature_map) #[4,10,H/4-6, W/4-6] + + final_shape = prediction.shape + heatmap = prediction.view(final_shape[0], final_shape[1], -1) #[4, 10, 58*58] + heatmap = F.softmax(heatmap / self.temperature, dim=2) + heatmap = heatmap.view(*final_shape) #[4,10,58,58] + + out = self.gaussian2kp(heatmap) + out['heatmap'] = heatmap + if self.jacobian is not None: + jacobian_map = self.jacobian(feature_map) ##[4,40,H/4-6, W/4-6] + 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 #[4,10,4,H/4-6, W/4-6] + jacobian = jacobian.view(final_shape[0], final_shape[1], 4, -1) + jacobian = jacobian.sum(dim=-1) #[4,10,4] + jacobian = jacobian.view(jacobian.shape[0], jacobian.shape[1], 2, 2) #[4,10,2,2] + out['jacobian'] = jacobian + + return out +''' diff --git a/FONT/modules/model.py b/FONT/modules/model.py new file mode 100644 index 0000000000000000000000000000000000000000..4d3bfcdcf9dd3db4ebcad575a971547a41560696 --- /dev/null +++ b/FONT/modules/model.py @@ -0,0 +1,597 @@ +from torch import nn +import torch +import torch.nn.functional as F +from modules.util import AntiAliasInterpolation2d, make_coordinate_grid +from torchvision import models +import numpy as np +from torch.autograd import grad + + +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 - 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): + 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 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) #[1,256,256,2] + 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 inverse_transform_frame(self, frame): + grid = make_coordinate_grid(frame.shape[2:], type=frame.type()).unsqueeze(0) #[1,256,256,2] + grid = grid.view(1, frame.shape[2] * frame.shape[3], 2) + grid = self.inverse_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) + 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()) + 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 inverse_warp_coordinates(self, coordinates): + theta = self.theta.type(coordinates.type()) + theta = theta.unsqueeze(1) + a = torch.FloatTensor([[[[0,0,1]]]]).repeat([self.bs,1,1,1]).cuda() + c = torch.cat((theta,a),2) + d = c.inverse()[:,:,:2,:] + d = d.type(coordinates.type()) + transformed = torch.matmul(d[:, :, :, :2], coordinates.unsqueeze(-1)) + d[:, :, :, 2:] + transformed = transformed.squeeze(-1) + + if self.tps: + control_points = self.control_points.type(coordinates.type()) + control_params = self.control_params.type(coordinates.type()) + 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): + coordinates.requires_grad=True + new_coordinates = self.warp_coordinates(coordinates)#[4,10,2] + 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 + + +def detach_kp(kp): + return {key: value.detach() for key, value in kp.items()} + +class TrainPart1Model(torch.nn.Module): + """ + Merge all generator related updates into single model for better multi-gpu usage + """ + + def __init__(self, kp_extractor, kp_extractor_a, audio_feature, generator, discriminator, train_params, device_ids): + super(TrainPart1Model, self).__init__() + self.kp_extractor = kp_extractor + self.kp_extractor_a = kp_extractor_a + + self.audio_feature = audio_feature + self.generator = generator + self.discriminator = discriminator + self.train_params = train_params + self.scales = train_params['scales'] + self.disc_scales = self.discriminator.scales + self.pyramid = ImagePyramide(self.scales, generator.num_channels) + if torch.cuda.is_available(): + self.pyramid = self.pyramid.cuda() + + self.loss_weights = train_params['loss_weights'] + + if sum(self.loss_weights['perceptual']) != 0: + self.vgg = Vgg19() + if torch.cuda.is_available(): + self.vgg = self.vgg.cuda() + + + self.mse_loss_fn = nn.MSELoss().cuda() + def forward(self, x): + + kp_source = self.kp_extractor(x['example_image']) + + + kp_driving = [] + for i in range(16): + kp_driving.append(self.kp_extractor(x['driving'][:,i])) + + + + kp_driving_a = [] #x['example_image'], + + + # print(x['example_image'].shape, x['driving_audio'].shape, x['driving_pose'].shape) + deco_out = self.audio_feature(x['example_image'], x['driving_audio'], x['driving_pose'], self.train_params['jaco_net']) + loss_values = {} + + if self.loss_weights['audio'] != 0: + + kp_driving_a = [] + for i in range(16): + kp_driving_a.append(self.kp_extractor_a(deco_out[:,i]))# + + + loss_value = 0 + loss_heatmap = 0 + loss_jacobian = 0 + loss_perceptual = 0 + for i in range(len(kp_driving)): + loss_jacobian += (torch.abs(kp_driving[i]['jacobian'] - kp_driving_a[i]['jacobian']).mean())*self.loss_weights['audio'] + + # loss_jacobian = loss_jacobian*self.loss_weights['audio'] + loss_heatmap += (torch.abs(kp_driving[i]['heatmap'] - kp_driving_a[i]['heatmap']).mean())*self.loss_weights['audio']*100 + + + loss_value += (torch.abs(kp_driving[i]['value'].detach() - kp_driving_a[i]['value']).mean())*self.loss_weights['audio'] + + loss_values['loss_value'] = loss_value/len(kp_driving) + loss_values['loss_heatmap'] = loss_heatmap/len(kp_driving) + loss_values['loss_jacobian'] = loss_jacobian/len(kp_driving) + + + if self.train_params['generator'] == 'not': + # loss_values['perceptual'] = self.mse_loss_fn(deco_out,deco_out) + for i in range(1): #0,len(kp_driving),4 + + generated = self.generator(x['example_image'], kp_source=kp_source, kp_driving=kp_driving_a[i]) + generated.update({'kp_source': kp_source, 'kp_driving': kp_driving_a}) + + + elif self.train_params['generator'] == 'visual': + for i in range(0,len(kp_driving),4): #0,len(kp_driving),4 + + generated = self.generator(x['example_image'], kp_source=kp_source, kp_driving=kp_driving[i]) + generated.update({'kp_source': kp_source, 'kp_driving': kp_driving}) + + pyramide_real = self.pyramid(x['driving'][:,i]) + pyramide_generated = self.pyramid(generated['prediction']) + + if sum(self.loss_weights['perceptual']) != 0: + 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_perceptual += value_total + + length = int((len(kp_driving)-1)/4)+1 + loss_values['perceptual'] = loss_perceptual/length + elif self.train_params['generator'] == 'audio': + for i in range(0,len(kp_driving),4): #0,len(kp_driving),4 + + generated = self.generator(x['example_image'], kp_source=kp_source, kp_driving=kp_driving_a[i]) + generated.update({'kp_source': kp_source, 'kp_driving': kp_driving_a}) + + pyramide_real = self.pyramid(x['driving'][:,i]) + pyramide_generated = self.pyramid(generated['prediction']) + + if sum(self.loss_weights['perceptual']) != 0: + 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_perceptual += value_total + + length = int((len(kp_driving)-1)/4)+1 + loss_values['perceptual'] = loss_perceptual/length + else: + print('wrong train_params: ', self.train_params['generator']) + + return loss_values,generated + + +class TrainPart2Model(torch.nn.Module): + """ + Merge all generator related updates into single model for better multi-gpu usage + """ + + def __init__(self, kp_extractor, emo_feature, kp_extractor_a, audio_feature, generator, discriminator, train_params, device_ids): + super(TrainPart2Model, self).__init__() + self.kp_extractor = kp_extractor + self.kp_extractor_a = kp_extractor_a + + self.audio_feature = audio_feature + self.emo_feature = emo_feature + self.generator = generator + self.discriminator = discriminator + self.train_params = train_params + self.scales = train_params['scales'] + self.disc_scales = self.discriminator.scales + self.pyramid = ImagePyramide(self.scales, generator.num_channels) + if torch.cuda.is_available(): + self.pyramid = self.pyramid.cuda() + + self.loss_weights = train_params['loss_weights'] + + if sum(self.loss_weights['perceptual']) != 0: + self.vgg = Vgg19() + if torch.cuda.is_available(): + self.vgg = self.vgg.cuda() + + self.mse_loss_fn = nn.MSELoss().cuda() + self.CroEn_loss = nn.CrossEntropyLoss().cuda() + def forward(self, x): + + kp_source = self.kp_extractor(x['example_image']) + + kp_driving = [] + kp_emo = [] + for i in range(16): + kp_driving.append(self.kp_extractor(x['driving'][:,i])) + # kp_emo.append(self.emo_detector(x['driving'][:,i])) + + kp_driving_a = [] #x['example_image'], + deco_out = self.audio_feature(x['example_image'], x['driving_audio'], x['driving_pose'], self.train_params['jaco_net']) + # emo_out = self.emo_feature(x['example_image'], x['driving_audio'], x['driving_pose'], self.train_params['jaco_net']) + loss_values = {} + + if self.loss_weights['emo'] != 0: + + kp_driving_a = [] + fakes = [] + for i in range(16): + kp_driving_a.append(self.kp_extractor_a(deco_out[:,i]))# + value = self.kp_extractor_a(deco_out[:,i])['value'] + jacobian = self.kp_extractor_a(deco_out[:,i])['jacobian'] + if self.train_params['type'] == 'linear_4' : + out, fake = self.emo_feature(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + # kp_emo.append(self.emo_feature(x['transformed_driving'][:,i],value,jacobian)) + elif self.train_params['type'] == 'linear_10': + # kp_emo.append(self.emo_feature.linear_10(x['transformed_driving'][:,i],value,jacobian)) + + out, fake = self.emo_feature.linear_10(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + elif self.train_params['type'] == 'linear_4_new': + # kp_emo.append(self.emo_feature.linear_10(x['transformed_driving'][:,i],value,jacobian)) + + out, fake = self.emo_feature.linear_4(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + elif self.train_params['type'] == 'linear_np_4': + # kp_emo.append(self.emo_feature.linear_10(x['transformed_driving'][:,i],value,jacobian)) + + out, fake = self.emo_feature.linear_np_4(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + elif self.train_params['type'] == 'linear_np_10': + # kp_emo.append(self.emo_feature.linear_10(x['transformed_driving'][:,i],value,jacobian)) + + out, fake = self.emo_feature.linear_np_10(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + + loss_value = 0 + + loss_jacobian = 0 + + loss_classify = 0 + kp_all = kp_driving_a + + for i in range(len(kp_driving)): + + if self.train_params['type'] == 'linear_4' or self.train_params['type'] == 'linear_4_new' or self.train_params['type'] == 'linear_np_4': + loss_jacobian += (torch.abs(kp_driving[i]['jacobian'][:,1] - kp_driving_a[i]['jacobian'][:,1] -kp_emo[i]['jacobian'][:,0]).mean())*self.loss_weights['emo'] + loss_jacobian += (torch.abs(kp_driving[i]['jacobian'][:,4] - kp_driving_a[i]['jacobian'][:,4] -kp_emo[i]['jacobian'][:,1]).mean())*self.loss_weights['emo'] + loss_jacobian += (torch.abs(kp_driving[i]['jacobian'][:,6] - kp_driving_a[i]['jacobian'][:,6] -kp_emo[i]['jacobian'][:,2]).mean())*self.loss_weights['emo'] + loss_jacobian += (torch.abs(kp_driving[i]['jacobian'][:,8] - kp_driving_a[i]['jacobian'][:,8] -kp_emo[i]['jacobian'][:,3]).mean())*self.loss_weights['emo'] + + loss_classify += self.CroEn_loss(fakes[i],x['emotion']) + loss_value += (torch.abs(kp_driving[i]['value'][:,1] .detach() - kp_driving_a[i]['value'][:,1] - kp_emo[i]['value'][:,0] ).mean())*self.loss_weights['emo'] + loss_value += (torch.abs(kp_driving[i]['value'][:,4] .detach() - kp_driving_a[i]['value'][:,4] - kp_emo[i]['value'][:,1] ).mean())*self.loss_weights['emo'] + loss_value += (torch.abs(kp_driving[i]['value'][:,6] .detach() - kp_driving_a[i]['value'][:,6] - kp_emo[i]['value'][:,2] ).mean())*self.loss_weights['emo'] + loss_value += (torch.abs(kp_driving[i]['value'][:,8] .detach() - kp_driving_a[i]['value'][:,8] - kp_emo[i]['value'][:,3] ).mean())*self.loss_weights['emo'] + kp_all[i]['jacobian'][:,1] = kp_emo[i]['jacobian'][:,0] + kp_driving_a[i]['jacobian'][:,1] + kp_all[i]['jacobian'][:,4] = kp_emo[i]['jacobian'][:,1] + kp_driving_a[i]['jacobian'][:,4] + kp_all[i]['jacobian'][:,6] = kp_emo[i]['jacobian'][:,2] + kp_driving_a[i]['jacobian'][:,6] + kp_all[i]['jacobian'][:,8] = kp_emo[i]['jacobian'][:,3] + kp_driving_a[i]['jacobian'][:,8] + kp_all[i]['value'][:,1] = kp_emo[i]['value'][:,0] + kp_driving_a[i]['value'][:,1] + kp_all[i]['value'][:,4] = kp_emo[i]['value'][:,1] + kp_driving_a[i]['value'][:,4] + kp_all[i]['value'][:,6] = kp_emo[i]['value'][:,2] + kp_driving_a[i]['value'][:,6] + kp_all[i]['value'][:,8] = kp_emo[i]['value'][:,3] + kp_driving_a[i]['value'][:,8] + elif self.train_params['type'] == 'linear_10' or self.train_params['type'] == 'linear_np_10': + loss_jacobian += (torch.abs(kp_driving[i]['jacobian'] - kp_driving_a[i]['jacobian'] -kp_emo[i]['jacobian']).mean())*self.loss_weights['emo'] + + loss_classify += self.CroEn_loss(fakes[i],x['emotion']) + loss_value += (torch.abs(kp_driving[i]['value'].detach() - kp_driving_a[i]['value'] - kp_emo[i]['value'] ).mean())*self.loss_weights['emo'] + + # kp_all[i]['value'] = kp_emo[i]['value'] + kp_driving_a[i]['value'] + + loss_values['loss_value'] = loss_value/len(kp_driving) + # loss_values['loss_heatmap'] = loss_heatmap/len(kp_driving) + loss_values['loss_jacobian'] = loss_jacobian/len(kp_driving) + if self.train_params['classify'] == True: + loss_values['loss_classify'] = loss_classify/len(kp_driving) + else: + loss_values['loss_classify'] = torch.tensor(0, device = loss_values['loss_value'].device) + + + + + + return loss_values,generated + + +class GeneratorFullModel(torch.nn.Module): + """ + Merge all generator related updates into single model for better multi-gpu usage + """ + + def __init__(self, kp_extractor, kp_extractor_a, audio_feature, generator, discriminator, train_params): + super(GeneratorFullModel, self).__init__() + self.kp_extractor = kp_extractor + self.kp_extractor_a = kp_extractor_a + # self.content_encoder = content_encoder + # self.emotion_encoder = emotion_encoder + self.audio_feature = audio_feature + self.generator = generator + self.discriminator = discriminator + self.train_params = train_params + self.scales = train_params['scales'] + self.disc_scales = self.discriminator.scales + self.pyramid = ImagePyramide(self.scales, generator.num_channels) + if torch.cuda.is_available(): + self.pyramid = self.pyramid.cuda() + + self.loss_weights = train_params['loss_weights'] + + if sum(self.loss_weights['perceptual']) != 0: + self.vgg = Vgg19() + if torch.cuda.is_available(): + self.vgg = self.vgg.cuda() + + self.pca = torch.FloatTensor(np.load('.../LRW/list/U_106.npy'))[:, :16].cuda() + self.mean = torch.FloatTensor(np.load('.../LRW/list/mean_106.npy')).cuda() + + def forward(self, x): + # source_a_f = self.audio_feature(x['source_audio'],x['source_lm'],x[]) + # source_a_f = self.audio_feature(self.content_encoder(x['source_audio'].unsqueeze(1)), self.emotion_encoder(x['source_audio'].unsqueeze(1))) + # kp_source = self.kp_extractor(x['source']) + # kp_source_a = self.kp_extractor_a(x['source'], x['source_cube'], source_a_f) + # driving_a_f = self.audio_feature(self.content_encoder(x['driving_audio'].unsqueeze(1)), self.emotion_encoder(x['driving_audio'].unsqueeze(1))) + # driving_a_f = self.audio_feature(x['driving_audio']) + # kp_driving = self.kp_extractor(x['driving']) + # kp_driving_a = self.kp_extractor_a(x['driving'], x['driving_cube'], driving_a_f) + + kp_driving = [] + for i in range(16): + kp_driving.append(self.kp_extractor(x['driving'][:,i],x['driving_landmark'][:,i],self.loss_weights['equivariance_value'])) + + kp_driving_a = [] + fc_out, deco_out = self.audio_feature(x['example_landmark'], x['driving_audio'], x['driving_pose']) + fake_lmark=fc_out + x['example_landmark'].expand_as(fc_out) + + + fake_lmark = torch.mm( fake_lmark, self.pca.t() ) + fake_lmark = fake_lmark + self.mean.expand_as(fake_lmark) + + + fake_lmark = fake_lmark.unsqueeze(0) + + # for i in range(16): + # kp_driving_a.append() + + # generated = self.generator(x['source'], kp_source=kp_source, kp_driving=kp_driving) + # generated.update({'kp_source': kp_source, 'kp_driving': kp_driving}) + + loss_values = {} + + pyramide_real = self.pyramid(x['driving']) + pyramide_generated = self.pyramid(generated['prediction']) + + if self.loss_weights['audio'] != 0: + value = torch.abs(kp_source['jacobian'].detach() - kp_source_a['jacobian'].detach()).mean() + torch.abs(kp_driving['jacobian'].detach() - kp_driving_a['jacobian']).mean() + value = value/2 + loss_values['jacobian'] = value*self.loss_weights['audio'] + value = torch.abs(kp_source['heatmap'].detach() - kp_source_a['heatmap'].detach()).mean() + torch.abs(kp_driving['heatmap'].detach() - kp_driving_a['heatmap']).mean() + value = value/2 + loss_values['heatmap'] = value*self.loss_weights['audio'] + value = torch.abs(kp_source['value'].detach() - kp_source_a['value'].detach()).mean() + torch.abs(kp_driving['value'].detach() - kp_driving_a['value']).mean() + value = value/2 + loss_values['value'] = value*self.loss_weights['audio'] + + if sum(self.loss_weights['perceptual']) != 0: + 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['generator_gan'] != 0: + discriminator_maps_generated = self.discriminator(pyramide_generated, kp=detach_kp(kp_driving)) + discriminator_maps_real = self.discriminator(pyramide_real, kp=detach_kp(kp_driving)) + value_total = 0 + for scale in self.disc_scales: + key = 'prediction_map_%s' % scale + value = ((1 - discriminator_maps_generated[key]) ** 2).mean() + value_total += self.loss_weights['generator_gan'] * value + loss_values['gen_gan'] = value_total + + if sum(self.loss_weights['feature_matching']) != 0: + value_total = 0 + for scale in self.disc_scales: + key = 'feature_maps_%s' % scale + for i, (a, b) in enumerate(zip(discriminator_maps_real[key], discriminator_maps_generated[key])): + if self.loss_weights['feature_matching'][i] == 0: + continue + value = torch.abs(a - b).mean() + value_total += self.loss_weights['feature_matching'][i] * value + loss_values['feature_matching'] = value_total + + if (self.loss_weights['equivariance_value'] + self.loss_weights['equivariance_jacobian']) != 0: + transform = Transform(x['driving'].shape[0], **self.train_params['transform_params']) + transformed_frame = transform.transform_frame(x['driving']) + transformed_landmark = transform.inverse_warp_coordinates(x['driving_landmark']) + transformed_kp = self.kp_extractor(transformed_frame) + + generated['transformed_frame'] = transformed_frame + generated['transformed_kp'] = transformed_kp + + ## Value loss part + if self.loss_weights['equivariance_value'] != 0: + value = torch.abs(kp_driving['value'] - transform.warp_coordinates(transformed_kp['value'])).mean() + loss_values['equivariance_value'] = self.loss_weights['equivariance_value'] * value + + ## jacobian loss part + if self.loss_weights['equivariance_jacobian'] != 0: + jacobian_transformed = torch.matmul(transform.jacobian(transformed_kp['value']), + transformed_kp['jacobian']) + + normed_driving = torch.inverse(kp_driving['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).mean() + loss_values['equivariance_jacobian'] = self.loss_weights['equivariance_jacobian'] * value + + return loss_values, generated + + +class DiscriminatorFullModel(torch.nn.Module): + """ + Merge all discriminator related updates into single model for better multi-gpu usage + """ + + def __init__(self, kp_extractor, generator, discriminator, train_params): + super(DiscriminatorFullModel, self).__init__() + self.kp_extractor = kp_extractor + self.generator = generator + self.discriminator = discriminator + self.train_params = train_params + self.scales = self.discriminator.scales + self.pyramid = ImagePyramide(self.scales, generator.num_channels) + if torch.cuda.is_available(): + self.pyramid = self.pyramid.cuda() + + self.loss_weights = train_params['loss_weights'] + + def forward(self, x, generated): + pyramide_real = self.pyramid(x['driving']) + pyramide_generated = self.pyramid(generated['prediction'].detach()) + + kp_driving = generated['kp_driving'] + discriminator_maps_generated = self.discriminator(pyramide_generated, kp=detach_kp(kp_driving)) + discriminator_maps_real = self.discriminator(pyramide_real, kp=detach_kp(kp_driving)) + + loss_values = {} + value_total = 0 + for scale in self.scales: + key = 'prediction_map_%s' % scale + value = (1 - discriminator_maps_real[key]) ** 2 + discriminator_maps_generated[key] ** 2 + value_total += self.loss_weights['discriminator_gan'] * value.mean() + loss_values['disc_gan'] = value_total + + return loss_values diff --git a/FONT/modules/model1.py b/FONT/modules/model1.py new file mode 100644 index 0000000000000000000000000000000000000000..8750ba905efe5832998d3b8f25e768f9de49b460 --- /dev/null +++ b/FONT/modules/model1.py @@ -0,0 +1,539 @@ +from torch import nn +import torch +import torch.nn.functional as F +from modules.util import AntiAliasInterpolation2d, make_coordinate_grid +from torchvision import models +import numpy as np +from torch.autograd import grad + + +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 - 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): + 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 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) #[1,256,256,2] + 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 inverse_transform_frame(self, frame): + grid = make_coordinate_grid(frame.shape[2:], type=frame.type()).unsqueeze(0) #[1,256,256,2] + grid = grid.view(1, frame.shape[2] * frame.shape[3], 2) + grid = self.inverse_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) + 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()) + 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 inverse_warp_coordinates(self, coordinates): + theta = self.theta.type(coordinates.type()) + theta = theta.unsqueeze(1) + a = torch.FloatTensor([[[[0,0,1]]]]).repeat([self.bs,1,1,1]).cuda() + c = torch.cat((theta,a),2) + d = c.inverse()[:,:,:2,:] + d = d.type(coordinates.type()) + transformed = torch.matmul(d[:, :, :, :2], coordinates.unsqueeze(-1)) + d[:, :, :, 2:] + transformed = transformed.squeeze(-1) + + if self.tps: + control_points = self.control_points.type(coordinates.type()) + control_params = self.control_params.type(coordinates.type()) + 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): + coordinates.requires_grad=True + new_coordinates = self.warp_coordinates(coordinates)#[4,10,2] + 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 + + +def detach_kp(kp): + return {key: value.detach() for key, value in kp.items()} + +class TrainFullModel(torch.nn.Module): + """ + Merge all generator related updates into single model for better multi-gpu usage + """ + + def __init__(self, kp_extractor, emo_feature, kp_extractor_a, audio_feature, generator, discriminator, train_params, device_ids): + super(TrainFullModel, self).__init__() + self.kp_extractor = kp_extractor + self.kp_extractor_a = kp_extractor_a + # self.emo_detector = emo_detector + # self.content_encoder = content_encoder + # self.emotion_encoder = emotion_encoder + self.audio_feature = audio_feature + self.emo_feature = emo_feature + self.generator = generator + self.discriminator = discriminator + self.train_params = train_params + self.scales = train_params['scales'] + self.disc_scales = self.discriminator.scales + self.pyramid = ImagePyramide(self.scales, generator.num_channels) + if torch.cuda.is_available(): + self.pyramid = self.pyramid.cuda() + + self.loss_weights = train_params['loss_weights'] + + if sum(self.loss_weights['perceptual']) != 0: + self.vgg = Vgg19() + if torch.cuda.is_available(): + self.vgg = self.vgg.cuda() + + # self.pca = torch.FloatTensor(np.load('/mnt/lustre/jixinya/Home/LRW/list/U_106.npy'))[:, :16].to(device_ids[0]) + # self.mean = torch.FloatTensor(np.load('/mnt/lustre/jixinya/Home/LRW/list/mean_106.npy')).to(device_ids[0]) + self.mse_loss_fn = nn.MSELoss().cuda() + self.CroEn_loss = nn.CrossEntropyLoss().cuda() + def forward(self, x): + # source_a_f = self.audio_feature(x['source_audio'],x['source_lm'],x[]) + # source_a_f = self.audio_feature(self.content_encoder(x['source_audio'].unsqueeze(1)), self.emotion_encoder(x['source_audio'].unsqueeze(1))) + kp_source = self.kp_extractor(x['example_image']) + # print(x['name'],len(x['name'])) + kp_driving = [] + kp_emo = [] + for i in range(16): + kp_driving.append(self.kp_extractor(x['driving'][:,i])) + # kp_emo.append(self.emo_detector(x['driving'][:,i])) + # print('KP_driving ', file=open('/mnt/lustre/jixinya/Home/fomm_audio/log/LRW_test.txt', 'a')) + kp_driving_a = [] #x['example_image'], + deco_out = self.audio_feature(x['example_image'], x['driving_audio'], x['driving_pose'], self.train_params['jaco_net']) + # emo_out = self.emo_feature(x['example_image'], x['driving_audio'], x['driving_pose'], self.train_params['jaco_net']) + loss_values = {} + + if self.loss_weights['emo'] != 0: + + kp_driving_a = [] + fakes = [] + for i in range(16): + kp_driving_a.append(self.kp_extractor_a(deco_out[:,i]))# + value = self.kp_extractor_a(deco_out[:,i])['value'] + jacobian = self.kp_extractor_a(deco_out[:,i])['jacobian'] + if self.train_params['type'] == 'linear_4' : + out, fake = self.emo_feature(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + # kp_emo.append(self.emo_feature(x['transformed_driving'][:,i],value,jacobian)) + elif self.train_params['type'] == 'linear_10': + # kp_emo.append(self.emo_feature.linear_10(x['transformed_driving'][:,i],value,jacobian)) + + out, fake = self.emo_feature.linear_10(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + elif self.train_params['type'] == 'linear_4_new': + # kp_emo.append(self.emo_feature.linear_10(x['transformed_driving'][:,i],value,jacobian)) + + out, fake = self.emo_feature.linear_4(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + elif self.train_params['type'] == 'linear_np_4': + # kp_emo.append(self.emo_feature.linear_10(x['transformed_driving'][:,i],value,jacobian)) + + out, fake = self.emo_feature.linear_np_4(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + elif self.train_params['type'] == 'linear_np_10': + # kp_emo.append(self.emo_feature.linear_10(x['transformed_driving'][:,i],value,jacobian)) + + out, fake = self.emo_feature.linear_np_10(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + # kp_emo.append(self.emo_feature(x['transformed_driving'][:,i],value,jacobian)) + # print('Kp_audio_driving ', file=open('/mnt/lustre/jixinya/Home/fomm_audio/log/LRW_test.txt', 'a')) + loss_value = 0 + # loss_heatmap = 0 + loss_jacobian = 0 + loss_perceptual = 0 + loss_classify = 0 + kp_all = kp_driving_a + if self.train_params['smooth'] == True: + value_all = torch.randn(len(kp_driving),out['value'].shape[0],out['value'].shape[1],out['value'].shape[2]).cuda() + jacobian_all = torch.randn(len(kp_driving),out['jacobian'].shape[0],out['jacobian'].shape[1],2,2).cuda() + print(len(kp_driving)) + for i in range(len(kp_driving)): + # if x['name'][i] == 'LRW': + # loss_jacobian += (torch.abs(kp_driving[i]['jacobian'] - kp_driving_a[i]['jacobian']).mean())*self.loss_weights['emo'] + + # loss_value += (torch.abs(kp_driving[i]['value'].detach() - kp_driving_a[i]['value']).mean())*self.loss_weights['emo'] + # loss_classify += self.mse_loss_fn(deco_out,deco_out) + if self.train_params['type'] == 'linear_4' or self.train_params['type'] == 'linear_4_new' or self.train_params['type'] == 'linear_np_4': + loss_jacobian += (torch.abs(kp_driving[i]['jacobian'][:,1] - kp_driving_a[i]['jacobian'][:,1] -kp_emo[i]['jacobian'][:,0]).mean())*self.loss_weights['emo'] + loss_jacobian += (torch.abs(kp_driving[i]['jacobian'][:,4] - kp_driving_a[i]['jacobian'][:,4] -kp_emo[i]['jacobian'][:,1]).mean())*self.loss_weights['emo'] + loss_jacobian += (torch.abs(kp_driving[i]['jacobian'][:,6] - kp_driving_a[i]['jacobian'][:,6] -kp_emo[i]['jacobian'][:,2]).mean())*self.loss_weights['emo'] + loss_jacobian += (torch.abs(kp_driving[i]['jacobian'][:,8] - kp_driving_a[i]['jacobian'][:,8] -kp_emo[i]['jacobian'][:,3]).mean())*self.loss_weights['emo'] + + loss_classify += self.CroEn_loss(fakes[i],x['emotion']) + loss_value += (torch.abs(kp_driving[i]['value'][:,1] .detach() - kp_driving_a[i]['value'][:,1] - kp_emo[i]['value'][:,0] ).mean())*self.loss_weights['emo'] + loss_value += (torch.abs(kp_driving[i]['value'][:,4] .detach() - kp_driving_a[i]['value'][:,4] - kp_emo[i]['value'][:,1] ).mean())*self.loss_weights['emo'] + loss_value += (torch.abs(kp_driving[i]['value'][:,6] .detach() - kp_driving_a[i]['value'][:,6] - kp_emo[i]['value'][:,2] ).mean())*self.loss_weights['emo'] + loss_value += (torch.abs(kp_driving[i]['value'][:,8] .detach() - kp_driving_a[i]['value'][:,8] - kp_emo[i]['value'][:,3] ).mean())*self.loss_weights['emo'] + kp_all[i]['jacobian'][:,1] = kp_emo[i]['jacobian'][:,0] + kp_driving_a[i]['jacobian'][:,1] + kp_all[i]['jacobian'][:,4] = kp_emo[i]['jacobian'][:,1] + kp_driving_a[i]['jacobian'][:,4] + kp_all[i]['jacobian'][:,6] = kp_emo[i]['jacobian'][:,2] + kp_driving_a[i]['jacobian'][:,6] + kp_all[i]['jacobian'][:,8] = kp_emo[i]['jacobian'][:,3] + kp_driving_a[i]['jacobian'][:,8] + kp_all[i]['value'][:,1] = kp_emo[i]['value'][:,0] + kp_driving_a[i]['value'][:,1] + kp_all[i]['value'][:,4] = kp_emo[i]['value'][:,1] + kp_driving_a[i]['value'][:,4] + kp_all[i]['value'][:,6] = kp_emo[i]['value'][:,2] + kp_driving_a[i]['value'][:,6] + kp_all[i]['value'][:,8] = kp_emo[i]['value'][:,3] + kp_driving_a[i]['value'][:,8] + elif self.train_params['type'] == 'linear_10' or self.train_params['type'] == 'linear_np_10': + loss_jacobian += (torch.abs(kp_driving[i]['jacobian'] - kp_driving_a[i]['jacobian'] -kp_emo[i]['jacobian']).mean())*self.loss_weights['emo'] + + loss_classify += self.CroEn_loss(fakes[i],x['emotion']) + loss_value += (torch.abs(kp_driving[i]['value'].detach() - kp_driving_a[i]['value'] - kp_emo[i]['value'] ).mean())*self.loss_weights['emo'] + if self.train_params['smooth'] == True: + value_all[i]=kp_emo[i]['value'] + jacobian_all[i] = kp_emo[i]['jacobian'] + + # kp_all[i]['value'] = kp_emo[i]['value'] + kp_driving_a[i]['value'] + + loss_values['loss_value'] = loss_value/len(kp_driving) + # loss_values['loss_heatmap'] = loss_heatmap/len(kp_driving) + loss_values['loss_jacobian'] = loss_jacobian/len(kp_driving) + if self.train_params['classify'] == True: + loss_values['loss_classify'] = loss_classify/len(kp_driving) + else: + loss_values['loss_classify'] = self.mse_loss_fn(deco_out,deco_out) + if self.train_params['smooth'] == True: + loss_smooth = 0 + loss_smooth += (torch.abs(value_all[2:,:,:,:] + value_all[:-2,:,:,:].detach() -2*value_all[1:-1,:,:,:].detach()).mean())*self.loss_weights['emo'] *100 + loss_smooth += (torch.abs(jacobian_all[2:,:,:,:] + jacobian_all[:-2,:,:,:].detach() -2*jacobian_all[1:-1,:,:,:].detach()).mean())*self.loss_weights['emo'] *100 + loss_values['loss_smooth'] = loss_smooth/len(kp_driving) + else: + loss_values['loss_smooth'] = self.mse_loss_fn(deco_out,deco_out) + if self.train_params['generator'] == 'not': + loss_values['perceptual'] = self.mse_loss_fn(deco_out,deco_out) + for i in range(1): #0,len(kp_driving),4 + + generated = self.generator(x['example_image'], kp_source=kp_source, kp_driving=kp_all[i]) + generated.update({'kp_source': kp_source, 'kp_driving': kp_all}) + elif self.train_params['generator'] == 'visual': + for i in range(0,len(kp_driving),4): #0,len(kp_driving),4 + + generated = self.generator(x['example_image'], kp_source=kp_source, kp_driving=kp_driving[i]) + generated.update({'kp_source': kp_source, 'kp_driving': kp_driving}) + + pyramide_real = self.pyramid(x['driving'][:,i]) + pyramide_generated = self.pyramid(generated['prediction']) + + if sum(self.loss_weights['perceptual']) != 0: + 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_perceptual += value_total + + length = int((len(kp_driving)-1)/4)+1 + loss_values['perceptual'] = loss_perceptual/length + elif self.train_params['generator'] == 'audio': + for i in range(0,len(kp_driving),4): #0,len(kp_driving),4 + + generated = self.generator(x['example_image'], kp_source=kp_source, kp_driving=kp_all[i]) + generated.update({'kp_source': kp_source, 'kp_driving': kp_all}) + + pyramide_real = self.pyramid(x['driving'][:,i]) + pyramide_generated = self.pyramid(generated['prediction']) + + if sum(self.loss_weights['perceptual']) != 0: + 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_perceptual += value_total + + length = int((len(kp_driving)-1)/4)+1 + loss_values['perceptual'] = loss_perceptual/length + else: + print('wrong train_params: ', self.train_params['generator']) + + + + return loss_values,generated + +class GeneratorFullModel(torch.nn.Module): + """ + Merge all generator related updates into single model for better multi-gpu usage + """ + + def __init__(self, kp_extractor, kp_extractor_a, audio_feature, generator, discriminator, train_params): + super(GeneratorFullModel, self).__init__() + self.kp_extractor = kp_extractor + self.kp_extractor_a = kp_extractor_a + # self.content_encoder = content_encoder + # self.emotion_encoder = emotion_encoder + self.audio_feature = audio_feature + self.generator = generator + self.discriminator = discriminator + self.train_params = train_params + self.scales = train_params['scales'] + self.disc_scales = self.discriminator.scales + self.pyramid = ImagePyramide(self.scales, generator.num_channels) + if torch.cuda.is_available(): + self.pyramid = self.pyramid.cuda() + + self.loss_weights = train_params['loss_weights'] + + if sum(self.loss_weights['perceptual']) != 0: + self.vgg = Vgg19() + if torch.cuda.is_available(): + self.vgg = self.vgg.cuda() + + self.pca = torch.FloatTensor(np.load('.../LRW/list/U_106.npy'))[:, :16].cuda() + self.mean = torch.FloatTensor(np.load('.../LRW/list/mean_106.npy')).cuda() + + def forward(self, x): + # source_a_f = self.audio_feature(x['source_audio'],x['source_lm'],x[]) + # source_a_f = self.audio_feature(self.content_encoder(x['source_audio'].unsqueeze(1)), self.emotion_encoder(x['source_audio'].unsqueeze(1))) + # kp_source = self.kp_extractor(x['source']) + # kp_source_a = self.kp_extractor_a(x['source'], x['source_cube'], source_a_f) + # driving_a_f = self.audio_feature(self.content_encoder(x['driving_audio'].unsqueeze(1)), self.emotion_encoder(x['driving_audio'].unsqueeze(1))) + # driving_a_f = self.audio_feature(x['driving_audio']) + # kp_driving = self.kp_extractor(x['driving']) + # kp_driving_a = self.kp_extractor_a(x['driving'], x['driving_cube'], driving_a_f) + + kp_driving = [] + for i in range(16): + kp_driving.append(self.kp_extractor(x['driving'][:,i],x['driving_landmark'][:,i],self.loss_weights['equivariance_value'])) + + kp_driving_a = [] + fc_out, deco_out = self.audio_feature(x['example_landmark'], x['driving_audio'], x['driving_pose']) + fake_lmark=fc_out + x['example_landmark'].expand_as(fc_out) + + + fake_lmark = torch.mm( fake_lmark, self.pca.t() ) + fake_lmark = fake_lmark + self.mean.expand_as(fake_lmark) + + + fake_lmark = fake_lmark.unsqueeze(0) + + # for i in range(16): + # kp_driving_a.append() + + # generated = self.generator(x['source'], kp_source=kp_source, kp_driving=kp_driving) + # generated.update({'kp_source': kp_source, 'kp_driving': kp_driving}) + + loss_values = {} + + pyramide_real = self.pyramid(x['driving']) + pyramide_generated = self.pyramid(generated['prediction']) + + if self.loss_weights['audio'] != 0: + value = torch.abs(kp_source['jacobian'].detach() - kp_source_a['jacobian'].detach()).mean() + torch.abs(kp_driving['jacobian'].detach() - kp_driving_a['jacobian']).mean() + value = value/2 + loss_values['jacobian'] = value*self.loss_weights['audio'] + value = torch.abs(kp_source['heatmap'].detach() - kp_source_a['heatmap'].detach()).mean() + torch.abs(kp_driving['heatmap'].detach() - kp_driving_a['heatmap']).mean() + value = value/2 + loss_values['heatmap'] = value*self.loss_weights['audio'] + value = torch.abs(kp_source['value'].detach() - kp_source_a['value'].detach()).mean() + torch.abs(kp_driving['value'].detach() - kp_driving_a['value']).mean() + value = value/2 + loss_values['value'] = value*self.loss_weights['audio'] + + if sum(self.loss_weights['perceptual']) != 0: + 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['generator_gan'] != 0: + discriminator_maps_generated = self.discriminator(pyramide_generated, kp=detach_kp(kp_driving)) + discriminator_maps_real = self.discriminator(pyramide_real, kp=detach_kp(kp_driving)) + value_total = 0 + for scale in self.disc_scales: + key = 'prediction_map_%s' % scale + value = ((1 - discriminator_maps_generated[key]) ** 2).mean() + value_total += self.loss_weights['generator_gan'] * value + loss_values['gen_gan'] = value_total + + if sum(self.loss_weights['feature_matching']) != 0: + value_total = 0 + for scale in self.disc_scales: + key = 'feature_maps_%s' % scale + for i, (a, b) in enumerate(zip(discriminator_maps_real[key], discriminator_maps_generated[key])): + if self.loss_weights['feature_matching'][i] == 0: + continue + value = torch.abs(a - b).mean() + value_total += self.loss_weights['feature_matching'][i] * value + loss_values['feature_matching'] = value_total + + if (self.loss_weights['equivariance_value'] + self.loss_weights['equivariance_jacobian']) != 0: + transform = Transform(x['driving'].shape[0], **self.train_params['transform_params']) + transformed_frame = transform.transform_frame(x['driving']) + transformed_landmark = transform.inverse_warp_coordinates(x['driving_landmark']) + transformed_kp = self.kp_extractor(transformed_frame) + + generated['transformed_frame'] = transformed_frame + generated['transformed_kp'] = transformed_kp + + ## Value loss part + if self.loss_weights['equivariance_value'] != 0: + value = torch.abs(kp_driving['value'] - transform.warp_coordinates(transformed_kp['value'])).mean() + loss_values['equivariance_value'] = self.loss_weights['equivariance_value'] * value + + ## jacobian loss part + if self.loss_weights['equivariance_jacobian'] != 0: + jacobian_transformed = torch.matmul(transform.jacobian(transformed_kp['value']), + transformed_kp['jacobian']) + + normed_driving = torch.inverse(kp_driving['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).mean() + loss_values['equivariance_jacobian'] = self.loss_weights['equivariance_jacobian'] * value + + return loss_values, generated + + +class DiscriminatorFullModel(torch.nn.Module): + """ + Merge all discriminator related updates into single model for better multi-gpu usage + """ + + def __init__(self, kp_extractor, generator, discriminator, train_params): + super(DiscriminatorFullModel, self).__init__() + self.kp_extractor = kp_extractor + self.generator = generator + self.discriminator = discriminator + self.train_params = train_params + self.scales = self.discriminator.scales + self.pyramid = ImagePyramide(self.scales, generator.num_channels) + if torch.cuda.is_available(): + self.pyramid = self.pyramid.cuda() + + self.loss_weights = train_params['loss_weights'] + + def forward(self, x, generated): + pyramide_real = self.pyramid(x['driving']) + pyramide_generated = self.pyramid(generated['prediction'].detach()) + + kp_driving = generated['kp_driving'] + discriminator_maps_generated = self.discriminator(pyramide_generated, kp=detach_kp(kp_driving)) + discriminator_maps_real = self.discriminator(pyramide_real, kp=detach_kp(kp_driving)) + + loss_values = {} + value_total = 0 + for scale in self.scales: + key = 'prediction_map_%s' % scale + value = (1 - discriminator_maps_real[key]) ** 2 + discriminator_maps_generated[key] ** 2 + value_total += self.loss_weights['discriminator_gan'] * value.mean() + loss_values['disc_gan'] = value_total + + return loss_values diff --git a/FONT/modules/model_delta_map.py b/FONT/modules/model_delta_map.py new file mode 100644 index 0000000000000000000000000000000000000000..bfdeb5cf90004260d5be87cecabd2785ee01a3f9 --- /dev/null +++ b/FONT/modules/model_delta_map.py @@ -0,0 +1,500 @@ +from torch import nn +import torch +import torch.nn.functional as F +from modules.util import AntiAliasInterpolation2d, make_coordinate_grid +from torchvision import models +import numpy as np +from torch.autograd import grad + + +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 - 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): + 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 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) #[1,256,256,2] + 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 inverse_transform_frame(self, frame): + grid = make_coordinate_grid(frame.shape[2:], type=frame.type()).unsqueeze(0) #[1,256,256,2] + grid = grid.view(1, frame.shape[2] * frame.shape[3], 2) + grid = self.inverse_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) + 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()) + 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 inverse_warp_coordinates(self, coordinates): + theta = self.theta.type(coordinates.type()) + theta = theta.unsqueeze(1) + a = torch.FloatTensor([[[[0,0,1]]]]).repeat([self.bs,1,1,1]).cuda() + c = torch.cat((theta,a),2) + d = c.inverse()[:,:,:2,:] + d = d.type(coordinates.type()) + transformed = torch.matmul(d[:, :, :, :2], coordinates.unsqueeze(-1)) + d[:, :, :, 2:] + transformed = transformed.squeeze(-1) + + if self.tps: + control_points = self.control_points.type(coordinates.type()) + control_params = self.control_params.type(coordinates.type()) + 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): + coordinates.requires_grad=True + new_coordinates = self.warp_coordinates(coordinates)#[4,10,2] + 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 + + +def detach_kp(kp): + return {key: value.detach() for key, value in kp.items()} + +class TrainFullModel(torch.nn.Module): + """ + Merge all generator related updates into single model for better multi-gpu usage + """ + + def __init__(self, kp_extractor, emo_feature, kp_extractor_a, audio_feature, generator, discriminator, train_params, device_ids): + super(TrainFullModel, self).__init__() + self.kp_extractor = kp_extractor + self.kp_extractor_a = kp_extractor_a + # self.emo_detector = emo_detector + # self.content_encoder = content_encoder + # self.emotion_encoder = emotion_encoder + self.audio_feature = audio_feature + self.emo_feature = emo_feature + self.generator = generator + self.discriminator = discriminator + self.train_params = train_params + self.scales = train_params['scales'] + self.disc_scales = self.discriminator.scales + self.pyramid = ImagePyramide(self.scales, generator.num_channels) + if torch.cuda.is_available(): + self.pyramid = self.pyramid.cuda() + + self.loss_weights = train_params['loss_weights'] + + if sum(self.loss_weights['perceptual']) != 0: + self.vgg = Vgg19() + if torch.cuda.is_available(): + self.vgg = self.vgg.cuda() + + # self.pca = torch.FloatTensor(np.load('/mnt/lustre/jixinya/Home/LRW/list/U_106.npy'))[:, :16].to(device_ids[0]) + # self.mean = torch.FloatTensor(np.load('/mnt/lustre/jixinya/Home/LRW/list/mean_106.npy')).to(device_ids[0]) + self.mse_loss_fn = nn.MSELoss().cuda() + self.CroEn_loss = nn.CrossEntropyLoss().cuda() + def forward(self, x): + # source_a_f = self.audio_feature(x['source_audio'],x['source_lm'],x[]) + # source_a_f = self.audio_feature(self.content_encoder(x['source_audio'].unsqueeze(1)), self.emotion_encoder(x['source_audio'].unsqueeze(1))) + kp_source = self.kp_extractor(x['example_image']) + + kp_driving = [] + kp_emo = [] + for i in range(16): + kp_driving.append(self.kp_extractor(x['driving'][:,i])) + # kp_emo.append(self.emo_detector(x['driving'][:,i])) + # print('KP_driving ', file=open('/mnt/lustre/jixinya/Home/fomm_audio/log/LRW_test.txt', 'a')) + kp_driving_a = [] #x['example_image'], + deco_out = self.audio_feature(x['example_image'], x['driving_audio'], x['driving_pose'], self.train_params['jaco_net']) + # emo_out = self.emo_feature(x['example_image'], x['driving_audio'], x['driving_pose'], self.train_params['jaco_net']) + loss_values = {} + + if self.loss_weights['emo'] != 0: + + kp_driving_a = [] + fakes = [] + for i in range(16): + kp_driving_a.append(self.kp_extractor_a(deco_out[:,i]))# + value = self.kp_extractor_a(deco_out[:,i])['value'] + jacobian = self.kp_extractor_a(deco_out[:,i])['jacobian'] + if self.train_params['type'] == 'map_4': + out, fake = self.emo_feature.map_4(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + # kp_emo.append(self.emo_feature(x['transformed_driving'][:,i],value,jacobian)) + elif self.train_params['type'] == 'map_10': + # kp_emo.append(self.emo_feature.linear_10(x['transformed_driving'][:,i],value,jacobian)) + + out, fake = self.emo_feature(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + # kp_emo.append(self.emo_feature(x['transformed_driving'][:,i],value,jacobian)) + # print('Kp_audio_driving ', file=open('/mnt/lustre/jixinya/Home/fomm_audio/log/LRW_test.txt', 'a')) + loss_value = 0 + # loss_heatmap = 0 + loss_jacobian = 0 + loss_perceptual = 0 + loss_classify = 0 + kp_all = kp_driving_a + for i in range(len(kp_driving)): + if self.train_params['type'] == 'map_4': + loss_jacobian += (torch.abs(kp_driving[i]['jacobian'][:,1] - kp_driving_a[i]['jacobian'][:,1] -kp_emo[i]['jacobian'][:,0]).mean())*self.loss_weights['emo'] + loss_jacobian += (torch.abs(kp_driving[i]['jacobian'][:,4] - kp_driving_a[i]['jacobian'][:,4] -kp_emo[i]['jacobian'][:,1]).mean())*self.loss_weights['emo'] + loss_jacobian += (torch.abs(kp_driving[i]['jacobian'][:,6] - kp_driving_a[i]['jacobian'][:,6] -kp_emo[i]['jacobian'][:,2]).mean())*self.loss_weights['emo'] + loss_jacobian += (torch.abs(kp_driving[i]['jacobian'][:,8] - kp_driving_a[i]['jacobian'][:,8] -kp_emo[i]['jacobian'][:,3]).mean())*self.loss_weights['emo'] + + loss_classify += self.CroEn_loss(fakes[i],x['emotion']) + loss_value += (torch.abs(kp_driving[i]['value'][:,1] .detach() - kp_driving_a[i]['value'][:,1] - kp_emo[i]['value'][:,0] ).mean())*self.loss_weights['emo'] + loss_value += (torch.abs(kp_driving[i]['value'][:,4] .detach() - kp_driving_a[i]['value'][:,4] - kp_emo[i]['value'][:,1] ).mean())*self.loss_weights['emo'] + loss_value += (torch.abs(kp_driving[i]['value'][:,6] .detach() - kp_driving_a[i]['value'][:,6] - kp_emo[i]['value'][:,2] ).mean())*self.loss_weights['emo'] + loss_value += (torch.abs(kp_driving[i]['value'][:,8] .detach() - kp_driving_a[i]['value'][:,8] - kp_emo[i]['value'][:,3] ).mean())*self.loss_weights['emo'] + kp_all[i]['jacobian'][:,1] = kp_emo[i]['jacobian'][:,0] + kp_driving_a[i]['jacobian'][:,1] + kp_all[i]['jacobian'][:,4] = kp_emo[i]['jacobian'][:,1] + kp_driving_a[i]['jacobian'][:,4] + kp_all[i]['jacobian'][:,6] = kp_emo[i]['jacobian'][:,2] + kp_driving_a[i]['jacobian'][:,6] + kp_all[i]['jacobian'][:,8] = kp_emo[i]['jacobian'][:,3] + kp_driving_a[i]['jacobian'][:,8] + kp_all[i]['value'][:,1] = kp_emo[i]['value'][:,0] + kp_driving_a[i]['value'][:,1] + kp_all[i]['value'][:,4] = kp_emo[i]['value'][:,1] + kp_driving_a[i]['value'][:,4] + kp_all[i]['value'][:,6] = kp_emo[i]['value'][:,2] + kp_driving_a[i]['value'][:,6] + kp_all[i]['value'][:,8] = kp_emo[i]['value'][:,3] + kp_driving_a[i]['value'][:,8] + elif self.train_params['type'] == 'map_10': + loss_jacobian += (torch.abs(kp_driving[i]['jacobian'] - kp_driving_a[i]['jacobian'] -kp_emo[i]['jacobian']).mean())*self.loss_weights['emo'] + + loss_classify += self.CroEn_loss(fakes[i],x['emotion']) + loss_value += (torch.abs(kp_driving[i]['value'].detach() - kp_driving_a[i]['value'] - kp_emo[i]['value'] ).mean())*self.loss_weights['emo'] + + # kp_all[i]['value'] = kp_emo[i]['value'] + kp_driving_a[i]['value'] + + loss_values['loss_value'] = loss_value/len(kp_driving) + # loss_values['loss_heatmap'] = loss_heatmap/len(kp_driving) + loss_values['loss_jacobian'] = loss_jacobian/len(kp_driving) + loss_values['loss_classify'] = loss_classify/len(kp_driving) + + if self.train_params['generator'] == 'not': + loss_values['perceptual'] = self.mse_loss_fn(deco_out,deco_out) + for i in range(1): #0,len(kp_driving),4 + + generated = self.generator(x['example_image'], kp_source=kp_source, kp_driving=kp_all[i]) + generated.update({'kp_source': kp_source, 'kp_driving': kp_all}) + elif self.train_params['generator'] == 'visual': + for i in range(0,len(kp_driving),4): #0,len(kp_driving),4 + + generated = self.generator(x['example_image'], kp_source=kp_source, kp_driving=kp_driving[i]) + generated.update({'kp_source': kp_source, 'kp_driving': kp_driving}) + + pyramide_real = self.pyramid(x['driving'][:,i]) + pyramide_generated = self.pyramid(generated['prediction']) + + if sum(self.loss_weights['perceptual']) != 0: + 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_perceptual += value_total + + length = int((len(kp_driving)-1)/4)+1 + loss_values['perceptual'] = loss_perceptual/length + elif self.train_params['generator'] == 'audio': + for i in range(0,len(kp_driving),4): #0,len(kp_driving),4 + + generated = self.generator(x['example_image'], kp_source=kp_source, kp_driving=kp_driving_a[i]) + generated.update({'kp_source': kp_source, 'kp_driving': kp_driving_a}) + + pyramide_real = self.pyramid(x['driving'][:,i]) + pyramide_generated = self.pyramid(generated['prediction']) + + if sum(self.loss_weights['perceptual']) != 0: + 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_perceptual += value_total + + length = int((len(kp_driving)-1)/4)+1 + loss_values['perceptual'] = loss_perceptual/length + else: + print('wrong train_params: ', self.train_params['generator']) + + + + return loss_values,generated + +class GeneratorFullModel(torch.nn.Module): + """ + Merge all generator related updates into single model for better multi-gpu usage + """ + + def __init__(self, kp_extractor, kp_extractor_a, audio_feature, generator, discriminator, train_params): + super(GeneratorFullModel, self).__init__() + self.kp_extractor = kp_extractor + self.kp_extractor_a = kp_extractor_a + # self.content_encoder = content_encoder + # self.emotion_encoder = emotion_encoder + self.audio_feature = audio_feature + self.generator = generator + self.discriminator = discriminator + self.train_params = train_params + self.scales = train_params['scales'] + self.disc_scales = self.discriminator.scales + self.pyramid = ImagePyramide(self.scales, generator.num_channels) + if torch.cuda.is_available(): + self.pyramid = self.pyramid.cuda() + + self.loss_weights = train_params['loss_weights'] + + if sum(self.loss_weights['perceptual']) != 0: + self.vgg = Vgg19() + if torch.cuda.is_available(): + self.vgg = self.vgg.cuda() + + self.pca = torch.FloatTensor(np.load('.../LRW/list/U_106.npy'))[:, :16].cuda() + self.mean = torch.FloatTensor(np.load('.../LRW/list/mean_106.npy')).cuda() + + def forward(self, x): + # source_a_f = self.audio_feature(x['source_audio'],x['source_lm'],x[]) + # source_a_f = self.audio_feature(self.content_encoder(x['source_audio'].unsqueeze(1)), self.emotion_encoder(x['source_audio'].unsqueeze(1))) + # kp_source = self.kp_extractor(x['source']) + # kp_source_a = self.kp_extractor_a(x['source'], x['source_cube'], source_a_f) + # driving_a_f = self.audio_feature(self.content_encoder(x['driving_audio'].unsqueeze(1)), self.emotion_encoder(x['driving_audio'].unsqueeze(1))) + # driving_a_f = self.audio_feature(x['driving_audio']) + # kp_driving = self.kp_extractor(x['driving']) + # kp_driving_a = self.kp_extractor_a(x['driving'], x['driving_cube'], driving_a_f) + + kp_driving = [] + for i in range(16): + kp_driving.append(self.kp_extractor(x['driving'][:,i],x['driving_landmark'][:,i],self.loss_weights['equivariance_value'])) + + kp_driving_a = [] + fc_out, deco_out = self.audio_feature(x['example_landmark'], x['driving_audio'], x['driving_pose']) + fake_lmark=fc_out + x['example_landmark'].expand_as(fc_out) + + + fake_lmark = torch.mm( fake_lmark, self.pca.t() ) + fake_lmark = fake_lmark + self.mean.expand_as(fake_lmark) + + + fake_lmark = fake_lmark.unsqueeze(0) + + # for i in range(16): + # kp_driving_a.append() + + # generated = self.generator(x['source'], kp_source=kp_source, kp_driving=kp_driving) + # generated.update({'kp_source': kp_source, 'kp_driving': kp_driving}) + + loss_values = {} + + pyramide_real = self.pyramid(x['driving']) + pyramide_generated = self.pyramid(generated['prediction']) + + if self.loss_weights['audio'] != 0: + value = torch.abs(kp_source['jacobian'].detach() - kp_source_a['jacobian'].detach()).mean() + torch.abs(kp_driving['jacobian'].detach() - kp_driving_a['jacobian']).mean() + value = value/2 + loss_values['jacobian'] = value*self.loss_weights['audio'] + value = torch.abs(kp_source['heatmap'].detach() - kp_source_a['heatmap'].detach()).mean() + torch.abs(kp_driving['heatmap'].detach() - kp_driving_a['heatmap']).mean() + value = value/2 + loss_values['heatmap'] = value*self.loss_weights['audio'] + value = torch.abs(kp_source['value'].detach() - kp_source_a['value'].detach()).mean() + torch.abs(kp_driving['value'].detach() - kp_driving_a['value']).mean() + value = value/2 + loss_values['value'] = value*self.loss_weights['audio'] + + if sum(self.loss_weights['perceptual']) != 0: + 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['generator_gan'] != 0: + discriminator_maps_generated = self.discriminator(pyramide_generated, kp=detach_kp(kp_driving)) + discriminator_maps_real = self.discriminator(pyramide_real, kp=detach_kp(kp_driving)) + value_total = 0 + for scale in self.disc_scales: + key = 'prediction_map_%s' % scale + value = ((1 - discriminator_maps_generated[key]) ** 2).mean() + value_total += self.loss_weights['generator_gan'] * value + loss_values['gen_gan'] = value_total + + if sum(self.loss_weights['feature_matching']) != 0: + value_total = 0 + for scale in self.disc_scales: + key = 'feature_maps_%s' % scale + for i, (a, b) in enumerate(zip(discriminator_maps_real[key], discriminator_maps_generated[key])): + if self.loss_weights['feature_matching'][i] == 0: + continue + value = torch.abs(a - b).mean() + value_total += self.loss_weights['feature_matching'][i] * value + loss_values['feature_matching'] = value_total + + if (self.loss_weights['equivariance_value'] + self.loss_weights['equivariance_jacobian']) != 0: + transform = Transform(x['driving'].shape[0], **self.train_params['transform_params']) + transformed_frame = transform.transform_frame(x['driving']) + transformed_landmark = transform.inverse_warp_coordinates(x['driving_landmark']) + transformed_kp = self.kp_extractor(transformed_frame) + + generated['transformed_frame'] = transformed_frame + generated['transformed_kp'] = transformed_kp + + ## Value loss part + if self.loss_weights['equivariance_value'] != 0: + value = torch.abs(kp_driving['value'] - transform.warp_coordinates(transformed_kp['value'])).mean() + loss_values['equivariance_value'] = self.loss_weights['equivariance_value'] * value + + ## jacobian loss part + if self.loss_weights['equivariance_jacobian'] != 0: + jacobian_transformed = torch.matmul(transform.jacobian(transformed_kp['value']), + transformed_kp['jacobian']) + + normed_driving = torch.inverse(kp_driving['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).mean() + loss_values['equivariance_jacobian'] = self.loss_weights['equivariance_jacobian'] * value + + return loss_values, generated + + +class DiscriminatorFullModel(torch.nn.Module): + """ + Merge all discriminator related updates into single model for better multi-gpu usage + """ + + def __init__(self, kp_extractor, generator, discriminator, train_params): + super(DiscriminatorFullModel, self).__init__() + self.kp_extractor = kp_extractor + self.generator = generator + self.discriminator = discriminator + self.train_params = train_params + self.scales = self.discriminator.scales + self.pyramid = ImagePyramide(self.scales, generator.num_channels) + if torch.cuda.is_available(): + self.pyramid = self.pyramid.cuda() + + self.loss_weights = train_params['loss_weights'] + + def forward(self, x, generated): + pyramide_real = self.pyramid(x['driving']) + pyramide_generated = self.pyramid(generated['prediction'].detach()) + + kp_driving = generated['kp_driving'] + discriminator_maps_generated = self.discriminator(pyramide_generated, kp=detach_kp(kp_driving)) + discriminator_maps_real = self.discriminator(pyramide_real, kp=detach_kp(kp_driving)) + + loss_values = {} + value_total = 0 + for scale in self.scales: + key = 'prediction_map_%s' % scale + value = (1 - discriminator_maps_real[key]) ** 2 + discriminator_maps_generated[key] ** 2 + value_total += self.loss_weights['discriminator_gan'] * value.mean() + loss_values['disc_gan'] = value_total + + return loss_values diff --git a/FONT/modules/model_gen.py b/FONT/modules/model_gen.py new file mode 100644 index 0000000000000000000000000000000000000000..eb946ba421b7071070907052b714f58517fb8263 --- /dev/null +++ b/FONT/modules/model_gen.py @@ -0,0 +1,516 @@ +from torch import nn +import torch +import torch.nn.functional as F +from modules.util import AntiAliasInterpolation2d, make_coordinate_grid +from torchvision import models +import numpy as np +from torch.autograd import grad + + +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 - 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): + 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 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) #[1,256,256,2] + 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 inverse_transform_frame(self, frame): + grid = make_coordinate_grid(frame.shape[2:], type=frame.type()).unsqueeze(0) #[1,256,256,2] + grid = grid.view(1, frame.shape[2] * frame.shape[3], 2) + grid = self.inverse_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) + 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()) + 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 inverse_warp_coordinates(self, coordinates): + theta = self.theta.type(coordinates.type()) + theta = theta.unsqueeze(1) + a = torch.FloatTensor([[[[0,0,1]]]]).repeat([self.bs,1,1,1]).cuda() + c = torch.cat((theta,a),2) + d = c.inverse()[:,:,:2,:] + d = d.type(coordinates.type()) + transformed = torch.matmul(d[:, :, :, :2], coordinates.unsqueeze(-1)) + d[:, :, :, 2:] + transformed = transformed.squeeze(-1) + + if self.tps: + control_points = self.control_points.type(coordinates.type()) + control_params = self.control_params.type(coordinates.type()) + 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): + coordinates.requires_grad=True + new_coordinates = self.warp_coordinates(coordinates)#[4,10,2] + 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 + + +def detach_kp(kp): + return {key: value.detach() for key, value in kp.items()} + +class TrainFullModel(torch.nn.Module): + """ + Merge all generator related updates into single model for better multi-gpu usage + """ + + def __init__(self, kp_extractor, emo_feature, kp_extractor_a, audio_feature, generator, discriminator, train_params, device_ids): + super(TrainFullModel, self).__init__() + self.kp_extractor = kp_extractor + self.kp_extractor_a = kp_extractor_a + # self.emo_detector = emo_detector + # self.content_encoder = content_encoder + # self.emotion_encoder = emotion_encoder + self.audio_feature = audio_feature + self.emo_feature = emo_feature + self.generator = generator + self.discriminator = discriminator + self.train_params = train_params + self.scales = train_params['scales'] + self.disc_scales = self.discriminator.scales + self.pyramid = ImagePyramide(self.scales, generator.num_channels) + if torch.cuda.is_available(): + self.pyramid = self.pyramid.cuda() + + self.loss_weights = train_params['loss_weights'] + + if sum(self.loss_weights['perceptual']) != 0: + self.vgg = Vgg19() + if torch.cuda.is_available(): + self.vgg = self.vgg.cuda() + + # self.pca = torch.FloatTensor(np.load('/mnt/lustre/jixinya/Home/LRW/list/U_106.npy'))[:, :16].to(device_ids[0]) + # self.mean = torch.FloatTensor(np.load('/mnt/lustre/jixinya/Home/LRW/list/mean_106.npy')).to(device_ids[0]) + self.mse_loss_fn = nn.MSELoss().cuda() + self.CroEn_loss = nn.CrossEntropyLoss().cuda() + def forward(self, x): + # source_a_f = self.audio_feature(x['source_audio'],x['source_lm'],x[]) + # source_a_f = self.audio_feature(self.content_encoder(x['source_audio'].unsqueeze(1)), self.emotion_encoder(x['source_audio'].unsqueeze(1))) + kp_source = self.kp_extractor(x['example_image']) + # print(x['name'],len(x['name'])) + kp_driving = [] + kp_emo = [] + for i in range(16): + kp_driving.append(self.kp_extractor(x['driving'][:,i])) + # kp_emo.append(self.emo_detector(x['driving'][:,i])) + # print('KP_driving ', file=open('/mnt/lustre/jixinya/Home/fomm_audio/log/LRW_test.txt', 'a')) + kp_driving_a = [] #x['example_image'], + deco_out = self.audio_feature(x['example_image'], x['driving_audio'], x['driving_pose'], self.train_params['jaco_net']) + # emo_out = self.emo_feature(x['example_image'], x['driving_audio'], x['driving_pose'], self.train_params['jaco_net']) + loss_values = {} + + if self.loss_weights['emo'] != 0: + + kp_driving_a = [] + fakes = [] + for i in range(16): + kp_driving_a.append(self.kp_extractor_a(deco_out[:,i]))# + value = self.kp_extractor_a(deco_out[:,i])['value'] + jacobian = self.kp_extractor_a(deco_out[:,i])['jacobian'] + if self.train_params['type'] == 'linear_4' and x['name'][0] == 0: + out, fake = self.emo_feature(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + # kp_emo.append(self.emo_feature(x['transformed_driving'][:,i],value,jacobian)) + elif self.train_params['type'] == 'linear_10' and x['name'][0] == 0: + # kp_emo.append(self.emo_feature.linear_10(x['transformed_driving'][:,i],value,jacobian)) + + out, fake = self.emo_feature.linear_10(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + elif self.train_params['type'] == 'linear_4_new' and x['name'][0] == 0: + # kp_emo.append(self.emo_feature.linear_10(x['transformed_driving'][:,i],value,jacobian)) + + out, fake = self.emo_feature.linear_4(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + elif self.train_params['type'] == 'linear_np_4': + # kp_emo.append(self.emo_feature.linear_10(x['transformed_driving'][:,i],value,jacobian)) + + out, fake = self.emo_feature.linear_np_4(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + elif self.train_params['type'] == 'linear_np_10': + # kp_emo.append(self.emo_feature.linear_10(x['transformed_driving'][:,i],value,jacobian)) + + out, fake = self.emo_feature.linear_np_10(x['transformed_driving'][:,i],value,jacobian) + kp_emo.append(out) + fakes.append(fake) + # kp_emo.append(self.emo_feature(x['transformed_driving'][:,i],value,jacobian)) + # print('Kp_audio_driving ', file=open('/mnt/lustre/jixinya/Home/fomm_audio/log/LRW_test.txt', 'a')) + + loss_perceptual = 0 + + kp_all = kp_driving_a + if self.train_params['smooth'] == True: + value_all = torch.randn(len(kp_driving),out['value'].shape[0],out['value'].shape[1],out['value'].shape[2]).cuda() + jacobian_all = torch.randn(len(kp_driving),out['jacobian'].shape[0],out['jacobian'].shape[1],2,2).cuda() + print(len(kp_driving)) + for i in range(len(kp_driving)): + # if x['name'][i] == 'LRW': + # loss_jacobian += (torch.abs(kp_driving[i]['jacobian'] - kp_driving_a[i]['jacobian']).mean())*self.loss_weights['emo'] + + # loss_value += (torch.abs(kp_driving[i]['value'].detach() - kp_driving_a[i]['value']).mean())*self.loss_weights['emo'] + # loss_classify += self.mse_loss_fn(deco_out,deco_out) + if self.train_params['type'] == 'linear_4' and x['name'][0] == 0: + + kp_all[i]['jacobian'][:,1] = kp_emo[i]['jacobian'][:,0] + kp_driving_a[i]['jacobian'][:,1] + kp_all[i]['jacobian'][:,4] = kp_emo[i]['jacobian'][:,1] + kp_driving_a[i]['jacobian'][:,4] + kp_all[i]['jacobian'][:,6] = kp_emo[i]['jacobian'][:,2] + kp_driving_a[i]['jacobian'][:,6] + kp_all[i]['jacobian'][:,8] = kp_emo[i]['jacobian'][:,3] + kp_driving_a[i]['jacobian'][:,8] + kp_all[i]['value'][:,1] = kp_emo[i]['value'][:,0] + kp_driving_a[i]['value'][:,1] + kp_all[i]['value'][:,4] = kp_emo[i]['value'][:,1] + kp_driving_a[i]['value'][:,4] + kp_all[i]['value'][:,6] = kp_emo[i]['value'][:,2] + kp_driving_a[i]['value'][:,6] + kp_all[i]['value'][:,8] = kp_emo[i]['value'][:,3] + kp_driving_a[i]['value'][:,8] + + # kp_all[i]['value'] = kp_emo[i]['value'] + kp_driving_a[i]['value'] + + + if self.train_params['smooth'] == True: + loss_smooth = 0 + loss_smooth += (torch.abs(value_all[2:,:,:,:] + value_all[:-2,:,:,:].detach() -2*value_all[1:-1,:,:,:].detach()).mean())*self.loss_weights['emo'] *100 + loss_smooth += (torch.abs(jacobian_all[2:,:,:,:] + jacobian_all[:-2,:,:,:].detach() -2*jacobian_all[1:-1,:,:,:].detach()).mean())*self.loss_weights['emo'] *100 + loss_values['loss_smooth'] = loss_smooth/len(kp_driving) + else: + loss_values['loss_smooth'] = self.mse_loss_fn(deco_out,deco_out) + if self.train_params['generator'] == 'not': + loss_values['perceptual'] = self.mse_loss_fn(deco_out,deco_out) + for i in range(1): #0,len(kp_driving),4 + + generated = self.generator(x['example_image'], kp_source=kp_source, kp_driving=kp_all[i]) + generated.update({'kp_source': kp_source, 'kp_driving': kp_all}) + elif self.train_params['generator'] == 'visual': + for i in range(0,len(kp_driving),4): #0,len(kp_driving),4 + + generated = self.generator(x['example_image'], kp_source=kp_source, kp_driving=kp_driving[i]) + generated.update({'kp_source': kp_source, 'kp_driving': kp_driving}) + + pyramide_real = self.pyramid(x['driving'][:,i]) + pyramide_generated = self.pyramid(generated['prediction']) + + if sum(self.loss_weights['perceptual']) != 0: + 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_perceptual += value_total + + length = int((len(kp_driving)-1)/4)+1 + loss_values['perceptual'] = loss_perceptual/length + elif self.train_params['generator'] == 'audio': + for i in range(0,len(kp_driving),4): #0,len(kp_driving),4 + + generated = self.generator(x['example_image'], kp_source=kp_source, kp_driving=kp_all[i]) + generated.update({'kp_source': kp_source, 'kp_driving': kp_all}) + + pyramide_real = self.pyramid(x['driving'][:,i]) + pyramide_generated = self.pyramid(generated['prediction']) + # loss_mse = nn.MSELoss(generated['prediction'],x['driving'][:,i]) + if sum(self.loss_weights['perceptual']) != 0: + 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_perceptual += value_total + + length = int((len(kp_driving)-1)/4)+1 + loss_values['perceptual'] = loss_perceptual/length + # loss_values['mse'] = loss_mse/length + + else: + print('wrong train_params: ', self.train_params['generator']) + + + + return loss_values,generated + +class GeneratorFullModel(torch.nn.Module): + """ + Merge all generator related updates into single model for better multi-gpu usage + """ + + def __init__(self, kp_extractor, kp_extractor_a, audio_feature, generator, discriminator, train_params): + super(GeneratorFullModel, self).__init__() + self.kp_extractor = kp_extractor + self.kp_extractor_a = kp_extractor_a + # self.content_encoder = content_encoder + # self.emotion_encoder = emotion_encoder + self.audio_feature = audio_feature + self.generator = generator + self.discriminator = discriminator + self.train_params = train_params + self.scales = train_params['scales'] + self.disc_scales = self.discriminator.scales + self.pyramid = ImagePyramide(self.scales, generator.num_channels) + if torch.cuda.is_available(): + self.pyramid = self.pyramid.cuda() + + self.loss_weights = train_params['loss_weights'] + + if sum(self.loss_weights['perceptual']) != 0: + self.vgg = Vgg19() + if torch.cuda.is_available(): + self.vgg = self.vgg.cuda() + + self.pca = torch.FloatTensor(np.load('.../LRW/list/U_106.npy'))[:, :16].cuda() + self.mean = torch.FloatTensor(np.load('.../LRW/list/mean_106.npy')).cuda() + + def forward(self, x): + # source_a_f = self.audio_feature(x['source_audio'],x['source_lm'],x[]) + # source_a_f = self.audio_feature(self.content_encoder(x['source_audio'].unsqueeze(1)), self.emotion_encoder(x['source_audio'].unsqueeze(1))) + # kp_source = self.kp_extractor(x['source']) + # kp_source_a = self.kp_extractor_a(x['source'], x['source_cube'], source_a_f) + # driving_a_f = self.audio_feature(self.content_encoder(x['driving_audio'].unsqueeze(1)), self.emotion_encoder(x['driving_audio'].unsqueeze(1))) + # driving_a_f = self.audio_feature(x['driving_audio']) + # kp_driving = self.kp_extractor(x['driving']) + # kp_driving_a = self.kp_extractor_a(x['driving'], x['driving_cube'], driving_a_f) + + kp_driving = [] + for i in range(16): + kp_driving.append(self.kp_extractor(x['driving'][:,i],x['driving_landmark'][:,i],self.loss_weights['equivariance_value'])) + + kp_driving_a = [] + fc_out, deco_out = self.audio_feature(x['example_landmark'], x['driving_audio'], x['driving_pose']) + fake_lmark=fc_out + x['example_landmark'].expand_as(fc_out) + + + fake_lmark = torch.mm( fake_lmark, self.pca.t() ) + fake_lmark = fake_lmark + self.mean.expand_as(fake_lmark) + + + fake_lmark = fake_lmark.unsqueeze(0) + + # for i in range(16): + # kp_driving_a.append() + + # generated = self.generator(x['source'], kp_source=kp_source, kp_driving=kp_driving) + # generated.update({'kp_source': kp_source, 'kp_driving': kp_driving}) + + loss_values = {} + + pyramide_real = self.pyramid(x['driving']) + pyramide_generated = self.pyramid(generated['prediction']) + + if self.loss_weights['audio'] != 0: + value = torch.abs(kp_source['jacobian'].detach() - kp_source_a['jacobian'].detach()).mean() + torch.abs(kp_driving['jacobian'].detach() - kp_driving_a['jacobian']).mean() + value = value/2 + loss_values['jacobian'] = value*self.loss_weights['audio'] + value = torch.abs(kp_source['heatmap'].detach() - kp_source_a['heatmap'].detach()).mean() + torch.abs(kp_driving['heatmap'].detach() - kp_driving_a['heatmap']).mean() + value = value/2 + loss_values['heatmap'] = value*self.loss_weights['audio'] + value = torch.abs(kp_source['value'].detach() - kp_source_a['value'].detach()).mean() + torch.abs(kp_driving['value'].detach() - kp_driving_a['value']).mean() + value = value/2 + loss_values['value'] = value*self.loss_weights['audio'] + + if sum(self.loss_weights['perceptual']) != 0: + 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['generator_gan'] != 0: + discriminator_maps_generated = self.discriminator(pyramide_generated, kp=detach_kp(kp_driving)) + discriminator_maps_real = self.discriminator(pyramide_real, kp=detach_kp(kp_driving)) + value_total = 0 + for scale in self.disc_scales: + key = 'prediction_map_%s' % scale + value = ((1 - discriminator_maps_generated[key]) ** 2).mean() + value_total += self.loss_weights['generator_gan'] * value + loss_values['gen_gan'] = value_total + + if sum(self.loss_weights['feature_matching']) != 0: + value_total = 0 + for scale in self.disc_scales: + key = 'feature_maps_%s' % scale + for i, (a, b) in enumerate(zip(discriminator_maps_real[key], discriminator_maps_generated[key])): + if self.loss_weights['feature_matching'][i] == 0: + continue + value = torch.abs(a - b).mean() + value_total += self.loss_weights['feature_matching'][i] * value + loss_values['feature_matching'] = value_total + + if (self.loss_weights['equivariance_value'] + self.loss_weights['equivariance_jacobian']) != 0: + transform = Transform(x['driving'].shape[0], **self.train_params['transform_params']) + transformed_frame = transform.transform_frame(x['driving']) + transformed_landmark = transform.inverse_warp_coordinates(x['driving_landmark']) + transformed_kp = self.kp_extractor(transformed_frame) + + generated['transformed_frame'] = transformed_frame + generated['transformed_kp'] = transformed_kp + + ## Value loss part + if self.loss_weights['equivariance_value'] != 0: + value = torch.abs(kp_driving['value'] - transform.warp_coordinates(transformed_kp['value'])).mean() + loss_values['equivariance_value'] = self.loss_weights['equivariance_value'] * value + + ## jacobian loss part + if self.loss_weights['equivariance_jacobian'] != 0: + jacobian_transformed = torch.matmul(transform.jacobian(transformed_kp['value']), + transformed_kp['jacobian']) + + normed_driving = torch.inverse(kp_driving['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).mean() + loss_values['equivariance_jacobian'] = self.loss_weights['equivariance_jacobian'] * value + + return loss_values, generated + + +class DiscriminatorFullModel(torch.nn.Module): + """ + Merge all discriminator related updates into single model for better multi-gpu usage + """ + + def __init__(self, kp_extractor, generator, discriminator, train_params): + super(DiscriminatorFullModel, self).__init__() + self.kp_extractor = kp_extractor + self.generator = generator + self.discriminator = discriminator + self.train_params = train_params + self.scales = self.discriminator.scales + self.pyramid = ImagePyramide(self.scales, generator.num_channels) + if torch.cuda.is_available(): + self.pyramid = self.pyramid.cuda() + + self.loss_weights = train_params['loss_weights'] + + def forward(self, x, generated): + pyramide_real = self.pyramid(x['driving']) + pyramide_generated = self.pyramid(generated['prediction'].detach()) + + kp_driving = generated['kp_driving'] + discriminator_maps_generated = self.discriminator(pyramide_generated, kp=detach_kp(kp_driving)) + discriminator_maps_real = self.discriminator(pyramide_real, kp=detach_kp(kp_driving)) + + loss_values = {} + value_total = 0 + for scale in self.scales: + key = 'prediction_map_%s' % scale + value = (1 - discriminator_maps_real[key]) ** 2 + discriminator_maps_generated[key] ** 2 + value_total += self.loss_weights['discriminator_gan'] * value.mean() + loss_values['disc_gan'] = value_total + + return loss_values diff --git a/FONT/modules/ops.py b/FONT/modules/ops.py new file mode 100644 index 0000000000000000000000000000000000000000..ed4f285fdee447e715089e14544d5e47b90c7fb9 --- /dev/null +++ b/FONT/modules/ops.py @@ -0,0 +1,77 @@ +import torch +import torchvision +import torch.nn as nn +import torch.nn.init as init +from torch.autograd import Variable + + +def linear(channel_in, channel_out, + activation=nn.ReLU, + normalizer=nn.BatchNorm1d): + layer = list() + bias = True if not normalizer else False + + layer.append(nn.Linear(channel_in, channel_out, bias=bias)) + _apply(layer, activation, normalizer, channel_out) + # init.kaiming_normal(layer[0].weight) + + return nn.Sequential(*layer) + + +def conv2d(channel_in, channel_out, + ksize=3, stride=1, padding=1, + activation=nn.ReLU, + normalizer=nn.BatchNorm2d): + layer = list() + bias = True if not normalizer else False + + layer.append(nn.Conv2d(channel_in, channel_out, + ksize, stride, padding, + bias=bias)) + _apply(layer, activation, normalizer, channel_out) + # init.kaiming_normal(layer[0].weight) + + return nn.Sequential(*layer) + + +def conv_transpose2d(channel_in, channel_out, + ksize=4, stride=2, padding=1, + activation=nn.ReLU, + normalizer=nn.BatchNorm2d): + layer = list() + bias = True if not normalizer else False + + layer.append(nn.ConvTranspose2d(channel_in, channel_out, + ksize, stride, padding, + bias=bias)) + _apply(layer, activation, normalizer, channel_out) + # init.kaiming_normal(layer[0].weight) + + return nn.Sequential(*layer) + + +def nn_conv2d(channel_in, channel_out, + ksize=3, stride=1, padding=1, + scale_factor=2, + activation=nn.ReLU, + normalizer=nn.BatchNorm2d): + layer = list() + bias = True if not normalizer else False + + layer.append(nn.UpsamplingNearest2d(scale_factor=scale_factor)) + layer.append(nn.Conv2d(channel_in, channel_out, + ksize, stride, padding, + bias=bias)) + _apply(layer, activation, normalizer, channel_out) + # init.kaiming_normal(layer[1].weight) + + return nn.Sequential(*layer) + + +def _apply(layer, activation, normalizer, channel_out=None): + if normalizer: + layer.append(normalizer(channel_out)) + if activation: + layer.append(activation()) + return layer + diff --git a/FONT/modules/stylegan2.py b/FONT/modules/stylegan2.py new file mode 100644 index 0000000000000000000000000000000000000000..8367036b28eb70951f6e6d406a32d4359e625b26 --- /dev/null +++ b/FONT/modules/stylegan2.py @@ -0,0 +1,923 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Thu Jul 8 01:03:50 2021 + +@author: thea +""" + +""" +The network architectures is based on PyTorch implemenation of StyleGAN2Encoder. +Original PyTorch repo: https://github.com/rosinality/style-based-gan-pytorch +Origianl StyelGAN2 paper: https://github.com/NVlabs/stylegan2 +We use the network architeture for our single-image traning setting. +""" + +import math +import numpy as np +import random + +import torch +from torch import nn +from torch.nn import functional as F + + +def fused_leaky_relu(input, bias, negative_slope=0.2, scale=2 ** 0.5): + return F.leaky_relu(input + bias, negative_slope) * scale + + +class FusedLeakyReLU(nn.Module): + def __init__(self, channel, negative_slope=0.2, scale=2 ** 0.5): + super().__init__() + self.bias = nn.Parameter(torch.zeros(1, channel, 1, 1)) + self.negative_slope = negative_slope + self.scale = scale + + def forward(self, input): + # print("FusedLeakyReLU: ", input.abs().mean()) + out = fused_leaky_relu(input, self.bias, + self.negative_slope, + self.scale) + # print("FusedLeakyReLU: ", out.abs().mean()) + return out + + +def upfirdn2d_native( + input, kernel, up_x, up_y, down_x, down_y, pad_x0, pad_x1, pad_y0, pad_y1 +): + _, minor, in_h, in_w = input.shape + kernel_h, kernel_w = kernel.shape + + out = input.view(-1, minor, in_h, 1, in_w, 1) + out = F.pad(out, [0, up_x - 1, 0, 0, 0, up_y - 1, 0, 0]) + out = out.view(-1, minor, in_h * up_y, in_w * up_x) + + out = F.pad( + out, [max(pad_x0, 0), max(pad_x1, 0), max(pad_y0, 0), max(pad_y1, 0)] + ) + out = out[ + :, + :, + max(-pad_y0, 0): out.shape[2] - max(-pad_y1, 0), + max(-pad_x0, 0): out.shape[3] - max(-pad_x1, 0), + ] + + # out = out.permute(0, 3, 1, 2) + out = out.reshape( + [-1, 1, in_h * up_y + pad_y0 + pad_y1, in_w * up_x + pad_x0 + pad_x1] + ) + w = torch.flip(kernel, [0, 1]).view(1, 1, kernel_h, kernel_w) + out = F.conv2d(out, w) + out = out.reshape( + -1, + minor, + in_h * up_y + pad_y0 + pad_y1 - kernel_h + 1, + in_w * up_x + pad_x0 + pad_x1 - kernel_w + 1, + ) + # out = out.permute(0, 2, 3, 1) + + return out[:, :, ::down_y, ::down_x] + + +def upfirdn2d(input, kernel, up=1, down=1, pad=(0, 0)): + return upfirdn2d_native(input, kernel, up, up, down, down, pad[0], pad[1], pad[0], pad[1]) + + +class PixelNorm(nn.Module): + def __init__(self): + super().__init__() + + def forward(self, input): + return input * torch.rsqrt(torch.mean(input ** 2, dim=1, keepdim=True) + 1e-8) + + +def make_kernel(k): + k = torch.tensor(k, dtype=torch.float32) + + if len(k.shape) == 1: + k = k[None, :] * k[:, None] + + k /= k.sum() + + return k + + +class Upsample(nn.Module): + def __init__(self, kernel, factor=2): + super().__init__() + + self.factor = factor + kernel = make_kernel(kernel) * (factor ** 2) + self.register_buffer('kernel', kernel) + + p = kernel.shape[0] - factor + + pad0 = (p + 1) // 2 + factor - 1 + pad1 = p // 2 + + self.pad = (pad0, pad1) + + def forward(self, input): + out = upfirdn2d(input, self.kernel, up=self.factor, down=1, pad=self.pad) + + return out + + +class Downsample(nn.Module): + def __init__(self, kernel, factor=2): + super().__init__() + + self.factor = factor + kernel = make_kernel(kernel) + self.register_buffer('kernel', kernel) + + p = kernel.shape[0] - factor + + pad0 = (p + 1) // 2 + pad1 = p // 2 + + self.pad = (pad0, pad1) + + def forward(self, input): + out = upfirdn2d(input, self.kernel, up=1, down=self.factor, pad=self.pad) + + return out + + +class Blur(nn.Module): + def __init__(self, kernel, pad, upsample_factor=1): + super().__init__() + + kernel = make_kernel(kernel) + + if upsample_factor > 1: + kernel = kernel * (upsample_factor ** 2) + + self.register_buffer('kernel', kernel) + + self.pad = pad + + def forward(self, input): + out = upfirdn2d(input, self.kernel, pad=self.pad) + + return out + + +class EqualConv2d(nn.Module): + def __init__( + self, in_channel, out_channel, kernel_size, stride=1, padding=0, bias=True + ): + super().__init__() + + self.weight = nn.Parameter( + torch.randn(out_channel, in_channel, kernel_size, kernel_size) + ) + self.scale = math.sqrt(1) / math.sqrt(in_channel * (kernel_size ** 2)) + + self.stride = stride + self.padding = padding + + if bias: + self.bias = nn.Parameter(torch.zeros(out_channel)) + + else: + self.bias = None + + def forward(self, input): + # print("Before EqualConv2d: ", input.abs().mean()) + out = F.conv2d( + input, + self.weight * self.scale, + bias=self.bias, + stride=self.stride, + padding=self.padding, + ) + # print("After EqualConv2d: ", out.abs().mean(), (self.weight * self.scale).abs().mean()) + + return out + + def __repr__(self): + return ( + f'{self.__class__.__name__}({self.weight.shape[1]}, {self.weight.shape[0]},' + f' {self.weight.shape[2]}, stride={self.stride}, padding={self.padding})' + ) + + +class EqualLinear(nn.Module): + def __init__( + self, in_dim, out_dim, bias=True, bias_init=0, lr_mul=1, activation=None + ): + super().__init__() + + self.weight = nn.Parameter(torch.randn(out_dim, in_dim).div_(lr_mul)) + + if bias: + self.bias = nn.Parameter(torch.zeros(out_dim).fill_(bias_init)) + + else: + self.bias = None + + self.activation = activation + + self.scale = (math.sqrt(1) / math.sqrt(in_dim)) * lr_mul + self.lr_mul = lr_mul + + def forward(self, input): + if self.activation: + out = F.linear(input, self.weight * self.scale) + out = fused_leaky_relu(out, self.bias * self.lr_mul) + + else: + out = F.linear( + input, self.weight * self.scale, bias=self.bias * self.lr_mul + ) + + return out + + def __repr__(self): + return ( + f'{self.__class__.__name__}({self.weight.shape[1]}, {self.weight.shape[0]})' + ) + + +class ScaledLeakyReLU(nn.Module): + def __init__(self, negative_slope=0.2): + super().__init__() + + self.negative_slope = negative_slope + + def forward(self, input): + out = F.leaky_relu(input, negative_slope=self.negative_slope) + + return out * math.sqrt(2) + + +class ModulatedConv2d(nn.Module): + def __init__( + self, + in_channel, + out_channel, + kernel_size, + style_dim, + demodulate=True, + upsample=False, + downsample=False, + blur_kernel=[1, 3, 3, 1], + ): + super().__init__() + + self.eps = 1e-8 + self.kernel_size = kernel_size + self.in_channel = in_channel + self.out_channel = out_channel + self.upsample = upsample + self.downsample = downsample + + if upsample: + factor = 2 + p = (len(blur_kernel) - factor) - (kernel_size - 1) + pad0 = (p + 1) // 2 + factor - 1 + pad1 = p // 2 + 1 + + self.blur = Blur(blur_kernel, pad=(pad0, pad1), upsample_factor=factor) + + if downsample: + factor = 2 + p = (len(blur_kernel) - factor) + (kernel_size - 1) + pad0 = (p + 1) // 2 + pad1 = p // 2 + + self.blur = Blur(blur_kernel, pad=(pad0, pad1)) + + fan_in = in_channel * kernel_size ** 2 + self.scale = math.sqrt(1) / math.sqrt(fan_in) + self.padding = kernel_size // 2 + + self.weight = nn.Parameter( + torch.randn(1, out_channel, in_channel, kernel_size, kernel_size) + ) + + if style_dim is not None and style_dim > 0: + self.modulation = EqualLinear(style_dim, in_channel, bias_init=1) + + self.demodulate = demodulate + + def __repr__(self): + return ( + f'{self.__class__.__name__}({self.in_channel}, {self.out_channel}, {self.kernel_size}, ' + f'upsample={self.upsample}, downsample={self.downsample})' + ) + + def forward(self, input, style): + batch, in_channel, height, width = input.shape + + if style is not None: + style = self.modulation(style).view(batch, 1, in_channel, 1, 1) + else: + style = torch.ones(batch, 1, in_channel, 1, 1).cuda() + weight = self.scale * self.weight * style + + if self.demodulate: + demod = torch.rsqrt(weight.pow(2).sum([2, 3, 4]) + 1e-8) + weight = weight * demod.view(batch, self.out_channel, 1, 1, 1) + + weight = weight.view( + batch * self.out_channel, in_channel, self.kernel_size, self.kernel_size + ) + + if self.upsample: + input = input.view(1, batch * in_channel, height, width) + weight = weight.view( + batch, self.out_channel, in_channel, self.kernel_size, self.kernel_size + ) + weight = weight.transpose(1, 2).reshape( + batch * in_channel, self.out_channel, self.kernel_size, self.kernel_size + ) + out = F.conv_transpose2d(input, weight, padding=0, stride=2, groups=batch) + _, _, height, width = out.shape + out = out.view(batch, self.out_channel, height, width) + out = self.blur(out) + + elif self.downsample: + input = self.blur(input) + _, _, height, width = input.shape + input = input.view(1, batch * in_channel, height, width) + out = F.conv2d(input, weight, padding=0, stride=2, groups=batch) + _, _, height, width = out.shape + out = out.view(batch, self.out_channel, height, width) + + else: + input = input.view(1, batch * in_channel, height, width) + out = F.conv2d(input, weight, padding=self.padding, groups=batch) + _, _, height, width = out.shape + out = out.view(batch, self.out_channel, height, width) + + return out + + +class NoiseInjection(nn.Module): + def __init__(self): + super().__init__() + + self.weight = nn.Parameter(torch.zeros(1)) + + def forward(self, image, noise=None): + if noise is None: + batch, _, height, width = image.shape + noise = image.new_empty(batch, 1, height, width).normal_() + + return image + self.weight * noise + + +class ConstantInput(nn.Module): + def __init__(self, channel, size=4): + super().__init__() + + self.input = nn.Parameter(torch.randn(1, channel, size, size)) + + def forward(self, input): + batch = input.shape[0] + out = self.input.repeat(batch, 1, 1, 1) + + return out + + +class StyledConv(nn.Module): + def __init__( + self, + in_channel, + out_channel, + kernel_size, + style_dim=None, + upsample=False, + blur_kernel=[1, 3, 3, 1], + demodulate=True, + inject_noise=False, #True + ): + super().__init__() + + self.inject_noise = inject_noise + self.conv = ModulatedConv2d( + in_channel, + out_channel, + kernel_size, + style_dim, + upsample=upsample, + blur_kernel=blur_kernel, + demodulate=demodulate, + ) + + self.noise = NoiseInjection() + # self.bias = nn.Parameter(torch.zeros(1, out_channel, 1, 1)) + # self.activate = ScaledLeakyReLU(0.2) + self.activate = FusedLeakyReLU(out_channel) + + def forward(self, input, style=None, noise=None): + out = self.conv(input, style) + if self.inject_noise: + out = self.noise(out, noise=noise) + # out = out + self.bias + out = self.activate(out) + + return out + + +class ToRGB(nn.Module): + def __init__(self, in_channel, style_dim, upsample=True, blur_kernel=[1, 3, 3, 1]): + super().__init__() + + if upsample: + self.upsample = Upsample(blur_kernel) + + self.conv = ModulatedConv2d(in_channel, 3+32, 1, style_dim, demodulate=False) + self.bias = nn.Parameter(torch.zeros(1, 3+32, 1, 1)) + + def forward(self, input, style, skip=None): + out = self.conv(input, style) + out = out + self.bias + + if skip is not None: + skip = self.upsample(skip) + + out = out + skip + + return out + + +class Generator(nn.Module): + def __init__( + self, + size, + style_dim, + n_mlp, + channel_multiplier=1, + blur_kernel=[1, 3, 3, 1], + lr_mlp=0.01, + ): + super().__init__() + + self.size = size + + self.style_dim = style_dim + + layers = [PixelNorm()] + + for i in range(n_mlp): + layers.append( + EqualLinear( + style_dim, style_dim, lr_mul=lr_mlp, activation='fused_lrelu' + ) + ) + + self.style = nn.Sequential(*layers) + + self.channels = { + 4: 256, + 8: 256, + 16: 128, + 32: 64, + 64: 32 * channel_multiplier, + 128: 16 * channel_multiplier, + 256: 8 * channel_multiplier, + 512: 4 * channel_multiplier, + 1024: 2 * channel_multiplier, + } + + self.input = ConstantInput(self.channels[4]) + self.conv1 = StyledConv( + self.channels[4], self.channels[4], 3, style_dim, blur_kernel=blur_kernel + ) + self.to_rgb1 = ToRGB(self.channels[4], style_dim, upsample=False) + + self.log_size = int(math.log(size, 2)) + self.num_layers = (self.log_size - 2) * 2 + 1 + + self.convs = nn.ModuleList() + self.upsamples = nn.ModuleList() + self.to_rgbs = nn.ModuleList() + self.noises = nn.Module() + + in_channel = self.channels[4] + + for layer_idx in range(self.num_layers): + res = (layer_idx + 5) // 2 + shape = [1, 1, 2 ** res, 2 ** res] + self.noises.register_buffer(f'noise_{layer_idx}', torch.randn(*shape)) + + for i in range(3, self.log_size + 1): + out_channel = self.channels[2 ** i] + + self.convs.append( + StyledConv( + in_channel, + out_channel, + 3, + style_dim, + upsample=True, + blur_kernel=blur_kernel, + ) + ) + + self.convs.append( + StyledConv( + out_channel, out_channel, 3, style_dim, blur_kernel=blur_kernel + ) + ) + + self.to_rgbs.append(ToRGB(out_channel, style_dim)) + + in_channel = out_channel + + self.n_latent = self.log_size * 2 - 2 + + def make_noise(self): + device = self.input.input.device + + noises = [torch.randn(1, 1, 2 ** 2, 2 ** 2, device=device)] + + for i in range(3, self.log_size + 1): + for _ in range(2): + noises.append(torch.randn(1, 1, 2 ** i, 2 ** i, device=device)) + + return noises + + def mean_latent(self, n_latent): + latent_in = torch.randn( + n_latent, self.style_dim, device=self.input.input.device + ) + latent = self.style(latent_in).mean(0, keepdim=True) + + return latent + + def get_latent(self, input): + return self.style(input) + + def forward( + self, + styles, + return_latents=False, + inject_index=None, + truncation=1, + truncation_latent=None, + input_is_latent=False, + noise=None, + randomize_noise=True, + ): + if not input_is_latent: + styles = [self.style(s) for s in styles] + + if noise is None: + if randomize_noise: + noise = [None] * self.num_layers + else: + noise = [ + getattr(self.noises, f'noise_{i}') for i in range(self.num_layers) + ] + + if truncation < 1: + style_t = [] + + for style in styles: + style_t.append( + truncation_latent + truncation * (style - truncation_latent) + ) + + styles = style_t + + if len(styles) < 2: + inject_index = self.n_latent + + if len(styles[0].shape) < 3: + latent = styles[0].unsqueeze(1).repeat(1, inject_index, 1) + + else: + latent = styles[0] + + else: + if inject_index is None: + inject_index = random.randint(1, self.n_latent - 1) + + latent = styles[0].unsqueeze(1).repeat(1, inject_index, 1) + latent2 = styles[1].unsqueeze(1).repeat(1, self.n_latent - inject_index, 1) + + latent = torch.cat([latent, latent2], 1) + + # out = self.input(latent) + out = styles[0].unsqueeze(-1).unsqueeze(-1).repeat(1,1,4,4) + out = self.conv1(out, latent[:, 0], noise=noise[0]) + + skip = self.to_rgb1(out, latent[:, 1]) + + i = 1 + for conv1, conv2, noise1, noise2, to_rgb in zip( + self.convs[::2], self.convs[1::2], noise[1::2], noise[2::2], self.to_rgbs + ): + out = conv1(out, latent[:, i], noise=noise1) + out = conv2(out, latent[:, i + 1], noise=noise2) + skip = to_rgb(out, latent[:, i + 2], skip) + + i += 2 + + image = skip + + if return_latents: + return image, latent + + else: + return image, None + + +class ConvLayer(nn.Sequential): + def __init__( + self, + in_channel, + out_channel, + kernel_size, + downsample=False, + blur_kernel=[1, 3, 3, 1], + bias=True, + activate=True, + ): + layers = [] + + if downsample: + factor = 2 + p = (len(blur_kernel) - factor) + (kernel_size - 1) + pad0 = (p + 1) // 2 + pad1 = p // 2 + + layers.append(Blur(blur_kernel, pad=(pad0, pad1))) + + stride = 2 + self.padding = 0 + + else: + stride = 1 + self.padding = kernel_size // 2 + + layers.append( + EqualConv2d( + in_channel, + out_channel, + kernel_size, + padding=self.padding, + stride=stride, + bias=bias and not activate, + ) + ) + + if activate: + if bias: + layers.append(FusedLeakyReLU(out_channel)) + + else: + layers.append(ScaledLeakyReLU(0.2)) + + super().__init__(*layers) + + +class ResBlock(nn.Module): + def __init__(self, in_channel, out_channel, blur_kernel=[1, 3, 3, 1], downsample=True, skip_gain=1.0): + super().__init__() + + self.skip_gain = skip_gain + self.conv1 = ConvLayer(in_channel, in_channel, 3) + self.conv2 = ConvLayer(in_channel, out_channel, 3, downsample=downsample, blur_kernel=blur_kernel) + + if in_channel != out_channel or downsample: + self.skip = ConvLayer( + in_channel, out_channel, 1, downsample=downsample, activate=False, bias=False + ) + else: + self.skip = nn.Identity() + + def forward(self, input): + out = self.conv1(input) + out = self.conv2(out) + + skip = self.skip(input) + out = (out * self.skip_gain + skip) / math.sqrt(self.skip_gain ** 2 + 1.0) + + return out + + +class StyleGAN2Discriminator(nn.Module): + def __init__(self, input_nc, ndf=64, n_layers=3, no_antialias=False, size=None, opt=None): + super().__init__() + self.opt = opt + self.stddev_group = 16 + if size is None: + size = 2 ** int((np.rint(np.log2(min(opt.load_size, opt.crop_size))))) + if "patch" in self.opt.netD and self.opt.D_patch_size is not None: + size = 2 ** int(np.log2(self.opt.D_patch_size)) + + blur_kernel = [1, 3, 3, 1] + channel_multiplier = ndf / 64 + channels = { + 4: min(384, int(4096 * channel_multiplier)), + 8: min(384, int(2048 * channel_multiplier)), + 16: min(384, int(1024 * channel_multiplier)), + 32: min(384, int(512 * channel_multiplier)), + 64: int(256 * channel_multiplier), + 128: int(128 * channel_multiplier), + 256: int(64 * channel_multiplier), + 512: int(32 * channel_multiplier), + 1024: int(16 * channel_multiplier), + } + + convs = [ConvLayer(3, channels[size], 1)] + + log_size = int(math.log(size, 2)) + + in_channel = channels[size] + + if "smallpatch" in self.opt.netD: + final_res_log2 = 4 + elif "patch" in self.opt.netD: + final_res_log2 = 3 + else: + final_res_log2 = 2 + + for i in range(log_size, final_res_log2, -1): + out_channel = channels[2 ** (i - 1)] + + convs.append(ResBlock(in_channel, out_channel, blur_kernel)) + + in_channel = out_channel + + self.convs = nn.Sequential(*convs) + + if False and "tile" in self.opt.netD: + in_channel += 1 + self.final_conv = ConvLayer(in_channel, channels[4], 3) + if "patch" in self.opt.netD: + self.final_linear = ConvLayer(channels[4], 1, 3, bias=False, activate=False) + else: + self.final_linear = nn.Sequential( + EqualLinear(channels[4] * 4 * 4, channels[4], activation='fused_lrelu'), + EqualLinear(channels[4], 1), + ) + + def forward(self, input, get_minibatch_features=False): + if "patch" in self.opt.netD and self.opt.D_patch_size is not None: + h, w = input.size(2), input.size(3) + y = torch.randint(h - self.opt.D_patch_size, ()) + x = torch.randint(w - self.opt.D_patch_size, ()) + input = input[:, :, y:y + self.opt.D_patch_size, x:x + self.opt.D_patch_size] + out = input + for i, conv in enumerate(self.convs): + out = conv(out) + # print(i, out.abs().mean()) + # out = self.convs(input) + + batch, channel, height, width = out.shape + + if False and "tile" in self.opt.netD: + group = min(batch, self.stddev_group) + stddev = out.view( + group, -1, 1, channel // 1, height, width + ) + stddev = torch.sqrt(stddev.var(0, unbiased=False) + 1e-8) + stddev = stddev.mean([2, 3, 4], keepdim=True).squeeze(2) + stddev = stddev.repeat(group, 1, height, width) + out = torch.cat([out, stddev], 1) + + out = self.final_conv(out) + # print(out.abs().mean()) + + if "patch" not in self.opt.netD: + out = out.view(batch, -1) + out = self.final_linear(out) + + return out + + +class TileStyleGAN2Discriminator(StyleGAN2Discriminator): + def forward(self, input): + B, C, H, W = input.size(0), input.size(1), input.size(2), input.size(3) + size = self.opt.D_patch_size + Y = H // size + X = W // size + input = input.view(B, C, Y, size, X, size) + input = input.permute(0, 2, 4, 1, 3, 5).contiguous().view(B * Y * X, C, size, size) + return super().forward(input) + + +class StyleGAN2Encoder(nn.Module): + def __init__(self, input_nc, output_nc, ngf=64, use_dropout=False, n_blocks=6, padding_type='reflect', no_antialias=False, opt=None): + super().__init__() + assert opt is not None + self.opt = opt + channel_multiplier = ngf / 32 + channels = { + 4: min(512, int(round(4096 * channel_multiplier))), + 8: min(512, int(round(2048 * channel_multiplier))), + 16: min(512, int(round(1024 * channel_multiplier))), + 32: min(512, int(round(512 * channel_multiplier))), + 64: int(round(256 * channel_multiplier)), + 128: int(round(128 * channel_multiplier)), + 256: int(round(64 * channel_multiplier)), + 512: int(round(32 * channel_multiplier)), + 1024: int(round(16 * channel_multiplier)), + } + + blur_kernel = [1, 3, 3, 1] + + cur_res = 2 ** int((np.rint(np.log2(min(opt.load_size, opt.crop_size))))) + convs = [nn.Identity(), + ConvLayer(3, channels[cur_res], 1)] + + num_downsampling = self.opt.stylegan2_G_num_downsampling + for i in range(num_downsampling): + in_channel = channels[cur_res] + out_channel = channels[cur_res // 2] + convs.append(ResBlock(in_channel, out_channel, blur_kernel, downsample=True)) + cur_res = cur_res // 2 + + for i in range(n_blocks // 2): + n_channel = channels[cur_res] + convs.append(ResBlock(n_channel, n_channel, downsample=False)) + + self.convs = nn.Sequential(*convs) + + def forward(self, input, layers=[], get_features=False): + feat = input + feats = [] + if -1 in layers: + layers.append(len(self.convs) - 1) + for layer_id, layer in enumerate(self.convs): + feat = layer(feat) + # print(layer_id, " features ", feat.abs().mean()) + if layer_id in layers: + feats.append(feat) + + if get_features: + return feat, feats + else: + return feat + + +class StyleGAN2Decoder(nn.Module): + def __init__(self, input_nc, output_nc, ngf=64, use_dropout=False, n_blocks=6, padding_type='reflect', no_antialias=False, opt=None): + super().__init__() + assert opt is not None + self.opt = opt + + blur_kernel = [1, 3, 3, 1] + + channel_multiplier = ngf / 32 + channels = { + 4: min(512, int(round(4096 * channel_multiplier))), + 8: min(512, int(round(2048 * channel_multiplier))), + 16: min(512, int(round(1024 * channel_multiplier))), + 32: min(512, int(round(512 * channel_multiplier))), + 64: int(round(256 * channel_multiplier)), + 128: int(round(128 * channel_multiplier)), + 256: int(round(64 * channel_multiplier)), + 512: int(round(32 * channel_multiplier)), + 1024: int(round(16 * channel_multiplier)), + } + + num_downsampling = self.opt.stylegan2_G_num_downsampling + cur_res = 2 ** int((np.rint(np.log2(min(opt.load_size, opt.crop_size))))) // (2 ** num_downsampling) + convs = [] + + for i in range(n_blocks // 2): + n_channel = channels[cur_res] + convs.append(ResBlock(n_channel, n_channel, downsample=False)) + + for i in range(num_downsampling): + in_channel = channels[cur_res] + out_channel = channels[cur_res * 2] + inject_noise = "small" not in self.opt.netG + convs.append( + StyledConv(in_channel, out_channel, 3, upsample=True, blur_kernel=blur_kernel, inject_noise=inject_noise) + ) + cur_res = cur_res * 2 + + convs.append(ConvLayer(channels[cur_res], 3, 1)) + + self.convs = nn.Sequential(*convs) + + def forward(self, input): + return self.convs(input) + + +class StyleGAN2Generator(nn.Module): + def __init__(self, input_nc, output_nc, ngf=64, use_dropout=False, n_blocks=6, padding_type='reflect', no_antialias=False, opt=None): + super().__init__() + self.opt = opt + self.encoder = StyleGAN2Encoder(input_nc, output_nc, ngf, use_dropout, n_blocks, padding_type, no_antialias, opt) + self.decoder = StyleGAN2Decoder(input_nc, output_nc, ngf, use_dropout, n_blocks, padding_type, no_antialias, opt) + + def forward(self, input, layers=[], encode_only=False): + feat, feats = self.encoder(input, layers, True) + if encode_only: + return feats + else: + fake = self.decoder(feat) + + if len(layers) > 0: + return fake, feats + else: + return fake \ No newline at end of file diff --git a/FONT/modules/util.py b/FONT/modules/util.py new file mode 100644 index 0000000000000000000000000000000000000000..4be14bb683856f60b37680a40694f41a6c005f97 --- /dev/null +++ b/FONT/modules/util.py @@ -0,0 +1,2008 @@ +from torch import nn + +import torch.nn.functional as F +import torch +import numpy as np +import cv2 +from FONT.sync_batchnorm import SynchronizedBatchNorm2d as BatchNorm2d + +from .stylegan2 import Generator + +import torch.nn as nn +import math +import torch.utils.model_zoo as model_zoo +from .function import adaptive_instance_normalization as adain + +import pdb +from .conv import Conv2dTranspose, Conv2d, nonorm_Conv2d + + + +# Misc +img2mse = lambda x, y : torch.mean((x - y) ** 2) +mse2psnr = lambda x : -10. * torch.log(x) / torch.log(torch.Tensor([10.])) +to8b = lambda x : (255*np.clip(x,0,1)).astype(np.uint8) + + +class InstanceNorm(nn.Module): + def __init__(self, epsilon=1e-8): + """ + @notice: avoid in-place ops. + https://discuss.pytorch.org/t/encounter-the-runtimeerror-one-of-the-variables-needed-for-gradient-computation-has-been-modified-by-an-inplace-operation/836/3 + """ + super(InstanceNorm, self).__init__() + self.epsilon = epsilon + + def forward(self, x): + x = x - torch.mean(x, (2, 3), True) + tmp = torch.mul(x, x) # or x ** 2 + tmp = torch.rsqrt(torch.mean(tmp, (2, 3), True) + self.epsilon) + return x * tmp + +class ApplyStyle(nn.Module): + """ + @ref: https://github.com/lernapparat/lernapparat/blob/master/style_gan/pytorch_style_gan.ipynb + """ + def __init__(self, latent_size, channels, use_wscale): + super(ApplyStyle, self).__init__() + self.linear = FC(latent_size, + channels * 2, + gain=1.0, + use_wscale=use_wscale) + + def forward(self, x, latent): + style = self.linear(latent) # style => [batch_size, n_channels*2] + shape = [-1, 2, x.size(1), 1, 1] + style = style.view(shape) # [batch_size, 2, n_channels, ...] + x = x * (style[:, 0] + 1.) + style[:, 1] + return x + + +class FC(nn.Module): + def __init__(self, + in_channels, + out_channels, + gain=2**(0.5), + use_wscale=False, + lrmul=1.0, + bias=True): + """ + The complete conversion of Dense/FC/Linear Layer of original Tensorflow version. + """ + super(FC, self).__init__() + he_std = gain * in_channels ** (-0.5) # He init + if use_wscale: + init_std = 1.0 / lrmul + self.w_lrmul = he_std * lrmul + else: + init_std = he_std / lrmul + self.w_lrmul = lrmul + + self.weight = torch.nn.Parameter(torch.randn(out_channels, in_channels) * init_std) + if bias: + self.bias = torch.nn.Parameter(torch.zeros(out_channels)) + self.b_lrmul = lrmul + else: + self.bias = None + + def forward(self, x): + if self.bias is not None: + out = F.linear(x, self.weight * self.w_lrmul, self.bias * self.b_lrmul) + else: + out = F.linear(x, self.weight * self.w_lrmul) + out = F.leaky_relu(out, 0.2, inplace=True) + return out + + +# Positional encoding (section 5.1) +class Embedder: + def __init__(self, **kwargs): + self.kwargs = kwargs + self.create_embedding_fn() + + def create_embedding_fn(self): + embed_fns = [] + d = self.kwargs['input_dims'] + out_dim = 0 + if self.kwargs['include_input']: + embed_fns.append(lambda x : x) + out_dim += d + + max_freq = self.kwargs['max_freq_log2'] + N_freqs = self.kwargs['num_freqs'] + + if self.kwargs['log_sampling']: + freq_bands = 2.**torch.linspace(0., max_freq, steps=N_freqs) + else: + freq_bands = torch.linspace(2.**0., 2.**max_freq, steps=N_freqs) + + for freq in freq_bands: + for p_fn in self.kwargs['periodic_fns']: + embed_fns.append(lambda x, p_fn=p_fn, freq=freq : p_fn(x * freq)) + out_dim += d + + self.embed_fns = embed_fns + self.out_dim = out_dim + + def embed(self, inputs): + return torch.cat([fn(inputs) for fn in self.embed_fns], -1) + + +def get_embedder(multires, i=0): + if i == -1: + return nn.Identity(), 6 + + embed_kwargs = { + 'include_input' : True, + 'input_dims' : 6, + 'max_freq_log2' : multires-1, + 'num_freqs' : multires, + 'log_sampling' : True, + 'periodic_fns' : [torch.sin, torch.cos], + } + + embedder_obj = Embedder(**embed_kwargs) + embed = lambda x, eo=embedder_obj : eo.embed(x) + return embed, embedder_obj.out_dim + + +def draw_heatmap(landmark, width, height): + batch = landmark.shape[0] + number = landmark.shape[1] + heatmap = np.zeros((batch, number,width, height), dtype=np.float32) + # draw mouth from mouth landmarks, landmarks: mouth landmark points, format: x1, y1, x2, y2, ..., x20, + + + landmark = (landmark+1)*29 + for i in range(batch): + for pts_idx in range(number): + if int(landmark[i,pts_idx,0])<0: + landmark[i,pts_idx,0] = 0 + if int(landmark[i,pts_idx,1])<0: + landmark[i,pts_idx,1] = 0 + if int(landmark[i,pts_idx,0])>57: + landmark[i,pts_idx,0] = 57 + if int(landmark[i,pts_idx,1])>57: + landmark[i,pts_idx,1] = 57 + heatmap[i,pts_idx, int(landmark[i,pts_idx,1]), int(landmark[i,pts_idx,0])]=1 + if heatmap[i,pts_idx].sum()== 1 : + + heatmap[i,pts_idx] = cv2.GaussianBlur(heatmap[i,pts_idx], ksize=(3, 3), sigmaX=1, sigmaY=1) + + + heatmap = torch.tensor(heatmap).cuda() + return heatmap + +class NA_net(nn.Module): + def __init__(self): + super(NA_net, self).__init__() + + + + self.decon = nn.Sequential( + nn.ConvTranspose2d(1, 16, kernel_size=(2,3), stride=2, padding=(2,1), bias=True),#16,16 + nn.BatchNorm2d(16), + nn.ReLU(True), + nn.ConvTranspose2d(16, 32, kernel_size=4, stride=2, padding=1, bias=True),#8,8 + nn.BatchNorm2d(32), + nn.ReLU(True), + nn.ConvTranspose2d(32, 32+3, kernel_size=4, stride=2, padding=1, bias=True)#16,16 + + + ) + + + + def forward(self, neutral): + + feature = neutral.unsqueeze(1) + current_feature = self.decon(feature) + + + return current_feature + +class AT_net(nn.Module): + def __init__(self): + super(AT_net, self).__init__() + + down_blocks = [] + for i in range(8): + down_blocks.append(DownBlock2d(3 if i == 0 else 2 * (2 ** i), + 2 * (2 ** (i + 1)), + kernel_size=3, padding=1)) + self.down_blocks = nn.ModuleList(down_blocks) + + + # self.lmark_encoder = nn.Sequential( + # nn.Linear(16,256), + # nn.ReLU(True), + # nn.Linear(256,512), + # nn.ReLU(True), + # ) + self.pose_encoder = nn.Sequential( + nn.Linear(6,128), + nn.ReLU(True), + nn.Linear(128,256), + nn.ReLU(True), + + ) + self.audio_eocder = nn.Sequential( + conv2d(1,64,3,1,1), + conv2d(64,128,3,1,1), + nn.MaxPool2d(3, stride=(1,2)), + conv2d(128,256,3,1,1), + conv2d(256,256,3,1,1), + conv2d(256,512,3,1,1), + nn.MaxPool2d(3, stride=(2,2)) + ) + self.audio_eocder_fc = nn.Sequential( + nn.Linear(1024 *12,2048), + nn.ReLU(True), + nn.Linear(2048,256), + nn.ReLU(True), + + ) + self.lstm = nn.LSTM(256*4,256,3,batch_first = True) + # self.lstm_fc = nn.Sequential( + # nn.Linear(256,16), + # ) + self.decon = nn.Sequential( + nn.ConvTranspose2d(256, 256, kernel_size=6, stride=2, padding=1, bias=True),#4,4 + nn.BatchNorm2d(256), + nn.ReLU(True), + nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1, bias=True),#8,8 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 128, kernel_size=4, stride=2, padding=1, bias=True), #16,16 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 128, kernel_size=4, stride=2, padding=1, bias=True),#32,32 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 32+3, kernel_size=4, stride=2, padding=1, bias=True),#64,64 + # nn.ConvTranspose2d(128, 32*4, kernel_size=2, stride=2, padding=3, bias=True),#64,64 + + + ) + self.generator = Generator(64,256,8) + + + + def forward(self, example_image, audio, pose, jaco_net): + hidden = ( torch.autograd.Variable(torch.zeros(3, audio.size(0), 256).cuda()), + torch.autograd.Variable(torch.zeros(3, audio.size(0), 256).cuda())) + outs = example_image + for down_block in self.down_blocks: + outs = down_block(outs) + image_feature = outs + image_feature = image_feature.view(image_feature.shape[0], -1) + lstm_input = [] + for step_t in range(audio.size(1)): + current_audio = audio[ : ,step_t , :, :].unsqueeze(1) + current_feature = self.audio_eocder(current_audio) + current_feature = current_feature.view(current_feature.size(0), -1) + current_feature = self.audio_eocder_fc(current_feature) + pose_f = self.pose_encoder(pose[:,step_t]) + features = torch.cat([image_feature, current_feature, pose_f], 1) + lstm_input.append(features) + lstm_input = torch.stack(lstm_input, dim = 1) + lstm_out, _ = self.lstm(lstm_input, hidden) + fc_out = [] + deco_out = [] + for step_t in range(audio.size(1)): + fc_in = lstm_out[:,step_t,:] + # fc_out.append(self.lstm_fc(fc_in)) + if jaco_net == 'cnn': + fc_feature = torch.unsqueeze(fc_in,2) + fc_feature = torch.unsqueeze(fc_feature,3) + deco_out.append(self.decon(fc_feature)) + elif jaco_net == 'gan': + result,_ = self.generator([fc_in]) + deco_out.append(result) + else: + raise Exception("jaco_net type wrong") + + return torch.stack(deco_out,dim=1) + + +class AT_net2(nn.Module): + def __init__(self): + super(AT_net2, self).__init__() + + down_blocks = [] + for i in range(8): + down_blocks.append(DownBlock2d(3 if i == 0 else 2 * (2 ** i), + 2 * (2 ** (i + 1)), + kernel_size=3, padding=1)) + self.down_blocks = nn.ModuleList(down_blocks) + + + # self.lmark_encoder = nn.Sequential( + # nn.Linear(16,256), + # nn.ReLU(True), + # nn.Linear(256,512), + # nn.ReLU(True), + # ) + self.pose_encoder = nn.Sequential( + nn.Linear(6,128), + nn.ReLU(True), + nn.Linear(128,256), + nn.ReLU(True), + + ) + self.audio_eocder = nn.Sequential( + conv2d(1,64,3,1,1), + conv2d(64,128,3,1,1), + nn.MaxPool2d(3, stride=(1,2)), + conv2d(128,256,3,1,1), + conv2d(256,256,3,1,1), + conv2d(256,512,3,1,1), + nn.MaxPool2d(3, stride=(2,2)) + ) + self.audio_eocder_fc = nn.Sequential( + nn.Linear(1024 *12,2048), + nn.ReLU(True), + nn.Linear(2048,256), + nn.ReLU(True), + + ) + self.lstm = nn.LSTM(256*4,256,3,batch_first = True) + # self.lstm_fc = nn.Sequential( + # nn.Linear(256,16), + # ) + self.decon = nn.Sequential( + nn.ConvTranspose2d(256, 256, kernel_size=6, stride=2, padding=1, bias=True),#4,4 + nn.BatchNorm2d(256), + nn.ReLU(True), + nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1, bias=True),#8,8 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 128, kernel_size=4, stride=2, padding=1, bias=True), #16,16 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 128, kernel_size=4, stride=2, padding=1, bias=True),#32,32 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 32+3, kernel_size=4, stride=2, padding=1, bias=True),#64,64 + # nn.ConvTranspose2d(128, 32*4, kernel_size=2, stride=2, padding=3, bias=True),#64,64 + + + ) + self.generator = Generator(64,256,8) + + + def forward(self, example_image, audio, pose, jaco_net, weight): + hidden = ( torch.autograd.Variable(torch.zeros(3, audio.size(0), 256).cuda()), + torch.autograd.Variable(torch.zeros(3, audio.size(0), 256).cuda())) + outs = example_image + for down_block in self.down_blocks: + outs = down_block(outs) + image_feature = outs + image_feature = image_feature.view(image_feature.shape[0], -1) + lstm_input = [] + for step_t in range(audio.size(1)): + current_audio = audio[ : ,step_t , :, :].unsqueeze(1) + current_feature = self.audio_eocder(current_audio) + current_feature = current_feature.view(current_feature.size(0), -1) + current_feature = self.audio_eocder_fc(current_feature)*weight + pose_f = self.pose_encoder(pose[:,step_t]) + features = torch.cat([image_feature, current_feature, pose_f], 1) + lstm_input.append(features) + lstm_input = torch.stack(lstm_input, dim = 1) + lstm_out, _ = self.lstm(lstm_input, hidden) + fc_out = [] + deco_out = [] + for step_t in range(audio.size(1)): + fc_in = lstm_out[:,step_t,:] + # fc_out.append(self.lstm_fc(fc_in)) + if jaco_net == 'cnn': + fc_feature = torch.unsqueeze(fc_in,2) + fc_feature = torch.unsqueeze(fc_feature,3) + deco_out.append(self.decon(fc_feature)) + elif jaco_net == 'gan': + result,_ = self.generator([fc_in]) + deco_out.append(result) + else: + raise Exception("jaco_net type wrong") + + return torch.stack(deco_out,dim=1) + + +class AT_net_liujin(nn.Module): + def __init__(self): + super(AT_net_liujin, self).__init__() + + down_blocks = [] + for i in range(8): + down_blocks.append(DownBlock2d(3 if i == 0 else 2 * (2 ** i), + 2 * (2 ** (i + 1)), + kernel_size=3, padding=1)) + self.down_blocks = nn.ModuleList(down_blocks) + + + # self.lmark_encoder = nn.Sequential( + # nn.Linear(16,256), + # nn.ReLU(True), + # nn.Linear(256,512), + # nn.ReLU(True), + # ) + self.pose_encoder = nn.Sequential( + nn.Linear(6,128), + nn.ReLU(True), + nn.Linear(128,256), + nn.ReLU(True), + + ) + self.audio_eocder = nn.Sequential( + conv2d(1,64,3,1,1), + conv2d(64,128,3,1,1), + nn.MaxPool2d(3, stride=(1,2)), + conv2d(128,256,3,1,1), + conv2d(256,256,3,1,1), + conv2d(256,512,3,1,1), + nn.MaxPool2d(3, stride=(2,2)) + ) + self.audio_eocder_fc = nn.Sequential( + nn.Linear(1024 *12,2048), + nn.ReLU(True), + nn.Linear(2048,256), + nn.ReLU(True), + ) + + self.audio_encoder_liujin = nn.Sequential( + Conv2d(1, 32, kernel_size=3, stride=1, padding=1), + Conv2d(32, 32, kernel_size=3, stride=1, padding=1, residual=True), + Conv2d(32, 32, kernel_size=3, stride=1, padding=1, residual=True), + + Conv2d(32, 64, kernel_size=3, stride=(3, 1), padding=1), + Conv2d(64, 64, kernel_size=3, stride=1, padding=1, residual=True), + Conv2d(64, 64, kernel_size=3, stride=1, padding=1, residual=True), + + Conv2d(64, 128, kernel_size=3, stride=3, padding=1), + Conv2d(128, 128, kernel_size=3, stride=1, padding=1, residual=True), + Conv2d(128, 128, kernel_size=3, stride=1, padding=1, residual=True), + + Conv2d(128, 256, kernel_size=3, stride=(3, 2), padding=1), + Conv2d(256, 256, kernel_size=3, stride=1, padding=1, residual=True), + + Conv2d(256, 512, kernel_size=3, stride=1, padding=0), + Conv2d(512, 512, kernel_size=1, stride=1, padding=0), ) + + for child in self.audio_encoder_liujin.children(): + for param in child.parameters(): + param.requires_grad = False + + # self.lstm = nn.LSTM(256*4,256,3,batch_first = True) + + self.lstm_liujin = nn.LSTM(256 * 5, 256, 3, batch_first=True) + + # self.lstm_fc = nn.Sequential( + # nn.Linear(256,16), + # ) + self.decon = nn.Sequential( + nn.ConvTranspose2d(256, 256, kernel_size=6, stride=2, padding=1, bias=True),#4,4 + nn.BatchNorm2d(256), + nn.ReLU(True), + nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1, bias=True),#8,8 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 128, kernel_size=4, stride=2, padding=1, bias=True), #16,16 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 128, kernel_size=4, stride=2, padding=1, bias=True),#32,32 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 32+3, kernel_size=4, stride=2, padding=1, bias=True),#64,64 + # nn.ConvTranspose2d(128, 32*4, kernel_size=2, stride=2, padding=3, bias=True),#64,64 + + + ) + self.generator = Generator(64,256,8) + + + + def forward(self, example_image, audio, pose, jaco_net): + hidden = ( torch.autograd.Variable(torch.zeros(3, audio.size(0), 256).cuda()), + torch.autograd.Variable(torch.zeros(3, audio.size(0), 256).cuda())) + outs = example_image + for down_block in self.down_blocks: + outs = down_block(outs) + image_feature = outs + image_feature = image_feature.view(image_feature.shape[0], -1) + lstm_input = [] + for step_t in range(audio.size(1)): + current_audio = audio[ : ,step_t , :, :].unsqueeze(1) + # current_feature = self.audio_eocder(current_audio) + # current_feature = current_feature.view(current_feature.size(0), -1) + # current_feature = self.audio_eocder_fc(current_feature) + + current_feature = self.audio_encoder_liujin(current_audio) + current_feature = current_feature.reshape(current_audio.shape[0], -1) + + pose_f = self.pose_encoder(pose[:,step_t]) + + + features = torch.cat([image_feature, current_feature, pose_f], 1) + lstm_input.append(features) + lstm_input = torch.stack(lstm_input, dim = 1) + # lstm_out, _ = self.lstm(lstm_input, hidden) + lstm_out, _ = self.lstm_liujin(lstm_input, hidden) + fc_out = [] + deco_out = [] + for step_t in range(audio.size(1)): + fc_in = lstm_out[:,step_t,:] + # fc_out.append(self.lstm_fc(fc_in)) + if jaco_net == 'cnn': + fc_feature = torch.unsqueeze(fc_in,2) + fc_feature = torch.unsqueeze(fc_feature,3) + deco_out.append(self.decon(fc_feature)) + elif jaco_net == 'gan': + result,_ = self.generator([fc_in]) + deco_out.append(result) + else: + raise Exception("jaco_net type wrong") + + return torch.stack(deco_out,dim=1) + +class Classify(nn.Module): + def __init__(self): + super(Classify, self).__init__() + + + + self.last_fc = nn.Linear(512,8) + + def forward(self, feature): + # mfcc= torch.unsqueeze(mfcc, 1) + + x = self.last_fc(feature) + + return x + +class TF_net(nn.Module): + def __init__(self): + super(TF_net, self).__init__() + + down_blocks = [] + for i in range(8): + down_blocks.append(DownBlock2d(3 if i == 0 else 2 * (2 ** i), + 2 * (2 ** (i + 1)), + kernel_size=3, padding=1)) + self.down_blocks = nn.ModuleList(down_blocks) + + + # self.lmark_encoder = nn.Sequential( + # nn.Linear(16,256), + # nn.ReLU(True), + # nn.Linear(256,512), + # nn.ReLU(True), + # ) + self.pose_encoder = nn.Sequential( + nn.Linear(6,128), + nn.ReLU(True), + nn.Linear(128,256), + nn.ReLU(True), + + ) + self.audio_eocder = nn.Sequential( + conv2d(1,64,3,1,1), + conv2d(64,128,3,1,1), + nn.MaxPool2d(3, stride=(1,2)), + conv2d(128,256,3,1,1), + conv2d(256,256,3,1,1), + conv2d(256,512,3,1,1), + nn.MaxPool2d(3, stride=(2,2)) + ) + self.audio_eocder_fc = nn.Sequential( + nn.Linear(1024 *12,2048), + nn.ReLU(True), + nn.Linear(2048,256), + nn.ReLU(True), + + ) + self.lstm = nn.LSTM(256*4,256,3,batch_first = True) + self.lstm_two = nn.LSTM(256*6,256,3,batch_first = True) + # self.lstm_fc = nn.Sequential( + # nn.Linear(256,16), + # ) + self.decon = nn.Sequential( + nn.ConvTranspose2d(256, 256, kernel_size=6, stride=2, padding=1, bias=True),#4,4 + nn.BatchNorm2d(256), + nn.ReLU(True), + nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1, bias=True),#8,8 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 128, kernel_size=4, stride=2, padding=1, bias=True), #16,16 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 128, kernel_size=4, stride=2, padding=1, bias=True),#32,32 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 32+3, kernel_size=4, stride=2, padding=1, bias=True),#64,64 + # nn.ConvTranspose2d(128, 32*4, kernel_size=2, stride=2, padding=3, bias=True),#64,64 + + + ) + self.generator = Generator(64,256,8) + self.instance_norm = InstanceNorm() + self.style_mod = ApplyStyle(512, 1024, use_wscale=True) + self.style_mod1 = ApplyStyle(512, 35, use_wscale=True) + + + def adain_forward(self, example_image, audio, pose, jaco_net, emo_features): + hidden = ( torch.autograd.Variable(torch.zeros(3, audio.size(0), 256).cuda()), + torch.autograd.Variable(torch.zeros(3, audio.size(0), 256).cuda())) + outs = example_image + for down_block in self.down_blocks: + outs = down_block(outs) + image_feature = outs + image_feature = image_feature.view(image_feature.shape[0], -1) + lstm_input = [] + for step_t in range(audio.size(1)): + current_audio = audio[ : ,step_t , :, :].unsqueeze(1) + current_feature = self.audio_eocder(current_audio) + current_feature = current_feature.view(current_feature.size(0), -1) + current_feature = self.audio_eocder_fc(current_feature) #256 + pose_f = self.pose_encoder(pose[:,step_t]) #256 + features = torch.cat([image_feature, current_feature, pose_f], 1) + features = torch.unsqueeze(torch.unsqueeze(features,-1),-1) + features = self.instance_norm(features) + x = self.style_mod(features, emo_features[step_t]) + # t = adain(torch.unsqueeze(torch.unsqueeze(features,-1),-1), torch.unsqueeze(torch.unsqueeze(emo_features[step_t],1),2)) + + lstm_input.append(torch.squeeze(torch.squeeze(x,-1),-1)) + lstm_input = torch.stack(lstm_input, dim = 1) + lstm_out, _ = self.lstm(lstm_input, hidden) + # fc_out = [] + deco_out = [] + for step_t in range(audio.size(1)): + fc_in = lstm_out[:,step_t,:] + # fc_out.append(self.lstm_fc(fc_in)) + if jaco_net == 'cnn': + fc_feature = torch.unsqueeze(fc_in,2) + fc_feature = torch.unsqueeze(fc_feature,3) + deco_out.append(self.decon(fc_feature)) + elif jaco_net == 'gan': + result,_ = self.generator([fc_in]) + deco_out.append(result) + else: + raise Exception("jaco_net type wrong") + + return torch.stack(deco_out,dim=1) + + + + def adain_feature2(self, example_image, audio, pose, jaco_net, emo_features): + hidden = ( torch.autograd.Variable(torch.zeros(3, audio.size(0), 256).cuda()), + torch.autograd.Variable(torch.zeros(3, audio.size(0), 256).cuda())) + outs = example_image + for down_block in self.down_blocks: + outs = down_block(outs) + image_feature = outs + image_feature = image_feature.view(image_feature.shape[0], -1) + lstm_input = [] + for step_t in range(audio.size(1)): + current_audio = audio[ : ,step_t , :, :].unsqueeze(1) + current_feature = self.audio_eocder(current_audio) + current_feature = current_feature.view(current_feature.size(0), -1) + current_feature = self.audio_eocder_fc(current_feature) #256 + pose_f = self.pose_encoder(pose[:,step_t]) #256 + features = torch.cat([image_feature, current_feature, pose_f], 1) + + lstm_input.append(features) + lstm_input = torch.stack(lstm_input, dim = 1) + lstm_out, _ = self.lstm(lstm_input, hidden) + # fc_out = [] + deco_out = [] + for step_t in range(audio.size(1)): + fc_in = lstm_out[:,step_t,:] + # fc_out.append(self.lstm_fc(fc_in)) + if jaco_net == 'cnn': + fc_feature = torch.unsqueeze(fc_in,2) + fc_feature = torch.unsqueeze(fc_feature,3) + fc_feature = self.decon(fc_feature) + fc_feature = self.instance_norm(fc_feature) + t = self.style_mod1(fc_feature, emo_features[step_t]) + # emo_feature = torch.unsqueeze(torch.unsqueeze(emo_features[step_t],-1),-1) + # emo_feature = emo_feature.repeat(1,fc_feature.shape[1],1,1) + # t = adain(fc_feature, emo_feature) + deco_out.append(t) + elif jaco_net == 'gan': + result,_ = self.generator([fc_in]) + deco_out.append(result) + else: + raise Exception("jaco_net type wrong") + + return torch.stack(deco_out,dim=1) + + def forward(self, example_image, audio, pose, jaco_net, emo_features): + hidden = ( torch.autograd.Variable(torch.zeros(3, audio.size(0), 256).cuda()), + torch.autograd.Variable(torch.zeros(3, audio.size(0), 256).cuda())) + outs = example_image + for down_block in self.down_blocks: + outs = down_block(outs) + image_feature = outs + image_feature = image_feature.view(image_feature.shape[0], -1) + lstm_input = [] + for step_t in range(audio.size(1)): + current_audio = audio[ : ,step_t , :, :].unsqueeze(1) + current_feature = self.audio_eocder(current_audio) + current_feature = current_feature.view(current_feature.size(0), -1) + current_feature = self.audio_eocder_fc(current_feature) #256 + pose_f = self.pose_encoder(pose[:,step_t]) #256 + features = torch.cat([image_feature, current_feature, pose_f, emo_features[step_t]], 1) + lstm_input.append(features) + lstm_input = torch.stack(lstm_input, dim = 1) + lstm_out, _ = self.lstm_two(lstm_input, hidden) + fc_out = [] + deco_out = [] + for step_t in range(audio.size(1)): + fc_in = lstm_out[:,step_t,:] + # fc_out.append(self.lstm_fc(fc_in)) + if jaco_net == 'cnn': + fc_feature = torch.unsqueeze(fc_in,2) + fc_feature = torch.unsqueeze(fc_feature,3) + deco_out.append(self.decon(fc_feature)) + elif jaco_net == 'gan': + result,_ = self.generator([fc_in]) + deco_out.append(result) + else: + raise Exception("jaco_net type wrong") + + return torch.stack(deco_out,dim=1) + + +class AT_net2_liujin(nn.Module): + def __init__(self): + super(AT_net2_liujin, self).__init__() + + down_blocks = [] + for i in range(8): + down_blocks.append(DownBlock2d(3 if i == 0 else 2 * (2 ** i), + 2 * (2 ** (i + 1)), + kernel_size=3, padding=1)) + self.down_blocks = nn.ModuleList(down_blocks) + + + # self.lmark_encoder = nn.Sequential( + # nn.Linear(16,256), + # nn.ReLU(True), + # nn.Linear(256,512), + # nn.ReLU(True), + # ) + self.pose_encoder = nn.Sequential( + nn.Linear(6,128), + nn.ReLU(True), + nn.Linear(128,256), + nn.ReLU(True), + ) + + self.audio_eocder = nn.Sequential( + conv2d(1,64,3,1,1), + conv2d(64,128,3,1,1), + nn.MaxPool2d(3, stride=(1,2)), + conv2d(128,256,3,1,1), + conv2d(256,256,3,1,1), + conv2d(256,512,3,1,1), + nn.MaxPool2d(3, stride=(2,2)) + ) + + self.audio_eocder_fc = nn.Sequential( + nn.Linear(1024 *12,2048), + nn.ReLU(True), + nn.Linear(2048,256), + nn.ReLU(True), + ) + + + + + # self.lstm = nn.LSTM(256*4,256,3,batch_first = True) + self.lstm_liujin = nn.LSTM(256 * 5, 256, 3, batch_first=True) + # self.lstm_fc = nn.Sequential( + # nn.Linear(256,16), + # ) + self.decon = nn.Sequential( + nn.ConvTranspose2d(256, 256, kernel_size=6, stride=2, padding=1, bias=True),#4,4 + nn.BatchNorm2d(256), + nn.ReLU(True), + nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1, bias=True),#8,8 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 128, kernel_size=4, stride=2, padding=1, bias=True), #16,16 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 128, kernel_size=4, stride=2, padding=1, bias=True),#32,32 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 32+3, kernel_size=4, stride=2, padding=1, bias=True),#64,64 + # nn.ConvTranspose2d(128, 32*4, kernel_size=2, stride=2, padding=3, bias=True),#64,64 + + + ) + self.generator = Generator(64,256,8) + + + def forward(self, example_image, audio, pose, jaco_net, weight): + hidden = ( torch.autograd.Variable(torch.zeros(3, audio.size(0), 256).cuda()), + torch.autograd.Variable(torch.zeros(3, audio.size(0), 256).cuda())) + outs = example_image + for down_block in self.down_blocks: + outs = down_block(outs) + image_feature = outs + image_feature = image_feature.view(image_feature.shape[0], -1) #1, 512, 1, 1 + lstm_input = [] + for step_t in range(audio.size(1)): + current_audio = audio[ : ,step_t , :, :].unsqueeze(1) + current_feature = self.audio_eocder(current_audio) + current_feature = current_feature.view(current_feature.size(0), -1) + current_feature = self.audio_eocder_fc(current_feature)*weight + pose_f = self.pose_encoder(pose[:,step_t]) + features = torch.cat([image_feature, current_feature, pose_f], 1) + lstm_input.append(features) + lstm_input = torch.stack(lstm_input, dim = 1) + lstm_out, _ = self.lstm(lstm_input, hidden) #1,N,256 + + + fc_out = [] + deco_out = [] + for step_t in range(audio.size(1)): + fc_in = lstm_out[:,step_t,:] + # fc_out.append(self.lstm_fc(fc_in)) + if jaco_net == 'cnn': + fc_feature = torch.unsqueeze(fc_in,2) + fc_feature = torch.unsqueeze(fc_feature,3) + deco_out.append(self.decon(fc_feature)) + elif jaco_net == 'gan': + result,_ = self.generator([fc_in]) + deco_out.append(result) + else: + raise Exception("jaco_net type wrong") + + return torch.stack(deco_out,dim=1) + + + +class Ct_encoder(nn.Module): + def __init__(self): + super(Ct_encoder, self).__init__() + self.audio_eocder = nn.Sequential( + conv2d(1,64,3,1,1), + conv2d(64,128,3,1,1), + nn.MaxPool2d(3, stride=(1,2)), + conv2d(128,256,3,1,1), + conv2d(256,256,3,1,1), + conv2d(256,512,3,1,1), + nn.MaxPool2d(3, stride=(2,2)) + ) + self.audio_eocder_fc = nn.Sequential( + nn.Linear(1024 *12,2048), + nn.ReLU(True), + nn.Linear(2048,256), + nn.ReLU(True), + + ) + + def forward(self, audio): + + feature = self.audio_eocder(audio) + feature = feature.view(feature.size(0),-1) + x = self.audio_eocder_fc(feature) + + return x + + +class EmotionNet(nn.Module): + def __init__(self): + super(EmotionNet, self).__init__() + + self.emotion_eocder = nn.Sequential( + conv2d(1,64,3,1,1), + + nn.MaxPool2d((1,3), stride=(1,2)), #[1, 64, 12, 12] + conv2d(64,128,3,1,1), + + conv2d(128,256,3,1,1), + + nn.MaxPool2d((12,1), stride=(12,1)), #[1, 256, 1, 12] + + conv2d(256,512,3,1,1), + + nn.MaxPool2d((1,2), stride=(1,2)) #[1, 512, 1, 6] + + ) + self.emotion_eocder_fc = nn.Sequential( + nn.Linear(512 *6,2048), + nn.ReLU(True), + nn.Linear(2048,128), + nn.ReLU(True), + + ) + + self.last_fc = nn.Linear(128,8) + + self.re_id = nn.Sequential( + conv2d(512,1024,3,1,1), + + nn.MaxPool2d((1,2), stride=(1,2)), #[1, 1024, 1, 3] + conv2d(1024,1024,3,1,1), + + conv2d(1024,2048,3,1,1), + + nn.MaxPool2d((1,2), stride=(1,2)) #[1, 2048, 1, 1] + + + ) + self.re_id_fc = nn.Sequential( + + nn.Linear(2048,512), + nn.ReLU(True), + nn.Linear(512,128), + nn.ReLU(True), + ) + + + def forward(self, mfcc): + # mfcc= torch.unsqueeze(mfcc, 1) + mfcc=torch.transpose(mfcc,2,3) + feature = self.emotion_eocder(mfcc) + + # id_feature = feature.detach() + + feature = feature.view(feature.size(0),-1) + x = self.emotion_eocder_fc(feature) + + + # remove_feature = self.re_id(id_feature) + # remove_feature = remove_feature.view(remove_feature.size(0),-1) + # y = self.re_id_fc(remove_feature) + + return x + + +class AF2F(nn.Module): + def __init__(self): + super(AF2F, self).__init__() + self.decon = nn.Sequential( + nn.ConvTranspose2d(384, 256, kernel_size=6, stride=2, padding=1, bias=True),#4,4 + nn.BatchNorm2d(256), + nn.ReLU(True), + nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1, bias=True),#8,8 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1, bias=True), #16,16 + nn.BatchNorm2d(64), + nn.ReLU(True), + nn.ConvTranspose2d(64, 64, kernel_size=4, stride=2, padding=1, bias=True),#32,32 + nn.BatchNorm2d(64), + nn.ReLU(True), + nn.ConvTranspose2d(64, 32+3, kernel_size=4, stride=2, padding=1, bias=True),#64,64 + + + ) + + def forward(self, content,emotion): + features = torch.cat([content, emotion], 1) #connect tensors inputs and dimension + features = torch.unsqueeze(features,2) + features = torch.unsqueeze(features,3) + x = self.decon(features) + + + return x + +class AF2F_s(nn.Module): + def __init__(self): + super(AF2F_s, self).__init__() + self.decon = nn.Sequential( + nn.ConvTranspose2d(256, 256, kernel_size=6, stride=2, padding=1, bias=True),#4,4 + nn.BatchNorm2d(256), + nn.ReLU(True), + nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1, bias=True),#8,8 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1, bias=True), #16,16 + nn.BatchNorm2d(64), + nn.ReLU(True), + nn.ConvTranspose2d(64, 64, kernel_size=4, stride=2, padding=1, bias=True),#32,32 + nn.BatchNorm2d(64), + nn.ReLU(True), + nn.ConvTranspose2d(64, 32+3, kernel_size=4, stride=2, padding=1, bias=True),#64,64 + + nn.ReLU(), + ) + + def forward(self, content): + # features = torch.cat([content, emotion], 1) #connect tensors inputs and dimension + features = torch.unsqueeze(content,2) + features = torch.unsqueeze(features,3) + x = self.decon(features) + + + return x + + +class A2I(nn.Module): + def __init__(self): + super(A2I, self).__init__() + self.audio_eocder = nn.Sequential( + conv2d(1,64,3,1,1), + conv2d(64,128,3,1,1), + nn.MaxPool2d((1,5), stride=(1,2)), + conv2d(128,256,3,1,1), + conv2d(256,256,3,1,1), + + nn.MaxPool2d((5,5), stride=(2,2)) + ) + self.decon = nn.Sequential( + + nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1, bias=True),#8,8 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1, bias=True), #16,16 + nn.BatchNorm2d(64), + nn.ReLU(True), + nn.ConvTranspose2d(64, 32, kernel_size=4, stride=2, padding=1, bias=True),#32,32 + nn.BatchNorm2d(32), + nn.ReLU(True), + nn.ConvTranspose2d(32, 2, kernel_size=4, stride=2, padding=1, bias=True),#64,64 + + nn.ReLU(), + ) + + def forward(self, mfcc): + mfcc= torch.unsqueeze(mfcc, 1) + mfcc=torch.transpose(mfcc,2,3) + feature = self.audio_eocder(mfcc) + + # id_feature = feature.detach() + + x = self.decon(feature) + + return x + +def kp2gaussian(kp, spatial_size, kp_variance): + """ + Transform a keypoint into gaussian like representation + """ + mean = kp['value'] #[4,10,2] + + coordinate_grid = make_coordinate_grid(spatial_size, mean.type()) #[h,w,2] + number_of_leading_dimensions = len(mean.shape) - 1 + shape = (1,) * number_of_leading_dimensions + coordinate_grid.shape #5 + coordinate_grid = coordinate_grid.view(*shape) #[1,1,h,w,2] + repeats = mean.shape[:number_of_leading_dimensions] + (1, 1, 1) + coordinate_grid = coordinate_grid.repeat(*repeats) #[4,10,h,w,2] + + # Preprocess kp shape + shape = mean.shape[:number_of_leading_dimensions] + (1, 1, 2) + mean = mean.view(*shape) #[4,10,1,1,2] + + mean_sub = (coordinate_grid - mean) + + out = torch.exp(-0.5 * (mean_sub ** 2).sum(-1) / kp_variance) + + 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) + + meshed = torch.cat([xx.unsqueeze_(2), yy.unsqueeze_(2)], 2) + + return meshed + + +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 + sigma = 1.5 + 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 + inv_scale = 1 / scale + self.int_inv_scale = int(inv_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 = out[:, :, ::self.int_inv_scale, ::self.int_inv_scale] + + return out + +def sigmoid(x): + return 1 / (1 + math.exp(-x)) + + +def norm_angle(angle): + norm_angle = sigmoid(10 * (abs(angle) / 0.7853975 - 1)) + return norm_angle + + +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) + self.relu = nn.ReLU() + self.conv2 = conv3x3(planes, planes) + self.bn2 = nn.BatchNorm2d(planes) + 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) + self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, + padding=1, bias=False) + self.bn2 = nn.BatchNorm2d(planes) + self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False) + self.bn3 = nn.BatchNorm2d(planes * 4) + self.relu = nn.ReLU() + 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 = out + residual + out = self.relu(out) + + return out + +class EmDetector(nn.Module): + """ + Detecting a keypoints. Return keypoint position and jacobian near each keypoint. + """ + + def __init__(self, block_expansion, num_channels, max_features, + num_blocks, scale_factor=1, num_classes=8): + super(EmDetector, self).__init__() + self.inplanes = 64 + self.predictor = Hourglass(block_expansion, in_features=num_channels, + max_features=max_features, num_blocks=num_blocks) + + + + + self.scale_factor = scale_factor + if self.scale_factor != 1: + self.down = AntiAliasInterpolation2d(num_channels, self.scale_factor) + self.conv1 = nn.Conv2d(self.predictor.out_filters, 64, kernel_size=3, stride=1, padding=1, + bias=False) + self.bn1 = nn.BatchNorm2d(64) + self.relu = nn.ReLU() + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + layers = [2,2,2,2] + self.layer1 = self._make_layer(BasicBlock, 64, layers[0]) + self.layer2 = self._make_layer(BasicBlock, 128, layers[1], stride=2) + self.layer3 = self._make_layer(BasicBlock, 256, layers[2], stride=2) + self.layer4 = self._make_layer(BasicBlock, 512, layers[3], stride=2) + self.avgpool = nn.AdaptiveAvgPool2d(1) + self.fc = nn.Linear(512 * BasicBlock.expansion, num_classes) + self.classify = Classify() + 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), + ) + + 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 adain_feature(self, x): #torch.Size([4, 3, H, W]) + if self.scale_factor != 1: + x = self.down(x) # 0.25 [4, 3, H/4, W/4] + + feature_map = self.predictor(x) #[4,3+32,H/4, W/4] + + # out = self.fc(out) + + return feature_map + + def forward(self, x): #torch.Size([4, 3, H, W]) + if self.scale_factor != 1: + x = self.down(x) # 0.25 [4, 3, H/4, W/4] + + feature_map = self.predictor(x) #[4,3+32,H/4, W/4] + f = self.conv1(feature_map) #[16,64,64,64] + f = self.bn1(f) #torch.Size([16, 64, 64, 64]) + f = self.relu(f) + f = self.maxpool(f) #[16, 64, 32, 32] + + f = self.layer1(f) #[16, 64, 32, 32] + f = self.layer2(f) #[16, 128, 16, 16]) + f = self.layer3(f) #[16, 256, 8, 8] + f = self.layer4(f) #[16, 512, 4, 4] + f = self.avgpool(f) #[16, 512, 1, 1] + out = f.squeeze(3).squeeze(2) + fake = self.classify(out) + # out = self.fc(out) + + return out, fake + + + + + + +class Emotion_k(nn.Module): + """ + Detecting a keypoints. Return keypoint position and jacobian near each keypoint. + """ + + def __init__(self, block_expansion, num_channels, max_features, + num_blocks, scale_factor=1, num_classes=8): + super(Emotion_k, self).__init__() + self.inplanes = 64 + self.predictor = Hourglass(block_expansion, in_features=num_channels, + max_features=max_features, num_blocks=num_blocks) + + + + + self.scale_factor = scale_factor + if self.scale_factor != 1: + self.down = AntiAliasInterpolation2d(num_channels, self.scale_factor) + self.conv1 = nn.Conv2d(self.predictor.out_filters, 64, kernel_size=3, stride=1, padding=1, + bias=False) + self.bn1 = nn.BatchNorm2d(64) + self.relu = nn.ReLU() + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + layers = [2,2,2,2] + self.layer1 = self._make_layer(BasicBlock, 64, layers[0]) + self.layer2 = self._make_layer(BasicBlock, 128, layers[1], stride=2) + self.layer3 = self._make_layer(BasicBlock, 256, layers[2], stride=2) + self.layer4 = self._make_layer(BasicBlock, 512, layers[3], stride=2) + self.avgpool = nn.AdaptiveAvgPool2d(1) + self.fc = nn.Linear(512 * BasicBlock.expansion, num_classes) + + self.embed_fn, self.input_ch = get_embedder(10, 0) + + self.fc_p = nn.Sequential( + nn.Linear(10 * 126,1024), + nn.ReLU(True), + nn.Linear(1024,512), + nn.ReLU(True), + + ) + self.fc_n = nn.Sequential( + nn.Linear(10 * 6,128), + nn.ReLU(True), + nn.Linear(128,512), + nn.ReLU(True), + + ) + + self.fc_all = nn.Sequential( + nn.Linear(1024,512), + nn.ReLU(True), + nn.Linear(512,256), + nn.ReLU(True), + nn.Linear(256,64), + nn.ReLU(True), + ) + + # self.fc_single = nn.Sequential( + # nn.Linear(512,256), + # nn.ReLU(True), + # nn.Linear(256,64), + # nn.ReLU(True), + # ) + + self.final = nn.Sequential( + nn.Conv1d(1,2,4,2,1), + nn.MaxPool1d(2,stride=2), + nn.ReLU(True), + nn.Conv1d(2,4,4,2,1), + nn.ReLU(True), + nn.Conv1d(4,4,3), + + ) + + self.final_4 = nn.Sequential( + nn.Conv1d(4,4,3,1,1), + nn.MaxPool1d(2,stride=2), + nn.ReLU(True), + nn.Conv1d(4,4,3,1) + + ) + + self.final_10 = nn.Sequential( + nn.Conv1d(4,8,3,1,1), #[B,8,16] + nn.MaxPool1d(2,stride=2), #[B,8,8] + nn.ReLU(True), + nn.Conv1d(8,10,3,1), #[B,10,6] + + + ) + + self.classify = Classify() + + 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), + ) + + 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 linear_10(self, x, value, jacobian): #torch.Size([4, 3, H, W]) + if self.scale_factor != 1: + x = self.down(x) # 0.25 [4, 3, H/4, W/4] + + feature_map = self.predictor(x) #[4,3+32,H/4, W/4] + f = self.conv1(feature_map) #[16,64,64,64] + f = self.bn1(f) #torch.Size([16, 64, 64, 64]) + f = self.relu(f) + f = self.maxpool(f) #[16, 64, 32, 32] + + f = self.layer1(f) #[16, 64, 32, 32] + f = self.layer2(f) #[16, 128, 16, 16]) + f = self.layer3(f) #[16, 256, 8, 8] + f = self.layer4(f) #[16, 512, 4, 4] + f = self.avgpool(f) #[16, 512, 1, 1] + out = f.squeeze(3).squeeze(2) + fake = self.classify(out) + jacobian = jacobian.reshape(jacobian.shape[0],jacobian.shape[1],4) + neu_input = torch.cat((value,jacobian),2) + posi_input = self.embed_fn(neu_input) + posi_input =posi_input.reshape(posi_input.shape[0],-1) + ner_feature = self.fc_p(posi_input) + all_fc = self.fc_all(torch.cat((out,ner_feature),1)).reshape(-1,4,16) + result = self.final_10(all_fc) + e_value = result[:,:,:2] + e_jacobian = result[:,:,2:].reshape(result.shape[0],10,2,2) + kp = {'value': e_value,'jacobian': e_jacobian} + + return kp, fake + + + def linear_4(self, x, value, jacobian): #torch.Size([4, 3, H, W]) + if self.scale_factor != 1: + x = self.down(x) # 0.25 [4, 3, H/4, W/4] + + feature_map = self.predictor(x) #[4,3+32,H/4, W/4] + f = self.conv1(feature_map) #[16,64,64,64] + f = self.bn1(f) #torch.Size([16, 64, 64, 64]) + f = self.relu(f) + f = self.maxpool(f) #[16, 64, 32, 32] + + f = self.layer1(f) #[16, 64, 32, 32] + f = self.layer2(f) #[16, 128, 16, 16]) + f = self.layer3(f) #[16, 256, 8, 8] + f = self.layer4(f) #[16, 512, 4, 4] + f = self.avgpool(f) #[16, 512, 1, 1] + out = f.squeeze(3).squeeze(2) + fake = self.classify(out) + # jacobian = jacobian.reshape(jacobian.shape[0],jacobian.shape[1],4) + # neu_input = torch.cat((value,jacobian),2) + # posi_input = self.embed_fn(neu_input) + # posi_input =posi_input.reshape(posi_input.shape[0],-1) + # ner_feature = self.fc_p(posi_input) + # all_fc = self.fc_all(torch.cat((out,ner_feature),1)).reshape(-1,4,16) + all_fc = torch.unsqueeze(self.fc_single(out),1) + result = self.final(all_fc) + e_value = result[:,:,:2] + e_jacobian = result[:,:,2:].reshape(result.shape[0],4,2,2) + kp = {'value': e_value,'jacobian': e_jacobian} + # out = self.fc(out) + + return kp, fake + + def linear_np_10(self, x, value, jacobian): #torch.Size([4, 3, H, W]) + if self.scale_factor != 1: + x = self.down(x) # 0.25 [4, 3, H/4, W/4] + + feature_map = self.predictor(x) #[4,3+32,H/4, W/4] + f = self.conv1(feature_map) #[16,64,64,64] + f = self.bn1(f) #torch.Size([16, 64, 64, 64]) + f = self.relu(f) + f = self.maxpool(f) #[16, 64, 32, 32] + + f = self.layer1(f) #[16, 64, 32, 32] + f = self.layer2(f) #[16, 128, 16, 16]) + f = self.layer3(f) #[16, 256, 8, 8] + f = self.layer4(f) #[16, 512, 4, 4] + f = self.avgpool(f) #[16, 512, 1, 1] + out = f.squeeze(3).squeeze(2) + fake = self.classify(out) + jacobian = jacobian.reshape(jacobian.shape[0],jacobian.shape[1],4) + neu_input = torch.cat((value,jacobian),2) + + posi_input =neu_input.reshape(neu_input.shape[0],-1) + ner_feature = self.fc_n(posi_input) + all_fc = self.fc_all(torch.cat((out,ner_feature),1)).reshape(-1,4,16) + result = self.final_10(all_fc) + e_value = result[:,:,:2] + e_jacobian = result[:,:,2:].reshape(result.shape[0],10,2,2) + kp = {'value': e_value,'jacobian': e_jacobian} + # out = self.fc(out) + + return kp, fake + + def linear_np_4(self, x, value, jacobian): #torch.Size([4, 3, H, W]) + if self.scale_factor != 1: + x = self.down(x) # 0.25 [4, 3, H/4, W/4] + + feature_map = self.predictor(x) #[4,3+32,H/4, W/4] + f = self.conv1(feature_map) #[16,64,64,64] + f = self.bn1(f) #torch.Size([16, 64, 64, 64]) + f = self.relu(f) + f = self.maxpool(f) #[16, 64, 32, 32] + + f = self.layer1(f) #[16, 64, 32, 32] + f = self.layer2(f) #[16, 128, 16, 16]) + f = self.layer3(f) #[16, 256, 8, 8] + f = self.layer4(f) #[16, 512, 4, 4] + f = self.avgpool(f) #[16, 512, 1, 1] + out = f.squeeze(3).squeeze(2) + fake = self.classify(out) + jacobian = jacobian.reshape(jacobian.shape[0],jacobian.shape[1],4) + neu_input = torch.cat((value,jacobian),2) + + posi_input =neu_input.reshape(neu_input.shape[0],-1) + ner_feature = self.fc_n(posi_input) + all_fc = torch.unsqueeze(self.fc_all(torch.cat((out,ner_feature),1)),1) + result = self.final(all_fc) + e_value = result[:,:,:2] + e_jacobian = result[:,:,2:].reshape(result.shape[0],4,2,2) + kp = {'value': e_value,'jacobian': e_jacobian} + # out = self.fc(out) + + return kp, fake + + + def emotion_feature(self, feature, value, jacobian): #torch.Size([4, 3, H, W]) + + out = feature + fake = self.classify(out) + jacobian = jacobian.reshape(jacobian.shape[0],jacobian.shape[1],4) + neu_input = torch.cat((value,jacobian),2) + posi_input = self.embed_fn(neu_input) + posi_input =posi_input.reshape(posi_input.shape[0],-1) + ner_feature = self.fc_p(posi_input) + all_fc = torch.unsqueeze(self.fc_all(torch.cat((out,ner_feature),1)),1) + result = self.final(all_fc) + e_value = result[:,:,:2] + e_jacobian = result[:,:,2:].reshape(result.shape[0],4,2,2) + kp = {'value': e_value,'jacobian': e_jacobian} + # out = self.fc(out) + + return kp, fake + + def feature(self, x): #torch.Size([4, 3, H, W]) + if self.scale_factor != 1: + x = self.down(x) # 0.25 [4, 3, H/4, W/4] + + feature_map = self.predictor(x) #[4,3+32,H/4, W/4] + f = self.conv1(feature_map) #[16,64,64,64] + f = self.bn1(f) #torch.Size([16, 64, 64, 64]) + f = self.relu(f) + f = self.maxpool(f) #[16, 64, 32, 32] + + f = self.layer1(f) #[16, 64, 32, 32] + f = self.layer2(f) #[16, 128, 16, 16]) + f = self.layer3(f) #[16, 256, 8, 8] + f = self.layer4(f) #[16, 512, 4, 4] + f = self.avgpool(f) #[16, 512, 1, 1] + out = f.squeeze(3).squeeze(2) + + # out = self.fc(out) + + return out + + def forward(self, x, value, jacobian): #torch.Size([4, 3, H, W]) + if self.scale_factor != 1: + x = self.down(x) # 0.25 [4, 3, H/4, W/4] + + feature_map = self.predictor(x) #[4,3+32,H/4, W/4] + f = self.conv1(feature_map) #[16,64,64,64] + f = self.bn1(f) #torch.Size([16, 64, 64, 64]) + f = self.relu(f) + f = self.maxpool(f) #[16, 64, 32, 32] + + f = self.layer1(f) #[16, 64, 32, 32] + f = self.layer2(f) #[16, 128, 16, 16]) + f = self.layer3(f) #[16, 256, 8, 8] + f = self.layer4(f) #[16, 512, 4, 4] + f = self.avgpool(f) #[16, 512, 1, 1] + out = f.squeeze(3).squeeze(2) + fake = self.classify(out) + jacobian = jacobian.reshape(jacobian.shape[0],jacobian.shape[1],4) + neu_input = torch.cat((value,jacobian),2) + posi_input = self.embed_fn(neu_input) + posi_input =posi_input.reshape(posi_input.shape[0],-1) + ner_feature = self.fc_p(posi_input) + all_fc = torch.unsqueeze(self.fc_all(torch.cat((out,ner_feature),1)),1) + result = self.final(all_fc) + e_value = result[:,:,:2] + e_jacobian = result[:,:,2:].reshape(result.shape[0],4,2,2) + kp = {'value': e_value,'jacobian': e_jacobian} + # out = self.fc(out) + + return kp, fake + +class Emotion_map(nn.Module): + """ + Detecting a keypoints. Return keypoint position and jacobian near each keypoint. + """ + + def __init__(self, block_expansion, num_channels, max_features, + num_blocks, scale_factor=1, num_classes=8): + super(Emotion_map, self).__init__() + self.inplanes = 64 + self.predictor = Hourglass(block_expansion, in_features=num_channels, + max_features=max_features, num_blocks=num_blocks) + + + + + self.scale_factor = scale_factor + if self.scale_factor != 1: + self.down = AntiAliasInterpolation2d(num_channels, self.scale_factor) + self.conv1 = nn.Conv2d(self.predictor.out_filters, 64, kernel_size=3, stride=1, padding=1, + bias=False) + self.bn1 = nn.BatchNorm2d(64) + self.relu = nn.ReLU() + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + layers = [2,2,2,2] + self.layer1 = self._make_layer(BasicBlock, 64, layers[0]) + self.layer2 = self._make_layer(BasicBlock, 128, layers[1], stride=2) + self.layer3 = self._make_layer(BasicBlock, 256, layers[2], stride=2) + self.layer4 = self._make_layer(BasicBlock, 512, layers[3], stride=2) + self.avgpool = nn.AdaptiveAvgPool2d(1) + self.fc = nn.Linear(512 * BasicBlock.expansion, num_classes) + + self.embed_fn, self.input_ch = get_embedder(10, 0) + + self.fc_p = nn.Sequential( + nn.Linear(10 * 126,1024), + nn.ReLU(True), + nn.Linear(1024,512), + nn.ReLU(True), + + ) + + self.fc_all = nn.Sequential( + nn.Linear(1024,2048), + nn.ReLU(True) + ) + + self.final = nn.Sequential( + nn.ConvTranspose2d(128, 128, kernel_size=4, stride=2, padding=1, bias=True),#8,8 + nn.BatchNorm2d(128), + nn.ReLU(True), + nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1, bias=True), #16,16 + nn.BatchNorm2d(64), + nn.ReLU(True), + nn.ConvTranspose2d(64, 64, kernel_size=4, stride=2, padding=1, bias=True),#32,32 + nn.BatchNorm2d(64), + nn.ReLU(True), + nn.ConvTranspose2d(64, 32+3, kernel_size=4, stride=2, padding=1, bias=True),#64,64 + + ) + + + self.classify = Classify() + self.kp = nn.Conv2d(in_channels=35, out_channels=10, kernel_size=(7, 7), + padding=0) + self.jacobian = nn.Conv2d(in_channels=35, + out_channels=4 * 10, kernel_size=(7, 7), padding=0) + self.jacobian.weight.data.zero_() + self.jacobian.bias.data.copy_(torch.tensor([1, 0, 0, 1] * 10, dtype=torch.float)) + self.temperature = 0.1 + + self.kp_4 = nn.Conv2d(in_channels=35, out_channels=4, kernel_size=(7, 7), + padding=0) + self.jacobian_4 = nn.Conv2d(in_channels=35, + out_channels=4 * 4, kernel_size=(7, 7), padding=0) + self.jacobian_4.weight.data.zero_() + self.jacobian_4.bias.data.copy_(torch.tensor([1, 0, 0, 1] * 4, dtype=torch.float)) + + + 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), + ) + + 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 gaussian2kp(self, heatmap): + """ + Extract the mean and from a heatmap + """ + shape = heatmap.shape + heatmap = heatmap.unsqueeze(-1) #[4,10,58,58,1] + grid = make_coordinate_grid(shape[2:], heatmap.type()).unsqueeze_(0).unsqueeze_(0) #[1,1,58,58,2] + value = (heatmap * grid).sum(dim=(2, 3)) #[4,10,2] + kp = {'value': value} + + return kp + + def map_4(self, x, value, jacobian): #torch.Size([4, 3, H, W]) + if self.scale_factor != 1: + x = self.down(x) # 0.25 [4, 3, H/4, W/4] + + feature_map = self.predictor(x) #[4,3+32,H/4, W/4] + f = self.conv1(feature_map) #[16,64,64,64] + f = self.bn1(f) #torch.Size([16, 64, 64, 64]) + f = self.relu(f) + f = self.maxpool(f) #[16, 64, 32, 32] + + f = self.layer1(f) #[16, 64, 32, 32] + f = self.layer2(f) #[16, 128, 16, 16]) + f = self.layer3(f) #[16, 256, 8, 8] + f = self.layer4(f) #[16, 512, 4, 4] + f = self.avgpool(f) #[16, 512, 1, 1] + out = f.squeeze(3).squeeze(2) + fake = self.classify(out) + jacobian = jacobian.reshape(jacobian.shape[0],jacobian.shape[1],4) + neu_input = torch.cat((value,jacobian),2) + posi_input = self.embed_fn(neu_input) + posi_input =posi_input.reshape(posi_input.shape[0],-1) + ner_feature = self.fc_p(posi_input) + all_fc = self.fc_all(torch.cat((out,ner_feature),1)).reshape(-1,128,4,4) + feature_map = self.final(all_fc) + prediction = self.kp_4(feature_map) #[4,10,H/4-6, W/4-6] + + final_shape = prediction.shape + + heatmap = prediction.view(final_shape[0], final_shape[1], -1) #[4, 10, 58*58] + heatmap = F.softmax(heatmap / self.temperature, dim=2) + heatmap = heatmap.view(*final_shape) #[4,10,58,58] + + out = self.gaussian2kp(heatmap) + out['heatmap'] = heatmap + + if self.jacobian is not None: + jacobian_map = self.jacobian_4(feature_map) ##[4,40,H/4-6, W/4-6] + jacobian_map = jacobian_map.reshape(final_shape[0], 4, 4, final_shape[2], + final_shape[3]) + heatmap = heatmap.unsqueeze(2) + + jacobian = heatmap * jacobian_map #[4,10,4,H/4-6, W/4-6] + jacobian = jacobian.view(final_shape[0], final_shape[1], 4, -1) + jacobian = jacobian.sum(dim=-1) #[4,10,4] + jacobian = jacobian.view(jacobian.shape[0], jacobian.shape[1], 2, 2) #[4,10,2,2] + out['jacobian'] = jacobian + + + + return out, fake + + def forward(self, x, value, jacobian): #torch.Size([4, 3, H, W]) + if self.scale_factor != 1: + x = self.down(x) # 0.25 [4, 3, H/4, W/4] + + feature_map = self.predictor(x) #[4,3+32,H/4, W/4] + f = self.conv1(feature_map) #[16,64,64,64] + f = self.bn1(f) #torch.Size([16, 64, 64, 64]) + f = self.relu(f) + f = self.maxpool(f) #[16, 64, 32, 32] + + f = self.layer1(f) #[16, 64, 32, 32] + f = self.layer2(f) #[16, 128, 16, 16]) + f = self.layer3(f) #[16, 256, 8, 8] + f = self.layer4(f) #[16, 512, 4, 4] + f = self.avgpool(f) #[16, 512, 1, 1] + out = f.squeeze(3).squeeze(2) + fake = self.classify(out) + jacobian = jacobian.reshape(jacobian.shape[0],jacobian.shape[1],4) + neu_input = torch.cat((value,jacobian),2) + posi_input = self.embed_fn(neu_input) + posi_input =posi_input.reshape(posi_input.shape[0],-1) + ner_feature = self.fc_p(posi_input) + all_fc = self.fc_all(torch.cat((out,ner_feature),1)).reshape(-1,128,4,4) + feature_map = self.final(all_fc) + + prediction = self.kp(feature_map) #[4,10,H/4-6, W/4-6] + + final_shape = prediction.shape + + heatmap = prediction.view(final_shape[0], final_shape[1], -1) #[4, 10, 58*58] + heatmap = F.softmax(heatmap / self.temperature, dim=2) + heatmap = heatmap.view(*final_shape) #[4,10,58,58] + + out = self.gaussian2kp(heatmap) + out['heatmap'] = heatmap + + if self.jacobian is not None: + jacobian_map = self.jacobian(feature_map) ##[4,40,H/4-6, W/4-6] + jacobian_map = jacobian_map.reshape(final_shape[0], 10, 4, final_shape[2], + final_shape[3]) + heatmap = heatmap.unsqueeze(2) + + jacobian = heatmap * jacobian_map #[4,10,4,H/4-6, W/4-6] + jacobian = jacobian.view(final_shape[0], final_shape[1], 4, -1) + jacobian = jacobian.sum(dim=-1) #[4,10,4] + jacobian = jacobian.view(jacobian.shape[0], jacobian.shape[1], 2, 2) #[4,10,2,2] + out['jacobian'] = jacobian + + + + return out, fake + + +def conv2d(channel_in, channel_out, + ksize=3, stride=1, padding=1, + activation=nn.ReLU, + normalizer=nn.BatchNorm2d): + layer = list() + bias = True if not normalizer else False + + layer.append(nn.Conv2d(channel_in, channel_out, + ksize, stride, padding, + bias=bias)) + _apply(layer, activation, normalizer, channel_out) + # init.kaiming_normal(layer[0].weight) + + return nn.Sequential(*layer) + +def _apply(layer, activation, normalizer, channel_out=None): + if normalizer: + layer.append(normalizer(channel_out)) + if activation: + layer.append(activation()) + return layer \ No newline at end of file diff --git a/FONT/ops.py b/FONT/ops.py new file mode 100644 index 0000000000000000000000000000000000000000..e956642183e7a5ab358d3656daf3d47e95ccd24e --- /dev/null +++ b/FONT/ops.py @@ -0,0 +1,96 @@ +import torch +import torchvision +import torch.nn as nn +import torch.nn.init as init +from torch.autograd import Variable + + +class ResidualBlock(nn.Module): + def __init__(self, channel_in, channel_out): + super(ResidualBlock, self).__init__() + + self.block = nn.Sequential( + conv3d(channel_in, channel_out, 3, 1, 1), + conv3d(channel_out, channel_out, 3, 1, 1, activation=None) + ) + + self.lrelu = nn.ReLU(0.2) + + def forward(self, x): + residual = x + out = self.block(x) + + out += residual + out = self.lrelu(out) + return out + +def linear(channel_in, channel_out, + activation=nn.ReLU, + normalizer=nn.BatchNorm1d): + layer = list() + bias = True if not normalizer else False + + layer.append(nn.Linear(channel_in, channel_out, bias=bias)) + _apply(layer, activation, normalizer, channel_out) + # init.kaiming_normal(layer[0].weight) + + return nn.Sequential(*layer) + + +def conv2d(channel_in, channel_out, + ksize=3, stride=1, padding=1, + activation=nn.ReLU, + normalizer=nn.BatchNorm2d): + layer = list() + bias = True if not normalizer else False + + layer.append(nn.Conv2d(channel_in, channel_out, + ksize, stride, padding, + bias=bias)) + _apply(layer, activation, normalizer, channel_out) + # init.kaiming_normal(layer[0].weight) + + return nn.Sequential(*layer) + + +def conv_transpose2d(channel_in, channel_out, + ksize=4, stride=2, padding=1, + activation=nn.ReLU, + normalizer=nn.BatchNorm2d): + layer = list() + bias = True if not normalizer else False + + layer.append(nn.ConvTranspose2d(channel_in, channel_out, + ksize, stride, padding, + bias=bias)) + _apply(layer, activation, normalizer, channel_out) + # init.kaiming_normal(layer[0].weight) + + return nn.Sequential(*layer) + + +def nn_conv2d(channel_in, channel_out, + ksize=3, stride=1, padding=1, + scale_factor=2, + activation=nn.ReLU, + normalizer=nn.BatchNorm2d): + layer = list() + bias = True if not normalizer else False + + layer.append(nn.UpsamplingNearest2d(scale_factor=scale_factor)) + layer.append(nn.Conv2d(channel_in, channel_out, + ksize, stride, padding, + bias=bias)) + _apply(layer, activation, normalizer, channel_out) + # init.kaiming_normal(layer[1].weight) + + return nn.Sequential(*layer) + + +def _apply(layer, activation, normalizer, channel_out=None): + if normalizer: + layer.append(normalizer(channel_out)) + if activation: + layer.append(activation()) + return layer + diff --git a/FONT/process_data.py b/FONT/process_data.py new file mode 100644 index 0000000000000000000000000000000000000000..53af7055b122a7c08c764917b5e840d6c00b7c67 --- /dev/null +++ b/FONT/process_data.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- +""" +Created on Thu Jun 24 11:36:01 2021 + +@author: Xinya +""" + +import os +import glob +import time +import numpy as np +import csv +import cv2 +import dlib + +from skimage import transform as tf + +detector = dlib.get_frontal_face_detector() +predictor = dlib.shape_predictor('./shape_predictor_68_face_landmarks.dat') + + +import imageio + + + +def save(path, frames, format): + if format == '.mp4': + imageio.mimsave(path, frames) + elif format == '.png': + if not os.path.exists(path): + + + os.makedirs(path) + for j, frame in enumerate(frames): + cv2.imwrite(path+'/'+str(j)+'.png',frame) + # imageio.imsave(os.path.join(path, str(j) + '.png'), frames[j]) + else: + print ("Unknown format %s" % format) + exit() + +def crop_image(image_path, out_path): + template = np.load('./M003_template.npy') + image = cv2.imread(image_path) + gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + rects = detector(gray, 1) #detect human face + if len(rects) != 1: + return 0 + for (j, rect) in enumerate(rects): + shape = predictor(gray, rect) #detect 68 points + shape = shape_to_np(shape) + + pts2 = np.float32(template[:47,:]) + # pts2 = np.float32(template[17:35,:]) + # pts1 = np.vstack((landmark[27:36,:], landmark[39,:],landmark[42,:],landmark[45,:])) + pts1 = np.float32(shape[:47,:]) #eye and nose + # pts1 = np.float32(landmark[17:35,:]) + tform = tf.SimilarityTransform() + tform.estimate( pts2, pts1) #Set the transformation matrix with the explicit parameters. + + dst = tf.warp(image, tform, output_shape=(256, 256)) + + dst = np.array(dst * 255, dtype=np.uint8) + + + cv2.imwrite(out_path,dst) + +def shape_to_np(shape, dtype="int"): + # initialize the list of (x, y)-coordinates + coords = np.zeros((shape.num_parts, 2), dtype=dtype) + + # loop over all facial landmarks and convert them + # to a 2-tuple of (x, y)-coordinates + for i in range(0, shape.num_parts): + coords[i] = (shape.part(i).x, shape.part(i).y) + + # return the list of (x, y)-coordinates + return coords + + +def crop_image_tem(video_path, out_path): + image_all = [] + videoCapture = cv2.VideoCapture(video_path) + success, frame = videoCapture.read() + n = 0 + while success : + image_all.append(frame) + n = n + 1 + success, frame = videoCapture.read() + + if len(image_all)!=0 : + template = np.load('./M003_template.npy') + image=image_all[0] + gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + rects = detector(gray, 1) #detect human face + if len(rects) != 1: + return 0 + for (j, rect) in enumerate(rects): + shape = predictor(gray, rect) #detect 68 points + shape = shape_to_np(shape) + + pts2 = np.float32(template[:47,:]) + # pts2 = np.float32(template[17:35,:]) + # pts1 = np.vstack((landmark[27:36,:], landmark[39,:],landmark[42,:],landmark[45,:])) + pts1 = np.float32(shape[:47,:]) #eye and nose + # pts1 = np.float32(landmark[17:35,:]) + tform = tf.SimilarityTransform() + tform.estimate( pts2, pts1) #Set the transformation matrix with the explicit parameters. + out = [] + for i in range(len(image_all)): + image = image_all[i] + dst = tf.warp(image, tform, output_shape=(256, 256)) + + dst = np.array(dst * 255, dtype=np.uint8) + out.append(dst) + if not os.path.exists(out_path): + os.makedirs(out_path) + save(out_path,out,'.png') + +def proc_audio(src_mouth_path, dst_audio_path): + audio_command = 'ffmpeg -i \"{}\" -loglevel error -y -f wav -acodec pcm_s16le ' \ + '-ar 16000 \"{}\"'.format(src_mouth_path, dst_audio_path) + os.system(audio_command) + + + +if __name__ == "__main__": + #video alignment + video_path = './test/crop/M030_sad_3_001.mp4' + out_path = './test/crop/M030_sad_3_001' + crop_image_tem(video_path, out_path) + + #image alignment + image_path = './test/raw_image/brade2.jpg' + out_path = './test/image/brade2.jpg' + crop_image(image_path, out_path) + + #change_audio_sample_rate + src_mouth_path = './test/audio/00015.mp3' + dst_audio_path = './test/audio/00015.mov' + proc_audio(src_mouth_path, dst_audio_path) + + + diff --git a/FONT/requirements.txt b/FONT/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..a34f7b8102b52c84d6d0eca1215377efaafdd5b0 --- /dev/null +++ b/FONT/requirements.txt @@ -0,0 +1,16 @@ +torch==1.10.1 +torchvision==0.11.2 +numpy +librosa +opencv-python +python_speech_features +pickle +matplotlib +scikit-image +Pillow +tqdm +dlib +scipy +yaml +imageio +csv diff --git a/FONT/result/all.mov b/FONT/result/all.mov new file mode 100644 index 0000000000000000000000000000000000000000..30d7088dcd5022dbb2165c234b6207fca8dd7403 --- /dev/null +++ b/FONT/result/all.mov @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dece5be32b925f419a781d3b931ceadfde409a3e89629161afbe61e8bf6cc42d +size 966293 diff --git a/FONT/result/all_with_pose.mov b/FONT/result/all_with_pose.mov new file mode 100644 index 0000000000000000000000000000000000000000..371821a578c0414d8d0c080bd4e47143bad2ba89 --- /dev/null +++ b/FONT/result/all_with_pose.mov @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac55c4f07db9444a5291bb619865e04c8361f276fdb4da8f39f95f49b8daed60 +size 1018871 diff --git a/FONT/run.py b/FONT/run.py new file mode 100644 index 0000000000000000000000000000000000000000..7f60e792d44eb2d407a5dade057f48b1cad16121 --- /dev/null +++ b/FONT/run.py @@ -0,0 +1,137 @@ +import matplotlib + +matplotlib.use('Agg') + +import os, sys +import yaml +from argparse import ArgumentParser +from time import gmtime, strftime +from shutil import copy + +# from frames_dataset import MeadDataset, AudioDataset, VoxDataset +from frames_dataset_liujin import MeadDataset, AudioDataset, VoxDataset, HDTFDataset + +from modules.generator import OcclusionAwareGenerator +from modules.discriminator import MultiScaleDiscriminator +from modules.keypoint_detector import KPDetector, Audio_Feature, KPDetector_a +from modules.util import AT_net,Emotion_k +# from modules.util import get_logger +import torch + +from train import train_part1, train_part1_fine_tune, train_part2 +# from reconstruction import reconstruction +# from animate import animate + +import warnings +warnings.filterwarnings("ignore") + +if __name__ == "__main__": + + if sys.version_info[0] < 3: + raise Exception("You must use Python 3 or higher. Recommended version is Python 3.7") + + parser = ArgumentParser() + parser.add_argument("--config", default="config/train_part1.yaml", help="path to config")# required=True + parser.add_argument("--mode", default="train_part1", choices=["train_part1", "train_part1_fine_tune", "train_part2"]) + parser.add_argument("--log_dir", default='log', help="path to log into") + parser.add_argument("--checkpoint", default='124_52000.pth.tar', help="path to checkpoint to restore") + parser.add_argument("--audio_checkpoint", default=None, help="path to audio_checkpoint to restore") + parser.add_argument("--emo_checkpoint", default=None, help="path to audio_checkpoint to restore") + parser.add_argument("--device_ids", default="0", type=lambda x: list(map(int, x.split(','))), + help="Names of the devices comma separated.") + parser.add_argument("--verbose", dest="verbose", action="store_true", help="Print model architecture") + parser.set_defaults(verbose=False) + parser.add_argument("--comment", default='comment', help="comment about experiment") + + opt = parser.parse_args() + with open(opt.config) as f: + config = yaml.load(f) + + name = os.path.basename(opt.config).split('.')[0] + if opt.checkpoint is not None: + + log_dir = os.path.join(opt.log_dir, os.path.basename(opt.config).split('.')[0]) + # log_dir += ' ' + strftime("%d_%m_%y_%H.%M.%S", gmtime()) + log_dir += '_' + opt.comment + else: + log_dir = os.path.join(opt.log_dir, os.path.basename(opt.config).split('.')[0]) + # log_dir += ' ' + strftime("%d_%m_%y_%H.%M.%S", gmtime()) + log_dir += '_' + opt.comment + + if not os.path.exists(log_dir): + os.makedirs(log_dir) + if not os.path.exists(os.path.join(log_dir, os.path.basename(opt.config))): + copy(opt.config, log_dir) + + # logger = get_logger(os.path.join(log_dir, "log.txt")) + + generator = OcclusionAwareGenerator(**config['model_params']['generator_params'], + **config['model_params']['common_params']) + + if torch.cuda.is_available(): + generator.to(opt.device_ids[0]) + + if opt.verbose: + print(generator) + + discriminator = MultiScaleDiscriminator(**config['model_params']['discriminator_params'], + **config['model_params']['common_params']) + if torch.cuda.is_available(): + discriminator.to(opt.device_ids[0]) + + + + if opt.verbose: + print(discriminator) + + kp_detector = KPDetector(**config['model_params']['kp_detector_params'], + **config['model_params']['common_params']) + + kp_detector_a = KPDetector_a(**config['model_params']['kp_detector_params'], + **config['model_params']['audio_params']) + + if torch.cuda.is_available(): + kp_detector.to(opt.device_ids[0]) + kp_detector_a.to(opt.device_ids[0]) + + audio_feature = AT_net() + emo_feature = Emotion_k(block_expansion=32, num_channels=3, max_features=1024, + num_blocks=5, scale_factor=0.25, num_classes=8) + + if torch.cuda.is_available(): + audio_feature.to(opt.device_ids[0]) + emo_feature.to(opt.device_ids[0]) + + if opt.verbose: + print(kp_detector) + print(kp_detector_a) + print(audio_feature) + print(emo_feature) + +# logger.info("Successfully load models.") + + if config['dataset_params']['name'] == 'Vox': + dataset = VoxDataset(is_train=True, **config['dataset_params']) + test_dataset = VoxDataset(is_train=False, **config['dataset_params']) + elif config['dataset_params']['name'] == 'Lrw': + dataset = AudioDataset(is_train=True, **config['dataset_params']) + test_dataset = AudioDataset(is_train=False, **config['dataset_params']) + elif config['dataset_params']['name'] == 'MEAD': + dataset = MeadDataset(is_train=True, **config['dataset_params']) + test_dataset = MeadDataset(is_train=False, **config['dataset_params']) + elif config['dataset_params']['name'] == 'hdtf': + dataset = HDTFDataset(is_train=True, **config['dataset_params']) + test_dataset = HDTFDataset(is_train=False, **config['dataset_params']) + + + + + if opt.mode == 'train_part1': + print("Training part1...") + train_part1(config, generator, discriminator, kp_detector, kp_detector_a,audio_feature, opt.checkpoint, opt.audio_checkpoint, log_dir, dataset, test_dataset,opt.device_ids, name) + elif opt.mode == 'train_part1_fine_tune': + print("Finetune part1...") + train_part1_fine_tune(config, generator, discriminator, kp_detector, kp_detector_a,audio_feature, opt.checkpoint, opt.audio_checkpoint, log_dir, dataset, test_dataset,opt.device_ids, name) + elif opt.mode == 'train_part2': + print("Training part2...") + train_part2(config, generator, discriminator, kp_detector, emo_feature,kp_detector_a,audio_feature, opt.checkpoint, opt.audio_checkpoint, opt.emo_checkpoint, log_dir, dataset,test_dataset,opt.device_ids, name) diff --git a/FONT/script_backup.sh b/FONT/script_backup.sh new file mode 100644 index 0000000000000000000000000000000000000000..c6def5185a8dad5aefa301372c1b0e39a8e98f88 --- /dev/null +++ b/FONT/script_backup.sh @@ -0,0 +1,17 @@ +--pose_given "/data/liujin/dataset/preprocess/LRW/data_file/test_video_pose.npy" \ +--pose_given "/data/liujin/_new_idea_211030_ICME/audio2pose_vae/result/test_pose_long_vae.npy" \ +--audio_checkpoint "./log/train_part1_wav2lip_pretrain_3dmm/294-00010000-checkpoint.pth.tar" \ +--checkpoint "./log/train_part1_fine_tune_wav2lip_pretrain_3dmm/392-00080000-checkpoint.pth.tar" \ +--audio_checkpoint "/data/liujin/EAMM-main/log/train_part1_hdtf_wav2lip_pretrain_3dmm_hdtf/3124-00022500-checkpoint.pth.tar" \ +--checkpoint "/data/liujin/EAMM-main/log/train_part1_fine_tune_hdtf_wav2lip_pretrain_3dmm_hdtf/3341-00005000-checkpoint.pth.tar" \ +--pose_long TRUE \ +--pose_file "/data/liujin/dataset/preprocess/LRW/data_file/test_first_frame_pose.npy" \ +--pose_given "/data/liujin/dataset/preprocess/LRW/data_file/test_video_pose.npy" \ +--source_image ./test/image/RD_Radio10_000.png \ +--source_image "./test/image/ABOUT_00994.jpg" \ +--source_image "/data/liujin/dataset/LRW/lipread_frames/ABOUT/train/ABOUT_00994/000000.jpg" \ +--in_file "/data/liujin/dataset/LRW/lipread_wav/ABOUT/train/ABOUT_00994.wav" \ +--pose_file "/data/liujin/dataset/HDTF/pose_3DDFA_256/RD_Radio20_000/000000.npy" \ +--pose_given "/data/liujin/dataset/preprocess/LRW/data_file/test_pose_long_3ddfa.npy" \ +--source_image "/data/liujin/dataset/HDTF/frames_256/RD_Radio26_000/000000.png" \ +--pose_given "./test/pose_long/50IAfJCypFI_Alex_Kingston_50IAfJCypFI_0001.npy" \ \ No newline at end of file diff --git a/FONT/script_finetune_liujin.sh b/FONT/script_finetune_liujin.sh new file mode 100644 index 0000000000000000000000000000000000000000..8df6dc472691ab7c59b687fbd05897e807ae36b2 --- /dev/null +++ b/FONT/script_finetune_liujin.sh @@ -0,0 +1 @@ +CUDA_VISIBLE_DEVICES=3 nohup python -u run.py --config config/train_part1_fine_tune.yaml --mode train_part1_fine_tune --checkpoint ckpt/124_52000.pth.tar --audio_checkpoint log/train_part1_wav2lip_pretrain_3dmm/294-00010000-checkpoint.pth.tar --comment wav2lip_pretrain_3dmm > out_liujin_finetune_wav2lip_pretrain_3dmm.log 2>&1 & \ No newline at end of file diff --git a/FONT/script_test_liujin.sh b/FONT/script_test_liujin.sh new file mode 100644 index 0000000000000000000000000000000000000000..5beff8b035923f42ffddb2205d3fe3b540f73871 --- /dev/null +++ b/FONT/script_test_liujin.sh @@ -0,0 +1,9 @@ +CUDA_VISIBLE_DEVICES=1 \ +python demo.py \ +--source_image "/data/liujin/dataset/LRW/lipread_frames/ABOUT/test/ABOUT_00014/000000.jpg" \ +--in_file "/data/liujin/EAMM-main/test/audio/icme_intro.mp3" \ +--pose_file "/data/liujin/dataset/LRW/lipread_3DDFA_pose/ABOUT/test/ABOUT_00014/000000.npy" \ +--pose_given "/data/liujin/dataset/preprocess/LRW/data_file/test_pose_long_3ddfa_hdtf.npy" \ +--pose_long TRUE + + diff --git a/FONT/script_train_liujin.sh b/FONT/script_train_liujin.sh new file mode 100644 index 0000000000000000000000000000000000000000..94cb40eb1814436bfb07cb3e1e2b6baa6fdcb81b --- /dev/null +++ b/FONT/script_train_liujin.sh @@ -0,0 +1 @@ +CUDA_VISIBLE_DEVICES=2 nohup python -u run.py --config config/train_part1.yaml --mode train_part1 --checkpoint ckpt/124_52000.pth.tar > out_liujin_230823.log 2>&1 & \ No newline at end of file diff --git a/FONT/sync_batchnorm/__init__.py b/FONT/sync_batchnorm/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..bc8709d92c610b36e0bcbd7da20c1eb41dc8cfcf --- /dev/null +++ b/FONT/sync_batchnorm/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# File : __init__.py +# Author : Jiayuan Mao +# Email : maojiayuan@gmail.com +# Date : 27/01/2018 +# +# This file is part of Synchronized-BatchNorm-PyTorch. +# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch +# Distributed under MIT License. + +from .batchnorm import SynchronizedBatchNorm1d, SynchronizedBatchNorm2d, SynchronizedBatchNorm3d +from .replicate import DataParallelWithCallback, patch_replication_callback diff --git a/FONT/sync_batchnorm/batchnorm.py b/FONT/sync_batchnorm/batchnorm.py new file mode 100644 index 0000000000000000000000000000000000000000..5f4e763f0366dffa10320116413f8c7181a8aeb1 --- /dev/null +++ b/FONT/sync_batchnorm/batchnorm.py @@ -0,0 +1,315 @@ +# -*- coding: utf-8 -*- +# File : batchnorm.py +# Author : Jiayuan Mao +# Email : maojiayuan@gmail.com +# Date : 27/01/2018 +# +# This file is part of Synchronized-BatchNorm-PyTorch. +# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch +# Distributed under MIT License. + +import collections + +import torch +import torch.nn.functional as F + +from torch.nn.modules.batchnorm import _BatchNorm +from torch.nn.parallel._functions import ReduceAddCoalesced, Broadcast + +from .comm import SyncMaster + +__all__ = ['SynchronizedBatchNorm1d', 'SynchronizedBatchNorm2d', 'SynchronizedBatchNorm3d'] + + +def _sum_ft(tensor): + """sum over the first and last dimention""" + return tensor.sum(dim=0).sum(dim=-1) + + +def _unsqueeze_ft(tensor): + """add new dementions at the front and the tail""" + return tensor.unsqueeze(0).unsqueeze(-1) + + +_ChildMessage = collections.namedtuple('_ChildMessage', ['sum', 'ssum', 'sum_size']) +_MasterMessage = collections.namedtuple('_MasterMessage', ['sum', 'inv_std']) + + +class _SynchronizedBatchNorm(_BatchNorm): + def __init__(self, num_features, eps=1e-5, momentum=0.1, affine=True): + super(_SynchronizedBatchNorm, self).__init__(num_features, eps=eps, momentum=momentum, affine=affine) + + self._sync_master = SyncMaster(self._data_parallel_master) + + self._is_parallel = False + self._parallel_id = None + self._slave_pipe = None + + def forward(self, input): + # If it is not parallel computation or is in evaluation mode, use PyTorch's implementation. + if not (self._is_parallel and self.training): + return F.batch_norm( + input, self.running_mean, self.running_var, self.weight, self.bias, + self.training, self.momentum, self.eps) + + # Resize the input to (B, C, -1). + input_shape = input.size() + input = input.view(input.size(0), self.num_features, -1) + + # Compute the sum and square-sum. + sum_size = input.size(0) * input.size(2) + input_sum = _sum_ft(input) + input_ssum = _sum_ft(input ** 2) + + # Reduce-and-broadcast the statistics. + if self._parallel_id == 0: + mean, inv_std = self._sync_master.run_master(_ChildMessage(input_sum, input_ssum, sum_size)) + else: + mean, inv_std = self._slave_pipe.run_slave(_ChildMessage(input_sum, input_ssum, sum_size)) + + # Compute the output. + if self.affine: + # MJY:: Fuse the multiplication for speed. + output = (input - _unsqueeze_ft(mean)) * _unsqueeze_ft(inv_std * self.weight) + _unsqueeze_ft(self.bias) + else: + output = (input - _unsqueeze_ft(mean)) * _unsqueeze_ft(inv_std) + + # Reshape it. + return output.view(input_shape) + + def __data_parallel_replicate__(self, ctx, copy_id): + self._is_parallel = True + self._parallel_id = copy_id + + # parallel_id == 0 means master device. + if self._parallel_id == 0: + ctx.sync_master = self._sync_master + else: + self._slave_pipe = ctx.sync_master.register_slave(copy_id) + + def _data_parallel_master(self, intermediates): + """Reduce the sum and square-sum, compute the statistics, and broadcast it.""" + + # Always using same "device order" makes the ReduceAdd operation faster. + # Thanks to:: Tete Xiao (http://tetexiao.com/) + intermediates = sorted(intermediates, key=lambda i: i[1].sum.get_device()) + + to_reduce = [i[1][:2] for i in intermediates] + to_reduce = [j for i in to_reduce for j in i] # flatten + target_gpus = [i[1].sum.get_device() for i in intermediates] + + sum_size = sum([i[1].sum_size for i in intermediates]) + sum_, ssum = ReduceAddCoalesced.apply(target_gpus[0], 2, *to_reduce) + mean, inv_std = self._compute_mean_std(sum_, ssum, sum_size) + + broadcasted = Broadcast.apply(target_gpus, mean, inv_std) + + outputs = [] + for i, rec in enumerate(intermediates): + outputs.append((rec[0], _MasterMessage(*broadcasted[i*2:i*2+2]))) + + return outputs + + def _compute_mean_std(self, sum_, ssum, size): + """Compute the mean and standard-deviation with sum and square-sum. This method + also maintains the moving average on the master device.""" + assert size > 1, 'BatchNorm computes unbiased standard-deviation, which requires size > 1.' + mean = sum_ / size + sumvar = ssum - sum_ * mean + unbias_var = sumvar / (size - 1) + bias_var = sumvar / size + + self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * mean.data + self.running_var = (1 - self.momentum) * self.running_var + self.momentum * unbias_var.data + + return mean, bias_var.clamp(self.eps) ** -0.5 + + +class SynchronizedBatchNorm1d(_SynchronizedBatchNorm): + r"""Applies Synchronized Batch Normalization over a 2d or 3d input that is seen as a + mini-batch. + + .. math:: + + y = \frac{x - mean[x]}{ \sqrt{Var[x] + \epsilon}} * gamma + beta + + This module differs from the built-in PyTorch BatchNorm1d as the mean and + standard-deviation are reduced across all devices during training. + + For example, when one uses `nn.DataParallel` to wrap the network during + training, PyTorch's implementation normalize the tensor on each device using + the statistics only on that device, which accelerated the computation and + is also easy to implement, but the statistics might be inaccurate. + Instead, in this synchronized version, the statistics will be computed + over all training samples distributed on multiple devices. + + Note that, for one-GPU or CPU-only case, this module behaves exactly same + as the built-in PyTorch implementation. + + The mean and standard-deviation are calculated per-dimension over + the mini-batches and gamma and beta are learnable parameter vectors + of size C (where C is the input size). + + During training, this layer keeps a running estimate of its computed mean + and variance. The running sum is kept with a default momentum of 0.1. + + During evaluation, this running mean/variance is used for normalization. + + Because the BatchNorm is done over the `C` dimension, computing statistics + on `(N, L)` slices, it's common terminology to call this Temporal BatchNorm + + Args: + num_features: num_features from an expected input of size + `batch_size x num_features [x width]` + eps: a value added to the denominator for numerical stability. + Default: 1e-5 + momentum: the value used for the running_mean and running_var + computation. Default: 0.1 + affine: a boolean value that when set to ``True``, gives the layer learnable + affine parameters. Default: ``True`` + + Shape: + - Input: :math:`(N, C)` or :math:`(N, C, L)` + - Output: :math:`(N, C)` or :math:`(N, C, L)` (same shape as input) + + Examples: + >>> # With Learnable Parameters + >>> m = SynchronizedBatchNorm1d(100) + >>> # Without Learnable Parameters + >>> m = SynchronizedBatchNorm1d(100, affine=False) + >>> input = torch.autograd.Variable(torch.randn(20, 100)) + >>> output = m(input) + """ + + def _check_input_dim(self, input): + if input.dim() != 2 and input.dim() != 3: + raise ValueError('expected 2D or 3D input (got {}D input)' + .format(input.dim())) + super(SynchronizedBatchNorm1d, self)._check_input_dim(input) + + +class SynchronizedBatchNorm2d(_SynchronizedBatchNorm): + r"""Applies Batch Normalization over a 4d input that is seen as a mini-batch + of 3d inputs + + .. math:: + + y = \frac{x - mean[x]}{ \sqrt{Var[x] + \epsilon}} * gamma + beta + + This module differs from the built-in PyTorch BatchNorm2d as the mean and + standard-deviation are reduced across all devices during training. + + For example, when one uses `nn.DataParallel` to wrap the network during + training, PyTorch's implementation normalize the tensor on each device using + the statistics only on that device, which accelerated the computation and + is also easy to implement, but the statistics might be inaccurate. + Instead, in this synchronized version, the statistics will be computed + over all training samples distributed on multiple devices. + + Note that, for one-GPU or CPU-only case, this module behaves exactly same + as the built-in PyTorch implementation. + + The mean and standard-deviation are calculated per-dimension over + the mini-batches and gamma and beta are learnable parameter vectors + of size C (where C is the input size). + + During training, this layer keeps a running estimate of its computed mean + and variance. The running sum is kept with a default momentum of 0.1. + + During evaluation, this running mean/variance is used for normalization. + + Because the BatchNorm is done over the `C` dimension, computing statistics + on `(N, H, W)` slices, it's common terminology to call this Spatial BatchNorm + + Args: + num_features: num_features from an expected input of + size batch_size x num_features x height x width + eps: a value added to the denominator for numerical stability. + Default: 1e-5 + momentum: the value used for the running_mean and running_var + computation. Default: 0.1 + affine: a boolean value that when set to ``True``, gives the layer learnable + affine parameters. Default: ``True`` + + Shape: + - Input: :math:`(N, C, H, W)` + - Output: :math:`(N, C, H, W)` (same shape as input) + + Examples: + >>> # With Learnable Parameters + >>> m = SynchronizedBatchNorm2d(100) + >>> # Without Learnable Parameters + >>> m = SynchronizedBatchNorm2d(100, affine=False) + >>> input = torch.autograd.Variable(torch.randn(20, 100, 35, 45)) + >>> output = m(input) + """ + + def _check_input_dim(self, input): + if input.dim() != 4: + raise ValueError('expected 4D input (got {}D input)' + .format(input.dim())) + super(SynchronizedBatchNorm2d, self)._check_input_dim(input) + + +class SynchronizedBatchNorm3d(_SynchronizedBatchNorm): + r"""Applies Batch Normalization over a 5d input that is seen as a mini-batch + of 4d inputs + + .. math:: + + y = \frac{x - mean[x]}{ \sqrt{Var[x] + \epsilon}} * gamma + beta + + This module differs from the built-in PyTorch BatchNorm3d as the mean and + standard-deviation are reduced across all devices during training. + + For example, when one uses `nn.DataParallel` to wrap the network during + training, PyTorch's implementation normalize the tensor on each device using + the statistics only on that device, which accelerated the computation and + is also easy to implement, but the statistics might be inaccurate. + Instead, in this synchronized version, the statistics will be computed + over all training samples distributed on multiple devices. + + Note that, for one-GPU or CPU-only case, this module behaves exactly same + as the built-in PyTorch implementation. + + The mean and standard-deviation are calculated per-dimension over + the mini-batches and gamma and beta are learnable parameter vectors + of size C (where C is the input size). + + During training, this layer keeps a running estimate of its computed mean + and variance. The running sum is kept with a default momentum of 0.1. + + During evaluation, this running mean/variance is used for normalization. + + Because the BatchNorm is done over the `C` dimension, computing statistics + on `(N, D, H, W)` slices, it's common terminology to call this Volumetric BatchNorm + or Spatio-temporal BatchNorm + + Args: + num_features: num_features from an expected input of + size batch_size x num_features x depth x height x width + eps: a value added to the denominator for numerical stability. + Default: 1e-5 + momentum: the value used for the running_mean and running_var + computation. Default: 0.1 + affine: a boolean value that when set to ``True``, gives the layer learnable + affine parameters. Default: ``True`` + + Shape: + - Input: :math:`(N, C, D, H, W)` + - Output: :math:`(N, C, D, H, W)` (same shape as input) + + Examples: + >>> # With Learnable Parameters + >>> m = SynchronizedBatchNorm3d(100) + >>> # Without Learnable Parameters + >>> m = SynchronizedBatchNorm3d(100, affine=False) + >>> input = torch.autograd.Variable(torch.randn(20, 100, 35, 45, 10)) + >>> output = m(input) + """ + + def _check_input_dim(self, input): + if input.dim() != 5: + raise ValueError('expected 5D input (got {}D input)' + .format(input.dim())) + super(SynchronizedBatchNorm3d, self)._check_input_dim(input) diff --git a/FONT/sync_batchnorm/comm.py b/FONT/sync_batchnorm/comm.py new file mode 100644 index 0000000000000000000000000000000000000000..922f8c4a3adaa9b32fdcaef09583be03b0d7eb2b --- /dev/null +++ b/FONT/sync_batchnorm/comm.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# File : comm.py +# Author : Jiayuan Mao +# Email : maojiayuan@gmail.com +# Date : 27/01/2018 +# +# This file is part of Synchronized-BatchNorm-PyTorch. +# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch +# Distributed under MIT License. + +import queue +import collections +import threading + +__all__ = ['FutureResult', 'SlavePipe', 'SyncMaster'] + + +class FutureResult(object): + """A thread-safe future implementation. Used only as one-to-one pipe.""" + + def __init__(self): + self._result = None + self._lock = threading.Lock() + self._cond = threading.Condition(self._lock) + + def put(self, result): + with self._lock: + assert self._result is None, 'Previous result has\'t been fetched.' + self._result = result + self._cond.notify() + + def get(self): + with self._lock: + if self._result is None: + self._cond.wait() + + res = self._result + self._result = None + return res + + +_MasterRegistry = collections.namedtuple('MasterRegistry', ['result']) +_SlavePipeBase = collections.namedtuple('_SlavePipeBase', ['identifier', 'queue', 'result']) + + +class SlavePipe(_SlavePipeBase): + """Pipe for master-slave communication.""" + + def run_slave(self, msg): + self.queue.put((self.identifier, msg)) + ret = self.result.get() + self.queue.put(True) + return ret + + +class SyncMaster(object): + """An abstract `SyncMaster` object. + + - During the replication, as the data parallel will trigger an callback of each module, all slave devices should + call `register(id)` and obtain an `SlavePipe` to communicate with the master. + - During the forward pass, master device invokes `run_master`, all messages from slave devices will be collected, + and passed to a registered callback. + - After receiving the messages, the master device should gather the information and determine to message passed + back to each slave devices. + """ + + def __init__(self, master_callback): + """ + + Args: + master_callback: a callback to be invoked after having collected messages from slave devices. + """ + self._master_callback = master_callback + self._queue = queue.Queue() + self._registry = collections.OrderedDict() + self._activated = False + + def __getstate__(self): + return {'master_callback': self._master_callback} + + def __setstate__(self, state): + self.__init__(state['master_callback']) + + def register_slave(self, identifier): + """ + Register an slave device. + + Args: + identifier: an identifier, usually is the device id. + + Returns: a `SlavePipe` object which can be used to communicate with the master device. + + """ + if self._activated: + assert self._queue.empty(), 'Queue is not clean before next initialization.' + self._activated = False + self._registry.clear() + future = FutureResult() + self._registry[identifier] = _MasterRegistry(future) + return SlavePipe(identifier, self._queue, future) + + def run_master(self, master_msg): + """ + Main entry for the master device in each forward pass. + The messages were first collected from each devices (including the master device), and then + an callback will be invoked to compute the message to be sent back to each devices + (including the master device). + + Args: + master_msg: the message that the master want to send to itself. This will be placed as the first + message when calling `master_callback`. For detailed usage, see `_SynchronizedBatchNorm` for an example. + + Returns: the message to be sent back to the master device. + + """ + self._activated = True + + intermediates = [(0, master_msg)] + for i in range(self.nr_slaves): + intermediates.append(self._queue.get()) + + results = self._master_callback(intermediates) + assert results[0][0] == 0, 'The first result should belongs to the master.' + + for i, res in results: + if i == 0: + continue + self._registry[i].result.put(res) + + for i in range(self.nr_slaves): + assert self._queue.get() is True + + return results[0][1] + + @property + def nr_slaves(self): + return len(self._registry) diff --git a/FONT/sync_batchnorm/replicate.py b/FONT/sync_batchnorm/replicate.py new file mode 100644 index 0000000000000000000000000000000000000000..b71c7b8ed51a1d6c55b1f753bdd8d90bad79bd06 --- /dev/null +++ b/FONT/sync_batchnorm/replicate.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# File : replicate.py +# Author : Jiayuan Mao +# Email : maojiayuan@gmail.com +# Date : 27/01/2018 +# +# This file is part of Synchronized-BatchNorm-PyTorch. +# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch +# Distributed under MIT License. + +import functools + +from torch.nn.parallel.data_parallel import DataParallel + +__all__ = [ + 'CallbackContext', + 'execute_replication_callbacks', + 'DataParallelWithCallback', + 'patch_replication_callback' +] + + +class CallbackContext(object): + pass + + +def execute_replication_callbacks(modules): + """ + Execute an replication callback `__data_parallel_replicate__` on each module created by original replication. + + The callback will be invoked with arguments `__data_parallel_replicate__(ctx, copy_id)` + + Note that, as all modules are isomorphism, we assign each sub-module with a context + (shared among multiple copies of this module on different devices). + Through this context, different copies can share some information. + + We guarantee that the callback on the master copy (the first copy) will be called ahead of calling the callback + of any slave copies. + """ + master_copy = modules[0] + nr_modules = len(list(master_copy.modules())) + ctxs = [CallbackContext() for _ in range(nr_modules)] + + for i, module in enumerate(modules): + for j, m in enumerate(module.modules()): + if hasattr(m, '__data_parallel_replicate__'): + m.__data_parallel_replicate__(ctxs[j], i) + + +class DataParallelWithCallback(DataParallel): + """ + Data Parallel with a replication callback. + + An replication callback `__data_parallel_replicate__` of each module will be invoked after being created by + original `replicate` function. + The callback will be invoked with arguments `__data_parallel_replicate__(ctx, copy_id)` + + Examples: + > sync_bn = SynchronizedBatchNorm1d(10, eps=1e-5, affine=False) + > sync_bn = DataParallelWithCallback(sync_bn, device_ids=[0, 1]) + # sync_bn.__data_parallel_replicate__ will be invoked. + """ + + def replicate(self, module, device_ids): + modules = super(DataParallelWithCallback, self).replicate(module, device_ids) + execute_replication_callbacks(modules) + return modules + + +def patch_replication_callback(data_parallel): + """ + Monkey-patch an existing `DataParallel` object. Add the replication callback. + Useful when you have customized `DataParallel` implementation. + + Examples: + > sync_bn = SynchronizedBatchNorm1d(10, eps=1e-5, affine=False) + > sync_bn = DataParallel(sync_bn, device_ids=[0, 1]) + > patch_replication_callback(sync_bn) + # this is equivalent to + > sync_bn = SynchronizedBatchNorm1d(10, eps=1e-5, affine=False) + > sync_bn = DataParallelWithCallback(sync_bn, device_ids=[0, 1]) + """ + + assert isinstance(data_parallel, DataParallel) + + old_replicate = data_parallel.replicate + + @functools.wraps(old_replicate) + def new_replicate(module, device_ids): + modules = old_replicate(module, device_ids) + execute_replication_callbacks(modules) + return modules + + data_parallel.replicate = new_replicate diff --git a/FONT/sync_batchnorm/unittest.py b/FONT/sync_batchnorm/unittest.py new file mode 100644 index 0000000000000000000000000000000000000000..0675c022e4ba85d38d1f813490f6740150909524 --- /dev/null +++ b/FONT/sync_batchnorm/unittest.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# File : unittest.py +# Author : Jiayuan Mao +# Email : maojiayuan@gmail.com +# Date : 27/01/2018 +# +# This file is part of Synchronized-BatchNorm-PyTorch. +# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch +# Distributed under MIT License. + +import unittest + +import numpy as np +from torch.autograd import Variable + + +def as_numpy(v): + if isinstance(v, Variable): + v = v.data + return v.cpu().numpy() + + +class TorchTestCase(unittest.TestCase): + def assertTensorClose(self, a, b, atol=1e-3, rtol=1e-3): + npa, npb = as_numpy(a), as_numpy(b) + self.assertTrue( + np.allclose(npa, npb, atol=atol), + 'Tensor close check failed\n{}\n{}\nadiff={}, rdiff={}'.format(a, b, np.abs(npa - npb).max(), np.abs((npa - npb) / np.fmax(npa, 1e-5)).max()) + ) diff --git a/FONT/test/audio/00015.mov b/FONT/test/audio/00015.mov new file mode 100644 index 0000000000000000000000000000000000000000..24f8b3b73c9ee9b1f51ad59426017cf131fe9c89 --- /dev/null +++ b/FONT/test/audio/00015.mov @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3a21c4f53bcfbf02f878cc165b05c05f735d8a4a2018a61eaa9ea26bada450a +size 449358 diff --git a/FONT/test/audio/00015.mp3 b/FONT/test/audio/00015.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..7e7243d2f5eef62e4f95225f4fbfa62d379b486d --- /dev/null +++ b/FONT/test/audio/00015.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33e7e89c9373c2e8b8a84a8000ca8a0a23c1c3a4a1f93b7ee461e546e5400b17 +size 113102 diff --git a/FONT/test/audio/741400104.mov b/FONT/test/audio/741400104.mov new file mode 100644 index 0000000000000000000000000000000000000000..a1fbb416a0853a4166850bc478f0502b7156fa6f --- /dev/null +++ b/FONT/test/audio/741400104.mov @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6aa11ed8ee5619d10a2d55570de40de1c9fac337d37b9bf834785600615a48e +size 766798 diff --git a/FONT/test/audio/sample1.mov b/FONT/test/audio/sample1.mov new file mode 100644 index 0000000000000000000000000000000000000000..621d7a3915720a8dfac7e772e688ba966a4998cb --- /dev/null +++ b/FONT/test/audio/sample1.mov @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:998f5c1a46d9071132954b3402192cc08eeaf7950d0c88f5dc7f4099874c4ef8 +size 723918 diff --git a/FONT/test/audio/sample1.mp3 b/FONT/test/audio/sample1.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..796afa08076995630aa2866e168433d2caa69c13 --- /dev/null +++ b/FONT/test/audio/sample1.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba41c4e11cec661a23b846dcde26ed0818572e9ae9e09067f26389bc4080b4b7 +size 181647 diff --git a/FONT/test/audio/sample2.mov b/FONT/test/audio/sample2.mov new file mode 100644 index 0000000000000000000000000000000000000000..e76241460147aed10b07352cdd049783a8ba57fd --- /dev/null +++ b/FONT/test/audio/sample2.mov @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:853cb2e923bcbcab3bd63d5ff8f00da49a9bfd8a4e858363ecd4d496b00b7be7 +size 404290 diff --git a/FONT/test/audio/sample2.mp3 b/FONT/test/audio/sample2.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ef2afb2c52260d314076c3da6a13437427dd1fac --- /dev/null +++ b/FONT/test/audio/sample2.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98835c35bdc4247c27c0f0b2a1463ff98018cfdc383cbd804cc5d4786e8d0d1f +size 404290 diff --git a/FONT/test/image/14.png b/FONT/test/image/14.png new file mode 100644 index 0000000000000000000000000000000000000000..b7cbbf7cf5f352999b2333b138587cd932061dfa --- /dev/null +++ b/FONT/test/image/14.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c30752e25cf382497342398cdc029faea25123bfe696af922ba7cd22085c4f5 +size 102458 diff --git a/FONT/test/image/21.png b/FONT/test/image/21.png new file mode 100644 index 0000000000000000000000000000000000000000..65d32223ad2a52d95dc852c204302ed35ff64f89 --- /dev/null +++ b/FONT/test/image/21.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f2d7fdb9c4c7fed5442b1b1e2380cd51d8b5b5f0368bbbbb756799cada350d7 +size 90104 diff --git a/FONT/test/image/60.png b/FONT/test/image/60.png new file mode 100644 index 0000000000000000000000000000000000000000..0671042252b7922b76ee18651b6ce17c6c2c693a --- /dev/null +++ b/FONT/test/image/60.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27984ac02c6262045b523647b888b1af1a06f01ebb3dbb6a45338ddeacf61935 +size 87035 diff --git a/FONT/test/image/7.png b/FONT/test/image/7.png new file mode 100644 index 0000000000000000000000000000000000000000..93801aaaba316f73de7c281752e3e03da2577e73 --- /dev/null +++ b/FONT/test/image/7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0a6f9b62123bfaef074958fa82af94e57ca7087b722547b8672d17a73910234 +size 81531 diff --git a/FONT/test/image/ABOUT_00994.jpg b/FONT/test/image/ABOUT_00994.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8867ffa69ca9b3455e4da2c8c78bb498df470a71 --- /dev/null +++ b/FONT/test/image/ABOUT_00994.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc9f6bd9b1e474562bf499fd429acc8dc9ee6a2b80b0b3e2ad15006e006065e5 +size 8300 diff --git a/FONT/test/image/anne.png b/FONT/test/image/anne.png new file mode 100644 index 0000000000000000000000000000000000000000..c20867b9f968ac2d5d7c3388b99378687416e852 --- /dev/null +++ b/FONT/test/image/anne.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:49f667f6e64bf2224b3d5422f6183f336ba7d0e42044c21eeeb1b90811ad8022 +size 108856 diff --git a/FONT/test/image/brade2.jpg b/FONT/test/image/brade2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0279f9ce1ba68fb1a58e67a1d3cff265e78f6bd2 --- /dev/null +++ b/FONT/test/image/brade2.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:922f4fb5178f476cb8c36ffa2334a2eb3b7774a8a86733260672434bd0a8868b +size 26904 diff --git a/FONT/test/image/dune_1.jpg b/FONT/test/image/dune_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..149a02c6a83feaad804f46e65bc69fe32a64773e --- /dev/null +++ b/FONT/test/image/dune_1.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2790d0084e8dac91662a569e62b2ffb61d453ad787b1819096c9f436d7f9f3a +size 17024 diff --git a/FONT/test/image/dune_2.jpg b/FONT/test/image/dune_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..70d93f6d0c8026bffac8bcf0e74c5175e088f547 --- /dev/null +++ b/FONT/test/image/dune_2.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b3d9b1786d99b30a824a1c739e1967d4da11b08ecc6868884f893a3bec37a20 +size 16642 diff --git a/FONT/test/image/jake4.png b/FONT/test/image/jake4.png new file mode 100644 index 0000000000000000000000000000000000000000..fe54a68ee32b4385f61cf69bfaf7bfc023e0223e --- /dev/null +++ b/FONT/test/image/jake4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:404a5b2416612d3cf56fc9211d74d13d33673c226488831a59028a5e35be40b2 +size 114109 diff --git a/FONT/test/image/mona.png b/FONT/test/image/mona.png new file mode 100644 index 0000000000000000000000000000000000000000..4b374fcbbde997644e600fe1706c9f00890d4df6 --- /dev/null +++ b/FONT/test/image/mona.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d26f1ff5ee16db603ba80b6e453e7034cbe15504763380d2d8714354d47e9138 +size 115675 diff --git a/FONT/test/image/paint1.jpg b/FONT/test/image/paint1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..79ca1a41c2bf429cda0d9a9009a79077ffa727dc --- /dev/null +++ b/FONT/test/image/paint1.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63ba01f7fb4ea147437b3e386e81253c7a42f40a5ed838c6b1988914e7a11f45 +size 27840 diff --git a/FONT/test/image/scarlett.jpg b/FONT/test/image/scarlett.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e43a0c67c17d044a140cf562c3c58eb52f425306 --- /dev/null +++ b/FONT/test/image/scarlett.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b631eb71e5a6b3e10771cc14f1973fbfea67a69582953cd53aba4c295d30a2eb +size 18064 diff --git a/FONT/test/pose/14.npy b/FONT/test/pose/14.npy new file mode 100644 index 0000000000000000000000000000000000000000..b2086049d67b80b1a7eaeb449ab6b46165aaff96 --- /dev/null +++ b/FONT/test/pose/14.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6528ad01e7e5c06cdcf2251cf17894b6de7e60a4d795fe17a88d0aecf3f282f7 +size 184 diff --git a/FONT/test/pose/21.npy b/FONT/test/pose/21.npy new file mode 100644 index 0000000000000000000000000000000000000000..35ea5b1b583bee75cd1e56aec10cd77d3ccc93ea --- /dev/null +++ b/FONT/test/pose/21.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:932a34633d817fcb49ec07810ce360dc5806aa92e59376657d462efcfeb7cfd0 +size 184 diff --git a/FONT/test/pose/60.npy b/FONT/test/pose/60.npy new file mode 100644 index 0000000000000000000000000000000000000000..da3f868379483884e67a4cd295072a192986fe1c --- /dev/null +++ b/FONT/test/pose/60.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:311e7faf874c26cf4ac59534d07d619de0b67d8fd9718c962999c5591913c502 +size 184 diff --git a/FONT/test/pose/7.npy b/FONT/test/pose/7.npy new file mode 100644 index 0000000000000000000000000000000000000000..3058002fc42ec5c19fa683c8cad86f13bb76124e --- /dev/null +++ b/FONT/test/pose/7.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f511be12251a66846a3b8973efe76a0bfab039904b59cf598c6843d3201d8fbd +size 184 diff --git a/FONT/test/pose/anne.npy b/FONT/test/pose/anne.npy new file mode 100644 index 0000000000000000000000000000000000000000..b64336d82f016f3d2fc7844b69aba0d0d13b00b7 --- /dev/null +++ b/FONT/test/pose/anne.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7aae66c424b6ef7d72d433ee17ed97a20a60ec348a2de6c7ee4bab3f7d361704 +size 184 diff --git a/FONT/test/pose/brade2.npy b/FONT/test/pose/brade2.npy new file mode 100644 index 0000000000000000000000000000000000000000..4dfa85f4ce77361495185f06d4b54ea0318b0edf --- /dev/null +++ b/FONT/test/pose/brade2.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f42d262492b157867eca8b21f259b0abcb6ebb3a00943229994b4af8a676f173 +size 184 diff --git a/FONT/test/pose/dune_1.npy b/FONT/test/pose/dune_1.npy new file mode 100644 index 0000000000000000000000000000000000000000..c09e75c77c1a15ecd3d3e11ffde7cdbd47831e99 --- /dev/null +++ b/FONT/test/pose/dune_1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78f32bd27b1bc0a62ac44203f72ae14c1283e2745f123cf28b126506f7a16d3b +size 184 diff --git a/FONT/test/pose/dune_2.npy b/FONT/test/pose/dune_2.npy new file mode 100644 index 0000000000000000000000000000000000000000..7a7080c8185ad1312c97a6b803653992bcc422bb --- /dev/null +++ b/FONT/test/pose/dune_2.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13557119658228854fe60e488cfedb3e6356f98dc38df222a5a162bc96c0496b +size 184 diff --git a/FONT/test/pose/jake4.npy b/FONT/test/pose/jake4.npy new file mode 100644 index 0000000000000000000000000000000000000000..714087f34dc2fcf2ac8893e41f80ef3bd1f1ea0a --- /dev/null +++ b/FONT/test/pose/jake4.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3df38a5b9101e1b2b02ceef11ddfac25c6c2108079e85b86a249813ceb1e2cdd +size 184 diff --git a/FONT/test/pose/mona.npy b/FONT/test/pose/mona.npy new file mode 100644 index 0000000000000000000000000000000000000000..6d3dd8038830236b80734e501172409464a32ea8 --- /dev/null +++ b/FONT/test/pose/mona.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b0bd8c608ce4d693ebdf84a69a4543478fb8bed7b9967c5674af6f643aa1a96 +size 184 diff --git a/FONT/test/pose/paint1.npy b/FONT/test/pose/paint1.npy new file mode 100644 index 0000000000000000000000000000000000000000..a5dad1d6172b01cd8d0cc65eb74c9f015f65515c --- /dev/null +++ b/FONT/test/pose/paint1.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6cf0313fb28b6dad84dc0cf98a0041fc2c1d952ed86a0292a06f67d49f607cc8 +size 184 diff --git a/FONT/test/pose/scarlett.npy b/FONT/test/pose/scarlett.npy new file mode 100644 index 0000000000000000000000000000000000000000..6a532936eb40faa3d7ddc2ea2393e4d4f1c7578a --- /dev/null +++ b/FONT/test/pose/scarlett.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:589f704414feb3bf68b18c2b2d7bf6a630f9e907d05da36604b4f9cec1bcf7bd +size 184 diff --git a/FONT/test/pose_long/0zn70Ak8lRc_Daniel_Auteuil_0zn70Ak8lRc_0002.npy b/FONT/test/pose_long/0zn70Ak8lRc_Daniel_Auteuil_0zn70Ak8lRc_0002.npy new file mode 100644 index 0000000000000000000000000000000000000000..e1bffa41687e42569bfdbea491962838979e01dc --- /dev/null +++ b/FONT/test/pose_long/0zn70Ak8lRc_Daniel_Auteuil_0zn70Ak8lRc_0002.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93d2da1051269412801118567967f57ea1a276ec4e4e7d43fd639d4df5974d28 +size 5336 diff --git a/FONT/test/pose_long/1hEr7qKRKL4_Daniel_Dae_Kim_1hEr7qKRKL4_0004.npy b/FONT/test/pose_long/1hEr7qKRKL4_Daniel_Dae_Kim_1hEr7qKRKL4_0004.npy new file mode 100644 index 0000000000000000000000000000000000000000..fd3461e629ca4d5e50a209b6bcfa36753e418fa9 --- /dev/null +++ b/FONT/test/pose_long/1hEr7qKRKL4_Daniel_Dae_Kim_1hEr7qKRKL4_0004.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:268ea44d10cf603d3c75691b123a867a5218eb4cb84abcfba035ca9ef0ef580e +size 9648 diff --git a/FONT/test/pose_long/50IAfJCypFI_Alex_Kingston_50IAfJCypFI_0001.npy b/FONT/test/pose_long/50IAfJCypFI_Alex_Kingston_50IAfJCypFI_0001.npy new file mode 100644 index 0000000000000000000000000000000000000000..fa2ec84f6cf2b16f97c7d82fda3f694210a41802 --- /dev/null +++ b/FONT/test/pose_long/50IAfJCypFI_Alex_Kingston_50IAfJCypFI_0001.npy @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f2eb32d1713206576fc04ae399f484bb64fb47c23c1cbb6cd05f08010ebbed4 +size 7016 diff --git a/FONT/train.py b/FONT/train.py new file mode 100644 index 0000000000000000000000000000000000000000..c9a2dfd5feae7b2a48c689df94797db7acbc3680 --- /dev/null +++ b/FONT/train.py @@ -0,0 +1,430 @@ +from tqdm import trange +import torch +import torch.nn as nn +from torch.utils.data import DataLoader + +from logger import Logger +from modules.model import DiscriminatorFullModel, TrainPart1Model, TrainPart2Model +import itertools + +from torch.optim.lr_scheduler import MultiStepLR + +from sync_batchnorm import DataParallelWithCallback + +from frames_dataset import DatasetRepeater,TestsetRepeater +import time +from tensorboardX import SummaryWriter + +def train_part1(config, generator, discriminator, kp_detector, kp_detector_a,audio_feature, checkpoint, audio_checkpoint, log_dir, dataset, test_dataset, device_ids, name): + train_params = config['train_params'] + + optimizer_audio_feature = torch.optim.Adam(itertools.chain(audio_feature.parameters(),kp_detector_a.parameters()), lr=train_params['lr_audio_feature'], betas=(0.5, 0.999)) + optimizer_generator = None + optimizer_discriminator = None + optimizer_kp_detector = None + + if checkpoint is not None: + start_epoch = Logger.load_cpk(checkpoint, generator, discriminator, kp_detector, audio_feature, + optimizer_generator, optimizer_discriminator, + None if train_params['lr_kp_detector'] == 0 else optimizer_kp_detector, + None if train_params['lr_audio_feature'] == 0 else optimizer_audio_feature) + + + # audio_feature load wav2lip + wav2lip_ckpt_path = "/data/liujin/Wav2Lip-master/checkpoints/wav2lip.pth" + checkpoint = torch.load(wav2lip_ckpt_path) + s = checkpoint["state_dict"] + new_s = {} + for k, v in s.items(): + new_s[k.replace('module.', '')] = v + audio_feature.load_state_dict(new_s, strict=False) + + + if audio_checkpoint is not None: + pretrain = torch.load(audio_checkpoint) + kp_detector_a.load_state_dict(pretrain['kp_detector_a']) + audio_feature.load_state_dict(pretrain['audio_feature']) + optimizer_audio_feature.load_state_dict(pretrain['optimizer_audio_feature']) + start_epoch = pretrain['epoch'] + + else: + start_epoch = 0 + + + scheduler_audio_feature = MultiStepLR(optimizer_audio_feature, train_params['epoch_milestones'], gamma=0.1, + last_epoch=-1 + start_epoch * (train_params['lr_audio_feature'] != 0)) + + if 'num_repeats' in train_params or train_params['num_repeats'] != 1: + dataset = DatasetRepeater(dataset, train_params['num_repeats']) + test_dataset = TestsetRepeater(test_dataset, train_params['num_repeats']) + dataloader = DataLoader(dataset, batch_size=train_params['batch_size'], shuffle=True, num_workers=0, drop_last=True)#6 + test_dataloader = DataLoader(test_dataset, batch_size=train_params['batch_size'], shuffle=True, num_workers=0, drop_last=True)#6 + num_steps_per_epoch = len(dataloader) + num_steps_test_epoch = len(test_dataloader) + generator_full = TrainPart1Model(kp_detector, kp_detector_a, audio_feature, generator, discriminator, train_params,device_ids) + discriminator_full = DiscriminatorFullModel(kp_detector, generator, discriminator, train_params) + + if len(device_ids)>1: + generator_full=torch.nn.DataParallel(generator_full) + discriminator_full=torch.nn.DataParallel(discriminator_full) + + if torch.cuda.is_available(): + if len(device_ids) == 1: + generator_full = DataParallelWithCallback(generator_full, device_ids=device_ids) + discriminator_full = DataParallelWithCallback(discriminator_full, device_ids=device_ids) + elif len(device_ids)>1: + generator_full = generator_full.to(device_ids[0]) + discriminator_full = discriminator_full.to(device_ids[0]) + + step = 0 + t0 = time.time() + + writer=SummaryWriter(comment=name) + train_itr=0 + test_itr=0 + with Logger(log_dir=log_dir, visualizer_params=config['visualizer_params'], checkpoint_freq=train_params['checkpoint_freq']) as logger: + for epoch in trange(start_epoch, train_params['num_epochs']): + + for x in dataloader: + + losses_generator, generated = generator_full(x) + + + loss_values = [val.mean() for val in losses_generator.values()] + loss = sum(loss_values) + + writer.add_scalar('Train',loss,train_itr) + + writer.add_scalar('Train_value',loss_values[0],train_itr) + writer.add_scalar('Train_heatmap',loss_values[1],train_itr) + writer.add_scalar('Train_jacobian',loss_values[2],train_itr) + + train_itr+=1 + loss.backward() + + + optimizer_audio_feature.step() + optimizer_audio_feature.zero_grad() + d = time.time() + + # if train_params['loss_weights']['generator_gan'] != 0: + # optimizer_discriminator.zero_grad() + # else: + # losses_discriminator = {} + + # losses_generator.update(losses_discriminator) + losses = {key: value.mean().detach().data.cpu().numpy() for key, value in losses_generator.items()} + logger.log_iter(losses=losses) + e = time.time() + + step += 1 + + if(step % 2500 == 0): + print('Save ckpt and training visualization!') + logger.log_epoch(epoch,step, {'audio_feature': audio_feature, + 'kp_detector_a':kp_detector_a, + 'optimizer_audio_feature': optimizer_audio_feature}, inp=x, out=generated) + + + scheduler_audio_feature.step() + + + for x in test_dataloader: + with torch.no_grad(): + losses_generator, generated = generator_full(x) + + loss_values = [val.mean() for val in losses_generator.values()] + loss = sum(loss_values) + + writer.add_scalar('Test',loss,test_itr) + + writer.add_scalar('Test_value',loss_values[0],test_itr) + writer.add_scalar('Test_heatmap',loss_values[1],test_itr) + writer.add_scalar('Test_jacobian',loss_values[2],test_itr) + + test_itr+=1 + + + +def train_part1_fine_tune(config, generator, discriminator, kp_detector, kp_detector_a,audio_feature, checkpoint, audio_checkpoint, log_dir, dataset, test_dataset, device_ids, name): + train_params = config['train_params'] + + optimizer_generator = torch.optim.Adam(generator.parameters(), lr=train_params['lr_generator'], betas=(0.5, 0.999)) + optimizer_discriminator = torch.optim.Adam(discriminator.parameters(), lr=train_params['lr_discriminator'], betas=(0.5, 0.999)) + optimizer_audio_feature = torch.optim.Adam(itertools.chain(audio_feature.parameters(),kp_detector_a.parameters()), lr=train_params['lr_audio_feature'], betas=(0.5, 0.999)) + # optimizer_kp_detector_a = torch.optim.Adam(kp_detector_a.parameters(), lr=train_params['lr_audio_feature'], betas=(0.5, 0.999)) + optimizer_kp_detector = None + + + if checkpoint is not None: + start_epoch = Logger.load_cpk(checkpoint, generator, discriminator, kp_detector, audio_feature, + optimizer_generator, optimizer_discriminator, + None if train_params['lr_kp_detector'] == 0 else optimizer_kp_detector, + None if train_params['lr_audio_feature'] == 0 else optimizer_audio_feature) + if audio_checkpoint is not None: + pretrain = torch.load(audio_checkpoint) + kp_detector_a.load_state_dict(pretrain['kp_detector_a']) + audio_feature.load_state_dict(pretrain['audio_feature']) + # optimizer_kp_detector_a.load_state_dict(pretrain['optimizer_kp_detector_a']) + optimizer_audio_feature.load_state_dict(pretrain['optimizer_audio_feature']) + start_epoch = pretrain['epoch'] + + + else: + start_epoch = 0 + + scheduler_generator = MultiStepLR(optimizer_generator, train_params['epoch_milestones'], gamma=0.1, + last_epoch=start_epoch - 1) + scheduler_discriminator = MultiStepLR(optimizer_discriminator, train_params['epoch_milestones'], gamma=0.1, + last_epoch=start_epoch - 1) + scheduler_audio_feature = MultiStepLR(optimizer_audio_feature, train_params['epoch_milestones'], gamma=0.1, + last_epoch=-1 + start_epoch * (train_params['lr_audio_feature'] != 0)) + + if 'num_repeats' in train_params or train_params['num_repeats'] != 1: + dataset = DatasetRepeater(dataset, train_params['num_repeats']) + test_dataset = TestsetRepeater(test_dataset, train_params['num_repeats']) + dataloader = DataLoader(dataset, batch_size=train_params['batch_size'], shuffle=True, num_workers=0, drop_last=True)#6 + test_dataloader = DataLoader(test_dataset, batch_size=train_params['batch_size'], shuffle=True, num_workers=0, drop_last=True)#6 + num_steps_per_epoch = len(dataloader) + num_steps_test_epoch = len(test_dataloader) + # generator_full = TrainFullModel(kp_detector, kp_detector_a, audio_feature, generator, discriminator, train_params,device_ids) + generator_full = TrainPart1Model(kp_detector, kp_detector_a, audio_feature, generator, discriminator, train_params, device_ids) + + + discriminator_full = DiscriminatorFullModel(kp_detector, generator, discriminator, train_params) + print('End dataload ', file=open('log/MEAD_LRW_test_a.txt', 'a')) + if len(device_ids)>1: + generator_full=torch.nn.DataParallel(generator_full) + discriminator_full=torch.nn.DataParallel(discriminator_full) + + if torch.cuda.is_available(): + if len(device_ids) == 1: + generator_full = DataParallelWithCallback(generator_full, device_ids=device_ids) + discriminator_full = DataParallelWithCallback(discriminator_full, device_ids=device_ids) + elif len(device_ids)>1: + generator_full = generator_full.to(device_ids[0]) + discriminator_full = discriminator_full.to(device_ids[0]) + + step = 0 + t0 = time.time() + + writer=SummaryWriter(comment=name) + train_itr=0 + test_itr=0 + with Logger(log_dir=log_dir, visualizer_params=config['visualizer_params'], checkpoint_freq=train_params['checkpoint_freq']) as logger: + for epoch in trange(start_epoch, train_params['num_epochs']): + + for x in dataloader: + + + losses_generator, generated = generator_full(x) + + loss_values = [val.mean() for val in losses_generator.values()] + loss = sum(loss_values) + + writer.add_scalar('Train',loss,train_itr) + + writer.add_scalar('Train_value',loss_values[0],train_itr) + writer.add_scalar('Train_heatmap',loss_values[1],train_itr) + writer.add_scalar('Train_jacobian',loss_values[2],train_itr) + writer.add_scalar('Train_perceptual',loss_values[3],train_itr) + + + train_itr+=1 + loss.backward() + + + + optimizer_audio_feature.step() + optimizer_audio_feature.zero_grad() + + optimizer_generator.step() + optimizer_generator.zero_grad() + # optimizer_kp_detector_a.step() + # optimizer_kp_detector_a.zero_grad() + + if train_params['loss_weights']['discriminator_gan'] != 0: + optimizer_discriminator.zero_grad() + # losses_discriminator = discriminator_full(x, generated) + # loss_values = [val.mean() for val in losses_discriminator.values()] + # loss = sum(loss_values) + + # loss.backward() + # optimizer_discriminator.step() + # optimizer_discriminator.zero_grad() + else: + losses_discriminator = {} + + losses_generator.update(losses_discriminator) + losses = {key: value.mean().detach().data.cpu().numpy() for key, value in losses_generator.items()} + logger.log_iter(losses=losses) + + step += 1 + + if(step % 5000 == 0): + + logger.log_epoch(epoch,step, {'audio_feature': audio_feature, + 'kp_detector_a':kp_detector_a, + 'generator': generator, + 'optimizer_generator':optimizer_generator, + 'optimizer_audio_feature': optimizer_audio_feature}, inp=x, out=generated) + + scheduler_generator.step() + scheduler_discriminator.step() + scheduler_audio_feature.step() + + + for x in test_dataloader: + with torch.no_grad(): + losses_generator, generated = generator_full(x) + + loss_values = [val.mean() for val in losses_generator.values()] + loss = sum(loss_values) + + writer.add_scalar('Test',loss,test_itr) + + writer.add_scalar('Test_value',loss_values[0],test_itr) + writer.add_scalar('Test_heatmap',loss_values[1],test_itr) + writer.add_scalar('Test_jacobian',loss_values[2],test_itr) + writer.add_scalar('Test_perceptual',loss_values[3],test_itr) + + test_itr+=1 + + +def train_part2(config, generator, discriminator, kp_detector, emo_detector, kp_detector_a,audio_feature, checkpoint, audio_checkpoint, emo_checkpoint, log_dir, dataset, test_dataset, device_ids, exp_name): + train_params = config['train_params'] + + optimizer_emo_detector = torch.optim.Adam(emo_detector.parameters(), lr=train_params['lr_audio_feature'], betas=(0.5, 0.999)) + + if checkpoint is not None: + start_epoch = Logger.load_cpk(checkpoint, generator, discriminator, kp_detector, audio_feature, + optimizer_generator, optimizer_discriminator, + None if train_params['lr_kp_detector'] == 0 else optimizer_kp_detector, + None if train_params['lr_audio_feature'] == 0 else optimizer_audio_feature) + if emo_checkpoint is not None: + pretrain = torch.load(emo_checkpoint) + tgt_state = emo_detector.state_dict() + strip = 'module.' + if 'emo_detector' in pretrain: + emo_detector.load_state_dict(pretrain['emo_detector']) + optimizer_emo_detector.load_state_dict(pretrain['optimizer_emo_detector']) + print('emo_detector in pretrain + load', file=open('log/'+exp_name+'.txt', 'a')) + for name, param in pretrain.items(): + if isinstance(param, nn.Parameter): + param = param.data + if strip is not None and name.startswith(strip): + name = name[len(strip):] + if name not in tgt_state: + continue + tgt_state[name].copy_(param) + print(name) + if audio_checkpoint is not None: + pretrain = torch.load(audio_checkpoint) + kp_detector_a.load_state_dict(pretrain['kp_detector_a']) + audio_feature.load_state_dict(pretrain['audio_feature']) + optimizer_audio_feature.load_state_dict(pretrain['optimizer_audio_feature']) + if 'emo_detector' in pretrain: + emo_detector.load_state_dict(pretrain['emo_detector']) + optimizer_emo_detector.load_state_dict(pretrain['optimizer_emo_detector']) + start_epoch = pretrain['epoch'] + + else: + start_epoch = 0 + + + scheduler_emo_detector = MultiStepLR(optimizer_emo_detector, train_params['epoch_milestones'], gamma=0.1, + last_epoch=-1 + start_epoch * (train_params['lr_audio_feature'] != 0)) + + if 'num_repeats' in train_params or train_params['num_repeats'] != 1: + dataset = DatasetRepeater(dataset, train_params['num_repeats']) + test_dataset = TestsetRepeater(test_dataset, train_params['num_repeats']) + dataloader = DataLoader(dataset, batch_size=train_params['batch_size'], shuffle=True, num_workers=0, drop_last=True)#6 + test_dataloader = DataLoader(test_dataset, batch_size=train_params['batch_size'], shuffle=True, num_workers=0, drop_last=True)#6 + num_steps_per_epoch = len(dataloader) + num_steps_test_epoch = len(test_dataloader) + generator_full = TrainPart2Model(kp_detector, emo_detector,kp_detector_a, audio_feature,generator, discriminator, train_params,device_ids) + discriminator_full = DiscriminatorFullModel(kp_detector, generator, discriminator, train_params) + + if len(device_ids)>1: + generator_full=torch.nn.DataParallel(generator_full) + discriminator_full=torch.nn.DataParallel(discriminator_full) + + if torch.cuda.is_available(): + if len(device_ids) == 1: + generator_full = DataParallelWithCallback(generator_full, device_ids=device_ids) + discriminator_full = DataParallelWithCallback(discriminator_full, device_ids=device_ids) + elif len(device_ids)>1: + generator_full = generator_full.to(device_ids[0]) + discriminator_full = discriminator_full.to(device_ids[0]) + + step = 0 + t0 = time.time() + + writer=SummaryWriter(comment=exp_name) + train_itr=0 + test_itr=0 + with Logger(log_dir=log_dir, visualizer_params=config['visualizer_params'], checkpoint_freq=train_params['checkpoint_freq']) as logger: + for epoch in trange(start_epoch, train_params['num_epochs']): + + for x in dataloader: + + losses_generator, generated = generator_full(x) + + loss_values = [val.mean() for val in losses_generator.values()] + loss = sum(loss_values) + + writer.add_scalar('Train',loss,train_itr) + + writer.add_scalar('Train_value',loss_values[0],train_itr) + # writer.add_scalar('Train_heatmap',loss_values[1],train_itr) + writer.add_scalar('Train_jacobian',loss_values[1],train_itr) + writer.add_scalar('Train_classify',loss_values[2],train_itr) + + + + train_itr+=1 + loss.backward() + + + optimizer_emo_detector.step() + optimizer_emo_detector.zero_grad() + + + losses = {key: value.mean().detach().data.cpu().numpy() for key, value in losses_generator.items()} + logger.log_iter(losses=losses) + + step += 1 + + if(step % 1000 == 0): + + logger.log_epoch(epoch,step, {'audio_feature': audio_feature, + 'kp_detector_a':kp_detector_a, + 'emo_detector':emo_detector, + 'optimizer_emo_detector': optimizer_emo_detector, + # 'optimizer_kp_detector_a':optimizer_kp_detector_a, + 'optimizer_audio_feature': optimizer_audio_feature}, inp=x, out=generated) + + scheduler_emo_detector.step() + + + for x in test_dataloader: + with torch.no_grad(): + losses_generator, generated = generator_full(x) + + loss_values = [val.mean() for val in losses_generator.values()] + loss = sum(loss_values) + + writer.add_scalar('Test',loss,test_itr) + + writer.add_scalar('Test_value',loss_values[0],test_itr) + # writer.add_scalar('Test_heatmap',loss_values[1],test_itr) + writer.add_scalar('Test_jacobian',loss_values[1],test_itr) + writer.add_scalar('Test_classify',loss_values[2],test_itr) + + + test_itr+=1 + + + + +