320 lines
12 KiB
Python
320 lines
12 KiB
Python
'''
|
||
@ Date: 2021-11-22 15:16:14
|
||
@ Author: Qing Shuai
|
||
@ LastEditors: Qing Shuai
|
||
@ LastEditTime: 2022-09-28 21:40:17
|
||
@ FilePath: /EasyMocapPublic/easymocap/visualize/render_base.py
|
||
'''
|
||
from glob import glob
|
||
from os.path import join
|
||
import numpy as np
|
||
|
||
from ..mytools.file_utils import read_json
|
||
from ..mytools.debug_utils import log
|
||
from ..mytools.reader import read_keypoints3d, read_smpl
|
||
import os
|
||
from ..mytools.camera_utils import read_cameras, Undistort
|
||
import cv2
|
||
from ..mytools.vis_base import merge, plot_keypoints_auto
|
||
from ..config.baseconfig import load_object
|
||
from .geometry import load_sphere
|
||
|
||
def imwrite(imgname, img):
|
||
if not os.path.exists(os.path.dirname(imgname)):
|
||
os.makedirs(os.path.dirname(imgname))
|
||
if img.shape[0] % 2 == 1 or img.shape[1] % 2 == 1:
|
||
img = cv2.resize(img, (img.shape[1]//2*2, img.shape[0]//2*2))
|
||
cv2.imwrite(imgname, img)
|
||
|
||
def compute_normals(vertices, faces):
|
||
normal = np.zeros_like(vertices)
|
||
|
||
# compute normal per triangle
|
||
normal_faces = np.cross(vertices[faces[:,1]] - vertices[faces[:,0]], vertices[faces[:,2]] - vertices[faces[:,0]])
|
||
|
||
# sum normals at vtx
|
||
normal[faces[:, 0]] += normal_faces[:]
|
||
normal[faces[:, 1]] += normal_faces[:]
|
||
normal[faces[:, 2]] += normal_faces[:]
|
||
|
||
# compute norms
|
||
normal = normal / np.linalg.norm(normal, axis=-1, keepdims=True)
|
||
|
||
return normal
|
||
|
||
def get_dilation_of_mesh(delta):
|
||
def func(info):
|
||
vertices = info['vertices']
|
||
normals = compute_normals(info['vertices'], info['faces'])
|
||
vertices += delta * normals
|
||
return info
|
||
return func
|
||
|
||
class Results:
|
||
def __init__(self, body_model, path, rend_type,
|
||
operation='none') -> None:
|
||
self.path = path
|
||
self.body_model = body_model
|
||
self.skelnames = sorted(glob(join(path, '*.json')))
|
||
self.ismulti = False
|
||
if len(self.skelnames) == 0:
|
||
# 尝试找多视角的结果
|
||
subs = sorted(os.listdir(path))
|
||
assert len(subs) > 0, path
|
||
self.ismulti = True
|
||
self.subs = subs
|
||
self.skelnames = {}
|
||
for sub in subs:
|
||
skelnames = sorted(glob(join(path, sub, '*.json')))
|
||
self.skelnames[sub] = skelnames
|
||
self.rend_type = rend_type
|
||
self.read_func = {'skel': read_keypoints3d, 'mesh': read_smpl}[rend_type]
|
||
if operation.startswith('dilation'):
|
||
# TODO: 暂时直接解析
|
||
opfunc = get_dilation_of_mesh(float(operation.replace('dilation:', '')))
|
||
else:
|
||
opfunc = lambda x:x
|
||
self.operation = opfunc
|
||
|
||
def process(self, info):
|
||
return info
|
||
|
||
def read(self, skelname):
|
||
results = self.read_func(skelname)
|
||
render_data = {}
|
||
trans = np.array([[0., 0., 0., 0.]])
|
||
for info in results:
|
||
info['vertices'] = self.body_model(return_verts=True, return_tensor=False, **info)[0]
|
||
info['keypoints3d'] = self.body_model(return_verts=False, return_tensor=False, **info)[0]
|
||
info['vertices'] += trans[:, :3]
|
||
info['faces'] = self.body_model.faces
|
||
d = self.operation(info)
|
||
render_data[d['id']] = {
|
||
'vertices': d['vertices'], 'keypoints3d': d['keypoints3d'],
|
||
'faces': info['faces'],
|
||
'vid': d['id'], 'name': 'human_{}'.format(d['id'])}
|
||
if self.rend_type == 'skel':
|
||
render_data[d['id']]['smooth'] = False
|
||
return render_data
|
||
|
||
def get_multi(self, index):
|
||
datas = {}
|
||
for sub in self.subs:
|
||
skelname = self.skelnames[sub][index]
|
||
basename = os.path.basename(skelname).replace('.json', '')
|
||
render_data = self.read(skelname)
|
||
datas[sub] = render_data
|
||
return basename, datas
|
||
|
||
def __getitem__(self, index):
|
||
if self.ismulti:
|
||
return self.get_multi(index)
|
||
else:
|
||
skelname = self.skelnames[index]
|
||
basename = os.path.basename(skelname).replace('.json', '')
|
||
return basename, self.read(skelname)
|
||
|
||
def __len__(self):
|
||
if self.ismulti:
|
||
return len(self.skelnames[self.subs[0]])
|
||
else:
|
||
return len(self.skelnames)
|
||
|
||
class ResultsObjects(Results):
|
||
def __init__(self, object,**kwargs) -> None:
|
||
super().__init__(**kwargs)
|
||
self.object = object
|
||
|
||
def __getitem__(self, index):
|
||
basename, datas = super().__getitem__(index)
|
||
objectname = join(self.path, '..', '..', self.object, basename+'.json')
|
||
objects = read_json(objectname)
|
||
ret_objects = {}
|
||
for obj in objects:
|
||
pid = obj['id']
|
||
vertices, faces = load_sphere()
|
||
vertices *= 0.12
|
||
vertices += np.array(obj['keypoints3d'])[:, :3]
|
||
ret_objects[1000+pid] = {
|
||
'vertices': vertices,
|
||
'faces': faces,
|
||
'vid': pid,
|
||
'name': 'object_{}'.format(pid)
|
||
}
|
||
if self.ismulti:
|
||
for key, data in datas.items():
|
||
data.update(ret_objects)
|
||
else:
|
||
datas.update(ret_objects)
|
||
return basename, datas
|
||
|
||
class Images:
|
||
def __init__(self, path, subs, image_args) -> None:
|
||
if path == 'none':
|
||
self.images = path
|
||
# no need for images
|
||
assert len(subs) > 0, '{} must non-empty'.format(subs)
|
||
else:
|
||
self.images = join(path, 'images')
|
||
if len(subs) == 0:
|
||
subs = sorted(os.listdir(self.images))
|
||
if subs[0].isdigit():
|
||
subs.sort(key=lambda x:int(x))
|
||
self.cameras = read_cameras(path)
|
||
self.subs = subs
|
||
self.image_args = image_args
|
||
self.cameras_vis = {sub:cam.copy() for sub, cam in self.cameras.items()}
|
||
# rescale the camera
|
||
for cam in self.cameras_vis.values():
|
||
K = cam['K'].copy()
|
||
cam['K'][:2, :] *= image_args.scale
|
||
self.distortMap = {}
|
||
|
||
def __call__(self, basename):
|
||
if self.images == 'none':
|
||
# 返回空的图像
|
||
imgs = {sub: self.blank.copy() for sub in self.subs}
|
||
import ipdb; ipdb.set_trace()
|
||
else:
|
||
imgs = {}
|
||
for sub in self.subs:
|
||
imgname = join(self.images, sub, basename+self.image_args.ext)
|
||
if not os.path.exists(imgname):
|
||
for ext in ['.jpg', '.png']:
|
||
imgname_ = imgname.replace(self.image_args.ext, ext)
|
||
if os.path.exists(imgname_):
|
||
self.image_args.ext = ext
|
||
imgname = imgname_
|
||
assert os.path.exists(imgname), imgname
|
||
img = cv2.imread(imgname)
|
||
if self.image_args.scale != 1:
|
||
img = cv2.resize(img, None, fx=self.image_args.scale, fy=self.image_args.scale)
|
||
if self.image_args.undis:
|
||
camera = self.cameras_vis[sub]
|
||
K, D = camera['K'], camera['dist']
|
||
if sub not in self.distortMap.keys():
|
||
h, w = img.shape[:2]
|
||
mapx, mapy = cv2.initUndistortRectifyMap(camera['K'], camera['dist'], None, camera['K'], (w,h), 5)
|
||
self.distortMap[sub] = (mapx, mapy)
|
||
mapx, mapy = self.distortMap[sub]
|
||
img = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
|
||
imgs[sub] = img
|
||
return imgs, self.cameras_vis
|
||
|
||
class Repro:
|
||
def __init__(self, out, merge='none') -> None:
|
||
self.out = out
|
||
os.makedirs(self.out, exist_ok=True)
|
||
self.merge = merge
|
||
|
||
@staticmethod
|
||
def repro(P, keypoints3d, img):
|
||
if keypoints3d.shape[1] == 3:
|
||
keypoints3d = np.hstack((keypoints3d, np.ones((keypoints3d.shape[0], 1))))
|
||
kcam = np.hstack([keypoints3d[:, :3], np.ones((keypoints3d.shape[0], 1))]) @ P.T
|
||
kcam = kcam[:, :]/kcam[:, 2:]
|
||
k2d = np.hstack((kcam, (keypoints3d[:, 3:]>0.)&(kcam[:, 2:] >0.1)))
|
||
from ..estimator.wrapper_base import bbox_from_keypoints
|
||
bbox = bbox_from_keypoints(k2d)
|
||
return k2d, bbox
|
||
|
||
def __call__(self, images, results, cameras, basename):
|
||
imgsout = {}
|
||
cams = list(images.keys())
|
||
for nv, cam in enumerate(cams):
|
||
img = images[cam]
|
||
outname = join(self.out, basename+'_' + cam +'.jpg')
|
||
# K可能缩放过了,所以需要重新计算
|
||
P = cameras[cam]['K'] @ cameras[cam]['RT']
|
||
for pid, info in results.items():
|
||
keypoints3d = info['keypoints3d']
|
||
k2d, bbox = self.repro(P, keypoints3d, img)
|
||
lw = int(max(bbox[2] - bbox[0], bbox[3] - bbox[1])/50)
|
||
# plot_bbox(img, bbox, pid=pid, vis_id=pid)
|
||
plot_keypoints_auto(img, k2d, pid=pid, use_limb_color=False)
|
||
imgsout[outname] = img
|
||
if self.merge == 'none':
|
||
for outname, img in imgsout.items():
|
||
cv2.imwrite(outname, img)
|
||
else:
|
||
outname = join(self.out, basename+'.jpg')
|
||
out = merge(list(imgsout.values()), square=True)
|
||
cv2.imwrite(outname, out)
|
||
|
||
class Outputs:
|
||
def __init__(self, out, mode, backend, scene={}) -> None:
|
||
self.out = out
|
||
os.makedirs(self.out, exist_ok=True)
|
||
from .render_func import get_render_func, get_ext
|
||
self.render_func = get_render_func(mode, backend)
|
||
self.mode = mode
|
||
self.ext = get_ext(mode)
|
||
self.extra_mesh = {}
|
||
self.scene = []
|
||
for key, val in scene.items():
|
||
mesh = load_object(val.module, val.args)
|
||
log('[vis] Load extra mesh {}'.format(key))
|
||
self.scene.append(mesh)
|
||
|
||
def __call__(self, images, results, cameras, basename):
|
||
for i, mesh in enumerate(self.scene):
|
||
results[10000+i] = mesh
|
||
render_results = self.render_func(images, results, cameras, self.extra_mesh)
|
||
output = merge(list(render_results.values()), square=True)
|
||
if output.shape[0] > 10000:
|
||
scale = 5000./output.shape[0]
|
||
output = cv2.resize(output, None, fx=scale, fy=scale)
|
||
outname = join(self.out, basename+self.ext)
|
||
imwrite(outname, output)
|
||
return 0
|
||
|
||
class MIOutputs(Outputs):
|
||
def __init__(self, out, mode, backend, merge=True, scene={}) -> None:
|
||
super().__init__(out, mode, backend, scene=scene)
|
||
self.merge = merge
|
||
|
||
def __call__(self, images, results, cameras, basename):
|
||
# 传个subs进来
|
||
# save the results to individual folders
|
||
subs = list(images.keys())
|
||
outputs = {}
|
||
for nv, sub in enumerate(subs):
|
||
if sub in results.keys():
|
||
result = results[sub]
|
||
# consider the case that the result is not in the results
|
||
# especially in multiview results and render novel view
|
||
else:
|
||
result = results
|
||
value0 = list(result.values())[0]
|
||
if 'vertices' not in value0.keys():
|
||
continue
|
||
for i, mesh in enumerate(self.scene):
|
||
result[10000+i] = mesh
|
||
if self.mode == 'instance-mask' or self.mode == 'instance-depth':
|
||
# TODO: use depth to render instance mask and consider occlusion
|
||
for pid, value in result.items():
|
||
if 'vertices' not in value.keys():
|
||
print('[ERROR] vis view {}, pid {}'.format(sub, pid))
|
||
output = self.render_func({sub:images[sub]},
|
||
{pid:value},
|
||
{sub:cameras[sub]}, self.extra_mesh)
|
||
outname = join(self.out, sub, basename + '_{}'.format(pid) + self.ext)
|
||
imwrite(outname, output[sub])
|
||
continue
|
||
else:
|
||
output = self.render_func({sub:images[sub]},
|
||
result,
|
||
{sub:cameras[sub]}, self.extra_mesh)
|
||
outputs[sub] = output[sub]
|
||
if len(outputs.keys()) == 0:
|
||
return 0
|
||
if self.merge:
|
||
outname = join(self.out, basename + self.ext)
|
||
outputs = merge(list(outputs.values()), square=True)
|
||
imwrite(outname, outputs)
|
||
else:
|
||
for nv, sub in enumerate(subs):
|
||
if sub not in outputs.keys():continue
|
||
outname = join(self.out, sub, basename + self.ext)
|
||
imwrite(outname, outputs[sub])
|
||
return 0 |