├── VideoFace3D ├── renderer │ ├── __init__.py │ ├── __pycache__ │ │ ├── render.cpython-37.pyc │ │ ├── __init__.cpython-37.pyc │ │ ├── lightning.cpython-37.pyc │ │ └── weak_projection.cpython-37.pyc │ ├── weak_projection.py │ ├── lightning.py │ └── render.py ├── SFS │ ├── SFSNet │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── mask.cpython-37.pyc │ │ │ ├── model.cpython-37.pyc │ │ │ ├── utils.cpython-37.pyc │ │ │ ├── __init__.cpython-37.pyc │ │ │ └── functions.cpython-37.pyc │ │ ├── utils.py │ │ ├── functions.py │ │ ├── model.py │ │ └── mask.py │ ├── __init__.py │ ├── __pycache__ │ │ ├── SfS.cpython-37.pyc │ │ └── __init__.cpython-37.pyc │ └── SfS.py ├── face_track │ ├── lib │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── utils.cpython-37.pyc │ │ │ ├── __init__.cpython-37.pyc │ │ │ └── face_utils.cpython-37.pyc │ │ ├── face_utils.py │ │ └── utils.py │ ├── src │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── sort.cpython-37.pyc │ │ │ ├── __init__.cpython-37.pyc │ │ │ ├── kalman_tracker.cpython-37.pyc │ │ │ └── data_association.cpython-37.pyc │ │ ├── data_association.py │ │ ├── sort.py │ │ └── kalman_tracker.py │ ├── align │ │ ├── __init__.py │ │ ├── det1.npy │ │ ├── det2.npy │ │ ├── det3.npy │ │ └── __pycache__ │ │ │ ├── __init__.cpython-37.pyc │ │ │ └── detect_face.cpython-37.pyc │ ├── __init__.py │ ├── __pycache__ │ │ ├── tracker.cpython-37.pyc │ │ └── __init__.cpython-37.pyc │ └── tracker.py ├── segmentation │ ├── __init__.py │ ├── __pycache__ │ │ ├── segment.cpython-37.pyc │ │ └── __init__.cpython-37.pyc │ ├── faceparsing │ │ ├── __pycache__ │ │ │ ├── model.cpython-37.pyc │ │ │ └── resnet.cpython-37.pyc │ │ ├── resnet.py │ │ └── model.py │ └── segment.py ├── landmark_detect │ ├── __init__.py │ ├── __pycache__ │ │ ├── ddfa_io.cpython-37.pyc │ │ ├── __init__.cpython-37.pyc │ │ ├── ddfa_ddfa.cpython-37.pyc │ │ ├── detector.cpython-37.pyc │ │ ├── ddfa_params.cpython-37.pyc │ │ ├── ddfa_inference.cpython-37.pyc │ │ ├── ddfa_landmarks.cpython-37.pyc │ │ ├── ddfa_mobilenet.cpython-37.pyc │ │ ├── dlib_landmark.cpython-37.pyc │ │ └── ddfa_estimate_pose.cpython-37.pyc │ ├── dlib_landmark.py │ ├── ddfa_params.py │ ├── ddfa_estimate_pose.py │ ├── detector.py │ ├── ddfa_io.py │ ├── ddfa_landmarks.py │ ├── ddfa_ddfa.py │ ├── ddfa_mobilenet.py │ └── ddfa_inference.py ├── models │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-37.pyc │ │ ├── fitting.cpython-37.pyc │ │ ├── face_model.cpython-37.pyc │ │ ├── ddfa_predict.cpython-37.pyc │ │ └── shape_predict.cpython-37.pyc │ ├── shape_predict.py │ ├── ddfa_predict.py │ └── face_model.py ├── utils │ ├── __init__.py │ ├── __pycache__ │ │ ├── Global.cpython-37.pyc │ │ ├── __init__.cpython-37.pyc │ │ ├── geometry.cpython-37.pyc │ │ ├── video_utils.cpython-37.pyc │ │ ├── visualization.cpython-37.pyc │ │ └── temporal_smooth.cpython-37.pyc │ ├── Global.py │ ├── video_utils.py │ ├── visualization.py │ ├── temporal_smooth.py │ └── geometry.py ├── __pycache__ │ └── __init__.cpython-37.pyc └── __init__.py ├── examples ├── example_pics │ ├── 1.png │ └── 2.png ├── example_results │ ├── sfs.png │ ├── 0_vis.png │ ├── 1_vis.png │ ├── sfs_1.png │ ├── sfs_2.png │ ├── 0_input.png │ ├── 0_mask.png │ ├── 1_input.png │ ├── 1_mask.png │ ├── fitting.png │ ├── landmark.png │ ├── lanmark_2D.png │ ├── lanmark_3D.png │ ├── 0_mask_prob.png │ ├── 1_mask_prob.png │ ├── segmentation.png │ ├── fitting_fast_0.png │ └── fitting_fast_1.png ├── example_landmarks_single_image.py ├── examples_segementation_single_image.py ├── example_sfs_single_image.py ├── example_fitting_single_image.py └── example_video.py ├── setup.py └── README.md /VideoFace3D/renderer/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VideoFace3D/SFS/SFSNet/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VideoFace3D/face_track/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VideoFace3D/face_track/src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VideoFace3D/face_track/align/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VideoFace3D/SFS/__init__.py: -------------------------------------------------------------------------------- 1 | from .SfS import * -------------------------------------------------------------------------------- /VideoFace3D/face_track/__init__.py: -------------------------------------------------------------------------------- 1 | from .tracker import * -------------------------------------------------------------------------------- /VideoFace3D/segmentation/__init__.py: -------------------------------------------------------------------------------- 1 | from .segment import * -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/__init__.py: -------------------------------------------------------------------------------- 1 | from .detector import * -------------------------------------------------------------------------------- /examples/example_pics/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_pics/1.png -------------------------------------------------------------------------------- /examples/example_pics/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_pics/2.png -------------------------------------------------------------------------------- /VideoFace3D/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .face_model import * 2 | from .fitting import * 3 | from .shape_predict import * -------------------------------------------------------------------------------- /examples/example_results/sfs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/sfs.png -------------------------------------------------------------------------------- /examples/example_results/0_vis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/0_vis.png -------------------------------------------------------------------------------- /examples/example_results/1_vis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/1_vis.png -------------------------------------------------------------------------------- /examples/example_results/sfs_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/sfs_1.png -------------------------------------------------------------------------------- /examples/example_results/sfs_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/sfs_2.png -------------------------------------------------------------------------------- /VideoFace3D/face_track/align/det1.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/face_track/align/det1.npy -------------------------------------------------------------------------------- /VideoFace3D/face_track/align/det2.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/face_track/align/det2.npy -------------------------------------------------------------------------------- /VideoFace3D/face_track/align/det3.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/face_track/align/det3.npy -------------------------------------------------------------------------------- /examples/example_results/0_input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/0_input.png -------------------------------------------------------------------------------- /examples/example_results/0_mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/0_mask.png -------------------------------------------------------------------------------- /examples/example_results/1_input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/1_input.png -------------------------------------------------------------------------------- /examples/example_results/1_mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/1_mask.png -------------------------------------------------------------------------------- /examples/example_results/fitting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/fitting.png -------------------------------------------------------------------------------- /examples/example_results/landmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/landmark.png -------------------------------------------------------------------------------- /examples/example_results/lanmark_2D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/lanmark_2D.png -------------------------------------------------------------------------------- /examples/example_results/lanmark_3D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/lanmark_3D.png -------------------------------------------------------------------------------- /VideoFace3D/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .Global import * 2 | from .visualization import * 3 | from .geometry import * 4 | from .video_utils import * -------------------------------------------------------------------------------- /examples/example_results/0_mask_prob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/0_mask_prob.png -------------------------------------------------------------------------------- /examples/example_results/1_mask_prob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/1_mask_prob.png -------------------------------------------------------------------------------- /examples/example_results/segmentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/segmentation.png -------------------------------------------------------------------------------- /examples/example_results/fitting_fast_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/fitting_fast_0.png -------------------------------------------------------------------------------- /examples/example_results/fitting_fast_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/examples/example_results/fitting_fast_1.png -------------------------------------------------------------------------------- /VideoFace3D/SFS/__pycache__/SfS.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/SFS/__pycache__/SfS.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/SFS/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/SFS/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/utils/__pycache__/Global.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/utils/__pycache__/Global.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/SFS/SFSNet/__pycache__/mask.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/SFS/SFSNet/__pycache__/mask.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/models/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/models/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/models/__pycache__/fitting.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/models/__pycache__/fitting.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/renderer/__pycache__/render.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/renderer/__pycache__/render.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/utils/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/utils/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/utils/__pycache__/geometry.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/utils/__pycache__/geometry.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/SFS/SFSNet/__pycache__/model.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/SFS/SFSNet/__pycache__/model.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/SFS/SFSNet/__pycache__/utils.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/SFS/SFSNet/__pycache__/utils.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/face_track/__pycache__/tracker.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/face_track/__pycache__/tracker.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/models/__pycache__/face_model.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/models/__pycache__/face_model.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/renderer/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/renderer/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/renderer/__pycache__/lightning.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/renderer/__pycache__/lightning.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/utils/__pycache__/video_utils.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/utils/__pycache__/video_utils.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/SFS/SFSNet/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/SFS/SFSNet/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/SFS/SFSNet/__pycache__/functions.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/SFS/SFSNet/__pycache__/functions.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/face_track/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/face_track/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/face_track/lib/__pycache__/utils.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/face_track/lib/__pycache__/utils.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/face_track/src/__pycache__/sort.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/face_track/src/__pycache__/sort.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/models/__pycache__/ddfa_predict.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/models/__pycache__/ddfa_predict.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/models/__pycache__/shape_predict.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/models/__pycache__/shape_predict.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/segmentation/__pycache__/segment.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/segmentation/__pycache__/segment.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/utils/__pycache__/visualization.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/utils/__pycache__/visualization.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/face_track/lib/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/face_track/lib/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/face_track/src/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/face_track/src/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/__pycache__/ddfa_io.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/landmark_detect/__pycache__/ddfa_io.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/segmentation/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/segmentation/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/utils/__pycache__/temporal_smooth.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/utils/__pycache__/temporal_smooth.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/face_track/align/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/face_track/align/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/face_track/lib/__pycache__/face_utils.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/face_track/lib/__pycache__/face_utils.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/landmark_detect/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/__pycache__/ddfa_ddfa.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/landmark_detect/__pycache__/ddfa_ddfa.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/__pycache__/detector.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/landmark_detect/__pycache__/detector.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/renderer/__pycache__/weak_projection.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/renderer/__pycache__/weak_projection.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/face_track/align/__pycache__/detect_face.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/face_track/align/__pycache__/detect_face.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/__pycache__/ddfa_params.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/landmark_detect/__pycache__/ddfa_params.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/face_track/src/__pycache__/kalman_tracker.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/face_track/src/__pycache__/kalman_tracker.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/__pycache__/ddfa_inference.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/landmark_detect/__pycache__/ddfa_inference.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/__pycache__/ddfa_landmarks.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/landmark_detect/__pycache__/ddfa_landmarks.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/__pycache__/ddfa_mobilenet.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/landmark_detect/__pycache__/ddfa_mobilenet.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/__pycache__/dlib_landmark.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/landmark_detect/__pycache__/dlib_landmark.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/segmentation/faceparsing/__pycache__/model.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/segmentation/faceparsing/__pycache__/model.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/face_track/src/__pycache__/data_association.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/face_track/src/__pycache__/data_association.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/segmentation/faceparsing/__pycache__/resnet.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/segmentation/faceparsing/__pycache__/resnet.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/__pycache__/ddfa_estimate_pose.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lstcutong/video-face-3d/HEAD/VideoFace3D/landmark_detect/__pycache__/ddfa_estimate_pose.cpython-37.pyc -------------------------------------------------------------------------------- /VideoFace3D/__init__.py: -------------------------------------------------------------------------------- 1 | from .face_track import * 2 | from .landmark_detect import * 3 | from .models import * 4 | from .renderer import * 5 | from .utils import * 6 | from .SFS import * 7 | from .segmentation import * -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/dlib_landmark.py: -------------------------------------------------------------------------------- 1 | import dlib 2 | import cv2 3 | import numpy as np 4 | 5 | def detect_landmark_dlib_2D(image_path, predictor): 6 | image = cv2.imread(image_path) 7 | detector = dlib.get_frontal_face_detector() 8 | gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 9 | 10 | landmarks = [] 11 | 12 | dets = detector(gray, 1) 13 | for face in dets: 14 | single_face = [] 15 | shape = predictor(image, face) 16 | for pt in shape.parts(): 17 | pt_pos = (pt.x, pt.y) 18 | 19 | single_face.append(np.array(pt_pos)) 20 | 21 | landmarks.append(np.array(single_face)) 22 | return landmarks -------------------------------------------------------------------------------- /examples/example_landmarks_single_image.py: -------------------------------------------------------------------------------- 1 | import VideoFace3D as vf3d 2 | import cv2 3 | import os 4 | def example_landmarks(): 5 | ld_2d = vf3d.FaceLandmarkDetector("2D") 6 | ld_3d = vf3d.FaceLandmarkDetector("3D") 7 | image_path = "./example_pics/1.png" 8 | 9 | l2d = ld_2d.detect_face_landmark(image_path) 10 | l3d = ld_3d.detect_face_landmark(image_path) 11 | 12 | im2d = vf3d.draw_landmarks(cv2.imread(image_path), l2d, colors=[vf3d.ComfortableColor().mint_d.to_bgr()]) 13 | im3d = vf3d.draw_landmarks(cv2.imread(image_path), l3d, colors=[vf3d.ComfortableColor().mint_d.to_bgr()]) 14 | 15 | cv2.imwrite("./example_results/lanmark_2D.png", im2d) 16 | cv2.imwrite("./example_results/lanmark_3D.png", im3d) 17 | 18 | if __name__ == '__main__': 19 | example_landmarks() -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | files =["data/*"] 4 | 5 | setup( 6 | description='utils for video face data preprocess, include video face tracking, landmark detecting, morphable face model fitting', 7 | author='Luo Shoutong', 8 | author_email='MF1933071@smail.nju.edu.cn', 9 | license='Magic', 10 | version='0.1.0', 11 | name='VideoFace3D', 12 | include_package_data = True, 13 | packages=find_packages(), 14 | ) 15 | 16 | ''' 17 | "./data/BFM_front_idx.mat", 18 | "./data/BFM_model_front.mat", 19 | "./data/FaceReconModel.pb", 20 | "./data/phase1_wpdc_vdc.pth.tar", 21 | "./data/shape_predictor_68_face_landmarks.dat", 22 | "./data/similarity_Lm3D_all.mat", 23 | "./data/tri.mat", 24 | "./data/keypoints_sim.npy", 25 | "./data/Model_PAF.pkl", 26 | "./data/param_whitening.pkl", 27 | "./data/pncc_code.npy", 28 | "./data/u_exp.npy", 29 | "./data/u_shp.npy", 30 | "./data/w_exp_sim.npy", 31 | "./data/w_shp_sim.npy" 32 | ''' -------------------------------------------------------------------------------- /VideoFace3D/SFS/SFSNet/utils.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | from __future__ import absolute_import, division, print_function 3 | import cv2 4 | import numpy as np 5 | 6 | 7 | def _convert(src, max_value): 8 | # find min and max 9 | _min = np.min(src) 10 | _max = np.max(src) 11 | # scale to (0, max_value) 12 | dst = (src - _min) / (_max - _min + 1e-10) 13 | dst *= max_value 14 | return dst 15 | 16 | 17 | def convert(src, dtype=np.uint8, max_value=255.0): 18 | # type: (np.ndarray, object, float) -> np.ndarray 19 | # copy src 20 | dst = src.copy() 21 | if src.ndim == 2: 22 | dst = _convert(dst, max_value) 23 | elif src.ndim == 3: 24 | dst = cv2.cvtColor(dst, cv2.COLOR_BGR2LAB) 25 | light_channel = _convert(dst[0], max_value) 26 | dst[0, ...] = light_channel 27 | dst = cv2.cvtColor(dst, cv2.COLOR_LAB2BGR)*255 28 | else: 29 | raise RuntimeError("src/utils.py(30): src.ndim should be 2 or 3") 30 | return dst.astype(dtype) 31 | -------------------------------------------------------------------------------- /VideoFace3D/face_track/lib/face_utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def judge_side_face(facial_landmarks): 5 | wide_dist = np.linalg.norm(facial_landmarks[0] - facial_landmarks[1]) 6 | high_dist = np.linalg.norm(facial_landmarks[0] - facial_landmarks[3]) 7 | dist_rate = high_dist / wide_dist 8 | 9 | # cal std 10 | vec_A = facial_landmarks[0] - facial_landmarks[2] 11 | vec_B = facial_landmarks[1] - facial_landmarks[2] 12 | vec_C = facial_landmarks[3] - facial_landmarks[2] 13 | vec_D = facial_landmarks[4] - facial_landmarks[2] 14 | dist_A = np.linalg.norm(vec_A) 15 | dist_B = np.linalg.norm(vec_B) 16 | dist_C = np.linalg.norm(vec_C) 17 | dist_D = np.linalg.norm(vec_D) 18 | 19 | # cal rate 20 | high_rate = dist_A / dist_C 21 | width_rate = dist_C / dist_D 22 | high_ratio_variance = np.fabs(high_rate - 1.1) # smaller is better 23 | width_ratio_variance = np.fabs(width_rate - 1) 24 | 25 | return dist_rate, high_ratio_variance, width_ratio_variance 26 | -------------------------------------------------------------------------------- /VideoFace3D/utils/Global.py: -------------------------------------------------------------------------------- 1 | SMOOTH_METHODS_OPTIMIZE = 1 2 | SMOOTH_METHODS_MEDUIM = 2 3 | SMOOTH_METHODS_MEAN = 3 4 | SMOOTH_METHODS_GAUSSIAN = 4 5 | SMOOTH_METHODS_DCNN = 5 6 | 7 | import os.path as osp 8 | import os 9 | 10 | def make_abs_path(d): 11 | return osp.join(osp.dirname(osp.realpath(__file__)), d) 12 | 13 | project_dir = os.path.dirname(os.path.abspath(__file__)) 14 | 15 | d = make_abs_path("../data") 16 | #d = "/home/magic/lst/codes/MorphableFaceFitting/MorphableModel" 17 | 18 | BFM_FRONT_MODEL_PATH = osp.join(d, "BFM_model_front.mat") 19 | SINGLE_IMAGE_RECON_MODEL_PATH = osp.join(d, "FaceReconModel.pb") 20 | SIMILARITY_LM3D_ALL_MODEL_PATH = osp.join(d, "similarity_Lm3D_all.mat") 21 | FRONT_FACE_INDEX_PATH = osp.join(d, "BFM_front_idx.mat") 22 | CHECKPOINT_FP_PATH = osp.join(d, "phase1_wpdc_vdc.pth.tar") 23 | DLIB_LANDMARK_MODEL_PATH = osp.join(d, "shape_predictor_68_face_landmarks.dat") 24 | TRI_PATH = osp.join(d, "tri.mat") 25 | SFSNET_PATH = osp.join(d, "SfSNet.pth") 26 | BISENET_MODEL_PATH = osp.join(d, "bisenet.pth") -------------------------------------------------------------------------------- /VideoFace3D/renderer/weak_projection.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | 3 | import torch 4 | 5 | 6 | def weak_projection(vertices, K, R, t, orig_size): 7 | ''' 8 | Calculate weak_projective transformation of vertices given a projection matrix 9 | Input parameters: 10 | K: batch_size * 3 * 3 scale matrix 11 | R, t: batch_size * 3 * 3, batch_size * 1 * 3 extrinsic calibration parameters 12 | 13 | Returns: For each point [X,Y,Z] in world coordinates [u,v,z] where u,v are the coordinates of the projection in 14 | pixels and z is the depth 15 | ''' 16 | 17 | # instead of P*x we compute x'*P' 18 | vertices = torch.matmul(vertices, R.transpose(2,1)) + t 19 | x, y, z = vertices[:, :, 0], vertices[:, :, 1], vertices[:, :, 2] 20 | 21 | vertices = torch.stack([x, y, torch.ones_like(z)], dim=-1) 22 | vertices = torch.matmul(vertices, K.transpose(1,2)) 23 | u, v = vertices[:, :, 0], vertices[:, :, 1] 24 | # map u,v from [0, img_size] to [-1, 1] to use by the renderer 25 | u = 2 * (u - orig_size / 2.) / orig_size 26 | v = 2 * (v - orig_size / 2.) / orig_size 27 | vertices = torch.stack([u, v, z], dim=-1) 28 | return vertices 29 | -------------------------------------------------------------------------------- /examples/examples_segementation_single_image.py: -------------------------------------------------------------------------------- 1 | import VideoFace3D as vf3d 2 | import cv2 3 | import shutil 4 | import os 5 | import numpy as np 6 | 7 | def example_segmentation(): 8 | image_path = ["./example_pics/1.png", 9 | "./example_pics/2.png"] 10 | 11 | segs = vf3d.FaceSegmentation() 12 | lmd = vf3d.FaceLandmarkDetector("3D") 13 | 14 | for id, imp in enumerate(image_path): 15 | lds = lmd.detect_face_landmark(imp) 16 | new_img, new_lds = vf3d.alignment_and_crop(imp, lds[0]) 17 | new_img = new_img[0] 18 | 19 | cv2.imwrite("./cache.png", new_img) 20 | 21 | org_parsing, mask, mask_prob = segs.create_face_mask("./cache.png") 22 | vis_parsing = segs.visualize(org_parsing, "./cache.png") 23 | mask = (mask * 255).astype(np.uint8) 24 | mask_prob = (mask_prob * 255).astype(np.uint8) 25 | inputs = cv2.imread("./cache.png") 26 | 27 | 28 | cv2.imwrite("./example_results/{}_mask.png".format(id), mask) 29 | cv2.imwrite("./example_results/{}_mask_prob.png".format(id), mask_prob) 30 | cv2.imwrite("./example_results/{}_vis.png".format(id), vis_parsing) 31 | cv2.imwrite("./example_results/{}_input.png".format(id), inputs) 32 | 33 | os.remove("./cache.png") 34 | 35 | if __name__ == '__main__': 36 | example_segmentation() -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/ddfa_params.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | import os.path as osp 5 | import numpy as np 6 | from .ddfa_io import _load 7 | 8 | 9 | def make_abs_path(d): 10 | return osp.join(osp.dirname(osp.realpath(__file__)), d) 11 | 12 | 13 | d = make_abs_path('../data') 14 | keypoints = _load(osp.join(d, 'keypoints_sim.npy')) 15 | w_shp = _load(osp.join(d, 'w_shp_sim.npy')) 16 | w_exp = _load(osp.join(d, 'w_exp_sim.npy')) # simplified version 17 | meta = _load(osp.join(d, 'param_whitening.pkl')) 18 | # param_mean and param_std are used for re-whitening 19 | param_mean = meta.get('param_mean') 20 | param_std = meta.get('param_std') 21 | u_shp = _load(osp.join(d, 'u_shp.npy')) 22 | u_exp = _load(osp.join(d, 'u_exp.npy')) 23 | u = u_shp + u_exp 24 | w = np.concatenate((w_shp, w_exp), axis=1) 25 | w_base = w[keypoints] 26 | w_norm = np.linalg.norm(w, axis=0) 27 | w_base_norm = np.linalg.norm(w_base, axis=0) 28 | 29 | # for inference 30 | dim = w_shp.shape[0] // 3 31 | u_base = u[keypoints].reshape(-1, 1) 32 | w_shp_base = w_shp[keypoints] 33 | w_exp_base = w_exp[keypoints] 34 | std_size = 120 35 | 36 | # for paf (pac) 37 | paf = _load(osp.join(d, 'Model_PAF.pkl')) 38 | u_filter = paf.get('mu_filter') 39 | w_filter = paf.get('w_filter') 40 | w_exp_filter = paf.get('w_exp_filter') 41 | 42 | # pncc code (mean shape) 43 | pncc_code = _load(osp.join(d, 'pncc_code.npy')) 44 | -------------------------------------------------------------------------------- /examples/example_sfs_single_image.py: -------------------------------------------------------------------------------- 1 | import VideoFace3D as vf3d 2 | import cv2 3 | import numpy as np 4 | 5 | def SfSTest(): 6 | fsp = vf3d.FaceSfSPipline() 7 | lmd = vf3d.FaceLandmarkDetector("3D") 8 | 9 | image_path = [ 10 | "./example_pics/1.png", 11 | "./example_pics/2.png" 12 | ] 13 | 14 | ncount = 0 15 | for im_path in image_path: 16 | ncount += 1 17 | lds = lmd.detect_face_landmark(im_path) 18 | new_img, new_lds = vf3d.alignment_and_crop(im_path, lds[0]) 19 | new_img = new_img[0] 20 | 21 | norm, albedo, light = fsp.disentangle(new_img) 22 | 23 | Irec, Ishd = vf3d.create_shading_recon(norm, albedo, light) 24 | 25 | from PIL import Image 26 | Irec = np.array(Image.fromarray((Irec.clip(0,1)*255).astype(np.uint8)).resize((224,224),Image.ANTIALIAS)) 27 | norm = np.array(Image.fromarray((norm.clip(0,1)*255).astype(np.uint8)).resize((224,224),Image.ANTIALIAS)) 28 | albedo = np.array(Image.fromarray((albedo.clip(0,1)*255).astype(np.uint8)).resize((224,224),Image.ANTIALIAS)) 29 | Ishd = np.array(Image.fromarray((Ishd.clip(0,1)*255).astype(np.uint8)).resize((224,224),Image.ANTIALIAS)) 30 | 31 | 32 | im = np.column_stack([new_img, albedo, norm, Ishd, Irec]) 33 | cv2.imwrite("./example_results/sfs_{}.png".format(ncount), im) 34 | 35 | if __name__ == '__main__': 36 | SfSTest() -------------------------------------------------------------------------------- /VideoFace3D/models/shape_predict.py: -------------------------------------------------------------------------------- 1 | from VideoFace3D.utils.Global import * 2 | import torch 3 | from VideoFace3D.landmark_detect import ddfa_mobilenet as mobilenet_v1 4 | import dlib 5 | from VideoFace3D.models.ddfa_predict import * 6 | import sys 7 | from scipy import io 8 | 9 | 10 | class FaceShapePredict(): 11 | def __init__(self, cuda=True): 12 | self.device = torch.device("cuda" if torch.cuda.is_available() and cuda else "cpu") 13 | self.__load_3D_static_data_file__() 14 | self.front_face_index = io.loadmat(FRONT_FACE_INDEX_PATH)["idx"][:, 0].astype(np.int) - 1 15 | 16 | def __load_3D_static_data_file__(self): 17 | checkpoint_fp = CHECKPOINT_FP_PATH 18 | 19 | arch = 'mobilenet_1' 20 | 21 | checkpoint = torch.load(checkpoint_fp, map_location=lambda storage, loc: storage)['state_dict'] 22 | self.model = getattr(mobilenet_v1, arch)(num_classes=62) # 62 = 12(pose) + 40(shape) +10(expression) 23 | 24 | model_dict = self.model.state_dict() 25 | # because the model is trained by multiple gpus, prefix module should be removed 26 | for k in checkpoint.keys(): 27 | model_dict[k.replace('module.', '')] = checkpoint[k] 28 | self.model.load_state_dict(model_dict) 29 | 30 | self.model.to(self.device) 31 | 32 | dlib_landmark_model = DLIB_LANDMARK_MODEL_PATH 33 | self.face_regressor = dlib.shape_predictor(dlib_landmark_model) 34 | 35 | def predict_shape(self, image_path, rects=None): 36 | vertices, color = detect_landmark_ddfa_3D_shape(image_path, self.model, self.face_regressor, self.device, 37 | rects=rects) 38 | 39 | return vertices[:, self.front_face_index, :], color[:, self.front_face_index, :] 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # video-face-3d 2 | 3 | 人脸工具包大杂烩,主要整合了近年来的SOTA的一些方法,包括视频人脸追踪,68关键点检测,三维人脸3DMM参数标注,shape-from-shading等 4 | 5 | 6 | 7 | ## 依赖 8 | 9 | - pytorch 0.4.1以上 10 | - tensorflow 1.0 以上 11 | - neural_renderer_pytorch 12 | 13 | 14 | 15 | ## 安装 16 | 17 | 安装本代码前请先确保安装了pytorch,tensorflow以及neural_renderer_pytorch 18 | 19 | - neural_renderer_pytorch安装说明 20 | 21 | - 如果你是Linux或者Mac用户,请直接`pip install neural_renderer_pytorch` 22 | 23 | - 如果你是Windows10用户,请参考[neural_renderer-win10](https://github.com/lstcutong/neural_renderer_pytorch-win10) 进行安装 24 | 25 | 下载本代码依赖的静态资源文件,解压到`/VideoFace3D/data`,下载链接[data.zip](https://pan.baidu.com/s/1-btsHsuPlFR4U_GO0Yxavg),密码 rle5 26 | 27 | 推荐使用`develop`模式安装代码 `python setup.py develop` 28 | 29 | 30 | 31 | ## Examples 32 | 33 | - 人脸关键点检测-单图像结果 34 | 35 | ``` 36 | cd examples 37 | python example_landmarks_single_image.py 38 | ``` 39 | 40 | 41 | 42 | - 3DMM参数化模型拟合-单图像结果 43 | 44 | ``` 45 | cd examples 46 | python example_fitting_single_image.py 47 | ``` 48 | 49 | 50 | 51 | - segmetation 单图像结果 52 | 53 | ``` 54 | cd examples 55 | python examples_segementation_single_image.py 56 | ``` 57 | 58 | 59 | 60 | - SFS- shape-from-shading 单图像结果 61 | 62 | ``` 63 | cd examples 64 | python example_sfs_single_image.py 65 | ``` 66 | 67 | 68 | 69 | ## TODO 70 | 71 | - example视频 72 | - api说明 73 | -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/ddfa_estimate_pose.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | """ 5 | Reference: https://github.com/YadiraF/PRNet/blob/master/utils/estimate_pose.py 6 | """ 7 | 8 | from math import cos, sin, atan2, asin, sqrt 9 | import numpy as np 10 | from VideoFace3D.landmark_detect.ddfa_params import param_mean, param_std 11 | 12 | 13 | def parse_pose(param): 14 | param = param * param_std + param_mean 15 | Ps = param[:12].reshape(3, -1) # camera matrix 16 | # R = P[:, :3] 17 | s, R, t3d = P2sRt(Ps) 18 | P = np.concatenate((R, t3d.reshape(3, -1)), axis=1) # without scale 19 | # P = Ps / s 20 | pose = matrix2angle(R) # yaw, pitch, roll 21 | # offset = p_[:, -1].reshape(3, 1) 22 | return P, pose 23 | 24 | def get_rotate_matrix(param): 25 | param = param * param_std + param_mean 26 | Ps = param[:12].reshape(3, -1) # camera matrix 27 | R = Ps[:, :3] 28 | 29 | return R 30 | 31 | def matrix2angle(R): 32 | ''' compute three Euler angles from a Rotation Matrix. Ref: http://www.gregslabaugh.net/publications/euler.pdf 33 | Args: 34 | R: (3,3). rotation matrix 35 | Returns: 36 | x: yaw 37 | y: pitch 38 | z: roll 39 | ''' 40 | # assert(isRotationMatrix(R)) 41 | 42 | if R[2, 0] != 1 and R[2, 0] != -1: 43 | x = asin(R[2, 0]) 44 | y = atan2(R[2, 1] / cos(x), R[2, 2] / cos(x)) 45 | z = atan2(R[1, 0] / cos(x), R[0, 0] / cos(x)) 46 | 47 | else: # Gimbal lock 48 | z = 0 # can be anything 49 | if R[2, 0] == -1: 50 | x = np.pi / 2 51 | y = z + atan2(R[0, 1], R[0, 2]) 52 | else: 53 | x = -np.pi / 2 54 | y = -z + atan2(-R[0, 1], -R[0, 2]) 55 | 56 | return x, y, z 57 | 58 | 59 | def P2sRt(P): 60 | ''' decompositing camera matrix P. 61 | Args: 62 | P: (3, 4). Affine Camera Matrix. 63 | Returns: 64 | s: scale factor. 65 | R: (3, 3). rotation matrix. 66 | t2d: (2,). 2d translation. 67 | ''' 68 | t3d = P[:, 3] 69 | R1 = P[0:1, :3] 70 | R2 = P[1:2, :3] 71 | s = (np.linalg.norm(R1) + np.linalg.norm(R2)) / 2.0 72 | r1 = R1 / np.linalg.norm(R1) 73 | r2 = R2 / np.linalg.norm(R2) 74 | r3 = np.cross(r1, r2) 75 | 76 | R = np.concatenate((r1, r2, r3), 0) 77 | return s, R, t3d 78 | 79 | 80 | def main(): 81 | pass 82 | 83 | 84 | if __name__ == '__main__': 85 | main() 86 | -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/detector.py: -------------------------------------------------------------------------------- 1 | from VideoFace3D.utils.Global import * 2 | import torch 3 | from VideoFace3D.landmark_detect import ddfa_mobilenet as mobilenet_v1 4 | import dlib 5 | from VideoFace3D.landmark_detect.ddfa_landmarks import detect_landmark_ddfa_3D 6 | from VideoFace3D.landmark_detect.dlib_landmark import detect_landmark_dlib_2D 7 | import sys 8 | 9 | 10 | class LandmarkDetector(): 11 | def __init__(self, cuda=True): 12 | self.device = torch.device("cuda" if torch.cuda.is_available() and cuda else "cpu") 13 | 14 | 15 | class FaceLandmarkDetector(LandmarkDetector): 16 | def __init__(self, mode="2D", cuda=True): 17 | super(FaceLandmarkDetector, self).__init__(cuda) 18 | self.mode = mode 19 | 20 | if self.mode == "2D": 21 | self.__load_2D_static_data_file__() 22 | elif self.mode == "3D": 23 | self.__load_3D_static_data_file__() 24 | else: 25 | raise Exception("Please choose between '2D' or '3D'") 26 | 27 | def __load_2D_static_data_file__(self): 28 | dlib_landmark_model = DLIB_LANDMARK_MODEL_PATH 29 | self.face_regressor = dlib.shape_predictor(dlib_landmark_model) 30 | 31 | def __load_3D_static_data_file__(self): 32 | checkpoint_fp = CHECKPOINT_FP_PATH 33 | 34 | arch = 'mobilenet_1' 35 | 36 | checkpoint = torch.load(checkpoint_fp, map_location=lambda storage, loc: storage)['state_dict'] 37 | self.model = getattr(mobilenet_v1, arch)(num_classes=62) # 62 = 12(pose) + 40(shape) +10(expression) 38 | 39 | model_dict = self.model.state_dict() 40 | # because the model is trained by multiple gpus, prefix module should be removed 41 | for k in checkpoint.keys(): 42 | model_dict[k.replace('module.', '')] = checkpoint[k] 43 | self.model.load_state_dict(model_dict) 44 | 45 | self.model.to(self.device) 46 | 47 | dlib_landmark_model = DLIB_LANDMARK_MODEL_PATH 48 | self.face_regressor = dlib.shape_predictor(dlib_landmark_model) 49 | 50 | def detect_face_landmark(self, image_path, rects=None): 51 | if self.mode == "3D": 52 | landmarks = detect_landmark_ddfa_3D(image_path, self.model, self.face_regressor, self.device, rects=rects) 53 | else: 54 | landmarks = detect_landmark_dlib_2D(image_path, self.face_regressor) 55 | 56 | if len(landmarks) == 0: 57 | return [] 58 | else: 59 | return landmarks 60 | -------------------------------------------------------------------------------- /VideoFace3D/face_track/lib/utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import time 4 | import uuid 5 | from operator import itemgetter 6 | 7 | import cv2 8 | from VideoFace3D.utils.Global import * 9 | 10 | log_file_root_path = os.path.join(project_dir, 'logs') 11 | log_time = time.strftime('%Y_%m_%d_%H_%M', time.localtime(time.time())) 12 | 13 | 14 | def mkdir(path): 15 | path.strip() 16 | path.rstrip('\\') 17 | isExists = os.path.exists(path) 18 | if not isExists: 19 | os.makedirs(path) 20 | 21 | 22 | def save_to_file(root_dic, tracker): 23 | filter_face_addtional_attribute_list = [] 24 | for item in tracker.face_addtional_attribute: 25 | if item[2] < 1.4 and item[4] < 1: # recommended thresold value 26 | filter_face_addtional_attribute_list.append(item) 27 | if len(filter_face_addtional_attribute_list) > 0: 28 | score_reverse_sorted_list = sorted(filter_face_addtional_attribute_list, key=itemgetter(4)) 29 | mkdir(root_dic) 30 | cv2.imwrite("{0}/{1}.jpg".format(root_dic, str(uuid.uuid1())), score_reverse_sorted_list[0][0]) 31 | 32 | 33 | class Logger: 34 | 35 | def __init__(self, module_name="MOT"): 36 | super().__init__() 37 | path_join = os.path.join(log_file_root_path, module_name) 38 | mkdir(path_join) 39 | 40 | self.logger = logging.getLogger(module_name) 41 | self.logger.setLevel(logging.INFO) 42 | log_file = os.path.join(path_join, '{}.log'.format(log_time)) 43 | if not self.logger.handlers: 44 | fh = logging.FileHandler(log_file, encoding='utf-8') 45 | fh.setLevel(logging.INFO) 46 | 47 | ch = logging.StreamHandler() 48 | ch.setLevel(logging.INFO) 49 | formatter = logging.Formatter( 50 | "%(asctime)s - %(name)s - %(levelname)s - %(message)s - %(threadName)s - %(process)d ") 51 | ch.setFormatter(formatter) 52 | fh.setFormatter(formatter) 53 | self.logger.addHandler(ch) 54 | self.logger.addHandler(fh) 55 | 56 | def error(self, msg, *args, **kwargs): 57 | if self.logger is not None: 58 | self.logger.error(msg, *args, **kwargs) 59 | 60 | def info(self, msg, *args, **kwargs): 61 | if self.logger is not None: 62 | self.logger.info(msg, *args, **kwargs) 63 | 64 | def warn(self, msg, *args, **kwargs): 65 | if self.logger is not None: 66 | self.logger.warning(msg, *args, **kwargs) 67 | 68 | def warning(self, msg, *args, **kwargs): 69 | if self.logger is not None: 70 | self.logger.warning(msg, *args, **kwargs) 71 | 72 | def exception(self, msg, *args, exc_info=True, **kwargs): 73 | if self.logger is not None: 74 | self.logger.exception(msg, *args, exc_info=True, **kwargs) 75 | -------------------------------------------------------------------------------- /VideoFace3D/face_track/src/data_association.py: -------------------------------------------------------------------------------- 1 | """ 2 | As implemented in https://github.com/abewley/sort but with some modifications 3 | 4 | For each detected item, it computes the intersection over union (IOU) w.r.t. each tracked object. (IOU matrix) 5 | Then, it applies the Hungarian algorithm (via linear_assignment) to assign each det. item to the best possible 6 | tracked item (i.e. to the one with max. IOU). 7 | 8 | Note: a more recent approach uses a Deep Association Metric instead. 9 | see https://github.com/nwojke/deep_sort 10 | """ 11 | 12 | import numpy as np 13 | from numba import jit 14 | from sklearn.utils.linear_assignment_ import linear_assignment 15 | 16 | 17 | @jit 18 | def iou(bb_test, bb_gt): 19 | """ 20 | Computes IUO between two bboxes in the form [x1,y1,x2,y2] 21 | """ 22 | xx1 = np.maximum(bb_test[0], bb_gt[0]) 23 | yy1 = np.maximum(bb_test[1], bb_gt[1]) 24 | xx2 = np.minimum(bb_test[2], bb_gt[2]) 25 | yy2 = np.minimum(bb_test[3], bb_gt[3]) 26 | w = np.maximum(0., xx2 - xx1) 27 | h = np.maximum(0., yy2 - yy1) 28 | wh = w * h 29 | o = wh / ((bb_test[2] - bb_test[0]) * (bb_test[3] - bb_test[1]) 30 | + (bb_gt[2] - bb_gt[0]) * (bb_gt[3] - bb_gt[1]) - wh) 31 | return (o) 32 | 33 | 34 | def associate_detections_to_trackers(detections, trackers, iou_threshold=0.25): 35 | """ 36 | Assigns detections to tracked object (both represented as bounding boxes) 37 | 38 | Returns 3 lists of matches, unmatched_detections and unmatched_trackers 39 | """ 40 | if len(trackers) == 0: 41 | return np.empty((0, 2), dtype=int), np.arange(len(detections)), np.empty((0, 5), dtype=int) 42 | iou_matrix = np.zeros((len(detections), len(trackers)), dtype=np.float32) 43 | 44 | for d, det in enumerate(detections): 45 | for t, trk in enumerate(trackers): 46 | iou_matrix[d, t] = iou(det, trk) 47 | '''The linear assignment module tries to minimise the total assignment cost. 48 | In our case we pass -iou_matrix as we want to maximise the total IOU between track predictions and the frame detection.''' 49 | matched_indices = linear_assignment(-iou_matrix) 50 | 51 | unmatched_detections = [] 52 | for d, det in enumerate(detections): 53 | if d not in matched_indices[:, 0]: 54 | unmatched_detections.append(d) 55 | unmatched_trackers = [] 56 | for t, trk in enumerate(trackers): 57 | if t not in matched_indices[:, 1]: 58 | unmatched_trackers.append(t) 59 | 60 | # filter out matched with low IOU 61 | matches = [] 62 | for m in matched_indices: 63 | if iou_matrix[m[0], m[1]] < iou_threshold: 64 | unmatched_detections.append(m[0]) 65 | unmatched_trackers.append(m[1]) 66 | else: 67 | matches.append(m.reshape(1, 2)) 68 | if len(matches) == 0: 69 | matches = np.empty((0, 2), dtype=int) 70 | else: 71 | matches = np.concatenate(matches, axis=0) 72 | 73 | return matches, np.array(unmatched_detections), np.array(unmatched_trackers) 74 | -------------------------------------------------------------------------------- /examples/example_fitting_single_image.py: -------------------------------------------------------------------------------- 1 | import time 2 | import torch 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | import VideoFace3D as vf3d 6 | 7 | def render_single_batch_image(annot, image_size): 8 | device = torch.device("cuda") 9 | 10 | facemodel = vf3d.FaceModelBFM() 11 | 12 | idi, ex, tex, r, t, s, gamma = annot["id"], annot["exp"], annot["tex"], annot["r"], annot["t"], annot["s"], annot[ 13 | "gamma"] 14 | 15 | s = torch.from_numpy(s).unsqueeze(2).to(device).float() 16 | t = torch.from_numpy(t).unsqueeze(1).to(device).float() 17 | r = torch.from_numpy(r).to(device).float() 18 | gamma = torch.from_numpy(gamma).to(device).float() 19 | 20 | batch = len(s) 21 | R = vf3d.euler2rot(r) 22 | K = torch.Tensor.repeat(torch.eye(3).unsqueeze(0), (batch, 1, 1)).to(device) * s 23 | 24 | shape = facemodel.shape_formation(torch.from_numpy(idi).to(device), torch.from_numpy(ex).to(device)) 25 | texture = facemodel.texture_formation(torch.from_numpy(tex).to(device)) 26 | 27 | triangles = torch.Tensor.repeat((torch.from_numpy(facemodel.tri) - 1).long().unsqueeze(0), (batch, 1, 1)).to(device) 28 | 29 | 30 | renderer = vf3d.Renderer(image_size=image_size, K=K, R=R, t=t, near=0.1, far=10000, light_mode="SH", SH_Coeff=gamma) 31 | rgb, depth, silh = renderer(shape, triangles, texture) 32 | rgb = rgb.detach().cpu().numpy().transpose((0, 2, 3, 1)) * 255 33 | rgb = np.clip(rgb, 0, 255).astype(np.uint8) 34 | return rgb[:, :, :, ::-1] 35 | 36 | 37 | def render_and_save_result(image, annot, save_path): 38 | render_im = render_single_batch_image(annot, 224) 39 | all_im = [] 40 | for i in range(len(image)): 41 | im = image[i] 42 | re_im = render_im[i] 43 | all_im.append(np.column_stack([im, re_im])) 44 | 45 | all_im = np.row_stack(all_im) 46 | plt.clf() 47 | fig = plt.figure(figsize=(10, 10)) 48 | plt.imshow(all_im[:, :, ::-1]) 49 | #plt.show() 50 | plt.savefig(save_path) 51 | 52 | 53 | def ModelFitting(): 54 | image_path = [ 55 | "./example_pics/1.png", 56 | "./example_pics/2.png" 57 | ] 58 | 59 | #accurate_fitting = FaceFittingPipline(image_path, "accurate", show_mid=False) 60 | fast_fitting = vf3d.FaceFittingPipline("fast", show_mid=False, checking=False) 61 | landmark_detect = vf3d.FaceLandmarkDetector("3D") 62 | #t0 = time.time() 63 | #result_accu = accurate_fitting.start_fiiting() 64 | #t1 = time.time() 65 | 66 | t2 = time.time() 67 | result_fast = fast_fitting.start_fiiting(image_path,landmark_detect) 68 | t3 = time.time() 69 | for i in range(len(result_fast)): 70 | render_and_save_result(result_fast[i][0], result_fast[i][2], "./example_results/fitting_fast_{}.png".format(i)) 71 | #for i in range(len(result_accu)): 72 | # render_and_save_result(result_accu[i][0],result_accu[i][2],"./accu_{}.png".format(i)) 73 | #print("accurate fitting using time:{}".format(t1-t0)) 74 | print("fast fitting using time:{}".format(t3 - t2)) 75 | 76 | if __name__ == '__main__': 77 | ModelFitting() 78 | -------------------------------------------------------------------------------- /VideoFace3D/SFS/SfS.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | import numpy as np 4 | import cv2 5 | import torch 6 | 7 | from VideoFace3D.SFS.SFSNet.functions import create_shading_recon 8 | from VideoFace3D.SFS.SFSNet.mask import MaskGenerator 9 | from VideoFace3D.SFS.SFSNet.model import SfSNet 10 | from VideoFace3D.SFS.SFSNet.utils import convert 11 | from VideoFace3D.utils.Global import SFSNET_PATH 12 | from VideoFace3D.utils.geometry import * 13 | from PIL import Image 14 | 15 | class SfSPipline(): 16 | def __init__(self, cuda=True): 17 | self.device = torch.device("cuda") if cuda and torch.cuda.is_available() else torch.device("cpu") 18 | 19 | 20 | class FaceSfSPipline(SfSPipline): 21 | def __init__(self, cuda=True): 22 | super(FaceSfSPipline, self).__init__(cuda) 23 | 24 | self.net = SfSNet() 25 | self.net.eval() 26 | self.net.load_state_dict(torch.load(SFSNET_PATH)) 27 | self.net.to(self.device) 28 | 29 | self.M = 128 30 | 31 | def disentangle(self, image, ldmark=None): 32 | ''' 33 | 34 | :param image: [h,w,3] bgr 35 | :param ldmark: [68,2] 36 | :return: norm[224,224,3], albedo[224,224,3], light[27] 37 | ''' 38 | 39 | if ldmark is not None: 40 | cv2.imwrite("./cache.png", image) 41 | im, kp = alignment_and_crop("./cache.png", ldmark) 42 | 43 | os.remove("./cache.png") 44 | else: 45 | im = np.array([image]) 46 | 47 | im = Image.fromarray(im[0]).resize((self.M, self.M), Image.ANTIALIAS) 48 | #im = cv2.resize(im[0], (self.M, self.M)) 49 | 50 | im = np.float32(im) / 255.0 51 | # from (128, 128, 3) to (1, 3, 128, 128) 52 | im = np.transpose(im, [2, 0, 1]) 53 | im = np.expand_dims(im, 0) 54 | 55 | # get the normal, albedo and light parameter 56 | normal, albedo, light = self.net(torch.from_numpy(im).to(self.device)) 57 | 58 | n_out = normal.cpu().detach().numpy() 59 | al_out = albedo.cpu().detach().numpy() 60 | light_out = light.cpu().detach().numpy() 61 | 62 | n_out = np.squeeze(n_out, 0) 63 | n_out = np.transpose(n_out, [1, 2, 0]) 64 | # from [1, 3, 128, 128] to [128, 128, 3] 65 | al_out = np.squeeze(al_out, 0) 66 | al_out = np.transpose(al_out, [1, 2, 0]) 67 | # from [1, 27] to [27, 1] 68 | light_out = np.transpose(light_out, [1, 0]) 69 | 70 | n_out2 = n_out[:, :, (2, 1, 0)] 71 | n_out2 = 2 * n_out2 - 1 72 | nr = np.sqrt(np.sum(n_out2 ** 2, axis=2)) # nr=sqrt(sum(n_out2.^2,3)) 73 | nr = np.expand_dims(nr, axis=2) 74 | n_out2 = n_out2 / np.repeat(nr, 3, axis=2) 75 | 76 | al_out2 = al_out[:, :, (2, 1, 0)] 77 | # Note: n_out2, al_out2, light_out is the actual output 78 | 79 | al_out2 = cv2.cvtColor(al_out2, cv2.COLOR_RGB2BGR) 80 | n_out2 = cv2.cvtColor(n_out2, cv2.COLOR_RGB2BGR) 81 | 82 | #al_out2 = cv2.resize(al_out2, (224,224)) 83 | #n_out2 = cv2.resize(n_out2, (224,224)) 84 | 85 | return n_out2, al_out2, light_out -------------------------------------------------------------------------------- /examples/example_video.py: -------------------------------------------------------------------------------- 1 | import VideoFace3D as vf3d 2 | import cv2 3 | import torch 4 | import numpy as np 5 | import os 6 | 7 | segmenter = vf3d.FaceSegmentation() 8 | land_2d = vf3d.FaceLandmarkDetector("2D") 9 | land_3d = vf3d.FaceLandmarkDetector("3D") 10 | 11 | comfortable_colors = vf3d.ComfortableColor() 12 | colors = [ 13 | comfortable_colors.sun_flower.to_bgr(), 14 | comfortable_colors.blue_jeans.to_bgr(), 15 | comfortable_colors.lavander.to_bgr(), 16 | comfortable_colors.bitter_sweet.to_bgr(), 17 | comfortable_colors.aqua.to_bgr()] 18 | 19 | 20 | def get_video_fps(video_path): 21 | cap = cv2.VideoCapture(video_path) 22 | fps = cap.get(cv2.CAP_PROP_FPS) 23 | cap.release() 24 | return fps 25 | 26 | 27 | def do_results_tracking(video_path): 28 | global colors 29 | tracker = vf3d.FaceTracker(echo=True) 30 | print("start tracking ...") 31 | tracking_info = tracker.start_track(video_path) 32 | print("tracking done...") 33 | single_face_bbox = [] 34 | all_frames = [] 35 | for frame, people in tracking_info: 36 | single_face_bbox.append(people[0][0]) 37 | all_frames.append(frame) 38 | 39 | single_face_bbox = np.array(single_face_bbox) # [seq_len, 4] 40 | single_face_bbox = vf3d.video_temporal_smooth_constrains(single_face_bbox.T).T 41 | single_face_bbox = list(single_face_bbox.astype(np.int)) 42 | 43 | all_frame_bboxs = [] 44 | for i, bbox in enumerate(single_face_bbox): 45 | frame_bbox = vf3d.draw_bbox(all_frames[i], [bbox], colors=colors) 46 | all_frame_bboxs.append(frame_bbox) 47 | vf3d.progressbar(i + 1, len(single_face_bbox), prefix="plot tracking...") 48 | 49 | return all_frames, all_frame_bboxs, single_face_bbox 50 | 51 | 52 | def do_results_landmark_detection(all_frames, bboxs, landmark_detector): 53 | # land_3d = vf3d.FaceLandmarkDetector("3D") 54 | cache_image_path = "./example_results/cache.png" 55 | 56 | ldmarks = [] 57 | for i, frame in enumerate(all_frames): 58 | cv2.imwrite(cache_image_path, frame) 59 | lds = landmark_detector.detect_face_landmark(cache_image_path, [bboxs[i]])[0] 60 | ldmarks.append(lds) 61 | vf3d.progressbar(i + 1, len(all_frames), prefix="detect landmarks...") 62 | 63 | os.remove(cache_image_path) 64 | 65 | ldmarks = np.array(ldmarks) # [seq, num_point, 2] 66 | seq, num_point, _ = ldmarks.shape 67 | ldmarks = ldmarks.reshape((seq, -1)) 68 | ldmarks = vf3d.video_temporal_smooth_constrains(ldmarks.T).T 69 | ldmarks = ldmarks.reshape((seq, num_point, -1)) 70 | 71 | all_frame_ldmarks = [] 72 | for i in range(len(ldmarks)): 73 | frame_ldmarks = vf3d.draw_landmarks(all_frames[i], [ldmarks[i]], colors=colors) 74 | all_frame_ldmarks.append(frame_ldmarks) 75 | vf3d.progressbar(i + 1, len(all_frames), prefix="plot landmarks...") 76 | return all_frame_ldmarks, ldmarks 77 | 78 | 79 | if __name__ == '__main__': 80 | video_path = r"E:\datasets\300VW_Dataset_2015_12_14\521\vid.avi" 81 | fps = get_video_fps(video_path) 82 | 83 | all_frames, all_frame_bboxs, all_bboxs = do_results_tracking(video_path) 84 | all_frame_ldmarks, all_ldmarks = do_results_landmark_detection(all_frames, all_bboxs, land_3d) 85 | vf3d.frames2video("./example_results/cache1.mp4", all_frame_ldmarks, fps=fps) 86 | -------------------------------------------------------------------------------- /VideoFace3D/models/ddfa_predict.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torchvision.transforms as transforms 3 | 4 | import numpy as np 5 | import cv2 6 | import dlib 7 | from VideoFace3D.landmark_detect.ddfa_ddfa import ToTensorGjz, NormalizeGjz, str2bool 8 | 9 | from VideoFace3D.landmark_detect.ddfa_inference import get_suffix, parse_roi_box_from_landmark, crop_img, \ 10 | predict_68pts, dump_to_ply, dump_vertex, \ 11 | draw_landmarks, predict_dense, parse_roi_box_from_bbox, get_colors, write_obj_with_colors 12 | from VideoFace3D.landmark_detect.ddfa_estimate_pose import parse_pose, get_rotate_matrix 13 | 14 | STD_SIZE = 120 15 | 16 | 17 | def detect_landmark_ddfa_3D_shape(image_path, model, face_regressor, device, bbox_init="one", rects=None): 18 | model.eval() 19 | 20 | transform = transforms.Compose([ToTensorGjz(), NormalizeGjz(mean=127.5, std=128)]) 21 | 22 | img_ori = cv2.imread(image_path) 23 | dlib_landmarks = True if rects is None else False 24 | if rects is None: 25 | face_detector = dlib.get_frontal_face_detector() 26 | gray = cv2.cvtColor(img_ori, cv2.COLOR_BGR2GRAY) 27 | rects = face_detector(gray, 1) 28 | 29 | if len(rects) == 0: 30 | return [] 31 | pts_res = [] 32 | Ps = [] # Camera matrix collection 33 | poses = [] # pose collection, [todo: validate it] 34 | vertices_lst = [] # store multiple face vertices 35 | ind = 0 36 | suffix = get_suffix(image_path) 37 | all_vertices = [] 38 | all_colors = [] 39 | for rect in rects: 40 | 41 | if dlib_landmarks: 42 | pts = face_regressor(img_ori, rect).parts() 43 | pts = np.array([[pt.x, pt.y] for pt in pts]).T 44 | roi_box = parse_roi_box_from_landmark(pts) 45 | else: 46 | roi_box = rect 47 | img = crop_img(img_ori, roi_box) 48 | 49 | # forward: one step 50 | img = cv2.resize(img, dsize=(STD_SIZE, STD_SIZE), interpolation=cv2.INTER_LINEAR) 51 | input = transform(img).unsqueeze(0).to(device) 52 | with torch.no_grad(): 53 | param = model(input) 54 | param = param.squeeze().cpu().numpy().flatten().astype(np.float32) 55 | # 68 pts 56 | pts68 = predict_68pts(param, roi_box) 57 | 58 | # two-step for more accurate bbox to crop face 59 | if bbox_init == 'two': 60 | roi_box = parse_roi_box_from_landmark(pts68) 61 | img_step2 = crop_img(img_ori, roi_box) 62 | img_step2 = cv2.resize(img_step2, dsize=(STD_SIZE, STD_SIZE), interpolation=cv2.INTER_LINEAR) 63 | input = transform(img_step2).unsqueeze(0).to(device) 64 | with torch.no_grad(): 65 | param = model(input) 66 | param = param.squeeze().cpu().numpy().flatten().astype(np.float32) 67 | 68 | pts68 = predict_68pts(param, roi_box) 69 | 70 | pts_res.append(pts68.transpose(1, 0)[:, 0:2]) 71 | P, pose = parse_pose(param) 72 | 73 | Ps.append(P) 74 | poses.append(pose) 75 | 76 | vertices = predict_dense(param, roi_box) 77 | colors = get_colors(img_ori, vertices) 78 | 79 | vertices = predict_dense(param, roi_box, rotate=False) 80 | #R = get_rotate_matrix(param) 81 | 82 | #vertices = np.linalg.inv(R) @ vertices 83 | 84 | all_vertices.append(vertices.transpose((1, 0))) 85 | all_colors.append(colors[:, ::-1]) 86 | return np.array(all_vertices), np.array(all_colors) 87 | -------------------------------------------------------------------------------- /VideoFace3D/models/face_model.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | from scipy.io import loadmat 4 | from VideoFace3D.utils.Global import * 5 | 6 | class FaceModelBFM(): 7 | def __init__(self): 8 | self.model_path = BFM_FRONT_MODEL_PATH 9 | model = loadmat(self.model_path) 10 | self.meanshape = model['meanshape'] # mean face shape 11 | self.idBase = model['idBase'] # identity basis 12 | self.exBase = model['exBase'] # expression basis 13 | self.meantex = model['meantex'] # mean face texture 14 | self.texBase = model['texBase'] # texture basis 15 | # point_buf can be used for calculating each vertex's norm once face's norm is computed 16 | self.point_buf = model[ 17 | 'point_buf'] # adjacent face index for each vertex, starts from 1 (only used for calculating face normal) 18 | # tri can be used for calculating each face's norm 19 | self.tri = model['tri'][:,::-1].copy() # vertex index for each triangle face, starts from 1 20 | self.keypoints = np.squeeze(model['keypoints']).astype(np.int32) - 1 # 68 face landmark index, starts from 0 21 | self.keypoints_org = self.keypoints.copy() 22 | self.transform_keypoints_index() 23 | 24 | def shape_formation(self, id_param, ex_param): 25 | ''' 26 | 27 | :param id_param: [batch, dim] 28 | :param ex_param: [batch, dim] 29 | :return: 30 | ''' 31 | batch = len(id_param) 32 | 33 | idBase = torch.from_numpy(self.idBase).float().to(id_param.device) 34 | ms = torch.from_numpy(self.meanshape.reshape(-1)).float().to(id_param.device) 35 | exBase = torch.from_numpy(self.exBase).float().to(id_param.device) 36 | 37 | 38 | shape = ms.unsqueeze(1) + idBase @ id_param.float().transpose(1, 0) + exBase @ ex_param.float().transpose(1, 0) 39 | shape = shape.transpose(1, 0).reshape((batch, -1, 3)) 40 | 41 | face_shape = shape - torch.mean(torch.reshape(ms, [1, -1, 3]), dim=1) 42 | 43 | return face_shape 44 | 45 | def texture_formation(self, tex_param): 46 | batch = len(tex_param) 47 | texBase = torch.from_numpy(self.texBase).float().to(tex_param.device) 48 | mt = torch.from_numpy(self.meantex.reshape(-1)).float().to(tex_param.device) 49 | 50 | tex = mt.unsqueeze(1) + texBase @ tex_param.transpose(1, 0) 51 | tex = tex.transpose(1, 0) 52 | 53 | tex = tex.reshape((batch, -1, 3)) 54 | return tex 55 | 56 | # keypoint index in BFM_front_face model is not the same as defined in dlib 57 | # this function makes them be same 58 | # default using dlib index 59 | def transform_keypoints_index(self): 60 | kp_idx = self.keypoints.reshape(-1) 61 | coutour = list(kp_idx[0:17][::-1]) 62 | nose_u = list(kp_idx[27:31]) 63 | nose_d = list(kp_idx[31:36][::-1]) 64 | 65 | eye_cor_order = np.array([45, 44, 43, 42, 47, 46, 39, 38, 37, 36, 41, 40]) 66 | mouse_cor_order = np.array([54, 53, 52, 51, 50, 49, 48, 59, 58, 57, 56, 55, 64, 63, 62, 61, 60, 67, 66, 65]) 67 | 68 | mouse = list(kp_idx[mouse_cor_order]) 69 | eyes = list(kp_idx[eye_cor_order]) 70 | eye_brow = list(kp_idx[17:27][::-1]) 71 | kp_idx = coutour + eye_brow + nose_u + nose_d + eyes + mouse 72 | 73 | self.keypoints = np.array(kp_idx).reshape(-1).astype(np.int64) 74 | 75 | def get_triangle_and_kp68_index(self): 76 | kp_idx = self.keypoints 77 | 78 | return torch.from_numpy(self.tri.astype(np.int32) - 1).unsqueeze(0).cuda(), torch.from_numpy( 79 | kp_idx.astype(np.int64)).cuda() -------------------------------------------------------------------------------- /VideoFace3D/face_track/src/sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | As implemented in https://github.com/abewley/sort but with some modifications 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import numpy as np 8 | from VideoFace3D.face_track.src.data_association import associate_detections_to_trackers 9 | from VideoFace3D.face_track.src.kalman_tracker import KalmanBoxTracker 10 | 11 | #logger = utils.Logger("MOT") 12 | 13 | 14 | class Sort: 15 | 16 | def __init__(self, max_age=1, min_hits=3): 17 | """ 18 | Sets key parameters for SORT 19 | """ 20 | self.max_age = max_age 21 | self.min_hits = min_hits 22 | self.trackers = [] 23 | self.frame_count = 0 24 | 25 | def update(self, dets, img_size, root_dic, addtional_attribute_list, predict_num): 26 | """ 27 | Params: 28 | dets - a numpy array of detections in the format [[x,y,w,h,score],[x,y,w,h,score],...] 29 | Requires: this method must be called once for each frame even with empty detections. 30 | Returns the a similar array, where the last column is the object ID. 31 | 32 | NOTE:as in practical realtime MOT, the detector doesn't run on every single frame 33 | """ 34 | self.frame_count += 1 35 | # get predicted locations from existing trackers. 36 | trks = np.zeros((len(self.trackers), 5)) 37 | to_del = [] 38 | ret = [] 39 | for t, trk in enumerate(trks): 40 | pos = self.trackers[t].predict() # kalman predict ,very fast ,<1ms 41 | trk[:] = [pos[0], pos[1], pos[2], pos[3], 0] 42 | if np.any(np.isnan(pos)): 43 | to_del.append(t) 44 | trks = np.ma.compress_rows(np.ma.masked_invalid(trks)) 45 | for t in reversed(to_del): 46 | self.trackers.pop(t) 47 | if dets != []: 48 | matched, unmatched_dets, unmatched_trks = associate_detections_to_trackers(dets, trks) 49 | 50 | # update matched trackers with assigned detections 51 | for t, trk in enumerate(self.trackers): 52 | if t not in unmatched_trks: 53 | d = matched[np.where(matched[:, 1] == t)[0], 0] 54 | trk.update(dets[d, :][0]) 55 | trk.face_addtional_attribute.append(addtional_attribute_list[d[0]]) 56 | 57 | # create and initialise new trackers for unmatched detections 58 | for i in unmatched_dets: 59 | trk = KalmanBoxTracker(dets[i, :]) 60 | trk.face_addtional_attribute.append(addtional_attribute_list[i]) 61 | #logger.info("new Tracker: {0}".format(trk.id + 1)) 62 | self.trackers.append(trk) 63 | 64 | i = len(self.trackers) 65 | for trk in reversed(self.trackers): 66 | if dets == []: 67 | trk.update([]) 68 | d = trk.get_state() 69 | if (trk.time_since_update < 1) and (trk.hit_streak >= self.min_hits or self.frame_count <= self.min_hits): 70 | ret.append(np.concatenate((d, [trk.id + 1])).reshape(1, -1)) # +1 as MOT benchmark requires positive 71 | i -= 1 72 | # remove dead tracklet 73 | if trk.time_since_update >= self.max_age or trk.predict_num >= predict_num or d[2] < 0 or d[3] < 0 or d[0] > img_size[1] or d[1] > img_size[0]: 74 | if len(trk.face_addtional_attribute) >= 5: 75 | pass 76 | #utils.save_to_file(root_dic, trk) 77 | #logger.info('remove tracker: {0}'.format(trk.id + 1)) 78 | self.trackers.pop(i) 79 | if len(ret) > 0: 80 | return np.concatenate(ret) 81 | return np.empty((0, 5)) 82 | -------------------------------------------------------------------------------- /VideoFace3D/face_track/src/kalman_tracker.py: -------------------------------------------------------------------------------- 1 | """ 2 | As implemented in https://github.com/abewley/sort but with some modifications 3 | """ 4 | 5 | import numpy as np 6 | from filterpy.kalman import KalmanFilter 7 | 8 | '''Motion Model''' 9 | 10 | 11 | class KalmanBoxTracker(object): 12 | """ 13 | This class represents the internal state of individual tracked objects observed as bbox. 14 | """ 15 | count = 0 16 | 17 | def __init__(self, bbox): 18 | """ 19 | Initialises a tracker using initial bounding box. 20 | """ 21 | # define constant velocity model 22 | self.kf = KalmanFilter(dim_x=7, dim_z=4) 23 | self.kf.F = np.array( 24 | [[1, 0, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 1, 0], [0, 0, 1, 0, 0, 0, 1], [0, 0, 0, 1, 0, 0, 0], 25 | [0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 1]]) 26 | self.kf.H = np.array( 27 | [[1, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0]]) 28 | 29 | self.kf.R[2:, 2:] *= 10. 30 | self.kf.P[4:, 4:] *= 1000. # give high uncertainty to the unobservable initial velocities 31 | self.kf.P *= 10. 32 | self.kf.Q[-1, -1] *= 0.01 33 | self.kf.Q[4:, 4:] *= 0.01 34 | 35 | self.kf.x[:4] = convert_bbox_to_z(bbox) 36 | self.time_since_update = 0 37 | self.id = KalmanBoxTracker.count 38 | KalmanBoxTracker.count += 1 39 | self.history = [] 40 | self.hits = 0 41 | self.hit_streak = 0 42 | self.age = 0 43 | 44 | self.predict_num = 0 # 解决画面中无人脸检测到时而导致的原有追踪器人像预测的漂移bug 45 | 46 | # addtional fields 47 | self.face_addtional_attribute = [] 48 | 49 | def update(self, bbox): 50 | """ 51 | Updates the state vector with observed bbox. 52 | """ 53 | self.time_since_update = 0 54 | self.history = [] 55 | self.hits += 1 56 | self.hit_streak += 1 57 | if bbox != []: 58 | self.kf.update(convert_bbox_to_z(bbox)) 59 | self.predict_num = 0 60 | else: 61 | self.predict_num += 1 62 | 63 | def predict(self): 64 | """ 65 | Advances the state vector and returns the predicted bounding box estimate. 66 | """ 67 | if (self.kf.x[6] + self.kf.x[2]) <= 0: 68 | self.kf.x[6] *= 0.0 69 | self.kf.predict() 70 | self.age += 1 71 | if self.time_since_update > 0: 72 | self.hit_streak = 0 73 | self.time_since_update += 1 74 | self.history.append(convert_x_to_bbox(self.kf.x)) 75 | return self.history[-1][0] 76 | 77 | def get_state(self): 78 | """ 79 | Returns the current bounding box estimate. 80 | """ 81 | return convert_x_to_bbox(self.kf.x)[0] 82 | 83 | 84 | def convert_bbox_to_z(bbox): 85 | """ 86 | Takes a bounding box in the form [x1,y1,x2,y2] and returns z in the form 87 | [x,y,s,r] where x,y is the centre of the box and s is the scale/area and r is 88 | the aspect ratio 89 | """ 90 | w = bbox[2] - bbox[0] 91 | h = bbox[3] - bbox[1] 92 | x = bbox[0] + w / 2. 93 | y = bbox[1] + h / 2. 94 | s = w * h # scale is just area 95 | r = w / float(h) 96 | return np.array([x, y, s, r]).reshape((4, 1)) 97 | 98 | 99 | def convert_x_to_bbox(x, score=None): 100 | """ 101 | Takes a bounding box in the centre form [x,y,s,r] and returns it in the form 102 | [x1,y1,x2,y2] where x1,y1 is the top left and x2,y2 is the bottom right 103 | """ 104 | w = np.sqrt(x[2] * x[3]) 105 | h = x[2] / w 106 | if score is None: 107 | return np.array([x[0] - w / 2., x[1] - h / 2., x[0] + w / 2., x[1] + h / 2.]).reshape((1, 4)) 108 | else: 109 | return np.array([x[0] - w / 2., x[1] - h / 2., x[0] + w / 2., x[1] + h / 2., score]).reshape((1, 5)) 110 | -------------------------------------------------------------------------------- /VideoFace3D/utils/video_utils.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import os 3 | import sys 4 | from VideoFace3D.utils.Global import * 5 | from VideoFace3D.utils.temporal_smooth import * 6 | 7 | def id_generator(number, id_len=4): 8 | number = str(number) 9 | assert len(number) < id_len 10 | 11 | return "0" * (id_len - len(number)) + number 12 | 13 | def progressbar(current, total, num=40, prefix=""): 14 | sys.stdout.write("\r{} {}/{} |{}{}| {:.2f}%".format(prefix, current, total, 15 | "*" * int(num * current / total), 16 | " " * (num - int(num * current / total)), 17 | 100 * current / total)) 18 | sys.stdout.flush() 19 | if current == total: 20 | print("") 21 | 22 | def str2seconds(time): 23 | try: 24 | 25 | h, m, s = time.split(":")[0], time.split(":")[1], time.split(":")[2] 26 | h, m, s = int(h), int(m), int(s) 27 | assert h >= 0 28 | assert 0 <= m < 60 29 | assert 0 <= s < 60 30 | seconds = h * 3600 + m * 60 + s 31 | return int(seconds) 32 | except: 33 | assert False, "wrong time format" 34 | #sys.exit(0) 35 | 36 | def extract_frame_from_video(video_path, save_path=None, ret_frame=True, time_start="default", time_end="default"): 37 | start_frame, end_frame = 0, 0 38 | 39 | if save_path is not None: 40 | if not os.path.exists(save_path): 41 | os.makedirs(save_path) 42 | 43 | cap = cv2.VideoCapture(video_path) 44 | 45 | fps = cap.get(5) 46 | frame_nums = cap.get(7) 47 | total_seconds = int(frame_nums / fps) 48 | 49 | if time_start == "default": 50 | start_frame = 0 51 | else: 52 | start_frame = int(frame_nums * (str2seconds(time_start) / total_seconds)) 53 | if time_end == "default": 54 | end_frame = frame_nums 55 | else: 56 | tmp = int(frame_nums * (str2seconds(time_end) / total_seconds)) 57 | if tmp > frame_nums: 58 | end_frame = frame_nums 59 | else: 60 | end_frame = tmp 61 | 62 | assert start_frame <= end_frame 63 | 64 | iters = int(end_frame - start_frame) 65 | 66 | cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame) 67 | 68 | all_frames = [] 69 | for count in range(iters): 70 | ret, frame = cap.read() 71 | if frame is None: 72 | break 73 | if ret_frame: 74 | all_frames.append(frame) 75 | if save_path is not None: 76 | cv2.imwrite(os.path.join(save_path, "{}.png".format(id_generator(count, 7))), frame) 77 | progressbar(count+1, iters, prefix="extract") 78 | if ret_frame: 79 | return all_frames 80 | else: 81 | return None 82 | 83 | def frames2video(save_path, frames, fps=24): 84 | base_folder = os.path.split(save_path)[0] 85 | if not os.path.exists(base_folder): 86 | os.makedirs(base_folder) 87 | 88 | H, W = frames[0].shape[0:2] 89 | img_size = (W, H) 90 | 91 | fourcc = cv2.VideoWriter_fourcc('M', 'J', 'P', 'G') 92 | video_writer = cv2.VideoWriter(save_path, fourcc, fps, img_size) 93 | 94 | num = 0 95 | for frame in frames: 96 | video_writer.write(frame) 97 | num += 1 98 | progressbar(num, len(frames), prefix="write video") 99 | 100 | video_writer.release() 101 | 102 | 103 | def video_temporal_smooth_constrains(ref_param, method=SMOOTH_METHODS_GAUSSIAN): 104 | if method == SMOOTH_METHODS_DCNN: 105 | return smooth_DCNN(ref_param) 106 | 107 | if method == SMOOTH_METHODS_GAUSSIAN: 108 | return smooth_gaussian_filter(ref_param) 109 | 110 | if method == SMOOTH_METHODS_MEAN: 111 | return smooth_mean_filter(ref_param) 112 | 113 | if method == SMOOTH_METHODS_MEDUIM: 114 | return smooth_medium_filter(ref_param) 115 | 116 | if method == SMOOTH_METHODS_OPTIMIZE: 117 | return smooth_optimize(ref_param) 118 | -------------------------------------------------------------------------------- /VideoFace3D/utils/visualization.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import random 3 | import numpy as np 4 | 5 | class CColor(): 6 | def __init__(self, rgb_string): 7 | self.rgb_string = rgb_string 8 | 9 | def to_rgb(self): 10 | (r, g, b) = string2rgb(self.rgb_string) 11 | return (r, g, b) 12 | 13 | def to_bgr(self): 14 | (r, g, b) = string2rgb(self.rgb_string) 15 | return (b, g, r) 16 | 17 | 18 | class ComfortableColor(): 19 | def __init__(self): 20 | self.grape_fruit = CColor("#ED5565") 21 | self.grape_fruit_d = CColor("#DA4453") 22 | self.sun_flower = CColor("#FFCE54") 23 | self.sun_flower_d = CColor("#F6BB42") 24 | self.mint = CColor("#48CFAD") 25 | self.mint_d = CColor("#37BC9B") 26 | self.blue_jeans = CColor("#5D9CEC") 27 | self.blue_jeans_d = CColor("#4A89DC") 28 | self.pink_rose = CColor("#EC89C0") 29 | self.pink_rose_d = CColor("#D770AD") 30 | self.bitter_sweet = CColor("#FC6E51") 31 | self.bitter_sweet_d = CColor("#E9573F") 32 | self.grass = CColor("#A0D468") 33 | self.grass_d = CColor("#8CC152") 34 | self.aqua = CColor("#4FC1E9") 35 | self.aqua_d = CColor("#3BAFDA") 36 | self.lavander = CColor("#AC92EC") 37 | self.lavander_d = CColor("#967ADC") 38 | self.light_gray = CColor("#F5F7FA") 39 | self.light_gray_d = CColor("#E6E9ED") 40 | self.medium_gray = CColor("#CCD1D9") 41 | self.medium_gray_d = CColor("#AAB2BD") 42 | self.dark_gray = CColor("#656D78") 43 | self.dark_gray_d = CColor("#434A54") 44 | 45 | def string2hex(string): 46 | hex = 0 47 | for i in range(len(string)): 48 | if string[i] in ["{}".format(i) for i in range(10)]: 49 | hex += int(string[i]) * 16 ** (len(string) - i - 1) 50 | elif string[i].upper() == "A": 51 | hex += 10 * 16 ** (len(string) - i - 1) 52 | elif string[i].upper() == "B": 53 | hex += 11 * 16 ** (len(string) - i - 1) 54 | elif string[i].upper() == "C": 55 | hex += 12 * 16 ** (len(string) - i - 1) 56 | elif string[i].upper() == "D": 57 | hex += 13 * 16 ** (len(string) - i - 1) 58 | elif string[i].upper() == "E": 59 | hex += 14 * 16 ** (len(string) - i - 1) 60 | elif string[i].upper() == "F": 61 | hex += 15 * 16 ** (len(string) - i - 1) 62 | return int(hex) 63 | 64 | 65 | def string2rgb(string): 66 | r, g, b = string[1:3], string[3:5], string[5:7] 67 | return (string2hex(r), string2hex(g), string2hex(b)) 68 | 69 | 70 | def draw_landmarks(image, landmarks, plot_index=False, colors=None): 71 | image1 = image.copy() 72 | for i in range(len(landmarks)): 73 | lm_num = len(landmarks[i]) 74 | if colors is not None: 75 | color = colors[i % len(colors)] 76 | else: 77 | color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) 78 | for j in range(lm_num): 79 | x, y = int(landmarks[i][j][0]), int(landmarks[i][j][1]) 80 | image1 = cv2.circle(image1, (x, y), radius=3, thickness=2, color=color) 81 | if plot_index: 82 | image1 = cv2.putText(image1, str(j), (x - 5, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.3, 83 | color, 84 | thickness=1) 85 | return image1 86 | 87 | 88 | def draw_bbox(image, bboxs, colors=None): 89 | image1 = image.copy() 90 | for i in range(len(bboxs)): 91 | if colors is not None: 92 | color = colors[i % len(colors)] 93 | else: 94 | color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) 95 | image1 = cv2.rectangle(image1, (bboxs[i][0], bboxs[i][1]), (bboxs[i][2], bboxs[i][3]), color[i]) 96 | return image1 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /VideoFace3D/utils/temporal_smooth.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import numpy as np 4 | import copy 5 | 6 | 7 | # from networks import TimeSmoothNetWork 8 | class Model(nn.Module): 9 | def __init__(self, H, W, initation=None): 10 | super(Model, self).__init__() 11 | 12 | self.H = H 13 | self.W = W 14 | self.l1 = nn.Linear(self.W, self.H) 15 | 16 | if initation is not None: 17 | self.l1.weight.data = torch.from_numpy(initation) 18 | 19 | self.smo_param = self.l1.weight 20 | 21 | def divergence(self, x): 22 | g_x = x[:, 1:self.W] - x[:, 0:self.W - 1] 23 | 24 | g_xx = g_x[:, 1:self.W - 1] - g_x[:, 0:self.W - 2] 25 | 26 | return g_xx 27 | 28 | def gradient(self, x): 29 | g_x = x[:, 1:self.W] - x[:, 0:self.W - 1] 30 | return g_x 31 | 32 | def forward(self, ref_param): 33 | sim_loss = torch.sum((ref_param - self.smo_param.float()) ** 2) 34 | 35 | smo_loss = torch.norm(self.divergence(self.smo_param.float()), p=2) 36 | smo_loss2 = torch.norm(self.gradient(self.smo_param.float()), p=2) 37 | return sim_loss, smo_loss 38 | 39 | 40 | ''' 41 | 提供5种平滑方式 42 | 均值平滑,中值平滑,高斯平滑,,基于优化的平滑,卷积网络平滑 43 | 输入: ref_param 参考参数,类型 ndarray, shape:[param_num,frames] 44 | 输出: new_param 平滑参数, 类型 ndarray, shape:[param_num,frames] 45 | ''' 46 | 47 | 48 | def smooth_optimize(ref_param): 49 | ref_param = ref_param.astype(np.float32) 50 | H, W = ref_param.shape 51 | 52 | model = Model(H, W, initation=ref_param).cuda() 53 | ref_param = torch.from_numpy(ref_param).float().cuda() 54 | 55 | iterations = 300 56 | optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) 57 | 58 | # print(ref_param.shape) 59 | 60 | for it in range(iterations): 61 | sim_loss, smo_loss = model(ref_param) 62 | 63 | loss = 1 * sim_loss + 1.3 * smo_loss 64 | 65 | optimizer.zero_grad() 66 | loss.backward() 67 | optimizer.step() 68 | 69 | new_param = model.smo_param.cpu().detach().numpy() 70 | 71 | return new_param 72 | 73 | 74 | def smooth_medium_filter(ref_param, k=5): 75 | assert k % 2 == 1, "未实现偶数步长" 76 | H, W = ref_param.shape 77 | 78 | s = int(k / 2) 79 | 80 | new_param = copy.deepcopy(ref_param) 81 | for i in range(0, W): 82 | start = np.maximum(0, i - s) 83 | end = np.minimum(W, i + s + 1) 84 | 85 | new_param[:, i] = np.median(ref_param[:, start:end], axis=1) 86 | 87 | return new_param 88 | 89 | 90 | def smooth_mean_filter(ref_param, k=5): 91 | assert k % 2 == 1, "未实现偶数步长" 92 | H, W = ref_param.shape 93 | 94 | s = int(k / 2) 95 | 96 | new_param = copy.deepcopy(ref_param) 97 | for i in range(0, W): 98 | start = np.maximum(0, i - s) 99 | end = np.minimum(W, i + s + 1) 100 | 101 | new_param[:, i] = np.mean(ref_param[:, start:end], axis=1) 102 | 103 | return new_param 104 | 105 | 106 | def smooth_gaussian_filter(ref_param, k=5): 107 | miu, sigma = 0, 1 108 | assert k % 2 == 1, "未实现偶数步长" 109 | H, W = ref_param.shape 110 | 111 | center = int(k / 2) 112 | x = np.array([i - center for i in range(k)]) 113 | 114 | weights = np.exp(-(x - miu) ** 2 / (2 * sigma ** 2)) / (sigma * np.sqrt(2 * np.pi)) 115 | weights = weights / np.sum(weights) 116 | 117 | new_param = copy.deepcopy(ref_param) 118 | 119 | for i in range(center, W - center): 120 | start = np.maximum(0, i - center) 121 | end = np.minimum(W, i + center + 1) 122 | 123 | new_param[:, i] = ref_param[:, start:end] @ weights 124 | 125 | return new_param 126 | 127 | 128 | def smooth_DCNN(ref_param): 129 | pass 130 | 131 | 132 | def calculate_smooth_loss(x): 133 | H, W = x.shape 134 | 135 | g_x = x[:, 1:W] - x[:, 0:W - 1] 136 | 137 | g_xx = g_x[:, 1:W - 1] - g_x[:, 0:W - 2] 138 | 139 | return np.sum(g_xx ** 2) 140 | # return np.sum(np.abs(g_xx)) 141 | 142 | 143 | def calculate_sim_loss(x, y): 144 | return np.sum((x - y) ** 2) 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /VideoFace3D/segmentation/faceparsing/resnet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- encoding: utf-8 -*- 3 | 4 | import torch 5 | import torch.nn as nn 6 | import torch.nn.functional as F 7 | import torch.utils.model_zoo as modelzoo 8 | 9 | # from modules.bn import InPlaceABNSync as BatchNorm2d 10 | 11 | resnet18_url = 'https://download.pytorch.org/models/resnet18-5c106cde.pth' 12 | 13 | 14 | def conv3x3(in_planes, out_planes, stride=1): 15 | """3x3 convolution with padding""" 16 | return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, 17 | padding=1, bias=False) 18 | 19 | 20 | class BasicBlock(nn.Module): 21 | def __init__(self, in_chan, out_chan, stride=1): 22 | super(BasicBlock, self).__init__() 23 | self.conv1 = conv3x3(in_chan, out_chan, stride) 24 | self.bn1 = nn.BatchNorm2d(out_chan) 25 | self.conv2 = conv3x3(out_chan, out_chan) 26 | self.bn2 = nn.BatchNorm2d(out_chan) 27 | self.relu = nn.ReLU(inplace=True) 28 | self.downsample = None 29 | if in_chan != out_chan or stride != 1: 30 | self.downsample = nn.Sequential( 31 | nn.Conv2d(in_chan, out_chan, 32 | kernel_size=1, stride=stride, bias=False), 33 | nn.BatchNorm2d(out_chan), 34 | ) 35 | 36 | def forward(self, x): 37 | residual = self.conv1(x) 38 | residual = F.relu(self.bn1(residual)) 39 | residual = self.conv2(residual) 40 | residual = self.bn2(residual) 41 | 42 | shortcut = x 43 | if self.downsample is not None: 44 | shortcut = self.downsample(x) 45 | 46 | out = shortcut + residual 47 | out = self.relu(out) 48 | return out 49 | 50 | 51 | def create_layer_basic(in_chan, out_chan, bnum, stride=1): 52 | layers = [BasicBlock(in_chan, out_chan, stride=stride)] 53 | for i in range(bnum-1): 54 | layers.append(BasicBlock(out_chan, out_chan, stride=1)) 55 | return nn.Sequential(*layers) 56 | 57 | 58 | class Resnet18(nn.Module): 59 | def __init__(self): 60 | super(Resnet18, self).__init__() 61 | self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, 62 | bias=False) 63 | self.bn1 = nn.BatchNorm2d(64) 64 | self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) 65 | self.layer1 = create_layer_basic(64, 64, bnum=2, stride=1) 66 | self.layer2 = create_layer_basic(64, 128, bnum=2, stride=2) 67 | self.layer3 = create_layer_basic(128, 256, bnum=2, stride=2) 68 | self.layer4 = create_layer_basic(256, 512, bnum=2, stride=2) 69 | self.init_weight() 70 | 71 | def forward(self, x): 72 | x = self.conv1(x) 73 | x = F.relu(self.bn1(x)) 74 | x = self.maxpool(x) 75 | 76 | x = self.layer1(x) 77 | feat8 = self.layer2(x) # 1/8 78 | feat16 = self.layer3(feat8) # 1/16 79 | feat32 = self.layer4(feat16) # 1/32 80 | return feat8, feat16, feat32 81 | 82 | def init_weight(self): 83 | state_dict = modelzoo.load_url(resnet18_url) 84 | self_state_dict = self.state_dict() 85 | for k, v in state_dict.items(): 86 | if 'fc' in k: continue 87 | self_state_dict.update({k: v}) 88 | self.load_state_dict(self_state_dict) 89 | 90 | def get_params(self): 91 | wd_params, nowd_params = [], [] 92 | for name, module in self.named_modules(): 93 | if isinstance(module, (nn.Linear, nn.Conv2d)): 94 | wd_params.append(module.weight) 95 | if not module.bias is None: 96 | nowd_params.append(module.bias) 97 | elif isinstance(module, nn.BatchNorm2d): 98 | nowd_params += list(module.parameters()) 99 | return wd_params, nowd_params 100 | 101 | 102 | if __name__ == "__main__": 103 | net = Resnet18() 104 | x = torch.randn(16, 3, 224, 224) 105 | out = net(x) 106 | print(out[0].size()) 107 | print(out[1].size()) 108 | print(out[2].size()) 109 | net.get_params() 110 | -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/ddfa_io.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | import os 5 | import numpy as np 6 | import torch 7 | import pickle 8 | import scipy.io as sio 9 | 10 | 11 | def mkdir(d): 12 | """only works on *nix system""" 13 | if not os.path.isdir(d) and not os.path.exists(d): 14 | os.system('mkdir -p {}'.format(d)) 15 | 16 | 17 | def _get_suffix(filename): 18 | """a.jpg -> jpg""" 19 | pos = filename.rfind('.') 20 | if pos == -1: 21 | return '' 22 | return filename[pos + 1:] 23 | 24 | 25 | def _load(fp): 26 | suffix = _get_suffix(fp) 27 | if suffix == 'npy': 28 | return np.load(fp) 29 | elif suffix == 'pkl': 30 | return pickle.load(open(fp, 'rb')) 31 | 32 | 33 | def _dump(wfp, obj): 34 | suffix = _get_suffix(wfp) 35 | if suffix == 'npy': 36 | np.save(wfp, obj) 37 | elif suffix == 'pkl': 38 | pickle.dump(obj, open(wfp, 'wb')) 39 | else: 40 | raise Exception('Unknown Type: {}'.format(suffix)) 41 | 42 | 43 | def _load_tensor(fp, mode='cpu'): 44 | if mode.lower() == 'cpu': 45 | return torch.from_numpy(_load(fp)) 46 | elif mode.lower() == 'gpu': 47 | return torch.from_numpy(_load(fp)).cuda() 48 | 49 | 50 | def _tensor_to_cuda(x): 51 | if x.is_cuda: 52 | return x 53 | else: 54 | return x.cuda() 55 | 56 | 57 | def _load_gpu(fp): 58 | return torch.from_numpy(_load(fp)).cuda() 59 | 60 | 61 | def load_bfm(model_path): 62 | suffix = _get_suffix(model_path) 63 | if suffix == 'mat': 64 | C = sio.loadmat(model_path) 65 | model = C['model_refine'] 66 | model = model[0, 0] 67 | 68 | model_new = {} 69 | w_shp = model['w'].astype(np.float32) 70 | model_new['w_shp_sim'] = w_shp[:, :40] 71 | w_exp = model['w_exp'].astype(np.float32) 72 | model_new['w_exp_sim'] = w_exp[:, :10] 73 | 74 | u_shp = model['mu_shape'] 75 | u_exp = model['mu_exp'] 76 | u = (u_shp + u_exp).astype(np.float32) 77 | model_new['mu'] = u 78 | model_new['tri'] = model['tri'].astype(np.int32) - 1 79 | 80 | # flatten it, pay attention to index value 81 | keypoints = model['keypoints'].astype(np.int32) - 1 82 | keypoints = np.concatenate((3 * keypoints, 3 * keypoints + 1, 3 * keypoints + 2), axis=0) 83 | 84 | model_new['keypoints'] = keypoints.T.flatten() 85 | 86 | # 87 | w = np.concatenate((w_shp, w_exp), axis=1) 88 | w_base = w[keypoints] 89 | w_norm = np.linalg.norm(w, axis=0) 90 | w_base_norm = np.linalg.norm(w_base, axis=0) 91 | 92 | dim = w_shp.shape[0] // 3 93 | u_base = u[keypoints].reshape(-1, 1) 94 | w_shp_base = w_shp[keypoints] 95 | w_exp_base = w_exp[keypoints] 96 | 97 | model_new['w_norm'] = w_norm 98 | model_new['w_base_norm'] = w_base_norm 99 | model_new['dim'] = dim 100 | model_new['u_base'] = u_base 101 | model_new['w_shp_base'] = w_shp_base 102 | model_new['w_exp_base'] = w_exp_base 103 | 104 | _dump(model_path.replace('.mat', '.pkl'), model_new) 105 | return model_new 106 | else: 107 | return _load(model_path) 108 | 109 | 110 | def load_obj(obj_file): 111 | sents = open(obj_file).read().split("\n") 112 | vertices, color, tri = [], [], [] 113 | for s in sents: 114 | p = s.split(" ") 115 | if p[0] == 'v': 116 | vertices.append([float(p[1]), float(p[2]), float(p[3])]) 117 | if len(p) == 7: 118 | color.append([int(p[4]), int(p[5]), int(p[6])]) 119 | if p[0] == 'c': 120 | color.append([int(p[4]), int(p[5]), int(p[6])]) 121 | if p[0] == 'f': 122 | tri.append([int(p[1]), int(p[2]), int(p[3])]) 123 | 124 | return np.array(vertices), np.array(color)[:, ::-1], np.array(tri) - 1 125 | 126 | 127 | def write_obj_with_colors(obj_name, vertices, triangles, colors): 128 | triangles = triangles.copy() # meshlab start with 1 129 | 130 | if obj_name.split('.')[-1] != 'obj': 131 | obj_name = obj_name + '.obj' 132 | 133 | # write obj 134 | with open(obj_name, 'w') as f: 135 | # write vertices & colors 136 | for i in range(vertices.shape[0]): 137 | s = 'v {:.4f} {:.4f} {:.4f} {} {} {}\n'.format(vertices[i, 0], vertices[i, 1], vertices[i, 2], colors[i, 2], 138 | colors[i, 1], colors[i, 0]) 139 | f.write(s) 140 | 141 | # write f: ver ind/ uv ind 142 | for i in range(triangles.shape[0]): 143 | s = 'f {} {} {}\n'.format(triangles[i, 0], triangles[i, 1], triangles[i, 2]) 144 | f.write(s) 145 | 146 | 147 | _load_cpu = _load 148 | _numpy_to_tensor = lambda x: torch.from_numpy(x) 149 | _tensor_to_numpy = lambda x: x.cpu() 150 | _numpy_to_cuda = lambda x: _tensor_to_cuda(torch.from_numpy(x)) 151 | _cuda_to_tensor = lambda x: x.cpu() 152 | _cuda_to_numpy = lambda x: x.cpu().numpy() 153 | -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/ddfa_landmarks.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torchvision.transforms as transforms 3 | 4 | import numpy as np 5 | import cv2 6 | import dlib 7 | from VideoFace3D.landmark_detect.ddfa_ddfa import ToTensorGjz, NormalizeGjz, str2bool 8 | 9 | from VideoFace3D.landmark_detect.ddfa_inference import get_suffix, parse_roi_box_from_landmark, crop_img, \ 10 | predict_68pts, dump_to_ply, dump_vertex, \ 11 | draw_landmarks, predict_dense, parse_roi_box_from_bbox, get_colors, write_obj_with_colors 12 | from VideoFace3D.landmark_detect.ddfa_estimate_pose import parse_pose 13 | 14 | STD_SIZE = 120 15 | 16 | 17 | def detect_landmark_ddfa_3D(image_path, model, face_regressor, device, bbox_init="one", rects=None): 18 | model.eval() 19 | 20 | transform = transforms.Compose([ToTensorGjz(), NormalizeGjz(mean=127.5, std=128)]) 21 | 22 | img_ori = cv2.imread(image_path) 23 | dlib_landmarks = True if rects is None else False 24 | if rects is None: 25 | face_detector = dlib.get_frontal_face_detector() 26 | gray = cv2.cvtColor(img_ori, cv2.COLOR_BGR2GRAY) 27 | rects = face_detector(gray, 1) 28 | 29 | if len(rects) == 0: 30 | return [] 31 | pts_res = [] 32 | Ps = [] # Camera matrix collection 33 | poses = [] # pose collection, [todo: validate it] 34 | vertices_lst = [] # store multiple face vertices 35 | ind = 0 36 | suffix = get_suffix(image_path) 37 | for rect in rects: 38 | 39 | 40 | if dlib_landmarks: 41 | pts = face_regressor(img_ori, rect).parts() 42 | pts = np.array([[pt.x, pt.y] for pt in pts]).T 43 | roi_box = parse_roi_box_from_landmark(pts) 44 | else: 45 | roi_box = rect 46 | img = crop_img(img_ori, roi_box) 47 | 48 | # forward: one step 49 | img = cv2.resize(img, dsize=(STD_SIZE, STD_SIZE), interpolation=cv2.INTER_LINEAR) 50 | input = transform(img).unsqueeze(0).to(device) 51 | with torch.no_grad(): 52 | param = model(input) 53 | param = param.squeeze().cpu().numpy().flatten().astype(np.float32) 54 | # 68 pts 55 | pts68 = predict_68pts(param, roi_box) 56 | 57 | # two-step for more accurate bbox to crop face 58 | if bbox_init == 'two': 59 | roi_box = parse_roi_box_from_landmark(pts68) 60 | img_step2 = crop_img(img_ori, roi_box) 61 | img_step2 = cv2.resize(img_step2, dsize=(STD_SIZE, STD_SIZE), interpolation=cv2.INTER_LINEAR) 62 | input = transform(img_step2).unsqueeze(0).to(device) 63 | with torch.no_grad(): 64 | param = model(input) 65 | param = param.squeeze().cpu().numpy().flatten().astype(np.float32) 66 | 67 | pts68 = predict_68pts(param, roi_box) 68 | 69 | pts_res.append(pts68.transpose(1, 0)[:, 0:2]) 70 | P, pose = parse_pose(param) 71 | Ps.append(P) 72 | poses.append(pose) 73 | 74 | vertices = predict_dense(param, roi_box) 75 | # colors = get_colors(img_ori, vertices) 76 | return pts_res 77 | 78 | 79 | ''' 80 | def detect_landmark_ddfa_3D(image_path, model, rects, face_regressor, device, bbox_init="one"): 81 | model.eval() 82 | 83 | transform = transforms.Compose([ToTensorGjz(), NormalizeGjz(mean=127.5, std=128)]) 84 | 85 | img_ori = cv2.imread(image_path) 86 | gray = cv2.cvtColor(img_ori, cv2.COLOR_BGR2GRAY) 87 | rects = face_detector(gray, 1) 88 | 89 | if len(rects) == 0: 90 | return [] 91 | pts_res = [] 92 | Ps = [] # Camera matrix collection 93 | poses = [] # pose collection, [todo: validate it] 94 | vertices_lst = [] # store multiple face vertices 95 | ind = 0 96 | suffix = get_suffix(image_path) 97 | for rect in rects: 98 | 99 | pts = face_regressor(img_ori, rect).parts() 100 | pts = np.array([[pt.x, pt.y] for pt in pts]).T 101 | roi_box = parse_roi_box_from_landmark(pts) 102 | 103 | img = crop_img(img_ori, roi_box) 104 | 105 | # forward: one step 106 | img = cv2.resize(img, dsize=(STD_SIZE, STD_SIZE), interpolation=cv2.INTER_LINEAR) 107 | input = transform(img).unsqueeze(0).to(device) 108 | with torch.no_grad(): 109 | param = model(input) 110 | param = param.squeeze().cpu().numpy().flatten().astype(np.float32) 111 | # 68 pts 112 | pts68 = predict_68pts(param, roi_box) 113 | 114 | # two-step for more accurate bbox to crop face 115 | if bbox_init == 'two': 116 | roi_box = parse_roi_box_from_landmark(pts68) 117 | img_step2 = crop_img(img_ori, roi_box) 118 | img_step2 = cv2.resize(img_step2, dsize=(STD_SIZE, STD_SIZE), interpolation=cv2.INTER_LINEAR) 119 | input = transform(img_step2).unsqueeze(0).to(device) 120 | with torch.no_grad(): 121 | param = model(input) 122 | param = param.squeeze().cpu().numpy().flatten().astype(np.float32) 123 | 124 | pts68 = predict_68pts(param, roi_box) 125 | 126 | pts_res.append(pts68.transpose(1, 0)[:, 0:2]) 127 | P, pose = parse_pose(param) 128 | Ps.append(P) 129 | poses.append(pose) 130 | 131 | vertices = predict_dense(param, roi_box) 132 | # colors = get_colors(img_ori, vertices) 133 | return pts_res 134 | ''' -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/ddfa_ddfa.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | import os.path as osp 5 | from pathlib import Path 6 | import numpy as np 7 | 8 | import torch 9 | import torch.utils.data as data 10 | import cv2 11 | import pickle 12 | import argparse 13 | from VideoFace3D.landmark_detect.ddfa_io import _numpy_to_tensor, _load_cpu, _load_gpu 14 | from VideoFace3D.landmark_detect.ddfa_params import * 15 | 16 | 17 | def _parse_param(param): 18 | """Work for both numpy and tensor""" 19 | p_ = param[:12].reshape(3, -1) 20 | p = p_[:, :3] 21 | offset = p_[:, -1].reshape(3, 1) 22 | alpha_shp = param[12:52].reshape(-1, 1) 23 | alpha_exp = param[52:].reshape(-1, 1) 24 | return p, offset, alpha_shp, alpha_exp 25 | 26 | 27 | def reconstruct_vertex(param, whitening=True, dense=False, transform=True, rotate=True): 28 | """Whitening param -> 3d vertex, based on the 3dmm param: u_base, w_shp, w_exp 29 | dense: if True, return dense vertex, else return 68 sparse landmarks. All dense or sparse vertex is transformed to 30 | image coordinate space, but without alignment caused by face cropping. 31 | transform: whether transform to image space 32 | """ 33 | if len(param) == 12: 34 | param = np.concatenate((param, [0] * 50)) 35 | if whitening: 36 | if len(param) == 62: 37 | param = param * param_std + param_mean 38 | else: 39 | param = np.concatenate((param[:11], [0], param[11:])) 40 | param = param * param_std + param_mean 41 | 42 | p, offset, alpha_shp, alpha_exp = _parse_param(param) 43 | 44 | if dense: 45 | if rotate: 46 | vertex = p @ (u + w_shp @ alpha_shp + w_exp @ alpha_exp).reshape(3, -1, order='F') + offset 47 | else: 48 | vertex = (u + w_shp @ alpha_shp + w_exp @ alpha_exp).reshape(3, -1, order='F') 49 | 50 | if transform: 51 | # transform to image coordinate space 52 | vertex[1, :] = std_size + 1 - vertex[1, :] 53 | else: 54 | """For 68 pts""" 55 | if rotate: 56 | vertex = p @ (u_base + w_shp_base @ alpha_shp + w_exp_base @ alpha_exp).reshape(3, -1, order='F') + offset 57 | else: 58 | vertex = (u_base + w_shp_base @ alpha_shp + w_exp_base @ alpha_exp).reshape(3, -1, order='F') 59 | 60 | if transform: 61 | # transform to image coordinate space 62 | vertex[1, :] = std_size + 1 - vertex[1, :] 63 | 64 | return vertex 65 | 66 | 67 | def img_loader(path): 68 | return cv2.imread(path, cv2.IMREAD_COLOR) 69 | 70 | 71 | def str2bool(v): 72 | if v.lower() in ('yes', 'true', 't', 'y', '1'): 73 | return True 74 | elif v.lower() in ('no', 'false', 'f', 'n', '0'): 75 | return False 76 | else: 77 | raise argparse.ArgumentTypeError('Boolean value expected') 78 | 79 | 80 | class AverageMeter(object): 81 | """Computes and stores the average and current value""" 82 | 83 | def __init__(self): 84 | self.reset() 85 | 86 | def reset(self): 87 | self.val = 0 88 | self.avg = 0 89 | self.sum = 0 90 | self.count = 0 91 | 92 | def update(self, val, n=1): 93 | self.val = val 94 | self.sum += val * n 95 | self.count += n 96 | self.avg = self.sum / self.count 97 | 98 | 99 | class ToTensorGjz(object): 100 | def __call__(self, pic): 101 | if isinstance(pic, np.ndarray): 102 | img = torch.from_numpy(pic.transpose((2, 0, 1))) 103 | return img.float() 104 | 105 | def __repr__(self): 106 | return self.__class__.__name__ + '()' 107 | 108 | 109 | class NormalizeGjz(object): 110 | def __init__(self, mean, std): 111 | self.mean = mean 112 | self.std = std 113 | 114 | def __call__(self, tensor): 115 | tensor.sub_(self.mean).div_(self.std) 116 | return tensor 117 | 118 | 119 | class DDFADataset(data.Dataset): 120 | def __init__(self, root, filelists, param_fp, transform=None, **kargs): 121 | self.root = root 122 | self.transform = transform 123 | self.lines = Path(filelists).read_text().strip().split('\n') 124 | self.params = _numpy_to_tensor(_load_cpu(param_fp)) 125 | self.img_loader = img_loader 126 | 127 | def _target_loader(self, index): 128 | target = self.params[index] 129 | 130 | return target 131 | 132 | def __getitem__(self, index): 133 | path = osp.join(self.root, self.lines[index]) 134 | img = self.img_loader(path) 135 | 136 | target = self._target_loader(index) 137 | 138 | if self.transform is not None: 139 | img = self.transform(img) 140 | return img, target 141 | 142 | def __len__(self): 143 | return len(self.lines) 144 | 145 | 146 | class DDFATestDataset(data.Dataset): 147 | def __init__(self, filelists, root='', transform=None): 148 | self.root = root 149 | self.transform = transform 150 | self.lines = Path(filelists).read_text().strip().split('\n') 151 | 152 | def __getitem__(self, index): 153 | path = osp.join(self.root, self.lines[index]) 154 | img = img_loader(path) 155 | 156 | if self.transform is not None: 157 | img = self.transform(img) 158 | return img 159 | 160 | def __len__(self): 161 | return len(self.lines) 162 | -------------------------------------------------------------------------------- /VideoFace3D/renderer/lightning.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import math 3 | 4 | 5 | def Illumination_SH(face_texture, norm, gamma): 6 | ''' 7 | 8 | :param face_texture: [batch, face_num, 3] 9 | :param norm: [batch, face_num, 3] 10 | :param gamma: [batch, 27] 11 | :return: 12 | ''' 13 | pi = 3.1415926 14 | num_vertex = face_texture.shape[1] 15 | batch = len(face_texture) 16 | 17 | init_lit = torch.Tensor([0.8, 0, 0, 0, 0, 0, 0, 0, 0]).to(gamma.device) 18 | gamma = torch.reshape(gamma, [-1, 3, 9]) 19 | gamma = gamma + torch.reshape(init_lit, [1, 1, 9]) 20 | 21 | # parameter of 9 SH function 22 | a0 = torch.Tensor([pi]).to(gamma.device) 23 | a1 = torch.Tensor([2 * pi / math.sqrt(3.0)]).to(gamma.device) 24 | a2 = torch.Tensor([2 * pi / math.sqrt(8.0)]).to(gamma.device) 25 | c0 = torch.Tensor([1 / math.sqrt(4 * pi)]).to(gamma.device) 26 | c1 = torch.Tensor([math.sqrt(3.0) / math.sqrt(4 * pi)]).to(gamma.device) 27 | c2 = torch.Tensor([3 * math.sqrt(5.0) / math.sqrt(12 * pi)]).to(gamma.device) 28 | 29 | Y0 = torch.Tensor.repeat(torch.reshape(a0 * c0, [1, 1, 1]), [batch, num_vertex, 1]) 30 | Y1 = torch.reshape(-a1 * c1 * norm[:, :, 1], [batch, num_vertex, 1]) 31 | Y2 = torch.reshape(a1 * c1 * norm[:, :, 2], [batch, num_vertex, 1]) 32 | Y3 = torch.reshape(-a1 * c1 * norm[:, :, 0], [batch, num_vertex, 1]) 33 | Y4 = torch.reshape(a2 * c2 * norm[:, :, 0] * norm[:, :, 1], [batch, num_vertex, 1]) 34 | Y5 = torch.reshape(-a2 * c2 * norm[:, :, 1] * norm[:, :, 2], [batch, num_vertex, 1]) 35 | Y6 = torch.reshape(a2 * c2 * 0.5 / math.sqrt(3.0) * (3 * norm[:, :, 2] ** 2 - 1), [batch, num_vertex, 1]) 36 | Y7 = torch.reshape(-a2 * c2 * norm[:, :, 0] * norm[:, :, 2], [batch, num_vertex, 1]) 37 | Y8 = torch.reshape(a2 * c2 * 0.5 * (norm[:, :, 0] ** 2 - norm[:, :, 1] ** 2), [batch, num_vertex, 1]) 38 | 39 | Y = torch.cat([Y0, Y1, Y2, Y3, Y4, Y5, Y6, Y7, Y8], dim=2) 40 | 41 | # Y shape:[batch,N,9]. 42 | 43 | # [batch,N,9] * [batch,9,1] = [batch,N] 44 | lit_r = torch.squeeze(torch.matmul(Y, torch.unsqueeze(gamma[:, 0, :], 2)), 2) 45 | lit_g = torch.squeeze(torch.matmul(Y, torch.unsqueeze(gamma[:, 1, :], 2)), 2) 46 | lit_b = torch.squeeze(torch.matmul(Y, torch.unsqueeze(gamma[:, 2, :], 2)), 2) 47 | 48 | # shape:[batch,N,3] 49 | 50 | face_color_r = (lit_r * face_texture[:, :, 0]).unsqueeze(2) 51 | face_color_g = (lit_g * face_texture[:, :, 1]).unsqueeze(2) 52 | face_color_b = (lit_b * face_texture[:, :, 2]).unsqueeze(2) 53 | 54 | face_color = torch.cat([face_color_r, face_color_g, face_color_b], dim=2) 55 | lighting = torch.cat([lit_r.unsqueeze(2), lit_g.unsqueeze(2), lit_b.unsqueeze(2)], dim=2) * 128 56 | return face_color, lighting 57 | 58 | 59 | import torch 60 | import torch.nn.functional as F 61 | import numpy as np 62 | 63 | def lighting_phong(faces, textures, intensity_ambient=0.5, intensity_directional=0.5, 64 | color_ambient=(1, 1, 1), color_directional=(1, 1, 1), direction=(0, 1, 0)): 65 | 66 | bs, nf = faces.shape[:2] 67 | device = faces.device 68 | 69 | # arguments 70 | # make sure to convert all inputs to float tensors 71 | if isinstance(color_ambient, tuple) or isinstance(color_ambient, list): 72 | color_ambient = torch.tensor(color_ambient, dtype=torch.float32, device=device) 73 | elif isinstance(color_ambient, np.ndarray): 74 | color_ambient = torch.from_numpy(color_ambient).float().to(device) 75 | if isinstance(color_directional, tuple) or isinstance(color_directional, list): 76 | color_directional = torch.tensor(color_directional, dtype=torch.float32, device=device) 77 | elif isinstance(color_directional, np.ndarray): 78 | color_directional = torch.from_numpy(color_directional).float().to(device) 79 | if isinstance(direction, tuple) or isinstance(direction, list): 80 | direction = torch.tensor(direction, dtype=torch.float32, device=device) 81 | elif isinstance(direction, np.ndarray): 82 | direction = torch.from_numpy(direction).float().to(device) 83 | if color_ambient.ndimension() == 1: 84 | color_ambient = color_ambient[None, :] 85 | if color_directional.ndimension() == 1: 86 | color_directional = color_directional[None, :] 87 | if direction.ndimension() == 1: 88 | direction = direction[None, :] 89 | 90 | # create light 91 | light = torch.zeros(bs, nf, 3, dtype=torch.float32).to(device) 92 | 93 | # ambient light 94 | if intensity_ambient != 0: 95 | light += intensity_ambient * color_ambient[:, None, :] 96 | 97 | # directional light 98 | if intensity_directional != 0: 99 | faces = faces.reshape((bs * nf, 3, 3)) 100 | v10 = faces[:, 0] - faces[:, 1] 101 | v12 = faces[:, 2] - faces[:, 1] 102 | # pytorch normalize divides by max(norm, eps) instead of (norm+eps) in chainer 103 | normals = F.normalize(torch.cross(v10, v12), eps=1e-5) 104 | normals = normals.reshape((bs, nf, 3)) 105 | 106 | if direction.ndimension() == 2: 107 | direction = direction[:, None, :] 108 | cos = F.relu(torch.sum(normals * direction, dim=2)) 109 | # may have to verify that the next line is correct 110 | light += intensity_directional * (color_directional[:, None, :] * cos[:, :, None]) 111 | 112 | # apply 113 | light = light[:,:,None, None, None, :] 114 | textures *= light 115 | return textures 116 | -------------------------------------------------------------------------------- /VideoFace3D/SFS/SFSNet/functions.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | import numpy as np 3 | import sys 4 | from matplotlib.path import Path 5 | 6 | 7 | def create_shading_recon(n_out2, al_out2, light_out): 8 | """ 9 | :type n_out2: np.ndarray 10 | :type al_out2: np.ndarray 11 | :type light_out: np.ndarray 12 | :return: 13 | """ 14 | M = n_out2.shape[0] 15 | No1 = np.reshape(n_out2, (M * M, 3)) 16 | tex1 = np.reshape(al_out2, (M * M, 3)) 17 | 18 | la = lambertian_attenuation(3) 19 | HN1 = normal_harmonics(No1.T, la) 20 | 21 | HS1r = np.matmul(HN1, light_out[0:9]) 22 | HS1g = np.matmul(HN1, light_out[9:18]) 23 | HS1b = np.matmul(HN1, light_out[18:27]) 24 | 25 | HS1 = np.zeros(shape=(M, M, 3), dtype=np.float32) 26 | HS1[:, :, 0] = np.reshape(HS1r, (M, M)) 27 | HS1[:, :, 1] = np.reshape(HS1g, (M, M)) 28 | HS1[:, :, 2] = np.reshape(HS1b, (M, M)) 29 | Tex1 = np.reshape(tex1, (M, M, 3)) * HS1 30 | 31 | IRen0 = Tex1 32 | Shd = (200 / 255.0) * HS1 # 200 is added instead of 255 so that not to scale the shading to all white 33 | Ishd0 = Shd 34 | return [IRen0, Ishd0] 35 | 36 | 37 | def lambertian_attenuation(n): 38 | # a = [.8862; 1.0233; .4954]; 39 | a = [np.pi * i for i in [1.0, 2 / 3.0, .25]] 40 | if n > 3: 41 | sys.stderr.write('don\'t record more than 3 attenuation') 42 | exit(-1) 43 | o = a[0:n] 44 | return o 45 | 46 | 47 | def normal_harmonics(N, att): 48 | """ 49 | Return the harmonics evaluated at surface normals N, attenuated by att. 50 | :param N: 51 | :param att: 52 | :return: 53 | 54 | Normals can be scaled surface normals, in which case value of each 55 | harmonic at each point is scaled by albedo. 56 | Harmonics written as polynomials 57 | 0,0 1/sqrt(4*pi) 58 | 1,0 z*sqrt(3/(4*pi)) 59 | 1,1e x*sqrt(3/(4*pi)) 60 | 1,1o y*sqrt(3/(4*pi)) 61 | 2,0 (2*z.^2 - x.^2 - y.^2)/2 * sqrt(5/(4*pi)) 62 | 2,1e x*z * 3*sqrt(5/(12*pi)) 63 | 2,1o y*z * 3*sqrt(5/(12*pi)) 64 | 2,2e (x.^2-y.^2) * 3*sqrt(5/(48*pi)) 65 | 2,2o x*y * 3*sqrt(5/(12*pi)) 66 | """ 67 | xs = N[0, :].T 68 | ys = N[1, :].T 69 | zs = N[2, :].T 70 | a = np.sqrt(xs ** 2 + ys ** 2 + zs ** 2) 71 | denom = (a == 0) + a 72 | # %x = xs./a; y = ys./a; z = zs./a; 73 | x = xs / denom 74 | y = ys / denom 75 | z = zs / denom 76 | 77 | x2 = x * x 78 | y2 = y * y 79 | z2 = z * z 80 | xy = x * y 81 | xz = x * z 82 | yz = y * z 83 | 84 | H1 = att[0] * (1 / np.sqrt(4 * np.pi)) * a 85 | H2 = att[1] * (np.sqrt(3 / (4 * np.pi))) * zs 86 | H3 = att[1] * (np.sqrt(3 / (4 * np.pi))) * xs 87 | H4 = att[1] * (np.sqrt(3 / (4 * np.pi))) * ys 88 | H5 = att[2] * (1 / 2.0) * (np.sqrt(5 / (4 * np.pi))) * ((2 * z2 - x2 - y2) * a) 89 | H6 = att[2] * (3 * np.sqrt(5 / (12 * np.pi))) * (xz * a) 90 | H7 = att[2] * (3 * np.sqrt(5 / (12 * np.pi))) * (yz * a) 91 | H8 = att[2] * (3 * np.sqrt(5 / (48 * np.pi))) * ((x2 - y2) * a) 92 | H9 = att[2] * (3 * np.sqrt(5 / (12 * np.pi))) * (xy * a) 93 | H = [H1, H2, H3, H4, H5, H6, H7, H8, H9] 94 | 95 | # --------add by wang ----------- 96 | H = [np.expand_dims(h, axis=1) for h in H] 97 | H = np.concatenate(H, -1) 98 | # -------------end--------------- 99 | return H 100 | 101 | 102 | def create_mask_fiducial(fiducials, Image): 103 | """ 104 | create mask use fiducials of Image 105 | :param fiducials: the 68 landmarks detected using dlib 106 | :type fiducials np.ndarray 107 | :param Image: a 3-channel image 108 | :type Image np.ndarray 109 | :return: 110 | """ 111 | # fiducals is 2x68 112 | fiducials = np.float32(fiducials) 113 | border_fid = fiducials[:, 0:17] 114 | face_fid = fiducials[:, 17:] 115 | 116 | c1 = np.array([border_fid[0, 0], face_fid[1, 2]]) # left 117 | c2 = np.array([border_fid[0, 16], face_fid[1, 7]]) # right 118 | eye = np.linalg.norm(face_fid[:, 22] - face_fid[:, 25]) 119 | c3 = face_fid[:, 2] 120 | c3[1] = c3[1] - 0.3 * eye 121 | c4 = face_fid[:, 7] 122 | c4[1] = c4[1] - 0.3 * eye 123 | 124 | border = [c1, border_fid, c2, c4, c3] 125 | border = [item.reshape(2, -1) for item in border] 126 | border = np.hstack(border) 127 | 128 | M = Image.shape[0] # row -> y 129 | N = Image.shape[1] # col -> x 130 | 131 | y = np.arange(0, M, step=1, dtype=np.float32) 132 | x = np.arange(0, N, step=1, dtype=np.float32) 133 | X, Y = np.meshgrid(x, y) 134 | 135 | _in, _on = inpolygon(X, Y, border[0, :].T, border[1, :].T) 136 | 137 | mask = np.round(np.reshape(_in | _on, [M, N])) 138 | mask = 255 * np.uint8(mask) 139 | mask = np.repeat(np.expand_dims(mask, -1), 3, axis=-1) 140 | return mask 141 | 142 | 143 | def inpolygon(xq, yq, xv, yv): 144 | """ 145 | reimplement inpolygon in matlab 146 | :type xq: np.ndarray 147 | :type yq: np.ndarray 148 | :type xv: np.ndarray 149 | :type yv: np.ndarray 150 | """ 151 | # http://blog.sina.com.cn/s/blog_70012f010102xnel.html 152 | # merge xy and yv into vertices 153 | vertices = np.vstack((xv, yv)).T 154 | # define a Path object 155 | path = Path(vertices) 156 | # merge X and Y into test_points 157 | test_points = np.hstack([xq.reshape(xq.size, -1), yq.reshape(yq.size, -1)]) 158 | # get mask of test_points in path 159 | _in = path.contains_points(test_points) 160 | # get mask of test_points in path(include the points on path) 161 | _in_on = path.contains_points(test_points, radius=-1e-10) 162 | # get the points on path 163 | _on = _in ^ _in_on 164 | return _in_on, _on 165 | 166 | -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/ddfa_mobilenet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | 4 | from __future__ import division 5 | 6 | """ 7 | Creates a MobileNet Model as defined in: 8 | Andrew G. Howard Menglong Zhu Bo Chen, et.al. (2017). 9 | MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications. 10 | Copyright (c) Yang Lu, 2017 11 | 12 | Modified By cleardusk 13 | """ 14 | import math 15 | import torch.nn as nn 16 | 17 | __all__ = ['mobilenet_2', 'mobilenet_1', 'mobilenet_075', 'mobilenet_05', 'mobilenet_025'] 18 | 19 | 20 | class DepthWiseBlock(nn.Module): 21 | def __init__(self, inplanes, planes, stride=1, prelu=False): 22 | super(DepthWiseBlock, self).__init__() 23 | inplanes, planes = int(inplanes), int(planes) 24 | self.conv_dw = nn.Conv2d(inplanes, inplanes, kernel_size=3, padding=1, stride=stride, groups=inplanes, 25 | bias=False) 26 | self.bn_dw = nn.BatchNorm2d(inplanes) 27 | self.conv_sep = nn.Conv2d(inplanes, planes, kernel_size=1, stride=1, padding=0, bias=False) 28 | self.bn_sep = nn.BatchNorm2d(planes) 29 | if prelu: 30 | self.relu = nn.PReLU() 31 | else: 32 | self.relu = nn.ReLU(inplace=True) 33 | 34 | def forward(self, x): 35 | out = self.conv_dw(x) 36 | out = self.bn_dw(out) 37 | out = self.relu(out) 38 | 39 | out = self.conv_sep(out) 40 | out = self.bn_sep(out) 41 | out = self.relu(out) 42 | 43 | return out 44 | 45 | 46 | class MobileNet(nn.Module): 47 | def __init__(self, widen_factor=1.0, num_classes=1000, prelu=False, input_channel=3): 48 | """ Constructor 49 | Args: 50 | widen_factor: config of widen_factor 51 | num_classes: number of classes 52 | """ 53 | super(MobileNet, self).__init__() 54 | 55 | block = DepthWiseBlock 56 | self.conv1 = nn.Conv2d(input_channel, int(32 * widen_factor), kernel_size=3, stride=2, padding=1, 57 | bias=False) 58 | 59 | self.bn1 = nn.BatchNorm2d(int(32 * widen_factor)) 60 | if prelu: 61 | self.relu = nn.PReLU() 62 | else: 63 | self.relu = nn.ReLU(inplace=True) 64 | 65 | self.dw2_1 = block(32 * widen_factor, 64 * widen_factor, prelu=prelu) 66 | self.dw2_2 = block(64 * widen_factor, 128 * widen_factor, stride=2, prelu=prelu) 67 | 68 | self.dw3_1 = block(128 * widen_factor, 128 * widen_factor, prelu=prelu) 69 | self.dw3_2 = block(128 * widen_factor, 256 * widen_factor, stride=2, prelu=prelu) 70 | 71 | self.dw4_1 = block(256 * widen_factor, 256 * widen_factor, prelu=prelu) 72 | self.dw4_2 = block(256 * widen_factor, 512 * widen_factor, stride=2, prelu=prelu) 73 | 74 | self.dw5_1 = block(512 * widen_factor, 512 * widen_factor, prelu=prelu) 75 | self.dw5_2 = block(512 * widen_factor, 512 * widen_factor, prelu=prelu) 76 | self.dw5_3 = block(512 * widen_factor, 512 * widen_factor, prelu=prelu) 77 | self.dw5_4 = block(512 * widen_factor, 512 * widen_factor, prelu=prelu) 78 | self.dw5_5 = block(512 * widen_factor, 512 * widen_factor, prelu=prelu) 79 | self.dw5_6 = block(512 * widen_factor, 1024 * widen_factor, stride=2, prelu=prelu) 80 | 81 | self.dw6 = block(1024 * widen_factor, 1024 * widen_factor, prelu=prelu) 82 | 83 | self.avgpool = nn.AdaptiveAvgPool2d(1) 84 | self.fc = nn.Linear(int(1024 * widen_factor), num_classes) 85 | 86 | for m in self.modules(): 87 | if isinstance(m, nn.Conv2d): 88 | n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels 89 | m.weight.data.normal_(0, math.sqrt(2. / n)) 90 | elif isinstance(m, nn.BatchNorm2d): 91 | m.weight.data.fill_(1) 92 | m.bias.data.zero_() 93 | 94 | def forward(self, x): 95 | x = self.conv1(x) 96 | x = self.bn1(x) 97 | x = self.relu(x) 98 | 99 | x = self.dw2_1(x) 100 | x = self.dw2_2(x) 101 | x = self.dw3_1(x) 102 | x = self.dw3_2(x) 103 | x = self.dw4_1(x) 104 | x = self.dw4_2(x) 105 | x = self.dw5_1(x) 106 | x = self.dw5_2(x) 107 | x = self.dw5_3(x) 108 | x = self.dw5_4(x) 109 | x = self.dw5_5(x) 110 | x = self.dw5_6(x) 111 | x = self.dw6(x) 112 | 113 | x = self.avgpool(x) 114 | x = x.view(x.size(0), -1) 115 | x = self.fc(x) 116 | 117 | return x 118 | 119 | 120 | def mobilenet(widen_factor=1.0, num_classes=1000): 121 | """ 122 | Construct MobileNet. 123 | widen_factor=1.0 for mobilenet_1 124 | widen_factor=0.75 for mobilenet_075 125 | widen_factor=0.5 for mobilenet_05 126 | widen_factor=0.25 for mobilenet_025 127 | """ 128 | model = MobileNet(widen_factor=widen_factor, num_classes=num_classes) 129 | return model 130 | 131 | 132 | def mobilenet_2(num_classes=62, input_channel=3): 133 | model = MobileNet(widen_factor=2.0, num_classes=num_classes, input_channel=input_channel) 134 | return model 135 | 136 | 137 | def mobilenet_1(num_classes=62, input_channel=3): 138 | model = MobileNet(widen_factor=1.0, num_classes=num_classes, input_channel=input_channel) 139 | return model 140 | 141 | 142 | def mobilenet_075(num_classes=62, input_channel=3): 143 | model = MobileNet(widen_factor=0.75, num_classes=num_classes, input_channel=input_channel) 144 | return model 145 | 146 | 147 | def mobilenet_05(num_classes=62, input_channel=3): 148 | model = MobileNet(widen_factor=0.5, num_classes=num_classes, input_channel=input_channel) 149 | return model 150 | 151 | 152 | def mobilenet_025(num_classes=62, input_channel=3): 153 | model = MobileNet(widen_factor=0.25, num_classes=num_classes, input_channel=input_channel) 154 | return model 155 | -------------------------------------------------------------------------------- /VideoFace3D/segmentation/segment.py: -------------------------------------------------------------------------------- 1 | from VideoFace3D.segmentation.faceparsing.model import BiSeNet 2 | import torch 3 | from VideoFace3D.utils.Global import BISENET_MODEL_PATH 4 | import torchvision.transforms as transforms 5 | from PIL import Image 6 | import numpy as np 7 | import cv2 8 | 9 | class FaceSegmentation(): 10 | def __init__(self, cuda=True): 11 | self.device = torch.device("cuda") if cuda and torch.cuda.is_available() else torch.device("cpu") 12 | self.model = BiSeNet(n_classes=19) 13 | self.model.load_state_dict(torch.load(BISENET_MODEL_PATH)) 14 | self.model = self.model.to(self.device) 15 | self.model.eval() 16 | self.to_tensor = transforms.Compose([ 17 | transforms.ToTensor(), 18 | transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)), 19 | ]) 20 | self.stride = 1 21 | 22 | self.bg = 0 23 | self.skin = 1 24 | self.l_brow = 2 25 | self.r_brow = 3 26 | self.l_eye = 4 27 | self.r_eye = 5 28 | self.eye_g = 6 29 | self.l_ear = 7 30 | self.r_ear = 8 31 | self.ear_r = 9 32 | self.nose = 10 33 | self.mouth = 11 34 | self.u_lip = 12 35 | self.l_lip = 13 36 | self.neck = 14 37 | self.neck_l = 15 38 | self.cloth = 16 39 | self.hair = 17 40 | self.hat = 18 41 | 42 | self.skins = [self.skin] 43 | self.eyes = [self.l_brow, self.r_brow, self.l_eye, self.r_eye, self.eye_g] 44 | self.noses = [self.nose] 45 | self.mouths = [self.mouth, self.u_lip, self.l_lip] 46 | self.ears = [self.l_ear, self.r_ear, self.ear_r] 47 | self.necks = [self.neck, self.neck_l] 48 | self.cloths = [self.cloth] 49 | self.hairs = [self.hair] 50 | self.hats = [self.hat] 51 | 52 | def create_face_mask(self, image_path, 53 | skin=True, 54 | eye=True, 55 | nose=True, 56 | mouth=True, 57 | ear=False, 58 | neck=False, 59 | cloth=False, 60 | hair=False, 61 | hat=False 62 | ): 63 | ''' 64 | 65 | :return: mask, mask_probability 66 | ''' 67 | img = Image.open(image_path) 68 | org_w, org_h = img.size 69 | image = img.resize((512, 512), Image.BILINEAR) 70 | img = self.to_tensor(image) 71 | img = torch.unsqueeze(img, 0) 72 | img = img.to(self.device) 73 | out = self.model(img)[0].squeeze(0).cpu().detach().numpy() 74 | 75 | parsing_anno = out.argmax(0) 76 | 77 | vis_parsing_anno = parsing_anno.copy().astype(np.uint8) 78 | vis_parsing_anno = cv2.resize(vis_parsing_anno, (org_h, org_w), fx=self.stride, fy=self.stride, interpolation=cv2.INTER_NEAREST) 79 | org_parsing = vis_parsing_anno.copy() 80 | mask_prob = out - out.min(0) 81 | mask_prob_exp = np.exp(mask_prob) 82 | mask_prob = mask_prob_exp / mask_prob_exp.sum(0) 83 | mask_prob = mask_prob.max(0) 84 | 85 | mask_prob = cv2.resize(mask_prob, (org_h, org_w), fx=self.stride, fy=self.stride) 86 | 87 | mask = np.zeros((org_w, org_h)) 88 | 89 | if skin: 90 | for p in self.skins: 91 | mask += vis_parsing_anno == p 92 | if eye: 93 | for p in self.eyes: 94 | mask += vis_parsing_anno == p 95 | if nose: 96 | for p in self.noses: 97 | mask += vis_parsing_anno == p 98 | if mouth: 99 | for p in self.mouths: 100 | mask += vis_parsing_anno == p 101 | if ear: 102 | for p in self.ears: 103 | mask += vis_parsing_anno == p 104 | if neck: 105 | for p in self.necks: 106 | mask += vis_parsing_anno == p 107 | if cloth: 108 | for p in self.cloths: 109 | mask += vis_parsing_anno == p 110 | if hair: 111 | for p in self.hairs: 112 | mask += vis_parsing_anno == p 113 | if hat: 114 | for p in self.hats: 115 | mask += vis_parsing_anno == p 116 | 117 | return org_parsing, mask, mask_prob * mask 118 | 119 | def visualize(self, parsing_anno, image_path): 120 | stride = 1 121 | part_colors = [[255, 0, 0], [255, 85, 0], [255, 170, 0], 122 | [255, 0, 85], [255, 0, 170], 123 | [0, 255, 0], [85, 255, 0], [170, 255, 0], 124 | [0, 255, 85], [0, 255, 170], 125 | [0, 0, 255], [85, 0, 255], [170, 0, 255], 126 | [0, 85, 255], [0, 170, 255], 127 | [255, 255, 0], [255, 255, 85], [255, 255, 170], 128 | [255, 0, 255], [255, 85, 255], [255, 170, 255], 129 | [0, 255, 255], [85, 255, 255], [170, 255, 255]] 130 | 131 | 132 | im = cv2.imread(image_path) 133 | vis_im = im.copy().astype(np.uint8) 134 | vis_parsing_anno = parsing_anno.copy().astype(np.uint8) 135 | vis_parsing_anno = cv2.resize(vis_parsing_anno, None, fx=stride, fy=stride, interpolation=cv2.INTER_NEAREST) 136 | vis_parsing_anno_color = np.zeros((vis_parsing_anno.shape[0], vis_parsing_anno.shape[1], 3)) + 255 137 | 138 | num_of_class = np.max(vis_parsing_anno) 139 | 140 | for pi in range(1, num_of_class + 1): 141 | index = np.where(vis_parsing_anno == pi) 142 | vis_parsing_anno_color[index[0], index[1], :] = part_colors[pi] 143 | 144 | vis_parsing_anno_color = vis_parsing_anno_color.astype(np.uint8) 145 | # print(vis_parsing_anno_color.shape, vis_im.shape) 146 | vis_im = cv2.addWeighted(cv2.cvtColor(vis_im, cv2.COLOR_RGB2BGR), 0.4, vis_parsing_anno_color, 0.6, 0) 147 | return vis_im -------------------------------------------------------------------------------- /VideoFace3D/face_track/tracker.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | from time import time 4 | import sys 5 | 6 | from VideoFace3D.face_track.align import detect_face 7 | import cv2 8 | import numpy as np 9 | import tensorflow as tf 10 | from VideoFace3D.face_track.lib.face_utils import judge_side_face 11 | from VideoFace3D.face_track.lib.utils import Logger, mkdir 12 | from VideoFace3D.utils.Global import project_dir 13 | from VideoFace3D.face_track.src.sort import Sort 14 | from VideoFace3D.utils.video_utils import progressbar 15 | import copy 16 | 17 | import warnings 18 | warnings.filterwarnings("ignore") 19 | 20 | class FaceTracker(): 21 | def __init__(self, scale_rate=1.0, detect_interval=1, face_score_threshold=0.85, margin=15, echo=False): 22 | self.scale_rate = scale_rate 23 | self.detect_interval = detect_interval 24 | self.face_score_threshold = face_score_threshold 25 | self.margin = margin 26 | 27 | self.tracker = Sort() 28 | self.minsize = 40 # minimum size of face for mtcnn to detect 29 | self.threshold = [0.6, 0.7, 0.7] # three steps's threshold 30 | self.factor = 0.709 # scale factor 31 | 32 | self.echo = echo 33 | 34 | def start_track(self, video_path): 35 | with tf.Graph().as_default(): 36 | with tf.Session(config=tf.ConfigProto(gpu_options=tf.GPUOptions(allow_growth=True), 37 | log_device_placement=False)) as sess: 38 | self.pnet, self.rnet, self.onet = detect_face.create_mtcnn(sess, os.path.join(project_dir, 39 | "../face_track/align")) 40 | 41 | cam = cv2.VideoCapture(video_path) 42 | frame_numbers = int(cam.get(cv2.CAP_PROP_FRAME_COUNT)) 43 | ccount = 0 44 | c = 0 45 | all_result = [] 46 | while True: 47 | ccount += 1 48 | final_faces = [] 49 | addtional_attribute_list = [] 50 | ret, frame = cam.read() 51 | if not ret: 52 | break 53 | 54 | frame = cv2.resize(frame, (0, 0), fx=self.scale_rate, fy=self.scale_rate) 55 | r_g_b_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 56 | if c % self.detect_interval == 0: 57 | img_size = np.asarray(frame.shape)[0:2] 58 | mtcnn_starttime = time() 59 | faces, points = detect_face.detect_face(r_g_b_frame, self.minsize, self.pnet, self.rnet, self.onet, 60 | self.threshold, 61 | self.factor) 62 | face_sums = faces.shape[0] 63 | if face_sums > 0: 64 | face_list = [] 65 | for i, item in enumerate(faces): 66 | score = round(faces[i, 4], 6) 67 | if score > self.face_score_threshold: 68 | det = np.squeeze(faces[i, 0:4]) 69 | 70 | # face rectangle 71 | det[0] = np.maximum(det[0] - self.margin, 0) 72 | det[1] = np.maximum(det[1] - self.margin, 0) 73 | det[2] = np.minimum(det[2] + self.margin, img_size[1]) 74 | det[3] = np.minimum(det[3] + self.margin, img_size[0]) 75 | face_list.append(item) 76 | 77 | # face cropped 78 | bb = np.array(det, dtype=np.int32) 79 | 80 | # use 5 face landmarks to judge the face is front or side 81 | squeeze_points = np.squeeze(points[:, i]) 82 | tolist = squeeze_points.tolist() 83 | facial_landmarks = [] 84 | for j in range(5): 85 | item = [tolist[j], tolist[(j + 5)]] 86 | facial_landmarks.append(item) 87 | 88 | cropped = frame[bb[1]:bb[3], bb[0]:bb[2], :].copy() 89 | 90 | dist_rate, high_ratio_variance, width_rate = judge_side_face( 91 | np.array(facial_landmarks)) 92 | 93 | # face addtional attribute(index 0:face score; index 1:0 represents front face and 1 for side face ) 94 | item_list = [cropped, score, dist_rate, high_ratio_variance, width_rate] 95 | addtional_attribute_list.append(item_list) 96 | 97 | final_faces = np.array(face_list) 98 | 99 | trackers = self.tracker.update(final_faces, img_size, None, addtional_attribute_list, self.detect_interval) 100 | 101 | people = [] 102 | for d in trackers: 103 | det = np.array([0,0,0,0]) 104 | d = d.astype(np.int32) 105 | det[0] = np.maximum(d[0] - self.margin, 0) 106 | det[1] = np.maximum(d[1] - self.margin, 0) 107 | det[2] = np.minimum(d[2] + self.margin, img_size[1]) 108 | det[3] = np.minimum(d[3] + self.margin, img_size[0]) 109 | bb = np.array(det, dtype=np.int32) 110 | people.append((bb, d[4])) 111 | 112 | all_result.append((frame, people)) 113 | 114 | if self.echo: 115 | progressbar(ccount, frame_numbers, prefix="tracking...") 116 | 117 | return all_result 118 | -------------------------------------------------------------------------------- /VideoFace3D/landmark_detect/ddfa_inference.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | __author__ = 'cleardusk' 4 | 5 | import numpy as np 6 | from math import sqrt 7 | import scipy.io as sio 8 | import matplotlib.pyplot as plt 9 | from VideoFace3D.landmark_detect.ddfa_ddfa import reconstruct_vertex 10 | 11 | 12 | def get_suffix(filename): 13 | """a.jpg -> jpg""" 14 | pos = filename.rfind('.') 15 | if pos == -1: 16 | return '' 17 | return filename[pos:] 18 | 19 | 20 | def crop_img(img, roi_box): 21 | h, w = img.shape[:2] 22 | 23 | sx, sy, ex, ey = [int(round(_)) for _ in roi_box] 24 | dh, dw = ey - sy, ex - sx 25 | if len(img.shape) == 3: 26 | res = np.zeros((dh, dw, 3), dtype=np.uint8) 27 | else: 28 | res = np.zeros((dh, dw), dtype=np.uint8) 29 | if sx < 0: 30 | sx, dsx = 0, -sx 31 | else: 32 | dsx = 0 33 | 34 | if ex > w: 35 | ex, dex = w, dw - (ex - w) 36 | else: 37 | dex = dw 38 | 39 | if sy < 0: 40 | sy, dsy = 0, -sy 41 | else: 42 | dsy = 0 43 | 44 | if ey > h: 45 | ey, dey = h, dh - (ey - h) 46 | else: 47 | dey = dh 48 | 49 | res[dsy:dey, dsx:dex] = img[sy:ey, sx:ex] 50 | return res 51 | 52 | 53 | def calc_hypotenuse(pts): 54 | bbox = [min(pts[0, :]), min(pts[1, :]), max(pts[0, :]), max(pts[1, :])] 55 | center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2] 56 | radius = max(bbox[2] - bbox[0], bbox[3] - bbox[1]) / 2 57 | bbox = [center[0] - radius, center[1] - radius, center[0] + radius, center[1] + radius] 58 | llength = sqrt((bbox[2] - bbox[0]) ** 2 + (bbox[3] - bbox[1]) ** 2) 59 | return llength / 3 60 | 61 | 62 | def parse_roi_box_from_landmark(pts): 63 | """calc roi box from landmark""" 64 | bbox = [min(pts[0, :]), min(pts[1, :]), max(pts[0, :]), max(pts[1, :])] 65 | center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2] 66 | radius = max(bbox[2] - bbox[0], bbox[3] - bbox[1]) / 2 67 | bbox = [center[0] - radius, center[1] - radius, center[0] + radius, center[1] + radius] 68 | 69 | llength = sqrt((bbox[2] - bbox[0]) ** 2 + (bbox[3] - bbox[1]) ** 2) 70 | center_x = (bbox[2] + bbox[0]) / 2 71 | center_y = (bbox[3] + bbox[1]) / 2 72 | 73 | roi_box = [0] * 4 74 | roi_box[0] = center_x - llength / 2 75 | roi_box[1] = center_y - llength / 2 76 | roi_box[2] = roi_box[0] + llength 77 | roi_box[3] = roi_box[1] + llength 78 | 79 | return roi_box 80 | 81 | 82 | def parse_roi_box_from_bbox(bbox): 83 | left, top, right, bottom = bbox 84 | old_size = (right - left + bottom - top) / 2 85 | center_x = right - (right - left) / 2.0 86 | center_y = bottom - (bottom - top) / 2.0 + old_size * 0.14 87 | size = int(old_size * 1.58) 88 | roi_box = [0] * 4 89 | roi_box[0] = center_x - size / 2 90 | roi_box[1] = center_y - size / 2 91 | roi_box[2] = roi_box[0] + size 92 | roi_box[3] = roi_box[1] + size 93 | return roi_box 94 | 95 | 96 | def dump_to_ply(vertex, tri, wfp): 97 | header = """ply 98 | format ascii 1.0 99 | element vertex {} 100 | property float x 101 | property float y 102 | property float z 103 | element face {} 104 | property list uchar int vertex_indices 105 | end_header""" 106 | 107 | n_vertex = vertex.shape[1] 108 | n_face = tri.shape[1] 109 | header = header.format(n_vertex, n_face) 110 | 111 | with open(wfp, 'w') as f: 112 | f.write(header + '\n') 113 | for i in range(n_vertex): 114 | x, y, z = vertex[:, i] 115 | f.write('{:.4f} {:.4f} {:.4f}\n'.format(x, y, z)) 116 | for i in range(n_face): 117 | idx1, idx2, idx3 = tri[:, i] 118 | f.write('3 {} {} {}\n'.format(idx1 - 1, idx2 - 1, idx3 - 1)) 119 | print('Dump tp {}'.format(wfp)) 120 | 121 | 122 | def dump_vertex(vertex, wfp): 123 | sio.savemat(wfp, {'vertex': vertex}) 124 | print('Dump to {}'.format(wfp)) 125 | 126 | 127 | def _predict_vertices(param, roi_bbox, dense, transform=True, rotate=True): 128 | vertex = reconstruct_vertex(param, dense=dense, rotate=rotate) 129 | sx, sy, ex, ey = roi_bbox 130 | scale_x = (ex - sx) / 120 131 | scale_y = (ey - sy) / 120 132 | vertex[0, :] = vertex[0, :] * scale_x + sx 133 | vertex[1, :] = vertex[1, :] * scale_y + sy 134 | 135 | s = (scale_x + scale_y) / 2 136 | vertex[2, :] *= s 137 | 138 | return vertex 139 | 140 | 141 | def predict_68pts(param, roi_box): 142 | return _predict_vertices(param, roi_box, dense=False) 143 | 144 | 145 | def predict_dense(param, roi_box, rotate=True): 146 | return _predict_vertices(param, roi_box, dense=True, rotate=rotate) 147 | 148 | 149 | def draw_landmarks(img, pts, style='fancy', wfp=None, show_flg=False, **kwargs): 150 | """Draw landmarks using matplotlib""" 151 | height, width = img.shape[:2] 152 | plt.figure(figsize=(12, height / width * 12)) 153 | plt.imshow(img[:, :, ::-1]) 154 | plt.subplots_adjust(left=0, right=1, top=1, bottom=0) 155 | plt.axis('off') 156 | 157 | if not type(pts) in [tuple, list]: 158 | pts = [pts] 159 | for i in range(len(pts)): 160 | if style == 'simple': 161 | plt.plot(pts[i][0, :], pts[i][1, :], 'o', markersize=4, color='g') 162 | 163 | elif style == 'fancy': 164 | alpha = 0.8 165 | markersize = 4 166 | lw = 1.5 167 | color = kwargs.get('color', 'w') 168 | markeredgecolor = kwargs.get('markeredgecolor', 'black') 169 | 170 | nums = [0, 17, 22, 27, 31, 36, 42, 48, 60, 68] 171 | 172 | # close eyes and mouths 173 | plot_close = lambda i1, i2: plt.plot([pts[i][0, i1], pts[i][0, i2]], [pts[i][1, i1], pts[i][1, i2]], 174 | color=color, lw=lw, alpha=alpha - 0.1) 175 | plot_close(41, 36) 176 | plot_close(47, 42) 177 | plot_close(59, 48) 178 | plot_close(67, 60) 179 | 180 | for ind in range(len(nums) - 1): 181 | l, r = nums[ind], nums[ind + 1] 182 | plt.plot(pts[i][0, l:r], pts[i][1, l:r], color=color, lw=lw, alpha=alpha - 0.1) 183 | 184 | plt.plot(pts[i][0, l:r], pts[i][1, l:r], marker='o', linestyle='None', markersize=markersize, 185 | color=color, 186 | markeredgecolor=markeredgecolor, alpha=alpha) 187 | 188 | if wfp is not None: 189 | plt.savefig(wfp, dpi=200) 190 | print('Save visualization result to {}'.format(wfp)) 191 | if show_flg: 192 | plt.show() 193 | 194 | 195 | def get_colors(image, vertices): 196 | [h, w, _] = image.shape 197 | vertices[0, :] = np.minimum(np.maximum(vertices[0, :], 0), w - 1) # x 198 | vertices[1, :] = np.minimum(np.maximum(vertices[1, :], 0), h - 1) # y 199 | ind = np.round(vertices).astype(np.int32) 200 | colors = image[ind[1, :], ind[0, :], :] # n x 3 201 | 202 | return colors 203 | 204 | 205 | def write_obj_with_colors(obj_name, vertices, triangles, colors): 206 | triangles = triangles.copy() # meshlab start with 1 207 | 208 | if obj_name.split('.')[-1] != 'obj': 209 | obj_name = obj_name + '.obj' 210 | 211 | # write obj 212 | with open(obj_name, 'w') as f: 213 | # write vertices & colors 214 | for i in range(vertices.shape[1]): 215 | s = 'v {:.4f} {:.4f} {:.4f} {} {} {}\n'.format(vertices[1, i], vertices[0, i], vertices[2, i], colors[i, 2], 216 | colors[i, 1], colors[i, 0]) 217 | f.write(s) 218 | 219 | # write f: ver ind/ uv ind 220 | for i in range(triangles.shape[1]): 221 | s = 'f {} {} {}\n'.format(triangles[0, i], triangles[1, i], triangles[2, i]) 222 | f.write(s) 223 | 224 | 225 | def main(): 226 | pass 227 | 228 | 229 | if __name__ == '__main__': 230 | main() 231 | -------------------------------------------------------------------------------- /VideoFace3D/SFS/SFSNet/model.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import absolute_import, division, print_function 3 | import torch 4 | import torchvision 5 | import pickle as pkl 6 | from torch import nn 7 | import torch.nn.functional as F 8 | 9 | 10 | class ResidualBlock(nn.Module): 11 | def __init__(self, in_channel, out_channel): 12 | super(ResidualBlock, self).__init__() 13 | # nbn1/nbn2/.../nbn5 abn1/abn2/.../abn5 14 | self.bn = nn.BatchNorm2d(in_channel) 15 | # nconv1/nconv2/.../nconv5 aconv1/aconv2/.../aconv5 16 | self.conv = nn.Conv2d(in_channel, out_channel, kernel_size=3, stride=1, padding=1) 17 | # nbn1r/nbn2r/.../nbn5r abn1r/abn2r/.../abn5r 18 | self.bnr = nn.BatchNorm2d(out_channel) 19 | # nconv1r/nconv2r/.../nconv5r aconv1r/aconv2r/.../anconv5r 20 | self.convr = nn.Conv2d(out_channel, out_channel, kernel_size=3, stride=1, padding=1) 21 | 22 | def forward(self, x): 23 | out = self.conv(F.relu(self.bn(x))) 24 | out = self.convr(F.relu(self.bnr(out))) 25 | out += x 26 | return out 27 | 28 | 29 | class SfSNet(nn.Module): # SfSNet = PS-Net in SfSNet_deploy.prototxt 30 | def __init__(self): 31 | # C64 32 | super(SfSNet, self).__init__() 33 | # TODO 初始化器 xavier 34 | self.conv1 = nn.Conv2d(3, 64, 7, 1, 3) 35 | self.bn1 = nn.BatchNorm2d(64) 36 | # C128 37 | self.conv2 = nn.Conv2d(64, 128, 3, 1, 1) 38 | self.bn2 = nn.BatchNorm2d(128) 39 | # C128 S2 40 | self.conv3 = nn.Conv2d(128, 128, 3, 2, 1) 41 | # ------------RESNET for normals------------ 42 | # RES1 43 | self.n_res1 = ResidualBlock(128, 128) 44 | # RES2 45 | self.n_res2 = ResidualBlock(128, 128) 46 | # RES3 47 | self.n_res3 = ResidualBlock(128, 128) 48 | # RES4 49 | self.n_res4 = ResidualBlock(128, 128) 50 | # RES5 51 | self.n_res5 = ResidualBlock(128, 128) 52 | # nbn6r 53 | self.nbn6r = nn.BatchNorm2d(128) 54 | # CD128 55 | # TODO 初始化器 bilinear 56 | self.nup6 = nn.ConvTranspose2d(128, 128, 4, 2, 1, groups=128, bias=False) 57 | # nconv6 58 | self.nconv6 = nn.Conv2d(128, 128, 1, 1, 0) 59 | # nbn6 60 | self.nbn6 = nn.BatchNorm2d(128) 61 | # CD 64 62 | self.nconv7 = nn.Conv2d(128, 64, 3, 1, 1) 63 | # nbn7 64 | self.nbn7 = nn.BatchNorm2d(64) 65 | # C*3 66 | self.Nconv0 = nn.Conv2d(64, 3, 1, 1, 0) 67 | 68 | # --------------------Albedo--------------- 69 | # RES1 70 | self.a_res1 = ResidualBlock(128, 128) 71 | # RES2 72 | self.a_res2 = ResidualBlock(128, 128) 73 | # RES3 74 | self.a_res3 = ResidualBlock(128, 128) 75 | # RES4 76 | self.a_res4 = ResidualBlock(128, 128) 77 | # RES5 78 | self.a_res5 = ResidualBlock(128, 128) 79 | # abn6r 80 | self.abn6r = nn.BatchNorm2d(128) 81 | # CD128 82 | self.aup6 = nn.ConvTranspose2d(128, 128, 4, 2, 1, groups=128, bias=False) 83 | # nconv6 84 | self.aconv6 = nn.Conv2d(128, 128, 1, 1, 0) 85 | # nbn6 86 | self.abn6 = nn.BatchNorm2d(128) 87 | # CD 64 88 | self.aconv7 = nn.Conv2d(128, 64, 3, 1, 1) 89 | # nbn7 90 | self.abn7 = nn.BatchNorm2d(64) 91 | # C*3 92 | self.Aconv0 = nn.Conv2d(64, 3, 1, 1, 0) 93 | 94 | # ---------------Light------------------ 95 | # lconv1 96 | self.lconv1 = nn.Conv2d(384, 128, 1, 1, 0) 97 | # lbn1 98 | self.lbn1 = nn.BatchNorm2d(128) 99 | # lpool2r 100 | self.lpool2r = nn.AvgPool2d(64) 101 | # fc_light 102 | self.fc_light = nn.Linear(128, 27) 103 | 104 | def forward(self, inputs): 105 | # C64 106 | x = F.relu(self.bn1(self.conv1(inputs))) 107 | # C128 108 | x = F.relu(self.bn2(self.conv2(x))) 109 | # C128 S2 110 | conv3 = self.conv3(x) 111 | # ------------RESNET for normals------------ 112 | # RES1 113 | x = self.n_res1(conv3) 114 | # RES2 115 | x = self.n_res2(x) 116 | # RES3 117 | x = self.n_res3(x) 118 | # RES4 119 | x = self.n_res4(x) 120 | # RES5 121 | nsum5 = self.n_res5(x) 122 | # nbn6r 123 | nrelu6r = F.relu(self.nbn6r(nsum5)) 124 | # CD128 125 | x = self.nup6(nrelu6r) 126 | # nconv6/nbn6/nrelu6 127 | x = F.relu(self.nbn6(self.nconv6(x))) 128 | # nconv7/nbn7/nrelu7 129 | x = F.relu(self.nbn7(self.nconv7(x))) 130 | # nconv0 131 | normal = self.Nconv0(x) 132 | # --------------------Albedo--------------- 133 | # RES1 134 | x = self.a_res1(conv3) 135 | # RES2 136 | x = self.a_res2(x) 137 | # RES3 138 | x = self.a_res3(x) 139 | # RES4 140 | x = self.a_res4(x) 141 | # RES5 142 | asum5 = self.a_res5(x) 143 | # nbn6r 144 | arelu6r = F.relu(self.abn6r(asum5)) 145 | # CD128 146 | x = self.aup6(arelu6r) 147 | # nconv6/nbn6/nrelu6 148 | x = F.relu(self.abn6(self.aconv6(x))) 149 | # nconv7/nbn7/nrelu7 150 | x = F.relu(self.abn7(self.aconv7(x))) 151 | # nconv0 152 | albedo = self.Aconv0(x) 153 | # ---------------Light------------------ 154 | # lconcat1, shape(1 256 64 64) 155 | x = torch.cat((nrelu6r, arelu6r), 1) 156 | # lconcat2, shape(1 384 64 64) 157 | x = torch.cat([x, conv3], 1) 158 | # lconv1/lbn1/lrelu1 shape(1 128 64 64) 159 | x = F.relu(self.lbn1(self.lconv1(x))) 160 | # lpool2r, shape(1 128 1 1) 161 | x = self.lpool2r(x) 162 | x = x.view(-1, 128) 163 | # fc_light 164 | light = self.fc_light(x) 165 | 166 | return normal, albedo, light 167 | 168 | def load_weights_from_pkl(self, weights_pkl): 169 | from torch import from_numpy 170 | with open(weights_pkl, 'rb') as wp: 171 | try: 172 | # for python3 173 | name_weights = pkl.load(wp, encoding='latin1') 174 | except TypeError as e: 175 | # for python2 176 | name_weights = pkl.load(wp) 177 | state_dict = {} 178 | 179 | def _set_deconv(layer, key): 180 | state_dict[layer+'.weight'] = from_numpy(name_weights[key]['weight']) 181 | 182 | def _set(layer, key): 183 | state_dict[layer + '.weight'] = from_numpy(name_weights[key]['weight']) 184 | state_dict[layer + '.bias'] = from_numpy(name_weights[key]['bias']) 185 | 186 | def _set_bn(layer, key): 187 | state_dict[layer + '.running_var'] = from_numpy(name_weights[key]['running_var']) 188 | state_dict[layer + '.running_mean'] = from_numpy(name_weights[key]['running_mean']) 189 | state_dict[layer + '.weight'] = torch.ones_like(state_dict[layer + '.running_var']) 190 | state_dict[layer + '.bias'] = torch.zeros_like(state_dict[layer + '.running_var']) 191 | 192 | def _set_res(layer, n_or_a, index): 193 | _set_bn(layer+'.bn', n_or_a + 'bn' + str(index)) 194 | _set(layer+'.conv', n_or_a + 'conv' + str(index)) 195 | _set_bn(layer+'.bnr', n_or_a + 'bn' + str(index) + 'r') 196 | _set(layer+'.convr', n_or_a + 'conv' + str(index) + 'r') 197 | 198 | _set('conv1', 'conv1') 199 | _set_bn('bn1', 'bn1') 200 | _set('conv2', 'conv2') 201 | _set_bn('bn2', 'bn2') 202 | _set('conv3', 'conv3') 203 | _set_res('n_res1', 'n', 1) 204 | _set_res('n_res2', 'n', 2) 205 | _set_res('n_res3', 'n', 3) 206 | _set_res('n_res4', 'n', 4) 207 | _set_res('n_res5', 'n', 5) 208 | _set_bn('nbn6r', 'nbn6r') 209 | _set_deconv('nup6', 'nup6') 210 | _set('nconv6', 'nconv6') 211 | _set_bn('nbn6', 'nbn6') 212 | _set('nconv7', 'nconv7') 213 | _set_bn('nbn7', 'nbn7') 214 | _set('Nconv0', 'Nconv0') 215 | _set_res('a_res1', 'a', 1) 216 | _set_res('a_res2', 'a', 2) 217 | _set_res('a_res3', 'a', 3) 218 | _set_res('a_res4', 'a', 4) 219 | _set_res('a_res5', 'a', 5) 220 | _set_bn('abn6r', 'abn6r') 221 | _set_deconv('aup6', 'aup6') 222 | _set('aconv6', 'aconv6') 223 | _set_bn('abn6', 'abn6') 224 | _set('aconv7', 'aconv7') 225 | _set_bn('abn7', 'abn7') 226 | _set('Aconv0', 'Aconv0') 227 | _set('lconv1', 'lconv1') 228 | _set_bn('lbn1', 'lbn1') 229 | _set('fc_light', 'fc_light') 230 | self.load_state_dict(state_dict) 231 | 232 | 233 | if __name__ == '__main__': 234 | net = SfSNet() 235 | net.eval() 236 | 237 | print(len(list(net.named_parameters()))) 238 | for name, param in list(net.named_parameters()): 239 | print(name, param.size()) 240 | -------------------------------------------------------------------------------- /VideoFace3D/utils/geometry.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | from scipy.io import loadmat, savemat 4 | from PIL import Image 5 | from VideoFace3D.utils.Global import * 6 | import neural_renderer as nr 7 | import math 8 | 9 | 10 | def euler2rot(euler_angle): 11 | batch_size = euler_angle.shape[0] 12 | theta = -euler_angle[:, 0].reshape(-1, 1, 1) 13 | phi = -euler_angle[:, 1].reshape(-1, 1, 1) 14 | psi = euler_angle[:, 2].reshape(-1, 1, 1) 15 | one = torch.ones(batch_size, 1, 1).to(euler_angle.device) 16 | zero = torch.zeros(batch_size, 1, 1).to(euler_angle.device) 17 | rot_x = torch.cat(( 18 | torch.cat((one, zero, zero), 1), 19 | torch.cat((zero, theta.cos(), theta.sin()), 1), 20 | torch.cat((zero, -theta.sin(), theta.cos()), 1), 21 | ), 2) 22 | rot_y = torch.cat(( 23 | torch.cat((phi.cos(), zero, -phi.sin()), 1), 24 | torch.cat((zero, one, zero), 1), 25 | torch.cat((phi.sin(), zero, phi.cos()), 1), 26 | ), 2) 27 | rot_z = torch.cat(( 28 | torch.cat((psi.cos(), -psi.sin(), zero), 1), 29 | torch.cat((psi.sin(), psi.cos(), zero), 1), 30 | torch.cat((zero, zero, one), 1) 31 | ), 2) 32 | return torch.bmm(rot_x, torch.bmm(rot_y, rot_z)) 33 | 34 | 35 | def texture_from_point2faces(triangles, texutures): 36 | batch = len(texutures) 37 | tex = nr.vertices_to_faces(texutures, triangles) 38 | tex = torch.Tensor.mean(tex, dim=2) 39 | return tex.reshape((batch, tex.shape[1], 1, 1, 1, 3)) 40 | 41 | 42 | def POS(xp, x): 43 | npts = xp.shape[1] 44 | 45 | A = np.zeros([2 * npts, 8]) 46 | 47 | A[0:2 * npts - 1:2, 0:3] = x.transpose() 48 | A[0:2 * npts - 1:2, 3] = 1 49 | 50 | A[1:2 * npts:2, 4:7] = x.transpose() 51 | A[1:2 * npts:2, 7] = 1 52 | 53 | b = np.reshape(xp.transpose(), [2 * npts, 1]) 54 | 55 | k, _, _, _ = np.linalg.lstsq(A, b) 56 | 57 | R1 = k[0:3] 58 | R2 = k[4:7] 59 | sTx = k[3] 60 | sTy = k[7] 61 | s = (np.linalg.norm(R1) + np.linalg.norm(R2)) / 2 62 | t = np.stack([sTx, sTy], axis=0) 63 | 64 | return t, s 65 | 66 | 67 | def alignment_and_crop(image_path, align_kp): 68 | Lm3D = loadmat(SIMILARITY_LM3D_ALL_MODEL_PATH) 69 | Lm3D = Lm3D['lm'] 70 | 71 | lm_idx = np.array([31, 37, 40, 43, 46, 49, 55]) - 1 72 | Lm3D = np.stack([Lm3D[lm_idx[0], :], np.mean(Lm3D[lm_idx[[1, 2]], :], 0), np.mean(Lm3D[lm_idx[[3, 4]], :], 0), 73 | Lm3D[lm_idx[5], :], Lm3D[lm_idx[6], :]], axis=0) 74 | Lm3D = Lm3D[[1, 2, 0, 3, 4], :] 75 | 76 | Lm2D = np.stack( 77 | [align_kp[lm_idx[0], :], np.mean(align_kp[lm_idx[[1, 2]], :], 0), np.mean(align_kp[lm_idx[[3, 4]], :], 0), 78 | align_kp[lm_idx[5], :], align_kp[lm_idx[6], :]], axis=0) 79 | Lm2D = Lm2D[[1, 2, 0, 3, 4], :] 80 | 81 | img = Image.open(image_path) 82 | w0, h0 = img.size 83 | 84 | Lm2D = np.stack([Lm2D[:, 0], h0 - 1 - Lm2D[:, 1]], axis=1) 85 | t, s = POS(Lm2D.transpose(), Lm3D.transpose()) 86 | 87 | img = img.transform(img.size, Image.AFFINE, (1, 0, t[0] - w0 / 2, 0, 1, h0 / 2 - t[1])) 88 | w = (w0 / s * 102).astype(np.int32) 89 | h = (h0 / s * 102).astype(np.int32) 90 | img = img.resize((w, h), resample=Image.BILINEAR) 91 | # lm = np.stack([lm[:, 0] - t[0] + w0 / 2, lm[:, 1] - t[1] + h0 / 2], axis=1) / s * 102 92 | lm68 = np.stack([align_kp[:, 0] - t[0] + w0 / 2, align_kp[:, 1] + t[1] - h0 / 2], axis=1) / s * 102 93 | # crop the image to 224*224 from image center 94 | left = (w / 2 - 112).astype(np.int32) 95 | right = left + 224 96 | up = (h / 2 - 112).astype(np.int32) 97 | below = up + 224 98 | 99 | img = img.crop((left, up, right, below)) 100 | img = np.array(img) 101 | img = img[:, :, ::-1] 102 | img = np.expand_dims(img, 0) 103 | lm68 = lm68 - np.reshape(np.array([(w / 2 - 112), (h / 2 - 112)]), [1, 2]) 104 | return img, np.expand_dims(lm68, 0) 105 | 106 | 107 | def estimate_affine_matrix_3d22d(X, x): 108 | ''' Using Golden Standard Algorithm for estimating an affine camera 109 | matrix P from world to image correspondences. 110 | See Alg.7.2. in MVGCV 111 | Code Ref: https://github.com/patrikhuber/eos/blob/master/include/eos/fitting/affine_camera_estimation.hpp 112 | x_homo = X_homo.dot(P_Affine) 113 | Args: 114 | X: [n, 3]. corresponding 3d points(fixed) 115 | x: [n, 2]. n>=4. 2d points(moving). x = PX 116 | Returns: 117 | P_Affine: [3, 4]. Affine camera matrix 118 | ''' 119 | X = X.T 120 | x = x.T 121 | assert (x.shape[1] == X.shape[1]) 122 | n = x.shape[1] 123 | assert (n >= 4) 124 | 125 | # --- 1. normalization 126 | # 2d points 127 | mean = np.mean(x, 1) # (2,) 128 | x = x - np.tile(mean[:, np.newaxis], [1, n]) 129 | average_norm = np.mean(np.sqrt(np.sum(x ** 2, 0))) 130 | scale = np.sqrt(2) / average_norm 131 | x = scale * x 132 | 133 | T = np.zeros((3, 3), dtype=np.float32) 134 | T[0, 0] = T[1, 1] = scale 135 | T[:2, 2] = -mean * scale 136 | T[2, 2] = 1 137 | 138 | # 3d points 139 | X_homo = np.vstack((X, np.ones((1, n)))) 140 | mean = np.mean(X, 1) # (3,) 141 | X = X - np.tile(mean[:, np.newaxis], [1, n]) 142 | m = X_homo[:3, :] - X 143 | average_norm = np.mean(np.sqrt(np.sum(X ** 2, 0))) 144 | scale = np.sqrt(3) / average_norm 145 | X = scale * X 146 | 147 | U = np.zeros((4, 4), dtype=np.float32) 148 | U[0, 0] = U[1, 1] = U[2, 2] = scale 149 | U[:3, 3] = -mean * scale 150 | U[3, 3] = 1 151 | 152 | # --- 2. equations 153 | A = np.zeros((n * 2, 8), dtype=np.float32); 154 | X_homo = np.vstack((X, np.ones((1, n)))).T 155 | A[:n, :4] = X_homo 156 | A[n:, 4:] = X_homo 157 | b = np.reshape(x, [-1, 1]) 158 | 159 | # --- 3. solution 160 | p_8 = np.linalg.pinv(A).dot(b) 161 | P = np.zeros((3, 4), dtype=np.float32) 162 | P[0, :] = p_8[:4, 0] 163 | P[1, :] = p_8[4:, 0] 164 | P[-1, -1] = 1 165 | 166 | # --- 4. denormalization 167 | P_Affine = np.linalg.inv(T).dot(P.dot(U)) 168 | return P_Affine 169 | 170 | 171 | def rad2degree(angles): 172 | ''' 173 | 174 | :param angles: [batch, 3] 175 | :return: 176 | ''' 177 | return angles * 180 / np.pi 178 | 179 | 180 | def isRotationMatrix(R): 181 | ''' checks if a matrix is a valid rotation matrix(whether orthogonal or not) 182 | ''' 183 | Rt = np.transpose(R) 184 | shouldBeIdentity = np.dot(Rt, R) 185 | I = np.identity(3, dtype=R.dtype) 186 | n = np.linalg.norm(I - shouldBeIdentity) 187 | return n < 1e-6 188 | 189 | 190 | def matrix2angle(R): 191 | ''' get three Euler angles from Rotation Matrix 192 | Args: 193 | R: (3,3). rotation matrix 194 | Returns: 195 | x: pitch 196 | y: yaw 197 | z: roll 198 | ''' 199 | assert (isRotationMatrix) 200 | sy = math.sqrt(R[0, 0] * R[0, 0] + R[1, 0] * R[1, 0]) 201 | 202 | singular = sy < 1e-6 203 | 204 | if not singular: 205 | x = math.atan2(R[2, 1], R[2, 2]) 206 | y = math.atan2(-R[2, 0], sy) 207 | z = math.atan2(R[1, 0], R[0, 0]) 208 | else: 209 | x = math.atan2(-R[1, 2], R[1, 1]) 210 | y = math.atan2(-R[2, 0], sy) 211 | z = 0 212 | 213 | return x, y, z 214 | 215 | 216 | def P2sRt(P): 217 | ''' decompositing camera matrix P 218 | Args: 219 | P: (3, 4). Affine Camera Matrix. 220 | Returns: 221 | s: scale factor. 222 | R: (3, 3). rotation matrix. 223 | t: (3,). translation. 224 | ''' 225 | t = P[:, 3] 226 | R1 = P[0:1, :3] 227 | R2 = P[1:2, :3] 228 | s = (np.linalg.norm(R1) + np.linalg.norm(R2)) / 2.0 229 | r1 = R1 / np.linalg.norm(R1) 230 | r2 = R2 / np.linalg.norm(R2) 231 | r3 = np.cross(r1, r2) 232 | 233 | R = np.concatenate((r1, r2, r3), 0) 234 | return s, R, t 235 | 236 | 237 | def compute_face_norm(vertices, triangles): 238 | pt1_index, pt2_index, pt3_index = triangles[:, 0], triangles[:, 1], triangles[:, 2] 239 | pts1, pts2, pts3 = vertices[:, pt1_index, :], vertices[:, pt2_index, :], vertices[:, pt3_index, :] 240 | 241 | vec1 = pts1 - pts2 242 | vec2 = pts1 - pts3 243 | 244 | face_norm = torch.Tensor.cross(vec1, vec2) 245 | 246 | return face_norm 247 | 248 | 249 | def compute_point_norm(vertices, triangles, point_buf): 250 | batch = len(vertices) 251 | face_norm = compute_face_norm(vertices, triangles) 252 | 253 | face_norm = torch.cat([face_norm, torch.zeros((batch, 1, 3)).to(vertices.device)], dim=1) 254 | 255 | v_norm = torch.sum(face_norm[:, point_buf, :], dim=2) 256 | v_norm = v_norm / (torch.norm(v_norm, dim=2).unsqueeze(2)) 257 | return v_norm 258 | 259 | 260 | def texture_mapping(image, geo, s, R, t): 261 | pass 262 | 263 | 264 | from matplotlib.path import Path 265 | 266 | 267 | def inpolygon(xq, yq, xv, yv): 268 | """ 269 | reimplement inpolygon in matlab 270 | :type xq: np.ndarray 271 | :type yq: np.ndarray 272 | :type xv: np.ndarray 273 | :type yv: np.ndarray 274 | """ 275 | # 合并xv和yv为顶点数组 276 | vertices = np.vstack((xv, yv)).T 277 | # 定义Path对象 278 | path = Path(vertices) 279 | # 把xq和yq合并为test_points 280 | test_points = np.hstack([xq.reshape(xq.size, -1), yq.reshape(yq.size, -1)]) 281 | # 得到一个test_points是否严格在path内的mask,是bool值数组 282 | _in = path.contains_points(test_points) 283 | # 得到一个test_points是否在path内部或者在路径上的mask 284 | _in_on = path.contains_points(test_points, radius=-1e-10) 285 | # 得到一个test_points是否在path路径上的mask 286 | _on = _in ^ _in_on 287 | 288 | return _in_on, _on 289 | 290 | 291 | def create_mask_fiducial(fiducials, image): 292 | # fiducials: 2x68 293 | border_fid = fiducials[:, 0:17] 294 | face_fid = fiducials[:, 18:] 295 | 296 | c1 = np.array([[border_fid[0, 0]], [face_fid[1, 2]]]) 297 | c2 = np.array([[border_fid[0, 16]], [face_fid[1, 7]]]) 298 | 299 | eye = np.linalg.norm(face_fid[:, 22] - face_fid[:, 25]) 300 | c3, c4 = face_fid[:, 2], face_fid[:, 7] 301 | c3[1] = c3[1] - 0.3 * eye 302 | c4[1] = c4[1] - 0.3 * eye 303 | 304 | border = np.column_stack([c1, border_fid, c2, c4, c3]) 305 | 306 | h, w = image.shape[0:2] 307 | 308 | X, Y = np.meshgrid(np.arange(w), np.arange(h)) 309 | 310 | _in, _on = inpolygon(X.reshape(-1), Y.reshape(-1), border[0, :], border[1, :]) 311 | 312 | mask = np.round(np.reshape(_in + _on, (h, w))) 313 | return (mask * 255).astype(np.uint8) 314 | 315 | 316 | def save_obj(v,c,f,save_path): 317 | folder = os.path.split(save_path)[0] 318 | if not os.path.exists(folder): 319 | os.makedirs(folder) 320 | with open(save_path, 'w') as file: 321 | for i in range(len(c)): 322 | file.write('v %f %f %f %f %f %f\n' % (v[i, 0], v[i, 1], v[i, 2], c[i, 0], c[i, 1], c[i, 2])) 323 | 324 | file.write('\n') 325 | 326 | for i in range(len(f)): 327 | file.write('f %d %d %d\n' % (f[i, 0], f[i, 1], f[i, 2])) 328 | file.close() -------------------------------------------------------------------------------- /VideoFace3D/segmentation/faceparsing/model.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- encoding: utf-8 -*- 3 | 4 | 5 | import torch 6 | import torch.nn as nn 7 | import torch.nn.functional as F 8 | import torchvision 9 | 10 | from VideoFace3D.segmentation.faceparsing.resnet import Resnet18 11 | # from modules.bn import InPlaceABNSync as BatchNorm2d 12 | 13 | 14 | class ConvBNReLU(nn.Module): 15 | def __init__(self, in_chan, out_chan, ks=3, stride=1, padding=1, *args, **kwargs): 16 | super(ConvBNReLU, self).__init__() 17 | self.conv = nn.Conv2d(in_chan, 18 | out_chan, 19 | kernel_size = ks, 20 | stride = stride, 21 | padding = padding, 22 | bias = False) 23 | self.bn = nn.BatchNorm2d(out_chan) 24 | self.init_weight() 25 | 26 | def forward(self, x): 27 | x = self.conv(x) 28 | x = F.relu(self.bn(x)) 29 | return x 30 | 31 | def init_weight(self): 32 | for ly in self.children(): 33 | if isinstance(ly, nn.Conv2d): 34 | nn.init.kaiming_normal_(ly.weight, a=1) 35 | if not ly.bias is None: nn.init.constant_(ly.bias, 0) 36 | 37 | class BiSeNetOutput(nn.Module): 38 | def __init__(self, in_chan, mid_chan, n_classes, *args, **kwargs): 39 | super(BiSeNetOutput, self).__init__() 40 | self.conv = ConvBNReLU(in_chan, mid_chan, ks=3, stride=1, padding=1) 41 | self.conv_out = nn.Conv2d(mid_chan, n_classes, kernel_size=1, bias=False) 42 | self.init_weight() 43 | 44 | def forward(self, x): 45 | x = self.conv(x) 46 | x = self.conv_out(x) 47 | return x 48 | 49 | def init_weight(self): 50 | for ly in self.children(): 51 | if isinstance(ly, nn.Conv2d): 52 | nn.init.kaiming_normal_(ly.weight, a=1) 53 | if not ly.bias is None: nn.init.constant_(ly.bias, 0) 54 | 55 | def get_params(self): 56 | wd_params, nowd_params = [], [] 57 | for name, module in self.named_modules(): 58 | if isinstance(module, nn.Linear) or isinstance(module, nn.Conv2d): 59 | wd_params.append(module.weight) 60 | if not module.bias is None: 61 | nowd_params.append(module.bias) 62 | elif isinstance(module, nn.BatchNorm2d): 63 | nowd_params += list(module.parameters()) 64 | return wd_params, nowd_params 65 | 66 | 67 | class AttentionRefinementModule(nn.Module): 68 | def __init__(self, in_chan, out_chan, *args, **kwargs): 69 | super(AttentionRefinementModule, self).__init__() 70 | self.conv = ConvBNReLU(in_chan, out_chan, ks=3, stride=1, padding=1) 71 | self.conv_atten = nn.Conv2d(out_chan, out_chan, kernel_size= 1, bias=False) 72 | self.bn_atten = nn.BatchNorm2d(out_chan) 73 | self.sigmoid_atten = nn.Sigmoid() 74 | self.init_weight() 75 | 76 | def forward(self, x): 77 | feat = self.conv(x) 78 | atten = F.avg_pool2d(feat, feat.size()[2:]) 79 | atten = self.conv_atten(atten) 80 | atten = self.bn_atten(atten) 81 | atten = self.sigmoid_atten(atten) 82 | out = torch.mul(feat, atten) 83 | return out 84 | 85 | def init_weight(self): 86 | for ly in self.children(): 87 | if isinstance(ly, nn.Conv2d): 88 | nn.init.kaiming_normal_(ly.weight, a=1) 89 | if not ly.bias is None: nn.init.constant_(ly.bias, 0) 90 | 91 | 92 | class ContextPath(nn.Module): 93 | def __init__(self, *args, **kwargs): 94 | super(ContextPath, self).__init__() 95 | self.resnet = Resnet18() 96 | self.arm16 = AttentionRefinementModule(256, 128) 97 | self.arm32 = AttentionRefinementModule(512, 128) 98 | self.conv_head32 = ConvBNReLU(128, 128, ks=3, stride=1, padding=1) 99 | self.conv_head16 = ConvBNReLU(128, 128, ks=3, stride=1, padding=1) 100 | self.conv_avg = ConvBNReLU(512, 128, ks=1, stride=1, padding=0) 101 | 102 | self.init_weight() 103 | 104 | def forward(self, x): 105 | H0, W0 = x.size()[2:] 106 | feat8, feat16, feat32 = self.resnet(x) 107 | H8, W8 = feat8.size()[2:] 108 | H16, W16 = feat16.size()[2:] 109 | H32, W32 = feat32.size()[2:] 110 | 111 | avg = F.avg_pool2d(feat32, feat32.size()[2:]) 112 | avg = self.conv_avg(avg) 113 | avg_up = F.interpolate(avg, (H32, W32), mode='nearest') 114 | 115 | feat32_arm = self.arm32(feat32) 116 | feat32_sum = feat32_arm + avg_up 117 | feat32_up = F.interpolate(feat32_sum, (H16, W16), mode='nearest') 118 | feat32_up = self.conv_head32(feat32_up) 119 | 120 | feat16_arm = self.arm16(feat16) 121 | feat16_sum = feat16_arm + feat32_up 122 | feat16_up = F.interpolate(feat16_sum, (H8, W8), mode='nearest') 123 | feat16_up = self.conv_head16(feat16_up) 124 | 125 | return feat8, feat16_up, feat32_up # x8, x8, x16 126 | 127 | def init_weight(self): 128 | for ly in self.children(): 129 | if isinstance(ly, nn.Conv2d): 130 | nn.init.kaiming_normal_(ly.weight, a=1) 131 | if not ly.bias is None: nn.init.constant_(ly.bias, 0) 132 | 133 | def get_params(self): 134 | wd_params, nowd_params = [], [] 135 | for name, module in self.named_modules(): 136 | if isinstance(module, (nn.Linear, nn.Conv2d)): 137 | wd_params.append(module.weight) 138 | if not module.bias is None: 139 | nowd_params.append(module.bias) 140 | elif isinstance(module, nn.BatchNorm2d): 141 | nowd_params += list(module.parameters()) 142 | return wd_params, nowd_params 143 | 144 | 145 | ### This is not used, since I replace this with the resnet feature with the same size 146 | class SpatialPath(nn.Module): 147 | def __init__(self, *args, **kwargs): 148 | super(SpatialPath, self).__init__() 149 | self.conv1 = ConvBNReLU(3, 64, ks=7, stride=2, padding=3) 150 | self.conv2 = ConvBNReLU(64, 64, ks=3, stride=2, padding=1) 151 | self.conv3 = ConvBNReLU(64, 64, ks=3, stride=2, padding=1) 152 | self.conv_out = ConvBNReLU(64, 128, ks=1, stride=1, padding=0) 153 | self.init_weight() 154 | 155 | def forward(self, x): 156 | feat = self.conv1(x) 157 | feat = self.conv2(feat) 158 | feat = self.conv3(feat) 159 | feat = self.conv_out(feat) 160 | return feat 161 | 162 | def init_weight(self): 163 | for ly in self.children(): 164 | if isinstance(ly, nn.Conv2d): 165 | nn.init.kaiming_normal_(ly.weight, a=1) 166 | if not ly.bias is None: nn.init.constant_(ly.bias, 0) 167 | 168 | def get_params(self): 169 | wd_params, nowd_params = [], [] 170 | for name, module in self.named_modules(): 171 | if isinstance(module, nn.Linear) or isinstance(module, nn.Conv2d): 172 | wd_params.append(module.weight) 173 | if not module.bias is None: 174 | nowd_params.append(module.bias) 175 | elif isinstance(module, nn.BatchNorm2d): 176 | nowd_params += list(module.parameters()) 177 | return wd_params, nowd_params 178 | 179 | 180 | class FeatureFusionModule(nn.Module): 181 | def __init__(self, in_chan, out_chan, *args, **kwargs): 182 | super(FeatureFusionModule, self).__init__() 183 | self.convblk = ConvBNReLU(in_chan, out_chan, ks=1, stride=1, padding=0) 184 | self.conv1 = nn.Conv2d(out_chan, 185 | out_chan//4, 186 | kernel_size = 1, 187 | stride = 1, 188 | padding = 0, 189 | bias = False) 190 | self.conv2 = nn.Conv2d(out_chan//4, 191 | out_chan, 192 | kernel_size = 1, 193 | stride = 1, 194 | padding = 0, 195 | bias = False) 196 | self.relu = nn.ReLU(inplace=True) 197 | self.sigmoid = nn.Sigmoid() 198 | self.init_weight() 199 | 200 | def forward(self, fsp, fcp): 201 | fcat = torch.cat([fsp, fcp], dim=1) 202 | feat = self.convblk(fcat) 203 | atten = F.avg_pool2d(feat, feat.size()[2:]) 204 | atten = self.conv1(atten) 205 | atten = self.relu(atten) 206 | atten = self.conv2(atten) 207 | atten = self.sigmoid(atten) 208 | feat_atten = torch.mul(feat, atten) 209 | feat_out = feat_atten + feat 210 | return feat_out 211 | 212 | def init_weight(self): 213 | for ly in self.children(): 214 | if isinstance(ly, nn.Conv2d): 215 | nn.init.kaiming_normal_(ly.weight, a=1) 216 | if not ly.bias is None: nn.init.constant_(ly.bias, 0) 217 | 218 | def get_params(self): 219 | wd_params, nowd_params = [], [] 220 | for name, module in self.named_modules(): 221 | if isinstance(module, nn.Linear) or isinstance(module, nn.Conv2d): 222 | wd_params.append(module.weight) 223 | if not module.bias is None: 224 | nowd_params.append(module.bias) 225 | elif isinstance(module, nn.BatchNorm2d): 226 | nowd_params += list(module.parameters()) 227 | return wd_params, nowd_params 228 | 229 | 230 | class BiSeNet(nn.Module): 231 | def __init__(self, n_classes, *args, **kwargs): 232 | super(BiSeNet, self).__init__() 233 | self.cp = ContextPath() 234 | ## here self.sp is deleted 235 | self.ffm = FeatureFusionModule(256, 256) 236 | self.conv_out = BiSeNetOutput(256, 256, n_classes) 237 | self.conv_out16 = BiSeNetOutput(128, 64, n_classes) 238 | self.conv_out32 = BiSeNetOutput(128, 64, n_classes) 239 | self.init_weight() 240 | 241 | def forward(self, x): 242 | H, W = x.size()[2:] 243 | feat_res8, feat_cp8, feat_cp16 = self.cp(x) # here return res3b1 feature 244 | feat_sp = feat_res8 # use res3b1 feature to replace spatial path feature 245 | feat_fuse = self.ffm(feat_sp, feat_cp8) 246 | 247 | feat_out = self.conv_out(feat_fuse) 248 | feat_out16 = self.conv_out16(feat_cp8) 249 | feat_out32 = self.conv_out32(feat_cp16) 250 | 251 | feat_out = F.interpolate(feat_out, (H, W), mode='bilinear', align_corners=True) 252 | feat_out16 = F.interpolate(feat_out16, (H, W), mode='bilinear', align_corners=True) 253 | feat_out32 = F.interpolate(feat_out32, (H, W), mode='bilinear', align_corners=True) 254 | return feat_out, feat_out16, feat_out32 255 | 256 | def init_weight(self): 257 | for ly in self.children(): 258 | if isinstance(ly, nn.Conv2d): 259 | nn.init.kaiming_normal_(ly.weight, a=1) 260 | if not ly.bias is None: nn.init.constant_(ly.bias, 0) 261 | 262 | def get_params(self): 263 | wd_params, nowd_params, lr_mul_wd_params, lr_mul_nowd_params = [], [], [], [] 264 | for name, child in self.named_children(): 265 | child_wd_params, child_nowd_params = child.get_params() 266 | if isinstance(child, FeatureFusionModule) or isinstance(child, BiSeNetOutput): 267 | lr_mul_wd_params += child_wd_params 268 | lr_mul_nowd_params += child_nowd_params 269 | else: 270 | wd_params += child_wd_params 271 | nowd_params += child_nowd_params 272 | return wd_params, nowd_params, lr_mul_wd_params, lr_mul_nowd_params 273 | 274 | 275 | if __name__ == "__main__": 276 | net = BiSeNet(19) 277 | net.cuda() 278 | net.eval() 279 | in_ten = torch.randn(16, 3, 640, 480).cuda() 280 | out, out16, out32 = net(in_ten) 281 | print(out.shape) 282 | 283 | net.get_params() 284 | -------------------------------------------------------------------------------- /VideoFace3D/SFS/SFSNet/mask.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | from __future__ import absolute_import, division, print_function 3 | import dlib 4 | import cv2 5 | import numpy as np 6 | import os 7 | import sys 8 | from matplotlib.path import Path 9 | 10 | 11 | class MaskGenerator: 12 | def __init__(self, landmarks_path): 13 | """ 14 | :param landmarks_path: the path of pretrained key points weight, 15 | it could be download from: 16 | http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2 17 | """ 18 | if not os.path.exists(landmarks_path): 19 | raise RuntimeError('face landmark file is not exist. please download if from: \n' 20 | 'http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2 ' 21 | 'and uncompress it.') 22 | self._detector = dlib.get_frontal_face_detector() 23 | self._predictor = dlib.shape_predictor(landmarks_path) 24 | 25 | def bounding_boxes(self, image): 26 | # convert to gray image 27 | gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 28 | # get rect contains face 29 | face_rects = self._detector(gray_image, 0) 30 | return face_rects 31 | 32 | def align(self, image, size=(240, 240), scale=1.8, warp=True, crop=True, resize=True, 33 | crop_function_version=0, align_multi=False, draw_landmarks=False): 34 | """ 35 | warp and crop image 36 | https://blog.csdn.net/qq_39438636/article/details/79304130 37 | 38 | :param image: a BGR format face image 39 | :type image: np.ndarray 40 | :param size: target size 41 | :param scale: 42 | :param warp: warp or not 43 | :param crop: crop or not 44 | :param resize: resize od not 45 | :param crop_function_version: crop function version 46 | :param align_multi: whther to detect multi face 47 | :param draw_landmarks: whether draw face landmarks 48 | :return: mask, image and whether successfully crop image 49 | """ 50 | # check option 51 | if crop_function_version == 1 and align_multi: 52 | raise RuntimeError("When align_multi is true, crop_function_version must be 0") 53 | # if image is too big, resize to a smaller image 54 | if np.min(image.shape[0:2]) > 1000: 55 | ratio = 1000 / np.min(image.shape[0:2]) 56 | image = cv2.resize(image, dsize=(0, 0), fx=ratio, fy=ratio) 57 | # make border for image 58 | border = int(np.min(image.shape[0:2]) * 0.3) 59 | image = cv2.copyMakeBorder(image, border, border, border, border, cv2.BORDER_CONSTANT) 60 | # backup image 61 | original_image = image.copy() 62 | # get rectangles which contains face 63 | face_rects = self.bounding_boxes(image) 64 | results = [] 65 | if len(face_rects) > 0: 66 | for i in range(len(face_rects)): 67 | # get 68 landmarks of face 68 | landmarks = np.array([[p.x, p.y] for p in self._predictor(original_image, face_rects[i]).parts()]) 69 | # draw landmarks 70 | if draw_landmarks: 71 | landmark_image = self.draw_landmarks(original_image, landmarks) 72 | # remove border 73 | _row, _col, _ = landmark_image.shape 74 | landmark_image = landmark_image[border:_row-border, border:_col-border, :] 75 | else: 76 | landmark_image = None 77 | # create mask using landmarks 78 | mask = create_mask_by_landmarks(landmarks.T, original_image) 79 | if warp: 80 | image, mask, r_mat = self._warp(original_image, mask, landmarks) 81 | landmarks = self._get_rotated_points(landmarks, r_mat) 82 | if crop: 83 | if crop_function_version == 0: 84 | image = self._crop_v0(image, landmarks, scale) 85 | mask = self._crop_v0(mask, landmarks, scale) 86 | elif crop_function_version == 1: 87 | image, mask, suc_ = self._crop_v1(image, mask, scale) 88 | if not suc_: 89 | sys.stderr.write('%s: Failed to crop image and mask\n' % __file__) 90 | else: 91 | raise RuntimeError("crop_function_version must be 0 or 1") 92 | 93 | if resize: 94 | results.append((True, cv2.resize(mask, size), cv2.resize(image, size), landmark_image)) 95 | else: 96 | results.append((True, mask, image, landmark_image)) 97 | 98 | if not align_multi: 99 | return results 100 | return results 101 | else: 102 | sys.stderr.write("%s: Can't detect face in image\n" % __file__) 103 | image = cv2.resize(image, size) 104 | return [(False, np.ones(image.shape, dtype=image.dtype) * 255, image, None)] 105 | 106 | @staticmethod 107 | def _get_rotated_points(points, rotate_mat): 108 | # Blog; https://www.cnblogs.com/zhoug2020/p/7842808.html 109 | # add 1 to every point 110 | __padding = np.ones((points.shape[0], 1), dtype=points.dtype) 111 | points = np.concatenate([points, __padding], axis=1) 112 | # add [0, 0, 1] to rotate matrix 113 | __padding = np.array([0, 0, 1], dtype=points.dtype).reshape(1, 3) 114 | rotate_mat = np.concatenate([rotate_mat, __padding], axis=0) 115 | # compute rotated landmarks 116 | rotate_landmarks = np.matmul(rotate_mat, points.T) 117 | # remove the padding and transpose landmarks 118 | rotate_landmarks = rotate_landmarks[0:2, :].T 119 | # return landmark as integer numpy array 120 | return rotate_landmarks.astype(points.dtype) 121 | 122 | @staticmethod 123 | def _warp(image, mask, landmarks): 124 | """ 125 | warp image and mask by landmarks 126 | :param image: 127 | :type image np.ndarray 128 | :param landmarks: 129 | :type landmarks np.ndarray 130 | :return: warped face and mask 131 | """ 132 | # landmarks.shape = (68, 2) 133 | landmarks = np.array(landmarks) 134 | # compute rotate angle, r_angle=arctan((y1-y2)/(x1-x2)) 135 | # landmarks[36]: corner of left eye 136 | # landmarks[42]: corner of right eye 137 | r_angle = np.arctan((landmarks[36][1] - landmarks[42][1]) / 138 | (landmarks[36][0] - landmarks[42][0])) 139 | r_angle = 180 * r_angle / np.pi 140 | # get rotation matrix 141 | rot_mat = cv2.getRotationMatrix2D(tuple(landmarks[2]), r_angle, scale=1) 142 | 143 | # rotate image and mask 144 | rotated_image = cv2.warpAffine(image, rot_mat, dsize=image.shape[0:2]) 145 | rotated_mask = cv2.warpAffine(mask, rot_mat, dsize=image.shape[0:2]) 146 | 147 | return rotated_image, rotated_mask, rot_mat 148 | 149 | def _crop_v0(self, image, landmarks, scale): 150 | """ 151 | crop image by face landmarks 152 | :param image: 153 | :param landmarks: 154 | :param scale: 155 | :return: 156 | """ 157 | # left eye: landmarks[36] 158 | # left mouth: landmarks[48] 159 | # nose: landmarks[29] 160 | # find the most left point and most right point 161 | landmarks_x = landmarks[:, 0] 162 | most_left_x = np.min(landmarks_x) 163 | most_right_x = np.max(landmarks_x) 164 | mid_x = (most_left_x + most_right_x) // 2 165 | # print(most_left_x, most_right_x, mid_x) 166 | # define new center point use mid_x and y from nose point 167 | center_point = [mid_x, landmarks[29][1]] 168 | # compute the distance between left eye(landmarks[36]) 169 | distance = most_right_x - mid_x 170 | size = distance * scale 171 | # print(center_point) 172 | # compute row_start, row_end, col_start, col_end 173 | row_start = int(center_point[1] - size) 174 | row_end = int(center_point[1] + size) 175 | col_start = int(center_point[0] - size) 176 | col_end = int(center_point[0] + size) 177 | # print('*' * 10) 178 | # print(row_start, row_end, col_start, col_end) 179 | # make range valid and compute padding 180 | if row_start < 0: 181 | padding_up = abs(row_start) 182 | row_start = 0 183 | else: 184 | padding_up = 0 185 | if col_start < 0: 186 | padding_left = abs(col_start) 187 | col_start = 0 188 | else: 189 | padding_left = 0 190 | if row_end > (image.shape[0] - 1): 191 | padding_down = row_end - (image.shape[0] - 1) 192 | row_end = image.shape[0] - 1 193 | else: 194 | padding_down = 0 195 | if col_end > (image.shape[1] - 1): 196 | padding_right = col_end - (image.shape[1] - 1) 197 | col_end = image.shape[1] - 1 198 | else: 199 | padding_right = 0 200 | # print(row_start, row_end, col_start, col_end) 201 | # print('*' * 10) 202 | # crop image 203 | cropped_image = self._crop_helper(image, row_start, row_end, col_start, col_end, 204 | padding_up, padding_down, padding_left, padding_right) 205 | return cropped_image 206 | 207 | def _crop_v1(self, image, mask, scale): 208 | face_rects = self.bounding_boxes(image) 209 | if len(face_rects) == 0: 210 | return image, mask, False 211 | # define crop size 212 | size = (face_rects[0].right() - face_rects[0].left()) / 2 213 | size *= scale 214 | # define new center point use mid_x and y from nose point 215 | _x = (face_rects[0].left() + face_rects[0].right()) // 2 216 | _y = (face_rects[0].top() + face_rects[0].bottom()) // 2 217 | center_point = [_x, _y] 218 | # compute the distance between left eye(landmarks[36]) 219 | # print(center_point) 220 | # compute row_start, row_end, col_start, col_end 221 | row_start = int(center_point[1] - size) 222 | row_end = int(center_point[1] + size) 223 | col_start = int(center_point[0] - size) 224 | col_end = int(center_point[0] + size) 225 | # print('*' * 10) 226 | # print(row_start, row_end, col_start, col_end) 227 | # make range valid and compute padding 228 | if row_start < 0: 229 | padding_up = abs(row_start) 230 | row_start = 0 231 | else: 232 | padding_up = 0 233 | if col_start < 0: 234 | padding_left = abs(col_start) 235 | col_start = 0 236 | else: 237 | padding_left = 0 238 | if row_end > (image.shape[0] - 1): 239 | padding_down = row_end - (image.shape[0] - 1) 240 | row_end = image.shape[0] - 1 241 | else: 242 | padding_down = 0 243 | if col_end > (image.shape[1] - 1): 244 | padding_right = col_end - (image.shape[1] - 1) 245 | col_end = image.shape[1] - 1 246 | else: 247 | padding_right = 0 248 | # print(row_start, row_end, col_start, col_end) 249 | # print('*' * 10) 250 | # crop image 251 | image = self._crop_helper(image, row_start, row_end, col_start, col_end, 252 | padding_up, padding_down, padding_left, padding_right) 253 | mask = self._crop_helper(mask, row_start, row_end, col_start, col_end, 254 | padding_up, padding_down, padding_left, padding_right) 255 | return image, mask, True 256 | 257 | @staticmethod 258 | def _crop_helper(image, row_start, row_end, col_start, col_end, 259 | padding_up, padding_down, padding_left, padding_right): 260 | cropped_image = image[row_start:row_end, col_start:col_end] 261 | 262 | # add padding to image 263 | rows, cols, _ = cropped_image.shape 264 | if padding_up > 0: 265 | padding = np.zeros(shape=(padding_up, cols, 3), dtype=cropped_image.dtype) 266 | cropped_image = np.vstack((padding, cropped_image)) 267 | if padding_down > 0: 268 | padding = np.zeros(shape=(padding_down, cols, 3), dtype=cropped_image.dtype) 269 | cropped_image = np.vstack((cropped_image, padding)) 270 | rows, cols, _ = cropped_image.shape 271 | if padding_left > 0: 272 | padding = np.zeros(shape=(rows, padding_left, 3), dtype=cropped_image.dtype) 273 | cropped_image = np.hstack((padding, cropped_image)) 274 | if padding_right > 0: 275 | padding = np.zeros(shape=(rows, padding_right, 3), dtype=cropped_image.dtype) 276 | cropped_image = np.hstack((cropped_image, padding)) 277 | return cropped_image 278 | 279 | @staticmethod 280 | def draw_landmarks(image, landmarks): 281 | landmark_im = image.copy() 282 | for i, landmark in enumerate(landmarks): 283 | cv2.circle(landmark_im, tuple(landmark), 3, (0, 0, 255)) 284 | cv2.putText(landmark_im, str(i), tuple(landmark), cv2.FONT_HERSHEY_SIMPLEX, 285 | 0.3, (0, 255, 0)) 286 | return landmark_im 287 | 288 | 289 | def create_mask_by_landmarks(landmarks, Image): 290 | """ 291 | create mask use fiducials of Image 292 | :param landmarks: the 68 landmarks detected using dlib 293 | :type landmarks np.ndarray 294 | :param Image: a 3-channel image 295 | :type Image np.ndarray 296 | :return: 297 | """ 298 | # fiducals is 2x68 299 | landmarks = np.float32(landmarks) 300 | border_fid = landmarks[:, 0:17] 301 | face_fid = landmarks[:, 17:] 302 | 303 | c1 = np.array([border_fid[0, 0], face_fid[1, 2]]) # left 304 | c2 = np.array([border_fid[0, 16], face_fid[1, 7]]) # right 305 | eye = np.linalg.norm(face_fid[:, 22] - face_fid[:, 25]) 306 | c3 = face_fid[:, 2] 307 | c3[1] = c3[1] - 0.3 * eye 308 | c4 = face_fid[:, 7] 309 | c4[1] = c4[1] - 0.3 * eye 310 | 311 | border = [c1, border_fid, c2, c4, c3] 312 | border = [item.reshape(2, -1) for item in border] 313 | border = np.hstack(border) 314 | 315 | M = Image.shape[0] # row -> y 316 | N = Image.shape[1] # col -> x 317 | 318 | y = np.arange(0, M, step=1, dtype=np.float32) 319 | x = np.arange(0, N, step=1, dtype=np.float32) 320 | X, Y = np.meshgrid(x, y) 321 | 322 | _in, _on = inpolygon(X, Y, border[0, :].T, border[1, :].T) 323 | 324 | mask = np.round(np.reshape(_in | _on, [M, N])) 325 | mask = 255 * np.uint8(mask) 326 | mask = np.repeat(np.expand_dims(mask, -1), 3, axis=-1) 327 | return mask 328 | 329 | 330 | def inpolygon(xq, yq, xv, yv): 331 | """ 332 | reimplement inpolygon in matlab 333 | :type xq: np.ndarray 334 | :type yq: np.ndarray 335 | :type xv: np.ndarray 336 | :type yv: np.ndarray 337 | """ 338 | # http://blog.sina.com.cn/s/blog_70012f010102xnel.html 339 | # merge xy and yv into vertices 340 | vertices = np.vstack((xv, yv)).T 341 | # define a Path object 342 | path = Path(vertices) 343 | # merge X and Y into test_points 344 | test_points = np.hstack([xq.reshape(xq.size, -1), yq.reshape(yq.size, -1)]) 345 | # get mask of test_points in path 346 | _in = path.contains_points(test_points) 347 | # get mask of test_points in path(include the points on path) 348 | _in_on = path.contains_points(test_points, radius=-1e-10) 349 | # get the points on path 350 | _on = _in ^ _in_on 351 | return _in_on, _on 352 | -------------------------------------------------------------------------------- /VideoFace3D/renderer/render.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | import math 3 | 4 | import torch 5 | import torch.nn as nn 6 | import numpy 7 | 8 | import neural_renderer as nr 9 | from VideoFace3D.renderer.weak_projection import weak_projection 10 | from VideoFace3D.utils.geometry import texture_from_point2faces, euler2rot, compute_face_norm, compute_point_norm 11 | from VideoFace3D.renderer.lightning import * 12 | from VideoFace3D.models.face_model import FaceModelBFM 13 | 14 | class Renderer(nn.Module): 15 | def __init__(self, image_size=256, anti_aliasing=True, background_color=[0, 0, 0], 16 | fill_back=True, camera_mode='weak_projection', 17 | K=None, R=None, t=None, dist_coeffs=None, orig_size=1024, 18 | perspective=True, viewing_angle=30, camera_direction=[0, 0, 1], 19 | near=0.1, far=100,light_mode="parallel",SH_Coeff=None, 20 | light_intensity_ambient=0.5, light_intensity_directional=0.5, 21 | light_color_ambient=[1, 1, 1], light_color_directional=[1, 1, 1], 22 | light_direction=[0, 1, 0]): 23 | super(Renderer, self).__init__() 24 | # rendering 25 | self.image_size = image_size 26 | self.anti_aliasing = anti_aliasing 27 | self.background_color = background_color 28 | self.fill_back = fill_back 29 | 30 | self.light_mode= light_mode 31 | self.SH_coeff = SH_Coeff 32 | self.facemodel = FaceModelBFM() 33 | # camera 34 | self.camera_mode = camera_mode 35 | if self.camera_mode in ['projection', 'weak_projection']: 36 | self.K = K 37 | self.R = R 38 | self.t = t 39 | if isinstance(self.K, numpy.ndarray): 40 | self.K = torch.cuda.FloatTensor(self.K) 41 | if isinstance(self.R, numpy.ndarray): 42 | self.R = torch.cuda.FloatTensor(self.R) 43 | if isinstance(self.t, numpy.ndarray): 44 | self.t = torch.cuda.FloatTensor(self.t) 45 | self.dist_coeffs = dist_coeffs 46 | if dist_coeffs is None: 47 | self.dist_coeffs = torch.cuda.FloatTensor([[0., 0., 0., 0., 0.]]) 48 | self.orig_size = orig_size 49 | elif self.camera_mode in ['look', 'look_at']: 50 | self.perspective = perspective 51 | self.viewing_angle = viewing_angle 52 | self.eye = [0, 0, -(1. / math.tan(math.radians(self.viewing_angle)) + 1)] 53 | self.camera_direction = [0, 0, 1] 54 | else: 55 | raise ValueError('Camera mode has to be one of projection, look or look_at') 56 | 57 | self.near = near 58 | self.far = far 59 | 60 | # light 61 | self.light_intensity_ambient = light_intensity_ambient 62 | self.light_intensity_directional = light_intensity_directional 63 | self.light_color_ambient = light_color_ambient 64 | self.light_color_directional = light_color_directional 65 | self.light_direction = light_direction 66 | 67 | # rasterization 68 | self.rasterizer_eps = 1e-3 69 | 70 | def forward(self, vertices, faces=None, textures=None, mode=None, K=None, R=None, t=None, dist_coeffs=None, 71 | orig_size=224): 72 | ''' 73 | Implementation of forward rendering method 74 | The old API is preserved for back-compatibility with the Chainer implementation 75 | ''' 76 | 77 | if mode is None: 78 | return self.render(vertices, faces, textures, K, R, t, dist_coeffs, orig_size) 79 | elif mode is 'rgb': 80 | return self.render_rgb(vertices, faces, textures, K, R, t, dist_coeffs, orig_size) 81 | elif mode == 'silhouettes': 82 | return self.render_silhouettes(vertices, faces, K, R, t, dist_coeffs, orig_size) 83 | elif mode == 'depth': 84 | return self.render_depth(vertices, faces, K, R, t, dist_coeffs, orig_size) 85 | elif mode == 'points': 86 | return self.project_points(vertices, K, R, t, dist_coeffs, orig_size) 87 | else: 88 | raise ValueError("mode should be one of None, 'silhouettes' or 'depth'") 89 | 90 | def render_silhouettes(self, vertices, faces, K=None, R=None, t=None, dist_coeffs=None, orig_size=None): 91 | 92 | # fill back 93 | if self.fill_back: 94 | faces = torch.cat((faces, faces[:, :, list(reversed(range(faces.shape[-1])))]), dim=1) 95 | 96 | # viewpoint transformation 97 | if self.camera_mode == 'look_at': 98 | vertices = nr.look_at(vertices, self.eye) 99 | # perspective transformation 100 | if self.perspective: 101 | vertices = nr.perspective(vertices, angle=self.viewing_angle) 102 | elif self.camera_mode == 'look': 103 | vertices = nr.look(vertices, self.eye, self.camera_direction) 104 | # perspective transformation 105 | if self.perspective: 106 | vertices = nr.perspective(vertices, angle=self.viewing_angle) 107 | elif self.camera_mode == 'projection': 108 | if K is None: 109 | K = self.K 110 | if R is None: 111 | R = self.R 112 | if t is None: 113 | t = self.t 114 | if dist_coeffs is None: 115 | dist_coeffs = self.dist_coeffs 116 | if orig_size is None: 117 | orig_size = self.orig_size 118 | vertices = nr.projection(vertices, K, R, t, dist_coeffs, orig_size) 119 | elif self.camera_mode == 'weak_projection': 120 | if K is None: 121 | K = self.K 122 | if R is None: 123 | R = self.R 124 | if t is None: 125 | t = self.t 126 | 127 | vertices = weak_projection(vertices, K, R, t, orig_size) 128 | 129 | # rasterization 130 | faces = nr.vertices_to_faces(vertices, faces) 131 | images = nr.rasterize_silhouettes(faces, self.image_size, self.anti_aliasing) 132 | return images 133 | 134 | def render_depth(self, vertices, faces, K=None, R=None, t=None, dist_coeffs=None, orig_size=None): 135 | 136 | # fill back 137 | if self.fill_back: 138 | faces = torch.cat((faces, faces[:, :, list(reversed(range(faces.shape[-1])))]), dim=1).detach() 139 | 140 | # viewpoint transformation 141 | if self.camera_mode == 'look_at': 142 | vertices = nr.look_at(vertices, self.eye) 143 | # perspective transformation 144 | if self.perspective: 145 | vertices = nr.perspective(vertices, angle=self.viewing_angle) 146 | elif self.camera_mode == 'look': 147 | vertices = nr.look(vertices, self.eye, self.camera_direction) 148 | # perspective transformation 149 | if self.perspective: 150 | vertices = nr.perspective(vertices, angle=self.viewing_angle) 151 | elif self.camera_mode == 'projection': 152 | if K is None: 153 | K = self.K 154 | if R is None: 155 | R = self.R 156 | if t is None: 157 | t = self.t 158 | if dist_coeffs is None: 159 | dist_coeffs = self.dist_coeffs 160 | if orig_size is None: 161 | orig_size = self.orig_size 162 | vertices = nr.projection(vertices, K, R, t, dist_coeffs, orig_size) 163 | elif self.camera_mode == 'weak_projection': 164 | if K is None: 165 | K = self.K 166 | if R is None: 167 | R = self.R 168 | if t is None: 169 | t = self.t 170 | 171 | vertices = weak_projection(vertices, K, R, t, orig_size) 172 | # rasterization 173 | faces = nr.vertices_to_faces(vertices, faces) 174 | images = nr.rasterize_depth(faces, self.image_size, self.anti_aliasing) 175 | return images 176 | 177 | def render_rgb(self, vertices, faces, textures, K=None, R=None, t=None, dist_coeffs=None, orig_size=None): 178 | # fill back 179 | if self.fill_back: 180 | faces = torch.cat((faces, faces[:, :, list(reversed(range(faces.shape[-1])))]), dim=1).detach() 181 | textures = torch.cat((textures, textures.permute((0, 1, 4, 3, 2, 5))), dim=1) 182 | 183 | # lighting 184 | faces_lighting = nr.vertices_to_faces(vertices, faces) 185 | textures = nr.lighting( 186 | faces_lighting, 187 | textures, 188 | self.light_intensity_ambient, 189 | self.light_intensity_directional, 190 | self.light_color_ambient, 191 | self.light_color_directional, 192 | self.light_direction) 193 | 194 | # viewpoint transformation 195 | if self.camera_mode == 'look_at': 196 | vertices = nr.look_at(vertices, self.eye) 197 | # perspective transformation 198 | if self.perspective: 199 | vertices = nr.perspective(vertices, angle=self.viewing_angle) 200 | elif self.camera_mode == 'look': 201 | vertices = nr.look(vertices, self.eye, self.camera_direction) 202 | # perspective transformation 203 | if self.perspective: 204 | vertices = nr.perspective(vertices, angle=self.viewing_angle) 205 | elif self.camera_mode == 'projection': 206 | if K is None: 207 | K = self.K 208 | if R is None: 209 | R = self.R 210 | if t is None: 211 | t = self.t 212 | if dist_coeffs is None: 213 | dist_coeffs = self.dist_coeffs 214 | if orig_size is None: 215 | orig_size = self.orig_size 216 | vertices = nr.projection(vertices, K, R, t, dist_coeffs, orig_size) 217 | elif self.camera_mode == 'weak_projection': 218 | if K is None: 219 | K = self.K 220 | if R is None: 221 | R = self.R 222 | if t is None: 223 | t = self.t 224 | 225 | vertices = weak_projection(vertices, K, R, t, orig_size) 226 | 227 | # rasterization 228 | faces = nr.vertices_to_faces(vertices, faces) 229 | images = nr.rasterize( 230 | faces, textures, self.image_size, self.anti_aliasing, self.near, self.far, self.rasterizer_eps, 231 | self.background_color) 232 | return images 233 | 234 | def render(self, vertices, faces, textures, K=None, R=None, t=None, dist_coeffs=None, orig_size=None): 235 | # fill back 236 | # viewpoint transformation 237 | if self.camera_mode == 'look_at': 238 | vertices = nr.look_at(vertices, self.eye) 239 | # perspective transformation 240 | if self.perspective: 241 | vertices = nr.perspective(vertices, angle=self.viewing_angle) 242 | elif self.camera_mode == 'look': 243 | vertices = nr.look(vertices, self.eye, self.camera_direction) 244 | # perspective transformation 245 | if self.perspective: 246 | vertices = nr.perspective(vertices, angle=self.viewing_angle) 247 | elif self.camera_mode == 'projection': 248 | if K is None: 249 | K = self.K 250 | if R is None: 251 | R = self.R 252 | if t is None: 253 | t = self.t 254 | if dist_coeffs is None: 255 | dist_coeffs = self.dist_coeffs 256 | if orig_size is None: 257 | orig_size = self.orig_size 258 | vertices = nr.projection(vertices, K, R, t, dist_coeffs, orig_size) 259 | elif self.camera_mode == 'weak_projection': 260 | if K is None: 261 | K = self.K 262 | if R is None: 263 | R = self.R 264 | if t is None: 265 | t = self.t 266 | 267 | vertices = weak_projection(vertices, K, R, t, orig_size) 268 | # 正则化一下,让脸颊到鼻尖的向量和z轴负方向夹角小于90度 (a,b,c)*(0,0,-1)>0 -> c<0 269 | # 另外将z值全部归到正轴上去 270 | #from MorphableModelFitting.models.face_model import FaceModelBFM 271 | 272 | 273 | front_idx, back_idx_1, back_idx_2 = self.facemodel.keypoints[30], self.facemodel.keypoints[0], self.facemodel.keypoints[16] 274 | for i in range(len(vertices)): 275 | back_z = (vertices[i, back_idx_1, 2] + vertices[i, back_idx_2, 2]) / 2 276 | if (vertices[i, front_idx, 2] - back_z) > 0: 277 | vertices[i, :, 2] = -vertices[i, :, 2] 278 | 279 | vertices[:, :, 2] = vertices[:, :, 2] - torch.min(vertices[:, :, 2], dim=1)[0].unsqueeze(1) + 1 280 | 281 | # lighting 282 | if self.light_mode == "parallel": 283 | textures = texture_from_point2faces(faces, textures) / 255 284 | if self.fill_back: 285 | faces = torch.cat((faces, faces[:, :, list(reversed(range(faces.shape[-1])))]), dim=1).detach() 286 | textures = torch.cat((textures, textures.permute((0, 1, 4, 3, 2, 5))), dim=1) 287 | faces_lighting = nr.vertices_to_faces(vertices, faces) 288 | textures = nr.lighting( 289 | faces_lighting, 290 | textures, 291 | self.light_intensity_ambient, 292 | self.light_intensity_directional, 293 | self.light_color_ambient, 294 | self.light_color_directional, 295 | self.light_direction) 296 | elif self.light_mode == "SH": 297 | point_buf = torch.from_numpy(self.facemodel.point_buf).long() - 1 298 | point_norm = compute_point_norm(vertices, faces[0], point_buf) 299 | 300 | # texture = texture_from_point2faces(triangles, texture).reshape((batch, -1, 3)) 301 | textures, _ = Illumination_SH(textures, point_norm, self.SH_coeff) 302 | textures = texture_from_point2faces(faces, textures) / 255 303 | if self.fill_back: 304 | faces = torch.cat((faces, faces[:, :, list(reversed(range(faces.shape[-1])))]), dim=1).detach() 305 | textures = torch.cat((textures, textures.permute((0, 1, 4, 3, 2, 5))), dim=1) 306 | else: 307 | return None 308 | 309 | # rasterization 310 | faces = nr.vertices_to_faces(vertices, faces) 311 | out = nr.rasterize_rgbad( 312 | faces, textures, self.image_size, self.anti_aliasing, self.near, self.far, self.rasterizer_eps, 313 | self.background_color) 314 | return out['rgb'], out['depth'], out['alpha'] 315 | 316 | def project_points(self, vertices, K=None, R=None, t=None, dist_coeffs=None, orig_size=None): 317 | if self.camera_mode == 'look_at': 318 | vertices = nr.look_at(vertices, self.eye) 319 | # perspective transformation 320 | if self.perspective: 321 | vertices = nr.perspective(vertices, angle=self.viewing_angle) 322 | elif self.camera_mode == 'look': 323 | vertices = nr.look(vertices, self.eye, self.camera_direction) 324 | # perspective transformation 325 | if self.perspective: 326 | vertices = nr.perspective(vertices, angle=self.viewing_angle) 327 | elif self.camera_mode == 'projection': 328 | if K is None: 329 | K = self.K 330 | if R is None: 331 | R = self.R 332 | if t is None: 333 | t = self.t 334 | if dist_coeffs is None: 335 | dist_coeffs = self.dist_coeffs 336 | if orig_size is None: 337 | orig_size = self.orig_size 338 | vertices = nr.projection(vertices, K, R, t, dist_coeffs, orig_size) 339 | elif self.camera_mode == 'weak_projection': 340 | if K is None: 341 | K = self.K 342 | if R is None: 343 | R = self.R 344 | if t is None: 345 | t = self.t 346 | 347 | vertices = weak_projection(vertices, K, R, t, orig_size) 348 | 349 | vertices[:, :, 0] = (orig_size / 2) * vertices[:, :, 0] + (orig_size / 2) 350 | vertices[:, :, 1] = orig_size - ((orig_size / 2) * vertices[:, :, 1] + (orig_size / 2)) 351 | return vertices 352 | --------------------------------------------------------------------------------