ozyman commited on
Commit
ddadf19
·
1 Parent(s): 03d287b

added depth model

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. app.py +45 -17
  2. requirements.txt +7 -0
  3. tddfa/Sim3DR/.gitignore +8 -0
  4. tddfa/Sim3DR/Sim3DR.py +29 -0
  5. tddfa/Sim3DR/__init__.py +4 -0
  6. tddfa/Sim3DR/_init_paths.py +14 -0
  7. tddfa/Sim3DR/build_sim3dr.sh +1 -0
  8. tddfa/Sim3DR/lib/rasterize.h +115 -0
  9. tddfa/Sim3DR/lib/rasterize.pyx +134 -0
  10. tddfa/Sim3DR/lib/rasterize_kernel.cpp +499 -0
  11. tddfa/Sim3DR/lighting.py +79 -0
  12. tddfa/Sim3DR/readme.md +8 -0
  13. tddfa/Sim3DR/setup.py +19 -0
  14. tddfa/Sim3DR/tests/.gitignore +1 -0
  15. tddfa/Sim3DR/tests/CMakeLists.txt +13 -0
  16. tddfa/Sim3DR/tests/io.cpp +89 -0
  17. tddfa/Sim3DR/tests/io.h +20 -0
  18. tddfa/Sim3DR/tests/test.cpp +172 -0
  19. tddfa/TDDFA.py +143 -0
  20. tddfa/TDDFA_ONNX.py +118 -0
  21. tddfa/bfm/.gitignore +1 -0
  22. tddfa/bfm/__init__.py +1 -0
  23. tddfa/bfm/bfm.py +40 -0
  24. tddfa/bfm/bfm_onnx.py +98 -0
  25. tddfa/bfm/readme.md +23 -0
  26. tddfa/build.sh +7 -0
  27. tddfa/configs/.gitignore +3 -0
  28. tddfa/configs/BFM_UV.mat +0 -0
  29. tddfa/configs/indices.npy +0 -0
  30. tddfa/configs/ncc_code.npy +0 -0
  31. tddfa/configs/readme.md +3 -0
  32. tddfa/models/__init__.py +3 -0
  33. tddfa/models/mobilenet_v1.py +163 -0
  34. tddfa/models/mobilenet_v3.py +246 -0
  35. tddfa/models/resnet.py +150 -0
  36. tddfa/utils/__init__.py +0 -0
  37. tddfa/utils/asset/.gitignore +1 -0
  38. tddfa/utils/asset/build_render_ctypes.sh +1 -0
  39. tddfa/utils/asset/render.c +233 -0
  40. tddfa/utils/depth.py +43 -0
  41. tddfa/utils/functions.py +182 -0
  42. tddfa/utils/io.py +64 -0
  43. tddfa/utils/onnx.py +40 -0
  44. tddfa/utils/pncc.py +64 -0
  45. tddfa/utils/pose.py +141 -0
  46. tddfa/utils/render.py +52 -0
  47. tddfa/utils/render_ctypes.py +89 -0
  48. tddfa/utils/serialization.py +146 -0
  49. tddfa/utils/tddfa_util.py +102 -0
  50. tddfa/utils/uv.py +100 -0
app.py CHANGED
@@ -1,10 +1,22 @@
1
  import gradio as gr
2
  from gradio.components import Dropdown
 
3
  import cv2 as cv
4
  import torch
5
  from torchvision import transforms
6
  from DeePixBiS.Model import DeePixBiS
7
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  labels = ['Live', 'Spoof']
10
  thresh = 0.45
@@ -25,6 +37,8 @@ model = DeePixBiS(pretrained=False)
25
  model.load_state_dict(torch.load('./DeePixBiS/DeePixBiS.pth'))
26
  model.eval()
27
 
 
 
28
 
29
  def find_largest_face(faces):
30
  largest_face = None
@@ -37,21 +51,21 @@ def find_largest_face(faces):
37
  largest_face = (x, y, w, h)
38
  return largest_face
39
 
40
-
41
  def inference(img, model_name):
42
  confidences = {}
43
- if model_name == 'DeePixBiS':
44
- grey = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
45
- faces = faceClassifier.detectMultiScale(
46
- grey, scaleFactor=1.1, minNeighbors=4)
47
- face = find_largest_face(faces)
48
-
49
- if face is not None:
50
- x, y, w, h = face
51
- faceRegion = img[y:y + h, x:x + w]
52
- faceRegion = cv.cvtColor(faceRegion, cv.COLOR_BGR2RGB)
53
- faceRegion = tfms(faceRegion)
54
- faceRegion = faceRegion.unsqueeze(0)
 
55
  mask, binary = model.forward(faceRegion)
56
  res = torch.mean(mask).item()
57
  if res < thresh:
@@ -61,11 +75,25 @@ def inference(img, model_name):
61
  else:
62
  cls = 'Real'
63
  color = (0, 255, 0)
64
- label = f'{cls} {res:.2f}'
65
- cv.rectangle(img, (x, y), (x + w, y + h), color, 2)
66
- cv.putText(img, label, (x, y + h + 30),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  cv.FONT_HERSHEY_COMPLEX, 1, color)
68
- confidences = {label: res}
69
  return img, confidences
70
 
71
 
 
1
  import gradio as gr
2
  from gradio.components import Dropdown
3
+
4
  import cv2 as cv
5
  import torch
6
  from torchvision import transforms
7
  from DeePixBiS.Model import DeePixBiS
8
 
9
+ import yaml
10
+ import numpy as np
11
+ import pandas as pd
12
+ from skimage.io import imread, imsave
13
+ # from tddfa.TDDFA import TDDFA
14
+ from tddfa.utils.depth import depth
15
+ from tddfa.TDDFA_ONNX import TDDFA_ONNX
16
+
17
+ import os
18
+ os.environ['KMP_DUPLICATE_LIB_OK'] = 'True'
19
+ os.environ['OMP_NUM_THREADS'] = '4'
20
 
21
  labels = ['Live', 'Spoof']
22
  thresh = 0.45
 
37
  model.load_state_dict(torch.load('./DeePixBiS/DeePixBiS.pth'))
38
  model.eval()
39
 
40
+ cfg = yaml.load(open('tddfa/configs/mb1_120x120.yml'), Loader=yaml.SafeLoader)
41
+ tddfa = TDDFA_ONNX(gpu_mode=False, **cfg)
42
 
43
  def find_largest_face(faces):
44
  largest_face = None
 
51
  largest_face = (x, y, w, h)
52
  return largest_face
53
 
 
54
  def inference(img, model_name):
55
  confidences = {}
56
+ grey = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
57
+ faces = faceClassifier.detectMultiScale(
58
+ grey, scaleFactor=1.1, minNeighbors=4)
59
+ face = find_largest_face(faces)
60
+
61
+ if face is not None:
62
+ x, y, w, h = face
63
+ faceRegion = img[y:y + h, x:x + w]
64
+ faceRegion = cv.cvtColor(faceRegion, cv.COLOR_BGR2RGB)
65
+ faceRegion = tfms(faceRegion)
66
+ faceRegion = faceRegion.unsqueeze(0)
67
+
68
+ if model_name == 'DeePixBiS':
69
  mask, binary = model.forward(faceRegion)
70
  res = torch.mean(mask).item()
71
  if res < thresh:
 
75
  else:
76
  cls = 'Real'
77
  color = (0, 255, 0)
78
+
79
+ else:
80
+ dense_flag = True
81
+ boxes = list(face)
82
+ boxes.append(1)
83
+ param_lst, roi_box_lst = tddfa(img, [boxes])
84
+
85
+ ver_lst = tddfa.recon_vers(param_lst, roi_box_lst, dense_flag=dense_flag)
86
+ img = depth(img, ver_lst, tddfa.tri, with_bg_flag=False)
87
+ cls = 'Other'
88
+ res = 0.5
89
+ color = (0, 0, 255)
90
+
91
+ label = f'{cls} {res:.2f}'
92
+ confidences = {label: res}
93
+ cv.rectangle(img, (x, y), (x + w, y + h), color, 2)
94
+ cv.putText(img, label, (x, y + h + 30),
95
  cv.FONT_HERSHEY_COMPLEX, 1, color)
96
+
97
  return img, confidences
98
 
99
 
requirements.txt CHANGED
@@ -2,4 +2,11 @@ torch
2
  torchvision
3
  numpy
4
  opencv-python
 
 
 
 
 
 
 
5
  --index-url=https://download.pytorch.org/whl/cpu --extra-index-url=https://pypi.org/simple
 
2
  torchvision
3
  numpy
4
  opencv-python
5
+ pyyaml
6
+ onnx
7
+ scikit-image # skimage
8
+ scipy
9
+ onnx
10
+ onnxruntime
11
+ cython
12
  --index-url=https://download.pytorch.org/whl/cpu --extra-index-url=https://pypi.org/simple
tddfa/Sim3DR/.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ .DS_Store
2
+ cmake-build-debug/
3
+ .idea/
4
+ build/
5
+ *.so
6
+ data/
7
+
8
+ lib/rasterize.cpp
tddfa/Sim3DR/Sim3DR.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ from . import _init_paths
4
+ import numpy as np
5
+ import Sim3DR_Cython
6
+
7
+
8
+ def get_normal(vertices, triangles):
9
+ normal = np.zeros_like(vertices, dtype=np.float32)
10
+ Sim3DR_Cython.get_normal(normal, vertices, triangles, vertices.shape[0], triangles.shape[0])
11
+ return normal
12
+
13
+
14
+ def rasterize(vertices, triangles, colors, bg=None,
15
+ height=None, width=None, channel=None,
16
+ reverse=False):
17
+ if bg is not None:
18
+ height, width, channel = bg.shape
19
+ else:
20
+ assert height is not None and width is not None and channel is not None
21
+ bg = np.zeros((height, width, channel), dtype=np.uint8)
22
+
23
+ buffer = np.zeros((height, width), dtype=np.float32) - 1e8
24
+
25
+ if colors.dtype != np.float32:
26
+ colors = colors.astype(np.float32)
27
+ Sim3DR_Cython.rasterize(bg, vertices, triangles, colors, buffer, triangles.shape[0], height, width, channel,
28
+ reverse=reverse)
29
+ return bg
tddfa/Sim3DR/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ from .Sim3DR import get_normal, rasterize
4
+ from .lighting import RenderPipeline
tddfa/Sim3DR/_init_paths.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ import os.path as osp
4
+ import sys
5
+
6
+
7
+ def add_path(path):
8
+ if path not in sys.path:
9
+ sys.path.insert(0, path)
10
+
11
+
12
+ this_dir = osp.dirname(__file__)
13
+ lib_path = osp.join(this_dir, '.')
14
+ add_path(lib_path)
tddfa/Sim3DR/build_sim3dr.sh ADDED
@@ -0,0 +1 @@
 
 
1
+ python3 setup.py build_ext --inplace
tddfa/Sim3DR/lib/rasterize.h ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef MESH_CORE_HPP_
2
+ #define MESH_CORE_HPP_
3
+
4
+ #include <stdio.h>
5
+ #include <cmath>
6
+ #include <algorithm>
7
+ #include <string>
8
+ #include <iostream>
9
+ #include <fstream>
10
+
11
+ using namespace std;
12
+
13
+ class Point3D {
14
+ public:
15
+ float x;
16
+ float y;
17
+ float z;
18
+
19
+ public:
20
+ Point3D() : x(0.f), y(0.f), z(0.f) {}
21
+ Point3D(float x_, float y_, float z_) : x(x_), y(y_), z(z_) {}
22
+
23
+ void initialize(float x_, float y_, float z_){
24
+ this->x = x_; this->y = y_; this->z = z_;
25
+ }
26
+
27
+ Point3D cross(Point3D &p){
28
+ Point3D c;
29
+ c.x = this->y * p.z - this->z * p.y;
30
+ c.y = this->z * p.x - this->x * p.z;
31
+ c.z = this->x * p.y - this->y * p.x;
32
+ return c;
33
+ }
34
+
35
+ float dot(Point3D &p) {
36
+ return this->x * p.x + this->y * p.y + this->z * p.z;
37
+ }
38
+
39
+ Point3D operator-(const Point3D &p) {
40
+ Point3D np;
41
+ np.x = this->x - p.x;
42
+ np.y = this->y - p.y;
43
+ np.z = this->z - p.z;
44
+ return np;
45
+ }
46
+
47
+ };
48
+
49
+ class Point {
50
+ public:
51
+ float x;
52
+ float y;
53
+
54
+ public:
55
+ Point() : x(0.f), y(0.f) {}
56
+ Point(float x_, float y_) : x(x_), y(y_) {}
57
+ float dot(Point p) {
58
+ return this->x * p.x + this->y * p.y;
59
+ }
60
+
61
+ Point operator-(const Point &p) {
62
+ Point np;
63
+ np.x = this->x - p.x;
64
+ np.y = this->y - p.y;
65
+ return np;
66
+ }
67
+
68
+ Point operator+(const Point &p) {
69
+ Point np;
70
+ np.x = this->x + p.x;
71
+ np.y = this->y + p.y;
72
+ return np;
73
+ }
74
+
75
+ Point operator*(float s) {
76
+ Point np;
77
+ np.x = s * this->x;
78
+ np.y = s * this->y;
79
+ return np;
80
+ }
81
+ };
82
+
83
+
84
+ bool is_point_in_tri(Point p, Point p0, Point p1, Point p2);
85
+
86
+ void get_point_weight(float *weight, Point p, Point p0, Point p1, Point p2);
87
+
88
+ void _get_tri_normal(float *tri_normal, float *vertices, int *triangles, int ntri, bool norm_flg);
89
+
90
+ void _get_ver_normal(float *ver_normal, float *tri_normal, int *triangles, int nver, int ntri);
91
+
92
+ void _get_normal(float *ver_normal, float *vertices, int *triangles, int nver, int ntri);
93
+
94
+ void _rasterize_triangles(
95
+ float *vertices, int *triangles, float *depth_buffer, int *triangle_buffer, float *barycentric_weight,
96
+ int ntri, int h, int w);
97
+
98
+ void _rasterize(
99
+ unsigned char *image, float *vertices, int *triangles, float *colors,
100
+ float *depth_buffer, int ntri, int h, int w, int c, float alpha, bool reverse);
101
+
102
+ void _render_texture_core(
103
+ float *image, float *vertices, int *triangles,
104
+ float *texture, float *tex_coords, int *tex_triangles,
105
+ float *depth_buffer,
106
+ int nver, int tex_nver, int ntri,
107
+ int h, int w, int c,
108
+ int tex_h, int tex_w, int tex_c,
109
+ int mapping_type);
110
+
111
+ void _write_obj_with_colors_texture(string filename, string mtl_name,
112
+ float *vertices, int *triangles, float *colors, float *uv_coords,
113
+ int nver, int ntri, int ntexver);
114
+
115
+ #endif
tddfa/Sim3DR/lib/rasterize.pyx ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ cimport numpy as np
3
+ # from libcpp.string cimport string
4
+ cimport cython
5
+ from libcpp cimport bool
6
+
7
+ # from cpython import bool
8
+
9
+ # use the Numpy-C-API from Cython
10
+ np.import_array()
11
+
12
+ # cdefine the signature of our c function
13
+ cdef extern from "rasterize.h":
14
+ void _rasterize_triangles(
15
+ float*vertices, int*triangles, float*depth_buffer, int*triangle_buffer, float*barycentric_weight,
16
+ int ntri, int h, int w
17
+ )
18
+
19
+ void _rasterize(
20
+ unsigned char*image, float*vertices, int*triangles, float*colors, float*depth_buffer,
21
+ int ntri, int h, int w, int c, float alpha, bool reverse
22
+ )
23
+
24
+ # void _render_texture_core(
25
+ # float* image, float* vertices, int* triangles,
26
+ # float* texture, float* tex_coords, int* tex_triangles,
27
+ # float* depth_buffer,
28
+ # int nver, int tex_nver, int ntri,
29
+ # int h, int w, int c,
30
+ # int tex_h, int tex_w, int tex_c,
31
+ # int mapping_type)
32
+
33
+ void _get_tri_normal(float *tri_normal, float *vertices, int *triangles, int nver, bool norm_flg)
34
+ void _get_ver_normal(float *ver_normal, float*tri_normal, int*triangles, int nver, int ntri)
35
+ void _get_normal(float *ver_normal, float *vertices, int *triangles, int nver, int ntri)
36
+
37
+
38
+ # void _write_obj_with_colors_texture(string filename, string mtl_name,
39
+ # float* vertices, int* triangles, float* colors, float* uv_coords,
40
+ # int nver, int ntri, int ntexver)
41
+
42
+ @cython.boundscheck(False)
43
+ @cython.wraparound(False)
44
+ def get_tri_normal(np.ndarray[float, ndim=2, mode="c"] tri_normal not None,
45
+ np.ndarray[float, ndim=2, mode = "c"] vertices not None,
46
+ np.ndarray[int, ndim=2, mode="c"] triangles not None,
47
+ int ntri, bool norm_flg = False):
48
+ _get_tri_normal(<float*> np.PyArray_DATA(tri_normal), <float*> np.PyArray_DATA(vertices),
49
+ <int*> np.PyArray_DATA(triangles), ntri, norm_flg)
50
+
51
+ @cython.boundscheck(False) # turn off bounds-checking for entire function
52
+ @cython.wraparound(False) # turn off negative index wrapping for entire function
53
+ def get_ver_normal(np.ndarray[float, ndim=2, mode = "c"] ver_normal not None,
54
+ np.ndarray[float, ndim=2, mode = "c"] tri_normal not None,
55
+ np.ndarray[int, ndim=2, mode="c"] triangles not None,
56
+ int nver, int ntri):
57
+ _get_ver_normal(
58
+ <float*> np.PyArray_DATA(ver_normal), <float*> np.PyArray_DATA(tri_normal), <int*> np.PyArray_DATA(triangles),
59
+ nver, ntri)
60
+
61
+ @cython.boundscheck(False) # turn off bounds-checking for entire function
62
+ @cython.wraparound(False) # turn off negative index wrapping for entire function
63
+ def get_normal(np.ndarray[float, ndim=2, mode = "c"] ver_normal not None,
64
+ np.ndarray[float, ndim=2, mode = "c"] vertices not None,
65
+ np.ndarray[int, ndim=2, mode="c"] triangles not None,
66
+ int nver, int ntri):
67
+ _get_normal(
68
+ <float*> np.PyArray_DATA(ver_normal), <float*> np.PyArray_DATA(vertices), <int*> np.PyArray_DATA(triangles),
69
+ nver, ntri)
70
+
71
+
72
+ @cython.boundscheck(False) # turn off bounds-checking for entire function
73
+ @cython.wraparound(False) # turn off negative index wrapping for entire function
74
+ def rasterize_triangles(
75
+ np.ndarray[float, ndim=2, mode = "c"] vertices not None,
76
+ np.ndarray[int, ndim=2, mode="c"] triangles not None,
77
+ np.ndarray[float, ndim=2, mode = "c"] depth_buffer not None,
78
+ np.ndarray[int, ndim=2, mode = "c"] triangle_buffer not None,
79
+ np.ndarray[float, ndim=2, mode = "c"] barycentric_weight not None,
80
+ int ntri, int h, int w
81
+ ):
82
+ _rasterize_triangles(
83
+ <float*> np.PyArray_DATA(vertices), <int*> np.PyArray_DATA(triangles),
84
+ <float*> np.PyArray_DATA(depth_buffer), <int*> np.PyArray_DATA(triangle_buffer),
85
+ <float*> np.PyArray_DATA(barycentric_weight),
86
+ ntri, h, w)
87
+
88
+ @cython.boundscheck(False) # turn off bounds-checking for entire function
89
+ @cython.wraparound(False) # turn off negative index wrapping for entire function
90
+ def rasterize(np.ndarray[unsigned char, ndim=3, mode = "c"] image not None,
91
+ np.ndarray[float, ndim=2, mode = "c"] vertices not None,
92
+ np.ndarray[int, ndim=2, mode="c"] triangles not None,
93
+ np.ndarray[float, ndim=2, mode = "c"] colors not None,
94
+ np.ndarray[float, ndim=2, mode = "c"] depth_buffer not None,
95
+ int ntri, int h, int w, int c, float alpha = 1, bool reverse = False
96
+ ):
97
+ _rasterize(
98
+ <unsigned char*> np.PyArray_DATA(image), <float*> np.PyArray_DATA(vertices),
99
+ <int*> np.PyArray_DATA(triangles),
100
+ <float*> np.PyArray_DATA(colors),
101
+ <float*> np.PyArray_DATA(depth_buffer),
102
+ ntri, h, w, c, alpha, reverse)
103
+
104
+ # def render_texture_core(np.ndarray[float, ndim=3, mode = "c"] image not None,
105
+ # np.ndarray[float, ndim=2, mode = "c"] vertices not None,
106
+ # np.ndarray[int, ndim=2, mode="c"] triangles not None,
107
+ # np.ndarray[float, ndim=3, mode = "c"] texture not None,
108
+ # np.ndarray[float, ndim=2, mode = "c"] tex_coords not None,
109
+ # np.ndarray[int, ndim=2, mode="c"] tex_triangles not None,
110
+ # np.ndarray[float, ndim=2, mode = "c"] depth_buffer not None,
111
+ # int nver, int tex_nver, int ntri,
112
+ # int h, int w, int c,
113
+ # int tex_h, int tex_w, int tex_c,
114
+ # int mapping_type
115
+ # ):
116
+ # _render_texture_core(
117
+ # <float*> np.PyArray_DATA(image), <float*> np.PyArray_DATA(vertices), <int*> np.PyArray_DATA(triangles),
118
+ # <float*> np.PyArray_DATA(texture), <float*> np.PyArray_DATA(tex_coords), <int*> np.PyArray_DATA(tex_triangles),
119
+ # <float*> np.PyArray_DATA(depth_buffer),
120
+ # nver, tex_nver, ntri,
121
+ # h, w, c,
122
+ # tex_h, tex_w, tex_c,
123
+ # mapping_type)
124
+ #
125
+ # def write_obj_with_colors_texture_core(string filename, string mtl_name,
126
+ # np.ndarray[float, ndim=2, mode = "c"] vertices not None,
127
+ # np.ndarray[int, ndim=2, mode="c"] triangles not None,
128
+ # np.ndarray[float, ndim=2, mode = "c"] colors not None,
129
+ # np.ndarray[float, ndim=2, mode = "c"] uv_coords not None,
130
+ # int nver, int ntri, int ntexver
131
+ # ):
132
+ # _write_obj_with_colors_texture(filename, mtl_name,
133
+ # <float*> np.PyArray_DATA(vertices), <int*> np.PyArray_DATA(triangles), <float*> np.PyArray_DATA(colors), <float*> np.PyArray_DATA(uv_coords),
134
+ # nver, ntri, ntexver)
tddfa/Sim3DR/lib/rasterize_kernel.cpp ADDED
@@ -0,0 +1,499 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ Author: Yao Feng
3
+ Modified by Jianzhu Guo
4
+
5
+ functions that can not be optimazed by vertorization in python.
6
+ 1. rasterization.(need process each triangle)
7
+ 2. normal of each vertex.(use one-ring, need process each vertex)
8
+ 3. write obj(seems that it can be verctorized? anyway, writing it in c++ is simple, so also add function here. --> however, why writting in c++ is still slow?)
9
+
10
+
11
+
12
+ */
13
+
14
+ #include "rasterize.h"
15
+
16
+
17
+ /* Judge whether the Point is in the triangle
18
+ Method:
19
+ http://blackpawn.com/texts/pointinpoly/
20
+ Args:
21
+ Point: [x, y]
22
+ tri_points: three vertices(2d points) of a triangle. 2 coords x 3 vertices
23
+ Returns:
24
+ bool: true for in triangle
25
+ */
26
+ bool is_point_in_tri(Point p, Point p0, Point p1, Point p2) {
27
+ // vectors
28
+ Point v0, v1, v2;
29
+ v0 = p2 - p0;
30
+ v1 = p1 - p0;
31
+ v2 = p - p0;
32
+
33
+ // dot products
34
+ float dot00 = v0.dot(v0); //v0.x * v0.x + v0.y * v0.y //np.dot(v0.T, v0)
35
+ float dot01 = v0.dot(v1); //v0.x * v1.x + v0.y * v1.y //np.dot(v0.T, v1)
36
+ float dot02 = v0.dot(v2); //v0.x * v2.x + v0.y * v2.y //np.dot(v0.T, v2)
37
+ float dot11 = v1.dot(v1); //v1.x * v1.x + v1.y * v1.y //np.dot(v1.T, v1)
38
+ float dot12 = v1.dot(v2); //v1.x * v2.x + v1.y * v2.y//np.dot(v1.T, v2)
39
+
40
+ // barycentric coordinates
41
+ float inverDeno;
42
+ if (dot00 * dot11 - dot01 * dot01 == 0)
43
+ inverDeno = 0;
44
+ else
45
+ inverDeno = 1 / (dot00 * dot11 - dot01 * dot01);
46
+
47
+ float u = (dot11 * dot02 - dot01 * dot12) * inverDeno;
48
+ float v = (dot00 * dot12 - dot01 * dot02) * inverDeno;
49
+
50
+ // check if Point in triangle
51
+ return (u >= 0) && (v >= 0) && (u + v < 1);
52
+ }
53
+
54
+ void get_point_weight(float *weight, Point p, Point p0, Point p1, Point p2) {
55
+ // vectors
56
+ Point v0, v1, v2;
57
+ v0 = p2 - p0;
58
+ v1 = p1 - p0;
59
+ v2 = p - p0;
60
+
61
+ // dot products
62
+ float dot00 = v0.dot(v0); //v0.x * v0.x + v0.y * v0.y //np.dot(v0.T, v0)
63
+ float dot01 = v0.dot(v1); //v0.x * v1.x + v0.y * v1.y //np.dot(v0.T, v1)
64
+ float dot02 = v0.dot(v2); //v0.x * v2.x + v0.y * v2.y //np.dot(v0.T, v2)
65
+ float dot11 = v1.dot(v1); //v1.x * v1.x + v1.y * v1.y //np.dot(v1.T, v1)
66
+ float dot12 = v1.dot(v2); //v1.x * v2.x + v1.y * v2.y//np.dot(v1.T, v2)
67
+
68
+ // barycentric coordinates
69
+ float inverDeno;
70
+ if (dot00 * dot11 - dot01 * dot01 == 0)
71
+ inverDeno = 0;
72
+ else
73
+ inverDeno = 1 / (dot00 * dot11 - dot01 * dot01);
74
+
75
+ float u = (dot11 * dot02 - dot01 * dot12) * inverDeno;
76
+ float v = (dot00 * dot12 - dot01 * dot02) * inverDeno;
77
+
78
+ // weight
79
+ weight[0] = 1 - u - v;
80
+ weight[1] = v;
81
+ weight[2] = u;
82
+ }
83
+
84
+ /*
85
+ * Get normals of triangles.
86
+ */
87
+ void _get_tri_normal(float *tri_normal, float *vertices, int *triangles, int ntri, bool norm_flg) {
88
+ int tri_p0_ind, tri_p1_ind, tri_p2_ind;
89
+ float v1x, v1y, v1z, v2x, v2y, v2z;
90
+
91
+ for (int i = 0; i < ntri; i++) {
92
+ tri_p0_ind = triangles[3 * i];
93
+ tri_p1_ind = triangles[3 * i + 1];
94
+ tri_p2_ind = triangles[3 * i + 2];
95
+
96
+ // counter clockwise order
97
+ v1x = vertices[3 * tri_p1_ind] - vertices[3 * tri_p0_ind];
98
+ v1y = vertices[3 * tri_p1_ind + 1] - vertices[3 * tri_p0_ind + 1];
99
+ v1z = vertices[3 * tri_p1_ind + 2] - vertices[3 * tri_p0_ind + 2];
100
+
101
+ v2x = vertices[3 * tri_p2_ind] - vertices[3 * tri_p0_ind];
102
+ v2y = vertices[3 * tri_p2_ind + 1] - vertices[3 * tri_p0_ind + 1];
103
+ v2z = vertices[3 * tri_p2_ind + 2] - vertices[3 * tri_p0_ind + 2];
104
+
105
+ if (norm_flg) {
106
+ float c1 = v1y * v2z - v1z * v2y;
107
+ float c2 = v1z * v2x - v1x * v2z;
108
+ float c3 = v1x * v2y - v1y * v2x;
109
+ float det = sqrt(c1 * c1 + c2 * c2 + c3 * c3);
110
+ if (det <= 0) det = 1e-6;
111
+ tri_normal[3 * i] = c1 / det;
112
+ tri_normal[3 * i + 1] = c2 / det;
113
+ tri_normal[3 * i + 2] = c3 / det;
114
+ } else {
115
+ tri_normal[3 * i] = v1y * v2z - v1z * v2y;
116
+ tri_normal[3 * i + 1] = v1z * v2x - v1x * v2z;
117
+ tri_normal[3 * i + 2] = v1x * v2y - v1y * v2x;
118
+ }
119
+ }
120
+ }
121
+
122
+ /*
123
+ * Get normal vector of vertices using triangle normals
124
+ */
125
+ void _get_ver_normal(float *ver_normal, float *tri_normal, int *triangles, int nver, int ntri) {
126
+ int tri_p0_ind, tri_p1_ind, tri_p2_ind;
127
+
128
+ for (int i = 0; i < ntri; i++) {
129
+ tri_p0_ind = triangles[3 * i];
130
+ tri_p1_ind = triangles[3 * i + 1];
131
+ tri_p2_ind = triangles[3 * i + 2];
132
+
133
+ for (int j = 0; j < 3; j++) {
134
+ ver_normal[3 * tri_p0_ind + j] += tri_normal[3 * i + j];
135
+ ver_normal[3 * tri_p1_ind + j] += tri_normal[3 * i + j];
136
+ ver_normal[3 * tri_p2_ind + j] += tri_normal[3 * i + j];
137
+ }
138
+ }
139
+
140
+ // normalizing
141
+ float nx, ny, nz, det;
142
+ for (int i = 0; i < nver; ++i) {
143
+ nx = ver_normal[3 * i];
144
+ ny = ver_normal[3 * i + 1];
145
+ nz = ver_normal[3 * i + 2];
146
+
147
+ det = sqrt(nx * nx + ny * ny + nz * nz);
148
+ if (det <= 0) det = 1e-6;
149
+ ver_normal[3 * i] = nx / det;
150
+ ver_normal[3 * i + 1] = ny / det;
151
+ ver_normal[3 * i + 2] = nz / det;
152
+ }
153
+ }
154
+
155
+ /*
156
+ * Directly get normal of vertices, which can be regraded as a combination of _get_tri_normal and _get_ver_normal
157
+ */
158
+ void _get_normal(float *ver_normal, float *vertices, int *triangles, int nver, int ntri) {
159
+ int tri_p0_ind, tri_p1_ind, tri_p2_ind;
160
+ float v1x, v1y, v1z, v2x, v2y, v2z;
161
+
162
+ // get tri_normal
163
+ // float tri_normal[3 * ntri];
164
+ float *tri_normal;
165
+ tri_normal = new float[3 * ntri];
166
+ for (int i = 0; i < ntri; i++) {
167
+ tri_p0_ind = triangles[3 * i];
168
+ tri_p1_ind = triangles[3 * i + 1];
169
+ tri_p2_ind = triangles[3 * i + 2];
170
+
171
+ // counter clockwise order
172
+ v1x = vertices[3 * tri_p1_ind] - vertices[3 * tri_p0_ind];
173
+ v1y = vertices[3 * tri_p1_ind + 1] - vertices[3 * tri_p0_ind + 1];
174
+ v1z = vertices[3 * tri_p1_ind + 2] - vertices[3 * tri_p0_ind + 2];
175
+
176
+ v2x = vertices[3 * tri_p2_ind] - vertices[3 * tri_p0_ind];
177
+ v2y = vertices[3 * tri_p2_ind + 1] - vertices[3 * tri_p0_ind + 1];
178
+ v2z = vertices[3 * tri_p2_ind + 2] - vertices[3 * tri_p0_ind + 2];
179
+
180
+
181
+ tri_normal[3 * i] = v1y * v2z - v1z * v2y;
182
+ tri_normal[3 * i + 1] = v1z * v2x - v1x * v2z;
183
+ tri_normal[3 * i + 2] = v1x * v2y - v1y * v2x;
184
+
185
+ }
186
+
187
+ // get ver_normal
188
+ for (int i = 0; i < ntri; i++) {
189
+ tri_p0_ind = triangles[3 * i];
190
+ tri_p1_ind = triangles[3 * i + 1];
191
+ tri_p2_ind = triangles[3 * i + 2];
192
+
193
+ for (int j = 0; j < 3; j++) {
194
+ ver_normal[3 * tri_p0_ind + j] += tri_normal[3 * i + j];
195
+ ver_normal[3 * tri_p1_ind + j] += tri_normal[3 * i + j];
196
+ ver_normal[3 * tri_p2_ind + j] += tri_normal[3 * i + j];
197
+ }
198
+ }
199
+
200
+ // normalizing
201
+ float nx, ny, nz, det;
202
+ for (int i = 0; i < nver; ++i) {
203
+ nx = ver_normal[3 * i];
204
+ ny = ver_normal[3 * i + 1];
205
+ nz = ver_normal[3 * i + 2];
206
+
207
+ det = sqrt(nx * nx + ny * ny + nz * nz);
208
+ if (det <= 0) det = 1e-6;
209
+ ver_normal[3 * i] = nx / det;
210
+ ver_normal[3 * i + 1] = ny / det;
211
+ ver_normal[3 * i + 2] = nz / det;
212
+ }
213
+
214
+ delete[] tri_normal;
215
+ }
216
+
217
+ // rasterization by Z-Buffer with optimization
218
+ // Complexity: < ntri * h * w * c
219
+ void _rasterize(
220
+ unsigned char *image, float *vertices, int *triangles, float *colors, float *depth_buffer,
221
+ int ntri, int h, int w, int c, float alpha, bool reverse) {
222
+ int x, y, k;
223
+ int tri_p0_ind, tri_p1_ind, tri_p2_ind;
224
+ Point p0, p1, p2, p;
225
+ int x_min, x_max, y_min, y_max;
226
+ float p_depth, p0_depth, p1_depth, p2_depth;
227
+ float p_color, p0_color, p1_color, p2_color;
228
+ float weight[3];
229
+
230
+ for (int i = 0; i < ntri; i++) {
231
+ tri_p0_ind = triangles[3 * i];
232
+ tri_p1_ind = triangles[3 * i + 1];
233
+ tri_p2_ind = triangles[3 * i + 2];
234
+
235
+ p0.x = vertices[3 * tri_p0_ind];
236
+ p0.y = vertices[3 * tri_p0_ind + 1];
237
+ p0_depth = vertices[3 * tri_p0_ind + 2];
238
+ p1.x = vertices[3 * tri_p1_ind];
239
+ p1.y = vertices[3 * tri_p1_ind + 1];
240
+ p1_depth = vertices[3 * tri_p1_ind + 2];
241
+ p2.x = vertices[3 * tri_p2_ind];
242
+ p2.y = vertices[3 * tri_p2_ind + 1];
243
+ p2_depth = vertices[3 * tri_p2_ind + 2];
244
+
245
+ x_min = max((int) ceil(min(p0.x, min(p1.x, p2.x))), 0);
246
+ x_max = min((int) floor(max(p0.x, max(p1.x, p2.x))), w - 1);
247
+
248
+ y_min = max((int) ceil(min(p0.y, min(p1.y, p2.y))), 0);
249
+ y_max = min((int) floor(max(p0.y, max(p1.y, p2.y))), h - 1);
250
+
251
+ if (x_max < x_min || y_max < y_min) {
252
+ continue;
253
+ }
254
+
255
+ for (y = y_min; y <= y_max; y++) {
256
+ for (x = x_min; x <= x_max; x++) {
257
+ p.x = float(x);
258
+ p.y = float(y);
259
+
260
+ // call get_point_weight function once
261
+ get_point_weight(weight, p, p0, p1, p2);
262
+
263
+ // and judge is_point_in_tri by below line of code
264
+ if (weight[2] > 0 && weight[1] > 0 && weight[0] > 0) {
265
+ get_point_weight(weight, p, p0, p1, p2);
266
+ p_depth = weight[0] * p0_depth + weight[1] * p1_depth + weight[2] * p2_depth;
267
+
268
+ if ((p_depth > depth_buffer[y * w + x])) {
269
+ for (k = 0; k < c; k++) {
270
+ p0_color = colors[c * tri_p0_ind + k];
271
+ p1_color = colors[c * tri_p1_ind + k];
272
+ p2_color = colors[c * tri_p2_ind + k];
273
+
274
+ p_color = weight[0] * p0_color + weight[1] * p1_color + weight[2] * p2_color;
275
+ if (reverse) {
276
+ image[(h - 1 - y) * w * c + x * c + k] = (unsigned char) (
277
+ (1 - alpha) * image[(h - 1 - y) * w * c + x * c + k] + alpha * 255 * p_color);
278
+ // image[(h - 1 - y) * w * c + x * c + k] = (unsigned char) (255 * p_color);
279
+ } else {
280
+ image[y * w * c + x * c + k] = (unsigned char) (
281
+ (1 - alpha) * image[y * w * c + x * c + k] + alpha * 255 * p_color);
282
+ // image[y * w * c + x * c + k] = (unsigned char) (255 * p_color);
283
+ }
284
+ }
285
+
286
+ depth_buffer[y * w + x] = p_depth;
287
+ }
288
+ }
289
+ }
290
+ }
291
+ }
292
+ }
293
+
294
+
295
+ void _rasterize_triangles(
296
+ float *vertices, int *triangles, float *depth_buffer, int *triangle_buffer, float *barycentric_weight,
297
+ int ntri, int h, int w) {
298
+ int i;
299
+ int x, y, k;
300
+ int tri_p0_ind, tri_p1_ind, tri_p2_ind;
301
+ Point p0, p1, p2, p;
302
+ int x_min, x_max, y_min, y_max;
303
+ float p_depth, p0_depth, p1_depth, p2_depth;
304
+ float weight[3];
305
+
306
+ for (i = 0; i < ntri; i++) {
307
+ tri_p0_ind = triangles[3 * i];
308
+ tri_p1_ind = triangles[3 * i + 1];
309
+ tri_p2_ind = triangles[3 * i + 2];
310
+
311
+ p0.x = vertices[3 * tri_p0_ind];
312
+ p0.y = vertices[3 * tri_p0_ind + 1];
313
+ p0_depth = vertices[3 * tri_p0_ind + 2];
314
+ p1.x = vertices[3 * tri_p1_ind];
315
+ p1.y = vertices[3 * tri_p1_ind + 1];
316
+ p1_depth = vertices[3 * tri_p1_ind + 2];
317
+ p2.x = vertices[3 * tri_p2_ind];
318
+ p2.y = vertices[3 * tri_p2_ind + 1];
319
+ p2_depth = vertices[3 * tri_p2_ind + 2];
320
+
321
+ x_min = max((int) ceil(min(p0.x, min(p1.x, p2.x))), 0);
322
+ x_max = min((int) floor(max(p0.x, max(p1.x, p2.x))), w - 1);
323
+
324
+ y_min = max((int) ceil(min(p0.y, min(p1.y, p2.y))), 0);
325
+ y_max = min((int) floor(max(p0.y, max(p1.y, p2.y))), h - 1);
326
+
327
+ if (x_max < x_min || y_max < y_min) {
328
+ continue;
329
+ }
330
+
331
+ for (y = y_min; y <= y_max; y++) //h
332
+ {
333
+ for (x = x_min; x <= x_max; x++) //w
334
+ {
335
+ p.x = x;
336
+ p.y = y;
337
+ // if (p.x < 2 || p.x > w - 3 || p.y < 2 || p.y > h - 3 || is_point_in_tri(p, p0, p1, p2)) {
338
+ if (is_point_in_tri(p, p0, p1, p2)) {
339
+ get_point_weight(weight, p, p0, p1, p2);
340
+ p_depth = weight[0] * p0_depth + weight[1] * p1_depth + weight[2] * p2_depth;
341
+
342
+ if ((p_depth > depth_buffer[y * w + x])) {
343
+ depth_buffer[y * w + x] = p_depth;
344
+ triangle_buffer[y * w + x] = i;
345
+ for (k = 0; k < 3; k++) {
346
+ barycentric_weight[y * w * 3 + x * 3 + k] = weight[k];
347
+ }
348
+ }
349
+ }
350
+ }
351
+ }
352
+ }
353
+ }
354
+
355
+
356
+ // Depth-Buffer 算法
357
+ // https://blog.csdn.net/Jurbo/article/details/75007260
358
+ void _render_texture_core(
359
+ float *image, float *vertices, int *triangles,
360
+ float *texture, float *tex_coords, int *tex_triangles,
361
+ float *depth_buffer,
362
+ int nver, int tex_nver, int ntri,
363
+ int h, int w, int c,
364
+ int tex_h, int tex_w, int tex_c,
365
+ int mapping_type) {
366
+ int i;
367
+ int x, y, k;
368
+ int tri_p0_ind, tri_p1_ind, tri_p2_ind;
369
+ int tex_tri_p0_ind, tex_tri_p1_ind, tex_tri_p2_ind;
370
+ Point p0, p1, p2, p;
371
+ Point tex_p0, tex_p1, tex_p2, tex_p;
372
+ int x_min, x_max, y_min, y_max;
373
+ float weight[3];
374
+ float p_depth, p0_depth, p1_depth, p2_depth;
375
+ float xd, yd;
376
+ float ul, ur, dl, dr;
377
+ for (i = 0; i < ntri; i++) {
378
+ // mesh
379
+ tri_p0_ind = triangles[3 * i];
380
+ tri_p1_ind = triangles[3 * i + 1];
381
+ tri_p2_ind = triangles[3 * i + 2];
382
+
383
+ p0.x = vertices[3 * tri_p0_ind];
384
+ p0.y = vertices[3 * tri_p0_ind + 1];
385
+ p0_depth = vertices[3 * tri_p0_ind + 2];
386
+ p1.x = vertices[3 * tri_p1_ind];
387
+ p1.y = vertices[3 * tri_p1_ind + 1];
388
+ p1_depth = vertices[3 * tri_p1_ind + 2];
389
+ p2.x = vertices[3 * tri_p2_ind];
390
+ p2.y = vertices[3 * tri_p2_ind + 1];
391
+ p2_depth = vertices[3 * tri_p2_ind + 2];
392
+
393
+ // texture
394
+ tex_tri_p0_ind = tex_triangles[3 * i];
395
+ tex_tri_p1_ind = tex_triangles[3 * i + 1];
396
+ tex_tri_p2_ind = tex_triangles[3 * i + 2];
397
+
398
+ tex_p0.x = tex_coords[3 * tex_tri_p0_ind];
399
+ tex_p0.y = tex_coords[3 * tri_p0_ind + 1];
400
+ tex_p1.x = tex_coords[3 * tex_tri_p1_ind];
401
+ tex_p1.y = tex_coords[3 * tri_p1_ind + 1];
402
+ tex_p2.x = tex_coords[3 * tex_tri_p2_ind];
403
+ tex_p2.y = tex_coords[3 * tri_p2_ind + 1];
404
+
405
+
406
+ x_min = max((int) ceil(min(p0.x, min(p1.x, p2.x))), 0);
407
+ x_max = min((int) floor(max(p0.x, max(p1.x, p2.x))), w - 1);
408
+
409
+ y_min = max((int) ceil(min(p0.y, min(p1.y, p2.y))), 0);
410
+ y_max = min((int) floor(max(p0.y, max(p1.y, p2.y))), h - 1);
411
+
412
+
413
+ if (x_max < x_min || y_max < y_min) {
414
+ continue;
415
+ }
416
+
417
+ for (y = y_min; y <= y_max; y++) //h
418
+ {
419
+ for (x = x_min; x <= x_max; x++) //w
420
+ {
421
+ p.x = x;
422
+ p.y = y;
423
+ if (p.x < 2 || p.x > w - 3 || p.y < 2 || p.y > h - 3 || is_point_in_tri(p, p0, p1, p2)) {
424
+ get_point_weight(weight, p, p0, p1, p2);
425
+ p_depth = weight[0] * p0_depth + weight[1] * p1_depth + weight[2] * p2_depth;
426
+
427
+ if ((p_depth > depth_buffer[y * w + x])) {
428
+ // -- color from texture
429
+ // cal weight in mesh tri
430
+ get_point_weight(weight, p, p0, p1, p2);
431
+ // cal coord in texture
432
+ tex_p = tex_p0 * weight[0] + tex_p1 * weight[1] + tex_p2 * weight[2];
433
+ tex_p.x = max(min(tex_p.x, float(tex_w - 1)), float(0));
434
+ tex_p.y = max(min(tex_p.y, float(tex_h - 1)), float(0));
435
+
436
+ yd = tex_p.y - floor(tex_p.y);
437
+ xd = tex_p.x - floor(tex_p.x);
438
+ for (k = 0; k < c; k++) {
439
+ if (mapping_type == 0)// nearest
440
+ {
441
+ image[y * w * c + x * c + k] = texture[int(round(tex_p.y)) * tex_w * tex_c +
442
+ int(round(tex_p.x)) * tex_c + k];
443
+ } else//bilinear interp
444
+ {
445
+ ul = texture[(int) floor(tex_p.y) * tex_w * tex_c + (int) floor(tex_p.x) * tex_c + k];
446
+ ur = texture[(int) floor(tex_p.y) * tex_w * tex_c + (int) ceil(tex_p.x) * tex_c + k];
447
+ dl = texture[(int) ceil(tex_p.y) * tex_w * tex_c + (int) floor(tex_p.x) * tex_c + k];
448
+ dr = texture[(int) ceil(tex_p.y) * tex_w * tex_c + (int) ceil(tex_p.x) * tex_c + k];
449
+
450
+ image[y * w * c + x * c + k] =
451
+ ul * (1 - xd) * (1 - yd) + ur * xd * (1 - yd) + dl * (1 - xd) * yd +
452
+ dr * xd * yd;
453
+ }
454
+
455
+ }
456
+
457
+ depth_buffer[y * w + x] = p_depth;
458
+ }
459
+ }
460
+ }
461
+ }
462
+ }
463
+ }
464
+
465
+
466
+ // ------------------------------------------------- write
467
+ // obj write
468
+ // Ref: https://github.com/patrikhuber/eos/blob/master/include/eos/core/Mesh.hpp
469
+ void _write_obj_with_colors_texture(string filename, string mtl_name,
470
+ float *vertices, int *triangles, float *colors, float *uv_coords,
471
+ int nver, int ntri, int ntexver) {
472
+ int i;
473
+
474
+ ofstream obj_file(filename);
475
+
476
+ // first line of the obj file: the mtl name
477
+ obj_file << "mtllib " << mtl_name << endl;
478
+
479
+ // write vertices
480
+ for (i = 0; i < nver; ++i) {
481
+ obj_file << "v " << vertices[3 * i] << " " << vertices[3 * i + 1] << " " << vertices[3 * i + 2] << colors[3 * i]
482
+ << " " << colors[3 * i + 1] << " " << colors[3 * i + 2] << endl;
483
+ }
484
+
485
+ // write uv coordinates
486
+ for (i = 0; i < ntexver; ++i) {
487
+ //obj_file << "vt " << uv_coords[2*i] << " " << (1 - uv_coords[2*i + 1]) << endl;
488
+ obj_file << "vt " << uv_coords[2 * i] << " " << uv_coords[2 * i + 1] << endl;
489
+ }
490
+
491
+ obj_file << "usemtl FaceTexture" << endl;
492
+ // write triangles
493
+ for (i = 0; i < ntri; ++i) {
494
+ // obj_file << "f " << triangles[3*i] << "/" << triangles[3*i] << " " << triangles[3*i + 1] << "/" << triangles[3*i + 1] << " " << triangles[3*i + 2] << "/" << triangles[3*i + 2] << endl;
495
+ obj_file << "f " << triangles[3 * i + 2] << "/" << triangles[3 * i + 2] << " " << triangles[3 * i + 1] << "/"
496
+ << triangles[3 * i + 1] << " " << triangles[3 * i] << "/" << triangles[3 * i] << endl;
497
+ }
498
+
499
+ }
tddfa/Sim3DR/lighting.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ import numpy as np
4
+ from .Sim3DR import get_normal, rasterize
5
+
6
+ _norm = lambda arr: arr / np.sqrt(np.sum(arr ** 2, axis=1))[:, None]
7
+
8
+
9
+ def norm_vertices(vertices):
10
+ vertices -= vertices.min(0)[None, :]
11
+ vertices /= vertices.max()
12
+ vertices *= 2
13
+ vertices -= vertices.max(0)[None, :] / 2
14
+ return vertices
15
+
16
+
17
+ def convert_type(obj):
18
+ if isinstance(obj, tuple) or isinstance(obj, list):
19
+ return np.array(obj, dtype=np.float32)[None, :]
20
+ return obj
21
+
22
+
23
+ class RenderPipeline(object):
24
+ def __init__(self, **kwargs):
25
+ self.intensity_ambient = convert_type(kwargs.get('intensity_ambient', 0.3))
26
+ self.intensity_directional = convert_type(kwargs.get('intensity_directional', 0.6))
27
+ self.intensity_specular = convert_type(kwargs.get('intensity_specular', 0.1))
28
+ self.specular_exp = kwargs.get('specular_exp', 5)
29
+ self.color_ambient = convert_type(kwargs.get('color_ambient', (1, 1, 1)))
30
+ self.color_directional = convert_type(kwargs.get('color_directional', (1, 1, 1)))
31
+ self.light_pos = convert_type(kwargs.get('light_pos', (0, 0, 5)))
32
+ self.view_pos = convert_type(kwargs.get('view_pos', (0, 0, 5)))
33
+
34
+ def update_light_pos(self, light_pos):
35
+ self.light_pos = convert_type(light_pos)
36
+
37
+ def __call__(self, vertices, triangles, bg, texture=None):
38
+ normal = get_normal(vertices, triangles)
39
+
40
+ # 2. lighting
41
+ light = np.zeros_like(vertices, dtype=np.float32)
42
+ # ambient component
43
+ if self.intensity_ambient > 0:
44
+ light += self.intensity_ambient * self.color_ambient
45
+
46
+ vertices_n = norm_vertices(vertices.copy())
47
+ if self.intensity_directional > 0:
48
+ # diffuse component
49
+ direction = _norm(self.light_pos - vertices_n)
50
+ cos = np.sum(normal * direction, axis=1)[:, None]
51
+ # cos = np.clip(cos, 0, 1)
52
+ # todo: check below
53
+ light += self.intensity_directional * (self.color_directional * np.clip(cos, 0, 1))
54
+
55
+ # specular component
56
+ if self.intensity_specular > 0:
57
+ v2v = _norm(self.view_pos - vertices_n)
58
+ reflection = 2 * cos * normal - direction
59
+ spe = np.sum((v2v * reflection) ** self.specular_exp, axis=1)[:, None]
60
+ spe = np.where(cos != 0, np.clip(spe, 0, 1), np.zeros_like(spe))
61
+ light += self.intensity_specular * self.color_directional * np.clip(spe, 0, 1)
62
+ light = np.clip(light, 0, 1)
63
+
64
+ # 2. rasterization, [0, 1]
65
+ if texture is None:
66
+ render_img = rasterize(vertices, triangles, light, bg=bg)
67
+ return render_img
68
+ else:
69
+ texture *= light
70
+ render_img = rasterize(vertices, triangles, texture, bg=bg)
71
+ return render_img
72
+
73
+
74
+ def main():
75
+ pass
76
+
77
+
78
+ if __name__ == '__main__':
79
+ main()
tddfa/Sim3DR/readme.md ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ ## Sim3DR
2
+ This is a simple 3D render, written by c++ and cython.
3
+
4
+ ### Build Sim3DR
5
+
6
+ ```shell script
7
+ python3 setup.py build_ext --inplace
8
+ ```
tddfa/Sim3DR/setup.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ python setup.py build_ext -i
3
+ to compile
4
+ '''
5
+
6
+ from distutils.core import setup, Extension
7
+ from Cython.Build import cythonize
8
+ from Cython.Distutils import build_ext
9
+ import numpy
10
+
11
+ setup(
12
+ name='Sim3DR_Cython', # not the package name
13
+ cmdclass={'build_ext': build_ext},
14
+ ext_modules=[Extension("Sim3DR_Cython",
15
+ sources=["lib/rasterize.pyx", "lib/rasterize_kernel.cpp"],
16
+ language='c++',
17
+ include_dirs=[numpy.get_include()],
18
+ extra_compile_args=["-std=c++11"])],
19
+ )
tddfa/Sim3DR/tests/.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ build/
tddfa/Sim3DR/tests/CMakeLists.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ cmake_minimum_required(VERSION 2.8)
2
+
3
+ set(TARGET test)
4
+ project(${TARGET})
5
+
6
+ #find_package( OpenCV REQUIRED )
7
+ #include_directories( ${OpenCV_INCLUDE_DIRS} )
8
+
9
+ #set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -O3")
10
+ include_directories(../lib)
11
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -std=c++11")
12
+ add_executable(${TARGET} test.cpp ../lib/rasterize_kernel.cpp io.cpp)
13
+ target_include_directories(${TARGET} PRIVATE ${PROJECT_SOURCE_DIR})
tddfa/Sim3DR/tests/io.cpp ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "io.h"
2
+
3
+ //void load_obj(const string obj_fp, float* vertices, float* colors, float* triangles){
4
+ // string line;
5
+ // ifstream in(obj_fp);
6
+ //
7
+ // if(in.is_open()){
8
+ // while (getline(in, line)){
9
+ // stringstream ss(line);
10
+ //
11
+ // char t; // type: v, f
12
+ // ss >> t;
13
+ // if (t == 'v'){
14
+ //
15
+ // }
16
+ // }
17
+ // }
18
+ //}
19
+
20
+ void load_obj(const char *obj_fp, float *vertices, float *colors, int *triangles, int nver, int ntri) {
21
+ FILE *fp;
22
+ fp = fopen(obj_fp, "r");
23
+
24
+ char t; // type: v or f
25
+ if (fp != nullptr) {
26
+ for (int i = 0; i < nver; ++i) {
27
+ fscanf(fp, "%c", &t);
28
+ for (int j = 0; j < 3; ++j)
29
+ fscanf(fp, " %f", &vertices[3 * i + j]);
30
+ for (int j = 0; j < 3; ++j)
31
+ fscanf(fp, " %f", &colors[3 * i + j]);
32
+ fscanf(fp, "\n");
33
+ }
34
+ // fscanf(fp, "%c", &t);
35
+ for (int i = 0; i < ntri; ++i) {
36
+ fscanf(fp, "%c", &t);
37
+ for (int j = 0; j < 3; ++j) {
38
+ fscanf(fp, " %d", &triangles[3 * i + j]);
39
+ triangles[3 * i + j] -= 1;
40
+ }
41
+ fscanf(fp, "\n");
42
+ }
43
+
44
+ fclose(fp);
45
+ }
46
+ }
47
+
48
+ void load_ply(const char *ply_fp, float *vertices, int *triangles, int nver, int ntri) {
49
+ FILE *fp;
50
+ fp = fopen(ply_fp, "r");
51
+
52
+ // char s[256];
53
+ char t;
54
+ if (fp != nullptr) {
55
+ // for (int i = 0; i < 9; ++i)
56
+ // fscanf(fp, "%s", s);
57
+ for (int i = 0; i < nver; ++i)
58
+ fscanf(fp, "%f %f %f\n", &vertices[3 * i], &vertices[3 * i + 1], &vertices[3 * i + 2]);
59
+
60
+ for (int i = 0; i < ntri; ++i)
61
+ fscanf(fp, "%c %d %d %d\n", &t, &triangles[3 * i], &triangles[3 * i + 1], &triangles[3 * i + 2]);
62
+
63
+ fclose(fp);
64
+ }
65
+ }
66
+
67
+ void write_ppm(const char *filename, unsigned char *img, int h, int w, int c) {
68
+ FILE *fp;
69
+ //open file for output
70
+ fp = fopen(filename, "wb");
71
+ if (!fp) {
72
+ fprintf(stderr, "Unable to open file '%s'\n", filename);
73
+ exit(1);
74
+ }
75
+
76
+ //write the header file
77
+ //image format
78
+ fprintf(fp, "P6\n");
79
+
80
+ //image size
81
+ fprintf(fp, "%d %d\n", w, h);
82
+
83
+ // rgb component depth
84
+ fprintf(fp, "%d\n", MAX_PXL_VALUE);
85
+
86
+ // pixel data
87
+ fwrite(img, sizeof(unsigned char), size_t(h * w * c), fp);
88
+ fclose(fp);
89
+ }
tddfa/Sim3DR/tests/io.h ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ifndef IO_H_
2
+ #define IO_H_
3
+
4
+ #include <iostream>
5
+ #include <string>
6
+ #include <fstream>
7
+ #include <sstream>
8
+ #include <stdio.h>
9
+
10
+ using namespace std;
11
+
12
+ #define MAX_PXL_VALUE 255
13
+
14
+ void load_obj(const char* obj_fp, float* vertices, float* colors, int* triangles, int nver, int ntri);
15
+ void load_ply(const char* ply_fp, float* vertices, int* triangles, int nver, int ntri);
16
+
17
+
18
+ void write_ppm(const char *filename, unsigned char *img, int h, int w, int c);
19
+
20
+ #endif
tddfa/Sim3DR/tests/test.cpp ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * Tesing cases
3
+ */
4
+
5
+ #include <iostream>
6
+ #include <time.h>
7
+ #include "rasterize.h"
8
+ #include "io.h"
9
+
10
+ void test_isPointInTri() {
11
+ Point p0(0, 0);
12
+ Point p1(1, 0);
13
+ Point p2(1, 1);
14
+
15
+ Point p(0.2, 0.2);
16
+
17
+ if (is_point_in_tri(p, p0, p1, p2))
18
+ std::cout << "In";
19
+ else
20
+ std::cout << "Out";
21
+ std::cout << std::endl;
22
+ }
23
+
24
+ void test_getPointWeight() {
25
+ Point p0(0, 0);
26
+ Point p1(1, 0);
27
+ Point p2(1, 1);
28
+
29
+ Point p(0.2, 0.2);
30
+
31
+ float weight[3];
32
+ get_point_weight(weight, p, p0, p1, p2);
33
+ std::cout << weight[0] << " " << weight[1] << " " << weight[2] << std::endl;
34
+ }
35
+
36
+ void test_get_tri_normal() {
37
+ float tri_normal[3];
38
+ // float vertices[9] = {1, 0, 0, 0, 0, 0, 0, 1, 0};
39
+ float vertices[9] = {1, 1.1, 0, 0, 0, 0, 0, 0.6, 0.7};
40
+ int triangles[3] = {0, 1, 2};
41
+ int ntri = 1;
42
+
43
+ _get_tri_normal(tri_normal, vertices, triangles, ntri);
44
+
45
+ for (int i = 0; i < 3; ++i)
46
+ std::cout << tri_normal[i] << ", ";
47
+ std::cout << std::endl;
48
+ }
49
+
50
+ void test_load_obj() {
51
+ const char *fp = "../data/vd005_mesh.obj";
52
+ int nver = 35709;
53
+ int ntri = 70789;
54
+
55
+ auto *vertices = new float[nver];
56
+ auto *colors = new float[nver];
57
+ auto *triangles = new int[ntri];
58
+ load_obj(fp, vertices, colors, triangles, nver, ntri);
59
+
60
+ delete[] vertices;
61
+ delete[] colors;
62
+ delete[] triangles;
63
+ }
64
+
65
+ void test_render() {
66
+ // 1. loading obj
67
+ // const char *fp = "/Users/gjz/gjzprojects/Sim3DR/data/vd005_mesh.obj";
68
+ const char *fp = "/Users/gjz/gjzprojects/Sim3DR/data/face1.obj";
69
+ int nver = 35709; //53215; //35709;
70
+ int ntri = 70789; //105840;//70789;
71
+
72
+ auto *vertices = new float[3 * nver];
73
+ auto *colors = new float[3 * nver];
74
+ auto *triangles = new int[3 * ntri];
75
+ load_obj(fp, vertices, colors, triangles, nver, ntri);
76
+
77
+ // 2. rendering
78
+ int h = 224, w = 224, c = 3;
79
+
80
+ // enlarging
81
+ int scale = 4;
82
+ h *= scale;
83
+ w *= scale;
84
+ for (int i = 0; i < nver * 3; ++i) vertices[i] *= scale;
85
+
86
+ auto *image = new unsigned char[h * w * c]();
87
+ auto *depth_buffer = new float[h * w]();
88
+
89
+ for (int i = 0; i < h * w; ++i) depth_buffer[i] = -999999;
90
+
91
+ clock_t t;
92
+ t = clock();
93
+
94
+ _rasterize(image, vertices, triangles, colors, depth_buffer, ntri, h, w, c, true);
95
+ t = clock() - t;
96
+ double time_taken = ((double) t) / CLOCKS_PER_SEC; // in seconds
97
+ printf("Render took %f seconds to execute \n", time_taken);
98
+
99
+
100
+ // auto *image_char = new u_char[h * w * c]();
101
+ // for (int i = 0; i < h * w * c; ++i)
102
+ // image_char[i] = u_char(255 * image[i]);
103
+ write_ppm("res.ppm", image, h, w, c);
104
+
105
+ // delete[] image_char;
106
+ delete[] vertices;
107
+ delete[] colors;
108
+ delete[] triangles;
109
+ delete[] image;
110
+ delete[] depth_buffer;
111
+ }
112
+
113
+ void test_light() {
114
+ // 1. loading obj
115
+ const char *fp = "/Users/gjz/gjzprojects/Sim3DR/data/emma_input_0_noheader.ply";
116
+ int nver = 53215; //35709;
117
+ int ntri = 105840; //70789;
118
+
119
+ auto *vertices = new float[3 * nver];
120
+ auto *colors = new float[3 * nver];
121
+ auto *triangles = new int[3 * ntri];
122
+ load_ply(fp, vertices, triangles, nver, ntri);
123
+
124
+ // 2. rendering
125
+ // int h = 1901, w = 3913, c = 3;
126
+ int h = 2000, w = 4000, c = 3;
127
+
128
+ // enlarging
129
+ // int scale = 1;
130
+ // h *= scale;
131
+ // w *= scale;
132
+ // for (int i = 0; i < nver * 3; ++i) vertices[i] *= scale;
133
+
134
+ auto *image = new unsigned char[h * w * c]();
135
+ auto *depth_buffer = new float[h * w]();
136
+
137
+ for (int i = 0; i < h * w; ++i) depth_buffer[i] = -999999;
138
+ for (int i = 0; i < 3 * nver; ++i) colors[i] = 0.8;
139
+
140
+ clock_t t;
141
+ t = clock();
142
+
143
+ _rasterize(image, vertices, triangles, colors, depth_buffer, ntri, h, w, c, true);
144
+ t = clock() - t;
145
+ double time_taken = ((double) t) / CLOCKS_PER_SEC; // in seconds
146
+ printf("Render took %f seconds to execute \n", time_taken);
147
+
148
+
149
+ // auto *image_char = new u_char[h * w * c]();
150
+ // for (int i = 0; i < h * w * c; ++i)
151
+ // image_char[i] = u_char(255 * image[i]);
152
+ write_ppm("emma.ppm", image, h, w, c);
153
+
154
+ // delete[] image_char;
155
+ delete[] vertices;
156
+ delete[] colors;
157
+ delete[] triangles;
158
+ delete[] image;
159
+ delete[] depth_buffer;
160
+ }
161
+
162
+ int main(int argc, char *argv[]) {
163
+ // std::cout << "Hello CMake!" << std::endl;
164
+
165
+ // test_isPointInTri();
166
+ // test_getPointWeight();
167
+ // test_get_tri_normal();
168
+ // test_load_obj();
169
+ // test_render();
170
+ test_light();
171
+ return 0;
172
+ }
tddfa/TDDFA.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ __author__ = 'cleardusk'
4
+
5
+ import os.path as osp
6
+ import time
7
+ import numpy as np
8
+ import cv2
9
+ import torch
10
+ from torchvision.transforms import Compose
11
+ import torch.backends.cudnn as cudnn
12
+
13
+ import models
14
+ from bfm import BFMModel
15
+ from utils.io import _load
16
+ from utils.functions import (
17
+ crop_img, parse_roi_box_from_bbox, parse_roi_box_from_landmark,
18
+ )
19
+ from utils.tddfa_util import (
20
+ load_model, _parse_param, similar_transform,
21
+ ToTensorGjz, NormalizeGjz
22
+ )
23
+
24
+ make_abs_path = lambda fn: osp.join(osp.dirname(osp.realpath(__file__)), fn)
25
+
26
+
27
+ class TDDFA(object):
28
+ """TDDFA: named Three-D Dense Face Alignment (TDDFA)"""
29
+
30
+ def __init__(self, **kvs):
31
+ torch.set_grad_enabled(False)
32
+
33
+ # load BFM
34
+ self.bfm = BFMModel(
35
+ bfm_fp=kvs.get('bfm_fp', make_abs_path('configs/bfm_noneck_v3.pkl')),
36
+ shape_dim=kvs.get('shape_dim', 40),
37
+ exp_dim=kvs.get('exp_dim', 10)
38
+ )
39
+ self.tri = self.bfm.tri
40
+
41
+ # config
42
+ self.gpu_mode = kvs.get('gpu_mode', False)
43
+ self.gpu_id = kvs.get('gpu_id', 0)
44
+ self.size = kvs.get('size', 120)
45
+
46
+ param_mean_std_fp = kvs.get(
47
+ 'param_mean_std_fp', make_abs_path(f'configs/param_mean_std_62d_{self.size}x{self.size}.pkl')
48
+ )
49
+
50
+ # load model, default output is dimension with length 62 = 12(pose) + 40(shape) +10(expression)
51
+ model = getattr(models, kvs.get('arch'))(
52
+ num_classes=kvs.get('num_params', 62),
53
+ widen_factor=kvs.get('widen_factor', 1),
54
+ size=self.size,
55
+ mode=kvs.get('mode', 'small')
56
+ )
57
+ model = load_model(model, kvs.get('checkpoint_fp'))
58
+
59
+ if self.gpu_mode:
60
+ cudnn.benchmark = True
61
+ model = model.cuda(device=self.gpu_id)
62
+
63
+ self.model = model
64
+ self.model.eval() # eval mode, fix BN
65
+
66
+ # data normalization
67
+ transform_normalize = NormalizeGjz(mean=127.5, std=128)
68
+ transform_to_tensor = ToTensorGjz()
69
+ transform = Compose([transform_to_tensor, transform_normalize])
70
+ self.transform = transform
71
+
72
+ # params normalization config
73
+ r = _load(param_mean_std_fp)
74
+ self.param_mean = r.get('mean')
75
+ self.param_std = r.get('std')
76
+
77
+ # print('param_mean and param_srd', self.param_mean, self.param_std)
78
+
79
+ def __call__(self, img_ori, objs, **kvs):
80
+ """The main call of TDDFA, given image and box / landmark, return 3DMM params and roi_box
81
+ :param img_ori: the input image
82
+ :param objs: the list of box or landmarks
83
+ :param kvs: options
84
+ :return: param list and roi_box list
85
+ """
86
+ # Crop image, forward to get the param
87
+ param_lst = []
88
+ roi_box_lst = []
89
+
90
+ crop_policy = kvs.get('crop_policy', 'box')
91
+ for obj in objs:
92
+ if crop_policy == 'box':
93
+ # by face box
94
+ roi_box = parse_roi_box_from_bbox(obj)
95
+ elif crop_policy == 'landmark':
96
+ # by landmarks
97
+ roi_box = parse_roi_box_from_landmark(obj)
98
+ else:
99
+ raise ValueError(f'Unknown crop policy {crop_policy}')
100
+
101
+ roi_box_lst.append(roi_box)
102
+ img = crop_img(img_ori, roi_box)
103
+ img = cv2.resize(img, dsize=(self.size, self.size), interpolation=cv2.INTER_LINEAR)
104
+ inp = self.transform(img).unsqueeze(0)
105
+
106
+ if self.gpu_mode:
107
+ inp = inp.cuda(device=self.gpu_id)
108
+
109
+ if kvs.get('timer_flag', False):
110
+ end = time.time()
111
+ param = self.model(inp)
112
+ elapse = f'Inference: {(time.time() - end) * 1000:.1f}ms'
113
+ print(elapse)
114
+ else:
115
+ param = self.model(inp)
116
+
117
+ param = param.squeeze().cpu().numpy().flatten().astype(np.float32)
118
+ param = param * self.param_std + self.param_mean # re-scale
119
+ # print('output', param)
120
+ param_lst.append(param)
121
+
122
+ return param_lst, roi_box_lst
123
+
124
+ def recon_vers(self, param_lst, roi_box_lst, **kvs):
125
+ dense_flag = kvs.get('dense_flag', False)
126
+ size = self.size
127
+
128
+ ver_lst = []
129
+ for param, roi_box in zip(param_lst, roi_box_lst):
130
+ if dense_flag:
131
+ R, offset, alpha_shp, alpha_exp = _parse_param(param)
132
+ pts3d = R @ (self.bfm.u + self.bfm.w_shp @ alpha_shp + self.bfm.w_exp @ alpha_exp). \
133
+ reshape(3, -1, order='F') + offset
134
+ pts3d = similar_transform(pts3d, roi_box, size)
135
+ else:
136
+ R, offset, alpha_shp, alpha_exp = _parse_param(param)
137
+ pts3d = R @ (self.bfm.u_base + self.bfm.w_shp_base @ alpha_shp + self.bfm.w_exp_base @ alpha_exp). \
138
+ reshape(3, -1, order='F') + offset
139
+ pts3d = similar_transform(pts3d, roi_box, size)
140
+
141
+ ver_lst.append(pts3d)
142
+
143
+ return ver_lst
tddfa/TDDFA_ONNX.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ __author__ = 'cleardusk'
4
+
5
+ import os.path as osp
6
+ import numpy as np
7
+ import cv2
8
+ import onnxruntime
9
+
10
+ from tddfa.utils.onnx import convert_to_onnx
11
+ from tddfa.utils.io import _load
12
+ from tddfa.utils.functions import (
13
+ crop_img, parse_roi_box_from_bbox, parse_roi_box_from_landmark,
14
+ )
15
+ from tddfa.utils.tddfa_util import _parse_param, similar_transform
16
+ from tddfa.bfm.bfm import BFMModel
17
+ from tddfa.bfm.bfm_onnx import convert_bfm_to_onnx
18
+
19
+ make_abs_path = lambda fn: osp.join(osp.dirname(osp.realpath(__file__)), fn)
20
+
21
+
22
+ class TDDFA_ONNX(object):
23
+ """TDDFA_ONNX: the ONNX version of Three-D Dense Face Alignment (TDDFA)"""
24
+
25
+ def __init__(self, **kvs):
26
+ # torch.set_grad_enabled(False)
27
+
28
+ # load onnx version of BFM
29
+ bfm_fp = kvs.get('bfm_fp', make_abs_path('configs/bfm_noneck_v3.pkl'))
30
+ bfm_onnx_fp = bfm_fp.replace('.pkl', '.onnx')
31
+ if not osp.exists(bfm_onnx_fp):
32
+ convert_bfm_to_onnx(
33
+ bfm_onnx_fp,
34
+ shape_dim=kvs.get('shape_dim', 40),
35
+ exp_dim=kvs.get('exp_dim', 10)
36
+ )
37
+ self.bfm_session = onnxruntime.InferenceSession(bfm_onnx_fp, None)
38
+
39
+ # load for optimization
40
+ bfm = BFMModel(bfm_fp, shape_dim=kvs.get('shape_dim', 40), exp_dim=kvs.get('exp_dim', 10))
41
+ self.tri = bfm.tri
42
+ self.u_base, self.w_shp_base, self.w_exp_base = bfm.u_base, bfm.w_shp_base, bfm.w_exp_base
43
+
44
+ # config
45
+ self.gpu_mode = kvs.get('gpu_mode', False)
46
+ self.gpu_id = kvs.get('gpu_id', 0)
47
+ self.size = kvs.get('size', 120)
48
+
49
+ param_mean_std_fp = kvs.get(
50
+ 'param_mean_std_fp', make_abs_path(f'configs/param_mean_std_62d_{self.size}x{self.size}.pkl')
51
+ )
52
+
53
+ onnx_fp = kvs.get('onnx_fp', kvs.get('checkpoint_fp').replace('.pth', '.onnx'))
54
+
55
+ # convert to onnx online if not existed
56
+ if onnx_fp is None or not osp.exists(onnx_fp):
57
+ print(f'{onnx_fp} does not exist, try to convert the `.pth` version to `.onnx` online')
58
+ onnx_fp = convert_to_onnx(**kvs)
59
+
60
+ self.session = onnxruntime.InferenceSession(onnx_fp, None)
61
+
62
+ # params normalization config
63
+ r = _load(param_mean_std_fp)
64
+ self.param_mean = r.get('mean')
65
+ self.param_std = r.get('std')
66
+
67
+ def __call__(self, img_ori, objs, **kvs):
68
+ # Crop image, forward to get the param
69
+ param_lst = []
70
+ roi_box_lst = []
71
+
72
+ crop_policy = kvs.get('crop_policy', 'box')
73
+ for obj in objs:
74
+ if crop_policy == 'box':
75
+ # by face box
76
+ roi_box = parse_roi_box_from_bbox(obj)
77
+ elif crop_policy == 'landmark':
78
+ # by landmarks
79
+ roi_box = parse_roi_box_from_landmark(obj)
80
+ else:
81
+ raise ValueError(f'Unknown crop policy {crop_policy}')
82
+
83
+ roi_box_lst.append(roi_box)
84
+ img = crop_img(img_ori, roi_box)
85
+ img = cv2.resize(img, dsize=(self.size, self.size), interpolation=cv2.INTER_LINEAR)
86
+ img = img.astype(np.float32).transpose(2, 0, 1)[np.newaxis, ...]
87
+ img = (img - 127.5) / 128.
88
+
89
+ inp_dct = {'input': img}
90
+
91
+ param = self.session.run(None, inp_dct)[0]
92
+ param = param.flatten().astype(np.float32)
93
+ param = param * self.param_std + self.param_mean # re-scale
94
+ param_lst.append(param)
95
+
96
+ return param_lst, roi_box_lst
97
+
98
+ def recon_vers(self, param_lst, roi_box_lst, **kvs):
99
+ dense_flag = kvs.get('dense_flag', False)
100
+ size = self.size
101
+
102
+ ver_lst = []
103
+ for param, roi_box in zip(param_lst, roi_box_lst):
104
+ R, offset, alpha_shp, alpha_exp = _parse_param(param)
105
+ if dense_flag:
106
+ inp_dct = {
107
+ 'R': R, 'offset': offset, 'alpha_shp': alpha_shp, 'alpha_exp': alpha_exp
108
+ }
109
+ pts3d = self.bfm_session.run(None, inp_dct)[0]
110
+ pts3d = similar_transform(pts3d, roi_box, size)
111
+ else:
112
+ pts3d = R @ (self.u_base + self.w_shp_base @ alpha_shp + self.w_exp_base @ alpha_exp). \
113
+ reshape(3, -1, order='F') + offset
114
+ pts3d = similar_transform(pts3d, roi_box, size)
115
+
116
+ ver_lst.append(pts3d)
117
+
118
+ return ver_lst
tddfa/bfm/.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ *.ply
tddfa/bfm/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .bfm import BFMModel
tddfa/bfm/bfm.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ __author__ = 'cleardusk'
4
+
5
+ import sys
6
+
7
+ sys.path.append('..')
8
+
9
+ import os.path as osp
10
+ import numpy as np
11
+ from tddfa.utils.io import _load
12
+
13
+ make_abs_path = lambda fn: osp.join(osp.dirname(osp.realpath(__file__)), fn)
14
+
15
+
16
+ def _to_ctype(arr):
17
+ if not arr.flags.c_contiguous:
18
+ return arr.copy(order='C')
19
+ return arr
20
+
21
+
22
+ class BFMModel(object):
23
+ def __init__(self, bfm_fp, shape_dim=40, exp_dim=10):
24
+ bfm = _load(bfm_fp)
25
+ self.u = bfm.get('u').astype(np.float32) # fix bug
26
+ self.w_shp = bfm.get('w_shp').astype(np.float32)[..., :shape_dim]
27
+ self.w_exp = bfm.get('w_exp').astype(np.float32)[..., :exp_dim]
28
+ if osp.split(bfm_fp)[-1] == 'bfm_noneck_v3.pkl':
29
+ self.tri = _load(make_abs_path('../configs/tri.pkl')) # this tri/face is re-built for bfm_noneck_v3
30
+ else:
31
+ self.tri = bfm.get('tri')
32
+
33
+ self.tri = _to_ctype(self.tri.T).astype(np.int32)
34
+ self.keypoints = bfm.get('keypoints').astype(np.long) # fix bug
35
+ w = np.concatenate((self.w_shp, self.w_exp), axis=1)
36
+ self.w_norm = np.linalg.norm(w, axis=0)
37
+
38
+ self.u_base = self.u[self.keypoints].reshape(-1, 1)
39
+ self.w_shp_base = self.w_shp[self.keypoints]
40
+ self.w_exp_base = self.w_exp[self.keypoints]
tddfa/bfm/bfm_onnx.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ __author__ = 'cleardusk'
4
+
5
+ import sys
6
+
7
+ sys.path.append('..')
8
+
9
+ import os.path as osp
10
+ import numpy as np
11
+ import torch
12
+ import torch.nn as nn
13
+
14
+ from tddfa.utils.io import _load, _numpy_to_cuda, _numpy_to_tensor
15
+
16
+ make_abs_path = lambda fn: osp.join(osp.dirname(osp.realpath(__file__)), fn)
17
+
18
+
19
+ def _to_ctype(arr):
20
+ if not arr.flags.c_contiguous:
21
+ return arr.copy(order='C')
22
+ return arr
23
+
24
+
25
+ def _load_tri(bfm_fp):
26
+ if osp.split(bfm_fp)[-1] == 'bfm_noneck_v3.pkl':
27
+ tri = _load(make_abs_path('../configs/tri.pkl')) # this tri/face is re-built for bfm_noneck_v3
28
+ else:
29
+ tri = _load(bfm_fp).get('tri')
30
+
31
+ tri = _to_ctype(tri.T).astype(np.int32)
32
+ return tri
33
+
34
+
35
+ class BFMModel_ONNX(nn.Module):
36
+ """BFM serves as a decoder"""
37
+
38
+ def __init__(self, bfm_fp, shape_dim=40, exp_dim=10):
39
+ super(BFMModel_ONNX, self).__init__()
40
+
41
+ _to_tensor = _numpy_to_tensor
42
+
43
+ # load bfm
44
+ bfm = _load(bfm_fp)
45
+
46
+ u = _to_tensor(bfm.get('u').astype(np.float32))
47
+ self.u = u.view(-1, 3).transpose(1, 0)
48
+ w_shp = _to_tensor(bfm.get('w_shp').astype(np.float32)[..., :shape_dim])
49
+ w_exp = _to_tensor(bfm.get('w_exp').astype(np.float32)[..., :exp_dim])
50
+ w = torch.cat((w_shp, w_exp), dim=1)
51
+ self.w = w.view(-1, 3, w.shape[-1]).contiguous().permute(1, 0, 2)
52
+
53
+ # self.u = _to_tensor(bfm.get('u').astype(np.float32)) # fix bug
54
+ # w_shp = _to_tensor(bfm.get('w_shp').astype(np.float32)[..., :shape_dim])
55
+ # w_exp = _to_tensor(bfm.get('w_exp').astype(np.float32)[..., :exp_dim])
56
+ # self.w = torch.cat((w_shp, w_exp), dim=1)
57
+
58
+ # self.keypoints = bfm.get('keypoints').astype(np.long) # fix bug
59
+ # self.u_base = self.u[self.keypoints].reshape(-1, 1)
60
+ # self.w_shp_base = self.w_shp[self.keypoints]
61
+ # self.w_exp_base = self.w_exp[self.keypoints]
62
+
63
+ def forward(self, *inps):
64
+ R, offset, alpha_shp, alpha_exp = inps
65
+ alpha = torch.cat((alpha_shp, alpha_exp))
66
+ # pts3d = R @ (self.u + self.w_shp.matmul(alpha_shp) + self.w_exp.matmul(alpha_exp)). \
67
+ # view(-1, 3).transpose(1, 0) + offset
68
+ # pts3d = R @ (self.u + self.w.matmul(alpha)).view(-1, 3).transpose(1, 0) + offset
69
+ pts3d = R @ (self.u + self.w.matmul(alpha).squeeze()) + offset
70
+ return pts3d
71
+
72
+
73
+ def convert_bfm_to_onnx(bfm_onnx_fp, shape_dim=40, exp_dim=10):
74
+ # print(shape_dim, exp_dim)
75
+ bfm_fp = bfm_onnx_fp.replace('.onnx', '.pkl')
76
+ bfm_decoder = BFMModel_ONNX(bfm_fp=bfm_fp, shape_dim=shape_dim, exp_dim=exp_dim)
77
+ bfm_decoder.eval()
78
+
79
+ # dummy_input = torch.randn(12 + shape_dim + exp_dim)
80
+ dummy_input = torch.randn(3, 3), torch.randn(3, 1), torch.randn(shape_dim, 1), torch.randn(exp_dim, 1)
81
+ R, offset, alpha_shp, alpha_exp = dummy_input
82
+ torch.onnx.export(
83
+ bfm_decoder,
84
+ (R, offset, alpha_shp, alpha_exp),
85
+ bfm_onnx_fp,
86
+ input_names=['R', 'offset', 'alpha_shp', 'alpha_exp'],
87
+ output_names=['output'],
88
+ dynamic_axes={
89
+ 'alpha_shp': [0],
90
+ 'alpha_exp': [0],
91
+ },
92
+ do_constant_folding=True
93
+ )
94
+ print(f'Convert {bfm_fp} to {bfm_onnx_fp} done.')
95
+
96
+
97
+ if __name__ == '__main__':
98
+ convert_bfm_to_onnx('../configs/bfm_noneck_v3.onnx')
tddfa/bfm/readme.md ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Statement
2
+
3
+ The modified BFM2009 face model in `../configs/bfm_noneck_v3.pkl` is only for academic use.
4
+ For commercial use, you need to apply for the commercial license, some refs are below:
5
+
6
+ [1] https://faces.dmi.unibas.ch/bfm/?nav=1-0&id=basel_face_model
7
+
8
+ [2] https://faces.dmi.unibas.ch/bfm/bfm2019.html
9
+
10
+ If your work benefits from this repo, please cite
11
+
12
+ @PROCEEDINGS{bfm09,
13
+ title={A 3D Face Model for Pose and Illumination Invariant Face Recognition},
14
+ author={P. Paysan and R. Knothe and B. Amberg
15
+ and S. Romdhani and T. Vetter},
16
+ journal={Proceedings of the 6th IEEE International Conference on Advanced Video and Signal based Surveillance (AVSS)
17
+ for Security, Safety and Monitoring in Smart Environments},
18
+ organization={IEEE},
19
+ year={2009},
20
+ address = {Genova, Italy},
21
+ }
22
+
23
+
tddfa/build.sh ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ cd tddfa/Sim3DR
2
+ sh ./build_sim3dr.sh
3
+ cd ../..
4
+
5
+ cd tddfa/utils/asset
6
+ gcc -shared -Wall -O3 render.c -o render.so -fPIC
7
+ cd ../../..
tddfa/configs/.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ *.pkl
2
+ *.yml
3
+ *.onnx
tddfa/configs/BFM_UV.mat ADDED
Binary file (779 kB). View file
 
tddfa/configs/indices.npy ADDED
Binary file (154 kB). View file
 
tddfa/configs/ncc_code.npy ADDED
Binary file (461 kB). View file
 
tddfa/configs/readme.md ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ ## The simplified version of BFM
2
+
3
+ `bfm_noneck_v3_slim.pkl`: [Google Drive](https://drive.google.com/file/d/1iK5lD49E_gCn9voUjWDPj2ItGKvM10GI/view?usp=sharing) or [Baidu Drive](https://pan.baidu.com/s/1C_SzYBOG3swZA_EjxpXlAw) (Password: p803)
tddfa/models/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .mobilenet_v1 import *
2
+ from .mobilenet_v3 import *
3
+ from .resnet import *
tddfa/models/mobilenet_v1.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ from __future__ import division
4
+
5
+ """
6
+ Creates a MobileNet Model as defined in:
7
+ Andrew G. Howard Menglong Zhu Bo Chen, et.al. (2017).
8
+ MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications.
9
+ Copyright (c) Yang Lu, 2017
10
+
11
+ Modified By cleardusk
12
+ """
13
+ import math
14
+ import torch.nn as nn
15
+
16
+ __all__ = ['MobileNet', 'mobilenet']
17
+
18
+
19
+ # __all__ = ['mobilenet_2', 'mobilenet_1', 'mobilenet_075', 'mobilenet_05', 'mobilenet_025']
20
+
21
+
22
+ class DepthWiseBlock(nn.Module):
23
+ def __init__(self, inplanes, planes, stride=1, prelu=False):
24
+ super(DepthWiseBlock, self).__init__()
25
+ inplanes, planes = int(inplanes), int(planes)
26
+ self.conv_dw = nn.Conv2d(inplanes, inplanes, kernel_size=3, padding=1, stride=stride, groups=inplanes,
27
+ bias=False)
28
+ self.bn_dw = nn.BatchNorm2d(inplanes)
29
+ self.conv_sep = nn.Conv2d(inplanes, planes, kernel_size=1, stride=1, padding=0, bias=False)
30
+ self.bn_sep = nn.BatchNorm2d(planes)
31
+ if prelu:
32
+ self.relu = nn.PReLU()
33
+ else:
34
+ self.relu = nn.ReLU(inplace=True)
35
+
36
+ def forward(self, x):
37
+ out = self.conv_dw(x)
38
+ out = self.bn_dw(out)
39
+ out = self.relu(out)
40
+
41
+ out = self.conv_sep(out)
42
+ out = self.bn_sep(out)
43
+ out = self.relu(out)
44
+
45
+ return out
46
+
47
+
48
+ class MobileNet(nn.Module):
49
+ def __init__(self, widen_factor=1.0, num_classes=1000, prelu=False, input_channel=3):
50
+ """ Constructor
51
+ Args:
52
+ widen_factor: config of widen_factor
53
+ num_classes: number of classes
54
+ """
55
+ super(MobileNet, self).__init__()
56
+
57
+ block = DepthWiseBlock
58
+ self.conv1 = nn.Conv2d(input_channel, int(32 * widen_factor), kernel_size=3, stride=2, padding=1,
59
+ bias=False)
60
+
61
+ self.bn1 = nn.BatchNorm2d(int(32 * widen_factor))
62
+ if prelu:
63
+ self.relu = nn.PReLU()
64
+ else:
65
+ self.relu = nn.ReLU(inplace=True)
66
+
67
+ self.dw2_1 = block(32 * widen_factor, 64 * widen_factor, prelu=prelu)
68
+ self.dw2_2 = block(64 * widen_factor, 128 * widen_factor, stride=2, prelu=prelu)
69
+
70
+ self.dw3_1 = block(128 * widen_factor, 128 * widen_factor, prelu=prelu)
71
+ self.dw3_2 = block(128 * widen_factor, 256 * widen_factor, stride=2, prelu=prelu)
72
+
73
+ self.dw4_1 = block(256 * widen_factor, 256 * widen_factor, prelu=prelu)
74
+ self.dw4_2 = block(256 * widen_factor, 512 * widen_factor, stride=2, prelu=prelu)
75
+
76
+ self.dw5_1 = block(512 * widen_factor, 512 * widen_factor, prelu=prelu)
77
+ self.dw5_2 = block(512 * widen_factor, 512 * widen_factor, prelu=prelu)
78
+ self.dw5_3 = block(512 * widen_factor, 512 * widen_factor, prelu=prelu)
79
+ self.dw5_4 = block(512 * widen_factor, 512 * widen_factor, prelu=prelu)
80
+ self.dw5_5 = block(512 * widen_factor, 512 * widen_factor, prelu=prelu)
81
+ self.dw5_6 = block(512 * widen_factor, 1024 * widen_factor, stride=2, prelu=prelu)
82
+
83
+ self.dw6 = block(1024 * widen_factor, 1024 * widen_factor, prelu=prelu)
84
+
85
+ self.avgpool = nn.AdaptiveAvgPool2d(1)
86
+ self.fc = nn.Linear(int(1024 * widen_factor), num_classes)
87
+
88
+ for m in self.modules():
89
+ if isinstance(m, nn.Conv2d):
90
+ n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
91
+ m.weight.data.normal_(0, math.sqrt(2. / n))
92
+ elif isinstance(m, nn.BatchNorm2d):
93
+ m.weight.data.fill_(1)
94
+ m.bias.data.zero_()
95
+
96
+ def forward(self, x):
97
+ x = self.conv1(x)
98
+ x = self.bn1(x)
99
+ x = self.relu(x)
100
+
101
+ x = self.dw2_1(x)
102
+ x = self.dw2_2(x)
103
+ x = self.dw3_1(x)
104
+ x = self.dw3_2(x)
105
+ x = self.dw4_1(x)
106
+ x = self.dw4_2(x)
107
+ x = self.dw5_1(x)
108
+ x = self.dw5_2(x)
109
+ x = self.dw5_3(x)
110
+ x = self.dw5_4(x)
111
+ x = self.dw5_5(x)
112
+ x = self.dw5_6(x)
113
+ x = self.dw6(x)
114
+
115
+ x = self.avgpool(x)
116
+ x = x.view(x.size(0), -1)
117
+ x = self.fc(x)
118
+
119
+ return x
120
+
121
+
122
+ def mobilenet(**kwargs):
123
+ """
124
+ Construct MobileNet.
125
+ widen_factor=1.0 for mobilenet_1
126
+ widen_factor=0.75 for mobilenet_075
127
+ widen_factor=0.5 for mobilenet_05
128
+ widen_factor=0.25 for mobilenet_025
129
+ """
130
+ # widen_factor = 1.0, num_classes = 1000
131
+ # model = MobileNet(widen_factor=widen_factor, num_classes=num_classes)
132
+ # return model
133
+
134
+ model = MobileNet(
135
+ widen_factor=kwargs.get('widen_factor', 1.0),
136
+ num_classes=kwargs.get('num_classes', 62)
137
+ )
138
+ return model
139
+
140
+
141
+ def mobilenet_2(num_classes=62, input_channel=3):
142
+ model = MobileNet(widen_factor=2.0, num_classes=num_classes, input_channel=input_channel)
143
+ return model
144
+
145
+
146
+ def mobilenet_1(num_classes=62, input_channel=3):
147
+ model = MobileNet(widen_factor=1.0, num_classes=num_classes, input_channel=input_channel)
148
+ return model
149
+
150
+
151
+ def mobilenet_075(num_classes=62, input_channel=3):
152
+ model = MobileNet(widen_factor=0.75, num_classes=num_classes, input_channel=input_channel)
153
+ return model
154
+
155
+
156
+ def mobilenet_05(num_classes=62, input_channel=3):
157
+ model = MobileNet(widen_factor=0.5, num_classes=num_classes, input_channel=input_channel)
158
+ return model
159
+
160
+
161
+ def mobilenet_025(num_classes=62, input_channel=3):
162
+ model = MobileNet(widen_factor=0.25, num_classes=num_classes, input_channel=input_channel)
163
+ return model
tddfa/models/mobilenet_v3.py ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+
4
+ import torch.nn as nn
5
+ import torch.nn.functional as F
6
+
7
+ __all__ = ['MobileNetV3', 'mobilenet_v3']
8
+
9
+
10
+ def conv_bn(inp, oup, stride, conv_layer=nn.Conv2d, norm_layer=nn.BatchNorm2d, nlin_layer=nn.ReLU):
11
+ return nn.Sequential(
12
+ conv_layer(inp, oup, 3, stride, 1, bias=False),
13
+ norm_layer(oup),
14
+ nlin_layer(inplace=True)
15
+ )
16
+
17
+
18
+ def conv_1x1_bn(inp, oup, conv_layer=nn.Conv2d, norm_layer=nn.BatchNorm2d, nlin_layer=nn.ReLU):
19
+ return nn.Sequential(
20
+ conv_layer(inp, oup, 1, 1, 0, bias=False),
21
+ norm_layer(oup),
22
+ nlin_layer(inplace=True)
23
+ )
24
+
25
+
26
+ class Hswish(nn.Module):
27
+ def __init__(self, inplace=True):
28
+ super(Hswish, self).__init__()
29
+ self.inplace = inplace
30
+
31
+ def forward(self, x):
32
+ return x * F.relu6(x + 3., inplace=self.inplace) / 6.
33
+
34
+
35
+ class Hsigmoid(nn.Module):
36
+ def __init__(self, inplace=True):
37
+ super(Hsigmoid, self).__init__()
38
+ self.inplace = inplace
39
+
40
+ def forward(self, x):
41
+ return F.relu6(x + 3., inplace=self.inplace) / 6.
42
+
43
+
44
+ class SEModule(nn.Module):
45
+ def __init__(self, channel, reduction=4):
46
+ super(SEModule, self).__init__()
47
+ self.avg_pool = nn.AdaptiveAvgPool2d(1)
48
+ self.fc = nn.Sequential(
49
+ nn.Linear(channel, channel // reduction, bias=False),
50
+ nn.ReLU(inplace=True),
51
+ nn.Linear(channel // reduction, channel, bias=False),
52
+ Hsigmoid()
53
+ # nn.Sigmoid()
54
+ )
55
+
56
+ def forward(self, x):
57
+ b, c, _, _ = x.size()
58
+ y = self.avg_pool(x).view(b, c)
59
+ y = self.fc(y).view(b, c, 1, 1)
60
+ return x * y.expand_as(x)
61
+
62
+
63
+ class Identity(nn.Module):
64
+ def __init__(self, channel):
65
+ super(Identity, self).__init__()
66
+
67
+ def forward(self, x):
68
+ return x
69
+
70
+
71
+ def make_divisible(x, divisible_by=8):
72
+ import numpy as np
73
+ return int(np.ceil(x * 1. / divisible_by) * divisible_by)
74
+
75
+
76
+ class MobileBottleneck(nn.Module):
77
+ def __init__(self, inp, oup, kernel, stride, exp, se=False, nl='RE'):
78
+ super(MobileBottleneck, self).__init__()
79
+ assert stride in [1, 2]
80
+ assert kernel in [3, 5]
81
+ padding = (kernel - 1) // 2
82
+ self.use_res_connect = stride == 1 and inp == oup
83
+
84
+ conv_layer = nn.Conv2d
85
+ norm_layer = nn.BatchNorm2d
86
+ if nl == 'RE':
87
+ nlin_layer = nn.ReLU # or ReLU6
88
+ elif nl == 'HS':
89
+ nlin_layer = Hswish
90
+ else:
91
+ raise NotImplementedError
92
+ if se:
93
+ SELayer = SEModule
94
+ else:
95
+ SELayer = Identity
96
+
97
+ self.conv = nn.Sequential(
98
+ # pw
99
+ conv_layer(inp, exp, 1, 1, 0, bias=False),
100
+ norm_layer(exp),
101
+ nlin_layer(inplace=True),
102
+ # dw
103
+ conv_layer(exp, exp, kernel, stride, padding, groups=exp, bias=False),
104
+ norm_layer(exp),
105
+ SELayer(exp),
106
+ nlin_layer(inplace=True),
107
+ # pw-linear
108
+ conv_layer(exp, oup, 1, 1, 0, bias=False),
109
+ norm_layer(oup),
110
+ )
111
+
112
+ def forward(self, x):
113
+ if self.use_res_connect:
114
+ return x + self.conv(x)
115
+ else:
116
+ return self.conv(x)
117
+
118
+
119
+ class MobileNetV3(nn.Module):
120
+ def __init__(self, widen_factor=1.0, num_classes=141, num_landmarks=136, input_size=120, mode='small'):
121
+ super(MobileNetV3, self).__init__()
122
+ input_channel = 16
123
+ last_channel = 1280
124
+ if mode == 'large':
125
+ # refer to Table 1 in paper
126
+ mobile_setting = [
127
+ # k, exp, c, se, nl, s,
128
+ [3, 16, 16, False, 'RE', 1],
129
+ [3, 64, 24, False, 'RE', 2],
130
+ [3, 72, 24, False, 'RE', 1],
131
+ [5, 72, 40, True, 'RE', 2],
132
+ [5, 120, 40, True, 'RE', 1],
133
+ [5, 120, 40, True, 'RE', 1],
134
+ [3, 240, 80, False, 'HS', 2],
135
+ [3, 200, 80, False, 'HS', 1],
136
+ [3, 184, 80, False, 'HS', 1],
137
+ [3, 184, 80, False, 'HS', 1],
138
+ [3, 480, 112, True, 'HS', 1],
139
+ [3, 672, 112, True, 'HS', 1],
140
+ [5, 672, 160, True, 'HS', 2],
141
+ [5, 960, 160, True, 'HS', 1],
142
+ [5, 960, 160, True, 'HS', 1],
143
+ ]
144
+ elif mode == 'small':
145
+ # refer to Table 2 in paper
146
+ mobile_setting = [
147
+ # k, exp, c, se, nl, s,
148
+ [3, 16, 16, True, 'RE', 2],
149
+ [3, 72, 24, False, 'RE', 2],
150
+ [3, 88, 24, False, 'RE', 1],
151
+ [5, 96, 40, True, 'HS', 2],
152
+ [5, 240, 40, True, 'HS', 1],
153
+ [5, 240, 40, True, 'HS', 1],
154
+ [5, 120, 48, True, 'HS', 1],
155
+ [5, 144, 48, True, 'HS', 1],
156
+ [5, 288, 96, True, 'HS', 2],
157
+ [5, 576, 96, True, 'HS', 1],
158
+ [5, 576, 96, True, 'HS', 1],
159
+ ]
160
+ else:
161
+ raise NotImplementedError
162
+
163
+ # building first layer
164
+ assert input_size % 32 == 0
165
+ last_channel = make_divisible(last_channel * widen_factor) if widen_factor > 1.0 else last_channel
166
+ self.features = [conv_bn(3, input_channel, 2, nlin_layer=Hswish)]
167
+ # self.classifier = []
168
+
169
+ # building mobile blocks
170
+ for k, exp, c, se, nl, s in mobile_setting:
171
+ output_channel = make_divisible(c * widen_factor)
172
+ exp_channel = make_divisible(exp * widen_factor)
173
+ self.features.append(MobileBottleneck(input_channel, output_channel, k, s, exp_channel, se, nl))
174
+ input_channel = output_channel
175
+
176
+ # building last several layers
177
+ if mode == 'large':
178
+ last_conv = make_divisible(960 * widen_factor)
179
+ self.features.append(conv_1x1_bn(input_channel, last_conv, nlin_layer=Hswish))
180
+ self.features.append(nn.AdaptiveAvgPool2d(1))
181
+ self.features.append(nn.Conv2d(last_conv, last_channel, 1, 1, 0))
182
+ self.features.append(Hswish(inplace=True))
183
+ elif mode == 'small':
184
+ last_conv = make_divisible(576 * widen_factor)
185
+ self.features.append(conv_1x1_bn(input_channel, last_conv, nlin_layer=Hswish))
186
+ # self.features.append(SEModule(last_conv)) # refer to paper Table2, but I think this is a mistake
187
+ self.features.append(nn.AdaptiveAvgPool2d(1))
188
+ self.features.append(nn.Conv2d(last_conv, last_channel, 1, 1, 0))
189
+ self.features.append(Hswish(inplace=True))
190
+ else:
191
+ raise NotImplementedError
192
+
193
+ # make it nn.Sequential
194
+ self.features = nn.Sequential(*self.features)
195
+
196
+ # self.fc_param = nn.Linear(int(last_channel), num_classes)
197
+ self.fc = nn.Linear(int(last_channel), num_classes)
198
+ # self.fc_lm = nn.Linear(int(last_channel), num_landmarks)
199
+
200
+ # building classifier
201
+ # self.classifier = nn.Sequential(
202
+ # nn.Dropout(p=dropout), # refer to paper section 6
203
+ # nn.Linear(last_channel, n_class),
204
+ # )
205
+
206
+ self._initialize_weights()
207
+
208
+ def forward(self, x):
209
+ x = self.features(x)
210
+ x_share = x.mean(3).mean(2)
211
+
212
+ # x = self.classifier(x)
213
+ # print(x_share.shape)
214
+ # xp = self.fc_param(x_share) # param
215
+ # xl = self.fc_lm(x_share) # lm
216
+
217
+ xp = self.fc(x_share) # param
218
+
219
+ return xp # , xl
220
+
221
+ def _initialize_weights(self):
222
+ # weight initialization
223
+ for m in self.modules():
224
+ if isinstance(m, nn.Conv2d):
225
+ nn.init.kaiming_normal_(m.weight, mode='fan_out')
226
+ if m.bias is not None:
227
+ nn.init.zeros_(m.bias)
228
+ elif isinstance(m, nn.BatchNorm2d):
229
+ nn.init.ones_(m.weight)
230
+ nn.init.zeros_(m.bias)
231
+ elif isinstance(m, nn.Linear):
232
+ nn.init.normal_(m.weight, 0, 0.01)
233
+ if m.bias is not None:
234
+ nn.init.zeros_(m.bias)
235
+
236
+
237
+ def mobilenet_v3(**kwargs):
238
+ model = MobileNetV3(
239
+ widen_factor=kwargs.get('widen_factor', 1.0),
240
+ num_classes=kwargs.get('num_classes', 62),
241
+ num_landmarks=kwargs.get('num_landmarks', 136),
242
+ input_size=kwargs.get('size', 128),
243
+ mode=kwargs.get('mode', 'small')
244
+ )
245
+
246
+ return model
tddfa/models/resnet.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+
4
+ import torch.nn as nn
5
+
6
+ __all__ = ['ResNet', 'resnet22']
7
+
8
+
9
+ def conv3x3(in_planes, out_planes, stride=1):
10
+ "3x3 convolution with padding"
11
+ return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
12
+ padding=1, bias=False)
13
+
14
+
15
+ class BasicBlock(nn.Module):
16
+ expansion = 1
17
+
18
+ def __init__(self, inplanes, planes, stride=1, downsample=None):
19
+ super(BasicBlock, self).__init__()
20
+ self.conv1 = conv3x3(inplanes, planes, stride)
21
+ self.bn1 = nn.BatchNorm2d(planes)
22
+ self.relu = nn.ReLU(inplace=True)
23
+ self.conv2 = conv3x3(planes, planes)
24
+ self.bn2 = nn.BatchNorm2d(planes)
25
+ self.downsample = downsample
26
+ self.stride = stride
27
+
28
+ def forward(self, x):
29
+ residual = x
30
+
31
+ out = self.conv1(x)
32
+ out = self.bn1(out)
33
+ out = self.relu(out)
34
+
35
+ out = self.conv2(out)
36
+ out = self.bn2(out)
37
+
38
+ if self.downsample is not None:
39
+ residual = self.downsample(x)
40
+
41
+ out += residual
42
+ out = self.relu(out)
43
+
44
+ return out
45
+
46
+
47
+ class ResNet(nn.Module):
48
+ """Another Strucutre used in caffe-resnet25"""
49
+
50
+ def __init__(self, block, layers, num_classes=62, num_landmarks=136, input_channel=3, fc_flg=False):
51
+ self.inplanes = 64
52
+ super(ResNet, self).__init__()
53
+ self.conv1 = nn.Conv2d(input_channel, 32, kernel_size=5, stride=2, padding=2, bias=False)
54
+ self.bn1 = nn.BatchNorm2d(32) # 32 is input channels number
55
+ self.relu1 = nn.ReLU(inplace=True)
56
+
57
+ self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1, bias=False)
58
+ self.bn2 = nn.BatchNorm2d(64)
59
+ self.relu2 = nn.ReLU(inplace=True)
60
+
61
+ # self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
62
+
63
+ self.layer1 = self._make_layer(block, 128, layers[0], stride=2)
64
+ self.layer2 = self._make_layer(block, 256, layers[1], stride=2)
65
+ self.layer3 = self._make_layer(block, 512, layers[2], stride=2)
66
+
67
+ self.conv_param = nn.Conv2d(512, num_classes, 1)
68
+ # self.conv_lm = nn.Conv2d(512, num_landmarks, 1)
69
+ self.avgpool = nn.AdaptiveAvgPool2d(1)
70
+ # self.fc = nn.Linear(512 * block.expansion, num_classes)
71
+ self.fc_flg = fc_flg
72
+
73
+ # parameter initialization
74
+ for m in self.modules():
75
+ if isinstance(m, nn.Conv2d):
76
+ # 1.
77
+ # n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
78
+ # m.weight.data.normal_(0, math.sqrt(2. / n))
79
+
80
+ # 2. kaiming normal
81
+ nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
82
+ elif isinstance(m, nn.BatchNorm2d):
83
+ m.weight.data.fill_(1)
84
+ m.bias.data.zero_()
85
+
86
+ def _make_layer(self, block, planes, blocks, stride=1):
87
+ downsample = None
88
+ if stride != 1 or self.inplanes != planes * block.expansion:
89
+ downsample = nn.Sequential(
90
+ nn.Conv2d(self.inplanes, planes * block.expansion,
91
+ kernel_size=1, stride=stride, bias=False),
92
+ nn.BatchNorm2d(planes * block.expansion),
93
+ )
94
+
95
+ layers = []
96
+ layers.append(block(self.inplanes, planes, stride, downsample))
97
+ self.inplanes = planes * block.expansion
98
+ for i in range(1, blocks):
99
+ layers.append(block(self.inplanes, planes))
100
+
101
+ return nn.Sequential(*layers)
102
+
103
+ def forward(self, x):
104
+ x = self.conv1(x)
105
+ x = self.bn1(x)
106
+ x = self.relu1(x)
107
+
108
+ x = self.conv2(x)
109
+ x = self.bn2(x)
110
+ x = self.relu2(x)
111
+
112
+ # x = self.maxpool(x)
113
+
114
+ x = self.layer1(x)
115
+ x = self.layer2(x)
116
+ x = self.layer3(x)
117
+
118
+ # if self.fc_flg:
119
+ # x = self.avgpool(x)
120
+ # x = x.view(x.size(0), -1)
121
+ # x = self.fc(x)
122
+ # else:
123
+ xp = self.conv_param(x)
124
+ xp = self.avgpool(xp)
125
+ xp = xp.view(xp.size(0), -1)
126
+
127
+ # xl = self.conv_lm(x)
128
+ # xl = self.avgpool(xl)
129
+ # xl = xl.view(xl.size(0), -1)
130
+
131
+ return xp # , xl
132
+
133
+
134
+ def resnet22(**kwargs):
135
+ model = ResNet(
136
+ BasicBlock,
137
+ [3, 4, 3],
138
+ num_landmarks=kwargs.get('num_landmarks', 136),
139
+ input_channel=kwargs.get('input_channel', 3),
140
+ fc_flg=False
141
+ )
142
+ return model
143
+
144
+
145
+ def main():
146
+ pass
147
+
148
+
149
+ if __name__ == '__main__':
150
+ main()
tddfa/utils/__init__.py ADDED
File without changes
tddfa/utils/asset/.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ *.so
tddfa/utils/asset/build_render_ctypes.sh ADDED
@@ -0,0 +1 @@
 
 
1
+ gcc -shared -Wall -O3 render.c -o render.so -fPIC
tddfa/utils/asset/render.c ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include <math.h>
2
+ #include <stdlib.h>
3
+ #include <stdio.h>
4
+
5
+ #define max(x, y) (((x) > (y)) ? (x) : (y))
6
+ #define min(x, y) (((x) < (y)) ? (x) : (y))
7
+ #define clip(_x, _min, _max) min(max(_x, _min), _max)
8
+
9
+ struct Tuple3D
10
+ {
11
+ float x;
12
+ float y;
13
+ float z;
14
+ };
15
+
16
+ void _render(const int *triangles,
17
+ const int ntri,
18
+ const float *light,
19
+ const float *directional,
20
+ const float *ambient,
21
+ const float *vertices,
22
+ const int nver,
23
+ unsigned char *image,
24
+ const int h, const int w)
25
+ {
26
+ int tri_p0_ind, tri_p1_ind, tri_p2_ind;
27
+ int color_index;
28
+ float dot00, dot01, dot11, dot02, dot12;
29
+ float cos_sum, det;
30
+
31
+ struct Tuple3D p0, p1, p2;
32
+ struct Tuple3D v0, v1, v2;
33
+ struct Tuple3D p, start, end;
34
+
35
+ struct Tuple3D ver_max = {-1.0e8, -1.0e8, -1.0e8};
36
+ struct Tuple3D ver_min = {1.0e8, 1.0e8, 1.0e8};
37
+ struct Tuple3D ver_mean = {0.0, 0.0, 0.0};
38
+
39
+ float *ver_normal = (float *)calloc(3 * nver, sizeof(float));
40
+ float *colors = (float *)malloc(3 * nver * sizeof(float));
41
+ float *depth_buffer = (float *)calloc(h * w, sizeof(float));
42
+
43
+ for (int i = 0; i < ntri; i++)
44
+ {
45
+ tri_p0_ind = triangles[3 * i];
46
+ tri_p1_ind = triangles[3 * i + 1];
47
+ tri_p2_ind = triangles[3 * i + 2];
48
+
49
+ // counter clockwise order
50
+ start.x = vertices[tri_p1_ind] - vertices[tri_p0_ind];
51
+ start.y = vertices[tri_p1_ind + 1] - vertices[tri_p0_ind + 1];
52
+ start.z = vertices[tri_p1_ind + 2] - vertices[tri_p0_ind + 2];
53
+
54
+ end.x = vertices[tri_p2_ind] - vertices[tri_p0_ind];
55
+ end.y = vertices[tri_p2_ind + 1] - vertices[tri_p0_ind + 1];
56
+ end.z = vertices[tri_p2_ind + 2] - vertices[tri_p0_ind + 2];
57
+
58
+ p.x = start.y * end.z - start.z * end.y;
59
+ p.y = start.z * end.x - start.x * end.z;
60
+ p.z = start.x * end.y - start.y * end.x;
61
+
62
+ ver_normal[tri_p0_ind] += p.x;
63
+ ver_normal[tri_p1_ind] += p.x;
64
+ ver_normal[tri_p2_ind] += p.x;
65
+
66
+ ver_normal[tri_p0_ind + 1] += p.y;
67
+ ver_normal[tri_p1_ind + 1] += p.y;
68
+ ver_normal[tri_p2_ind + 1] += p.y;
69
+
70
+ ver_normal[tri_p0_ind + 2] += p.z;
71
+ ver_normal[tri_p1_ind + 2] += p.z;
72
+ ver_normal[tri_p2_ind + 2] += p.z;
73
+ }
74
+
75
+ for (int i = 0; i < nver; ++i)
76
+ {
77
+ p.x = ver_normal[3 * i];
78
+ p.y = ver_normal[3 * i + 1];
79
+ p.z = ver_normal[3 * i + 2];
80
+
81
+ det = sqrt(p.x * p.x + p.y * p.y + p.z * p.z);
82
+ if (det <= 0)
83
+ det = 1e-6;
84
+
85
+ ver_normal[3 * i] /= det;
86
+ ver_normal[3 * i + 1] /= det;
87
+ ver_normal[3 * i + 2] /= det;
88
+
89
+ ver_mean.x += p.x;
90
+ ver_mean.y += p.y;
91
+ ver_mean.z += p.z;
92
+
93
+ ver_max.x = max(ver_max.x, p.x);
94
+ ver_max.y = max(ver_max.y, p.y);
95
+ ver_max.z = max(ver_max.z, p.z);
96
+
97
+ ver_min.x = min(ver_min.x, p.x);
98
+ ver_min.y = min(ver_min.y, p.y);
99
+ ver_min.z = min(ver_min.z, p.z);
100
+ }
101
+
102
+ ver_mean.x /= nver;
103
+ ver_mean.y /= nver;
104
+ ver_mean.z /= nver;
105
+
106
+ for (int i = 0; i < nver; ++i)
107
+ {
108
+ colors[3 * i] = vertices[3 * i];
109
+ colors[3 * i + 1] = vertices[3 * i + 1];
110
+ colors[3 * i + 2] = vertices[3 * i + 2];
111
+
112
+ colors[3 * i] -= ver_mean.x;
113
+ colors[3 * i] /= ver_max.x - ver_min.x;
114
+
115
+ colors[3 * i + 1] -= ver_mean.y;
116
+ colors[3 * i + 1] /= ver_max.y - ver_min.y;
117
+
118
+ colors[3 * i + 2] -= ver_mean.z;
119
+ colors[3 * i + 2] /= ver_max.z - ver_min.z;
120
+
121
+ p.x = light[0] - colors[3 * i];
122
+ p.y = light[1] - colors[3 * i + 1];
123
+ p.z = light[2] - colors[3 * i + 2];
124
+
125
+ det = sqrt(p.x * p.x + p.y * p.y + p.z * p.z);
126
+ if (det <= 0)
127
+ det = 1e-6;
128
+
129
+ colors[3 * i] = p.x / det;
130
+ colors[3 * i + 1] = p.y / det;
131
+ colors[3 * i + 2] = p.z / det;
132
+
133
+ colors[3 * i] *= ver_normal[3 * i];
134
+ colors[3 * i + 1] *= ver_normal[3 * i + 1];
135
+ colors[3 * i + 2] *= ver_normal[3 * i + 2];
136
+
137
+ cos_sum = colors[3 * i] + colors[3 * i + 1] + colors[3 * i + 2];
138
+
139
+ colors[3 * i] = clip(cos_sum * directional[0] + ambient[0], 0, 1);
140
+ colors[3 * i + 1] = clip(cos_sum * directional[1] + ambient[1], 0, 1);
141
+ colors[3 * i + 2] = clip(cos_sum * directional[2] + ambient[2], 0, 1);
142
+ }
143
+
144
+ for (int i = 0; i < ntri; ++i)
145
+ {
146
+ tri_p0_ind = triangles[3 * i];
147
+ tri_p1_ind = triangles[3 * i + 1];
148
+ tri_p2_ind = triangles[3 * i + 2];
149
+
150
+ p0.x = vertices[tri_p0_ind];
151
+ p0.y = vertices[tri_p0_ind + 1];
152
+ p0.z = vertices[tri_p0_ind + 2];
153
+
154
+ p1.x = vertices[tri_p1_ind];
155
+ p1.y = vertices[tri_p1_ind + 1];
156
+ p1.z = vertices[tri_p1_ind + 2];
157
+
158
+ p2.x = vertices[tri_p2_ind];
159
+ p2.y = vertices[tri_p2_ind + 1];
160
+ p2.z = vertices[tri_p2_ind + 2];
161
+
162
+ start.x = max(ceil(min(p0.x, min(p1.x, p2.x))), 0);
163
+ end.x = min(floor(max(p0.x, max(p1.x, p2.x))), w - 1);
164
+
165
+ start.y = max(ceil(min(p0.y, min(p1.y, p2.y))), 0);
166
+ end.y = min(floor(max(p0.y, max(p1.y, p2.y))), h - 1);
167
+
168
+ if (end.x < start.x || end.y < start.y)
169
+ continue;
170
+
171
+ v0.x = p2.x - p0.x;
172
+ v0.y = p2.y - p0.y;
173
+ v1.x = p1.x - p0.x;
174
+ v1.y = p1.y - p0.y;
175
+
176
+ // dot products np.dot(v0.T, v0)
177
+ dot00 = v0.x * v0.x + v0.y * v0.y;
178
+ dot01 = v0.x * v1.x + v0.y * v1.y;
179
+ dot11 = v1.x * v1.x + v1.y * v1.y;
180
+
181
+ // barycentric coordinates
182
+ start.z = dot00 * dot11 - dot01 * dot01;
183
+ if (start.z != 0)
184
+ start.z = 1 / start.z;
185
+
186
+ for (p.y = start.y; p.y <= end.y; p.y += 1.0)
187
+ {
188
+ for (p.x = start.x; p.x <= end.x; p.x += 1.0)
189
+ {
190
+ v2.x = p.x - p0.x;
191
+ v2.y = p.y - p0.y;
192
+
193
+ dot02 = v0.x * v2.x + v0.y * v2.y;
194
+ dot12 = v1.x * v2.x + v1.y * v2.y;
195
+
196
+ v2.z = (dot11 * dot02 - dot01 * dot12) * start.z;
197
+ v1.z = (dot00 * dot12 - dot01 * dot02) * start.z;
198
+ v0.z = 1 - v2.z - v1.z;
199
+
200
+ // judge is_point_in_tri by below line of code
201
+ if (v2.z > 0 && v1.z > 0 && v0.z > 0)
202
+ {
203
+ p.z = v0.z * p0.z + v1.z * p1.z + v2.z * p2.z;
204
+ color_index = p.y * w + p.x;
205
+
206
+ if (p.z > depth_buffer[color_index])
207
+ {
208
+ end.z = v0.z * colors[tri_p0_ind];
209
+ end.z += v1.z * colors[tri_p1_ind];
210
+ end.z += v2.z * colors[tri_p2_ind];
211
+ image[3 * color_index] = end.z * 255;
212
+
213
+ end.z = v0.z * colors[tri_p0_ind + 1];
214
+ end.z += v1.z * colors[tri_p1_ind + 1];
215
+ end.z += v2.z * colors[tri_p2_ind + 1];
216
+ image[3 * color_index + 1] = end.z * 255;
217
+
218
+ end.z = v0.z * colors[tri_p0_ind + 2];
219
+ end.z += v1.z * colors[tri_p1_ind + 2];
220
+ end.z += v2.z * colors[tri_p2_ind + 2];
221
+ image[3 * color_index + 2] = end.z * 255;
222
+
223
+ depth_buffer[color_index] = p.z;
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
229
+
230
+ free(depth_buffer);
231
+ free(colors);
232
+ free(ver_normal);
233
+ }
tddfa/utils/depth.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ __author__ = 'cleardusk'
4
+
5
+ import sys
6
+
7
+ sys.path.append('..')
8
+
9
+ import cv2
10
+ import numpy as np
11
+
12
+ from tddfa.Sim3DR import rasterize
13
+ from tddfa.utils.functions import plot_image
14
+ from .tddfa_util import _to_ctype
15
+
16
+
17
+ def depth(img, ver_lst, tri, show_flag=False, wfp=None, with_bg_flag=True):
18
+ if with_bg_flag:
19
+ overlap = img.copy()
20
+ else:
21
+ overlap = np.zeros_like(img)
22
+
23
+ for ver_ in ver_lst:
24
+ ver = _to_ctype(ver_.T) # transpose
25
+
26
+ z = ver[:, 2]
27
+ z_min, z_max = min(z), max(z)
28
+
29
+ z = (z - z_min) / (z_max - z_min)
30
+
31
+ # expand
32
+ z = np.repeat(z[:, np.newaxis], 3, axis=1)
33
+
34
+ overlap = rasterize(ver, tri, z, bg=overlap)
35
+
36
+ if wfp is not None:
37
+ cv2.imwrite(wfp, overlap)
38
+ # print(f'Save visualization result to {wfp}')
39
+
40
+ if show_flag:
41
+ plot_image(overlap)
42
+
43
+ return overlap
tddfa/utils/functions.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ __author__ = 'cleardusk'
4
+
5
+ import numpy as np
6
+ import cv2
7
+ from math import sqrt
8
+ import matplotlib.pyplot as plt
9
+
10
+ RED = (0, 0, 255)
11
+ GREEN = (0, 255, 0)
12
+ BLUE = (255, 0, 0)
13
+
14
+
15
+ def get_suffix(filename):
16
+ """a.jpg -> jpg"""
17
+ pos = filename.rfind('.')
18
+ if pos == -1:
19
+ return ''
20
+ return filename[pos:]
21
+
22
+
23
+ def crop_img(img, roi_box):
24
+ h, w = img.shape[:2]
25
+
26
+ sx, sy, ex, ey = [int(round(_)) for _ in roi_box]
27
+ dh, dw = ey - sy, ex - sx
28
+ if len(img.shape) == 3:
29
+ res = np.zeros((dh, dw, 3), dtype=np.uint8)
30
+ else:
31
+ res = np.zeros((dh, dw), dtype=np.uint8)
32
+ if sx < 0:
33
+ sx, dsx = 0, -sx
34
+ else:
35
+ dsx = 0
36
+
37
+ if ex > w:
38
+ ex, dex = w, dw - (ex - w)
39
+ else:
40
+ dex = dw
41
+
42
+ if sy < 0:
43
+ sy, dsy = 0, -sy
44
+ else:
45
+ dsy = 0
46
+
47
+ if ey > h:
48
+ ey, dey = h, dh - (ey - h)
49
+ else:
50
+ dey = dh
51
+
52
+ res[dsy:dey, dsx:dex] = img[sy:ey, sx:ex]
53
+ return res
54
+
55
+
56
+ def calc_hypotenuse(pts):
57
+ bbox = [min(pts[0, :]), min(pts[1, :]), max(pts[0, :]), max(pts[1, :])]
58
+ center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2]
59
+ radius = max(bbox[2] - bbox[0], bbox[3] - bbox[1]) / 2
60
+ bbox = [center[0] - radius, center[1] - radius, center[0] + radius, center[1] + radius]
61
+ llength = sqrt((bbox[2] - bbox[0]) ** 2 + (bbox[3] - bbox[1]) ** 2)
62
+ return llength / 3
63
+
64
+
65
+ def parse_roi_box_from_landmark(pts):
66
+ """calc roi box from landmark"""
67
+ bbox = [min(pts[0, :]), min(pts[1, :]), max(pts[0, :]), max(pts[1, :])]
68
+ center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2]
69
+ radius = max(bbox[2] - bbox[0], bbox[3] - bbox[1]) / 2
70
+ bbox = [center[0] - radius, center[1] - radius, center[0] + radius, center[1] + radius]
71
+
72
+ llength = sqrt((bbox[2] - bbox[0]) ** 2 + (bbox[3] - bbox[1]) ** 2)
73
+ center_x = (bbox[2] + bbox[0]) / 2
74
+ center_y = (bbox[3] + bbox[1]) / 2
75
+
76
+ roi_box = [0] * 4
77
+ roi_box[0] = center_x - llength / 2
78
+ roi_box[1] = center_y - llength / 2
79
+ roi_box[2] = roi_box[0] + llength
80
+ roi_box[3] = roi_box[1] + llength
81
+
82
+ return roi_box
83
+
84
+
85
+ def parse_roi_box_from_bbox(bbox):
86
+ left, top, right, bottom = bbox[:4]
87
+ old_size = (right - left + bottom - top) / 2
88
+ center_x = right - (right - left) / 2.0
89
+ center_y = bottom - (bottom - top) / 2.0 + old_size * 0.14
90
+ size = int(old_size * 1.58)
91
+
92
+ roi_box = [0] * 4
93
+ roi_box[0] = center_x - size / 2
94
+ roi_box[1] = center_y - size / 2
95
+ roi_box[2] = roi_box[0] + size
96
+ roi_box[3] = roi_box[1] + size
97
+
98
+ return roi_box
99
+
100
+
101
+ def plot_image(img):
102
+ height, width = img.shape[:2]
103
+ plt.figure(figsize=(12, height / width * 12))
104
+
105
+ plt.subplots_adjust(left=0, right=1, top=1, bottom=0)
106
+ plt.axis('off')
107
+
108
+ plt.imshow(img[..., ::-1])
109
+ plt.show()
110
+
111
+
112
+ def draw_landmarks(img, pts, style='fancy', wfp=None, show_flag=False, **kwargs):
113
+ """Draw landmarks using matplotlib"""
114
+ height, width = img.shape[:2]
115
+ plt.figure(figsize=(12, height / width * 12))
116
+ plt.imshow(img[..., ::-1])
117
+ plt.subplots_adjust(left=0, right=1, top=1, bottom=0)
118
+ plt.axis('off')
119
+
120
+ dense_flag = kwargs.get('dense_flag')
121
+
122
+ if not type(pts) in [tuple, list]:
123
+ pts = [pts]
124
+ for i in range(len(pts)):
125
+ if dense_flag:
126
+ plt.plot(pts[i][0, ::6], pts[i][1, ::6], 'o', markersize=0.4, color='c', alpha=0.7)
127
+ else:
128
+ alpha = 0.8
129
+ markersize = 4
130
+ lw = 1.5
131
+ color = kwargs.get('color', 'w')
132
+ markeredgecolor = kwargs.get('markeredgecolor', 'black')
133
+
134
+ nums = [0, 17, 22, 27, 31, 36, 42, 48, 60, 68]
135
+
136
+ # close eyes and mouths
137
+ plot_close = lambda i1, i2: plt.plot([pts[i][0, i1], pts[i][0, i2]], [pts[i][1, i1], pts[i][1, i2]],
138
+ color=color, lw=lw, alpha=alpha - 0.1)
139
+ plot_close(41, 36)
140
+ plot_close(47, 42)
141
+ plot_close(59, 48)
142
+ plot_close(67, 60)
143
+
144
+ for ind in range(len(nums) - 1):
145
+ l, r = nums[ind], nums[ind + 1]
146
+ plt.plot(pts[i][0, l:r], pts[i][1, l:r], color=color, lw=lw, alpha=alpha - 0.1)
147
+
148
+ plt.plot(pts[i][0, l:r], pts[i][1, l:r], marker='o', linestyle='None', markersize=markersize,
149
+ color=color,
150
+ markeredgecolor=markeredgecolor, alpha=alpha)
151
+ if wfp is not None:
152
+ plt.savefig(wfp, dpi=150)
153
+ print(f'Save visualization result to {wfp}')
154
+
155
+ if show_flag:
156
+ plt.show()
157
+
158
+
159
+ def cv_draw_landmark(img_ori, pts, box=None, color=GREEN, size=1):
160
+ img = img_ori.copy()
161
+ n = pts.shape[1]
162
+ if n <= 106:
163
+ for i in range(n):
164
+ cv2.circle(img, (int(round(pts[0, i])), int(round(pts[1, i]))), size, color, -1)
165
+ else:
166
+ sep = 1
167
+ for i in range(0, n, sep):
168
+ cv2.circle(img, (int(round(pts[0, i])), int(round(pts[1, i]))), size, color, 1)
169
+
170
+ if box is not None:
171
+ left, top, right, bottom = np.round(box).astype(np.int32)
172
+ left_top = (left, top)
173
+ right_top = (right, top)
174
+ right_bottom = (right, bottom)
175
+ left_bottom = (left, bottom)
176
+ cv2.line(img, left_top, right_top, BLUE, 1, cv2.LINE_AA)
177
+ cv2.line(img, right_top, right_bottom, BLUE, 1, cv2.LINE_AA)
178
+ cv2.line(img, right_bottom, left_bottom, BLUE, 1, cv2.LINE_AA)
179
+ cv2.line(img, left_bottom, left_top, BLUE, 1, cv2.LINE_AA)
180
+
181
+ return img
182
+
tddfa/utils/io.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ __author__ = 'cleardusk'
4
+
5
+ import os
6
+ import numpy as np
7
+ import torch
8
+ import pickle
9
+
10
+
11
+ def mkdir(d):
12
+ os.makedirs(d, exist_ok=True)
13
+
14
+
15
+ def _get_suffix(filename):
16
+ """a.jpg -> jpg"""
17
+ pos = filename.rfind('.')
18
+ if pos == -1:
19
+ return ''
20
+ return filename[pos + 1:]
21
+
22
+
23
+ def _load(fp):
24
+ suffix = _get_suffix(fp)
25
+ if suffix == 'npy':
26
+ return np.load(fp)
27
+ elif suffix == 'pkl':
28
+ return pickle.load(open(fp, 'rb'))
29
+
30
+
31
+ def _dump(wfp, obj):
32
+ suffix = _get_suffix(wfp)
33
+ if suffix == 'npy':
34
+ np.save(wfp, obj)
35
+ elif suffix == 'pkl':
36
+ pickle.dump(obj, open(wfp, 'wb'))
37
+ else:
38
+ raise Exception('Unknown Type: {}'.format(suffix))
39
+
40
+
41
+ def _load_tensor(fp, mode='cpu'):
42
+ if mode.lower() == 'cpu':
43
+ return torch.from_numpy(_load(fp))
44
+ elif mode.lower() == 'gpu':
45
+ return torch.from_numpy(_load(fp)).cuda()
46
+
47
+
48
+ def _tensor_to_cuda(x):
49
+ if x.is_cuda:
50
+ return x
51
+ else:
52
+ return x.cuda()
53
+
54
+
55
+ def _load_gpu(fp):
56
+ return torch.from_numpy(_load(fp)).cuda()
57
+
58
+
59
+ _load_cpu = _load
60
+ _numpy_to_tensor = lambda x: torch.from_numpy(x)
61
+ _tensor_to_numpy = lambda x: x.numpy()
62
+ _numpy_to_cuda = lambda x: _tensor_to_cuda(torch.from_numpy(x))
63
+ _cuda_to_tensor = lambda x: x.cpu()
64
+ _cuda_to_numpy = lambda x: x.cpu().numpy()
tddfa/utils/onnx.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ __author__ = 'cleardusk'
4
+
5
+ import sys
6
+
7
+ sys.path.append('..')
8
+
9
+ import torch
10
+ import tddfa.models
11
+ from tddfa.utils.tddfa_util import load_model
12
+
13
+
14
+ def convert_to_onnx(**kvs):
15
+ # 1. load model
16
+ size = kvs.get('size', 120)
17
+ model = getattr(models, kvs.get('arch'))(
18
+ num_classes=kvs.get('num_params', 62),
19
+ widen_factor=kvs.get('widen_factor', 1),
20
+ size=size,
21
+ mode=kvs.get('mode', 'small')
22
+ )
23
+ checkpoint_fp = kvs.get('checkpoint_fp')
24
+ model = load_model(model, checkpoint_fp)
25
+ model.eval()
26
+
27
+ # 2. convert
28
+ batch_size = 1
29
+ dummy_input = torch.randn(batch_size, 3, size, size)
30
+ wfp = checkpoint_fp.replace('.pth', '.onnx')
31
+ torch.onnx.export(
32
+ model,
33
+ (dummy_input, ),
34
+ wfp,
35
+ input_names=['input'],
36
+ output_names=['output'],
37
+ do_constant_folding=True
38
+ )
39
+ print(f'Convert {checkpoint_fp} to {wfp} done.')
40
+ return wfp
tddfa/utils/pncc.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ __author__ = 'cleardusk'
4
+
5
+ import sys
6
+
7
+ sys.path.append('..')
8
+
9
+ import cv2
10
+ import numpy as np
11
+ import os.path as osp
12
+
13
+ from Sim3DR import rasterize
14
+ from utils.functions import plot_image
15
+ from utils.io import _load, _dump
16
+ from utils.tddfa_util import _to_ctype
17
+
18
+ make_abs_path = lambda fn: osp.join(osp.dirname(osp.realpath(__file__)), fn)
19
+
20
+
21
+ def calc_ncc_code():
22
+ from bfm import bfm
23
+
24
+ # formula: ncc_d = ( u_d - min(u_d) ) / ( max(u_d) - min(u_d) ), d = {r, g, b}
25
+ u = bfm.u
26
+ u = u.reshape(3, -1, order='F')
27
+
28
+ for i in range(3):
29
+ u[i] = (u[i] - u[i].min()) / (u[i].max() - u[i].min())
30
+
31
+ _dump('../configs/ncc_code.npy', u)
32
+
33
+
34
+ def pncc(img, ver_lst, tri, show_flag=False, wfp=None, with_bg_flag=True):
35
+ ncc_code = _load(make_abs_path('../configs/ncc_code.npy'))
36
+
37
+ if with_bg_flag:
38
+ overlap = img.copy()
39
+ else:
40
+ overlap = np.zeros_like(img)
41
+
42
+ # rendering pncc
43
+ for ver_ in ver_lst:
44
+ ver = _to_ctype(ver_.T) # transpose
45
+ overlap = rasterize(ver, tri, ncc_code.T, bg=overlap) # m x 3
46
+
47
+ if wfp is not None:
48
+ cv2.imwrite(wfp, overlap)
49
+ print(f'Save visualization result to {wfp}')
50
+
51
+ if show_flag:
52
+ plot_image(overlap)
53
+
54
+ return overlap
55
+
56
+
57
+ def main():
58
+ # `configs/ncc_code.npy` is generated by `calc_nnc_code` function
59
+ # calc_ncc_code()
60
+ pass
61
+
62
+
63
+ if __name__ == '__main__':
64
+ main()
tddfa/utils/pose.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ """
4
+ Reference: https://github.com/YadiraF/PRNet/blob/master/utils/estimate_pose.py
5
+
6
+ Calculating pose from the output 3DMM parameters, you can also try to use solvePnP to perform estimation
7
+ """
8
+
9
+ __author__ = 'cleardusk'
10
+
11
+ import cv2
12
+ import numpy as np
13
+ from math import cos, sin, atan2, asin, sqrt
14
+
15
+ from .functions import calc_hypotenuse, plot_image
16
+
17
+
18
+ def P2sRt(P):
19
+ """ decompositing camera matrix P.
20
+ Args:
21
+ P: (3, 4). Affine Camera Matrix.
22
+ Returns:
23
+ s: scale factor.
24
+ R: (3, 3). rotation matrix.
25
+ t2d: (2,). 2d translation.
26
+ """
27
+ t3d = P[:, 3]
28
+ R1 = P[0:1, :3]
29
+ R2 = P[1:2, :3]
30
+ s = (np.linalg.norm(R1) + np.linalg.norm(R2)) / 2.0
31
+ r1 = R1 / np.linalg.norm(R1)
32
+ r2 = R2 / np.linalg.norm(R2)
33
+ r3 = np.cross(r1, r2)
34
+
35
+ R = np.concatenate((r1, r2, r3), 0)
36
+ return s, R, t3d
37
+
38
+
39
+ def matrix2angle(R):
40
+ """ compute three Euler angles from a Rotation Matrix. Ref: http://www.gregslabaugh.net/publications/euler.pdf
41
+ refined by: https://stackoverflow.com/questions/43364900/rotation-matrix-to-euler-angles-with-opencv
42
+ todo: check and debug
43
+ Args:
44
+ R: (3,3). rotation matrix
45
+ Returns:
46
+ x: yaw
47
+ y: pitch
48
+ z: roll
49
+ """
50
+ if R[2, 0] > 0.998:
51
+ z = 0
52
+ x = np.pi / 2
53
+ y = z + atan2(-R[0, 1], -R[0, 2])
54
+ elif R[2, 0] < -0.998:
55
+ z = 0
56
+ x = -np.pi / 2
57
+ y = -z + atan2(R[0, 1], R[0, 2])
58
+ else:
59
+ x = asin(R[2, 0])
60
+ y = atan2(R[2, 1] / cos(x), R[2, 2] / cos(x))
61
+ z = atan2(R[1, 0] / cos(x), R[0, 0] / cos(x))
62
+
63
+ return x, y, z
64
+
65
+
66
+ def calc_pose(param):
67
+ P = param[:12].reshape(3, -1) # camera matrix
68
+ s, R, t3d = P2sRt(P)
69
+ P = np.concatenate((R, t3d.reshape(3, -1)), axis=1) # without scale
70
+ pose = matrix2angle(R)
71
+ pose = [p * 180 / np.pi for p in pose]
72
+
73
+ return P, pose
74
+
75
+
76
+ def build_camera_box(rear_size=90):
77
+ point_3d = []
78
+ rear_depth = 0
79
+ point_3d.append((-rear_size, -rear_size, rear_depth))
80
+ point_3d.append((-rear_size, rear_size, rear_depth))
81
+ point_3d.append((rear_size, rear_size, rear_depth))
82
+ point_3d.append((rear_size, -rear_size, rear_depth))
83
+ point_3d.append((-rear_size, -rear_size, rear_depth))
84
+
85
+ front_size = int(4 / 3 * rear_size)
86
+ front_depth = int(4 / 3 * rear_size)
87
+ point_3d.append((-front_size, -front_size, front_depth))
88
+ point_3d.append((-front_size, front_size, front_depth))
89
+ point_3d.append((front_size, front_size, front_depth))
90
+ point_3d.append((front_size, -front_size, front_depth))
91
+ point_3d.append((-front_size, -front_size, front_depth))
92
+ point_3d = np.array(point_3d, dtype=np.float32).reshape(-1, 3)
93
+
94
+ return point_3d
95
+
96
+
97
+ def plot_pose_box(img, P, ver, color=(40, 255, 0), line_width=2):
98
+ """ Draw a 3D box as annotation of pose.
99
+ Ref:https://github.com/yinguobing/head-pose-estimation/blob/master/pose_estimator.py
100
+ Args:
101
+ img: the input image
102
+ P: (3, 4). Affine Camera Matrix.
103
+ kpt: (2, 68) or (3, 68)
104
+ """
105
+ llength = calc_hypotenuse(ver)
106
+ point_3d = build_camera_box(llength)
107
+ # Map to 2d image points
108
+ point_3d_homo = np.hstack((point_3d, np.ones([point_3d.shape[0], 1]))) # n x 4
109
+ point_2d = point_3d_homo.dot(P.T)[:, :2]
110
+
111
+ point_2d[:, 1] = - point_2d[:, 1]
112
+ point_2d[:, :2] = point_2d[:, :2] - np.mean(point_2d[:4, :2], 0) + np.mean(ver[:2, :27], 1)
113
+ point_2d = np.int32(point_2d.reshape(-1, 2))
114
+
115
+ # Draw all the lines
116
+ cv2.polylines(img, [point_2d], True, color, line_width, cv2.LINE_AA)
117
+ cv2.line(img, tuple(point_2d[1]), tuple(
118
+ point_2d[6]), color, line_width, cv2.LINE_AA)
119
+ cv2.line(img, tuple(point_2d[2]), tuple(
120
+ point_2d[7]), color, line_width, cv2.LINE_AA)
121
+ cv2.line(img, tuple(point_2d[3]), tuple(
122
+ point_2d[8]), color, line_width, cv2.LINE_AA)
123
+
124
+ return img
125
+
126
+
127
+ def viz_pose(img, param_lst, ver_lst, show_flag=False, wfp=None):
128
+ for param, ver in zip(param_lst, ver_lst):
129
+ P, pose = calc_pose(param)
130
+ img = plot_pose_box(img, P, ver)
131
+ # print(P[:, :3])
132
+ print(f'yaw: {pose[0]:.1f}, pitch: {pose[1]:.1f}, roll: {pose[2]:.1f}')
133
+
134
+ if wfp is not None:
135
+ cv2.imwrite(wfp, img)
136
+ print(f'Save visualization result to {wfp}')
137
+
138
+ if show_flag:
139
+ plot_image(img)
140
+
141
+ return img
tddfa/utils/render.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ __author__ = 'cleardusk'
4
+
5
+ import sys
6
+
7
+ sys.path.append('..')
8
+
9
+ import cv2
10
+ import numpy as np
11
+
12
+ from Sim3DR import RenderPipeline
13
+ from utils.functions import plot_image
14
+ from .tddfa_util import _to_ctype
15
+
16
+ cfg = {
17
+ 'intensity_ambient': 0.3,
18
+ 'color_ambient': (1, 1, 1),
19
+ 'intensity_directional': 0.6,
20
+ 'color_directional': (1, 1, 1),
21
+ 'intensity_specular': 0.1,
22
+ 'specular_exp': 5,
23
+ 'light_pos': (0, 0, 5),
24
+ 'view_pos': (0, 0, 5)
25
+ }
26
+
27
+ render_app = RenderPipeline(**cfg)
28
+
29
+
30
+ def render(img, ver_lst, tri, alpha=0.6, show_flag=False, wfp=None, with_bg_flag=True):
31
+ if with_bg_flag:
32
+ overlap = img.copy()
33
+ else:
34
+ overlap = np.zeros_like(img)
35
+
36
+ for ver_ in ver_lst:
37
+ ver = _to_ctype(ver_.T) # transpose
38
+ overlap = render_app(ver, tri, overlap)
39
+
40
+ if with_bg_flag:
41
+ res = cv2.addWeighted(img, 1 - alpha, overlap, alpha, 0)
42
+ else:
43
+ res = overlap
44
+
45
+ if wfp is not None:
46
+ cv2.imwrite(wfp, res)
47
+ print(f'Save visualization result to {wfp}')
48
+
49
+ if show_flag:
50
+ plot_image(res)
51
+
52
+ return res
tddfa/utils/render_ctypes.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ """
4
+ Borrowed from https://github.com/1996scarlet/Dense-Head-Pose-Estimation/blob/main/service/CtypesMeshRender.py
5
+
6
+ To use this render, you should build the clib first:
7
+ ```
8
+ cd utils/asset
9
+ gcc -shared -Wall -O3 render.c -o render.so -fPIC
10
+ cd ../..
11
+ ```
12
+ """
13
+
14
+ import sys
15
+
16
+ sys.path.append('..')
17
+
18
+ import os.path as osp
19
+ import cv2
20
+ import numpy as np
21
+ import ctypes
22
+ from utils.functions import plot_image
23
+
24
+ make_abs_path = lambda fn: osp.join(osp.dirname(osp.realpath(__file__)), fn)
25
+
26
+
27
+ class TrianglesMeshRender(object):
28
+ def __init__(
29
+ self,
30
+ clibs,
31
+ light=(0, 0, 5),
32
+ direction=(0.6, 0.6, 0.6),
33
+ ambient=(0.3, 0.3, 0.3)
34
+ ):
35
+ if not osp.exists(clibs):
36
+ raise Exception(f'{clibs} not found, please build it first, by run '
37
+ f'"gcc -shared -Wall -O3 render.c -o render.so -fPIC" in utils/asset directory')
38
+
39
+ self._clibs = ctypes.CDLL(clibs)
40
+
41
+ self._light = np.array(light, dtype=np.float32)
42
+ self._light = np.ctypeslib.as_ctypes(self._light)
43
+
44
+ self._direction = np.array(direction, dtype=np.float32)
45
+ self._direction = np.ctypeslib.as_ctypes(self._direction)
46
+
47
+ self._ambient = np.array(ambient, dtype=np.float32)
48
+ self._ambient = np.ctypeslib.as_ctypes(self._ambient)
49
+
50
+ def __call__(self, vertices, triangles, bg):
51
+ self.triangles = np.ctypeslib.as_ctypes(3 * triangles) # Attention
52
+ self.tri_nums = triangles.shape[0]
53
+
54
+ self._clibs._render(
55
+ self.triangles, self.tri_nums,
56
+ self._light, self._direction, self._ambient,
57
+ np.ctypeslib.as_ctypes(vertices),
58
+ vertices.shape[0],
59
+ np.ctypeslib.as_ctypes(bg),
60
+ bg.shape[0], bg.shape[1]
61
+ )
62
+
63
+
64
+ render_app = TrianglesMeshRender(clibs=make_abs_path('asset/render.so'))
65
+
66
+
67
+ def render(img, ver_lst, tri, alpha=0.6, show_flag=False, wfp=None, with_bg_flag=True):
68
+ if with_bg_flag:
69
+ overlap = img.copy()
70
+ else:
71
+ overlap = np.zeros_like(img)
72
+
73
+ for ver_ in ver_lst:
74
+ ver = np.ascontiguousarray(ver_.T) # transpose
75
+ render_app(ver, tri, bg=overlap)
76
+
77
+ if with_bg_flag:
78
+ res = cv2.addWeighted(img, 1 - alpha, overlap, alpha, 0)
79
+ else:
80
+ res = overlap
81
+
82
+ if wfp is not None:
83
+ cv2.imwrite(wfp, res)
84
+ print(f'Save visualization result to {wfp}')
85
+
86
+ if show_flag:
87
+ plot_image(res)
88
+
89
+ return res
tddfa/utils/serialization.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ __author__ = 'cleardusk'
4
+
5
+ import numpy as np
6
+
7
+ from .tddfa_util import _to_ctype
8
+ from .functions import get_suffix
9
+
10
+ header_temp = """ply
11
+ format ascii 1.0
12
+ element vertex {}
13
+ property float x
14
+ property float y
15
+ property float z
16
+ element face {}
17
+ property list uchar int vertex_indices
18
+ end_header
19
+ """
20
+
21
+
22
+ def ser_to_ply_single(ver_lst, tri, height, wfp, reverse=True):
23
+ suffix = get_suffix(wfp)
24
+
25
+ for i, ver in enumerate(ver_lst):
26
+ wfp_new = wfp.replace(suffix, f'_{i + 1}{suffix}')
27
+
28
+ n_vertex = ver.shape[1]
29
+ n_face = tri.shape[0]
30
+ header = header_temp.format(n_vertex, n_face)
31
+
32
+ with open(wfp_new, 'w') as f:
33
+ f.write(header + '\n')
34
+ for i in range(n_vertex):
35
+ x, y, z = ver[:, i]
36
+ if reverse:
37
+ f.write(f'{x:.2f} {height-y:.2f} {z:.2f}\n')
38
+ else:
39
+ f.write(f'{x:.2f} {y:.2f} {z:.2f}\n')
40
+ for i in range(n_face):
41
+ idx1, idx2, idx3 = tri[i] # m x 3
42
+ if reverse:
43
+ f.write(f'3 {idx3} {idx2} {idx1}\n')
44
+ else:
45
+ f.write(f'3 {idx1} {idx2} {idx3}\n')
46
+
47
+ print(f'Dump tp {wfp_new}')
48
+
49
+
50
+ def ser_to_ply_multiple(ver_lst, tri, height, wfp, reverse=True):
51
+ n_ply = len(ver_lst) # count ply
52
+
53
+ if n_ply <= 0:
54
+ return
55
+
56
+ n_vertex = ver_lst[0].shape[1]
57
+ n_face = tri.shape[0]
58
+ header = header_temp.format(n_vertex * n_ply, n_face * n_ply)
59
+
60
+ with open(wfp, 'w') as f:
61
+ f.write(header + '\n')
62
+
63
+ for i in range(n_ply):
64
+ ver = ver_lst[i]
65
+ for j in range(n_vertex):
66
+ x, y, z = ver[:, j]
67
+ if reverse:
68
+ f.write(f'{x:.2f} {height - y:.2f} {z:.2f}\n')
69
+ else:
70
+ f.write(f'{x:.2f} {y:.2f} {z:.2f}\n')
71
+
72
+ for i in range(n_ply):
73
+ offset = i * n_vertex
74
+ for j in range(n_face):
75
+ idx1, idx2, idx3 = tri[j] # m x 3
76
+ if reverse:
77
+ f.write(f'3 {idx3 + offset} {idx2 + offset} {idx1 + offset}\n')
78
+ else:
79
+ f.write(f'3 {idx1 + offset} {idx2 + offset} {idx3 + offset}\n')
80
+
81
+ print(f'Dump tp {wfp}')
82
+
83
+
84
+ def get_colors(img, ver):
85
+ h, w, _ = img.shape
86
+ ver[0, :] = np.minimum(np.maximum(ver[0, :], 0), w - 1) # x
87
+ ver[1, :] = np.minimum(np.maximum(ver[1, :], 0), h - 1) # y
88
+ ind = np.round(ver).astype(np.int32)
89
+ colors = img[ind[1, :], ind[0, :], :] / 255. # n x 3
90
+
91
+ return colors.copy()
92
+
93
+
94
+ def ser_to_obj_single(img, ver_lst, tri, height, wfp):
95
+ suffix = get_suffix(wfp)
96
+
97
+ n_face = tri.shape[0]
98
+ for i, ver in enumerate(ver_lst):
99
+ colors = get_colors(img, ver)
100
+
101
+ n_vertex = ver.shape[1]
102
+
103
+ wfp_new = wfp.replace(suffix, f'_{i + 1}{suffix}')
104
+
105
+ with open(wfp_new, 'w') as f:
106
+ for i in range(n_vertex):
107
+ x, y, z = ver[:, i]
108
+ f.write(
109
+ f'v {x:.2f} {height - y:.2f} {z:.2f} {colors[i, 2]:.2f} {colors[i, 1]:.2f} {colors[i, 0]:.2f}\n')
110
+ for i in range(n_face):
111
+ idx1, idx2, idx3 = tri[i] # m x 3
112
+ f.write(f'f {idx3 + 1} {idx2 + 1} {idx1 + 1}\n')
113
+
114
+ print(f'Dump tp {wfp_new}')
115
+
116
+
117
+ def ser_to_obj_multiple(img, ver_lst, tri, height, wfp):
118
+ n_obj = len(ver_lst) # count obj
119
+
120
+ if n_obj <= 0:
121
+ return
122
+
123
+ n_vertex = ver_lst[0].shape[1]
124
+ n_face = tri.shape[0]
125
+
126
+ with open(wfp, 'w') as f:
127
+ for i in range(n_obj):
128
+ ver = ver_lst[i]
129
+ colors = get_colors(img, ver)
130
+
131
+ for j in range(n_vertex):
132
+ x, y, z = ver[:, j]
133
+ f.write(
134
+ f'v {x:.2f} {height - y:.2f} {z:.2f} {colors[j, 2]:.2f} {colors[j, 1]:.2f} {colors[j, 0]:.2f}\n')
135
+
136
+ for i in range(n_obj):
137
+ offset = i * n_vertex
138
+ for j in range(n_face):
139
+ idx1, idx2, idx3 = tri[j] # m x 3
140
+ f.write(f'f {idx3 + 1 + offset} {idx2 + 1 + offset} {idx1 + 1 + offset}\n')
141
+
142
+ print(f'Dump tp {wfp}')
143
+
144
+
145
+ ser_to_ply = ser_to_ply_multiple # ser_to_ply_single
146
+ ser_to_obj = ser_to_obj_multiple # ser_to_obj_multiple
tddfa/utils/tddfa_util.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ __author__ = 'cleardusk'
4
+
5
+ import sys
6
+
7
+ sys.path.append('..')
8
+
9
+ import argparse
10
+ import numpy as np
11
+ import torch
12
+
13
+
14
+ def _to_ctype(arr):
15
+ if not arr.flags.c_contiguous:
16
+ return arr.copy(order='C')
17
+ return arr
18
+
19
+
20
+ def str2bool(v):
21
+ if v.lower() in ('yes', 'true', 't', 'y', '1'):
22
+ return True
23
+ elif v.lower() in ('no', 'false', 'f', 'n', '0'):
24
+ return False
25
+ else:
26
+ raise argparse.ArgumentTypeError('Boolean value expected')
27
+
28
+
29
+ def load_model(model, checkpoint_fp):
30
+ checkpoint = torch.load(checkpoint_fp, map_location=lambda storage, loc: storage)['state_dict']
31
+ model_dict = model.state_dict()
32
+ # because the model is trained by multiple gpus, prefix module should be removed
33
+ for k in checkpoint.keys():
34
+ kc = k.replace('module.', '')
35
+ if kc in model_dict.keys():
36
+ model_dict[kc] = checkpoint[k]
37
+ if kc in ['fc_param.bias', 'fc_param.weight']:
38
+ model_dict[kc.replace('_param', '')] = checkpoint[k]
39
+
40
+ model.load_state_dict(model_dict)
41
+ return model
42
+
43
+
44
+ class ToTensorGjz(object):
45
+ def __call__(self, pic):
46
+ if isinstance(pic, np.ndarray):
47
+ img = torch.from_numpy(pic.transpose((2, 0, 1)))
48
+ return img.float()
49
+
50
+ def __repr__(self):
51
+ return self.__class__.__name__ + '()'
52
+
53
+
54
+ class NormalizeGjz(object):
55
+ def __init__(self, mean, std):
56
+ self.mean = mean
57
+ self.std = std
58
+
59
+ def __call__(self, tensor):
60
+ tensor.sub_(self.mean).div_(self.std)
61
+ return tensor
62
+
63
+
64
+ def similar_transform(pts3d, roi_box, size):
65
+ pts3d[0, :] -= 1 # for Python compatibility
66
+ pts3d[2, :] -= 1
67
+ pts3d[1, :] = size - pts3d[1, :]
68
+
69
+ sx, sy, ex, ey = roi_box
70
+ scale_x = (ex - sx) / size
71
+ scale_y = (ey - sy) / size
72
+ pts3d[0, :] = pts3d[0, :] * scale_x + sx
73
+ pts3d[1, :] = pts3d[1, :] * scale_y + sy
74
+ s = (scale_x + scale_y) / 2
75
+ pts3d[2, :] *= s
76
+ pts3d[2, :] -= np.min(pts3d[2, :])
77
+ return np.array(pts3d, dtype=np.float32)
78
+
79
+
80
+ def _parse_param(param):
81
+ """matrix pose form
82
+ param: shape=(trans_dim+shape_dim+exp_dim,), i.e., 62 = 12 + 40 + 10
83
+ """
84
+
85
+ # pre-defined templates for parameter
86
+ n = param.shape[0]
87
+ if n == 62:
88
+ trans_dim, shape_dim, exp_dim = 12, 40, 10
89
+ elif n == 72:
90
+ trans_dim, shape_dim, exp_dim = 12, 40, 20
91
+ elif n == 141:
92
+ trans_dim, shape_dim, exp_dim = 12, 100, 29
93
+ else:
94
+ raise Exception(f'Undefined templated param parsing rule')
95
+
96
+ R_ = param[:trans_dim].reshape(3, -1)
97
+ R = R_[:, :3]
98
+ offset = R_[:, -1].reshape(3, 1)
99
+ alpha_shp = param[trans_dim:trans_dim + shape_dim].reshape(-1, 1)
100
+ alpha_exp = param[trans_dim + shape_dim:].reshape(-1, 1)
101
+
102
+ return R, offset, alpha_shp, alpha_exp
tddfa/utils/uv.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding: utf-8
2
+
3
+ __author__ = 'cleardusk'
4
+
5
+ import sys
6
+
7
+ sys.path.append('..')
8
+
9
+ import cv2
10
+ import numpy as np
11
+ import os.path as osp
12
+ import scipy.io as sio
13
+
14
+ from Sim3DR import rasterize
15
+ from utils.functions import plot_image
16
+ from utils.io import _load
17
+ from utils.tddfa_util import _to_ctype
18
+
19
+ make_abs_path = lambda fn: osp.join(osp.dirname(osp.realpath(__file__)), fn)
20
+
21
+
22
+ def load_uv_coords(fp):
23
+ C = sio.loadmat(fp)
24
+ uv_coords = C['UV'].copy(order='C').astype(np.float32)
25
+ return uv_coords
26
+
27
+
28
+ def process_uv(uv_coords, uv_h=256, uv_w=256):
29
+ uv_coords[:, 0] = uv_coords[:, 0] * (uv_w - 1)
30
+ uv_coords[:, 1] = uv_coords[:, 1] * (uv_h - 1)
31
+ uv_coords[:, 1] = uv_h - uv_coords[:, 1] - 1
32
+ uv_coords = np.hstack((uv_coords, np.zeros((uv_coords.shape[0], 1), dtype=np.float32))) # add z
33
+ return uv_coords
34
+
35
+
36
+ g_uv_coords = load_uv_coords(make_abs_path('../configs/BFM_UV.mat'))
37
+ indices = _load(make_abs_path('../configs/indices.npy')) # todo: handle bfm_slim
38
+ g_uv_coords = g_uv_coords[indices, :]
39
+
40
+
41
+ def get_colors(img, ver):
42
+ # nearest-neighbor sampling
43
+ [h, w, _] = img.shape
44
+ ver[0, :] = np.minimum(np.maximum(ver[0, :], 0), w - 1) # x
45
+ ver[1, :] = np.minimum(np.maximum(ver[1, :], 0), h - 1) # y
46
+ ind = np.round(ver).astype(np.int32)
47
+ colors = img[ind[1, :], ind[0, :], :] # n x 3
48
+
49
+ return colors
50
+
51
+
52
+ def bilinear_interpolate(img, x, y):
53
+ """
54
+ https://stackoverflow.com/questions/12729228/simple-efficient-bilinear-interpolation-of-images-in-numpy-and-python
55
+ """
56
+ x0 = np.floor(x).astype(np.int32)
57
+ x1 = x0 + 1
58
+ y0 = np.floor(y).astype(np.int32)
59
+ y1 = y0 + 1
60
+
61
+ x0 = np.clip(x0, 0, img.shape[1] - 1)
62
+ x1 = np.clip(x1, 0, img.shape[1] - 1)
63
+ y0 = np.clip(y0, 0, img.shape[0] - 1)
64
+ y1 = np.clip(y1, 0, img.shape[0] - 1)
65
+
66
+ i_a = img[y0, x0]
67
+ i_b = img[y1, x0]
68
+ i_c = img[y0, x1]
69
+ i_d = img[y1, x1]
70
+
71
+ wa = (x1 - x) * (y1 - y)
72
+ wb = (x1 - x) * (y - y0)
73
+ wc = (x - x0) * (y1 - y)
74
+ wd = (x - x0) * (y - y0)
75
+
76
+ return wa[..., np.newaxis] * i_a + wb[..., np.newaxis] * i_b + wc[..., np.newaxis] * i_c + wd[..., np.newaxis] * i_d
77
+
78
+
79
+ def uv_tex(img, ver_lst, tri, uv_h=256, uv_w=256, uv_c=3, show_flag=False, wfp=None):
80
+ uv_coords = process_uv(g_uv_coords.copy(), uv_h=uv_h, uv_w=uv_w)
81
+
82
+ res_lst = []
83
+ for ver_ in ver_lst:
84
+ ver = _to_ctype(ver_.T) # transpose to m x 3
85
+ colors = bilinear_interpolate(img, ver[:, 0], ver[:, 1]) / 255.
86
+ # `rasterize` here serves as texture sampling, may need to optimization
87
+ res = rasterize(uv_coords, tri, colors, height=uv_h, width=uv_w, channel=uv_c)
88
+ res_lst.append(res)
89
+
90
+ # concat if there more than one image
91
+ res = np.concatenate(res_lst, axis=1) if len(res_lst) > 1 else res_lst[0]
92
+
93
+ if wfp is not None:
94
+ cv2.imwrite(wfp, res)
95
+ print(f'Save visualization result to {wfp}')
96
+
97
+ if show_flag:
98
+ plot_image(res)
99
+
100
+ return res