Spaces:
Build error
Build error
Upload 5 files
Browse files- lib/renderer/camera.py +226 -0
- lib/renderer/glm.py +143 -0
- lib/renderer/mesh.py +526 -0
- lib/renderer/opengl_util.py +369 -0
- lib/renderer/prt_util.py +199 -0
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)
|