added depth model
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- app.py +45 -17
- requirements.txt +7 -0
- tddfa/Sim3DR/.gitignore +8 -0
- tddfa/Sim3DR/Sim3DR.py +29 -0
- tddfa/Sim3DR/__init__.py +4 -0
- tddfa/Sim3DR/_init_paths.py +14 -0
- tddfa/Sim3DR/build_sim3dr.sh +1 -0
- tddfa/Sim3DR/lib/rasterize.h +115 -0
- tddfa/Sim3DR/lib/rasterize.pyx +134 -0
- tddfa/Sim3DR/lib/rasterize_kernel.cpp +499 -0
- tddfa/Sim3DR/lighting.py +79 -0
- tddfa/Sim3DR/readme.md +8 -0
- tddfa/Sim3DR/setup.py +19 -0
- tddfa/Sim3DR/tests/.gitignore +1 -0
- tddfa/Sim3DR/tests/CMakeLists.txt +13 -0
- tddfa/Sim3DR/tests/io.cpp +89 -0
- tddfa/Sim3DR/tests/io.h +20 -0
- tddfa/Sim3DR/tests/test.cpp +172 -0
- tddfa/TDDFA.py +143 -0
- tddfa/TDDFA_ONNX.py +118 -0
- tddfa/bfm/.gitignore +1 -0
- tddfa/bfm/__init__.py +1 -0
- tddfa/bfm/bfm.py +40 -0
- tddfa/bfm/bfm_onnx.py +98 -0
- tddfa/bfm/readme.md +23 -0
- tddfa/build.sh +7 -0
- tddfa/configs/.gitignore +3 -0
- tddfa/configs/BFM_UV.mat +0 -0
- tddfa/configs/indices.npy +0 -0
- tddfa/configs/ncc_code.npy +0 -0
- tddfa/configs/readme.md +3 -0
- tddfa/models/__init__.py +3 -0
- tddfa/models/mobilenet_v1.py +163 -0
- tddfa/models/mobilenet_v3.py +246 -0
- tddfa/models/resnet.py +150 -0
- tddfa/utils/__init__.py +0 -0
- tddfa/utils/asset/.gitignore +1 -0
- tddfa/utils/asset/build_render_ctypes.sh +1 -0
- tddfa/utils/asset/render.c +233 -0
- tddfa/utils/depth.py +43 -0
- tddfa/utils/functions.py +182 -0
- tddfa/utils/io.py +64 -0
- tddfa/utils/onnx.py +40 -0
- tddfa/utils/pncc.py +64 -0
- tddfa/utils/pose.py +141 -0
- tddfa/utils/render.py +52 -0
- tddfa/utils/render_ctypes.py +89 -0
- tddfa/utils/serialization.py +146 -0
- tddfa/utils/tddfa_util.py +102 -0
- 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 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
|
|
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 |
-
|
65 |
-
|
66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
cv.FONT_HERSHEY_COMPLEX, 1, color)
|
68 |
-
|
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
|