Spaces:
Runtime error
Runtime error
testing
Browse files- README.md +1 -0
- apps/avatarizer.py +1 -1
- apps/benchmark.py +316 -0
- apps/infer.py +7 -1
- configs/econ.yaml +2 -1
- docs/testing.md +71 -0
- lib/common/BNI.py +8 -6
- lib/common/BNI_utils.py +55 -9
- lib/common/config.py +1 -0
- lib/common/local_affine.py +9 -5
- lib/dataset/EvalDataset.py +307 -0
- lib/dataset/Evaluator.py +1 -2
- lib/dataset/mesh_util.py +33 -9
- loose.txt +100 -0
- pose.txt +100 -0
README.md
CHANGED
@@ -89,6 +89,7 @@ ECON is designed for "Human digitization from a color image", which combines the
|
|
89 |
- See [installion doc for Windows](docs/installation-windows.md) to install all the required packages and setup the models on _Windows_
|
90 |
- See [installion doc for Ubuntu](docs/installation-ubuntu.md) to install all the required packages and setup the models on _Ubuntu_
|
91 |
- See [magic tricks](docs/tricks.md) to know a few technical tricks to further improve and accelerate ECON
|
|
|
92 |
|
93 |
## Demo
|
94 |
|
|
|
89 |
- See [installion doc for Windows](docs/installation-windows.md) to install all the required packages and setup the models on _Windows_
|
90 |
- See [installion doc for Ubuntu](docs/installation-ubuntu.md) to install all the required packages and setup the models on _Ubuntu_
|
91 |
- See [magic tricks](docs/tricks.md) to know a few technical tricks to further improve and accelerate ECON
|
92 |
+
- See [testing](docs/testing.md) to prepare the testing data and evaluate ECON
|
93 |
|
94 |
## Demo
|
95 |
|
apps/avatarizer.py
CHANGED
@@ -143,7 +143,7 @@ if not osp.exists(f"{prefix}_econ_da.obj") or not osp.exists(f"{prefix}_smpl_da.
|
|
143 |
smpl_da_body.remove_unreferenced_vertices()
|
144 |
|
145 |
smpl_hand = smpl_da.copy()
|
146 |
-
smpl_hand.update_faces(smplx_container.
|
147 |
smpl_hand.remove_unreferenced_vertices()
|
148 |
econ_da = sum([smpl_hand, smpl_da_body, econ_da_body])
|
149 |
econ_da = poisson(econ_da, f"{prefix}_econ_da.obj", depth=10, decimation=False)
|
|
|
143 |
smpl_da_body.remove_unreferenced_vertices()
|
144 |
|
145 |
smpl_hand = smpl_da.copy()
|
146 |
+
smpl_hand.update_faces(smplx_container.smplx_mano_vertex_mask.numpy()[smpl_hand.faces].all(axis=1))
|
147 |
smpl_hand.remove_unreferenced_vertices()
|
148 |
econ_da = sum([smpl_hand, smpl_da_body, econ_da_body])
|
149 |
econ_da = poisson(econ_da, f"{prefix}_econ_da.obj", depth=10, decimation=False)
|
apps/benchmark.py
ADDED
@@ -0,0 +1,316 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
|
3 |
+
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
4 |
+
# holder of all proprietary rights on this computer program.
|
5 |
+
# You can only use this computer program if you have closed
|
6 |
+
# a license agreement with MPG or you get the right to use the computer
|
7 |
+
# program from someone who is authorized to grant you that right.
|
8 |
+
# Any use of the computer program without a valid license is prohibited and
|
9 |
+
# liable to prosecution.
|
10 |
+
#
|
11 |
+
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
12 |
+
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
13 |
+
# for Intelligent Systems. All rights reserved.
|
14 |
+
#
|
15 |
+
# Contact: ps-license@tuebingen.mpg.de
|
16 |
+
|
17 |
+
import warnings
|
18 |
+
import logging
|
19 |
+
|
20 |
+
warnings.filterwarnings("ignore")
|
21 |
+
logging.getLogger("lightning").setLevel(logging.ERROR)
|
22 |
+
logging.getLogger("trimesh").setLevel(logging.ERROR)
|
23 |
+
|
24 |
+
import torch
|
25 |
+
import argparse
|
26 |
+
import os
|
27 |
+
|
28 |
+
from termcolor import colored
|
29 |
+
from tqdm.auto import tqdm
|
30 |
+
from apps.Normal import Normal
|
31 |
+
from apps.IFGeo import IFGeo
|
32 |
+
from lib.common.config import cfg
|
33 |
+
from lib.common.BNI import BNI
|
34 |
+
from lib.common.BNI_utils import save_normal_tensor
|
35 |
+
from lib.dataset.EvalDataset import EvalDataset
|
36 |
+
from lib.dataset.Evaluator import Evaluator
|
37 |
+
from lib.dataset.mesh_util import *
|
38 |
+
from lib.common.voxelize import VoxelGrid
|
39 |
+
|
40 |
+
torch.backends.cudnn.benchmark = True
|
41 |
+
speed_analysis = False
|
42 |
+
|
43 |
+
if __name__ == "__main__":
|
44 |
+
|
45 |
+
if speed_analysis:
|
46 |
+
import cProfile
|
47 |
+
import pstats
|
48 |
+
profiler = cProfile.Profile()
|
49 |
+
profiler.enable()
|
50 |
+
|
51 |
+
# loading cfg file
|
52 |
+
parser = argparse.ArgumentParser()
|
53 |
+
|
54 |
+
parser.add_argument("-gpu", "--gpu_device", type=int, default=0)
|
55 |
+
parser.add_argument("-ifnet", action="store_true")
|
56 |
+
parser.add_argument("-cfg", "--config", type=str, default="./configs/econ.yaml")
|
57 |
+
|
58 |
+
args = parser.parse_args()
|
59 |
+
|
60 |
+
# cfg read and merge
|
61 |
+
cfg.merge_from_file(args.config)
|
62 |
+
device = torch.device("cuda:0")
|
63 |
+
|
64 |
+
cfg_test_list = [
|
65 |
+
"dataset.rotation_num", 3, "bni.use_smpl", ["hand"], "bni.use_ifnet", args.ifnet,
|
66 |
+
"bni.cut_intersection", True,
|
67 |
+
]
|
68 |
+
|
69 |
+
# # if w/ RenderPeople+CAPE
|
70 |
+
# cfg_test_list += ["dataset.types", ["cape", "renderpeople"], "dataset.scales", [100.0, 1.0]]
|
71 |
+
|
72 |
+
# if only w/ CAPE
|
73 |
+
cfg_test_list += ["dataset.types", ["cape"], "dataset.scales", [100.0]]
|
74 |
+
|
75 |
+
cfg.merge_from_list(cfg_test_list)
|
76 |
+
cfg.freeze()
|
77 |
+
|
78 |
+
# load normal model
|
79 |
+
normal_net = Normal.load_from_checkpoint(
|
80 |
+
cfg=cfg, checkpoint_path=cfg.normal_path, map_location=device, strict=False
|
81 |
+
)
|
82 |
+
normal_net = normal_net.to(device)
|
83 |
+
normal_net.netG.eval()
|
84 |
+
print(
|
85 |
+
colored(
|
86 |
+
f"Resume Normal Estimator from {Format.start} {cfg.normal_path} {Format.end}", "green"
|
87 |
+
)
|
88 |
+
)
|
89 |
+
|
90 |
+
# SMPLX object
|
91 |
+
SMPLX_object = SMPLX()
|
92 |
+
|
93 |
+
dataset = EvalDataset(cfg=cfg, device=device)
|
94 |
+
evaluator = Evaluator(device=device)
|
95 |
+
export_dir = osp.join(cfg.results_path, cfg.name, "IF-Net+" if cfg.bni.use_ifnet else "SMPL-X")
|
96 |
+
print(colored(f"Dataset Size: {len(dataset)}", "green"))
|
97 |
+
|
98 |
+
if cfg.bni.use_ifnet:
|
99 |
+
# load IFGeo model
|
100 |
+
ifnet = IFGeo.load_from_checkpoint(
|
101 |
+
cfg=cfg, checkpoint_path=cfg.ifnet_path, map_location=device, strict=False
|
102 |
+
)
|
103 |
+
ifnet = ifnet.to(device)
|
104 |
+
ifnet.netG.eval()
|
105 |
+
|
106 |
+
print(colored(f"Resume IF-Net+ from {Format.start} {cfg.ifnet_path} {Format.end}", "green"))
|
107 |
+
print(colored(f"Complete with {Format.start} IF-Nets+ (Implicit) {Format.end}", "green"))
|
108 |
+
else:
|
109 |
+
print(colored(f"Complete with {Format.start} SMPL-X (Explicit) {Format.end}", "green"))
|
110 |
+
|
111 |
+
pbar = tqdm(dataset)
|
112 |
+
benchmark = {}
|
113 |
+
|
114 |
+
for data in pbar:
|
115 |
+
|
116 |
+
for key in data.keys():
|
117 |
+
if torch.is_tensor(data[key]):
|
118 |
+
data[key] = data[key].unsqueeze(0).to(device)
|
119 |
+
|
120 |
+
is_smplx = True if 'smplx_path' in data.keys() else False
|
121 |
+
|
122 |
+
# filenames and makedirs
|
123 |
+
current_name = f"{data['dataset']}-{data['subject']}-{data['rotation']:03d}"
|
124 |
+
current_dir = osp.join(export_dir, data['dataset'], data['subject'])
|
125 |
+
os.makedirs(current_dir, exist_ok=True)
|
126 |
+
final_path = osp.join(current_dir, f"{current_name}_final.obj")
|
127 |
+
|
128 |
+
if not osp.exists(final_path):
|
129 |
+
|
130 |
+
in_tensor = data.copy()
|
131 |
+
|
132 |
+
batch_smpl_verts = in_tensor["smpl_verts"].detach()
|
133 |
+
batch_smpl_verts *= torch.tensor([1.0, -1.0, 1.0]).to(device)
|
134 |
+
batch_smpl_faces = in_tensor["smpl_faces"].detach()
|
135 |
+
|
136 |
+
in_tensor["depth_F"], in_tensor["depth_B"] = dataset.render_depth(
|
137 |
+
batch_smpl_verts, batch_smpl_faces
|
138 |
+
)
|
139 |
+
|
140 |
+
with torch.no_grad():
|
141 |
+
in_tensor["normal_F"], in_tensor["normal_B"] = normal_net.netG(in_tensor)
|
142 |
+
|
143 |
+
smpl_mesh = trimesh.Trimesh(
|
144 |
+
batch_smpl_verts.cpu().numpy()[0],
|
145 |
+
batch_smpl_faces.cpu().numpy()[0]
|
146 |
+
)
|
147 |
+
|
148 |
+
side_mesh = smpl_mesh.copy()
|
149 |
+
face_mesh = smpl_mesh.copy()
|
150 |
+
hand_mesh = smpl_mesh.copy()
|
151 |
+
smplx_mesh = smpl_mesh.copy()
|
152 |
+
|
153 |
+
# save normals, depths and masks
|
154 |
+
BNI_dict = save_normal_tensor(
|
155 |
+
in_tensor,
|
156 |
+
0,
|
157 |
+
osp.join(current_dir, "BNI/param_dict"),
|
158 |
+
cfg.bni.thickness if data['dataset'] == 'renderpeople' else 0.0,
|
159 |
+
)
|
160 |
+
|
161 |
+
# BNI process
|
162 |
+
BNI_object = BNI(
|
163 |
+
dir_path=osp.join(current_dir, "BNI"),
|
164 |
+
name=current_name,
|
165 |
+
BNI_dict=BNI_dict,
|
166 |
+
cfg=cfg.bni,
|
167 |
+
device=device
|
168 |
+
)
|
169 |
+
|
170 |
+
BNI_object.extract_surface(False)
|
171 |
+
|
172 |
+
if is_smplx:
|
173 |
+
side_mesh = apply_face_mask(side_mesh, ~SMPLX_object.smplx_eyeball_fid_mask)
|
174 |
+
|
175 |
+
if cfg.bni.use_ifnet:
|
176 |
+
|
177 |
+
# mesh completion via IF-net
|
178 |
+
in_tensor.update(
|
179 |
+
dataset.depth_to_voxel(
|
180 |
+
{
|
181 |
+
"depth_F": BNI_object.F_depth.unsqueeze(0).to(device),
|
182 |
+
"depth_B": BNI_object.B_depth.unsqueeze(0).to(device)
|
183 |
+
}
|
184 |
+
)
|
185 |
+
)
|
186 |
+
|
187 |
+
occupancies = VoxelGrid.from_mesh(side_mesh, cfg.vol_res, loc=[
|
188 |
+
0,
|
189 |
+
] * 3, scale=2.0).data.transpose(2, 1, 0)
|
190 |
+
occupancies = np.flip(occupancies, axis=1)
|
191 |
+
|
192 |
+
in_tensor["body_voxels"] = torch.tensor(occupancies.copy()
|
193 |
+
).float().unsqueeze(0).to(device)
|
194 |
+
|
195 |
+
with torch.no_grad():
|
196 |
+
sdf = ifnet.reconEngine(netG=ifnet.netG, batch=in_tensor)
|
197 |
+
verts_IF, faces_IF = ifnet.reconEngine.export_mesh(sdf)
|
198 |
+
|
199 |
+
if ifnet.clean_mesh_flag:
|
200 |
+
verts_IF, faces_IF = clean_mesh(verts_IF, faces_IF)
|
201 |
+
|
202 |
+
side_mesh_path = osp.join(current_dir, f"{current_name}_IF.obj")
|
203 |
+
side_mesh = remesh_laplacian(trimesh.Trimesh(verts_IF, faces_IF), side_mesh_path)
|
204 |
+
|
205 |
+
full_lst = []
|
206 |
+
|
207 |
+
if "hand" in cfg.bni.use_smpl:
|
208 |
+
|
209 |
+
# only hands
|
210 |
+
if is_smplx:
|
211 |
+
hand_mesh = apply_vertex_mask(hand_mesh, SMPLX_object.smplx_mano_vertex_mask)
|
212 |
+
else:
|
213 |
+
hand_mesh = apply_vertex_mask(hand_mesh, SMPLX_object.smpl_mano_vertex_mask)
|
214 |
+
|
215 |
+
# remove hand neighbor triangles
|
216 |
+
BNI_object.F_B_trimesh = part_removal(
|
217 |
+
BNI_object.F_B_trimesh,
|
218 |
+
hand_mesh,
|
219 |
+
cfg.bni.hand_thres,
|
220 |
+
device,
|
221 |
+
smplx_mesh,
|
222 |
+
region="hand"
|
223 |
+
)
|
224 |
+
side_mesh = part_removal(
|
225 |
+
side_mesh, hand_mesh, cfg.bni.hand_thres, device, smplx_mesh, region="hand"
|
226 |
+
)
|
227 |
+
# hand_mesh.export(osp.join(current_dir, f"{current_name}_hands.obj"))
|
228 |
+
full_lst += [hand_mesh]
|
229 |
+
|
230 |
+
full_lst += [BNI_object.F_B_trimesh]
|
231 |
+
|
232 |
+
# initial side_mesh could be SMPLX or IF-net
|
233 |
+
side_mesh = part_removal(
|
234 |
+
side_mesh, sum(full_lst), 2e-2, device, smplx_mesh, region="", clean=False
|
235 |
+
)
|
236 |
+
|
237 |
+
full_lst += [side_mesh]
|
238 |
+
|
239 |
+
if cfg.bni.use_poisson:
|
240 |
+
final_mesh = poisson(
|
241 |
+
sum(full_lst),
|
242 |
+
final_path,
|
243 |
+
cfg.bni.poisson_depth,
|
244 |
+
)
|
245 |
+
else:
|
246 |
+
final_mesh = sum(full_lst)
|
247 |
+
final_mesh.export(final_path)
|
248 |
+
else:
|
249 |
+
final_mesh = trimesh.load(final_path)
|
250 |
+
|
251 |
+
# evaluation
|
252 |
+
metric_path = osp.join(export_dir, "metric.npy")
|
253 |
+
|
254 |
+
if osp.exists(metric_path):
|
255 |
+
benchmark = np.load(metric_path, allow_pickle=True).item()
|
256 |
+
|
257 |
+
if benchmark == {} or data["dataset"] not in benchmark.keys(
|
258 |
+
) or f"{data['subject']}-{data['rotation']}" not in benchmark[data["dataset"]]["subject"]:
|
259 |
+
|
260 |
+
result_eval = {
|
261 |
+
"verts_gt": data["verts"][0],
|
262 |
+
"faces_gt": data["faces"][0],
|
263 |
+
"verts_pr": final_mesh.vertices,
|
264 |
+
"faces_pr": final_mesh.faces,
|
265 |
+
"calib": data["calib"][0],
|
266 |
+
}
|
267 |
+
|
268 |
+
evaluator.set_mesh(result_eval, scale=False)
|
269 |
+
chamfer, p2s = evaluator.calculate_chamfer_p2s(num_samples=1000)
|
270 |
+
nc = evaluator.calculate_normal_consist(osp.join(current_dir, f"{current_name}_nc.png"))
|
271 |
+
|
272 |
+
if data["dataset"] not in benchmark.keys():
|
273 |
+
benchmark[data["dataset"]] = {
|
274 |
+
"chamfer": [chamfer.item()],
|
275 |
+
"p2s": [p2s.item()],
|
276 |
+
"nc": [nc.item()],
|
277 |
+
"subject": [f"{data['subject']}-{data['rotation']}"],
|
278 |
+
"total": 1,
|
279 |
+
}
|
280 |
+
else:
|
281 |
+
benchmark[data["dataset"]]["chamfer"] += [chamfer.item()]
|
282 |
+
benchmark[data["dataset"]]["p2s"] += [p2s.item()]
|
283 |
+
benchmark[data["dataset"]]["nc"] += [nc.item()]
|
284 |
+
benchmark[data["dataset"]]["subject"] += [f"{data['subject']}-{data['rotation']}"]
|
285 |
+
benchmark[data["dataset"]]["total"] += 1
|
286 |
+
|
287 |
+
np.save(metric_path, benchmark, allow_pickle=True)
|
288 |
+
|
289 |
+
else:
|
290 |
+
|
291 |
+
subject_idx = benchmark[data["dataset"]
|
292 |
+
]["subject"].index(f"{data['subject']}-{data['rotation']}")
|
293 |
+
chamfer = torch.tensor(benchmark[data["dataset"]]["chamfer"][subject_idx])
|
294 |
+
p2s = torch.tensor(benchmark[data["dataset"]]["p2s"][subject_idx])
|
295 |
+
nc = torch.tensor(benchmark[data["dataset"]]["nc"][subject_idx])
|
296 |
+
|
297 |
+
pbar.set_description(
|
298 |
+
f"{current_name} | {chamfer.item():.3f} | {p2s.item():.3f} | {nc.item():.4f}"
|
299 |
+
)
|
300 |
+
|
301 |
+
for dataset in benchmark.keys():
|
302 |
+
for metric in ["chamfer", "p2s", "nc"]:
|
303 |
+
print(
|
304 |
+
f"{dataset}-{metric}: {sum(benchmark[dataset][metric])/benchmark[dataset]['total']:.4f}"
|
305 |
+
)
|
306 |
+
|
307 |
+
if cfg.bni.use_ifnet:
|
308 |
+
print(colored("Finish evaluating on ECON_IF", "green"))
|
309 |
+
else:
|
310 |
+
print(colored("Finish evaluating of ECON_EX", "green"))
|
311 |
+
|
312 |
+
if speed_analysis:
|
313 |
+
profiler.disable()
|
314 |
+
profiler.dump_stats(osp.join(export_dir, "econ.stats"))
|
315 |
+
stats = pstats.Stats(osp.join(export_dir, "econ.stats"))
|
316 |
+
stats.sort_stats("cumtime").print_stats(10)
|
apps/infer.py
CHANGED
@@ -521,7 +521,7 @@ if __name__ == "__main__":
|
|
521 |
side_mesh = apply_vertex_mask(
|
522 |
side_mesh,
|
523 |
(
|
524 |
-
SMPLX_object.front_flame_vertex_mask + SMPLX_object.
|
525 |
SMPLX_object.eyeball_vertex_mask
|
526 |
).eq(0).float(),
|
527 |
)
|
@@ -620,6 +620,12 @@ if __name__ == "__main__":
|
|
620 |
final_path,
|
621 |
cfg.bni.poisson_depth,
|
622 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
623 |
else:
|
624 |
final_mesh = sum(full_lst)
|
625 |
final_mesh.export(final_path)
|
|
|
521 |
side_mesh = apply_vertex_mask(
|
522 |
side_mesh,
|
523 |
(
|
524 |
+
SMPLX_object.front_flame_vertex_mask + SMPLX_object.smplx_mano_vertex_mask +
|
525 |
SMPLX_object.eyeball_vertex_mask
|
526 |
).eq(0).float(),
|
527 |
)
|
|
|
620 |
final_path,
|
621 |
cfg.bni.poisson_depth,
|
622 |
)
|
623 |
+
print(
|
624 |
+
colored(
|
625 |
+
f"\n Poisson completion to {Format.start} {final_path} {Format.end}",
|
626 |
+
"yellow"
|
627 |
+
)
|
628 |
+
)
|
629 |
else:
|
630 |
final_mesh = sum(full_lst)
|
631 |
final_mesh.export(final_path)
|
configs/econ.yaml
CHANGED
@@ -15,7 +15,7 @@ dataset:
|
|
15 |
prior_type: "SMPL"
|
16 |
|
17 |
vol_res: 256
|
18 |
-
mcube_res:
|
19 |
clean_mesh: True
|
20 |
cloth_overlap_thres: 0.50
|
21 |
body_overlap_thres: 0.00
|
@@ -36,3 +36,4 @@ bni:
|
|
36 |
thickness: 0.02
|
37 |
hps_type: "pixie"
|
38 |
texture_src: "SD"
|
|
|
|
15 |
prior_type: "SMPL"
|
16 |
|
17 |
vol_res: 256
|
18 |
+
mcube_res: 256
|
19 |
clean_mesh: True
|
20 |
cloth_overlap_thres: 0.50
|
21 |
body_overlap_thres: 0.00
|
|
|
36 |
thickness: 0.02
|
37 |
hps_type: "pixie"
|
38 |
texture_src: "SD"
|
39 |
+
cut_intersection: True
|
docs/testing.md
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Evaluation
|
2 |
+
|
3 |
+
## Testing Data
|
4 |
+
|
5 |
+
![dataset](../assets/dataset.png)
|
6 |
+
|
7 |
+
- OOD pose (CAPE, [download](https://github.com/YuliangXiu/ICON/blob/master/docs/evaluation.md#cape-testset)): [`pose.txt`](../pose.txt)
|
8 |
+
- OOD outfits (RenderPeople, [link](https://renderpeople.com/)): [`loose.txt`](../loose.txt)
|
9 |
+
|
10 |
+
## Run the evaluation
|
11 |
+
|
12 |
+
```bash
|
13 |
+
# Benchmark of ECON_{IF}, which uses IF-Net+ for completion
|
14 |
+
export CUDA_VISIBLE_DEVICES=0; python -m apps.benchmark -ifnet
|
15 |
+
|
16 |
+
# Benchmark of ECON_{EX}, which uses registered SMPL for completion
|
17 |
+
export CUDA_VISIBLE_DEVICES=1; python -m apps.benchmark
|
18 |
+
|
19 |
+
```
|
20 |
+
|
21 |
+
## Benchmark
|
22 |
+
|
23 |
+
| Method | $\text{ECON}_\text{IF}$ | $\text{ECON}_\text{EX}$ |
|
24 |
+
| :---------: | :-----------------------: | :---------------------: |
|
25 |
+
| | OOD poses (CAPE) | |
|
26 |
+
| Chamfer(cm) | 0.996 | **0.926** |
|
27 |
+
| P2S(cm) | 0.967 | **0.917** |
|
28 |
+
| Normal(L2) | 0.0413 | **0.0367** |
|
29 |
+
| | OOD oufits (RenderPeople) | |
|
30 |
+
| Chamfer(cm) | 1.401 | **1.342** |
|
31 |
+
| P2S(cm) | **1.422** | 1.458 |
|
32 |
+
| Normal(L2) | 0.0516 | **0.0478** |
|
33 |
+
|
34 |
+
**\*OOD: Out-of-Distribution**
|
35 |
+
|
36 |
+
## Citation
|
37 |
+
|
38 |
+
:+1: Please cite these CAPE-related papers
|
39 |
+
|
40 |
+
```
|
41 |
+
|
42 |
+
@inproceedings{xiu2022icon,
|
43 |
+
title = {{ICON}: {I}mplicit {C}lothed humans {O}btained from {N}ormals},
|
44 |
+
author = {Xiu, Yuliang and Yang, Jinlong and Tzionas, Dimitrios and Black, Michael J.},
|
45 |
+
booktitle = {Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)},
|
46 |
+
month = {June},
|
47 |
+
year = {2022},
|
48 |
+
pages = {13296-13306}
|
49 |
+
}
|
50 |
+
|
51 |
+
@inproceedings{CAPE:CVPR:20,
|
52 |
+
title = {{Learning to Dress 3D People in Generative Clothing}},
|
53 |
+
author = {Ma, Qianli and Yang, Jinlong and Ranjan, Anurag and Pujades, Sergi and Pons-Moll, Gerard and Tang, Siyu and Black, Michael J.},
|
54 |
+
booktitle = {Computer Vision and Pattern Recognition (CVPR)},
|
55 |
+
month = June,
|
56 |
+
year = {2020},
|
57 |
+
month_numeric = {6}
|
58 |
+
}
|
59 |
+
|
60 |
+
@article{Pons-Moll:Siggraph2017,
|
61 |
+
title = {ClothCap: Seamless 4D Clothing Capture and Retargeting},
|
62 |
+
author = {Pons-Moll, Gerard and Pujades, Sergi and Hu, Sonny and Black, Michael},
|
63 |
+
journal = {ACM Transactions on Graphics, (Proc. SIGGRAPH)},
|
64 |
+
volume = {36},
|
65 |
+
number = {4},
|
66 |
+
year = {2017},
|
67 |
+
note = {Two first authors contributed equally},
|
68 |
+
crossref = {},
|
69 |
+
url = {http://dx.doi.org/10.1145/3072959.3073711}
|
70 |
+
}
|
71 |
+
```
|
lib/common/BNI.py
CHANGED
@@ -28,6 +28,7 @@ class BNI:
|
|
28 |
self.k = self.cfg['k']
|
29 |
self.lambda1 = self.cfg['lambda1']
|
30 |
self.boundary_consist = self.cfg['boundary_consist']
|
|
|
31 |
|
32 |
self.F_B_surface = None
|
33 |
self.F_B_trimesh = None
|
@@ -54,6 +55,7 @@ class BNI:
|
|
54 |
lambda_depth_front=self.lambda1,
|
55 |
lambda_depth_back=self.lambda1,
|
56 |
lambda_boundary_consistency=self.boundary_consist,
|
|
|
57 |
)
|
58 |
|
59 |
F_verts = verts_inverse_transform(bni_result["F_verts"], self.scale)
|
@@ -71,13 +73,13 @@ class BNI:
|
|
71 |
F_B_verts.float(), F_B_faces.long(), process=False, maintain_order=True
|
72 |
)
|
73 |
|
74 |
-
self.F_trimesh = trimesh.Trimesh(
|
75 |
-
|
76 |
-
)
|
77 |
|
78 |
-
self.B_trimesh = trimesh.Trimesh(
|
79 |
-
|
80 |
-
)
|
81 |
|
82 |
|
83 |
if __name__ == "__main__":
|
|
|
28 |
self.k = self.cfg['k']
|
29 |
self.lambda1 = self.cfg['lambda1']
|
30 |
self.boundary_consist = self.cfg['boundary_consist']
|
31 |
+
self.cut_intersection = self.cfg['cut_intersection']
|
32 |
|
33 |
self.F_B_surface = None
|
34 |
self.F_B_trimesh = None
|
|
|
55 |
lambda_depth_front=self.lambda1,
|
56 |
lambda_depth_back=self.lambda1,
|
57 |
lambda_boundary_consistency=self.boundary_consist,
|
58 |
+
cut_intersection=self.cut_intersection,
|
59 |
)
|
60 |
|
61 |
F_verts = verts_inverse_transform(bni_result["F_verts"], self.scale)
|
|
|
73 |
F_B_verts.float(), F_B_faces.long(), process=False, maintain_order=True
|
74 |
)
|
75 |
|
76 |
+
# self.F_trimesh = trimesh.Trimesh(
|
77 |
+
# F_verts.float(), bni_result["F_faces"].long(), process=False, maintain_order=True
|
78 |
+
# )
|
79 |
|
80 |
+
# self.B_trimesh = trimesh.Trimesh(
|
81 |
+
# B_verts.float(), bni_result["B_faces"].long(), process=False, maintain_order=True
|
82 |
+
# )
|
83 |
|
84 |
|
85 |
if __name__ == "__main__":
|
lib/common/BNI_utils.py
CHANGED
@@ -435,7 +435,8 @@ def double_side_bilateral_normal_integration(
|
|
435 |
max_iter=150,
|
436 |
tol=1e-4,
|
437 |
cg_max_iter=5000,
|
438 |
-
cg_tol=1e-3
|
|
|
439 |
):
|
440 |
|
441 |
# To avoid confusion, we list the coordinate systems in this code as follows
|
@@ -538,6 +539,13 @@ def double_side_bilateral_normal_integration(
|
|
538 |
lambda_depth_back * (z_back - z_prior_back).T @ M @ (z_back - z_prior_back) + \
|
539 |
lambda_boundary_consistency * (z_back - z_front).T @ B @ (z_back - z_front)
|
540 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
541 |
for i in range(max_iter):
|
542 |
A_mat_front = A_front_data.T @ W_front @ A_front_data
|
543 |
b_vec_front = A_front_data.T @ W_front @ b_front
|
@@ -606,22 +614,61 @@ def double_side_bilateral_normal_integration(
|
|
606 |
|
607 |
energy_list.append(energy)
|
608 |
relative_energy = cp.abs(energy - energy_old) / energy_old
|
|
|
609 |
# print(f"step {i + 1}/{max_iter} energy: {energy:.3e}"
|
610 |
# f" relative energy: {relative_energy:.3e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
611 |
if relative_energy < tol:
|
612 |
break
|
613 |
# del A1, A2, A3, A4, nx, ny
|
614 |
|
615 |
-
depth_map_front_est = cp.ones_like(normal_mask, float) * cp.nan
|
616 |
depth_map_front_est[normal_mask] = z_front
|
617 |
-
|
618 |
-
depth_map_back_est = cp.ones_like(normal_mask, float) * cp.nan
|
619 |
depth_map_back_est[normal_mask] = z_back
|
620 |
|
621 |
-
|
622 |
-
|
623 |
-
|
624 |
-
|
|
|
625 |
|
626 |
vertices_front = cp.asnumpy(
|
627 |
map_depth_map_to_point_clouds(
|
@@ -633,7 +680,6 @@ def double_side_bilateral_normal_integration(
|
|
633 |
)
|
634 |
|
635 |
facets_back = cp.asnumpy(construct_facets_from(normal_mask))
|
636 |
-
|
637 |
faces_back = np.concatenate((facets_back[:, [1, 4, 3]], facets_back[:, [1, 3, 2]]), axis=0)
|
638 |
faces_front = np.concatenate((facets_back[:, [1, 2, 3]], facets_back[:, [1, 3, 4]]), axis=0)
|
639 |
|
|
|
435 |
max_iter=150,
|
436 |
tol=1e-4,
|
437 |
cg_max_iter=5000,
|
438 |
+
cg_tol=1e-3,
|
439 |
+
cut_intersection=True,
|
440 |
):
|
441 |
|
442 |
# To avoid confusion, we list the coordinate systems in this code as follows
|
|
|
539 |
lambda_depth_back * (z_back - z_prior_back).T @ M @ (z_back - z_prior_back) + \
|
540 |
lambda_boundary_consistency * (z_back - z_front).T @ B @ (z_back - z_front)
|
541 |
|
542 |
+
depth_map_front_est = cp.ones_like(normal_mask, float) * cp.nan
|
543 |
+
depth_map_back_est = cp.ones_like(normal_mask, float) * cp.nan
|
544 |
+
|
545 |
+
facets_back = cp.asnumpy(construct_facets_from(normal_mask))
|
546 |
+
faces_back = np.concatenate((facets_back[:, [1, 4, 3]], facets_back[:, [1, 3, 2]]), axis=0)
|
547 |
+
faces_front = np.concatenate((facets_back[:, [1, 2, 3]], facets_back[:, [1, 3, 4]]), axis=0)
|
548 |
+
|
549 |
for i in range(max_iter):
|
550 |
A_mat_front = A_front_data.T @ W_front @ A_front_data
|
551 |
b_vec_front = A_front_data.T @ W_front @ b_front
|
|
|
614 |
|
615 |
energy_list.append(energy)
|
616 |
relative_energy = cp.abs(energy - energy_old) / energy_old
|
617 |
+
|
618 |
# print(f"step {i + 1}/{max_iter} energy: {energy:.3e}"
|
619 |
# f" relative energy: {relative_energy:.3e}")
|
620 |
+
|
621 |
+
if False:
|
622 |
+
# intermediate results
|
623 |
+
depth_map_front_est[normal_mask] = z_front
|
624 |
+
depth_map_back_est[normal_mask] = z_back
|
625 |
+
vertices_front = cp.asnumpy(
|
626 |
+
map_depth_map_to_point_clouds(
|
627 |
+
depth_map_front_est, normal_mask, K=None, step_size=step_size
|
628 |
+
)
|
629 |
+
)
|
630 |
+
vertices_back = cp.asnumpy(
|
631 |
+
map_depth_map_to_point_clouds(
|
632 |
+
depth_map_back_est, normal_mask, K=None, step_size=step_size
|
633 |
+
)
|
634 |
+
)
|
635 |
+
|
636 |
+
vertices_front, faces_front_ = remove_stretched_faces(vertices_front, faces_front)
|
637 |
+
vertices_back, faces_back_ = remove_stretched_faces(vertices_back, faces_back)
|
638 |
+
|
639 |
+
F_verts = verts_inverse_transform(torch.as_tensor(vertices_front).float(), 256.0)
|
640 |
+
B_verts = verts_inverse_transform(torch.as_tensor(vertices_back).float(), 256.0)
|
641 |
+
|
642 |
+
F_B_verts = torch.cat((F_verts, B_verts), dim=0)
|
643 |
+
F_B_faces = torch.cat(
|
644 |
+
(
|
645 |
+
torch.as_tensor(faces_front_).long(),
|
646 |
+
torch.as_tensor(faces_back_).long() + faces_front_.max() + 1
|
647 |
+
),
|
648 |
+
dim=0
|
649 |
+
)
|
650 |
+
|
651 |
+
front_surf = trimesh.Trimesh(F_verts, faces_front_)
|
652 |
+
back_surf = trimesh.Trimesh(B_verts, faces_back_)
|
653 |
+
double_surf = trimesh.Trimesh(F_B_verts, F_B_faces)
|
654 |
+
|
655 |
+
bini_dir = "/home/yxiu/Code/ECON/log/bini/OBJ"
|
656 |
+
front_surf.export(osp.join(bini_dir, f"{i:04d}_F.obj"))
|
657 |
+
back_surf.export(osp.join(bini_dir, f"{i:04d}_B.obj"))
|
658 |
+
double_surf.export(osp.join(bini_dir, f"{i:04d}_FB.obj"))
|
659 |
+
|
660 |
if relative_energy < tol:
|
661 |
break
|
662 |
# del A1, A2, A3, A4, nx, ny
|
663 |
|
|
|
664 |
depth_map_front_est[normal_mask] = z_front
|
|
|
|
|
665 |
depth_map_back_est[normal_mask] = z_back
|
666 |
|
667 |
+
if cut_intersection:
|
668 |
+
# manually cut the intersection
|
669 |
+
normal_mask[depth_map_front_est >= depth_map_back_est] = False
|
670 |
+
depth_map_front_est[~normal_mask] = cp.nan
|
671 |
+
depth_map_back_est[~normal_mask] = cp.nan
|
672 |
|
673 |
vertices_front = cp.asnumpy(
|
674 |
map_depth_map_to_point_clouds(
|
|
|
680 |
)
|
681 |
|
682 |
facets_back = cp.asnumpy(construct_facets_from(normal_mask))
|
|
|
683 |
faces_back = np.concatenate((facets_back[:, [1, 4, 3]], facets_back[:, [1, 3, 2]]), axis=0)
|
684 |
faces_front = np.concatenate((facets_back[:, [1, 2, 3]], facets_back[:, [1, 3, 4]]), axis=0)
|
685 |
|
lib/common/config.py
CHANGED
@@ -101,6 +101,7 @@ _C.bni.hand_thres = 4e-2
|
|
101 |
_C.bni.face_thres = 6e-2
|
102 |
_C.bni.hps_type = "pixie"
|
103 |
_C.bni.texture_src = "image"
|
|
|
104 |
|
105 |
# kernel_size, stride, dilation, padding
|
106 |
|
|
|
101 |
_C.bni.face_thres = 6e-2
|
102 |
_C.bni.hps_type = "pixie"
|
103 |
_C.bni.texture_src = "image"
|
104 |
+
_C.bni.cut_intersection = True
|
105 |
|
106 |
# kernel_size, stride, dilation, padding
|
107 |
|
lib/common/local_affine.py
CHANGED
@@ -73,7 +73,7 @@ def trimesh2meshes(mesh):
|
|
73 |
return mesh
|
74 |
|
75 |
|
76 |
-
def register(target_mesh, src_mesh, device):
|
77 |
|
78 |
# define local_affine deform verts
|
79 |
tgt_mesh = trimesh2meshes(target_mesh).to(device)
|
@@ -100,8 +100,11 @@ def register(target_mesh, src_mesh, device):
|
|
100 |
|
101 |
losses = init_loss()
|
102 |
|
103 |
-
|
104 |
-
|
|
|
|
|
|
|
105 |
for i in loop_cloth:
|
106 |
|
107 |
optimizer_cloth.zero_grad()
|
@@ -128,8 +131,9 @@ def register(target_mesh, src_mesh, device):
|
|
128 |
losses[k]["value"] * losses[k]["weight"]
|
129 |
pbar_desc += f"{k}:{losses[k]['value']* losses[k]['weight']:.3f} | "
|
130 |
|
131 |
-
|
132 |
-
|
|
|
133 |
|
134 |
# update params
|
135 |
cloth_loss.backward(retain_graph=True)
|
|
|
73 |
return mesh
|
74 |
|
75 |
|
76 |
+
def register(target_mesh, src_mesh, device, verbose=True):
|
77 |
|
78 |
# define local_affine deform verts
|
79 |
tgt_mesh = trimesh2meshes(target_mesh).to(device)
|
|
|
100 |
|
101 |
losses = init_loss()
|
102 |
|
103 |
+
if verbose:
|
104 |
+
loop_cloth = tqdm(range(100))
|
105 |
+
else:
|
106 |
+
loop_cloth = range(100)
|
107 |
+
|
108 |
for i in loop_cloth:
|
109 |
|
110 |
optimizer_cloth.zero_grad()
|
|
|
131 |
losses[k]["value"] * losses[k]["weight"]
|
132 |
pbar_desc += f"{k}:{losses[k]['value']* losses[k]['weight']:.3f} | "
|
133 |
|
134 |
+
if verbose:
|
135 |
+
pbar_desc += f"TOTAL: {cloth_loss:.3f}"
|
136 |
+
loop_cloth.set_description(pbar_desc)
|
137 |
|
138 |
# update params
|
139 |
cloth_loss.backward(retain_graph=True)
|
lib/dataset/EvalDataset.py
ADDED
@@ -0,0 +1,307 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
|
3 |
+
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
4 |
+
# holder of all proprietary rights on this computer program.
|
5 |
+
# You can only use this computer program if you have closed
|
6 |
+
# a license agreement with MPG or you get the right to use the computer
|
7 |
+
# program from someone who is authorized to grant you that right.
|
8 |
+
# Any use of the computer program without a valid license is prohibited and
|
9 |
+
# liable to prosecution.
|
10 |
+
#
|
11 |
+
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
12 |
+
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
13 |
+
# for Intelligent Systems. All rights reserved.
|
14 |
+
#
|
15 |
+
# Contact: ps-license@tuebingen.mpg.de
|
16 |
+
|
17 |
+
import torch.nn.functional as F
|
18 |
+
from lib.common.render import Render
|
19 |
+
from lib.dataset.mesh_util import (SMPLX, projection, rescale_smpl, HoppeMesh)
|
20 |
+
import os.path as osp
|
21 |
+
import numpy as np
|
22 |
+
from PIL import Image
|
23 |
+
import os
|
24 |
+
import cv2
|
25 |
+
import trimesh
|
26 |
+
import torch
|
27 |
+
import torchvision.transforms as transforms
|
28 |
+
|
29 |
+
cape_gender = {
|
30 |
+
"male":
|
31 |
+
['00032', '00096', '00122', '00127', '00145', '00215', '02474', '03284', '03375', '03394'],
|
32 |
+
"female": ['00134', '00159', '03223', '03331', '03383']
|
33 |
+
}
|
34 |
+
|
35 |
+
|
36 |
+
class EvalDataset:
|
37 |
+
def __init__(self, cfg, device):
|
38 |
+
|
39 |
+
self.root = cfg.root
|
40 |
+
self.bsize = cfg.batch_size
|
41 |
+
|
42 |
+
self.opt = cfg.dataset
|
43 |
+
self.datasets = self.opt.types
|
44 |
+
self.input_size = self.opt.input_size
|
45 |
+
self.scales = self.opt.scales
|
46 |
+
self.vol_res = cfg.vol_res
|
47 |
+
|
48 |
+
# [(feat_name, channel_num),...]
|
49 |
+
self.in_geo = [item[0] for item in cfg.net.in_geo]
|
50 |
+
self.in_nml = [item[0] for item in cfg.net.in_nml]
|
51 |
+
|
52 |
+
self.in_geo_dim = [item[1] for item in cfg.net.in_geo]
|
53 |
+
self.in_nml_dim = [item[1] for item in cfg.net.in_nml]
|
54 |
+
|
55 |
+
self.in_total = self.in_geo + self.in_nml
|
56 |
+
self.in_total_dim = self.in_geo_dim + self.in_nml_dim
|
57 |
+
|
58 |
+
self.rotations = range(0, 360, 120)
|
59 |
+
|
60 |
+
self.datasets_dict = {}
|
61 |
+
|
62 |
+
for dataset_id, dataset in enumerate(self.datasets):
|
63 |
+
|
64 |
+
dataset_dir = osp.join(self.root, dataset)
|
65 |
+
|
66 |
+
mesh_dir = osp.join(dataset_dir, "scans")
|
67 |
+
smplx_dir = osp.join(dataset_dir, "smplx")
|
68 |
+
smpl_dir = osp.join(dataset_dir, "smpl")
|
69 |
+
|
70 |
+
self.datasets_dict[dataset] = {
|
71 |
+
"smplx_dir": smplx_dir,
|
72 |
+
"smpl_dir": smpl_dir,
|
73 |
+
"mesh_dir": mesh_dir,
|
74 |
+
"scale": self.scales[dataset_id],
|
75 |
+
}
|
76 |
+
|
77 |
+
self.datasets_dict[dataset].update(
|
78 |
+
{"subjects": np.loadtxt(osp.join(dataset_dir, "all.txt"), dtype=str)}
|
79 |
+
)
|
80 |
+
|
81 |
+
self.subject_list = self.get_subject_list()
|
82 |
+
self.smplx = SMPLX()
|
83 |
+
|
84 |
+
# PIL to tensor
|
85 |
+
self.image_to_tensor = transforms.Compose(
|
86 |
+
[
|
87 |
+
transforms.Resize(self.input_size),
|
88 |
+
transforms.ToTensor(),
|
89 |
+
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
|
90 |
+
]
|
91 |
+
)
|
92 |
+
|
93 |
+
# PIL to tensor
|
94 |
+
self.mask_to_tensor = transforms.Compose(
|
95 |
+
[
|
96 |
+
transforms.Resize(self.input_size),
|
97 |
+
transforms.ToTensor(),
|
98 |
+
transforms.Normalize((0.0, ), (1.0, )),
|
99 |
+
]
|
100 |
+
)
|
101 |
+
|
102 |
+
self.device = device
|
103 |
+
self.render = Render(size=512, device=self.device)
|
104 |
+
|
105 |
+
def render_normal(self, verts, faces):
|
106 |
+
|
107 |
+
# render optimized mesh (normal, T_normal, image [-1,1])
|
108 |
+
self.render.load_meshes(verts, faces)
|
109 |
+
return self.render.get_image()
|
110 |
+
|
111 |
+
def get_subject_list(self):
|
112 |
+
|
113 |
+
subject_list = []
|
114 |
+
|
115 |
+
for dataset in self.datasets:
|
116 |
+
|
117 |
+
split_txt = ""
|
118 |
+
|
119 |
+
if dataset == 'renderpeople':
|
120 |
+
split_txt = osp.join(self.root, dataset, "loose.txt")
|
121 |
+
elif dataset == 'cape':
|
122 |
+
split_txt = osp.join(self.root, dataset, "pose.txt")
|
123 |
+
|
124 |
+
if osp.exists(split_txt) and osp.getsize(split_txt) > 0:
|
125 |
+
print(f"load from {split_txt}")
|
126 |
+
subject_list += np.loadtxt(split_txt, dtype=str).tolist()
|
127 |
+
|
128 |
+
return subject_list
|
129 |
+
|
130 |
+
def __len__(self):
|
131 |
+
return len(self.subject_list) * len(self.rotations)
|
132 |
+
|
133 |
+
def __getitem__(self, index):
|
134 |
+
|
135 |
+
rid = index % len(self.rotations)
|
136 |
+
mid = index // len(self.rotations)
|
137 |
+
|
138 |
+
rotation = self.rotations[rid]
|
139 |
+
subject = self.subject_list[mid].split("/")[1]
|
140 |
+
dataset = self.subject_list[mid].split("/")[0]
|
141 |
+
render_folder = "/".join([dataset + f"_{self.opt.rotation_num}views", subject])
|
142 |
+
|
143 |
+
if not osp.exists(osp.join(self.root, render_folder)):
|
144 |
+
render_folder = "/".join([dataset + "_36views", subject])
|
145 |
+
|
146 |
+
# setup paths
|
147 |
+
data_dict = {
|
148 |
+
"dataset": dataset,
|
149 |
+
"subject": subject,
|
150 |
+
"rotation": rotation,
|
151 |
+
"scale": self.datasets_dict[dataset]["scale"],
|
152 |
+
"calib_path": osp.join(self.root, render_folder, "calib", f"{rotation:03d}.txt"),
|
153 |
+
"image_path": osp.join(self.root, render_folder, "render", f"{rotation:03d}.png"),
|
154 |
+
}
|
155 |
+
|
156 |
+
if dataset == "cape":
|
157 |
+
data_dict.update(
|
158 |
+
{
|
159 |
+
"mesh_path":
|
160 |
+
osp.join(self.datasets_dict[dataset]["mesh_dir"], f"{subject}.obj"),
|
161 |
+
"smpl_path":
|
162 |
+
osp.join(self.datasets_dict[dataset]["smpl_dir"], f"{subject}.obj"),
|
163 |
+
}
|
164 |
+
)
|
165 |
+
else:
|
166 |
+
|
167 |
+
data_dict.update(
|
168 |
+
{
|
169 |
+
"mesh_path":
|
170 |
+
osp.join(
|
171 |
+
self.datasets_dict[dataset]["mesh_dir"],
|
172 |
+
f"{subject}.obj",
|
173 |
+
),
|
174 |
+
"smplx_path":
|
175 |
+
osp.join(self.datasets_dict[dataset]["smplx_dir"], f"{subject}.obj"),
|
176 |
+
}
|
177 |
+
)
|
178 |
+
|
179 |
+
# load training data
|
180 |
+
data_dict.update(self.load_calib(data_dict))
|
181 |
+
|
182 |
+
# image/normal/depth loader
|
183 |
+
for name, channel in zip(self.in_total, self.in_total_dim):
|
184 |
+
|
185 |
+
if f"{name}_path" not in data_dict.keys():
|
186 |
+
data_dict.update(
|
187 |
+
{
|
188 |
+
f"{name}_path":
|
189 |
+
osp.join(self.root, render_folder, name, f"{rotation:03d}.png")
|
190 |
+
}
|
191 |
+
)
|
192 |
+
|
193 |
+
# tensor update
|
194 |
+
if os.path.exists(data_dict[f"{name}_path"]):
|
195 |
+
data_dict.update(
|
196 |
+
{name: self.imagepath2tensor(data_dict[f"{name}_path"], channel, inv=False)}
|
197 |
+
)
|
198 |
+
|
199 |
+
data_dict.update(self.load_mesh(data_dict))
|
200 |
+
data_dict.update(self.load_smpl(data_dict))
|
201 |
+
|
202 |
+
del data_dict["mesh"]
|
203 |
+
|
204 |
+
return data_dict
|
205 |
+
|
206 |
+
def imagepath2tensor(self, path, channel=3, inv=False):
|
207 |
+
|
208 |
+
rgba = Image.open(path).convert("RGBA")
|
209 |
+
|
210 |
+
# remove CAPE's noisy outliers using OpenCV's inpainting
|
211 |
+
if "cape" in path and "T_" not in path:
|
212 |
+
mask = cv2.imread(path.replace(path.split("/")[-2], "mask"), 0) > 1
|
213 |
+
img = np.asarray(rgba)[:, :, :3]
|
214 |
+
fill_mask = ((mask & (img.sum(axis=2) == 0))).astype(np.uint8)
|
215 |
+
image = Image.fromarray(
|
216 |
+
cv2.inpaint(img * mask[..., None], fill_mask, 3, cv2.INPAINT_TELEA)
|
217 |
+
)
|
218 |
+
mask = Image.fromarray(mask)
|
219 |
+
else:
|
220 |
+
mask = rgba.split()[-1]
|
221 |
+
image = rgba.convert("RGB")
|
222 |
+
|
223 |
+
image = self.image_to_tensor(image)
|
224 |
+
mask = self.mask_to_tensor(mask)
|
225 |
+
image = (image * mask)[:channel]
|
226 |
+
|
227 |
+
return (image * (0.5 - inv) * 2.0).float()
|
228 |
+
|
229 |
+
def load_calib(self, data_dict):
|
230 |
+
calib_data = np.loadtxt(data_dict["calib_path"], dtype=float)
|
231 |
+
extrinsic = calib_data[:4, :4]
|
232 |
+
intrinsic = calib_data[4:8, :4]
|
233 |
+
calib_mat = np.matmul(intrinsic, extrinsic)
|
234 |
+
calib_mat = torch.from_numpy(calib_mat).float()
|
235 |
+
return {"calib": calib_mat}
|
236 |
+
|
237 |
+
def load_mesh(self, data_dict):
|
238 |
+
|
239 |
+
mesh_path = data_dict["mesh_path"]
|
240 |
+
scale = data_dict["scale"]
|
241 |
+
|
242 |
+
# tinyobjloader has wired bug
|
243 |
+
scan_mesh = trimesh.load(mesh_path)
|
244 |
+
verts = scan_mesh.vertices
|
245 |
+
faces = scan_mesh.faces
|
246 |
+
|
247 |
+
# verts, faces = obj_loader(mesh_path, with_uv=False)
|
248 |
+
|
249 |
+
mesh = HoppeMesh(verts * scale, faces)
|
250 |
+
|
251 |
+
return {
|
252 |
+
"mesh": mesh,
|
253 |
+
"verts": torch.as_tensor(verts * scale).float(),
|
254 |
+
"faces": torch.as_tensor(faces).long(),
|
255 |
+
}
|
256 |
+
|
257 |
+
def load_smpl(self, data_dict):
|
258 |
+
|
259 |
+
smpl_type = ("smplx" if ("smplx_path" in data_dict.keys()) else "smpl")
|
260 |
+
|
261 |
+
smplx_verts = rescale_smpl(data_dict[f"{smpl_type}_path"], scale=100.0)
|
262 |
+
smplx_faces = torch.as_tensor(getattr(self.smplx, f"{smpl_type}_faces")).long()
|
263 |
+
smplx_verts = projection(smplx_verts, data_dict["calib"]).float()
|
264 |
+
|
265 |
+
return_dict = {
|
266 |
+
"smpl_verts": smplx_verts,
|
267 |
+
"smpl_faces": smplx_faces,
|
268 |
+
}
|
269 |
+
|
270 |
+
return return_dict
|
271 |
+
|
272 |
+
def depth_to_voxel(self, data_dict):
|
273 |
+
|
274 |
+
data_dict["depth_F"] = transforms.Resize(self.vol_res)(data_dict["depth_F"])
|
275 |
+
data_dict["depth_B"] = transforms.Resize(self.vol_res)(data_dict["depth_B"])
|
276 |
+
|
277 |
+
depth_mask = (~torch.isnan(data_dict['depth_F']))
|
278 |
+
depth_FB = torch.cat([data_dict['depth_F'], data_dict['depth_B']], dim=0)
|
279 |
+
depth_FB[:, ~depth_mask[0]] = 0.
|
280 |
+
|
281 |
+
# Important: index_long = depth_value - 1
|
282 |
+
index_z = (((depth_FB + 1.) * 0.5 * self.vol_res) - 1).clip(0, self.vol_res -
|
283 |
+
1).permute(1, 2, 0)
|
284 |
+
index_z_ceil = torch.ceil(index_z).long()
|
285 |
+
index_z_floor = torch.floor(index_z).long()
|
286 |
+
index_z_frac = torch.frac(index_z)
|
287 |
+
|
288 |
+
index_mask = index_z[..., 0] == torch.tensor(self.vol_res * 0.5 - 1).long()
|
289 |
+
voxels = F.one_hot(index_z_ceil[..., 0], self.vol_res) * index_z_frac[..., 0] + \
|
290 |
+
F.one_hot(index_z_floor[..., 0], self.vol_res) * (1.0-index_z_frac[..., 0]) + \
|
291 |
+
F.one_hot(index_z_ceil[..., 1], self.vol_res) * index_z_frac[..., 1]+ \
|
292 |
+
F.one_hot(index_z_floor[..., 1], self.vol_res) * (1.0 - index_z_frac[..., 1])
|
293 |
+
|
294 |
+
voxels[index_mask] *= 0
|
295 |
+
voxels = torch.flip(voxels, [2]).permute(2, 0, 1).float() #[x-2, y-0, z-1]
|
296 |
+
|
297 |
+
return {
|
298 |
+
"depth_voxels": voxels.flip([
|
299 |
+
0,
|
300 |
+
]).unsqueeze(0).to(self.device),
|
301 |
+
}
|
302 |
+
|
303 |
+
def render_depth(self, verts, faces):
|
304 |
+
|
305 |
+
# render optimized mesh (normal, T_normal, image [-1,1])
|
306 |
+
self.render.load_meshes(verts, faces)
|
307 |
+
return self.render.get_image(type="depth")
|
lib/dataset/Evaluator.py
CHANGED
@@ -296,8 +296,7 @@ class Evaluator:
|
|
296 |
tgt_points = Pointclouds(samples_tgt)
|
297 |
src_points = Pointclouds(samples_src)
|
298 |
|
299 |
-
|
300 |
-
p2s_dist = p2s_dist_all.sum()
|
301 |
|
302 |
chamfer_dist = (
|
303 |
point_mesh_distance(self.tgt_mesh, src_points)[0].sum() * 100.0 + p2s_dist
|
|
|
296 |
tgt_points = Pointclouds(samples_tgt)
|
297 |
src_points = Pointclouds(samples_src)
|
298 |
|
299 |
+
p2s_dist = point_mesh_distance(self.src_mesh, tgt_points)[0].sum() * 100.0
|
|
|
300 |
|
301 |
chamfer_dist = (
|
302 |
point_mesh_distance(self.tgt_mesh, src_points)[0].sum() * 100.0 + p2s_dist
|
lib/dataset/mesh_util.py
CHANGED
@@ -19,6 +19,7 @@ import numpy as np
|
|
19 |
import torch
|
20 |
import torchvision
|
21 |
import trimesh
|
|
|
22 |
import open3d as o3d
|
23 |
import tinyobjloader
|
24 |
import os.path as osp
|
@@ -59,6 +60,9 @@ class SMPLX:
|
|
59 |
self.current_dir, "smpl_data/FLAME_SMPLX_vertex_ids.npy"
|
60 |
)
|
61 |
self.smplx_mano_vid_path = osp.join(self.current_dir, "smpl_data/MANO_SMPLX_vertex_ids.pkl")
|
|
|
|
|
|
|
62 |
self.front_flame_path = osp.join(self.current_dir, "smpl_data/FLAME_face_mask_ids.npy")
|
63 |
self.smplx_vertex_lmkid_path = osp.join(
|
64 |
self.current_dir, "smpl_data/smplx_vertex_lmkid.npy"
|
@@ -70,6 +74,14 @@ class SMPLX:
|
|
70 |
self.smpl_faces = np.load(self.smpl_faces_path)
|
71 |
self.smplx_vertex_lmkid = np.load(self.smplx_vertex_lmkid_path)
|
72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
self.smplx_eyeball_fid_mask = np.load(self.smplx_eyeball_fid_path)
|
74 |
self.smplx_mouth_fid = np.load(self.smplx_fill_mouth_fid_path)
|
75 |
self.smplx_mano_vid_dict = np.load(self.smplx_mano_vid_path, allow_pickle=True)
|
@@ -80,9 +92,13 @@ class SMPLX:
|
|
80 |
self.smplx_front_flame_vid = self.smplx_flame_vid[np.load(self.front_flame_path)]
|
81 |
|
82 |
# hands
|
83 |
-
self.
|
84 |
0, torch.tensor(self.smplx_mano_vid), 1.0
|
85 |
)
|
|
|
|
|
|
|
|
|
86 |
# face
|
87 |
self.front_flame_vertex_mask = torch.zeros(self.smplx_verts.shape[0], ).index_fill_(
|
88 |
0, torch.tensor(self.smplx_front_flame_vid), 1.0
|
@@ -237,11 +253,20 @@ def part_removal(full_mesh, part_mesh, thres, device, smpl_obj, region, clean=Tr
|
|
237 |
|
238 |
if region == "hand":
|
239 |
_, idx = smpl_tree.query(full_mesh.vertices, k=1)
|
240 |
-
|
241 |
-
|
242 |
-
remove_mask
|
243 |
-
|
244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
|
246 |
elif region == "face":
|
247 |
_, idx = smpl_tree.query(full_mesh.vertices, k=5)
|
@@ -386,15 +411,14 @@ def remesh_laplacian(mesh, obj_path):
|
|
386 |
|
387 |
def poisson(mesh, obj_path, depth=10, decimation=True):
|
388 |
|
389 |
-
pcd_path = obj_path[:-4] + ".ply"
|
390 |
assert (mesh.vertex_normals.shape[1] == 3)
|
391 |
mesh.export(pcd_path)
|
392 |
pcl = o3d.io.read_point_cloud(pcd_path)
|
393 |
with o3d.utility.VerbosityContextManager(o3d.utility.VerbosityLevel.Error) as cm:
|
394 |
mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
|
395 |
-
pcl, depth=depth, n_threads
|
396 |
)
|
397 |
-
print(colored(f"\n Poisson completion to {Format.start} {obj_path} {Format.end}", "yellow"))
|
398 |
|
399 |
# only keep the largest component
|
400 |
largest_mesh = keep_largest(trimesh.Trimesh(np.array(mesh.vertices), np.array(mesh.triangles)))
|
|
|
19 |
import torch
|
20 |
import torchvision
|
21 |
import trimesh
|
22 |
+
import json
|
23 |
import open3d as o3d
|
24 |
import tinyobjloader
|
25 |
import os.path as osp
|
|
|
60 |
self.current_dir, "smpl_data/FLAME_SMPLX_vertex_ids.npy"
|
61 |
)
|
62 |
self.smplx_mano_vid_path = osp.join(self.current_dir, "smpl_data/MANO_SMPLX_vertex_ids.pkl")
|
63 |
+
self.smpl_vert_seg_path = osp.join(
|
64 |
+
osp.dirname(__file__), "../../lib/common/smpl_vert_segmentation.json"
|
65 |
+
)
|
66 |
self.front_flame_path = osp.join(self.current_dir, "smpl_data/FLAME_face_mask_ids.npy")
|
67 |
self.smplx_vertex_lmkid_path = osp.join(
|
68 |
self.current_dir, "smpl_data/smplx_vertex_lmkid.npy"
|
|
|
74 |
self.smpl_faces = np.load(self.smpl_faces_path)
|
75 |
self.smplx_vertex_lmkid = np.load(self.smplx_vertex_lmkid_path)
|
76 |
|
77 |
+
self.smpl_vert_seg = json.load(open(self.smpl_vert_seg_path))
|
78 |
+
self.smpl_mano_vid = np.concatenate(
|
79 |
+
[
|
80 |
+
self.smpl_vert_seg["rightHand"], self.smpl_vert_seg["rightHandIndex1"],
|
81 |
+
self.smpl_vert_seg["leftHand"], self.smpl_vert_seg["leftHandIndex1"]
|
82 |
+
]
|
83 |
+
)
|
84 |
+
|
85 |
self.smplx_eyeball_fid_mask = np.load(self.smplx_eyeball_fid_path)
|
86 |
self.smplx_mouth_fid = np.load(self.smplx_fill_mouth_fid_path)
|
87 |
self.smplx_mano_vid_dict = np.load(self.smplx_mano_vid_path, allow_pickle=True)
|
|
|
92 |
self.smplx_front_flame_vid = self.smplx_flame_vid[np.load(self.front_flame_path)]
|
93 |
|
94 |
# hands
|
95 |
+
self.smplx_mano_vertex_mask = torch.zeros(self.smplx_verts.shape[0], ).index_fill_(
|
96 |
0, torch.tensor(self.smplx_mano_vid), 1.0
|
97 |
)
|
98 |
+
self.smpl_mano_vertex_mask = torch.zeros(self.smpl_verts.shape[0], ).index_fill_(
|
99 |
+
0, torch.tensor(self.smpl_mano_vid), 1.0
|
100 |
+
)
|
101 |
+
|
102 |
# face
|
103 |
self.front_flame_vertex_mask = torch.zeros(self.smplx_verts.shape[0], ).index_fill_(
|
104 |
0, torch.tensor(self.smplx_front_flame_vid), 1.0
|
|
|
253 |
|
254 |
if region == "hand":
|
255 |
_, idx = smpl_tree.query(full_mesh.vertices, k=1)
|
256 |
+
if smpl_obj.vertices.shape[0] > 6890:
|
257 |
+
full_lmkid = SMPL_container.smplx_vertex_lmkid[idx]
|
258 |
+
remove_mask = torch.logical_and(
|
259 |
+
remove_mask,
|
260 |
+
torch.tensor(full_lmkid >= 20).type_as(remove_mask).unsqueeze(0)
|
261 |
+
)
|
262 |
+
else:
|
263 |
+
remove_mask = torch.logical_and(
|
264 |
+
remove_mask,
|
265 |
+
torch.isin(
|
266 |
+
torch.tensor(idx).long(),
|
267 |
+
torch.tensor(SMPL_container.smpl_mano_vid).long()
|
268 |
+
).type_as(remove_mask).unsqueeze(0)
|
269 |
+
)
|
270 |
|
271 |
elif region == "face":
|
272 |
_, idx = smpl_tree.query(full_mesh.vertices, k=5)
|
|
|
411 |
|
412 |
def poisson(mesh, obj_path, depth=10, decimation=True):
|
413 |
|
414 |
+
pcd_path = obj_path[:-4] + "_soups.ply"
|
415 |
assert (mesh.vertex_normals.shape[1] == 3)
|
416 |
mesh.export(pcd_path)
|
417 |
pcl = o3d.io.read_point_cloud(pcd_path)
|
418 |
with o3d.utility.VerbosityContextManager(o3d.utility.VerbosityLevel.Error) as cm:
|
419 |
mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
|
420 |
+
pcl, depth=depth, n_threads=6
|
421 |
)
|
|
|
422 |
|
423 |
# only keep the largest component
|
424 |
largest_mesh = keep_largest(trimesh.Trimesh(np.array(mesh.vertices), np.array(mesh.triangles)))
|
loose.txt
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
renderpeople/rp_yasmin_posed_007
|
2 |
+
renderpeople/rp_victoria_posed_006
|
3 |
+
renderpeople/rp_tilda_posed_005
|
4 |
+
renderpeople/rp_tiffany_posed_015
|
5 |
+
renderpeople/rp_tanja_posed_018
|
6 |
+
renderpeople/rp_stephanie_posed_010
|
7 |
+
renderpeople/rp_stacy_posed_002
|
8 |
+
renderpeople/rp_serena_posed_027
|
9 |
+
renderpeople/rp_serena_posed_024
|
10 |
+
renderpeople/rp_seiko_posed_031
|
11 |
+
renderpeople/rp_seiko_posed_015
|
12 |
+
renderpeople/rp_saki_posed_033
|
13 |
+
renderpeople/rp_rosy_posed_014
|
14 |
+
renderpeople/rp_rosy_posed_001
|
15 |
+
renderpeople/rp_roberta_posed_022
|
16 |
+
renderpeople/rp_rick_posed_016
|
17 |
+
renderpeople/rp_ray_posed_007
|
18 |
+
renderpeople/rp_ramon_posed_002
|
19 |
+
renderpeople/rp_ralph_posed_013
|
20 |
+
renderpeople/rp_philip_posed_030
|
21 |
+
renderpeople/rp_petra_posed_008
|
22 |
+
renderpeople/rp_olivia_posed_014
|
23 |
+
renderpeople/rp_olivia_posed_007
|
24 |
+
renderpeople/rp_naomi_posed_034
|
25 |
+
renderpeople/rp_naomi_posed_030
|
26 |
+
renderpeople/rp_martha_posed_002
|
27 |
+
renderpeople/rp_martha_posed_001
|
28 |
+
renderpeople/rp_marleen_posed_002
|
29 |
+
renderpeople/rp_lina_posed_004
|
30 |
+
renderpeople/rp_kylie_posed_017
|
31 |
+
renderpeople/rp_kylie_posed_006
|
32 |
+
renderpeople/rp_kylie_posed_003
|
33 |
+
renderpeople/rp_kent_posed_005
|
34 |
+
renderpeople/rp_kent_posed_002
|
35 |
+
renderpeople/rp_julia_posed_022
|
36 |
+
renderpeople/rp_julia_posed_014
|
37 |
+
renderpeople/rp_judy_posed_002
|
38 |
+
renderpeople/rp_jessica_posed_058
|
39 |
+
renderpeople/rp_jessica_posed_022
|
40 |
+
renderpeople/rp_jennifer_posed_003
|
41 |
+
renderpeople/rp_janna_posed_046
|
42 |
+
renderpeople/rp_janna_posed_043
|
43 |
+
renderpeople/rp_janna_posed_034
|
44 |
+
renderpeople/rp_janna_posed_019
|
45 |
+
renderpeople/rp_janett_posed_016
|
46 |
+
renderpeople/rp_jamal_posed_012
|
47 |
+
renderpeople/rp_helen_posed_037
|
48 |
+
renderpeople/rp_fiona_posed_002
|
49 |
+
renderpeople/rp_felice_posed_005
|
50 |
+
renderpeople/rp_felice_posed_004
|
51 |
+
renderpeople/rp_eve_posed_003
|
52 |
+
renderpeople/rp_eve_posed_002
|
53 |
+
renderpeople/rp_eve_posed_001
|
54 |
+
renderpeople/rp_eric_posed_048
|
55 |
+
renderpeople/rp_emma_posed_029
|
56 |
+
renderpeople/rp_ellie_posed_015
|
57 |
+
renderpeople/rp_ellie_posed_014
|
58 |
+
renderpeople/rp_debra_posed_016
|
59 |
+
renderpeople/rp_debra_posed_014
|
60 |
+
renderpeople/rp_debra_posed_004
|
61 |
+
renderpeople/rp_corey_posed_020
|
62 |
+
renderpeople/rp_corey_posed_009
|
63 |
+
renderpeople/rp_corey_posed_004
|
64 |
+
renderpeople/rp_cody_posed_016
|
65 |
+
renderpeople/rp_claudia_posed_034
|
66 |
+
renderpeople/rp_claudia_posed_033
|
67 |
+
renderpeople/rp_claudia_posed_024
|
68 |
+
renderpeople/rp_claudia_posed_025
|
69 |
+
renderpeople/rp_cindy_posed_020
|
70 |
+
renderpeople/rp_christine_posed_023
|
71 |
+
renderpeople/rp_christine_posed_022
|
72 |
+
renderpeople/rp_christine_posed_020
|
73 |
+
renderpeople/rp_christine_posed_010
|
74 |
+
renderpeople/rp_carla_posed_016
|
75 |
+
renderpeople/rp_caren_posed_009
|
76 |
+
renderpeople/rp_caren_posed_008
|
77 |
+
renderpeople/rp_brandon_posed_006
|
78 |
+
renderpeople/rp_belle_posed_001
|
79 |
+
renderpeople/rp_beatrice_posed_025
|
80 |
+
renderpeople/rp_beatrice_posed_024
|
81 |
+
renderpeople/rp_beatrice_posed_023
|
82 |
+
renderpeople/rp_beatrice_posed_021
|
83 |
+
renderpeople/rp_beatrice_posed_019
|
84 |
+
renderpeople/rp_beatrice_posed_017
|
85 |
+
renderpeople/rp_anna_posed_008
|
86 |
+
renderpeople/rp_anna_posed_007
|
87 |
+
renderpeople/rp_anna_posed_006
|
88 |
+
renderpeople/rp_anna_posed_003
|
89 |
+
renderpeople/rp_anna_posed_001
|
90 |
+
renderpeople/rp_alvin_posed_016
|
91 |
+
renderpeople/rp_alison_posed_028
|
92 |
+
renderpeople/rp_alison_posed_024
|
93 |
+
renderpeople/rp_alison_posed_017
|
94 |
+
renderpeople/rp_alexandra_posed_022
|
95 |
+
renderpeople/rp_alexandra_posed_023
|
96 |
+
renderpeople/rp_alexandra_posed_019
|
97 |
+
renderpeople/rp_alexandra_posed_018
|
98 |
+
renderpeople/rp_alexandra_posed_013
|
99 |
+
renderpeople/rp_alexandra_posed_012
|
100 |
+
renderpeople/rp_alexandra_posed_011
|
pose.txt
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
cape/00215-jerseyshort-pose_model-000200
|
2 |
+
cape/00134-longlong-ballet4_trial2-000250
|
3 |
+
cape/00134-longlong-badminton_trial1-000230
|
4 |
+
cape/00134-longlong-frisbee_trial1-000190
|
5 |
+
cape/03375-shortlong-ballet1_trial1-000210
|
6 |
+
cape/03375-longlong-babysit_trial2-000110
|
7 |
+
cape/00134-shortlong-stretch_trial1-000310
|
8 |
+
cape/03375-shortshort-lean_trial1-000060
|
9 |
+
cape/03375-shortshort-swim_trial2-000110
|
10 |
+
cape/03375-longlong-box_trial1-000190
|
11 |
+
cape/03375-longlong-row_trial2-000150
|
12 |
+
cape/00134-shortlong-hockey_trial1-000140
|
13 |
+
cape/00134-shortlong-hockey_trial1-000090
|
14 |
+
cape/00134-longlong-ski_trial2-000200
|
15 |
+
cape/00134-longlong-stretch_trial1-000450
|
16 |
+
cape/00096-shirtshort-soccer-000160
|
17 |
+
cape/03375-shortshort-hands_up_trial2-000270
|
18 |
+
cape/03375-shortshort-ballet1_trial1-000110
|
19 |
+
cape/03375-longlong-babysit_trial2-000150
|
20 |
+
cape/03375-shortshort-fashion_trial1-000140
|
21 |
+
cape/00134-shortlong-ballet2_trial1-000110
|
22 |
+
cape/00134-longlong-ballet2_trial1-000120
|
23 |
+
cape/00134-shortlong-ballet2_trial1-000120
|
24 |
+
cape/00134-shortlong-ballet2_trial1-000090
|
25 |
+
cape/00134-longlong-ballet2_trial2-000110
|
26 |
+
cape/00134-longlong-volleyball_trial2-000050
|
27 |
+
cape/00134-longlong-stretch_trial1-000500
|
28 |
+
cape/00134-longlong-housework_trial1-000380
|
29 |
+
cape/00134-shortlong-dig_trial1-000150
|
30 |
+
cape/03375-longlong-catchpick_trial1-000110
|
31 |
+
cape/03375-shortlong-ballet1_trial1-000250
|
32 |
+
cape/03375-shortlong-shoulders_trial1-000360
|
33 |
+
cape/03375-shortlong-slack_trial2-000070
|
34 |
+
cape/03375-shortlong-shoulders_trial1-000220
|
35 |
+
cape/03375-shortlong-stretch_trial1-000330
|
36 |
+
cape/00127-shortlong-ballerina_spin-000080
|
37 |
+
cape/00127-shortlong-ballerina_spin-000200
|
38 |
+
cape/00096-shortshort-basketball-000100
|
39 |
+
cape/00096-shortshort-ballerina_spin-000160
|
40 |
+
cape/00134-longlong-stretch_trial2-000440
|
41 |
+
cape/02474-longlong-ATUsquat-000100
|
42 |
+
cape/03375-longlong-ATUsquat_trial1-000120
|
43 |
+
cape/02474-longlong-ATUsquat-000110
|
44 |
+
cape/00134-longlong-ballet1_trial1-000180
|
45 |
+
cape/00096-shirtlong-ATUsquat-000130
|
46 |
+
cape/00032-shortshort-pose_model-000030
|
47 |
+
cape/00134-shortlong-athletics_trial2-000070
|
48 |
+
cape/00032-longshort-pose_model-000060
|
49 |
+
cape/00032-shortshort-shoulders_mill-000060
|
50 |
+
cape/00127-shortlong-pose_model-000430
|
51 |
+
cape/00122-shortshort-ATUsquat-000120
|
52 |
+
cape/00032-shortshort-bend_back_and_front-000220
|
53 |
+
cape/00096-shortshort-squats-000180
|
54 |
+
cape/00032-shortlong-squats-000090
|
55 |
+
cape/03375-shortlong-ATUsquat_trial2-000080
|
56 |
+
cape/03375-shortshort-lean_trial1-000130
|
57 |
+
cape/03375-blazerlong-music_trial1-000150
|
58 |
+
cape/03284-longlong-hips-000170
|
59 |
+
cape/03375-shortlong-shoulders_trial1-000370
|
60 |
+
cape/03375-shortlong-ballet1_trial1-000290
|
61 |
+
cape/00215-jerseyshort-shoulders_mill-000320
|
62 |
+
cape/00215-poloshort-soccer-000110
|
63 |
+
cape/00122-shortshort-punching-000170
|
64 |
+
cape/00096-jerseyshort-shoulders_mill-000140
|
65 |
+
cape/00032-longshort-flying_eagle-000240
|
66 |
+
cape/00134-shortlong-swim_trial1-000160
|
67 |
+
cape/03375-shortshort-music_trial1-000120
|
68 |
+
cape/03375-shortshort-handball_trial1-000120
|
69 |
+
cape/00215-longshort-punching-000060
|
70 |
+
cape/00134-shortlong-swim_trial2-000120
|
71 |
+
cape/03375-shortshort-hands_up_trial1-000140
|
72 |
+
cape/03375-shortshort-hands_up_trial1-000270
|
73 |
+
cape/03375-shortshort-volleyball_trial1-000110
|
74 |
+
cape/03375-shortshort-swim_trial1-000270
|
75 |
+
cape/03375-longlong-row_trial2-000190
|
76 |
+
cape/00215-poloshort-flying_eagle-000120
|
77 |
+
cape/03223-shortshort-flying_eagle-000280
|
78 |
+
cape/00096-shirtlong-shoulders_mill-000110
|
79 |
+
cape/00096-shirtshort-pose_model-000190
|
80 |
+
cape/03375-shortshort-swim_trial1-000190
|
81 |
+
cape/03375-shortlong-music_trial2-000040
|
82 |
+
cape/03375-shortlong-babysit_trial2-000070
|
83 |
+
cape/00215-jerseyshort-flying_eagle-000110
|
84 |
+
cape/03375-blazerlong-music_trial1-000030
|
85 |
+
cape/03375-longlong-volleyball_trial2-000230
|
86 |
+
cape/03375-blazerlong-lean_trial2-000110
|
87 |
+
cape/03375-longlong-box_trial2-000110
|
88 |
+
cape/03375-longlong-drinkeat_trial2-000050
|
89 |
+
cape/00134-shortlong-slack_trial1-000150
|
90 |
+
cape/03375-shortshort-climb_trial1-000170
|
91 |
+
cape/00032-longshort-tilt_twist_left-000060
|
92 |
+
cape/00215-longshort-chicken_wings-000060
|
93 |
+
cape/00215-poloshort-bend_back_and_front-000130
|
94 |
+
cape/03223-longshort-flying_eagle-000480
|
95 |
+
cape/00215-longshort-bend_back_and_front-000100
|
96 |
+
cape/00215-longshort-tilt_twist_left-000130
|
97 |
+
cape/00096-longshort-tilt_twist_left-000150
|
98 |
+
cape/03284-longshort-twist_tilt_left-000080
|
99 |
+
cape/03223-shortshort-flying_eagle-000270
|
100 |
+
cape/02474-longshort-improvise-000080
|