EasyMocap/apps/demo/mocap.py

401 lines
18 KiB
Python
Raw Normal View History

2022-08-21 16:11:59 +08:00
import os
from os.path import exists
from os.path import join
from easymocap.config import Config, CfgNode
from glob import glob
from easymocap.mytools.debug_utils import run_cmd, check_exists, myerror, log, mywarn
def check_image(path):
if not check_exists(join(path, 'images')):
mywarn('Images not found in {}'.format(path))
if exists(join(path, 'videos')):
cmd = 'python3 apps/preprocess/extract_image.py {}'.format(path)
run_cmd(cmd)
def check_camera(path, mode):
if mode == 'scan':
return 0
if not os.path.exists(join(path, 'intri.yml')) or \
not os.path.exists(join(path, 'extri.yml')):
myerror('[error] No camera calibration found in {}'.format(path))
raise FileNotFoundError
def format_subs(subs):
subs = ', '.join(list(map(lambda x:"'{}'".format(x), subs)))
subs = f'''"[{subs}]"'''
return subs
def mocap_demo(path, mode, exp=None):
# check images
check_image(path)
# check camera
check_camera(path, mode)
# run triangulation
if mode in ['object3d']:
dir_k3d = join(path, 'output-object3d')
else:
dir_k3d = join(path, 'output-keypoints3d')
if not check_exists(join(dir_k3d, 'keypoints3d')) or args.restart_mocap:
if 'half' in mode:
cfg_data = 'config/recon/mv1p.yml'
cfg_exp = 'config/recon/mv1p-half.yml'
elif mode == 'object3d':
cfg_data = 'config/recon/mvobj.yml'
cfg_exp = 'config/recon/tri-mvobj.yml'
elif mode.startswith('smpl-3d-mp-wild'):
cfg_data = 'config/recon/mvmp.yml'
cfg_exp = 'config/recon/mvmp-wild.yml'
elif args.mp:
# In this mode, we just perform triangulation on matched 3d keypoints
cfg_data = 'config/recon/mvmp.yml'
cfg_exp = 'config/recon/mvmp-match.yml'
else:
cfg_data = 'config/recon/mv1p.yml'
cfg_exp = 'config/recon/mv1p-total.yml'
opt_data = f'args.path {path} args.out {dir_k3d}'
if args.subs is not None:
opt_data += ' args.subs {}'.format(args.subs)
if args.subs_vis is not None:
opt_data += ' args.subs_vis {}'.format(format_subs(args.subs_vis))
if args.disable_visdetec:
opt_data += ' args.writer.visdetect.enable False'
if args.vismatch:
opt_data += ' args.writer.vismatch.enable True'
if args.disable_visrepro:
opt_data += ' args.writer.visrepro.enable False'
if args.disable_crop:
opt_data += ' args.writer.vismatch.crop False args.writer.visdetect.crop False '
if args.ranges is not None:
opt_data += ' args.ranges {},{},{}'.format(*args.ranges)
# config for experiment
opt_exp = ' args.debug {}'.format('True' if args.debug else 'False')
cmd = 'python3 apps/fit/triangulate1p.py --cfg_data {cfg_data} --opt_data {opt_data} --cfg_exp {cfg_exp} --opt_exp {opt_exp}'.format(
cfg_data=cfg_data,
cfg_exp=cfg_exp,
opt_data=opt_data,
opt_exp=opt_exp
)
run_cmd(cmd)
# compose videos
cmd = f'python3 -m easymocap.visualize.ffmpeg_wrapper {dir_k3d}/match --fps 50'
run_cmd(cmd)
# TODO: check triangulation
# run reconstruction
if mode in ['object3d']:
return 0
exp = mode if exp is None else exp
if not check_exists(join(path, 'output-{}'.format(exp), 'smpl')) or args.restart:
# load config
config = config_dict[args.mode]
cfg_data = config.data
cfg_model = config.model
cfg_exp = config.exp
_config_data = Config.load(cfg_data)
cmd = f'python3 apps/fit/fit.py --cfg_model {cfg_model} --cfg_data {cfg_data} --cfg_exp {cfg_exp}'
# opt data
output = join(path, 'output-{}'.format(exp))
opt_data = ['args.path', path, 'args.out', output]
opt_data += args.opt_data
opt_data += config.get('opt_data', [])
if 'camera' in _config_data.args.keys():
opt_data.extend(['args.camera', path])
if args.ranges is not None:
opt_data.extend(['args.ranges', '{},{},{}'.format(*args.ranges)])
if args.subs is not None:
opt_data.extend(["args.subs", "{}".format(args.subs)])
if args.disable_vismesh:
opt_data += ['args.writer.render.enable', 'False']
if args.vis_scale is not None:
opt_data += ['args.writer.render.scale', '{}'.format(args.vis_scale)]
if args.vis_mode is not None:
opt_data += ['args.writer.render.mode', args.vis_mode]
if args.pids is not None and args.mp:
opt_data += ['args.pids', ','.join(map(str, args.pids))]
cmd += ' --opt_data "{}"'.format('" "'.join(opt_data))
# opt model
opt_model = config.get('opt_model', [])
if len(opt_model) > 0:
cmd += ' --opt_model "{}"'.format('" "'.join(opt_model))
# opt exp
opt_exp = ['args.monitor.printloss', "True"] + args.opt_exp
opt_exp += config.get('opt_exp', [])
if len(opt_exp) > 0:
cmd += ' --opt_exp "{}"'.format('" "'.join(opt_exp))
log(cmd.replace(output, '${output}').replace(path, '${data}'))
run_cmd(cmd)
videoname = join(path, 'output-{}'.format(exp), 'smplmesh.mp4')
if not exists(videoname) or args.restart:
cmd = 'python3 -m easymocap.visualize.ffmpeg_wrapper {data}/output-{exp}/smplmesh --fps 50'.format(
data=path, exp=exp
)
run_cmd(cmd)
def mono_demo(path, mode, exp=None):
check_image(path)
# check cameras
if not os.path.exists(join(path, 'intri.yml')):
cmd = f'python3 apps/calibration/create_blank_camera.py {path}'
run_cmd(cmd)
# run reconstruction
exp = mode if exp is None else exp
if args.subs is None:
args.subs = sorted(os.listdir(join(path, 'images')))
for sub in args.subs:
outdir = join(path, 'output-{}'.format(exp), 'smplmesh')
videoname = join(outdir, sub+'.mp4')
if os.path.exists(videoname) and not args.restart:
continue
# load config
config = config_dict[mode]
cfg_data = config.data
cfg_model = config.model
cfg_exp = config.exp
cmd = f'python3 apps/fit/fit.py --cfg_model {cfg_model} --cfg_data {cfg_data} --cfg_exp {cfg_exp}'
_config_data = Config.load(cfg_data)
# opt data
output = join(path, 'output-{}'.format(exp))
opt_data = ['args.path', path, 'args.out', output, 'args.subs', format_subs([sub]).replace('"', '')]
opt_data += args.opt_data
opt_data += config.get('opt_data', [])
if 'camera' in _config_data.args.keys():
opt_data.extend(['args.camera', path])
if args.ranges is not None:
opt_data.extend(['args.ranges', '{},{},{}'.format(*args.ranges)])
if args.vis_scale is not None:
opt_data += ['args.writer.render.scale', '{}'.format(args.vis_scale)]
if args.vis_mode is not None:
opt_data += ['args.writer.render.mode', args.vis_mode]
if args.pids is not None and args.mp:
opt_data += ['args.pids', ','.join(map(str, args.pids))]
if args.render_side:
opt_data += ['args.writer.render.mode', "left"]
cmd += ' --opt_data "{}"'.format('" "'.join(opt_data))
# opt model
opt_model = config.get('opt_model', [])
if len(opt_model) > 0:
cmd += ' --opt_model "{}"'.format('" "'.join(opt_model))
# opt exp
opt_exp = [] + args.opt_exp
if args.debug:
opt_exp.extend(['args.monitor.printloss', "True", 'args.monitor.check', 'True'])
opt_exp += config.get('opt_exp', [])
if len(opt_exp) > 0:
cmd += ' --opt_exp "{}"'.format('" "'.join(opt_exp))
log(cmd.replace(output, '${output}').replace(path, '${data}'))
run_cmd(cmd)
cmd = 'python3 -m easymocap.visualize.ffmpeg_wrapper {data}/output-{exp}/smplmesh/{sub} --fps {fps}'.format(
data=path, exp=exp, sub=sub, fps=30
)
run_cmd(cmd)
def run_triangulation(cfg_data, cfg_exp, path, out, args):
opt_data = f'args.path {path} args.out {out}'
if args.subs is not None:
opt_data += ' args.subs "{}"'.format(format_subs(args.subs).replace('"', ''))
if args.subs_vis is not None:
opt_data += ' args.subs_vis {}'.format(format_subs(args.subs_vis))
if args.pids is not None and 'mp' in args.work:
opt_data += ' args.pids ' + ','.join(map(str, args.pids))
if args.disable_visdetec:
opt_data += ' args.writer.visdetect.enable False'
if args.vismatch:
opt_data += ' args.writer.vismatch.enable True'
if args.disable_visrepro:
2022-10-25 20:06:04 +08:00
opt_data += ' args.writer.visrepro.enable False'
2022-08-21 16:11:59 +08:00
if args.disable_crop:
opt_data += ' args.writer.vismatch.crop False args.writer.visdetect.crop False '
2022-10-25 20:06:04 +08:00
if args.vis_scale is not None:
opt_data += ' args.writer.visrepro.scale {}'.format(args.vis_scale)
opt_data += ' args.writer.visdetect.scale {}'.format(args.vis_scale)
opt_data += ' args.writer.vismatch.scale {}'.format(args.vis_scale)
2022-08-21 16:11:59 +08:00
if args.ranges is not None:
opt_data += ' args.ranges {},{},{}'.format(*args.ranges)
# config for experiment
opt_exp = ' args.debug {}'.format('True' if args.debug else 'False')
2022-10-25 20:06:04 +08:00
if args.triangulator_min_views is not None:
opt_exp += ' args.config.keypoints2d.min_view {view}'.format(view=args.triangulator_min_views)
2022-08-21 16:11:59 +08:00
cmd = 'python3 apps/fit/triangulate1p.py --cfg_data {cfg_data} --opt_data {opt_data} --cfg_exp {cfg_exp} --opt_exp {opt_exp}'.format(
cfg_data=cfg_data,
cfg_exp=cfg_exp,
opt_data=opt_data,
opt_exp=opt_exp
)
run_cmd(cmd)
cmd = f'python3 -m easymocap.visualize.ffmpeg_wrapper {out}/match --fps {args.fps}'
run_cmd(cmd)
def append_mocap_flags(path, output, cfg_data, cfg_model, cfg_exp, config, args):
cmd = f'python3 apps/fit/fit.py --cfg_model {cfg_model} --cfg_data {cfg_data} --cfg_exp {cfg_exp}'
_config_data = Config.load(cfg_data)
# opt data
opt_data = ['args.path', path, 'args.out', output]
if args.subs is not None:
opt_data.extend(['args.subs', format_subs(args.subs).replace('"', '')])
if args.subs_vis is not None:
opt_data.extend(['args.subs_vis', format_subs(args.subs_vis).replace('"', '')])
opt_data += args.opt_data
opt_data += config.get('opt_data', [])
if 'camera' in _config_data.args.keys():
opt_data.extend(['args.camera', path])
if args.ranges is not None:
opt_data.extend(['args.ranges', '{},{},{}'.format(*args.ranges)])
if args.disable_vismesh:
opt_data += ['args.writer.render.enable', 'False']
if args.vis_scale is not None:
opt_data += ['args.writer.render.scale', '{}'.format(args.vis_scale)]
if args.vis_mode is not None:
opt_data += ['args.writer.render.mode', args.vis_mode]
if args.disable_vismesh:
opt_data += ['args.writer.render.enable', 'False']
if args.pids is not None:
opt_data += ['args.pids', ','.join(map(str, args.pids))]
if len(args.pids) == 1:
opt_data[-1] += ','
if args.render_side:
opt_data += ['args.writer.render.mode', "left"]
cmd += ' --opt_data "{}"'.format('" "'.join(opt_data))
# opt model
opt_model = config.get('opt_model', [])
if len(opt_model) > 0:
cmd += ' --opt_model "{}"'.format('" "'.join(opt_model))
# opt exp
opt_exp = [] + args.opt_exp
if args.debug:
opt_exp.extend(['args.monitor.printloss', "True", 'args.monitor.check', 'True'])
opt_exp += config.get('opt_exp', [])
if len(opt_exp) > 0:
cmd += ' --opt_exp "{}"'.format('" "'.join(opt_exp))
run_cmd(cmd)
outdir = join(output, 'smplmesh')
filenames = os.listdir(outdir)
filenames_ = [i for i in filenames if i.endswith('.jpg') and os.path.isfile(join(outdir, i))]
subs_ = [i for i in filenames if os.path.isdir(join(outdir, i))]
if len(filenames_) == 0:
# try to find sub-folders
if args.subs is not None:
subs_ = args.subs
for sub in subs_:
cmd = f'python3 -m easymocap.visualize.ffmpeg_wrapper {output}/smplmesh/{sub} --fps {args.fps}'
run_cmd(cmd)
else:
cmd = f'python3 -m easymocap.visualize.ffmpeg_wrapper {output}/smplmesh --fps {args.fps}'
run_cmd(cmd)
return cmd
def workflow(work, args):
if not os.path.exists(join(args.path, 'images')):
mywarn('Images not exists, extract it use default setting')
cmd = f'python3 apps/preprocess/extract_image.py {args.path}'
run_cmd(cmd)
workflow_dict = Config.load('config/mocap_workflow.yml')
for filename in glob(join('config', 'mocap_workflow_*.yml')):
dict_ = Config.load(filename)
workflow_dict.update(dict_)
workflow = workflow_dict[work]
for key_work in ['subs', 'pids']:
if key_work in workflow.keys():
if key_work == 'subs':
args.subs = workflow[key_work]
elif key_work == 'pids':
args.pids = workflow[key_work]
exp = work if args.exp is None else args.exp
if 'extract_keypoints' in workflow.keys() and not args.skip_detect:
if isinstance(workflow['extract_keypoints'], str):
cmd = workflow['extract_keypoints'].replace('${data}', args.path)
run_cmd(cmd)
else:
pass
if 'calibration' in workflow.keys() and workflow['calibration'] != 'none':
cmd = workflow['calibration'].replace('${data}', args.path)
run_cmd(cmd)
# check triangulation
if 'triangulation' in workflow.keys():
cfg_data = workflow.triangulation.data
cfg_exp = workflow.triangulation.exp
out = join(args.path, workflow.triangulation.out)
# check output
if not args.restart_mocap and os.path.exists(join(out, 'keypoints3d')) and len(os.listdir(join(out, 'keypoints3d'))) > 10:
log('[Skip] Triangulation already done, skipping...')
else:
run_triangulation(cfg_data, cfg_exp, args.path, out, args)
if 'fit' in workflow.keys():
if isinstance(workflow.fit, str):
workflow.fit = config_dict[workflow.fit]
cfg_data = workflow.fit.data
cfg_model = workflow.fit.model
cfg_exp = workflow.fit.exp
# check output
path = args.path
if 'output' in workflow.keys():
output = join(args.path, workflow.output)
else:
output = join(args.path, 'output-{}'.format(exp))
append_mocap_flags(path, output, cfg_data, cfg_model, cfg_exp, workflow.fit, args)
if 'postprocess' in workflow.keys():
for key, cmd in workflow.postprocess.items():
2022-10-25 20:06:04 +08:00
cmd = cmd.replace('${data}', args.path).replace('${exp}', args.exp)
if '${subs_vis}' in cmd:
cmd = cmd.replace('${subs_vis}', ' '.join(args.subs_vis))
if '${vis_scale}' in cmd:
cmd = cmd.replace('${vis_scale}', '{}'.format(args.vis_scale))
2022-08-21 16:11:59 +08:00
run_cmd(cmd)
if __name__ == '__main__':
config_dict = Config.load('config/mocap_index.yml')
modes = list(config_dict.keys())
usage = '''This script helps you to motion capture from multiple views.
The following modes are supported:
'''
for mode, config in config_dict.items():
usage += '\t{:20s}: {}\n'.format(mode, config.comment)
import argparse
parser = argparse.ArgumentParser(usage=usage)
parser.add_argument('--work', type=str, default=None,
help='This is the most top abstract of the workflow')
parser.add_argument('path', type=str)
parser.add_argument('--num', type=int, default=1)
parser.add_argument('--fps', type=int, default=50)
parser.add_argument('--ranges', type=int, default=None, nargs=3)
parser.add_argument('--pids', type=int, default=None, nargs='+')
parser.add_argument('--vis_scale', type=float, default=None)
parser.add_argument('--vis_mode', type=str, default=None)
parser.add_argument('--subs', type=str, default=None, nargs='+')
parser.add_argument('--subs_vis', type=str, default=None, nargs='+')
parser.add_argument('--mode', type=str, default='smpl-3d')
2022-10-25 20:06:04 +08:00
parser.add_argument('--exp', type=str, default='output-smpl-3d')
2022-08-21 16:11:59 +08:00
parser.add_argument('--opt_data', type=str, default=[], nargs='+')
parser.add_argument('--opt_exp', type=str, default=[], nargs='+')
parser.add_argument('--debug', action='store_true')
parser.add_argument('--mono', action='store_true')
parser.add_argument('--mp', action='store_true', help='use multi-person')
parser.add_argument('--skip_detect', action='store_true')
parser.add_argument('--restart_mocap', action='store_true')
parser.add_argument('--restart', action='store_true')
parser.add_argument('--bodyonly', action='store_true')
parser.add_argument('--disable_visdetec', action='store_true')
parser.add_argument('--vismatch', action='store_true')
parser.add_argument('--render_side', action='store_true',
help='render the mesh on the right')
parser.add_argument('--disable_visrepro', action='store_true')
parser.add_argument('--disable_vismesh', action='store_true')
parser.add_argument('--disable_crop', action='store_true')
2022-10-25 20:06:04 +08:00
parser.add_argument('--triangulator_min_views', type=int, default=None)
2022-08-21 16:11:59 +08:00
args = parser.parse_args()
if args.work is not None:
workflow(args.work, args)
exit()
if args.mono:
mono_demo(args.path, mode='mono-'+args.mode, exp=args.exp)
else:
if args.subs is not None:
args.subs = format_subs(args.subs)
mocap_demo(args.path, mode=args.mode, exp=args.exp)