YoonaAI commited on
Commit
5909968
·
1 Parent(s): 2d1c1ce

Upload 5 files

Browse files
lib/renderer/camera.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
5
+ # holder of all proprietary rights on this computer program.
6
+ # You can only use this computer program if you have closed
7
+ # a license agreement with MPG or you get the right to use the computer
8
+ # program from someone who is authorized to grant you that right.
9
+ # Any use of the computer program without a valid license is prohibited and
10
+ # liable to prosecution.
11
+ #
12
+ # Copyright©2019 Max-Planck-Gesellschaft zur Förderung
13
+ # der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
14
+ # for Intelligent Systems. All rights reserved.
15
+ #
16
+ # Contact: ps-license@tuebingen.mpg.de
17
+
18
+ import cv2
19
+ import numpy as np
20
+
21
+ from .glm import ortho
22
+
23
+
24
+ class Camera:
25
+ def __init__(self, width=1600, height=1200):
26
+ # Focal Length
27
+ # equivalent 50mm
28
+ focal = np.sqrt(width * width + height * height)
29
+ self.focal_x = focal
30
+ self.focal_y = focal
31
+ # Principal Point Offset
32
+ self.principal_x = width / 2
33
+ self.principal_y = height / 2
34
+ # Axis Skew
35
+ self.skew = 0
36
+ # Image Size
37
+ self.width = width
38
+ self.height = height
39
+
40
+ self.near = 1
41
+ self.far = 10
42
+
43
+ # Camera Center
44
+ self.center = np.array([0, 0, 1.6])
45
+ self.direction = np.array([0, 0, -1])
46
+ self.right = np.array([1, 0, 0])
47
+ self.up = np.array([0, 1, 0])
48
+
49
+ self.ortho_ratio = None
50
+
51
+ def sanity_check(self):
52
+ self.center = self.center.reshape([-1])
53
+ self.direction = self.direction.reshape([-1])
54
+ self.right = self.right.reshape([-1])
55
+ self.up = self.up.reshape([-1])
56
+
57
+ assert len(self.center) == 3
58
+ assert len(self.direction) == 3
59
+ assert len(self.right) == 3
60
+ assert len(self.up) == 3
61
+
62
+ @staticmethod
63
+ def normalize_vector(v):
64
+ v_norm = np.linalg.norm(v)
65
+ return v if v_norm == 0 else v / v_norm
66
+
67
+ def get_real_z_value(self, z):
68
+ z_near = self.near
69
+ z_far = self.far
70
+ z_n = 2.0 * z - 1.0
71
+ z_e = 2.0 * z_near * z_far / (z_far + z_near - z_n * (z_far - z_near))
72
+ return z_e
73
+
74
+ def get_rotation_matrix(self):
75
+ rot_mat = np.eye(3)
76
+ s = self.right
77
+ s = self.normalize_vector(s)
78
+ rot_mat[0, :] = s
79
+ u = self.up
80
+ u = self.normalize_vector(u)
81
+ rot_mat[1, :] = -u
82
+ rot_mat[2, :] = self.normalize_vector(self.direction)
83
+
84
+ return rot_mat
85
+
86
+ def get_translation_vector(self):
87
+ rot_mat = self.get_rotation_matrix()
88
+ trans = -np.dot(rot_mat, self.center)
89
+ return trans
90
+
91
+ def get_intrinsic_matrix(self):
92
+ int_mat = np.eye(3)
93
+
94
+ int_mat[0, 0] = self.focal_x
95
+ int_mat[1, 1] = self.focal_y
96
+ int_mat[0, 1] = self.skew
97
+ int_mat[0, 2] = self.principal_x
98
+ int_mat[1, 2] = self.principal_y
99
+
100
+ return int_mat
101
+
102
+ def get_projection_matrix(self):
103
+ ext_mat = self.get_extrinsic_matrix()
104
+ int_mat = self.get_intrinsic_matrix()
105
+
106
+ return np.matmul(int_mat, ext_mat)
107
+
108
+ def get_extrinsic_matrix(self):
109
+ rot_mat = self.get_rotation_matrix()
110
+ int_mat = self.get_intrinsic_matrix()
111
+ trans = self.get_translation_vector()
112
+
113
+ extrinsic = np.eye(4)
114
+ extrinsic[:3, :3] = rot_mat
115
+ extrinsic[:3, 3] = trans
116
+
117
+ return extrinsic[:3, :]
118
+
119
+ def set_rotation_matrix(self, rot_mat):
120
+ self.direction = rot_mat[2, :]
121
+ self.up = -rot_mat[1, :]
122
+ self.right = rot_mat[0, :]
123
+
124
+ def set_intrinsic_matrix(self, int_mat):
125
+ self.focal_x = int_mat[0, 0]
126
+ self.focal_y = int_mat[1, 1]
127
+ self.skew = int_mat[0, 1]
128
+ self.principal_x = int_mat[0, 2]
129
+ self.principal_y = int_mat[1, 2]
130
+
131
+ def set_projection_matrix(self, proj_mat):
132
+ res = cv2.decomposeProjectionMatrix(proj_mat)
133
+ int_mat, rot_mat, camera_center_homo = res[0], res[1], res[2]
134
+ camera_center = camera_center_homo[0:3] / camera_center_homo[3]
135
+ camera_center = camera_center.reshape(-1)
136
+ int_mat = int_mat / int_mat[2][2]
137
+
138
+ self.set_intrinsic_matrix(int_mat)
139
+ self.set_rotation_matrix(rot_mat)
140
+ self.center = camera_center
141
+
142
+ self.sanity_check()
143
+
144
+ def get_gl_matrix(self):
145
+ z_near = self.near
146
+ z_far = self.far
147
+ rot_mat = self.get_rotation_matrix()
148
+ int_mat = self.get_intrinsic_matrix()
149
+ trans = self.get_translation_vector()
150
+
151
+ extrinsic = np.eye(4)
152
+ extrinsic[:3, :3] = rot_mat
153
+ extrinsic[:3, 3] = trans
154
+ axis_adj = np.eye(4)
155
+ axis_adj[2, 2] = -1
156
+ axis_adj[1, 1] = -1
157
+ model_view = np.matmul(axis_adj, extrinsic)
158
+
159
+ projective = np.zeros([4, 4])
160
+ projective[:2, :2] = int_mat[:2, :2]
161
+ projective[:2, 2:3] = -int_mat[:2, 2:3]
162
+ projective[3, 2] = -1
163
+ projective[2, 2] = (z_near + z_far)
164
+ projective[2, 3] = (z_near * z_far)
165
+
166
+ if self.ortho_ratio is None:
167
+ ndc = ortho(0, self.width, 0, self.height, z_near, z_far)
168
+ perspective = np.matmul(ndc, projective)
169
+ else:
170
+ perspective = ortho(-self.width * self.ortho_ratio / 2,
171
+ self.width * self.ortho_ratio / 2,
172
+ -self.height * self.ortho_ratio / 2,
173
+ self.height * self.ortho_ratio / 2, z_near,
174
+ z_far)
175
+
176
+ return perspective, model_view
177
+
178
+
179
+ def KRT_from_P(proj_mat, normalize_K=True):
180
+ res = cv2.decomposeProjectionMatrix(proj_mat)
181
+ K, Rot, camera_center_homog = res[0], res[1], res[2]
182
+ camera_center = camera_center_homog[0:3] / camera_center_homog[3]
183
+ trans = -Rot.dot(camera_center)
184
+ if normalize_K:
185
+ K = K / K[2][2]
186
+ return K, Rot, trans
187
+
188
+
189
+ def MVP_from_P(proj_mat, width, height, near=0.1, far=10000):
190
+ '''
191
+ Convert OpenCV camera calibration matrix to OpenGL projection and model view matrix
192
+ :param proj_mat: OpenCV camera projeciton matrix
193
+ :param width: Image width
194
+ :param height: Image height
195
+ :param near: Z near value
196
+ :param far: Z far value
197
+ :return: OpenGL projection matrix and model view matrix
198
+ '''
199
+ res = cv2.decomposeProjectionMatrix(proj_mat)
200
+ K, Rot, camera_center_homog = res[0], res[1], res[2]
201
+ camera_center = camera_center_homog[0:3] / camera_center_homog[3]
202
+ trans = -Rot.dot(camera_center)
203
+ K = K / K[2][2]
204
+
205
+ extrinsic = np.eye(4)
206
+ extrinsic[:3, :3] = Rot
207
+ extrinsic[:3, 3:4] = trans
208
+ axis_adj = np.eye(4)
209
+ axis_adj[2, 2] = -1
210
+ axis_adj[1, 1] = -1
211
+ model_view = np.matmul(axis_adj, extrinsic)
212
+
213
+ zFar = far
214
+ zNear = near
215
+ projective = np.zeros([4, 4])
216
+ projective[:2, :2] = K[:2, :2]
217
+ projective[:2, 2:3] = -K[:2, 2:3]
218
+ projective[3, 2] = -1
219
+ projective[2, 2] = (zNear + zFar)
220
+ projective[2, 3] = (zNear * zFar)
221
+
222
+ ndc = ortho(0, width, 0, height, zNear, zFar)
223
+
224
+ perspective = np.matmul(ndc, projective)
225
+
226
+ return perspective, model_view
lib/renderer/glm.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
5
+ # holder of all proprietary rights on this computer program.
6
+ # You can only use this computer program if you have closed
7
+ # a license agreement with MPG or you get the right to use the computer
8
+ # program from someone who is authorized to grant you that right.
9
+ # Any use of the computer program without a valid license is prohibited and
10
+ # liable to prosecution.
11
+ #
12
+ # Copyright©2019 Max-Planck-Gesellschaft zur Förderung
13
+ # der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
14
+ # for Intelligent Systems. All rights reserved.
15
+ #
16
+ # Contact: ps-license@tuebingen.mpg.de
17
+
18
+ import numpy as np
19
+
20
+
21
+ def vec3(x, y, z):
22
+ return np.array([x, y, z], dtype=np.float32)
23
+
24
+
25
+ def radians(v):
26
+ return np.radians(v)
27
+
28
+
29
+ def identity():
30
+ return np.identity(4, dtype=np.float32)
31
+
32
+
33
+ def empty():
34
+ return np.zeros([4, 4], dtype=np.float32)
35
+
36
+
37
+ def magnitude(v):
38
+ return np.linalg.norm(v)
39
+
40
+
41
+ def normalize(v):
42
+ m = magnitude(v)
43
+ return v if m == 0 else v / m
44
+
45
+
46
+ def dot(u, v):
47
+ return np.sum(u * v)
48
+
49
+
50
+ def cross(u, v):
51
+ res = vec3(0, 0, 0)
52
+ res[0] = u[1] * v[2] - u[2] * v[1]
53
+ res[1] = u[2] * v[0] - u[0] * v[2]
54
+ res[2] = u[0] * v[1] - u[1] * v[0]
55
+ return res
56
+
57
+
58
+ # below functions can be optimized
59
+
60
+
61
+ def translate(m, v):
62
+ res = np.copy(m)
63
+ res[:, 3] = m[:, 0] * v[0] + m[:, 1] * v[1] + m[:, 2] * v[2] + m[:, 3]
64
+ return res
65
+
66
+
67
+ def rotate(m, angle, v):
68
+ a = angle
69
+ c = np.cos(a)
70
+ s = np.sin(a)
71
+
72
+ axis = normalize(v)
73
+ temp = (1 - c) * axis
74
+
75
+ rot = empty()
76
+ rot[0][0] = c + temp[0] * axis[0]
77
+ rot[0][1] = temp[0] * axis[1] + s * axis[2]
78
+ rot[0][2] = temp[0] * axis[2] - s * axis[1]
79
+
80
+ rot[1][0] = temp[1] * axis[0] - s * axis[2]
81
+ rot[1][1] = c + temp[1] * axis[1]
82
+ rot[1][2] = temp[1] * axis[2] + s * axis[0]
83
+
84
+ rot[2][0] = temp[2] * axis[0] + s * axis[1]
85
+ rot[2][1] = temp[2] * axis[1] - s * axis[0]
86
+ rot[2][2] = c + temp[2] * axis[2]
87
+
88
+ res = empty()
89
+ res[:, 0] = m[:, 0] * rot[0][0] + m[:, 1] * rot[0][1] + m[:, 2] * rot[0][2]
90
+ res[:, 1] = m[:, 0] * rot[1][0] + m[:, 1] * rot[1][1] + m[:, 2] * rot[1][2]
91
+ res[:, 2] = m[:, 0] * rot[2][0] + m[:, 1] * rot[2][1] + m[:, 2] * rot[2][2]
92
+ res[:, 3] = m[:, 3]
93
+ return res
94
+
95
+
96
+ def perspective(fovy, aspect, zNear, zFar):
97
+ tanHalfFovy = np.tan(fovy / 2)
98
+
99
+ res = empty()
100
+ res[0][0] = 1 / (aspect * tanHalfFovy)
101
+ res[1][1] = 1 / (tanHalfFovy)
102
+ res[2][3] = -1
103
+ res[2][2] = -(zFar + zNear) / (zFar - zNear)
104
+ res[3][2] = -(2 * zFar * zNear) / (zFar - zNear)
105
+
106
+ return res.T
107
+
108
+
109
+ def ortho(left, right, bottom, top, zNear, zFar):
110
+ # res = np.ones([4, 4], dtype=np.float32)
111
+ res = identity()
112
+ res[0][0] = 2 / (right - left)
113
+ res[1][1] = 2 / (top - bottom)
114
+ res[2][2] = -2 / (zFar - zNear)
115
+ res[3][0] = -(right + left) / (right - left)
116
+ res[3][1] = -(top + bottom) / (top - bottom)
117
+ res[3][2] = -(zFar + zNear) / (zFar - zNear)
118
+ return res.T
119
+
120
+
121
+ def lookat(eye, center, up):
122
+ f = normalize(center - eye)
123
+ s = normalize(cross(f, up))
124
+ u = cross(s, f)
125
+
126
+ res = identity()
127
+ res[0][0] = s[0]
128
+ res[1][0] = s[1]
129
+ res[2][0] = s[2]
130
+ res[0][1] = u[0]
131
+ res[1][1] = u[1]
132
+ res[2][1] = u[2]
133
+ res[0][2] = -f[0]
134
+ res[1][2] = -f[1]
135
+ res[2][2] = -f[2]
136
+ res[3][0] = -dot(s, eye)
137
+ res[3][1] = -dot(u, eye)
138
+ res[3][2] = -dot(f, eye)
139
+ return res.T
140
+
141
+
142
+ def transform(d, m):
143
+ return np.dot(m, d.T).T
lib/renderer/mesh.py ADDED
@@ -0,0 +1,526 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
5
+ # holder of all proprietary rights on this computer program.
6
+ # You can only use this computer program if you have closed
7
+ # a license agreement with MPG or you get the right to use the computer
8
+ # program from someone who is authorized to grant you that right.
9
+ # Any use of the computer program without a valid license is prohibited and
10
+ # liable to prosecution.
11
+ #
12
+ # Copyright©2019 Max-Planck-Gesellschaft zur Förderung
13
+ # der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
14
+ # for Intelligent Systems. All rights reserved.
15
+ #
16
+ # Contact: ps-license@tuebingen.mpg.de
17
+
18
+ from lib.dataset.mesh_util import SMPLX
19
+ from lib.common.render_utils import face_vertices
20
+ import numpy as np
21
+ import lib.smplx as smplx
22
+ import trimesh
23
+ import torch
24
+ import torch.nn.functional as F
25
+
26
+ model_init_params = dict(
27
+ gender='male',
28
+ model_type='smplx',
29
+ model_path=SMPLX().model_dir,
30
+ create_global_orient=False,
31
+ create_body_pose=False,
32
+ create_betas=False,
33
+ create_left_hand_pose=False,
34
+ create_right_hand_pose=False,
35
+ create_expression=False,
36
+ create_jaw_pose=False,
37
+ create_leye_pose=False,
38
+ create_reye_pose=False,
39
+ create_transl=False,
40
+ num_pca_comps=12)
41
+
42
+
43
+ def get_smpl_model(model_type, gender): return smplx.create(
44
+ **model_init_params)
45
+
46
+
47
+ def normalization(data):
48
+ _range = np.max(data) - np.min(data)
49
+ return ((data - np.min(data)) / _range)
50
+
51
+
52
+ def sigmoid(x):
53
+ z = 1 / (1 + np.exp(-x))
54
+ return z
55
+
56
+
57
+ def load_fit_body(fitted_path, scale, smpl_type='smplx', smpl_gender='neutral', noise_dict=None):
58
+
59
+ param = np.load(fitted_path, allow_pickle=True)
60
+ for key in param.keys():
61
+ param[key] = torch.as_tensor(param[key])
62
+
63
+ smpl_model = get_smpl_model(smpl_type, smpl_gender)
64
+ model_forward_params = dict(betas=param['betas'],
65
+ global_orient=param['global_orient'],
66
+ body_pose=param['body_pose'],
67
+ left_hand_pose=param['left_hand_pose'],
68
+ right_hand_pose=param['right_hand_pose'],
69
+ jaw_pose=param['jaw_pose'],
70
+ leye_pose=param['leye_pose'],
71
+ reye_pose=param['reye_pose'],
72
+ expression=param['expression'],
73
+ return_verts=True)
74
+
75
+ if noise_dict is not None:
76
+ model_forward_params.update(noise_dict)
77
+
78
+ smpl_out = smpl_model(**model_forward_params)
79
+
80
+ smpl_verts = (
81
+ (smpl_out.vertices[0] * param['scale'] + param['translation']) * scale).detach()
82
+ smpl_joints = (
83
+ (smpl_out.joints[0] * param['scale'] + param['translation']) * scale).detach()
84
+ smpl_mesh = trimesh.Trimesh(smpl_verts,
85
+ smpl_model.faces,
86
+ process=False, maintain_order=True)
87
+
88
+ return smpl_mesh, smpl_joints
89
+
90
+
91
+ def load_ori_fit_body(fitted_path, smpl_type='smplx', smpl_gender='neutral'):
92
+
93
+ param = np.load(fitted_path, allow_pickle=True)
94
+ for key in param.keys():
95
+ param[key] = torch.as_tensor(param[key])
96
+
97
+ smpl_model = get_smpl_model(smpl_type, smpl_gender)
98
+ model_forward_params = dict(betas=param['betas'],
99
+ global_orient=param['global_orient'],
100
+ body_pose=param['body_pose'],
101
+ left_hand_pose=param['left_hand_pose'],
102
+ right_hand_pose=param['right_hand_pose'],
103
+ jaw_pose=param['jaw_pose'],
104
+ leye_pose=param['leye_pose'],
105
+ reye_pose=param['reye_pose'],
106
+ expression=param['expression'],
107
+ return_verts=True)
108
+
109
+ smpl_out = smpl_model(**model_forward_params)
110
+
111
+ smpl_verts = smpl_out.vertices[0].detach()
112
+ smpl_mesh = trimesh.Trimesh(smpl_verts,
113
+ smpl_model.faces,
114
+ process=False, maintain_order=True)
115
+
116
+ return smpl_mesh
117
+
118
+
119
+ def save_obj_mesh(mesh_path, verts, faces):
120
+ file = open(mesh_path, 'w')
121
+ for v in verts:
122
+ file.write('v %.4f %.4f %.4f\n' % (v[0], v[1], v[2]))
123
+ for f in faces:
124
+ f_plus = f + 1
125
+ file.write('f %d %d %d\n' % (f_plus[0], f_plus[1], f_plus[2]))
126
+ file.close()
127
+
128
+
129
+ # https://github.com/ratcave/wavefront_reader
130
+ def read_mtlfile(fname):
131
+ materials = {}
132
+ with open(fname) as f:
133
+ lines = f.read().splitlines()
134
+
135
+ for line in lines:
136
+ if line:
137
+ split_line = line.strip().split(' ', 1)
138
+ if len(split_line) < 2:
139
+ continue
140
+
141
+ prefix, data = split_line[0], split_line[1]
142
+ if 'newmtl' in prefix:
143
+ material = {}
144
+ materials[data] = material
145
+ elif materials:
146
+ if data:
147
+ split_data = data.strip().split(' ')
148
+
149
+ # assume texture maps are in the same level
150
+ # WARNING: do not include space in your filename!!
151
+ if 'map' in prefix:
152
+ material[prefix] = split_data[-1].split('\\')[-1]
153
+ elif len(split_data) > 1:
154
+ material[prefix] = tuple(float(d) for d in split_data)
155
+ else:
156
+ try:
157
+ material[prefix] = int(data)
158
+ except ValueError:
159
+ material[prefix] = float(data)
160
+
161
+ return materials
162
+
163
+
164
+ def load_obj_mesh_mtl(mesh_file):
165
+ vertex_data = []
166
+ norm_data = []
167
+ uv_data = []
168
+
169
+ face_data = []
170
+ face_norm_data = []
171
+ face_uv_data = []
172
+
173
+ # face per material
174
+ face_data_mat = {}
175
+ face_norm_data_mat = {}
176
+ face_uv_data_mat = {}
177
+
178
+ # current material name
179
+ mtl_data = None
180
+ cur_mat = None
181
+
182
+ if isinstance(mesh_file, str):
183
+ f = open(mesh_file, "r")
184
+ else:
185
+ f = mesh_file
186
+ for line in f:
187
+ if isinstance(line, bytes):
188
+ line = line.decode("utf-8")
189
+ if line.startswith('#'):
190
+ continue
191
+ values = line.split()
192
+ if not values:
193
+ continue
194
+
195
+ if values[0] == 'v':
196
+ v = list(map(float, values[1:4]))
197
+ vertex_data.append(v)
198
+ elif values[0] == 'vn':
199
+ vn = list(map(float, values[1:4]))
200
+ norm_data.append(vn)
201
+ elif values[0] == 'vt':
202
+ vt = list(map(float, values[1:3]))
203
+ uv_data.append(vt)
204
+ elif values[0] == 'mtllib':
205
+ mtl_data = read_mtlfile(
206
+ mesh_file.replace(mesh_file.split('/')[-1], values[1]))
207
+ elif values[0] == 'usemtl':
208
+ cur_mat = values[1]
209
+ elif values[0] == 'f':
210
+ # local triangle data
211
+ l_face_data = []
212
+ l_face_uv_data = []
213
+ l_face_norm_data = []
214
+
215
+ # quad mesh
216
+ if len(values) > 4:
217
+ f = list(
218
+ map(
219
+ lambda x: int(x.split('/')[0]) if int(x.split('/')[0])
220
+ < 0 else int(x.split('/')[0]) - 1, values[1:4]))
221
+ l_face_data.append(f)
222
+ f = list(
223
+ map(
224
+ lambda x: int(x.split('/')[0])
225
+ if int(x.split('/')[0]) < 0 else int(x.split('/')[0]) -
226
+ 1, [values[3], values[4], values[1]]))
227
+ l_face_data.append(f)
228
+ # tri mesh
229
+ else:
230
+ f = list(
231
+ map(
232
+ lambda x: int(x.split('/')[0]) if int(x.split('/')[0])
233
+ < 0 else int(x.split('/')[0]) - 1, values[1:4]))
234
+ l_face_data.append(f)
235
+ # deal with texture
236
+ if len(values[1].split('/')) >= 2:
237
+ # quad mesh
238
+ if len(values) > 4:
239
+ f = list(
240
+ map(
241
+ lambda x: int(x.split('/')[1])
242
+ if int(x.split('/')[1]) < 0 else int(
243
+ x.split('/')[1]) - 1, values[1:4]))
244
+ l_face_uv_data.append(f)
245
+ f = list(
246
+ map(
247
+ lambda x: int(x.split('/')[1])
248
+ if int(x.split('/')[1]) < 0 else int(
249
+ x.split('/')[1]) - 1,
250
+ [values[3], values[4], values[1]]))
251
+ l_face_uv_data.append(f)
252
+ # tri mesh
253
+ elif len(values[1].split('/')[1]) != 0:
254
+ f = list(
255
+ map(
256
+ lambda x: int(x.split('/')[1])
257
+ if int(x.split('/')[1]) < 0 else int(
258
+ x.split('/')[1]) - 1, values[1:4]))
259
+ l_face_uv_data.append(f)
260
+ # deal with normal
261
+ if len(values[1].split('/')) == 3:
262
+ # quad mesh
263
+ if len(values) > 4:
264
+ f = list(
265
+ map(
266
+ lambda x: int(x.split('/')[2])
267
+ if int(x.split('/')[2]) < 0 else int(
268
+ x.split('/')[2]) - 1, values[1:4]))
269
+ l_face_norm_data.append(f)
270
+ f = list(
271
+ map(
272
+ lambda x: int(x.split('/')[2])
273
+ if int(x.split('/')[2]) < 0 else int(
274
+ x.split('/')[2]) - 1,
275
+ [values[3], values[4], values[1]]))
276
+ l_face_norm_data.append(f)
277
+ # tri mesh
278
+ elif len(values[1].split('/')[2]) != 0:
279
+ f = list(
280
+ map(
281
+ lambda x: int(x.split('/')[2])
282
+ if int(x.split('/')[2]) < 0 else int(
283
+ x.split('/')[2]) - 1, values[1:4]))
284
+ l_face_norm_data.append(f)
285
+
286
+ face_data += l_face_data
287
+ face_uv_data += l_face_uv_data
288
+ face_norm_data += l_face_norm_data
289
+
290
+ if cur_mat is not None:
291
+ if cur_mat not in face_data_mat.keys():
292
+ face_data_mat[cur_mat] = []
293
+ if cur_mat not in face_uv_data_mat.keys():
294
+ face_uv_data_mat[cur_mat] = []
295
+ if cur_mat not in face_norm_data_mat.keys():
296
+ face_norm_data_mat[cur_mat] = []
297
+ face_data_mat[cur_mat] += l_face_data
298
+ face_uv_data_mat[cur_mat] += l_face_uv_data
299
+ face_norm_data_mat[cur_mat] += l_face_norm_data
300
+
301
+ vertices = np.array(vertex_data)
302
+ faces = np.array(face_data)
303
+
304
+ norms = np.array(norm_data)
305
+ norms = normalize_v3(norms)
306
+ face_normals = np.array(face_norm_data)
307
+
308
+ uvs = np.array(uv_data)
309
+ face_uvs = np.array(face_uv_data)
310
+
311
+ out_tuple = (vertices, faces, norms, face_normals, uvs, face_uvs)
312
+
313
+ if cur_mat is not None and mtl_data is not None:
314
+ for key in face_data_mat:
315
+ face_data_mat[key] = np.array(face_data_mat[key])
316
+ face_uv_data_mat[key] = np.array(face_uv_data_mat[key])
317
+ face_norm_data_mat[key] = np.array(face_norm_data_mat[key])
318
+
319
+ out_tuple += (face_data_mat, face_norm_data_mat, face_uv_data_mat,
320
+ mtl_data)
321
+
322
+ return out_tuple
323
+
324
+
325
+ def load_scan(mesh_file, with_normal=False, with_texture=False):
326
+ vertex_data = []
327
+ norm_data = []
328
+ uv_data = []
329
+
330
+ face_data = []
331
+ face_norm_data = []
332
+ face_uv_data = []
333
+
334
+ if isinstance(mesh_file, str):
335
+ f = open(mesh_file, "r")
336
+ else:
337
+ f = mesh_file
338
+ for line in f:
339
+ if isinstance(line, bytes):
340
+ line = line.decode("utf-8")
341
+ if line.startswith('#'):
342
+ continue
343
+ values = line.split()
344
+ if not values:
345
+ continue
346
+
347
+ if values[0] == 'v':
348
+ v = list(map(float, values[1:4]))
349
+ vertex_data.append(v)
350
+ elif values[0] == 'vn':
351
+ vn = list(map(float, values[1:4]))
352
+ norm_data.append(vn)
353
+ elif values[0] == 'vt':
354
+ vt = list(map(float, values[1:3]))
355
+ uv_data.append(vt)
356
+
357
+ elif values[0] == 'f':
358
+ # quad mesh
359
+ if len(values) > 4:
360
+ f = list(map(lambda x: int(x.split('/')[0]), values[1:4]))
361
+ face_data.append(f)
362
+ f = list(
363
+ map(lambda x: int(x.split('/')[0]),
364
+ [values[3], values[4], values[1]]))
365
+ face_data.append(f)
366
+ # tri mesh
367
+ else:
368
+ f = list(map(lambda x: int(x.split('/')[0]), values[1:4]))
369
+ face_data.append(f)
370
+
371
+ # deal with texture
372
+ if len(values[1].split('/')) >= 2:
373
+ # quad mesh
374
+ if len(values) > 4:
375
+ f = list(map(lambda x: int(x.split('/')[1]), values[1:4]))
376
+ face_uv_data.append(f)
377
+ f = list(
378
+ map(lambda x: int(x.split('/')[1]),
379
+ [values[3], values[4], values[1]]))
380
+ face_uv_data.append(f)
381
+ # tri mesh
382
+ elif len(values[1].split('/')[1]) != 0:
383
+ f = list(map(lambda x: int(x.split('/')[1]), values[1:4]))
384
+ face_uv_data.append(f)
385
+ # deal with normal
386
+ if len(values[1].split('/')) == 3:
387
+ # quad mesh
388
+ if len(values) > 4:
389
+ f = list(map(lambda x: int(x.split('/')[2]), values[1:4]))
390
+ face_norm_data.append(f)
391
+ f = list(
392
+ map(lambda x: int(x.split('/')[2]),
393
+ [values[3], values[4], values[1]]))
394
+ face_norm_data.append(f)
395
+ # tri mesh
396
+ elif len(values[1].split('/')[2]) != 0:
397
+ f = list(map(lambda x: int(x.split('/')[2]), values[1:4]))
398
+ face_norm_data.append(f)
399
+
400
+ vertices = np.array(vertex_data)
401
+ faces = np.array(face_data) - 1
402
+
403
+ if with_texture and with_normal:
404
+ uvs = np.array(uv_data)
405
+ face_uvs = np.array(face_uv_data) - 1
406
+ norms = np.array(norm_data)
407
+ if norms.shape[0] == 0:
408
+ norms = compute_normal(vertices, faces)
409
+ face_normals = faces
410
+ else:
411
+ norms = normalize_v3(norms)
412
+ face_normals = np.array(face_norm_data) - 1
413
+ return vertices, faces, norms, face_normals, uvs, face_uvs
414
+
415
+ if with_texture:
416
+ uvs = np.array(uv_data)
417
+ face_uvs = np.array(face_uv_data) - 1
418
+ return vertices, faces, uvs, face_uvs
419
+
420
+ if with_normal:
421
+ norms = np.array(norm_data)
422
+ norms = normalize_v3(norms)
423
+ face_normals = np.array(face_norm_data) - 1
424
+ return vertices, faces, norms, face_normals
425
+
426
+ return vertices, faces
427
+
428
+
429
+ def normalize_v3(arr):
430
+ ''' Normalize a numpy array of 3 component vectors shape=(n,3) '''
431
+ lens = np.sqrt(arr[:, 0]**2 + arr[:, 1]**2 + arr[:, 2]**2)
432
+ eps = 0.00000001
433
+ lens[lens < eps] = eps
434
+ arr[:, 0] /= lens
435
+ arr[:, 1] /= lens
436
+ arr[:, 2] /= lens
437
+ return arr
438
+
439
+
440
+ def compute_normal(vertices, faces):
441
+ # Create a zeroed array with the same type and shape as our vertices i.e., per vertex normal
442
+ norm = np.zeros(vertices.shape, dtype=vertices.dtype)
443
+ # Create an indexed view into the vertex array using the array of three indices for triangles
444
+ tris = vertices[faces]
445
+ # Calculate the normal for all the triangles, by taking the cross product of the vectors v1-v0, and v2-v0 in each triangle
446
+ n = np.cross(tris[::, 1] - tris[::, 0], tris[::, 2] - tris[::, 0])
447
+ # n is now an array of normals per triangle. The length of each normal is dependent the vertices,
448
+ # we need to normalize these, so that our next step weights each normal equally.
449
+ normalize_v3(n)
450
+ # now we have a normalized array of normals, one per triangle, i.e., per triangle normals.
451
+ # But instead of one per triangle (i.e., flat shading), we add to each vertex in that triangle,
452
+ # the triangles' normal. Multiple triangles would then contribute to every vertex, so we need to normalize again afterwards.
453
+ # The cool part, we can actually add the normals through an indexed view of our (zeroed) per vertex normal array
454
+ norm[faces[:, 0]] += n
455
+ norm[faces[:, 1]] += n
456
+ norm[faces[:, 2]] += n
457
+ normalize_v3(norm)
458
+
459
+ return norm
460
+
461
+
462
+ def compute_normal_batch(vertices, faces):
463
+
464
+ bs, nv = vertices.shape[:2]
465
+ bs, nf = faces.shape[:2]
466
+
467
+ vert_norm = torch.zeros(bs * nv, 3).type_as(vertices)
468
+ tris = face_vertices(vertices, faces)
469
+ face_norm = F.normalize(torch.cross(tris[:, :, 1] - tris[:, :, 0],
470
+ tris[:, :, 2] - tris[:, :, 0]),
471
+ dim=-1)
472
+
473
+ faces = (faces +
474
+ (torch.arange(bs).type_as(faces) * nv)[:, None, None]).view(
475
+ -1, 3)
476
+
477
+ vert_norm[faces[:, 0]] += face_norm.view(-1, 3)
478
+ vert_norm[faces[:, 1]] += face_norm.view(-1, 3)
479
+ vert_norm[faces[:, 2]] += face_norm.view(-1, 3)
480
+
481
+ vert_norm = F.normalize(vert_norm, dim=-1).view(bs, nv, 3)
482
+
483
+ return vert_norm
484
+
485
+
486
+ # compute tangent and bitangent
487
+ def compute_tangent(vertices, faces, normals, uvs, faceuvs):
488
+ # NOTE: this could be numerically unstable around [0,0,1]
489
+ # but other current solutions are pretty freaky somehow
490
+ c1 = np.cross(normals, np.array([0, 1, 0.0]))
491
+ tan = c1
492
+ normalize_v3(tan)
493
+ btan = np.cross(normals, tan)
494
+
495
+ # NOTE: traditional version is below
496
+
497
+ # pts_tris = vertices[faces]
498
+ # uv_tris = uvs[faceuvs]
499
+
500
+ # W = np.stack([pts_tris[::, 1] - pts_tris[::, 0], pts_tris[::, 2] - pts_tris[::, 0]],2)
501
+ # UV = np.stack([uv_tris[::, 1] - uv_tris[::, 0], uv_tris[::, 2] - uv_tris[::, 0]], 1)
502
+
503
+ # for i in range(W.shape[0]):
504
+ # W[i,::] = W[i,::].dot(np.linalg.inv(UV[i,::]))
505
+
506
+ # tan = np.zeros(vertices.shape, dtype=vertices.dtype)
507
+ # tan[faces[:,0]] += W[:,:,0]
508
+ # tan[faces[:,1]] += W[:,:,0]
509
+ # tan[faces[:,2]] += W[:,:,0]
510
+
511
+ # btan = np.zeros(vertices.shape, dtype=vertices.dtype)
512
+ # btan[faces[:,0]] += W[:,:,1]
513
+ # btan[faces[:,1]] += W[:,:,1]
514
+ # btan[faces[:,2]] += W[:,:,1]
515
+
516
+ # normalize_v3(tan)
517
+
518
+ # ndott = np.sum(normals*tan, 1, keepdims=True)
519
+ # tan = tan - ndott * normals
520
+
521
+ # normalize_v3(btan)
522
+ # normalize_v3(tan)
523
+
524
+ # tan[np.sum(np.cross(normals, tan) * btan, 1) < 0,:] *= -1.0
525
+
526
+ return tan, btan
lib/renderer/opengl_util.py ADDED
@@ -0,0 +1,369 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
5
+ # holder of all proprietary rights on this computer program.
6
+ # You can only use this computer program if you have closed
7
+ # a license agreement with MPG or you get the right to use the computer
8
+ # program from someone who is authorized to grant you that right.
9
+ # Any use of the computer program without a valid license is prohibited and
10
+ # liable to prosecution.
11
+ #
12
+ # Copyright©2019 Max-Planck-Gesellschaft zur Förderung
13
+ # der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
14
+ # for Intelligent Systems. All rights reserved.
15
+ #
16
+ # Contact: ps-license@tuebingen.mpg.de
17
+
18
+ import os
19
+
20
+ from lib.renderer.mesh import load_scan, compute_tangent
21
+ from lib.renderer.camera import Camera
22
+ import cv2
23
+ import math
24
+ import random
25
+ import numpy as np
26
+
27
+
28
+ def render_result(rndr, shader_id, path, mask=False):
29
+
30
+ cam_render = rndr.get_color(shader_id)
31
+ cam_render = cv2.cvtColor(cam_render, cv2.COLOR_RGBA2BGRA)
32
+
33
+ os.makedirs(os.path.dirname(path), exist_ok=True)
34
+ if shader_id != 2:
35
+ cv2.imwrite(path, np.uint8(255.0 * cam_render))
36
+ else:
37
+ cam_render[:, :, -1] -= 0.5
38
+ cam_render[:, :, -1] *= 2.0
39
+ if not mask:
40
+ cv2.imwrite(path, np.uint8(255.0 / 2.0 * (cam_render + 1.0)))
41
+ else:
42
+ cv2.imwrite(path, np.uint8(-1.0 * cam_render[:, :, [3]]))
43
+
44
+
45
+ def make_rotate(rx, ry, rz):
46
+ sinX = np.sin(rx)
47
+ sinY = np.sin(ry)
48
+ sinZ = np.sin(rz)
49
+
50
+ cosX = np.cos(rx)
51
+ cosY = np.cos(ry)
52
+ cosZ = np.cos(rz)
53
+
54
+ Rx = np.zeros((3, 3))
55
+ Rx[0, 0] = 1.0
56
+ Rx[1, 1] = cosX
57
+ Rx[1, 2] = -sinX
58
+ Rx[2, 1] = sinX
59
+ Rx[2, 2] = cosX
60
+
61
+ Ry = np.zeros((3, 3))
62
+ Ry[0, 0] = cosY
63
+ Ry[0, 2] = sinY
64
+ Ry[1, 1] = 1.0
65
+ Ry[2, 0] = -sinY
66
+ Ry[2, 2] = cosY
67
+
68
+ Rz = np.zeros((3, 3))
69
+ Rz[0, 0] = cosZ
70
+ Rz[0, 1] = -sinZ
71
+ Rz[1, 0] = sinZ
72
+ Rz[1, 1] = cosZ
73
+ Rz[2, 2] = 1.0
74
+
75
+ R = np.matmul(np.matmul(Rz, Ry), Rx)
76
+ return R
77
+
78
+
79
+ def rotateSH(SH, R):
80
+ SHn = SH
81
+
82
+ # 1st order
83
+ SHn[1] = R[1, 1] * SH[1] - R[1, 2] * SH[2] + R[1, 0] * SH[3]
84
+ SHn[2] = -R[2, 1] * SH[1] + R[2, 2] * SH[2] - R[2, 0] * SH[3]
85
+ SHn[3] = R[0, 1] * SH[1] - R[0, 2] * SH[2] + R[0, 0] * SH[3]
86
+
87
+ # 2nd order
88
+ SHn[4:, 0] = rotateBand2(SH[4:, 0], R)
89
+ SHn[4:, 1] = rotateBand2(SH[4:, 1], R)
90
+ SHn[4:, 2] = rotateBand2(SH[4:, 2], R)
91
+
92
+ return SHn
93
+
94
+
95
+ def rotateBand2(x, R):
96
+ s_c3 = 0.94617469575
97
+ s_c4 = -0.31539156525
98
+ s_c5 = 0.54627421529
99
+
100
+ s_c_scale = 1.0 / 0.91529123286551084
101
+ s_c_scale_inv = 0.91529123286551084
102
+
103
+ s_rc2 = 1.5853309190550713 * s_c_scale
104
+ s_c4_div_c3 = s_c4 / s_c3
105
+ s_c4_div_c3_x2 = (s_c4 / s_c3) * 2.0
106
+
107
+ s_scale_dst2 = s_c3 * s_c_scale_inv
108
+ s_scale_dst4 = s_c5 * s_c_scale_inv
109
+
110
+ sh0 = x[3] + x[4] + x[4] - x[1]
111
+ sh1 = x[0] + s_rc2 * x[2] + x[3] + x[4]
112
+ sh2 = x[0]
113
+ sh3 = -x[3]
114
+ sh4 = -x[1]
115
+
116
+ r2x = R[0][0] + R[0][1]
117
+ r2y = R[1][0] + R[1][1]
118
+ r2z = R[2][0] + R[2][1]
119
+
120
+ r3x = R[0][0] + R[0][2]
121
+ r3y = R[1][0] + R[1][2]
122
+ r3z = R[2][0] + R[2][2]
123
+
124
+ r4x = R[0][1] + R[0][2]
125
+ r4y = R[1][1] + R[1][2]
126
+ r4z = R[2][1] + R[2][2]
127
+
128
+ sh0_x = sh0 * R[0][0]
129
+ sh0_y = sh0 * R[1][0]
130
+ d0 = sh0_x * R[1][0]
131
+ d1 = sh0_y * R[2][0]
132
+ d2 = sh0 * (R[2][0] * R[2][0] + s_c4_div_c3)
133
+ d3 = sh0_x * R[2][0]
134
+ d4 = sh0_x * R[0][0] - sh0_y * R[1][0]
135
+
136
+ sh1_x = sh1 * R[0][2]
137
+ sh1_y = sh1 * R[1][2]
138
+ d0 += sh1_x * R[1][2]
139
+ d1 += sh1_y * R[2][2]
140
+ d2 += sh1 * (R[2][2] * R[2][2] + s_c4_div_c3)
141
+ d3 += sh1_x * R[2][2]
142
+ d4 += sh1_x * R[0][2] - sh1_y * R[1][2]
143
+
144
+ sh2_x = sh2 * r2x
145
+ sh2_y = sh2 * r2y
146
+ d0 += sh2_x * r2y
147
+ d1 += sh2_y * r2z
148
+ d2 += sh2 * (r2z * r2z + s_c4_div_c3_x2)
149
+ d3 += sh2_x * r2z
150
+ d4 += sh2_x * r2x - sh2_y * r2y
151
+
152
+ sh3_x = sh3 * r3x
153
+ sh3_y = sh3 * r3y
154
+ d0 += sh3_x * r3y
155
+ d1 += sh3_y * r3z
156
+ d2 += sh3 * (r3z * r3z + s_c4_div_c3_x2)
157
+ d3 += sh3_x * r3z
158
+ d4 += sh3_x * r3x - sh3_y * r3y
159
+
160
+ sh4_x = sh4 * r4x
161
+ sh4_y = sh4 * r4y
162
+ d0 += sh4_x * r4y
163
+ d1 += sh4_y * r4z
164
+ d2 += sh4 * (r4z * r4z + s_c4_div_c3_x2)
165
+ d3 += sh4_x * r4z
166
+ d4 += sh4_x * r4x - sh4_y * r4y
167
+
168
+ dst = x
169
+ dst[0] = d0
170
+ dst[1] = -d1
171
+ dst[2] = d2 * s_scale_dst2
172
+ dst[3] = -d3
173
+ dst[4] = d4 * s_scale_dst4
174
+
175
+ return dst
176
+
177
+
178
+ def load_calib(param, render_size=512):
179
+ # pixel unit / world unit
180
+ ortho_ratio = param['ortho_ratio']
181
+ # world unit / model unit
182
+ scale = param['scale']
183
+ # camera center world coordinate
184
+ center = param['center']
185
+ # model rotation
186
+ R = param['R']
187
+
188
+ translate = -np.matmul(R, center).reshape(3, 1)
189
+ extrinsic = np.concatenate([R, translate], axis=1)
190
+ extrinsic = np.concatenate(
191
+ [extrinsic, np.array([0, 0, 0, 1]).reshape(1, 4)], 0)
192
+ # Match camera space to image pixel space
193
+ scale_intrinsic = np.identity(4)
194
+ scale_intrinsic[0, 0] = scale / ortho_ratio
195
+ scale_intrinsic[1, 1] = -scale / ortho_ratio
196
+ scale_intrinsic[2, 2] = scale / ortho_ratio
197
+ # Match image pixel space to image uv space
198
+ uv_intrinsic = np.identity(4)
199
+ uv_intrinsic[0, 0] = 1.0 / float(render_size // 2)
200
+ uv_intrinsic[1, 1] = 1.0 / float(render_size // 2)
201
+ uv_intrinsic[2, 2] = 1.0 / float(render_size // 2)
202
+
203
+ intrinsic = np.matmul(uv_intrinsic, scale_intrinsic)
204
+ calib = np.concatenate([extrinsic, intrinsic], axis=0)
205
+ return calib
206
+
207
+
208
+ def render_prt_ortho(out_path,
209
+ folder_name,
210
+ subject_name,
211
+ shs,
212
+ rndr,
213
+ rndr_uv,
214
+ im_size,
215
+ angl_step=4,
216
+ n_light=1,
217
+ pitch=[0]):
218
+ cam = Camera(width=im_size, height=im_size)
219
+ cam.ortho_ratio = 0.4 * (512 / im_size)
220
+ cam.near = -100
221
+ cam.far = 100
222
+ cam.sanity_check()
223
+
224
+ # set path for obj, prt
225
+ mesh_file = os.path.join(folder_name, subject_name + '_100k.obj')
226
+ if not os.path.exists(mesh_file):
227
+ print('ERROR: obj file does not exist!!', mesh_file)
228
+ return
229
+ prt_file = os.path.join(folder_name, 'bounce', 'bounce0.txt')
230
+ if not os.path.exists(prt_file):
231
+ print('ERROR: prt file does not exist!!!', prt_file)
232
+ return
233
+ face_prt_file = os.path.join(folder_name, 'bounce', 'face.npy')
234
+ if not os.path.exists(face_prt_file):
235
+ print('ERROR: face prt file does not exist!!!', prt_file)
236
+ return
237
+ text_file = os.path.join(folder_name, 'tex', subject_name + '_dif_2k.jpg')
238
+ if not os.path.exists(text_file):
239
+ print('ERROR: dif file does not exist!!', text_file)
240
+ return
241
+
242
+ texture_image = cv2.imread(text_file)
243
+ texture_image = cv2.cvtColor(texture_image, cv2.COLOR_BGR2RGB)
244
+
245
+ vertices, faces, normals, faces_normals, textures, face_textures = load_scan(
246
+ mesh_file, with_normal=True, with_texture=True)
247
+ vmin = vertices.min(0)
248
+ vmax = vertices.max(0)
249
+ up_axis = 1 if (vmax - vmin).argmax() == 1 else 2
250
+
251
+ vmed = np.median(vertices, 0)
252
+ vmed[up_axis] = 0.5 * (vmax[up_axis] + vmin[up_axis])
253
+ y_scale = 180 / (vmax[up_axis] - vmin[up_axis])
254
+
255
+ rndr.set_norm_mat(y_scale, vmed)
256
+ rndr_uv.set_norm_mat(y_scale, vmed)
257
+
258
+ tan, bitan = compute_tangent(vertices, faces, normals, textures,
259
+ face_textures)
260
+ prt = np.loadtxt(prt_file)
261
+ face_prt = np.load(face_prt_file)
262
+ rndr.set_mesh(vertices, faces, normals, faces_normals, textures,
263
+ face_textures, prt, face_prt, tan, bitan)
264
+ rndr.set_albedo(texture_image)
265
+
266
+ rndr_uv.set_mesh(vertices, faces, normals, faces_normals, textures,
267
+ face_textures, prt, face_prt, tan, bitan)
268
+ rndr_uv.set_albedo(texture_image)
269
+
270
+ os.makedirs(os.path.join(out_path, 'GEO', 'OBJ', subject_name),
271
+ exist_ok=True)
272
+ os.makedirs(os.path.join(out_path, 'PARAM', subject_name), exist_ok=True)
273
+ os.makedirs(os.path.join(out_path, 'RENDER', subject_name), exist_ok=True)
274
+ os.makedirs(os.path.join(out_path, 'MASK', subject_name), exist_ok=True)
275
+ os.makedirs(os.path.join(out_path, 'UV_RENDER', subject_name),
276
+ exist_ok=True)
277
+ os.makedirs(os.path.join(out_path, 'UV_MASK', subject_name), exist_ok=True)
278
+ os.makedirs(os.path.join(out_path, 'UV_POS', subject_name), exist_ok=True)
279
+ os.makedirs(os.path.join(out_path, 'UV_NORMAL', subject_name),
280
+ exist_ok=True)
281
+
282
+ if not os.path.exists(os.path.join(out_path, 'val.txt')):
283
+ f = open(os.path.join(out_path, 'val.txt'), 'w')
284
+ f.close()
285
+
286
+ # copy obj file
287
+ cmd = 'cp %s %s' % (mesh_file,
288
+ os.path.join(out_path, 'GEO', 'OBJ', subject_name))
289
+ print(cmd)
290
+ os.system(cmd)
291
+
292
+ for p in pitch:
293
+ for y in tqdm(range(0, 360, angl_step)):
294
+ R = np.matmul(make_rotate(math.radians(p), 0, 0),
295
+ make_rotate(0, math.radians(y), 0))
296
+ if up_axis == 2:
297
+ R = np.matmul(R, make_rotate(math.radians(90), 0, 0))
298
+
299
+ rndr.rot_matrix = R
300
+ rndr_uv.rot_matrix = R
301
+ rndr.set_camera(cam)
302
+ rndr_uv.set_camera(cam)
303
+
304
+ for j in range(n_light):
305
+ sh_id = random.randint(0, shs.shape[0] - 1)
306
+ sh = shs[sh_id]
307
+ sh_angle = 0.2 * np.pi * (random.random() - 0.5)
308
+ sh = rotateSH(sh, make_rotate(0, sh_angle, 0).T)
309
+
310
+ dic = {
311
+ 'sh': sh,
312
+ 'ortho_ratio': cam.ortho_ratio,
313
+ 'scale': y_scale,
314
+ 'center': vmed,
315
+ 'R': R
316
+ }
317
+
318
+ rndr.set_sh(sh)
319
+ rndr.analytic = False
320
+ rndr.use_inverse_depth = False
321
+ rndr.display()
322
+
323
+ out_all_f = rndr.get_color(0)
324
+ out_mask = out_all_f[:, :, 3]
325
+ out_all_f = cv2.cvtColor(out_all_f, cv2.COLOR_RGBA2BGR)
326
+
327
+ np.save(
328
+ os.path.join(out_path, 'PARAM', subject_name,
329
+ '%d_%d_%02d.npy' % (y, p, j)), dic)
330
+ cv2.imwrite(
331
+ os.path.join(out_path, 'RENDER', subject_name,
332
+ '%d_%d_%02d.jpg' % (y, p, j)),
333
+ 255.0 * out_all_f)
334
+ cv2.imwrite(
335
+ os.path.join(out_path, 'MASK', subject_name,
336
+ '%d_%d_%02d.png' % (y, p, j)),
337
+ 255.0 * out_mask)
338
+
339
+ rndr_uv.set_sh(sh)
340
+ rndr_uv.analytic = False
341
+ rndr_uv.use_inverse_depth = False
342
+ rndr_uv.display()
343
+
344
+ uv_color = rndr_uv.get_color(0)
345
+ uv_color = cv2.cvtColor(uv_color, cv2.COLOR_RGBA2BGR)
346
+ cv2.imwrite(
347
+ os.path.join(out_path, 'UV_RENDER', subject_name,
348
+ '%d_%d_%02d.jpg' % (y, p, j)),
349
+ 255.0 * uv_color)
350
+
351
+ if y == 0 and j == 0 and p == pitch[0]:
352
+ uv_pos = rndr_uv.get_color(1)
353
+ uv_mask = uv_pos[:, :, 3]
354
+ cv2.imwrite(
355
+ os.path.join(out_path, 'UV_MASK', subject_name,
356
+ '00.png'), 255.0 * uv_mask)
357
+
358
+ data = {
359
+ 'default': uv_pos[:, :, :3]
360
+ } # default is a reserved name
361
+ pyexr.write(
362
+ os.path.join(out_path, 'UV_POS', subject_name,
363
+ '00.exr'), data)
364
+
365
+ uv_nml = rndr_uv.get_color(2)
366
+ uv_nml = cv2.cvtColor(uv_nml, cv2.COLOR_RGBA2BGR)
367
+ cv2.imwrite(
368
+ os.path.join(out_path, 'UV_NORMAL', subject_name,
369
+ '00.png'), 255.0 * uv_nml)
lib/renderer/prt_util.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
5
+ # holder of all proprietary rights on this computer program.
6
+ # You can only use this computer program if you have closed
7
+ # a license agreement with MPG or you get the right to use the computer
8
+ # program from someone who is authorized to grant you that right.
9
+ # Any use of the computer program without a valid license is prohibited and
10
+ # liable to prosecution.
11
+ #
12
+ # Copyright©2019 Max-Planck-Gesellschaft zur Förderung
13
+ # der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
14
+ # for Intelligent Systems. All rights reserved.
15
+ #
16
+ # Contact: ps-license@tuebingen.mpg.de
17
+
18
+ import os
19
+ import trimesh
20
+ import numpy as np
21
+ import math
22
+ from scipy.special import sph_harm
23
+ import argparse
24
+ from tqdm import tqdm
25
+ from trimesh.util import bounds_tree
26
+
27
+
28
+ def factratio(N, D):
29
+ if N >= D:
30
+ prod = 1.0
31
+ for i in range(D + 1, N + 1):
32
+ prod *= i
33
+ return prod
34
+ else:
35
+ prod = 1.0
36
+ for i in range(N + 1, D + 1):
37
+ prod *= i
38
+ return 1.0 / prod
39
+
40
+
41
+ def KVal(M, L):
42
+ return math.sqrt(((2 * L + 1) / (4 * math.pi)) * (factratio(L - M, L + M)))
43
+
44
+
45
+ def AssociatedLegendre(M, L, x):
46
+ if M < 0 or M > L or np.max(np.abs(x)) > 1.0:
47
+ return np.zeros_like(x)
48
+
49
+ pmm = np.ones_like(x)
50
+ if M > 0:
51
+ somx2 = np.sqrt((1.0 + x) * (1.0 - x))
52
+ fact = 1.0
53
+ for i in range(1, M + 1):
54
+ pmm = -pmm * fact * somx2
55
+ fact = fact + 2
56
+
57
+ if L == M:
58
+ return pmm
59
+ else:
60
+ pmmp1 = x * (2 * M + 1) * pmm
61
+ if L == M + 1:
62
+ return pmmp1
63
+ else:
64
+ pll = np.zeros_like(x)
65
+ for i in range(M + 2, L + 1):
66
+ pll = (x * (2 * i - 1) * pmmp1 - (i + M - 1) * pmm) / (i - M)
67
+ pmm = pmmp1
68
+ pmmp1 = pll
69
+ return pll
70
+
71
+
72
+ def SphericalHarmonic(M, L, theta, phi):
73
+ if M > 0:
74
+ return math.sqrt(2.0) * KVal(M, L) * np.cos(
75
+ M * phi) * AssociatedLegendre(M, L, np.cos(theta))
76
+ elif M < 0:
77
+ return math.sqrt(2.0) * KVal(-M, L) * np.sin(
78
+ -M * phi) * AssociatedLegendre(-M, L, np.cos(theta))
79
+ else:
80
+ return KVal(0, L) * AssociatedLegendre(0, L, np.cos(theta))
81
+
82
+
83
+ def save_obj(mesh_path, verts):
84
+ file = open(mesh_path, 'w')
85
+ for v in verts:
86
+ file.write('v %.4f %.4f %.4f\n' % (v[0], v[1], v[2]))
87
+ file.close()
88
+
89
+
90
+ def sampleSphericalDirections(n):
91
+ xv = np.random.rand(n, n)
92
+ yv = np.random.rand(n, n)
93
+ theta = np.arccos(1 - 2 * xv)
94
+ phi = 2.0 * math.pi * yv
95
+
96
+ phi = phi.reshape(-1)
97
+ theta = theta.reshape(-1)
98
+
99
+ vx = -np.sin(theta) * np.cos(phi)
100
+ vy = -np.sin(theta) * np.sin(phi)
101
+ vz = np.cos(theta)
102
+ return np.stack([vx, vy, vz], 1), phi, theta
103
+
104
+
105
+ def getSHCoeffs(order, phi, theta):
106
+ shs = []
107
+ for n in range(0, order + 1):
108
+ for m in range(-n, n + 1):
109
+ s = SphericalHarmonic(m, n, theta, phi)
110
+ shs.append(s)
111
+
112
+ return np.stack(shs, 1)
113
+
114
+
115
+ def computePRT(mesh_path, scale, n, order):
116
+
117
+ prt_dir = os.path.join(os.path.dirname(mesh_path), "prt")
118
+ bounce_path = os.path.join(prt_dir, "bounce.npy")
119
+ face_path = os.path.join(prt_dir, "face.npy")
120
+
121
+ os.makedirs(prt_dir, exist_ok=True)
122
+
123
+ PRT = None
124
+ F = None
125
+
126
+ if os.path.exists(bounce_path) and os.path.exists(face_path):
127
+
128
+ PRT = np.load(bounce_path)
129
+ F = np.load(face_path)
130
+
131
+ else:
132
+
133
+ mesh = trimesh.load(mesh_path,
134
+ skip_materials=True,
135
+ process=False,
136
+ maintain_order=True)
137
+ mesh.vertices *= scale
138
+
139
+ vectors_orig, phi, theta = sampleSphericalDirections(n)
140
+ SH_orig = getSHCoeffs(order, phi, theta)
141
+
142
+ w = 4.0 * math.pi / (n * n)
143
+
144
+ origins = mesh.vertices
145
+ normals = mesh.vertex_normals
146
+ n_v = origins.shape[0]
147
+
148
+ origins = np.repeat(origins[:, None], n, axis=1).reshape(-1, 3)
149
+ normals = np.repeat(normals[:, None], n, axis=1).reshape(-1, 3)
150
+ PRT_all = None
151
+ for i in range(n):
152
+ SH = np.repeat(SH_orig[None, (i * n):((i + 1) * n)], n_v,
153
+ axis=0).reshape(-1, SH_orig.shape[1])
154
+ vectors = np.repeat(vectors_orig[None, (i * n):((i + 1) * n)],
155
+ n_v,
156
+ axis=0).reshape(-1, 3)
157
+
158
+ dots = (vectors * normals).sum(1)
159
+ front = (dots > 0.0)
160
+
161
+ delta = 1e-3 * min(mesh.bounding_box.extents)
162
+
163
+ hits = mesh.ray.intersects_any(origins + delta * normals, vectors)
164
+ nohits = np.logical_and(front, np.logical_not(hits))
165
+
166
+ PRT = (nohits.astype(np.float) * dots)[:, None] * SH
167
+
168
+ if PRT_all is not None:
169
+ PRT_all += (PRT.reshape(-1, n, SH.shape[1]).sum(1))
170
+ else:
171
+ PRT_all = (PRT.reshape(-1, n, SH.shape[1]).sum(1))
172
+
173
+ PRT = w * PRT_all
174
+ F = mesh.faces
175
+
176
+ np.save(bounce_path, PRT)
177
+ np.save(face_path, F)
178
+
179
+ # NOTE: trimesh sometimes break the original vertex order, but topology will not change.
180
+ # when loading PRT in other program, use the triangle list from trimesh.
181
+
182
+ return PRT, F
183
+
184
+
185
+ def testPRT(obj_path, n=40):
186
+
187
+ os.makedirs(os.path.join(os.path.dirname(obj_path),
188
+ f'../bounce/{os.path.basename(obj_path)[:-4]}'),
189
+ exist_ok=True)
190
+
191
+ PRT, F = computePRT(obj_path, n, 2)
192
+ np.savetxt(
193
+ os.path.join(os.path.dirname(obj_path),
194
+ f'../bounce/{os.path.basename(obj_path)[:-4]}',
195
+ 'bounce.npy'), PRT)
196
+ np.save(
197
+ os.path.join(os.path.dirname(obj_path),
198
+ f'../bounce/{os.path.basename(obj_path)[:-4]}',
199
+ 'face.npy'), F)