Init commit
This commit is contained in:
+17
@@ -0,0 +1,17 @@
|
||||
__pycache__/
|
||||
common/utils/human_model_files
|
||||
pretrained_models/
|
||||
dataset
|
||||
output
|
||||
.vscode
|
||||
*.npy
|
||||
*.tar
|
||||
main/vis
|
||||
main/vis*
|
||||
tool/AGORA/vis*
|
||||
tool/SHAPY/vis*
|
||||
demo/
|
||||
*egg-info/
|
||||
.idea/
|
||||
data/SHAPY/mesh-mesh-intersection/build/
|
||||
data/SHAPY/mesh-mesh-intersection/mesh_mesh_intersect_cuda.cpython-38-x86_64-linux-gnu.so
|
||||
@@ -0,0 +1,9 @@
|
||||
S-Lab License 1.0
|
||||
|
||||
Copyright 2022 S-Lab
|
||||
Redistribution and use for non-commercial purpose in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
4. In the event that redistribution and/or use for commercial purpose in source or binary forms, with or without modification is required, please contact the contributor(s) of the work.
|
||||
@@ -1 +1,93 @@
|
||||
# SMPLer-X
|
||||
|
||||
## Install
|
||||
```bash
|
||||
conda create -n smplerx python=3.8 -y
|
||||
conda activate smplerx
|
||||
conda install pytorch==1.12.0 torchvision==0.13.0 torchaudio==0.12.0 cudatoolkit=11.3 -c pytorch -y
|
||||
wget http://download.openmmlab.sensetime.com/mmcv/dist/cu113/torch1.12.0/mmcv_full-1.7.1-cp38-cp38-manylinux1_x86_64.whl
|
||||
pip install mmcv_full-1.7.1-cp38-cp38-manylinux1_x86_64.whl
|
||||
rm mmcv_full-1.7.1-cp38-cp38-manylinux1_x86_64.whl
|
||||
pip install -r requirements.txt
|
||||
|
||||
# install mmpose
|
||||
cd main/transformer_utils
|
||||
pip install -v -e .
|
||||
cd ../..
|
||||
```
|
||||
|
||||
## Preparation
|
||||
- process all datasets into [HumanData](https://github.com/open-mmlab/mmhuman3d/blob/main/docs/human_data.md) format, except the following:
|
||||
- AGORA, MSCOCO, MPII, Human3.6M, UBody
|
||||
- follow [OSX](https://github.com/IDEA-Research/OSX) in preparing pretrained ViT-Pose models.
|
||||
- download [SMPL-X](https://smpl-x.is.tue.mpg.de/) body models
|
||||
|
||||
The file structure should be like:
|
||||
```
|
||||
SMPLer-X/
|
||||
├── common/
|
||||
│ └── utils/
|
||||
│ └── human_model_files/ # body model
|
||||
├── data/
|
||||
├── main/
|
||||
├── pretrained_models/ # pretrained ViT-Pose models
|
||||
└── dataset/
|
||||
├── AGORA/
|
||||
├── ARCTIC/
|
||||
├── BEDLAM/
|
||||
├── Behave/
|
||||
├── cache/
|
||||
├── CHI3D/
|
||||
├── CrowdPose/
|
||||
├── dataset.py/
|
||||
├── EgoBody/
|
||||
├── EHF/
|
||||
├── FIT3D/
|
||||
├── GTA_Human2/
|
||||
├── Human36M/
|
||||
├── HumanSC3D/
|
||||
├── InstaVariety/
|
||||
├── LSPET/
|
||||
├── MPII/
|
||||
├── MPI_INF_3DHP/
|
||||
├── MPI_INF_3DHP_folder/
|
||||
├── MSCOCO/
|
||||
├── MTP/
|
||||
├── MuCo/
|
||||
├── OCHuman/
|
||||
├── PoseTrack/
|
||||
├── PROX/
|
||||
├── PW3D/
|
||||
├── RenBody/
|
||||
├── RICH/
|
||||
├── SHAPY/
|
||||
├── SPEC/
|
||||
├── SSP3D/
|
||||
├── SynBody/
|
||||
├── Talkshow/
|
||||
├── UBody/
|
||||
├── UP3D/
|
||||
└── preprocessed_datasets/ # HumanData files
|
||||
```
|
||||
|
||||
## Training
|
||||
```bash
|
||||
cd main
|
||||
sh slurm_train.sh {JOB_NAME} {NUM_GPU} {CONFIG_FILE}
|
||||
# logs and ckpts will be saved to ../output/train_{JOB_NAME}_{DATE_TIME}
|
||||
# config file is the file name under ./config, e.g. ./config/config_base.py
|
||||
# a copy of current config file wil be saved to ../output/train_{JOB_NAME}_{DATE_TIME}/code/config_base.py
|
||||
```
|
||||
|
||||
## Testing
|
||||
```bash
|
||||
cd main
|
||||
sh slurm_test.sh {JOB_NAME} {NUM_GPU} {TRAIN_OUTPUT_DIR} {CKPT_ID}
|
||||
# this will eval the model ../output/train_{JOB_NAME}_{DATE_TIME}/model_dump/snapshot_{CKPT_ID}.pth.tar with confing ../output/train_{JOB_NAME}_{DATE_TIME}/code/config_base.py
|
||||
# logs and results will be saved to ../output/test_{JOB_NAME}_{DATE_TIME}
|
||||
```
|
||||
|
||||
## References
|
||||
- [Hand4Whole](https://github.com/mks0601/Hand4Whole_RELEASE)
|
||||
- [OSX](https://github.com/IDEA-Research/OSX)
|
||||
- [MMHuman3D](https://github.com/open-mmlab/mmhuman3d)
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
import os.path as osp
|
||||
import math
|
||||
import abc
|
||||
from torch.utils.data import DataLoader
|
||||
import torch.optim
|
||||
import torchvision.transforms as transforms
|
||||
from timer import Timer
|
||||
from logger import colorlogger
|
||||
from torch.nn.parallel.data_parallel import DataParallel
|
||||
from config import cfg
|
||||
from SMPLer_X import get_model
|
||||
from dataset import MultipleDatasets
|
||||
# ddp
|
||||
import torch.distributed as dist
|
||||
from torch.utils.data import DistributedSampler
|
||||
import torch.utils.data.distributed
|
||||
from utils.distribute_utils import (
|
||||
get_rank, is_main_process, time_synchronized, get_group_idx, get_process_groups
|
||||
)
|
||||
from mmcv.runner import get_dist_info
|
||||
|
||||
# dynamic dataset import
|
||||
for i in range(len(cfg.trainset_3d)):
|
||||
exec('from ' + cfg.trainset_3d[i] + ' import ' + cfg.trainset_3d[i])
|
||||
for i in range(len(cfg.trainset_2d)):
|
||||
exec('from ' + cfg.trainset_2d[i] + ' import ' + cfg.trainset_2d[i])
|
||||
for i in range(len(cfg.trainset_humandata)):
|
||||
exec('from ' + cfg.trainset_humandata[i] + ' import ' + cfg.trainset_humandata[i])
|
||||
exec('from ' + cfg.testset + ' import ' + cfg.testset)
|
||||
|
||||
|
||||
class Base(object):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def __init__(self, log_name='logs.txt'):
|
||||
self.cur_epoch = 0
|
||||
|
||||
# timer
|
||||
self.tot_timer = Timer()
|
||||
self.gpu_timer = Timer()
|
||||
self.read_timer = Timer()
|
||||
|
||||
# logger
|
||||
self.logger = colorlogger(cfg.log_dir, log_name=log_name)
|
||||
|
||||
@abc.abstractmethod
|
||||
def _make_batch_generator(self):
|
||||
return
|
||||
|
||||
@abc.abstractmethod
|
||||
def _make_model(self):
|
||||
return
|
||||
|
||||
|
||||
class Trainer(Base):
|
||||
def __init__(self, distributed=False, gpu_idx=None):
|
||||
super(Trainer, self).__init__(log_name='train_logs.txt')
|
||||
self.distributed = distributed
|
||||
self.gpu_idx = gpu_idx
|
||||
|
||||
def get_optimizer(self, model):
|
||||
normal_param = []
|
||||
special_param = []
|
||||
for module in model.module.special_trainable_modules:
|
||||
special_param += list(module.parameters())
|
||||
# print(module)
|
||||
for module in model.module.trainable_modules:
|
||||
normal_param += list(module.parameters())
|
||||
# self.logger.info(f"N-{self.gpu_idx}, {normal_param}")
|
||||
# self.logger.info("S", special_param)
|
||||
optim_params = [
|
||||
{ # add normal params first
|
||||
'params': normal_param,
|
||||
'lr': cfg.lr
|
||||
},
|
||||
{
|
||||
'params': special_param,
|
||||
'lr': cfg.lr * cfg.lr_mult
|
||||
},
|
||||
]
|
||||
optimizer = torch.optim.Adam(optim_params, lr=cfg.lr)
|
||||
return optimizer
|
||||
|
||||
def save_model(self, state, epoch):
|
||||
file_path = osp.join(cfg.model_dir, 'snapshot_{}.pth.tar'.format(str(epoch)))
|
||||
|
||||
# do not save smplx layer weights
|
||||
dump_key = []
|
||||
for k in state['network'].keys():
|
||||
if 'smplx_layer' in k:
|
||||
dump_key.append(k)
|
||||
for k in dump_key:
|
||||
state['network'].pop(k, None)
|
||||
|
||||
torch.save(state, file_path)
|
||||
self.logger.info("Write snapshot into {}".format(file_path))
|
||||
|
||||
def load_model(self, model, optimizer):
|
||||
if cfg.pretrained_model_path is not None:
|
||||
ckpt_path = cfg.pretrained_model_path
|
||||
ckpt = torch.load(ckpt_path, map_location=torch.device('cpu')) # solve CUDA OOM error in DDP
|
||||
model.load_state_dict(ckpt['network'], strict=False)
|
||||
self.logger.info('Load checkpoint from {}'.format(ckpt_path))
|
||||
if not hasattr(cfg, 'start_over') or cfg.start_over:
|
||||
start_epoch = 0
|
||||
else:
|
||||
optimizer.load_state_dict(ckpt['optimizer'])
|
||||
start_epoch = ckpt['epoch'] + 1
|
||||
self.logger.info(f'Load optimizer, start from{start_epoch}')
|
||||
else:
|
||||
start_epoch = 0
|
||||
|
||||
return start_epoch, model, optimizer
|
||||
|
||||
def get_lr(self):
|
||||
for g in self.optimizer.param_groups:
|
||||
cur_lr = g['lr']
|
||||
return cur_lr
|
||||
|
||||
def _make_batch_generator(self):
|
||||
# data load and construct batch generator
|
||||
self.logger_info("Creating dataset...")
|
||||
trainset3d_loader = []
|
||||
for i in range(len(cfg.trainset_3d)):
|
||||
trainset3d_loader.append(eval(cfg.trainset_3d[i])(transforms.ToTensor(), "train"))
|
||||
trainset2d_loader = []
|
||||
for i in range(len(cfg.trainset_2d)):
|
||||
trainset2d_loader.append(eval(cfg.trainset_2d[i])(transforms.ToTensor(), "train"))
|
||||
trainset_humandata_loader = []
|
||||
for i in range(len(cfg.trainset_humandata)):
|
||||
trainset_humandata_loader.append(eval(cfg.trainset_humandata[i])(transforms.ToTensor(), "train"))
|
||||
|
||||
data_strategy = getattr(cfg, 'data_strategy', None)
|
||||
if data_strategy == 'concat':
|
||||
print("Using [concat] strategy...")
|
||||
trainset_loader = MultipleDatasets(trainset3d_loader + trainset2d_loader + trainset_humandata_loader,
|
||||
make_same_len=False, verbose=True)
|
||||
elif data_strategy == 'balance':
|
||||
total_len = getattr(cfg, 'total_data_len', 'auto')
|
||||
print(f"Using [balance] strategy with total_data_len : {total_len}...")
|
||||
trainset_loader = MultipleDatasets(trainset3d_loader + trainset2d_loader + trainset_humandata_loader,
|
||||
make_same_len=True, total_len=total_len, verbose=True)
|
||||
else:
|
||||
# original strategy implementation
|
||||
valid_loader_num = 0
|
||||
if len(trainset3d_loader) > 0:
|
||||
trainset3d_loader = [MultipleDatasets(trainset3d_loader, make_same_len=False)]
|
||||
valid_loader_num += 1
|
||||
else:
|
||||
trainset3d_loader = []
|
||||
if len(trainset2d_loader) > 0:
|
||||
trainset2d_loader = [MultipleDatasets(trainset2d_loader, make_same_len=False)]
|
||||
valid_loader_num += 1
|
||||
else:
|
||||
trainset2d_loader = []
|
||||
if len(trainset_humandata_loader) > 0:
|
||||
trainset_humandata_loader = [MultipleDatasets(trainset_humandata_loader, make_same_len=False)]
|
||||
valid_loader_num += 1
|
||||
|
||||
if valid_loader_num > 1:
|
||||
trainset_loader = MultipleDatasets(trainset3d_loader + trainset2d_loader + trainset_humandata_loader, make_same_len=True)
|
||||
else:
|
||||
trainset_loader = MultipleDatasets(trainset3d_loader + trainset2d_loader + trainset_humandata_loader, make_same_len=False)
|
||||
|
||||
self.itr_per_epoch = math.ceil(len(trainset_loader) / cfg.num_gpus / cfg.train_batch_size)
|
||||
|
||||
if self.distributed:
|
||||
self.logger_info(f"Total data length {len(trainset_loader)}.")
|
||||
rank, world_size = get_dist_info()
|
||||
self.logger_info("Using distributed data sampler.")
|
||||
|
||||
sampler_train = DistributedSampler(trainset_loader, world_size, rank, shuffle=True)
|
||||
self.batch_generator = DataLoader(dataset=trainset_loader, batch_size=cfg.train_batch_size,
|
||||
shuffle=False, num_workers=cfg.num_thread, sampler=sampler_train,
|
||||
pin_memory=True, persistent_workers=True if cfg.num_thread > 0 else False, drop_last=True)
|
||||
else:
|
||||
self.batch_generator = DataLoader(dataset=trainset_loader, batch_size=cfg.num_gpus * cfg.train_batch_size,
|
||||
shuffle=True, num_workers=cfg.num_thread,
|
||||
pin_memory=True, drop_last=True)
|
||||
|
||||
def _make_model(self):
|
||||
# prepare network
|
||||
self.logger_info("Creating graph and optimizer...")
|
||||
model = get_model('train')
|
||||
|
||||
if getattr(cfg, 'fine_tune', None) == 'backbone':
|
||||
print("Fine-tuning [backbone]...")
|
||||
for module in model.head:
|
||||
for param in module.parameters():
|
||||
param.requires_grad = False
|
||||
for module in model.neck:
|
||||
for param in module.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
elif getattr(cfg, 'fine_tune', None) == 'neck_and_head':
|
||||
print("Fine-tuning [neck and head]...")
|
||||
for param in model.encoder.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
elif getattr(cfg, 'fine_tune', None) == 'head':
|
||||
print("Fine-tuning [head]...")
|
||||
for param in model.encoder.parameters():
|
||||
param.requires_grad = False
|
||||
for module in model.neck:
|
||||
for param in module.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
|
||||
# ddp
|
||||
if self.distributed:
|
||||
self.logger_info("Using distributed data parallel.")
|
||||
model.cuda()
|
||||
if hasattr(cfg, 'syncbn') and cfg.syncbn:
|
||||
self.logger_info("Using sync batch norm layers.")
|
||||
|
||||
process_groups = get_process_groups()
|
||||
process_group = process_groups[get_group_idx()]
|
||||
syncbn_model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model, process_group)
|
||||
model = torch.nn.parallel.DistributedDataParallel(
|
||||
syncbn_model, device_ids=[self.gpu_idx],
|
||||
find_unused_parameters=True)
|
||||
else:
|
||||
model = torch.nn.parallel.DistributedDataParallel(
|
||||
model, device_ids=[self.gpu_idx],
|
||||
find_unused_parameters=True)
|
||||
else:
|
||||
# dp
|
||||
model = DataParallel(model).cuda()
|
||||
|
||||
optimizer = self.get_optimizer(model)
|
||||
|
||||
if hasattr(cfg, "scheduler"):
|
||||
if cfg.scheduler == 'cos':
|
||||
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, cfg.end_epoch * self.itr_per_epoch,
|
||||
eta_min=1e-6)
|
||||
elif cfg.scheduler == 'step':
|
||||
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, cfg.step_size, gamma=cfg.gamma,
|
||||
last_epoch=- 1, verbose=False)
|
||||
|
||||
else:
|
||||
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, cfg.end_epoch * self.itr_per_epoch,
|
||||
eta_min=getattr(cfg,'min_lr',1e-6))
|
||||
if cfg.continue_train:
|
||||
if self.distributed:
|
||||
start_epoch, model, optimizer = self.load_model(model, optimizer)
|
||||
else:
|
||||
start_epoch, model, optimizer = self.load_model(model, optimizer)
|
||||
else:
|
||||
start_epoch = 0
|
||||
model.train()
|
||||
|
||||
self.scheduler = scheduler
|
||||
self.start_epoch = start_epoch
|
||||
self.model = model
|
||||
self.optimizer = optimizer
|
||||
|
||||
def logger_info(self, info):
|
||||
if self.distributed:
|
||||
if is_main_process():
|
||||
self.logger.info(info)
|
||||
else:
|
||||
self.logger.info(info)
|
||||
|
||||
|
||||
class Tester(Base):
|
||||
def __init__(self, test_epoch=None):
|
||||
if test_epoch is not None:
|
||||
self.test_epoch = int(test_epoch)
|
||||
super(Tester, self).__init__(log_name='test_logs.txt')
|
||||
|
||||
def _make_batch_generator(self):
|
||||
# data load and construct batch generator
|
||||
self.logger.info("Creating dataset...")
|
||||
testset_loader = eval(cfg.testset)(transforms.ToTensor(), "test")
|
||||
batch_generator = DataLoader(dataset=testset_loader, batch_size=cfg.num_gpus * cfg.test_batch_size,
|
||||
shuffle=False, num_workers=cfg.num_thread, pin_memory=True)
|
||||
|
||||
self.testset = testset_loader
|
||||
self.batch_generator = batch_generator
|
||||
|
||||
def _make_model(self):
|
||||
self.logger.info('Load checkpoint from {}'.format(cfg.pretrained_model_path))
|
||||
|
||||
# prepare network
|
||||
self.logger.info("Creating graph...")
|
||||
model = get_model('test')
|
||||
model = DataParallel(model).cuda()
|
||||
if not getattr(cfg, 'random_init', False):
|
||||
ckpt = torch.load(cfg.pretrained_model_path, map_location=torch.device('cpu'))
|
||||
|
||||
from collections import OrderedDict
|
||||
new_state_dict = OrderedDict()
|
||||
for k, v in ckpt['network'].items():
|
||||
if 'module' not in k:
|
||||
k = 'module.' + k
|
||||
k = k.replace('backbone', 'encoder').replace('body_rotation_net', 'body_regressor').replace(
|
||||
'hand_rotation_net', 'hand_regressor')
|
||||
new_state_dict[k] = v
|
||||
self.logger.warning("Attention: Strict=False is set for checkpoint loading. Please check manually.")
|
||||
model.load_state_dict(new_state_dict, strict=False)
|
||||
model.eval()
|
||||
else:
|
||||
print('Random init!!!!!!!')
|
||||
|
||||
self.model = model
|
||||
|
||||
def _evaluate(self, outs, cur_sample_idx):
|
||||
eval_result = self.testset.evaluate(outs, cur_sample_idx)
|
||||
return eval_result
|
||||
|
||||
def _print_eval_result(self, eval_result):
|
||||
self.testset.print_eval_result(eval_result)
|
||||
|
||||
class Demoer(Base):
|
||||
def __init__(self, test_epoch=None):
|
||||
if test_epoch is not None:
|
||||
self.test_epoch = int(test_epoch)
|
||||
super(Demoer, self).__init__(log_name='test_logs.txt')
|
||||
|
||||
def _make_model(self):
|
||||
self.logger.info('Load checkpoint from {}'.format(cfg.pretrained_model_path))
|
||||
|
||||
# prepare network
|
||||
self.logger.info("Creating graph...")
|
||||
model = get_model('test')
|
||||
model = DataParallel(model).cuda()
|
||||
ckpt = torch.load(cfg.pretrained_model_path, map_location=torch.device('cpu'))
|
||||
|
||||
from collections import OrderedDict
|
||||
new_state_dict = OrderedDict()
|
||||
for k, v in ckpt['network'].items():
|
||||
if 'module' not in k:
|
||||
k = 'module.' + k
|
||||
k = k.replace('module.backbone', 'module.encoder').replace('body_rotation_net', 'body_regressor').replace(
|
||||
'hand_rotation_net', 'hand_regressor')
|
||||
new_state_dict[k] = v
|
||||
model.load_state_dict(new_state_dict, strict=False)
|
||||
model.eval()
|
||||
|
||||
self.model = model
|
||||
@@ -0,0 +1,50 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
OK = '\033[92m'
|
||||
WARNING = '\033[93m'
|
||||
FAIL = '\033[91m'
|
||||
END = '\033[0m'
|
||||
|
||||
PINK = '\033[95m'
|
||||
BLUE = '\033[94m'
|
||||
GREEN = OK
|
||||
RED = FAIL
|
||||
WHITE = END
|
||||
YELLOW = WARNING
|
||||
|
||||
class colorlogger():
|
||||
def __init__(self, log_dir, log_name='train_logs.txt'):
|
||||
# set log
|
||||
self._logger = logging.getLogger(log_name)
|
||||
self._logger.setLevel(logging.INFO)
|
||||
log_file = os.path.join(log_dir, log_name)
|
||||
if not os.path.exists(log_dir):
|
||||
os.makedirs(log_dir)
|
||||
file_log = logging.FileHandler(log_file, mode='a')
|
||||
file_log.setLevel(logging.INFO)
|
||||
console_log = logging.StreamHandler()
|
||||
console_log.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter(
|
||||
"{}%(asctime)s{} %(message)s".format(GREEN, END),
|
||||
"%m-%d %H:%M:%S")
|
||||
file_log.setFormatter(formatter)
|
||||
console_log.setFormatter(formatter)
|
||||
self._logger.addHandler(file_log)
|
||||
self._logger.addHandler(console_log)
|
||||
|
||||
def debug(self, msg):
|
||||
self._logger.debug(str(msg))
|
||||
|
||||
def info(self, msg):
|
||||
self._logger.info(str(msg))
|
||||
|
||||
def warning(self, msg):
|
||||
self._logger.warning(WARNING + 'WRN: ' + str(msg) + END)
|
||||
|
||||
def critical(self, msg):
|
||||
self._logger.critical(RED + 'CRI: ' + str(msg) + END)
|
||||
|
||||
def error(self, msg):
|
||||
self._logger.error(RED + 'ERR: ' + str(msg) + END)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import torch.nn as nn
|
||||
|
||||
def make_linear_layers(feat_dims, relu_final=True, use_bn=False):
|
||||
layers = []
|
||||
for i in range(len(feat_dims)-1):
|
||||
layers.append(nn.Linear(feat_dims[i], feat_dims[i+1]))
|
||||
|
||||
# Do not use ReLU for final estimation
|
||||
if i < len(feat_dims)-2 or (i == len(feat_dims)-2 and relu_final):
|
||||
if use_bn:
|
||||
layers.append(nn.BatchNorm1d(feat_dims[i+1]))
|
||||
layers.append(nn.ReLU(inplace=True))
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def make_conv_layers(feat_dims, kernel=3, stride=1, padding=1, bnrelu_final=True):
|
||||
layers = []
|
||||
for i in range(len(feat_dims)-1):
|
||||
layers.append(
|
||||
nn.Conv2d(
|
||||
in_channels=feat_dims[i],
|
||||
out_channels=feat_dims[i+1],
|
||||
kernel_size=kernel,
|
||||
stride=stride,
|
||||
padding=padding
|
||||
))
|
||||
# Do not use BN and ReLU for final estimation
|
||||
if i < len(feat_dims)-2 or (i == len(feat_dims)-2 and bnrelu_final):
|
||||
layers.append(nn.BatchNorm2d(feat_dims[i+1]))
|
||||
layers.append(nn.ReLU(inplace=True))
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def make_deconv_layers(feat_dims, bnrelu_final=True):
|
||||
layers = []
|
||||
for i in range(len(feat_dims)-1):
|
||||
layers.append(
|
||||
nn.ConvTranspose2d(
|
||||
in_channels=feat_dims[i],
|
||||
out_channels=feat_dims[i+1],
|
||||
kernel_size=4,
|
||||
stride=2,
|
||||
padding=1,
|
||||
output_padding=0,
|
||||
bias=False))
|
||||
|
||||
# Do not use BN and ReLU for final estimation
|
||||
if i < len(feat_dims)-2 or (i == len(feat_dims)-2 and bnrelu_final):
|
||||
layers.append(nn.BatchNorm2d(feat_dims[i+1]))
|
||||
layers.append(nn.ReLU(inplace=True))
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
class CoordLoss(nn.Module):
|
||||
def __init__(self):
|
||||
super(CoordLoss, self).__init__()
|
||||
|
||||
def forward(self, coord_out, coord_gt, valid, is_3D=None):
|
||||
loss = torch.abs(coord_out - coord_gt) * valid
|
||||
if is_3D is not None:
|
||||
loss_z = loss[:,:,2:] * is_3D[:,None,None].float()
|
||||
loss = torch.cat((loss[:,:,:2], loss_z),2)
|
||||
return loss
|
||||
|
||||
class ParamLoss(nn.Module):
|
||||
def __init__(self):
|
||||
super(ParamLoss, self).__init__()
|
||||
|
||||
def forward(self, param_out, param_gt, valid):
|
||||
loss = torch.abs(param_out - param_gt) * valid
|
||||
return loss
|
||||
|
||||
class CELoss(nn.Module):
|
||||
def __init__(self):
|
||||
super(CELoss, self).__init__()
|
||||
self.ce_loss = nn.CrossEntropyLoss(reduction='none')
|
||||
|
||||
def forward(self, out, gt_index):
|
||||
loss = self.ce_loss(out, gt_index)
|
||||
return loss
|
||||
@@ -0,0 +1,172 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.nn import functional as F
|
||||
from nets.layer import make_conv_layers, make_linear_layers, make_deconv_layers
|
||||
from utils.transforms import sample_joint_features, soft_argmax_2d, soft_argmax_3d
|
||||
from utils.human_models import smpl_x
|
||||
from config import cfg
|
||||
from mmcv.ops.roi_align import roi_align
|
||||
|
||||
class PositionNet(nn.Module):
|
||||
def __init__(self, part, feat_dim=768):
|
||||
super(PositionNet, self).__init__()
|
||||
if part == 'body':
|
||||
self.joint_num = len(smpl_x.pos_joint_part['body'])
|
||||
self.hm_shape = cfg.output_hm_shape
|
||||
elif part == 'hand':
|
||||
self.joint_num = len(smpl_x.pos_joint_part['rhand'])
|
||||
self.hm_shape = cfg.output_hand_hm_shape
|
||||
self.conv = make_conv_layers([feat_dim, self.joint_num * self.hm_shape[0]], kernel=1, stride=1, padding=0, bnrelu_final=False)
|
||||
|
||||
def forward(self, img_feat):
|
||||
joint_hm = self.conv(img_feat).view(-1, self.joint_num, self.hm_shape[0], self.hm_shape[1], self.hm_shape[2])
|
||||
joint_coord = soft_argmax_3d(joint_hm)
|
||||
joint_hm = F.softmax(joint_hm.view(-1, self.joint_num, self.hm_shape[0] * self.hm_shape[1] * self.hm_shape[2]), 2)
|
||||
joint_hm = joint_hm.view(-1, self.joint_num, self.hm_shape[0], self.hm_shape[1], self.hm_shape[2])
|
||||
return joint_hm, joint_coord
|
||||
|
||||
class HandRotationNet(nn.Module):
|
||||
def __init__(self, part, feat_dim = 768):
|
||||
super(HandRotationNet, self).__init__()
|
||||
self.part = part
|
||||
self.joint_num = len(smpl_x.pos_joint_part['rhand'])
|
||||
self.hand_conv = make_conv_layers([feat_dim, 512], kernel=1, stride=1, padding=0)
|
||||
self.hand_pose_out = make_linear_layers([self.joint_num * 515, len(smpl_x.orig_joint_part['rhand']) * 6], relu_final=False)
|
||||
self.feat_dim = feat_dim
|
||||
|
||||
def forward(self, img_feat, joint_coord_img):
|
||||
batch_size = img_feat.shape[0]
|
||||
img_feat = self.hand_conv(img_feat)
|
||||
img_feat_joints = sample_joint_features(img_feat, joint_coord_img[:, :, :2])
|
||||
feat = torch.cat((img_feat_joints, joint_coord_img), 2) # batch_size, joint_num, 512+3
|
||||
hand_pose = self.hand_pose_out(feat.view(batch_size, -1))
|
||||
return hand_pose
|
||||
|
||||
class BodyRotationNet(nn.Module):
|
||||
def __init__(self, feat_dim = 768):
|
||||
super(BodyRotationNet, self).__init__()
|
||||
self.joint_num = len(smpl_x.pos_joint_part['body'])
|
||||
self.body_conv = make_linear_layers([feat_dim, 512], relu_final=False)
|
||||
self.root_pose_out = make_linear_layers([self.joint_num * (512+3), 6], relu_final=False)
|
||||
self.body_pose_out = make_linear_layers(
|
||||
[self.joint_num * (512+3), (len(smpl_x.orig_joint_part['body']) - 1) * 6], relu_final=False) # without root
|
||||
self.shape_out = make_linear_layers([feat_dim, smpl_x.shape_param_dim], relu_final=False)
|
||||
self.cam_out = make_linear_layers([feat_dim, 3], relu_final=False)
|
||||
self.feat_dim = feat_dim
|
||||
|
||||
def forward(self, body_pose_token, shape_token, cam_token, body_joint_img):
|
||||
batch_size = body_pose_token.shape[0]
|
||||
|
||||
# shape parameter
|
||||
shape_param = self.shape_out(shape_token)
|
||||
|
||||
# camera parameter
|
||||
cam_param = self.cam_out(cam_token)
|
||||
|
||||
# body pose parameter
|
||||
body_pose_token = self.body_conv(body_pose_token)
|
||||
body_pose_token = torch.cat((body_pose_token, body_joint_img), 2)
|
||||
root_pose = self.root_pose_out(body_pose_token.view(batch_size, -1))
|
||||
body_pose = self.body_pose_out(body_pose_token.view(batch_size, -1))
|
||||
|
||||
return root_pose, body_pose, shape_param, cam_param
|
||||
|
||||
class FaceRegressor(nn.Module):
|
||||
def __init__(self, feat_dim=768):
|
||||
super(FaceRegressor, self).__init__()
|
||||
self.expr_out = make_linear_layers([feat_dim, smpl_x.expr_code_dim], relu_final=False)
|
||||
self.jaw_pose_out = make_linear_layers([feat_dim, 6], relu_final=False)
|
||||
|
||||
def forward(self, expr_token, jaw_pose_token):
|
||||
expr_param = self.expr_out(expr_token) # expression parameter
|
||||
jaw_pose = self.jaw_pose_out(jaw_pose_token) # jaw pose parameter
|
||||
return expr_param, jaw_pose
|
||||
|
||||
class BoxNet(nn.Module):
|
||||
def __init__(self, feat_dim=768):
|
||||
super(BoxNet, self).__init__()
|
||||
self.joint_num = len(smpl_x.pos_joint_part['body'])
|
||||
self.deconv = make_deconv_layers([feat_dim + self.joint_num * cfg.output_hm_shape[0], 256, 256, 256])
|
||||
self.bbox_center = make_conv_layers([256, 3], kernel=1, stride=1, padding=0, bnrelu_final=False)
|
||||
self.lhand_size = make_linear_layers([256, 256, 2], relu_final=False)
|
||||
self.rhand_size = make_linear_layers([256, 256, 2], relu_final=False)
|
||||
self.face_size = make_linear_layers([256, 256, 2], relu_final=False)
|
||||
|
||||
def forward(self, img_feat, joint_hm):
|
||||
joint_hm = joint_hm.view(joint_hm.shape[0], joint_hm.shape[1] * cfg.output_hm_shape[0], cfg.output_hm_shape[1], cfg.output_hm_shape[2])
|
||||
img_feat = torch.cat((img_feat, joint_hm), 1)
|
||||
img_feat = self.deconv(img_feat)
|
||||
|
||||
# bbox center
|
||||
bbox_center_hm = self.bbox_center(img_feat)
|
||||
bbox_center = soft_argmax_2d(bbox_center_hm)
|
||||
lhand_center, rhand_center, face_center = bbox_center[:, 0, :], bbox_center[:, 1, :], bbox_center[:, 2, :]
|
||||
|
||||
# bbox size
|
||||
lhand_feat = sample_joint_features(img_feat, lhand_center[:, None, :].detach())[:, 0, :]
|
||||
lhand_size = self.lhand_size(lhand_feat)
|
||||
rhand_feat = sample_joint_features(img_feat, rhand_center[:, None, :].detach())[:, 0, :]
|
||||
rhand_size = self.rhand_size(rhand_feat)
|
||||
face_feat = sample_joint_features(img_feat, face_center[:, None, :].detach())[:, 0, :]
|
||||
face_size = self.face_size(face_feat)
|
||||
|
||||
lhand_center = lhand_center / 8
|
||||
rhand_center = rhand_center / 8
|
||||
face_center = face_center / 8
|
||||
return lhand_center, lhand_size, rhand_center, rhand_size, face_center, face_size
|
||||
|
||||
class BoxSizeNet(nn.Module):
|
||||
def __init__(self):
|
||||
super(BoxSizeNet, self).__init__()
|
||||
self.lhand_size = make_linear_layers([256, 256, 2], relu_final=False)
|
||||
self.rhand_size = make_linear_layers([256, 256, 2], relu_final=False)
|
||||
self.face_size = make_linear_layers([256, 256, 2], relu_final=False)
|
||||
|
||||
def forward(self, box_fea):
|
||||
# box_fea: [bs, 3, C]
|
||||
lhand_size = self.lhand_size(box_fea[:, 0])
|
||||
rhand_size = self.rhand_size(box_fea[:, 1])
|
||||
face_size = self.face_size(box_fea[:, 2])
|
||||
return lhand_size, rhand_size, face_size
|
||||
|
||||
class HandRoI(nn.Module):
|
||||
def __init__(self, feat_dim=768, upscale=4):
|
||||
super(HandRoI, self).__init__()
|
||||
self.upscale = upscale
|
||||
if upscale==1:
|
||||
self.deconv = make_conv_layers([feat_dim, feat_dim], kernel=1, stride=1, padding=0, bnrelu_final=False)
|
||||
self.conv = make_conv_layers([feat_dim, feat_dim], kernel=1, stride=1, padding=0, bnrelu_final=False)
|
||||
elif upscale==2:
|
||||
self.deconv = make_deconv_layers([feat_dim, feat_dim//2])
|
||||
self.conv = make_conv_layers([feat_dim//2, feat_dim], kernel=1, stride=1, padding=0, bnrelu_final=False)
|
||||
elif upscale==4:
|
||||
self.deconv = make_deconv_layers([feat_dim, feat_dim//2, feat_dim//4])
|
||||
self.conv = make_conv_layers([feat_dim//4, feat_dim], kernel=1, stride=1, padding=0, bnrelu_final=False)
|
||||
elif upscale==8:
|
||||
self.deconv = make_deconv_layers([feat_dim, feat_dim//2, feat_dim//4, feat_dim//8])
|
||||
self.conv = make_conv_layers([feat_dim//8, feat_dim], kernel=1, stride=1, padding=0, bnrelu_final=False)
|
||||
|
||||
def forward(self, img_feat, lhand_bbox, rhand_bbox):
|
||||
lhand_bbox = torch.cat((torch.arange(lhand_bbox.shape[0]).float().cuda()[:, None], lhand_bbox),
|
||||
1) # batch_idx, xmin, ymin, xmax, ymax
|
||||
rhand_bbox = torch.cat((torch.arange(rhand_bbox.shape[0]).float().cuda()[:, None], rhand_bbox),
|
||||
1) # batch_idx, xmin, ymin, xmax, ymax
|
||||
img_feat = self.deconv(img_feat)
|
||||
lhand_bbox_roi = lhand_bbox.clone()
|
||||
lhand_bbox_roi[:, 1] = lhand_bbox_roi[:, 1] / cfg.input_body_shape[1] * cfg.output_hm_shape[2] * self.upscale
|
||||
lhand_bbox_roi[:, 2] = lhand_bbox_roi[:, 2] / cfg.input_body_shape[0] * cfg.output_hm_shape[1] * self.upscale
|
||||
lhand_bbox_roi[:, 3] = lhand_bbox_roi[:, 3] / cfg.input_body_shape[1] * cfg.output_hm_shape[2] * self.upscale
|
||||
lhand_bbox_roi[:, 4] = lhand_bbox_roi[:, 4] / cfg.input_body_shape[0] * cfg.output_hm_shape[1] * self.upscale
|
||||
assert (cfg.output_hm_shape[1]*self.upscale, cfg.output_hm_shape[2]*self.upscale) == (img_feat.shape[2], img_feat.shape[3])
|
||||
lhand_img_feat = roi_align(img_feat, lhand_bbox_roi, (cfg.output_hand_hm_shape[1], cfg.output_hand_hm_shape[2]), 1.0, 0, 'avg', False)
|
||||
lhand_img_feat = torch.flip(lhand_img_feat, [3]) # flip to the right hand
|
||||
|
||||
rhand_bbox_roi = rhand_bbox.clone()
|
||||
rhand_bbox_roi[:, 1] = rhand_bbox_roi[:, 1] / cfg.input_body_shape[1] * cfg.output_hm_shape[2] * self.upscale
|
||||
rhand_bbox_roi[:, 2] = rhand_bbox_roi[:, 2] / cfg.input_body_shape[0] * cfg.output_hm_shape[1] * self.upscale
|
||||
rhand_bbox_roi[:, 3] = rhand_bbox_roi[:, 3] / cfg.input_body_shape[1] * cfg.output_hm_shape[2] * self.upscale
|
||||
rhand_bbox_roi[:, 4] = rhand_bbox_roi[:, 4] / cfg.input_body_shape[0] * cfg.output_hm_shape[1] * self.upscale
|
||||
rhand_img_feat = roi_align(img_feat, rhand_bbox_roi, (cfg.output_hand_hm_shape[1], cfg.output_hand_hm_shape[2]), 1.0, 0, 'avg', False)
|
||||
hand_img_feat = torch.cat((lhand_img_feat, rhand_img_feat)) # [bs, c, cfg.output_hand_hm_shape[2]*scale, cfg.output_hand_hm_shape[1]*scale]
|
||||
hand_img_feat = self.conv(hand_img_feat)
|
||||
return hand_img_feat
|
||||
@@ -0,0 +1,38 @@
|
||||
# --------------------------------------------------------
|
||||
# Fast R-CNN
|
||||
# Copyright (c) 2015 Microsoft
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# Written by Ross Girshick
|
||||
# --------------------------------------------------------
|
||||
|
||||
import time
|
||||
|
||||
class Timer(object):
|
||||
"""A simple timer."""
|
||||
def __init__(self):
|
||||
self.total_time = 0.
|
||||
self.calls = 0
|
||||
self.start_time = 0.
|
||||
self.diff = 0.
|
||||
self.average_time = 0.
|
||||
self.warm_up = 0
|
||||
|
||||
def tic(self):
|
||||
# using time.time instead of time.clock because time time.clock
|
||||
# does not normalize for multithreading
|
||||
self.start_time = time.time()
|
||||
|
||||
def toc(self, average=True):
|
||||
self.diff = time.time() - self.start_time
|
||||
if self.warm_up < 10:
|
||||
self.warm_up += 1
|
||||
return self.diff
|
||||
else:
|
||||
self.total_time += self.diff
|
||||
self.calls += 1
|
||||
self.average_time = self.total_time / self.calls
|
||||
|
||||
if average:
|
||||
return self.average_time
|
||||
else:
|
||||
return self.diff
|
||||
@@ -0,0 +1,10 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
def make_folder(folder_name):
|
||||
os.makedirs(folder_name, exist_ok=True)
|
||||
|
||||
def add_pypath(path):
|
||||
if path not in sys.path:
|
||||
sys.path.insert(0, path)
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
import mmcv
|
||||
import os
|
||||
import os.path as osp
|
||||
import pickle
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
from mmcv.runner import get_dist_info
|
||||
import random
|
||||
import numpy as np
|
||||
import subprocess
|
||||
|
||||
def set_seed(seed):
|
||||
random.seed(seed)
|
||||
np.random.seed(seed)
|
||||
torch.manual_seed(seed)
|
||||
torch.cuda.manual_seed_all(seed)
|
||||
# torch.set_deterministic(True)
|
||||
|
||||
|
||||
def time_synchronized():
|
||||
torch.cuda.synchronize() if torch.cuda.is_available() else None
|
||||
return time.time()
|
||||
|
||||
|
||||
def setup_for_distributed(is_master):
|
||||
"""This function disables printing when not in master process."""
|
||||
import builtins as __builtin__
|
||||
builtin_print = __builtin__.print
|
||||
|
||||
def print(*args, **kwargs):
|
||||
force = kwargs.pop('force', False)
|
||||
if is_master or force:
|
||||
builtin_print(*args, **kwargs)
|
||||
|
||||
__builtin__.print = print
|
||||
|
||||
|
||||
def init_distributed_mode(port = None, master_port=29500):
|
||||
"""Initialize slurm distributed training environment.
|
||||
|
||||
If argument ``port`` is not specified, then the master port will be system
|
||||
environment variable ``MASTER_PORT``. If ``MASTER_PORT`` is not in system
|
||||
environment variable, then a default port ``29500`` will be used.
|
||||
|
||||
Args:
|
||||
backend (str): Backend of torch.distributed.
|
||||
port (int, optional): Master port. Defaults to None.
|
||||
"""
|
||||
dist_backend = 'nccl'
|
||||
proc_id = int(os.environ['SLURM_PROCID'])
|
||||
ntasks = int(os.environ['SLURM_NTASKS'])
|
||||
node_list = os.environ['SLURM_NODELIST']
|
||||
num_gpus = torch.cuda.device_count()
|
||||
torch.cuda.set_device(proc_id % num_gpus)
|
||||
addr = subprocess.getoutput(
|
||||
f'scontrol show hostname {node_list} | head -n1')
|
||||
# specify master port
|
||||
if port is not None:
|
||||
os.environ['MASTER_PORT'] = str(port)
|
||||
elif 'MASTER_PORT' in os.environ:
|
||||
pass # use MASTER_PORT in the environment variable
|
||||
else:
|
||||
# 29500 is torch.distributed default port
|
||||
os.environ['MASTER_PORT'] = str(master_port)
|
||||
# use MASTER_ADDR in the environment variable if it already exists
|
||||
if 'MASTER_ADDR' not in os.environ:
|
||||
os.environ['MASTER_ADDR'] = addr
|
||||
os.environ['WORLD_SIZE'] = str(ntasks)
|
||||
os.environ['LOCAL_RANK'] = str(proc_id % num_gpus)
|
||||
os.environ['RANK'] = str(proc_id)
|
||||
dist.init_process_group(backend=dist_backend)
|
||||
|
||||
distributed = True
|
||||
gpu_idx = proc_id % num_gpus
|
||||
|
||||
return distributed, gpu_idx
|
||||
|
||||
|
||||
def is_dist_avail_and_initialized():
|
||||
if not dist.is_available():
|
||||
return False
|
||||
if not dist.is_initialized():
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_world_size():
|
||||
if not is_dist_avail_and_initialized():
|
||||
return 1
|
||||
return dist.get_world_size()
|
||||
|
||||
|
||||
def get_rank():
|
||||
if not is_dist_avail_and_initialized():
|
||||
return 0
|
||||
return dist.get_rank()
|
||||
|
||||
def get_process_groups():
|
||||
world_size = int(os.environ['WORLD_SIZE'])
|
||||
ranks = list(range(world_size))
|
||||
num_gpus = torch.cuda.device_count()
|
||||
num_nodes = world_size // num_gpus
|
||||
if world_size % num_gpus != 0:
|
||||
raise NotImplementedError('Not implemented for node not fully used.')
|
||||
|
||||
groups = []
|
||||
for node_idx in range(num_nodes):
|
||||
groups.append(ranks[node_idx*num_gpus : (node_idx+1)*num_gpus])
|
||||
process_groups = [torch.distributed.new_group(group) for group in groups]
|
||||
|
||||
return process_groups
|
||||
|
||||
def get_group_idx():
|
||||
num_gpus = torch.cuda.device_count()
|
||||
proc_id = get_rank()
|
||||
group_idx = proc_id // num_gpus
|
||||
|
||||
return group_idx
|
||||
|
||||
|
||||
def is_main_process():
|
||||
return get_rank() == 0
|
||||
|
||||
def cleanup():
|
||||
dist.destroy_process_group()
|
||||
|
||||
|
||||
def collect_results(result_part, size, tmpdir=None):
|
||||
rank, world_size = get_dist_info()
|
||||
# create a tmp dir if it is not specified
|
||||
if tmpdir is None:
|
||||
MAX_LEN = 512
|
||||
# 32 is whitespace
|
||||
dir_tensor = torch.full((MAX_LEN, ),
|
||||
32,
|
||||
dtype=torch.uint8,
|
||||
device='cuda')
|
||||
if rank == 0:
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
tmpdir = torch.tensor(
|
||||
bytearray(tmpdir.encode()), dtype=torch.uint8, device='cuda')
|
||||
dir_tensor[:len(tmpdir)] = tmpdir
|
||||
dist.broadcast(dir_tensor, 0)
|
||||
tmpdir = dir_tensor.cpu().numpy().tobytes().decode().rstrip()
|
||||
else:
|
||||
mmcv.mkdir_or_exist(tmpdir)
|
||||
# dump the part result to the dir
|
||||
mmcv.dump(result_part, osp.join(tmpdir, f'part_{rank}.pkl'))
|
||||
dist.barrier()
|
||||
# collect all parts
|
||||
if rank != 0:
|
||||
return None
|
||||
else:
|
||||
# load results of all parts from tmp dir
|
||||
part_list = []
|
||||
for i in range(world_size):
|
||||
part_file = osp.join(tmpdir, f'part_{i}.pkl')
|
||||
part_list.append(mmcv.load(part_file))
|
||||
# sort the results
|
||||
ordered_results = []
|
||||
for res in zip(*part_list):
|
||||
ordered_results.extend(list(res))
|
||||
# the dataloader may pad some samples
|
||||
ordered_results = ordered_results[:size]
|
||||
# remove tmp dir
|
||||
shutil.rmtree(tmpdir)
|
||||
return ordered_results
|
||||
|
||||
|
||||
def all_gather(data):
|
||||
"""
|
||||
Run all_gather on arbitrary picklable data (not necessarily tensors)
|
||||
Args:
|
||||
data:
|
||||
Any picklable object
|
||||
Returns:
|
||||
data_list(list):
|
||||
List of data gathered from each rank
|
||||
"""
|
||||
world_size = get_world_size()
|
||||
if world_size == 1:
|
||||
return [data]
|
||||
|
||||
# serialized to a Tensor
|
||||
buffer = pickle.dumps(data)
|
||||
storage = torch.ByteStorage.from_buffer(buffer)
|
||||
tensor = torch.ByteTensor(storage).to('cuda')
|
||||
|
||||
# obtain Tensor size of each rank
|
||||
local_size = torch.tensor([tensor.numel()], device='cuda')
|
||||
size_list = [torch.tensor([0], device='cuda') for _ in range(world_size)]
|
||||
dist.all_gather(size_list, local_size)
|
||||
size_list = [int(size.item()) for size in size_list]
|
||||
max_size = max(size_list)
|
||||
|
||||
# receiving Tensor from all ranks
|
||||
# we pad the tensor because torch all_gather does not support
|
||||
# gathering tensors of different shapes
|
||||
tensor_list = []
|
||||
for _ in size_list:
|
||||
tensor_list.append(
|
||||
torch.empty((max_size, ), dtype=torch.uint8, device='cuda'))
|
||||
if local_size != max_size:
|
||||
padding = torch.empty(
|
||||
size=(max_size - local_size, ), dtype=torch.uint8, device='cuda')
|
||||
tensor = torch.cat((tensor, padding), dim=0)
|
||||
dist.all_gather(tensor_list, tensor)
|
||||
|
||||
data_list = []
|
||||
for size, tensor in zip(size_list, tensor_list):
|
||||
buffer = tensor.cpu().numpy().tobytes()[:size]
|
||||
data_list.append(pickle.loads(buffer))
|
||||
|
||||
return data_list
|
||||
@@ -0,0 +1,176 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
import os.path as osp
|
||||
from config import cfg
|
||||
from utils.smplx import smplx
|
||||
import pickle
|
||||
|
||||
class SMPLX(object):
|
||||
def __init__(self):
|
||||
self.layer_arg = {'create_global_orient': False, 'create_body_pose': False, 'create_left_hand_pose': False, 'create_right_hand_pose': False, 'create_jaw_pose': False, 'create_leye_pose': False, 'create_reye_pose': False, 'create_betas': False, 'create_expression': False, 'create_transl': False}
|
||||
self.layer = {'neutral': smplx.create(cfg.human_model_path, 'smplx', gender='NEUTRAL', use_pca=False, use_face_contour=True, **self.layer_arg),
|
||||
'male': smplx.create(cfg.human_model_path, 'smplx', gender='MALE', use_pca=False, use_face_contour=True, **self.layer_arg),
|
||||
'female': smplx.create(cfg.human_model_path, 'smplx', gender='FEMALE', use_pca=False, use_face_contour=True, **self.layer_arg)
|
||||
}
|
||||
self.vertex_num = 10475
|
||||
self.face = self.layer['neutral'].faces
|
||||
self.shape_param_dim = 10
|
||||
self.expr_code_dim = 10
|
||||
with open(osp.join(cfg.human_model_path, 'smplx', 'SMPLX_to_J14.pkl'), 'rb') as f:
|
||||
self.j14_regressor = pickle.load(f, encoding='latin1')
|
||||
with open(osp.join(cfg.human_model_path, 'smplx', 'MANO_SMPLX_vertex_ids.pkl'), 'rb') as f:
|
||||
self.hand_vertex_idx = pickle.load(f, encoding='latin1')
|
||||
self.face_vertex_idx = np.load(osp.join(cfg.human_model_path, 'smplx', 'SMPL-X__FLAME_vertex_ids.npy'))
|
||||
self.J_regressor = self.layer['neutral'].J_regressor.numpy()
|
||||
self.J_regressor_idx = {'pelvis': 0, 'lwrist': 20, 'rwrist': 21, 'neck': 12}
|
||||
self.orig_hand_regressor = self.make_hand_regressor()
|
||||
#self.orig_hand_regressor = {'left': self.layer.J_regressor.numpy()[[20,37,38,39,25,26,27,28,29,30,34,35,36,31,32,33],:], 'right': self.layer.J_regressor.numpy()[[21,52,53,54,40,41,42,43,44,45,49,50,51,46,47,48],:]}
|
||||
|
||||
# original SMPLX joint set
|
||||
self.orig_joint_num = 53 # 22 (body joints) + 30 (hand joints) + 1 (face jaw joint)
|
||||
self.orig_joints_name = \
|
||||
('Pelvis', 'L_Hip', 'R_Hip', 'Spine_1', 'L_Knee', 'R_Knee', 'Spine_2', 'L_Ankle', 'R_Ankle', 'Spine_3', 'L_Foot', 'R_Foot', 'Neck', 'L_Collar', 'R_Collar', 'Head', 'L_Shoulder', 'R_Shoulder', 'L_Elbow', 'R_Elbow', 'L_Wrist', 'R_Wrist', # body joints
|
||||
'L_Index_1', 'L_Index_2', 'L_Index_3', 'L_Middle_1', 'L_Middle_2', 'L_Middle_3', 'L_Pinky_1', 'L_Pinky_2', 'L_Pinky_3', 'L_Ring_1', 'L_Ring_2', 'L_Ring_3', 'L_Thumb_1', 'L_Thumb_2', 'L_Thumb_3', # left hand joints
|
||||
'R_Index_1', 'R_Index_2', 'R_Index_3', 'R_Middle_1', 'R_Middle_2', 'R_Middle_3', 'R_Pinky_1', 'R_Pinky_2', 'R_Pinky_3', 'R_Ring_1', 'R_Ring_2', 'R_Ring_3', 'R_Thumb_1', 'R_Thumb_2', 'R_Thumb_3', # right hand joints
|
||||
'Jaw' # face jaw joint
|
||||
)
|
||||
self.orig_flip_pairs = \
|
||||
( (1,2), (4,5), (7,8), (10,11), (13,14), (16,17), (18,19), (20,21), # body joints
|
||||
(22,37), (23,38), (24,39), (25,40), (26,41), (27,42), (28,43), (29,44), (30,45), (31,46), (32,47), (33,48), (34,49), (35,50), (36,51) # hand joints
|
||||
)
|
||||
self.orig_root_joint_idx = self.orig_joints_name.index('Pelvis')
|
||||
self.orig_joint_part = \
|
||||
{'body': range(self.orig_joints_name.index('Pelvis'), self.orig_joints_name.index('R_Wrist')+1),
|
||||
'lhand': range(self.orig_joints_name.index('L_Index_1'), self.orig_joints_name.index('L_Thumb_3')+1),
|
||||
'rhand': range(self.orig_joints_name.index('R_Index_1'), self.orig_joints_name.index('R_Thumb_3')+1),
|
||||
'face': range(self.orig_joints_name.index('Jaw'), self.orig_joints_name.index('Jaw')+1)}
|
||||
|
||||
# changed SMPLX joint set for the supervision
|
||||
self.joint_num = 137 # 25 (body joints) + 40 (hand joints) + 72 (face keypoints)
|
||||
self.joints_name = \
|
||||
('Pelvis', 'L_Hip', 'R_Hip', 'L_Knee', 'R_Knee', 'L_Ankle', 'R_Ankle', 'Neck', 'L_Shoulder', 'R_Shoulder', 'L_Elbow', 'R_Elbow', 'L_Wrist', 'R_Wrist', 'L_Big_toe', 'L_Small_toe', 'L_Heel', 'R_Big_toe', 'R_Small_toe', 'R_Heel', 'L_Ear', 'R_Ear', 'L_Eye', 'R_Eye', 'Nose',# body joints
|
||||
'L_Thumb_1', 'L_Thumb_2', 'L_Thumb_3', 'L_Thumb_4', 'L_Index_1', 'L_Index_2', 'L_Index_3', 'L_Index_4', 'L_Middle_1', 'L_Middle_2', 'L_Middle_3', 'L_Middle_4', 'L_Ring_1', 'L_Ring_2', 'L_Ring_3', 'L_Ring_4', 'L_Pinky_1', 'L_Pinky_2', 'L_Pinky_3', 'L_Pinky_4', # left hand joints
|
||||
'R_Thumb_1', 'R_Thumb_2', 'R_Thumb_3', 'R_Thumb_4', 'R_Index_1', 'R_Index_2', 'R_Index_3', 'R_Index_4', 'R_Middle_1', 'R_Middle_2', 'R_Middle_3', 'R_Middle_4', 'R_Ring_1', 'R_Ring_2', 'R_Ring_3', 'R_Ring_4', 'R_Pinky_1', 'R_Pinky_2', 'R_Pinky_3', 'R_Pinky_4', # right hand joints
|
||||
*['Face_' + str(i) for i in range(1,73)] # face keypoints (too many keypoints... omit real names. have same name of keypoints defined in FLAME class)
|
||||
)
|
||||
self.root_joint_idx = self.joints_name.index('Pelvis')
|
||||
self.lwrist_idx = self.joints_name.index('L_Wrist')
|
||||
self.rwrist_idx = self.joints_name.index('R_Wrist')
|
||||
self.neck_idx = self.joints_name.index('Neck')
|
||||
self.flip_pairs = \
|
||||
( (1,2), (3,4), (5,6), (8,9), (10,11), (12,13), (14,17), (15,18), (16,19), (20,21), (22,23), # body joints
|
||||
(25,45), (26,46), (27,47), (28,48), (29,49), (30,50), (31,51), (32,52), (33,53), (34,54), (35,55), (36,56), (37,57), (38,58), (39,59), (40,60), (41,61), (42,62), (43,63), (44,64), # hand joints
|
||||
(67,68), # face eyeballs
|
||||
(69,78), (70,77), (71,76), (72,75), (73,74), # face eyebrow
|
||||
(83,87), (84,86), # face below nose
|
||||
(88,97), (89,96), (90,95), (91,94), (92,99), (93,98), # face eyes
|
||||
(100,106), (101,105), (102,104), (107,111), (108,110), # face mouth
|
||||
(112,116), (113,115), (117,119), # face lip
|
||||
(120,136), (121,135), (122,134), (123,133), (124,132), (125,131), (126,130), (127,129) # face contours
|
||||
)
|
||||
self.joint_idx = \
|
||||
(0,1,2,4,5,7,8,12,16,17,18,19,20,21,60,61,62,63,64,65,59,58,57,56,55, # body joints
|
||||
37,38,39,66,25,26,27,67,28,29,30,68,34,35,36,69,31,32,33,70, # left hand joints
|
||||
52,53,54,71,40,41,42,72,43,44,45,73,49,50,51,74,46,47,48,75, # right hand joints
|
||||
22,15, # jaw, head
|
||||
57,56, # eyeballs
|
||||
76,77,78,79,80,81,82,83,84,85, # eyebrow
|
||||
86,87,88,89, # nose
|
||||
90,91,92,93,94, # below nose
|
||||
95,96,97,98,99,100,101,102,103,104,105,106, # eyes
|
||||
107, # right mouth
|
||||
108,109,110,111,112, # upper mouth
|
||||
113, # left mouth
|
||||
114,115,116,117,118, # lower mouth
|
||||
119, # right lip
|
||||
120,121,122, # upper lip
|
||||
123, # left lip
|
||||
124,125,126, # lower lip
|
||||
127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143 # face contour
|
||||
)
|
||||
self.joint_part = \
|
||||
{'body': range(self.joints_name.index('Pelvis'), self.joints_name.index('Nose')+1),
|
||||
'lhand': range(self.joints_name.index('L_Thumb_1'), self.joints_name.index('L_Pinky_4')+1),
|
||||
'rhand': range(self.joints_name.index('R_Thumb_1'), self.joints_name.index('R_Pinky_4')+1),
|
||||
'hand': range(self.joints_name.index('L_Thumb_1'), self.joints_name.index('R_Pinky_4')+1),
|
||||
'face': range(self.joints_name.index('Face_1'), self.joints_name.index('Face_72')+1)}
|
||||
|
||||
# changed SMPLX joint set for PositionNet prediction
|
||||
self.pos_joint_num = 65 # 25 (body joints) + 40 (hand joints)
|
||||
self.pos_joints_name = \
|
||||
('Pelvis', 'L_Hip', 'R_Hip', 'L_Knee', 'R_Knee', 'L_Ankle', 'R_Ankle', 'Neck', 'L_Shoulder', 'R_Shoulder', 'L_Elbow', 'R_Elbow', 'L_Wrist', 'R_Wrist', 'L_Big_toe', 'L_Small_toe', 'L_Heel', 'R_Big_toe', 'R_Small_toe', 'R_Heel', 'L_Ear', 'R_Ear', 'L_Eye', 'R_Eye', 'Nose', # body joints
|
||||
'L_Thumb_1', 'L_Thumb_2', 'L_Thumb_3', 'L_Thumb_4', 'L_Index_1', 'L_Index_2', 'L_Index_3', 'L_Index_4', 'L_Middle_1', 'L_Middle_2', 'L_Middle_3', 'L_Middle_4', 'L_Ring_1', 'L_Ring_2', 'L_Ring_3', 'L_Ring_4', 'L_Pinky_1', 'L_Pinky_2', 'L_Pinky_3', 'L_Pinky_4', # left hand joints
|
||||
'R_Thumb_1', 'R_Thumb_2', 'R_Thumb_3', 'R_Thumb_4', 'R_Index_1', 'R_Index_2', 'R_Index_3', 'R_Index_4', 'R_Middle_1', 'R_Middle_2', 'R_Middle_3', 'R_Middle_4', 'R_Ring_1', 'R_Ring_2', 'R_Ring_3', 'R_Ring_4', 'R_Pinky_1', 'R_Pinky_2', 'R_Pinky_3', 'R_Pinky_4', # right hand joints
|
||||
)
|
||||
self.pos_joint_part = \
|
||||
{'body': range(self.pos_joints_name.index('Pelvis'), self.pos_joints_name.index('Nose')+1),
|
||||
'lhand': range(self.pos_joints_name.index('L_Thumb_1'), self.pos_joints_name.index('L_Pinky_4')+1),
|
||||
'rhand': range(self.pos_joints_name.index('R_Thumb_1'), self.pos_joints_name.index('R_Pinky_4')+1),
|
||||
'hand': range(self.pos_joints_name.index('L_Thumb_1'), self.pos_joints_name.index('R_Pinky_4')+1)}
|
||||
self.pos_joint_part['L_MCP'] = [self.pos_joints_name.index('L_Index_1') - len(self.pos_joint_part['body']),
|
||||
self.pos_joints_name.index('L_Middle_1') - len(self.pos_joint_part['body']),
|
||||
self.pos_joints_name.index('L_Ring_1') - len(self.pos_joint_part['body']),
|
||||
self.pos_joints_name.index('L_Pinky_1') - len(self.pos_joint_part['body'])]
|
||||
self.pos_joint_part['R_MCP'] = [self.pos_joints_name.index('R_Index_1') - len(self.pos_joint_part['body']) - len(self.pos_joint_part['lhand']),
|
||||
self.pos_joints_name.index('R_Middle_1') - len(self.pos_joint_part['body']) - len(self.pos_joint_part['lhand']),
|
||||
self.pos_joints_name.index('R_Ring_1') - len(self.pos_joint_part['body']) - len(self.pos_joint_part['lhand']),
|
||||
self.pos_joints_name.index('R_Pinky_1') - len(self.pos_joint_part['body']) - len(self.pos_joint_part['lhand'])]
|
||||
|
||||
def make_hand_regressor(self):
|
||||
regressor = self.layer['neutral'].J_regressor.numpy()
|
||||
lhand_regressor = np.concatenate((regressor[[20,37,38,39],:],
|
||||
np.eye(self.vertex_num)[5361,None],
|
||||
regressor[[25,26,27],:],
|
||||
np.eye(self.vertex_num)[4933,None],
|
||||
regressor[[28,29,30],:],
|
||||
np.eye(self.vertex_num)[5058,None],
|
||||
regressor[[34,35,36],:],
|
||||
np.eye(self.vertex_num)[5169,None],
|
||||
regressor[[31,32,33],:],
|
||||
np.eye(self.vertex_num)[5286,None]))
|
||||
rhand_regressor = np.concatenate((regressor[[21,52,53,54],:],
|
||||
np.eye(self.vertex_num)[8079,None],
|
||||
regressor[[40,41,42],:],
|
||||
np.eye(self.vertex_num)[7669,None],
|
||||
regressor[[43,44,45],:],
|
||||
np.eye(self.vertex_num)[7794,None],
|
||||
regressor[[49,50,51],:],
|
||||
np.eye(self.vertex_num)[7905,None],
|
||||
regressor[[46,47,48],:],
|
||||
np.eye(self.vertex_num)[8022,None]))
|
||||
hand_regressor = {'left': lhand_regressor, 'right': rhand_regressor}
|
||||
return hand_regressor
|
||||
|
||||
|
||||
def reduce_joint_set(self, joint):
|
||||
new_joint = []
|
||||
for name in self.pos_joints_name:
|
||||
idx = self.joints_name.index(name)
|
||||
new_joint.append(joint[:,idx,:])
|
||||
new_joint = torch.stack(new_joint,1)
|
||||
return new_joint
|
||||
|
||||
class SMPL(object):
|
||||
def __init__(self):
|
||||
self.layer_arg = {'create_body_pose': False, 'create_betas': False, 'create_global_orient': False, 'create_transl': False}
|
||||
self.layer = {'neutral': smplx.create(cfg.human_model_path, 'smpl', gender='NEUTRAL', **self.layer_arg), 'male': smplx.create(cfg.human_model_path, 'smpl', gender='MALE', **self.layer_arg), 'female': smplx.create(cfg.human_model_path, 'smpl', gender='FEMALE', **self.layer_arg)}
|
||||
self.vertex_num = 6890
|
||||
self.face = self.layer['neutral'].faces
|
||||
self.shape_param_dim = 10
|
||||
self.vposer_code_dim = 32
|
||||
|
||||
# original SMPL joint set
|
||||
self.orig_joint_num = 24
|
||||
self.orig_joints_name = ('Pelvis', 'L_Hip', 'R_Hip', 'Spine_1', 'L_Knee', 'R_Knee', 'Spine_2', 'L_Ankle', 'R_Ankle', 'Spine_3', 'L_Foot', 'R_Foot', 'Neck', 'L_Collar', 'R_Collar', 'Head', 'L_Shoulder', 'R_Shoulder', 'L_Elbow', 'R_Elbow', 'L_Wrist', 'R_Wrist', 'L_Hand', 'R_Hand')
|
||||
self.orig_flip_pairs = ( (1,2), (4,5), (7,8), (10,11), (13,14), (16,17), (18,19), (20,21), (22,23) )
|
||||
self.orig_root_joint_idx = self.orig_joints_name.index('Pelvis')
|
||||
self.orig_joint_regressor = self.layer['neutral'].J_regressor.numpy().astype(np.float32)
|
||||
|
||||
self.joint_num = self.orig_joint_num
|
||||
self.joints_name = self.orig_joints_name
|
||||
self.flip_pairs = self.orig_flip_pairs
|
||||
self.root_joint_idx = self.orig_root_joint_idx
|
||||
self.joint_regressor = self.orig_joint_regressor
|
||||
|
||||
smpl_x = SMPLX()
|
||||
smpl = SMPL()
|
||||
@@ -0,0 +1,566 @@
|
||||
import numpy as np
|
||||
import cv2
|
||||
import random
|
||||
from config import cfg
|
||||
import math
|
||||
from utils.human_models import smpl_x, smpl
|
||||
from utils.transforms import cam2pixel, transform_joint_to_other_db
|
||||
from plyfile import PlyData, PlyElement
|
||||
import torch
|
||||
|
||||
|
||||
def load_img(path, order='RGB'):
|
||||
img = cv2.imread(path, cv2.IMREAD_COLOR | cv2.IMREAD_IGNORE_ORIENTATION)
|
||||
if not isinstance(img, np.ndarray):
|
||||
raise IOError("Fail to read %s" % path)
|
||||
|
||||
if order == 'RGB':
|
||||
img = img[:, :, ::-1].copy()
|
||||
|
||||
img = img.astype(np.float32)
|
||||
return img
|
||||
|
||||
|
||||
def get_bbox(joint_img, joint_valid, extend_ratio=1.2):
|
||||
x_img, y_img = joint_img[:, 0], joint_img[:, 1]
|
||||
x_img = x_img[joint_valid == 1];
|
||||
y_img = y_img[joint_valid == 1];
|
||||
xmin = min(x_img);
|
||||
ymin = min(y_img);
|
||||
xmax = max(x_img);
|
||||
ymax = max(y_img);
|
||||
|
||||
x_center = (xmin + xmax) / 2.;
|
||||
width = xmax - xmin;
|
||||
xmin = x_center - 0.5 * width * extend_ratio
|
||||
xmax = x_center + 0.5 * width * extend_ratio
|
||||
|
||||
y_center = (ymin + ymax) / 2.;
|
||||
height = ymax - ymin;
|
||||
ymin = y_center - 0.5 * height * extend_ratio
|
||||
ymax = y_center + 0.5 * height * extend_ratio
|
||||
|
||||
bbox = np.array([xmin, ymin, xmax - xmin, ymax - ymin]).astype(np.float32)
|
||||
return bbox
|
||||
|
||||
|
||||
def sanitize_bbox(bbox, img_width, img_height):
|
||||
x, y, w, h = bbox
|
||||
x1 = np.max((0, x))
|
||||
y1 = np.max((0, y))
|
||||
x2 = np.min((img_width - 1, x1 + np.max((0, w - 1))))
|
||||
y2 = np.min((img_height - 1, y1 + np.max((0, h - 1))))
|
||||
if w * h > 0 and x2 > x1 and y2 > y1:
|
||||
bbox = np.array([x1, y1, x2 - x1, y2 - y1])
|
||||
else:
|
||||
bbox = None
|
||||
|
||||
return bbox
|
||||
|
||||
|
||||
def process_bbox(bbox, img_width, img_height, ratio=1.25):
|
||||
bbox = sanitize_bbox(bbox, img_width, img_height)
|
||||
if bbox is None:
|
||||
return bbox
|
||||
|
||||
# aspect ratio preserving bbox
|
||||
w = bbox[2]
|
||||
h = bbox[3]
|
||||
c_x = bbox[0] + w / 2.
|
||||
c_y = bbox[1] + h / 2.
|
||||
aspect_ratio = cfg.input_img_shape[1] / cfg.input_img_shape[0]
|
||||
if w > aspect_ratio * h:
|
||||
h = w / aspect_ratio
|
||||
elif w < aspect_ratio * h:
|
||||
w = h * aspect_ratio
|
||||
bbox[2] = w * ratio
|
||||
bbox[3] = h * ratio
|
||||
bbox[0] = c_x - bbox[2] / 2.
|
||||
bbox[1] = c_y - bbox[3] / 2.
|
||||
|
||||
bbox = bbox.astype(np.float32)
|
||||
return bbox
|
||||
|
||||
|
||||
def get_aug_config():
|
||||
scale_factor = 0.25
|
||||
rot_factor = 30
|
||||
color_factor = 0.2
|
||||
|
||||
scale = np.clip(np.random.randn(), -1.0, 1.0) * scale_factor + 1.0
|
||||
rot = np.clip(np.random.randn(), -2.0,
|
||||
2.0) * rot_factor if random.random() <= 0.6 else 0
|
||||
c_up = 1.0 + color_factor
|
||||
c_low = 1.0 - color_factor
|
||||
color_scale = np.array([random.uniform(c_low, c_up), random.uniform(c_low, c_up), random.uniform(c_low, c_up)])
|
||||
do_flip = random.random() <= 0.5
|
||||
|
||||
return scale, rot, color_scale, do_flip
|
||||
|
||||
|
||||
def augmentation(img, bbox, data_split):
|
||||
if getattr(cfg, 'no_aug', False):
|
||||
scale, rot, color_scale, do_flip = 1.0, 0.0, np.array([1, 1, 1]), False
|
||||
elif data_split == 'train':
|
||||
scale, rot, color_scale, do_flip = get_aug_config()
|
||||
else:
|
||||
scale, rot, color_scale, do_flip = 1.0, 0.0, np.array([1, 1, 1]), False
|
||||
|
||||
img, trans, inv_trans = generate_patch_image(img, bbox, scale, rot, do_flip, cfg.input_img_shape)
|
||||
img = np.clip(img * color_scale[None, None, :], 0, 255)
|
||||
return img, trans, inv_trans, rot, do_flip
|
||||
|
||||
|
||||
def generate_patch_image(cvimg, bbox, scale, rot, do_flip, out_shape):
|
||||
img = cvimg.copy()
|
||||
img_height, img_width, img_channels = img.shape
|
||||
|
||||
bb_c_x = float(bbox[0] + 0.5 * bbox[2])
|
||||
bb_c_y = float(bbox[1] + 0.5 * bbox[3])
|
||||
bb_width = float(bbox[2])
|
||||
bb_height = float(bbox[3])
|
||||
|
||||
if do_flip:
|
||||
img = img[:, ::-1, :]
|
||||
bb_c_x = img_width - bb_c_x - 1
|
||||
|
||||
trans = gen_trans_from_patch_cv(bb_c_x, bb_c_y, bb_width, bb_height, out_shape[1], out_shape[0], scale, rot)
|
||||
img_patch = cv2.warpAffine(img, trans, (int(out_shape[1]), int(out_shape[0])), flags=cv2.INTER_LINEAR)
|
||||
img_patch = img_patch.astype(np.float32)
|
||||
inv_trans = gen_trans_from_patch_cv(bb_c_x, bb_c_y, bb_width, bb_height, out_shape[1], out_shape[0], scale, rot,
|
||||
inv=True)
|
||||
|
||||
return img_patch, trans, inv_trans
|
||||
|
||||
|
||||
def rotate_2d(pt_2d, rot_rad):
|
||||
x = pt_2d[0]
|
||||
y = pt_2d[1]
|
||||
sn, cs = np.sin(rot_rad), np.cos(rot_rad)
|
||||
xx = x * cs - y * sn
|
||||
yy = x * sn + y * cs
|
||||
return np.array([xx, yy], dtype=np.float32)
|
||||
|
||||
|
||||
def gen_trans_from_patch_cv(c_x, c_y, src_width, src_height, dst_width, dst_height, scale, rot, inv=False):
|
||||
# augment size with scale
|
||||
src_w = src_width * scale
|
||||
src_h = src_height * scale
|
||||
src_center = np.array([c_x, c_y], dtype=np.float32)
|
||||
|
||||
# augment rotation
|
||||
rot_rad = np.pi * rot / 180
|
||||
src_downdir = rotate_2d(np.array([0, src_h * 0.5], dtype=np.float32), rot_rad)
|
||||
src_rightdir = rotate_2d(np.array([src_w * 0.5, 0], dtype=np.float32), rot_rad)
|
||||
|
||||
dst_w = dst_width
|
||||
dst_h = dst_height
|
||||
dst_center = np.array([dst_w * 0.5, dst_h * 0.5], dtype=np.float32)
|
||||
dst_downdir = np.array([0, dst_h * 0.5], dtype=np.float32)
|
||||
dst_rightdir = np.array([dst_w * 0.5, 0], dtype=np.float32)
|
||||
|
||||
src = np.zeros((3, 2), dtype=np.float32)
|
||||
src[0, :] = src_center
|
||||
src[1, :] = src_center + src_downdir
|
||||
src[2, :] = src_center + src_rightdir
|
||||
|
||||
dst = np.zeros((3, 2), dtype=np.float32)
|
||||
dst[0, :] = dst_center
|
||||
dst[1, :] = dst_center + dst_downdir
|
||||
dst[2, :] = dst_center + dst_rightdir
|
||||
|
||||
if inv:
|
||||
trans = cv2.getAffineTransform(np.float32(dst), np.float32(src))
|
||||
else:
|
||||
trans = cv2.getAffineTransform(np.float32(src), np.float32(dst))
|
||||
|
||||
trans = trans.astype(np.float32)
|
||||
return trans
|
||||
|
||||
|
||||
def process_db_coord(joint_img, joint_cam, joint_valid, do_flip, img_shape, flip_pairs, img2bb_trans, rot,
|
||||
src_joints_name, target_joints_name):
|
||||
joint_img_original = joint_img.copy()
|
||||
joint_img, joint_cam, joint_valid = joint_img.copy(), joint_cam.copy(), joint_valid.copy()
|
||||
|
||||
# flip augmentation
|
||||
if do_flip:
|
||||
joint_cam[:, 0] = -joint_cam[:, 0]
|
||||
joint_img[:, 0] = img_shape[1] - 1 - joint_img[:, 0]
|
||||
for pair in flip_pairs:
|
||||
joint_img[pair[0], :], joint_img[pair[1], :] = joint_img[pair[1], :].copy(), joint_img[pair[0], :].copy()
|
||||
joint_cam[pair[0], :], joint_cam[pair[1], :] = joint_cam[pair[1], :].copy(), joint_cam[pair[0], :].copy()
|
||||
joint_valid[pair[0], :], joint_valid[pair[1], :] = joint_valid[pair[1], :].copy(), joint_valid[pair[0],
|
||||
:].copy()
|
||||
|
||||
# 3D data rotation augmentation
|
||||
rot_aug_mat = np.array([[np.cos(np.deg2rad(-rot)), -np.sin(np.deg2rad(-rot)), 0],
|
||||
[np.sin(np.deg2rad(-rot)), np.cos(np.deg2rad(-rot)), 0],
|
||||
[0, 0, 1]], dtype=np.float32)
|
||||
joint_cam = np.dot(rot_aug_mat, joint_cam.transpose(1, 0)).transpose(1, 0)
|
||||
|
||||
# affine transformation
|
||||
joint_img_xy1 = np.concatenate((joint_img[:, :2], np.ones_like(joint_img[:, :1])), 1)
|
||||
joint_img[:, :2] = np.dot(img2bb_trans, joint_img_xy1.transpose(1, 0)).transpose(1, 0)
|
||||
joint_img[:, 0] = joint_img[:, 0] / cfg.input_img_shape[1] * cfg.output_hm_shape[2]
|
||||
joint_img[:, 1] = joint_img[:, 1] / cfg.input_img_shape[0] * cfg.output_hm_shape[1]
|
||||
|
||||
# check truncation
|
||||
# TODO
|
||||
joint_trunc = joint_valid * ((joint_img_original[:, 0] > 0) * (joint_img[:, 0] >= 0) * (joint_img[:, 0] < cfg.output_hm_shape[2]) * \
|
||||
(joint_img_original[:, 1] > 0) *(joint_img[:, 1] >= 0) * (joint_img[:, 1] < cfg.output_hm_shape[1]) * \
|
||||
(joint_img_original[:, 2] > 0) *(joint_img[:, 2] >= 0) * (joint_img[:, 2] < cfg.output_hm_shape[0])).reshape(-1,
|
||||
1).astype(
|
||||
np.float32)
|
||||
|
||||
# transform joints to target db joints
|
||||
joint_img = transform_joint_to_other_db(joint_img, src_joints_name, target_joints_name)
|
||||
joint_cam_wo_ra = transform_joint_to_other_db(joint_cam, src_joints_name, target_joints_name)
|
||||
joint_valid = transform_joint_to_other_db(joint_valid, src_joints_name, target_joints_name)
|
||||
joint_trunc = transform_joint_to_other_db(joint_trunc, src_joints_name, target_joints_name)
|
||||
|
||||
# root-alignment, for joint_cam input wo ra
|
||||
joint_cam_ra = joint_cam_wo_ra.copy()
|
||||
joint_cam_ra = joint_cam_ra - joint_cam_ra[smpl_x.root_joint_idx, None, :] # root-relative
|
||||
joint_cam_ra[smpl_x.joint_part['lhand'], :] = joint_cam_ra[smpl_x.joint_part['lhand'], :] - joint_cam_ra[
|
||||
smpl_x.lwrist_idx, None,
|
||||
:] # left hand root-relative
|
||||
joint_cam_ra[smpl_x.joint_part['rhand'], :] = joint_cam_ra[smpl_x.joint_part['rhand'], :] - joint_cam_ra[
|
||||
smpl_x.rwrist_idx, None,
|
||||
:] # right hand root-relative
|
||||
joint_cam_ra[smpl_x.joint_part['face'], :] = joint_cam_ra[smpl_x.joint_part['face'], :] - joint_cam_ra[smpl_x.neck_idx,
|
||||
None,
|
||||
:] # face root-relative
|
||||
|
||||
return joint_img, joint_cam_wo_ra, joint_cam_ra, joint_valid, joint_trunc
|
||||
|
||||
|
||||
def process_human_model_output(human_model_param, cam_param, do_flip, img_shape, img2bb_trans, rot, human_model_type, joint_img=None):
|
||||
if human_model_type == 'smplx':
|
||||
human_model = smpl_x
|
||||
rotation_valid = np.ones((smpl_x.orig_joint_num), dtype=np.float32)
|
||||
coord_valid = np.ones((smpl_x.joint_num), dtype=np.float32)
|
||||
|
||||
root_pose, body_pose, shape, trans = human_model_param['root_pose'], human_model_param['body_pose'], \
|
||||
human_model_param['shape'], human_model_param['trans']
|
||||
if 'lhand_pose' in human_model_param and human_model_param['lhand_valid']:
|
||||
lhand_pose = human_model_param['lhand_pose']
|
||||
else:
|
||||
lhand_pose = np.zeros((3 * len(smpl_x.orig_joint_part['lhand'])), dtype=np.float32)
|
||||
rotation_valid[smpl_x.orig_joint_part['lhand']] = 0
|
||||
coord_valid[smpl_x.joint_part['lhand']] = 0
|
||||
if 'rhand_pose' in human_model_param and human_model_param['rhand_valid']:
|
||||
rhand_pose = human_model_param['rhand_pose']
|
||||
else:
|
||||
rhand_pose = np.zeros((3 * len(smpl_x.orig_joint_part['rhand'])), dtype=np.float32)
|
||||
rotation_valid[smpl_x.orig_joint_part['rhand']] = 0
|
||||
coord_valid[smpl_x.joint_part['rhand']] = 0
|
||||
if 'jaw_pose' in human_model_param and 'expr' in human_model_param and human_model_param['face_valid']:
|
||||
jaw_pose = human_model_param['jaw_pose']
|
||||
expr = human_model_param['expr']
|
||||
expr_valid = True
|
||||
else:
|
||||
jaw_pose = np.zeros((3), dtype=np.float32)
|
||||
expr = np.zeros((smpl_x.expr_code_dim), dtype=np.float32)
|
||||
rotation_valid[smpl_x.orig_joint_part['face']] = 0
|
||||
coord_valid[smpl_x.joint_part['face']] = 0
|
||||
expr_valid = False
|
||||
if 'gender' in human_model_param:
|
||||
gender = human_model_param['gender']
|
||||
else:
|
||||
gender = 'neutral'
|
||||
root_pose = torch.FloatTensor(root_pose).view(1, 3) # (1,3)
|
||||
body_pose = torch.FloatTensor(body_pose).view(-1, 3) # (21,3)
|
||||
lhand_pose = torch.FloatTensor(lhand_pose).view(-1, 3) # (15,3)
|
||||
rhand_pose = torch.FloatTensor(rhand_pose).view(-1, 3) # (15,3)
|
||||
jaw_pose = torch.FloatTensor(jaw_pose).view(-1, 3) # (1,3)
|
||||
shape = torch.FloatTensor(shape).view(1, -1) # SMPLX shape parameter
|
||||
expr = torch.FloatTensor(expr).view(1, -1) # SMPLX expression parameter
|
||||
trans = torch.FloatTensor(trans).view(1, -1) # translation vector
|
||||
|
||||
# apply camera extrinsic (rotation)
|
||||
# merge root pose and camera rotation
|
||||
if 'R' in cam_param:
|
||||
R = np.array(cam_param['R'], dtype=np.float32).reshape(3, 3)
|
||||
root_pose = root_pose.numpy()
|
||||
root_pose, _ = cv2.Rodrigues(root_pose)
|
||||
root_pose, _ = cv2.Rodrigues(np.dot(R, root_pose))
|
||||
root_pose = torch.from_numpy(root_pose).view(1, 3)
|
||||
|
||||
# get mesh and joint coordinates
|
||||
zero_pose = torch.zeros((1, 3)).float() # eye poses
|
||||
with torch.no_grad():
|
||||
output = smpl_x.layer[gender](betas=shape, body_pose=body_pose.view(1, -1), global_orient=root_pose,
|
||||
transl=trans, left_hand_pose=lhand_pose.view(1, -1),
|
||||
right_hand_pose=rhand_pose.view(1, -1), jaw_pose=jaw_pose.view(1, -1),
|
||||
leye_pose=zero_pose, reye_pose=zero_pose, expression=expr)
|
||||
mesh_cam = output.vertices[0].numpy()
|
||||
joint_cam = output.joints[0].numpy()[smpl_x.joint_idx, :]
|
||||
### HARDCODE
|
||||
# joint_cam_orig_ = joint_cam.copy()
|
||||
|
||||
# apply camera exrinsic (translation)
|
||||
# compenstate rotation (translation from origin to root joint was not cancled)
|
||||
if 'R' in cam_param and 't' in cam_param:
|
||||
R, t = np.array(cam_param['R'], dtype=np.float32).reshape(3, 3), np.array(cam_param['t'],
|
||||
dtype=np.float32).reshape(1, 3)
|
||||
root_cam = joint_cam[smpl_x.root_joint_idx, None, :]
|
||||
joint_cam = joint_cam - root_cam + np.dot(R, root_cam.transpose(1, 0)).transpose(1, 0) + t
|
||||
mesh_cam = mesh_cam - root_cam + np.dot(R, root_cam.transpose(1, 0)).transpose(1, 0) + t
|
||||
|
||||
# concat root, body, two hands, and jaw pose
|
||||
pose = torch.cat((root_pose, body_pose, lhand_pose, rhand_pose, jaw_pose))
|
||||
|
||||
# joint coordinates
|
||||
if 'focal' not in cam_param or 'princpt' not in cam_param:
|
||||
assert joint_img is not None
|
||||
else:
|
||||
joint_img = cam2pixel(joint_cam, cam_param['focal'], cam_param['princpt'])
|
||||
|
||||
joint_img_original = joint_img.copy()
|
||||
|
||||
joint_cam = joint_cam - joint_cam[smpl_x.root_joint_idx, None, :] # root-relative
|
||||
joint_cam[smpl_x.joint_part['lhand'], :] = joint_cam[smpl_x.joint_part['lhand'], :] - joint_cam[
|
||||
smpl_x.lwrist_idx, None,
|
||||
:] # left hand root-relative
|
||||
joint_cam[smpl_x.joint_part['rhand'], :] = joint_cam[smpl_x.joint_part['rhand'], :] - joint_cam[
|
||||
smpl_x.rwrist_idx, None,
|
||||
:] # right hand root-relative
|
||||
joint_cam[smpl_x.joint_part['face'], :] = joint_cam[smpl_x.joint_part['face'], :] - joint_cam[smpl_x.neck_idx,
|
||||
None,
|
||||
:] # face root-relative
|
||||
joint_img[smpl_x.joint_part['body'], 2] = (joint_cam[smpl_x.joint_part['body'], 2].copy() / (
|
||||
cfg.body_3d_size / 2) + 1) / 2. * cfg.output_hm_shape[0] # body depth discretize
|
||||
joint_img[smpl_x.joint_part['lhand'], 2] = (joint_cam[smpl_x.joint_part['lhand'], 2].copy() / (
|
||||
cfg.hand_3d_size / 2) + 1) / 2. * cfg.output_hm_shape[0] # left hand depth discretize
|
||||
joint_img[smpl_x.joint_part['rhand'], 2] = (joint_cam[smpl_x.joint_part['rhand'], 2].copy() / (
|
||||
cfg.hand_3d_size / 2) + 1) / 2. * cfg.output_hm_shape[0] # right hand depth discretize
|
||||
joint_img[smpl_x.joint_part['face'], 2] = (joint_cam[smpl_x.joint_part['face'], 2].copy() / (
|
||||
cfg.face_3d_size / 2) + 1) / 2. * cfg.output_hm_shape[0] # face depth discretize
|
||||
|
||||
elif human_model_type == 'smpl':
|
||||
human_model = smpl
|
||||
pose, shape, trans = human_model_param['pose'], human_model_param['shape'], human_model_param['trans']
|
||||
if 'gender' in human_model_param:
|
||||
gender = human_model_param['gender']
|
||||
else:
|
||||
gender = 'neutral'
|
||||
pose = torch.FloatTensor(pose).view(-1, 3)
|
||||
shape = torch.FloatTensor(shape).view(1, -1);
|
||||
trans = torch.FloatTensor(trans).view(1, -1) # translation vector
|
||||
|
||||
# apply camera extrinsic (rotation)
|
||||
# merge root pose and camera rotation
|
||||
if 'R' in cam_param:
|
||||
R = np.array(cam_param['R'], dtype=np.float32).reshape(3, 3)
|
||||
root_pose = pose[smpl.orig_root_joint_idx, :].numpy()
|
||||
root_pose, _ = cv2.Rodrigues(root_pose)
|
||||
root_pose, _ = cv2.Rodrigues(np.dot(R, root_pose))
|
||||
pose[smpl.orig_root_joint_idx] = torch.from_numpy(root_pose).view(3)
|
||||
|
||||
# get mesh and joint coordinates
|
||||
root_pose = pose[smpl.orig_root_joint_idx].view(1, 3)
|
||||
body_pose = torch.cat((pose[:smpl.orig_root_joint_idx, :], pose[smpl.orig_root_joint_idx + 1:, :])).view(1, -1)
|
||||
with torch.no_grad():
|
||||
output = smpl.layer[gender](betas=shape, body_pose=body_pose, global_orient=root_pose, transl=trans)
|
||||
mesh_cam = output.vertices[0].numpy()
|
||||
joint_cam = np.dot(smpl.joint_regressor, mesh_cam)
|
||||
|
||||
# apply camera exrinsic (translation)
|
||||
# compenstate rotation (translation from origin to root joint was not cancled)
|
||||
if 'R' in cam_param and 't' in cam_param:
|
||||
R, t = np.array(cam_param['R'], dtype=np.float32).reshape(3, 3), np.array(cam_param['t'],
|
||||
dtype=np.float32).reshape(1, 3)
|
||||
root_cam = joint_cam[smpl.root_joint_idx, None, :]
|
||||
joint_cam = joint_cam - root_cam + np.dot(R, root_cam.transpose(1, 0)).transpose(1, 0) + t
|
||||
mesh_cam = mesh_cam - root_cam + np.dot(R, root_cam.transpose(1, 0)).transpose(1, 0) + t
|
||||
|
||||
# joint coordinates
|
||||
if 'focal' not in cam_param or 'princpt' not in cam_param:
|
||||
assert joint_img is not None
|
||||
else:
|
||||
joint_img = cam2pixel(joint_cam, cam_param['focal'], cam_param['princpt'])
|
||||
|
||||
joint_img_original = joint_img.copy()
|
||||
joint_cam = joint_cam - joint_cam[smpl.root_joint_idx, None, :] # body root-relative
|
||||
joint_img[:, 2] = (joint_cam[:, 2].copy() / (cfg.body_3d_size / 2) + 1) / 2. * cfg.output_hm_shape[
|
||||
0] # body depth discretize
|
||||
|
||||
elif human_model_type == 'mano':
|
||||
human_model = mano
|
||||
pose, shape, trans = human_model_param['pose'], human_model_param['shape'], human_model_param['trans']
|
||||
hand_type = human_model_param['hand_type']
|
||||
pose = torch.FloatTensor(pose).view(-1, 3)
|
||||
shape = torch.FloatTensor(shape).view(1, -1);
|
||||
trans = torch.FloatTensor(trans).view(1, -1) # translation vector
|
||||
|
||||
# apply camera extrinsic (rotation)
|
||||
# merge root pose and camera rotation
|
||||
if 'R' in cam_param:
|
||||
R = np.array(cam_param['R'], dtype=np.float32).reshape(3, 3)
|
||||
root_pose = pose[mano.orig_root_joint_idx, :].numpy()
|
||||
root_pose, _ = cv2.Rodrigues(root_pose)
|
||||
root_pose, _ = cv2.Rodrigues(np.dot(R, root_pose))
|
||||
pose[mano.orig_root_joint_idx] = torch.from_numpy(root_pose).view(3)
|
||||
|
||||
# get mesh and joint coordinates
|
||||
root_pose = pose[mano.orig_root_joint_idx].view(1, 3)
|
||||
hand_pose = torch.cat((pose[:mano.orig_root_joint_idx, :], pose[mano.orig_root_joint_idx + 1:, :])).view(1, -1)
|
||||
with torch.no_grad():
|
||||
output = mano.layer[hand_type](betas=shape, hand_pose=hand_pose, global_orient=root_pose, transl=trans)
|
||||
mesh_cam = output.vertices[0].numpy()
|
||||
joint_cam = np.dot(mano.joint_regressor, mesh_cam)
|
||||
|
||||
# apply camera exrinsic (translation)
|
||||
# compenstate rotation (translation from origin to root joint was not cancled)
|
||||
if 'R' in cam_param and 't' in cam_param:
|
||||
R, t = np.array(cam_param['R'], dtype=np.float32).reshape(3, 3), np.array(cam_param['t'],
|
||||
dtype=np.float32).reshape(1, 3)
|
||||
root_cam = joint_cam[mano.root_joint_idx, None, :]
|
||||
joint_cam = joint_cam - root_cam + np.dot(R, root_cam.transpose(1, 0)).transpose(1, 0) + t
|
||||
mesh_cam = mesh_cam - root_cam + np.dot(R, root_cam.transpose(1, 0)).transpose(1, 0) + t
|
||||
|
||||
# joint coordinates
|
||||
if 'focal' not in cam_param or 'princpt' not in cam_param:
|
||||
assert joint_img is not None
|
||||
else:
|
||||
joint_img = cam2pixel(joint_cam, cam_param['focal'], cam_param['princpt'])
|
||||
joint_cam = joint_cam - joint_cam[mano.root_joint_idx, None, :] # hand root-relative
|
||||
joint_img[:, 2] = (joint_cam[:, 2].copy() / (cfg.hand_3d_size / 2) + 1) / 2. * cfg.output_hm_shape[
|
||||
0] # hand depth discretize
|
||||
|
||||
mesh_cam_orig = mesh_cam.copy() # back-up the original one
|
||||
|
||||
## so far, data augmentations are not applied yet
|
||||
## now, apply data augmentations
|
||||
|
||||
# image projection
|
||||
if do_flip:
|
||||
joint_cam[:, 0] = -joint_cam[:, 0]
|
||||
joint_img[:, 0] = img_shape[1] - 1 - joint_img[:, 0]
|
||||
for pair in human_model.flip_pairs:
|
||||
joint_cam[pair[0], :], joint_cam[pair[1], :] = joint_cam[pair[1], :].copy(), joint_cam[pair[0], :].copy()
|
||||
joint_img[pair[0], :], joint_img[pair[1], :] = joint_img[pair[1], :].copy(), joint_img[pair[0], :].copy()
|
||||
if human_model_type == 'smplx':
|
||||
coord_valid[pair[0]], coord_valid[pair[1]] = coord_valid[pair[1]].copy(), coord_valid[pair[0]].copy()
|
||||
|
||||
# x,y affine transform, root-relative depth
|
||||
joint_img_xy1 = np.concatenate((joint_img[:, :2], np.ones_like(joint_img[:, 0:1])), 1)
|
||||
joint_img[:, :2] = np.dot(img2bb_trans, joint_img_xy1.transpose(1, 0)).transpose(1, 0)[:, :2]
|
||||
joint_img[:, 0] = joint_img[:, 0] / cfg.input_img_shape[1] * cfg.output_hm_shape[2]
|
||||
joint_img[:, 1] = joint_img[:, 1] / cfg.input_img_shape[0] * cfg.output_hm_shape[1]
|
||||
|
||||
# check truncation
|
||||
# TODO
|
||||
joint_trunc = ((joint_img_original[:, 0] > 0) * (joint_img[:, 0] >= 0) * (joint_img[:, 0] < cfg.output_hm_shape[2]) * \
|
||||
(joint_img_original[:, 1] > 0) * (joint_img[:, 1] >= 0) * (joint_img[:, 1] < cfg.output_hm_shape[1]) * \
|
||||
(joint_img_original[:, 2] > 0) * (joint_img[:, 2] >= 0) * (joint_img[:, 2] < cfg.output_hm_shape[0])).reshape(-1, 1).astype(
|
||||
np.float32)
|
||||
|
||||
# 3D data rotation augmentation
|
||||
rot_aug_mat = np.array([[np.cos(np.deg2rad(-rot)), -np.sin(np.deg2rad(-rot)), 0],
|
||||
[np.sin(np.deg2rad(-rot)), np.cos(np.deg2rad(-rot)), 0],
|
||||
[0, 0, 1]], dtype=np.float32)
|
||||
# coordinate
|
||||
joint_cam = np.dot(rot_aug_mat, joint_cam.transpose(1, 0)).transpose(1, 0)
|
||||
# parameters
|
||||
# flip pose parameter (axis-angle)
|
||||
if do_flip:
|
||||
for pair in human_model.orig_flip_pairs:
|
||||
pose[pair[0], :], pose[pair[1], :] = pose[pair[1], :].clone(), pose[pair[0], :].clone()
|
||||
if human_model_type == 'smplx':
|
||||
rotation_valid[pair[0]], rotation_valid[pair[1]] = rotation_valid[pair[1]].copy(), rotation_valid[
|
||||
pair[0]].copy()
|
||||
pose[:, 1:3] *= -1 # multiply -1 to y and z axis of axis-angle
|
||||
|
||||
# rotate root pose
|
||||
pose = pose.numpy()
|
||||
root_pose = pose[human_model.orig_root_joint_idx, :]
|
||||
root_pose, _ = cv2.Rodrigues(root_pose)
|
||||
root_pose, _ = cv2.Rodrigues(np.dot(rot_aug_mat, root_pose))
|
||||
pose[human_model.orig_root_joint_idx] = root_pose.reshape(3)
|
||||
|
||||
# change to mean shape if beta is too far from it
|
||||
shape[(shape.abs() > 3).any(dim=1)] = 0.
|
||||
shape = shape.numpy().reshape(-1)
|
||||
|
||||
# return results
|
||||
if human_model_type == 'smplx':
|
||||
pose = pose.reshape(-1)
|
||||
expr = expr.numpy().reshape(-1)
|
||||
|
||||
### ### HARDCODE temp save vis for debug
|
||||
# POSE: torch.cat((root_pose, body_pose, lhand_pose, rhand_pose, jaw_pose))
|
||||
# pose_ = pose.numpy().copy()
|
||||
|
||||
# root_pose = torch.FloatTensor(pose[0:3]).view(1, 3) # (1,3)
|
||||
# body_pose = torch.FloatTensor(pose[3:66]).view(-1, 3) # (21,3)
|
||||
# lhand_pose = torch.FloatTensor(pose[66:111]).view(-1, 3) # (15,3)
|
||||
# rhand_pose = torch.FloatTensor(pose[111:156]).view(-1, 3) # (15,3)
|
||||
# jaw_pose = torch.FloatTensor(pose[156:159]).view(-1, 3) # (1,3)
|
||||
# shape = torch.FloatTensor(shape).view(1, -1) # SMPLX shape parameter
|
||||
# expr = torch.FloatTensor(expr).view(1, -1) # SMPLX expression parameter
|
||||
# trans = torch.FloatTensor(trans).view(1, -1) # translation vector
|
||||
|
||||
# with torch.no_grad():
|
||||
# output = smpl_x.layer[gender](betas=shape, body_pose=body_pose.view(1, -1), global_orient=root_pose,
|
||||
# transl=zero_pose, left_hand_pose=lhand_pose.view(1, -1),
|
||||
# right_hand_pose=rhand_pose.view(1, -1), jaw_pose=jaw_pose.view(1, -1),
|
||||
# leye_pose=zero_pose, reye_pose=zero_pose, expression=expr)
|
||||
# mesh_rot = output.vertices[0].numpy()
|
||||
# joint_rot = output.joints[0].numpy()[smpl_x.joint_idx, :]
|
||||
# return mesh_rot, joint_cam, joint_trunc, pose, shape, expr, rotation_valid, coord_valid, expr_valid, mesh_cam_orig, joint_cam_orig_
|
||||
|
||||
return joint_img, joint_cam, joint_trunc, pose, shape, expr, rotation_valid, coord_valid, expr_valid, mesh_cam_orig
|
||||
elif human_model_type == 'smpl':
|
||||
pose = pose.reshape(-1)
|
||||
return joint_img, joint_cam, joint_trunc, pose, shape, mesh_cam_orig
|
||||
elif human_model_type == 'mano':
|
||||
pose = pose.reshape(-1)
|
||||
return joint_img, joint_cam, joint_trunc, pose, shape, mesh_cam_orig
|
||||
|
||||
|
||||
def get_fitting_error_3D(db_joint, db_joint_from_fit, joint_valid):
|
||||
# mask coordinate
|
||||
db_joint = db_joint[np.tile(joint_valid, (1, 3)) == 1].reshape(-1, 3)
|
||||
db_joint_from_fit = db_joint_from_fit[np.tile(joint_valid, (1, 3)) == 1].reshape(-1, 3)
|
||||
|
||||
db_joint_from_fit = db_joint_from_fit - np.mean(db_joint_from_fit, 0)[None, :] + np.mean(db_joint, 0)[None,
|
||||
:] # translation alignment
|
||||
error = np.sqrt(np.sum((db_joint - db_joint_from_fit) ** 2, 1)).mean()
|
||||
return error
|
||||
|
||||
|
||||
def load_obj(file_name):
|
||||
v = []
|
||||
obj_file = open(file_name)
|
||||
for line in obj_file:
|
||||
words = line.split(' ')
|
||||
if words[0] == 'v':
|
||||
x, y, z = float(words[1]), float(words[2]), float(words[3])
|
||||
v.append(np.array([x, y, z]))
|
||||
return np.stack(v)
|
||||
|
||||
|
||||
def load_ply(file_name):
|
||||
plydata = PlyData.read(file_name)
|
||||
x = plydata['vertex']['x']
|
||||
y = plydata['vertex']['y']
|
||||
z = plydata['vertex']['z']
|
||||
v = np.stack((x, y, z), 1)
|
||||
return v
|
||||
|
||||
def resize_bbox(bbox, scale=1.2):
|
||||
if isinstance(bbox, list):
|
||||
x1, y1, x2, y2 = bbox[0], bbox[1], bbox[2], bbox[3]
|
||||
else:
|
||||
x1, y1, x2, y2 = bbox
|
||||
x_center = (x1+x2)/2.0
|
||||
y_center = (y1+y2)/2.0
|
||||
x_size, y_size = x2-x1, y2-y1
|
||||
x1_resize = x_center-x_size/2.0*scale
|
||||
x2_resize = x_center+x_size/2.0*scale
|
||||
y1_resize = y_center - y_size / 2.0 * scale
|
||||
y2_resize = y_center + y_size / 2.0 * scale
|
||||
bbox[0], bbox[1], bbox[2], bbox[3] = x1_resize, y1_resize, x2_resize, y2_resize
|
||||
return bbox
|
||||
@@ -0,0 +1,58 @@
|
||||
License
|
||||
|
||||
Software Copyright License for non-commercial scientific research purposes
|
||||
Please read carefully the following terms and conditions and any accompanying documentation before you download and/or use the SMPL-X/SMPLify-X model, data and software, (the "Model & Software"), including 3D meshes, blend weights, blend shapes, textures, software, scripts, and animations. By downloading and/or using the Model & Software (including downloading, cloning, installing, and any other use of this github repository), you acknowledge that you have read these terms and conditions, understand them, and agree to be bound by them. If you do not agree with these terms and conditions, you must not download and/or use the Model & Software. Any infringement of the terms of this agreement will automatically terminate your rights under this License
|
||||
|
||||
Ownership / Licensees
|
||||
The Software and the associated materials has been developed at the
|
||||
|
||||
Max Planck Institute for Intelligent Systems (hereinafter "MPI").
|
||||
|
||||
Any copyright or patent right is owned by and proprietary material of the
|
||||
|
||||
Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (hereinafter “MPG”; MPI and MPG hereinafter collectively “Max-Planck”)
|
||||
|
||||
hereinafter the “Licensor”.
|
||||
|
||||
License Grant
|
||||
Licensor grants you (Licensee) personally a single-user, non-exclusive, non-transferable, free of charge right:
|
||||
|
||||
To install the Model & Software on computers owned, leased or otherwise controlled by you and/or your organization;
|
||||
To use the Model & Software for the sole purpose of performing non-commercial scientific research, non-commercial education, or non-commercial artistic projects;
|
||||
Any other use, in particular any use for commercial purposes, is prohibited. This includes, without limitation, incorporation in a commercial product, use in a commercial service, or production of other artifacts for commercial purposes. The Model & Software may not be reproduced, modified and/or made available in any form to any third party without Max-Planck’s prior written permission.
|
||||
|
||||
The Model & Software may not be used for pornographic purposes or to generate pornographic material whether commercial or not. This license also prohibits the use of the Model & Software to train methods/algorithms/neural networks/etc. for commercial use of any kind. By downloading the Model & Software, you agree not to reverse engineer it.
|
||||
|
||||
No Distribution
|
||||
The Model & Software and the license herein granted shall not be copied, shared, distributed, re-sold, offered for re-sale, transferred or sub-licensed in whole or in part except that you may make one copy for archive purposes only.
|
||||
|
||||
Disclaimer of Representations and Warranties
|
||||
You expressly acknowledge and agree that the Model & Software results from basic research, is provided “AS IS”, may contain errors, and that any use of the Model & Software is at your sole risk. LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE MODEL & SOFTWARE, NEITHER EXPRESS NOR IMPLIED, AND THE ABSENCE OF ANY LEGAL OR ACTUAL DEFECTS, WHETHER DISCOVERABLE OR NOT. Specifically, and not to limit the foregoing, licensor makes no representations or warranties (i) regarding the merchantability or fitness for a particular purpose of the Model & Software, (ii) that the use of the Model & Software will not infringe any patents, copyrights or other intellectual property rights of a third party, and (iii) that the use of the Model & Software will not cause any damage of any kind to you or a third party.
|
||||
|
||||
Limitation of Liability
|
||||
Because this Model & Software License Agreement qualifies as a donation, according to Section 521 of the German Civil Code (Bürgerliches Gesetzbuch – BGB) Licensor as a donor is liable for intent and gross negligence only. If the Licensor fraudulently conceals a legal or material defect, they are obliged to compensate the Licensee for the resulting damage.
|
||||
Licensor shall be liable for loss of data only up to the amount of typical recovery costs which would have arisen had proper and regular data backup measures been taken. For the avoidance of doubt Licensor shall be liable in accordance with the German Product Liability Act in the event of product liability. The foregoing applies also to Licensor’s legal representatives or assistants in performance. Any further liability shall be excluded.
|
||||
Patent claims generated through the usage of the Model & Software cannot be directed towards the copyright holders.
|
||||
The Model & Software is provided in the state of development the licensor defines. If modified or extended by Licensee, the Licensor makes no claims about the fitness of the Model & Software and is not responsible for any problems such modifications cause.
|
||||
|
||||
No Maintenance Services
|
||||
You understand and agree that Licensor is under no obligation to provide either maintenance services, update services, notices of latent defects, or corrections of defects with regard to the Model & Software. Licensor nevertheless reserves the right to update, modify, or discontinue the Model & Software at any time.
|
||||
|
||||
Defects of the Model & Software must be notified in writing to the Licensor with a comprehensible description of the error symptoms. The notification of the defect should enable the reproduction of the error. The Licensee is encouraged to communicate any use, results, modification or publication.
|
||||
|
||||
Publications using the Model & Software
|
||||
You acknowledge that the Model & Software is a valuable scientific resource and agree to appropriately reference the following paper in any publication making use of the Model & Software.
|
||||
|
||||
Citation:
|
||||
|
||||
|
||||
@inproceedings{SMPL-X:2019,
|
||||
title = {Expressive Body Capture: 3D Hands, Face, and Body from a Single Image},
|
||||
author = {Pavlakos, Georgios and Choutas, Vasileios and Ghorbani, Nima and Bolkart, Timo and Osman, Ahmed A. A. and Tzionas, Dimitrios and Black, Michael J.},
|
||||
booktitle = {Proceedings IEEE Conf. on Computer Vision and Pattern Recognition (CVPR)},
|
||||
year = {2019}
|
||||
}
|
||||
Commercial licensing opportunities
|
||||
For commercial uses of the Software, please send email to ps-license@tue.mpg.de
|
||||
|
||||
This Agreement shall be governed by the laws of the Federal Republic of Germany except for the UN Sales Convention.
|
||||
@@ -0,0 +1,186 @@
|
||||
## SMPL-X: A new joint 3D model of the human body, face and hands together
|
||||
|
||||
[[Paper Page](https://smpl-x.is.tue.mpg.de)] [[Paper](https://ps.is.tuebingen.mpg.de/uploads_file/attachment/attachment/497/SMPL-X.pdf)]
|
||||
[[Supp. Mat.](https://ps.is.tuebingen.mpg.de/uploads_file/attachment/attachment/498/SMPL-X-supp.pdf)]
|
||||
|
||||

|
||||
|
||||
## Table of Contents
|
||||
* [License](#license)
|
||||
* [Description](#description)
|
||||
* [Installation](#installation)
|
||||
* [Downloading the model](#downloading-the-model)
|
||||
* [Loading SMPL-X, SMPL+H and SMPL](#loading-smpl-x-smplh-and-smpl)
|
||||
* [SMPL and SMPL+H setup](#smpl-and-smplh-setup)
|
||||
* [Model loading](https://github.com/vchoutas/smplx#model-loading)
|
||||
* [MANO and FLAME correspondences](#mano-and-flame-correspondences)
|
||||
* [Example](#example)
|
||||
* [Citation](#citation)
|
||||
* [Acknowledgments](#acknowledgments)
|
||||
* [Contact](#contact)
|
||||
|
||||
## License
|
||||
|
||||
Software Copyright License for **non-commercial scientific research purposes**.
|
||||
Please read carefully the [terms and conditions](https://github.com/vchoutas/smplx/blob/master/LICENSE) and any accompanying documentation before you download and/or use the SMPL-X/SMPLify-X model, data and software, (the "Model & Software"), including 3D meshes, blend weights, blend shapes, textures, software, scripts, and animations. By downloading and/or using the Model & Software (including downloading, cloning, installing, and any other use of this github repository), you acknowledge that you have read these terms and conditions, understand them, and agree to be bound by them. If you do not agree with these terms and conditions, you must not download and/or use the Model & Software. Any infringement of the terms of this agreement will automatically terminate your rights under this [License](./LICENSE).
|
||||
|
||||
## Disclaimer
|
||||
|
||||
The original images used for the figures 1 and 2 of the paper can be found in this link.
|
||||
The images in the paper are used under license from gettyimages.com.
|
||||
We have acquired the right to use them in the publication, but redistribution is not allowed.
|
||||
Please follow the instructions on the given link to acquire right of usage.
|
||||
Our results are obtained on the 483 × 724 pixels resolution of the original images.
|
||||
|
||||
## Description
|
||||
|
||||
*SMPL-X* (SMPL eXpressive) is a unified body model with shape parameters trained jointly for the
|
||||
face, hands and body. *SMPL-X* uses standard vertex based linear blend skinning with learned corrective blend
|
||||
shapes, has N = 10, 475 vertices and K = 54 joints,
|
||||
which include joints for the neck, jaw, eyeballs and fingers.
|
||||
SMPL-X is defined by a function M(θ, β, ψ), where θ is the pose parameters, β the shape parameters and
|
||||
ψ the facial expression parameters.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
To install the model please follow the next steps in the specified order:
|
||||
1. To install from PyPi simply run:
|
||||
```Shell
|
||||
pip install smplx[all]
|
||||
```
|
||||
2. Clone this repository and install it using the *setup.py* script:
|
||||
```Shell
|
||||
git clone https://github.com/vchoutas/smplx
|
||||
python setup.py install
|
||||
```
|
||||
|
||||
## Downloading the model
|
||||
|
||||
To download the *SMPL-X* model go to [this project website](https://smpl-x.is.tue.mpg.de) and register to get access to the downloads section.
|
||||
|
||||
To download the *SMPL+H* model go to [this project website](http://mano.is.tue.mpg.de) and register to get access to the downloads section.
|
||||
|
||||
To download the *SMPL* model go to [this](http://smpl.is.tue.mpg.de) (male and female models) and [this](http://smplify.is.tue.mpg.de) (gender neutral model) project website and register to get access to the downloads section.
|
||||
|
||||
## Loading SMPL-X, SMPL+H and SMPL
|
||||
|
||||
### SMPL and SMPL+H setup
|
||||
|
||||
The loader gives the option to use any of the SMPL-X, SMPL+H, SMPL, and MANO models. Depending on the model you want to use, please follow the respective download instructions. To switch between MANO, SMPL, SMPL+H and SMPL-X just change the *model_path* or *model_type* parameters. For more details please check the docs of the model classes.
|
||||
Before using SMPL and SMPL+H you should follow the instructions in [tools/README.md](./tools/README.md) to remove the
|
||||
Chumpy objects from both model pkls, as well as merge the MANO parameters with SMPL+H.
|
||||
|
||||
### Model loading
|
||||
|
||||
You can either use the [create](https://github.com/vchoutas/smplx/blob/c63c02b478c5c6f696491ed9167e3af6b08d89b1/smplx/body_models.py#L54)
|
||||
function from [body_models](./smplx/body_models.py) or directly call the constructor for the
|
||||
[SMPL](https://github.com/vchoutas/smplx/blob/c63c02b478c5c6f696491ed9167e3af6b08d89b1/smplx/body_models.py#L106),
|
||||
[SMPL+H](https://github.com/vchoutas/smplx/blob/c63c02b478c5c6f696491ed9167e3af6b08d89b1/smplx/body_models.py#L395) and
|
||||
[SMPL-X](https://github.com/vchoutas/smplx/blob/c63c02b478c5c6f696491ed9167e3af6b08d89b1/smplx/body_models.py#L628) model. The path to the model can either be the path to the file with the parameters or a directory with the following structure:
|
||||
```bash
|
||||
models
|
||||
├── smpl
|
||||
│ ├── SMPL_FEMALE.pkl
|
||||
│ └── SMPL_MALE.pkl
|
||||
│ └── SMPL_NEUTRAL.pkl
|
||||
├── smplh
|
||||
│ ├── SMPLH_FEMALE.pkl
|
||||
│ └── SMPLH_MALE.pkl
|
||||
├── mano
|
||||
| ├── MANO_RIGHT.pkl
|
||||
| └── MANO_LEFT.pkl
|
||||
└── smplx
|
||||
├── SMPLX_FEMALE.npz
|
||||
├── SMPLX_FEMALE.pkl
|
||||
├── SMPLX_MALE.npz
|
||||
├── SMPLX_MALE.pkl
|
||||
├── SMPLX_NEUTRAL.npz
|
||||
└── SMPLX_NEUTRAL.pkl
|
||||
```
|
||||
|
||||
|
||||
## MANO and FLAME correspondences
|
||||
|
||||
The vertex correspondences between SMPL-X and MANO, FLAME can be downloaded
|
||||
from [the project website](https://smpl-x.is.tue.mpg.de). If you have extracted
|
||||
the correspondence data in the folder *correspondences*, then use the following
|
||||
scripts to visualize them:
|
||||
|
||||
1. To view MANO correspondences run the following command:
|
||||
|
||||
```
|
||||
python examples/vis_mano_vertices.py --model-folder $SMPLX_FOLDER --corr-fname correspondences/MANO_SMPLX_vertex_ids.pkl
|
||||
```
|
||||
|
||||
2. To view FLAME correspondences run the following command:
|
||||
|
||||
```
|
||||
python examples/vis_flame_vertices.py --model-folder $SMPLX_FOLDER --corr-fname correspondences/SMPL-X__FLAME_vertex_ids.npy
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
After installing the *smplx* package and downloading the model parameters you should be able to run the *demo.py*
|
||||
script to visualize the results. For this step you have to install the [pyrender](https://pyrender.readthedocs.io/en/latest/index.html) and [trimesh](https://trimsh.org/) packages.
|
||||
|
||||
`python examples/demo.py --model-folder $SMPLX_FOLDER --plot-joints=True --gender="neutral"`
|
||||
|
||||

|
||||
|
||||
## Citation
|
||||
|
||||
Depending on which model is loaded for your project, i.e. SMPL-X or SMPL+H or SMPL, please cite the most relevant work below, listed in the same order:
|
||||
|
||||
```
|
||||
@inproceedings{SMPL-X:2019,
|
||||
title = {Expressive Body Capture: 3D Hands, Face, and Body from a Single Image},
|
||||
author = {Pavlakos, Georgios and Choutas, Vasileios and Ghorbani, Nima and Bolkart, Timo and Osman, Ahmed A. A. and Tzionas, Dimitrios and Black, Michael J.},
|
||||
booktitle = {Proceedings IEEE Conf. on Computer Vision and Pattern Recognition (CVPR)},
|
||||
year = {2019}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
@article{MANO:SIGGRAPHASIA:2017,
|
||||
title = {Embodied Hands: Modeling and Capturing Hands and Bodies Together},
|
||||
author = {Romero, Javier and Tzionas, Dimitrios and Black, Michael J.},
|
||||
journal = {ACM Transactions on Graphics, (Proc. SIGGRAPH Asia)},
|
||||
volume = {36},
|
||||
number = {6},
|
||||
series = {245:1--245:17},
|
||||
month = nov,
|
||||
year = {2017},
|
||||
month_numeric = {11}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
@article{SMPL:2015,
|
||||
author = {Loper, Matthew and Mahmood, Naureen and Romero, Javier and Pons-Moll, Gerard and Black, Michael J.},
|
||||
title = {{SMPL}: A Skinned Multi-Person Linear Model},
|
||||
journal = {ACM Transactions on Graphics, (Proc. SIGGRAPH Asia)},
|
||||
month = oct,
|
||||
number = {6},
|
||||
pages = {248:1--248:16},
|
||||
publisher = {ACM},
|
||||
volume = {34},
|
||||
year = {2015}
|
||||
}
|
||||
```
|
||||
|
||||
This repository was originally developed for SMPL-X / SMPLify-X (CVPR 2019), you might be interested in having a look: [https://smpl-x.is.tue.mpg.de](https://smpl-x.is.tue.mpg.de).
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
### Facial Contour
|
||||
|
||||
Special thanks to [Soubhik Sanyal](https://github.com/soubhiksanyal) for sharing the Tensorflow code used for the facial
|
||||
landmarks.
|
||||
|
||||
## Contact
|
||||
The code of this repository was implemented by [Vassilis Choutas](vassilis.choutas@tuebingen.mpg.de).
|
||||
|
||||
For questions, please contact [smplx@tue.mpg.de](smplx@tue.mpg.de).
|
||||
|
||||
For commercial licensing (and all related questions for business applications), please contact [ps-licensing@tue.mpg.de](ps-licensing@tue.mpg.de).
|
||||
@@ -0,0 +1,180 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
import os.path as osp
|
||||
import argparse
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
import smplx
|
||||
|
||||
|
||||
def main(model_folder,
|
||||
model_type='smplx',
|
||||
ext='npz',
|
||||
gender='neutral',
|
||||
plot_joints=False,
|
||||
num_betas=10,
|
||||
sample_shape=True,
|
||||
sample_expression=True,
|
||||
num_expression_coeffs=10,
|
||||
plotting_module='pyrender',
|
||||
use_face_contour=False):
|
||||
|
||||
model = smplx.create(model_folder, model_type=model_type,
|
||||
gender=gender, use_face_contour=use_face_contour,
|
||||
num_betas=num_betas,
|
||||
num_expression_coeffs=num_expression_coeffs,
|
||||
ext=ext)
|
||||
print(model)
|
||||
|
||||
betas, expression = None, None
|
||||
if sample_shape:
|
||||
betas = torch.randn([1, model.num_betas], dtype=torch.float32)
|
||||
if sample_expression:
|
||||
expression = torch.randn(
|
||||
[1, model.num_expression_coeffs], dtype=torch.float32)
|
||||
|
||||
output = model(betas=betas, expression=expression,
|
||||
return_verts=True)
|
||||
vertices = output.vertices.detach().cpu().numpy().squeeze()
|
||||
joints = output.joints.detach().cpu().numpy().squeeze()
|
||||
|
||||
print('Vertices shape =', vertices.shape)
|
||||
print('Joints shape =', joints.shape)
|
||||
|
||||
if plotting_module == 'pyrender':
|
||||
import pyrender
|
||||
import trimesh
|
||||
vertex_colors = np.ones([vertices.shape[0], 4]) * [0.3, 0.3, 0.3, 0.8]
|
||||
tri_mesh = trimesh.Trimesh(vertices, model.faces,
|
||||
vertex_colors=vertex_colors)
|
||||
|
||||
mesh = pyrender.Mesh.from_trimesh(tri_mesh)
|
||||
|
||||
scene = pyrender.Scene()
|
||||
scene.add(mesh)
|
||||
|
||||
if plot_joints:
|
||||
sm = trimesh.creation.uv_sphere(radius=0.005)
|
||||
sm.visual.vertex_colors = [0.9, 0.1, 0.1, 1.0]
|
||||
tfs = np.tile(np.eye(4), (len(joints), 1, 1))
|
||||
tfs[:, :3, 3] = joints
|
||||
joints_pcl = pyrender.Mesh.from_trimesh(sm, poses=tfs)
|
||||
scene.add(joints_pcl)
|
||||
|
||||
pyrender.Viewer(scene, use_raymond_lighting=True)
|
||||
elif plotting_module == 'matplotlib':
|
||||
from matplotlib import pyplot as plt
|
||||
from mpl_toolkits.mplot3d import Axes3D
|
||||
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
|
||||
|
||||
fig = plt.figure()
|
||||
ax = fig.add_subplot(111, projection='3d')
|
||||
|
||||
mesh = Poly3DCollection(vertices[model.faces], alpha=0.1)
|
||||
face_color = (1.0, 1.0, 0.9)
|
||||
edge_color = (0, 0, 0)
|
||||
mesh.set_edgecolor(edge_color)
|
||||
mesh.set_facecolor(face_color)
|
||||
ax.add_collection3d(mesh)
|
||||
ax.scatter(joints[:, 0], joints[:, 1], joints[:, 2], color='r')
|
||||
|
||||
if plot_joints:
|
||||
ax.scatter(joints[:, 0], joints[:, 1], joints[:, 2], alpha=0.1)
|
||||
plt.show()
|
||||
elif plotting_module == 'open3d':
|
||||
import open3d as o3d
|
||||
|
||||
mesh = o3d.geometry.TriangleMesh()
|
||||
mesh.vertices = o3d.utility.Vector3dVector(
|
||||
vertices)
|
||||
mesh.triangles = o3d.utility.Vector3iVector(model.faces)
|
||||
mesh.compute_vertex_normals()
|
||||
mesh.paint_uniform_color([0.3, 0.3, 0.3])
|
||||
|
||||
geometry = [mesh]
|
||||
if plot_joints:
|
||||
joints_pcl = o3d.geometry.PointCloud()
|
||||
joints_pcl.points = o3d.utility.Vector3dVector(joints)
|
||||
joints_pcl.paint_uniform_color([0.7, 0.3, 0.3])
|
||||
geometry.append(joints_pcl)
|
||||
|
||||
o3d.visualization.draw_geometries(geometry)
|
||||
else:
|
||||
raise ValueError('Unknown plotting_module: {}'.format(plotting_module))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='SMPL-X Demo')
|
||||
|
||||
parser.add_argument('--model-folder', required=True, type=str,
|
||||
help='The path to the model folder')
|
||||
parser.add_argument('--model-type', default='smplx', type=str,
|
||||
choices=['smpl', 'smplh', 'smplx', 'mano', 'flame'],
|
||||
help='The type of model to load')
|
||||
parser.add_argument('--gender', type=str, default='neutral',
|
||||
help='The gender of the model')
|
||||
parser.add_argument('--num-betas', default=10, type=int,
|
||||
dest='num_betas',
|
||||
help='Number of shape coefficients.')
|
||||
parser.add_argument('--num-expression-coeffs', default=10, type=int,
|
||||
dest='num_expression_coeffs',
|
||||
help='Number of expression coefficients.')
|
||||
parser.add_argument('--plotting-module', type=str, default='pyrender',
|
||||
dest='plotting_module',
|
||||
choices=['pyrender', 'matplotlib', 'open3d'],
|
||||
help='The module to use for plotting the result')
|
||||
parser.add_argument('--ext', type=str, default='npz',
|
||||
help='Which extension to use for loading')
|
||||
parser.add_argument('--plot-joints', default=False,
|
||||
type=lambda arg: arg.lower() in ['true', '1'],
|
||||
help='The path to the model folder')
|
||||
parser.add_argument('--sample-shape', default=True,
|
||||
dest='sample_shape',
|
||||
type=lambda arg: arg.lower() in ['true', '1'],
|
||||
help='Sample a random shape')
|
||||
parser.add_argument('--sample-expression', default=True,
|
||||
dest='sample_expression',
|
||||
type=lambda arg: arg.lower() in ['true', '1'],
|
||||
help='Sample a random expression')
|
||||
parser.add_argument('--use-face-contour', default=False,
|
||||
type=lambda arg: arg.lower() in ['true', '1'],
|
||||
help='Compute the contour of the face')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
model_folder = osp.expanduser(osp.expandvars(args.model_folder))
|
||||
model_type = args.model_type
|
||||
plot_joints = args.plot_joints
|
||||
use_face_contour = args.use_face_contour
|
||||
gender = args.gender
|
||||
ext = args.ext
|
||||
plotting_module = args.plotting_module
|
||||
num_betas = args.num_betas
|
||||
num_expression_coeffs = args.num_expression_coeffs
|
||||
sample_shape = args.sample_shape
|
||||
sample_expression = args.sample_expression
|
||||
|
||||
main(model_folder, model_type, ext=ext,
|
||||
gender=gender, plot_joints=plot_joints,
|
||||
num_betas=num_betas,
|
||||
num_expression_coeffs=num_expression_coeffs,
|
||||
sample_shape=sample_shape,
|
||||
sample_expression=sample_expression,
|
||||
plotting_module=plotting_module,
|
||||
use_face_contour=use_face_contour)
|
||||
@@ -0,0 +1,181 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
import os.path as osp
|
||||
import argparse
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
import smplx
|
||||
|
||||
|
||||
def main(model_folder,
|
||||
model_type='smplx',
|
||||
ext='npz',
|
||||
gender='neutral',
|
||||
plot_joints=False,
|
||||
num_betas=10,
|
||||
sample_shape=True,
|
||||
sample_expression=True,
|
||||
num_expression_coeffs=10,
|
||||
plotting_module='pyrender',
|
||||
use_face_contour=False):
|
||||
|
||||
model = smplx.build_layer(
|
||||
model_folder, model_type=model_type,
|
||||
gender=gender, use_face_contour=use_face_contour,
|
||||
num_betas=num_betas,
|
||||
num_expression_coeffs=num_expression_coeffs,
|
||||
ext=ext)
|
||||
print(model)
|
||||
|
||||
betas, expression = None, None
|
||||
if sample_shape:
|
||||
betas = torch.randn([1, model.num_betas], dtype=torch.float32)
|
||||
if sample_expression:
|
||||
expression = torch.randn(
|
||||
[1, model.num_expression_coeffs], dtype=torch.float32)
|
||||
|
||||
output = model(betas=betas, expression=expression,
|
||||
return_verts=True)
|
||||
vertices = output.vertices.detach().cpu().numpy().squeeze()
|
||||
joints = output.joints.detach().cpu().numpy().squeeze()
|
||||
|
||||
print('Vertices shape =', vertices.shape)
|
||||
print('Joints shape =', joints.shape)
|
||||
|
||||
if plotting_module == 'pyrender':
|
||||
import pyrender
|
||||
import trimesh
|
||||
vertex_colors = np.ones([vertices.shape[0], 4]) * [0.3, 0.3, 0.3, 0.8]
|
||||
tri_mesh = trimesh.Trimesh(vertices, model.faces,
|
||||
vertex_colors=vertex_colors)
|
||||
|
||||
mesh = pyrender.Mesh.from_trimesh(tri_mesh)
|
||||
|
||||
scene = pyrender.Scene()
|
||||
scene.add(mesh)
|
||||
|
||||
if plot_joints:
|
||||
sm = trimesh.creation.uv_sphere(radius=0.005)
|
||||
sm.visual.vertex_colors = [0.9, 0.1, 0.1, 1.0]
|
||||
tfs = np.tile(np.eye(4), (len(joints), 1, 1))
|
||||
tfs[:, :3, 3] = joints
|
||||
joints_pcl = pyrender.Mesh.from_trimesh(sm, poses=tfs)
|
||||
scene.add(joints_pcl)
|
||||
|
||||
pyrender.Viewer(scene, use_raymond_lighting=True)
|
||||
elif plotting_module == 'matplotlib':
|
||||
from matplotlib import pyplot as plt
|
||||
from mpl_toolkits.mplot3d import Axes3D
|
||||
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
|
||||
|
||||
fig = plt.figure()
|
||||
ax = fig.add_subplot(111, projection='3d')
|
||||
|
||||
mesh = Poly3DCollection(vertices[model.faces], alpha=0.1)
|
||||
face_color = (1.0, 1.0, 0.9)
|
||||
edge_color = (0, 0, 0)
|
||||
mesh.set_edgecolor(edge_color)
|
||||
mesh.set_facecolor(face_color)
|
||||
ax.add_collection3d(mesh)
|
||||
ax.scatter(joints[:, 0], joints[:, 1], joints[:, 2], color='r')
|
||||
|
||||
if plot_joints:
|
||||
ax.scatter(joints[:, 0], joints[:, 1], joints[:, 2], alpha=0.1)
|
||||
plt.show()
|
||||
elif plotting_module == 'open3d':
|
||||
import open3d as o3d
|
||||
|
||||
mesh = o3d.geometry.TriangleMesh()
|
||||
mesh.vertices = o3d.utility.Vector3dVector(
|
||||
vertices)
|
||||
mesh.triangles = o3d.utility.Vector3iVector(model.faces)
|
||||
mesh.compute_vertex_normals()
|
||||
mesh.paint_uniform_color([0.3, 0.3, 0.3])
|
||||
|
||||
geometry = [mesh]
|
||||
if plot_joints:
|
||||
joints_pcl = o3d.geometry.PointCloud()
|
||||
joints_pcl.points = o3d.utility.Vector3dVector(joints)
|
||||
joints_pcl.paint_uniform_color([0.7, 0.3, 0.3])
|
||||
geometry.append(joints_pcl)
|
||||
|
||||
o3d.visualization.draw_geometries(geometry)
|
||||
else:
|
||||
raise ValueError('Unknown plotting_module: {}'.format(plotting_module))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='SMPL-X Demo')
|
||||
|
||||
parser.add_argument('--model-folder', required=True, type=str,
|
||||
help='The path to the model folder')
|
||||
parser.add_argument('--model-type', default='smplx', type=str,
|
||||
choices=['smpl', 'smplh', 'smplx', 'mano', 'flame'],
|
||||
help='The type of model to load')
|
||||
parser.add_argument('--gender', type=str, default='neutral',
|
||||
help='The gender of the model')
|
||||
parser.add_argument('--num-betas', default=10, type=int,
|
||||
dest='num_betas',
|
||||
help='Number of shape coefficients.')
|
||||
parser.add_argument('--num-expression-coeffs', default=10, type=int,
|
||||
dest='num_expression_coeffs',
|
||||
help='Number of expression coefficients.')
|
||||
parser.add_argument('--plotting-module', type=str, default='pyrender',
|
||||
dest='plotting_module',
|
||||
choices=['pyrender', 'matplotlib', 'open3d'],
|
||||
help='The module to use for plotting the result')
|
||||
parser.add_argument('--ext', type=str, default='npz',
|
||||
help='Which extension to use for loading')
|
||||
parser.add_argument('--plot-joints', default=False,
|
||||
type=lambda arg: arg.lower() in ['true', '1'],
|
||||
help='The path to the model folder')
|
||||
parser.add_argument('--sample-shape', default=True,
|
||||
dest='sample_shape',
|
||||
type=lambda arg: arg.lower() in ['true', '1'],
|
||||
help='Sample a random shape')
|
||||
parser.add_argument('--sample-expression', default=True,
|
||||
dest='sample_expression',
|
||||
type=lambda arg: arg.lower() in ['true', '1'],
|
||||
help='Sample a random expression')
|
||||
parser.add_argument('--use-face-contour', default=False,
|
||||
type=lambda arg: arg.lower() in ['true', '1'],
|
||||
help='Compute the contour of the face')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
model_folder = osp.expanduser(osp.expandvars(args.model_folder))
|
||||
model_type = args.model_type
|
||||
plot_joints = args.plot_joints
|
||||
use_face_contour = args.use_face_contour
|
||||
gender = args.gender
|
||||
ext = args.ext
|
||||
plotting_module = args.plotting_module
|
||||
num_betas = args.num_betas
|
||||
num_expression_coeffs = args.num_expression_coeffs
|
||||
sample_shape = args.sample_shape
|
||||
sample_expression = args.sample_expression
|
||||
|
||||
main(model_folder, model_type, ext=ext,
|
||||
gender=gender, plot_joints=plot_joints,
|
||||
num_betas=num_betas,
|
||||
num_expression_coeffs=num_expression_coeffs,
|
||||
sample_shape=sample_shape,
|
||||
sample_expression=sample_expression,
|
||||
plotting_module=plotting_module,
|
||||
use_face_contour=use_face_contour)
|
||||
@@ -0,0 +1,92 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
import os.path as osp
|
||||
import argparse
|
||||
import pickle
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import open3d as o3d
|
||||
|
||||
import smplx
|
||||
|
||||
|
||||
def main(model_folder, corr_fname, ext='npz',
|
||||
head_color=(0.3, 0.3, 0.6),
|
||||
gender='neutral'):
|
||||
|
||||
head_idxs = np.load(corr_fname)
|
||||
|
||||
model = smplx.create(model_folder, model_type='smplx',
|
||||
gender=gender,
|
||||
ext=ext)
|
||||
betas = torch.zeros([1, 10], dtype=torch.float32)
|
||||
expression = torch.zeros([1, 10], dtype=torch.float32)
|
||||
|
||||
output = model(betas=betas, expression=expression,
|
||||
return_verts=True)
|
||||
vertices = output.vertices.detach().cpu().numpy().squeeze()
|
||||
joints = output.joints.detach().cpu().numpy().squeeze()
|
||||
|
||||
print('Vertices shape =', vertices.shape)
|
||||
print('Joints shape =', joints.shape)
|
||||
|
||||
mesh = o3d.geometry.TriangleMesh()
|
||||
mesh.vertices = o3d.utility.Vector3dVector(vertices)
|
||||
mesh.triangles = o3d.utility.Vector3iVector(model.faces)
|
||||
mesh.compute_vertex_normals()
|
||||
|
||||
colors = np.ones_like(vertices) * [0.3, 0.3, 0.3]
|
||||
colors[head_idxs] = head_color
|
||||
|
||||
mesh.vertex_colors = o3d.utility.Vector3dVector(colors)
|
||||
|
||||
o3d.visualization.draw_geometries([mesh])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='SMPL-X Demo')
|
||||
|
||||
parser.add_argument('--model-folder', required=True, type=str,
|
||||
help='The path to the model folder')
|
||||
parser.add_argument('--corr-fname', required=True, type=str,
|
||||
dest='corr_fname',
|
||||
help='Filename with the head correspondences')
|
||||
parser.add_argument('--gender', type=str, default='neutral',
|
||||
help='The gender of the model')
|
||||
parser.add_argument('--ext', type=str, default='npz',
|
||||
help='Which extension to use for loading')
|
||||
parser.add_argument('--head', default='right',
|
||||
choices=['right', 'left'],
|
||||
type=str, help='Which head to plot')
|
||||
parser.add_argument('--head-color', type=float, nargs=3, dest='head_color',
|
||||
default=(0.3, 0.3, 0.6),
|
||||
help='Color for the head vertices')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
model_folder = osp.expanduser(osp.expandvars(args.model_folder))
|
||||
corr_fname = args.corr_fname
|
||||
gender = args.gender
|
||||
ext = args.ext
|
||||
head = args.head
|
||||
head_color = args.head_color
|
||||
|
||||
main(model_folder, corr_fname, ext=ext,
|
||||
head_color=head_color,
|
||||
gender=gender
|
||||
)
|
||||
@@ -0,0 +1,99 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
import os.path as osp
|
||||
import argparse
|
||||
import pickle
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import open3d as o3d
|
||||
|
||||
import smplx
|
||||
|
||||
|
||||
def main(model_folder, corr_fname, ext='npz',
|
||||
hand_color=(0.3, 0.3, 0.6),
|
||||
gender='neutral', hand='right'):
|
||||
|
||||
with open(corr_fname, 'rb') as f:
|
||||
idxs_data = pickle.load(f)
|
||||
if hand == 'both':
|
||||
hand_idxs = np.concatenate(
|
||||
[idxs_data['left_hand'], idxs_data['right_hand']]
|
||||
)
|
||||
else:
|
||||
hand_idxs = idxs_data[f'{hand}_hand']
|
||||
|
||||
model = smplx.create(model_folder, model_type='smplx',
|
||||
gender=gender,
|
||||
ext=ext)
|
||||
betas = torch.zeros([1, 10], dtype=torch.float32)
|
||||
expression = torch.zeros([1, 10], dtype=torch.float32)
|
||||
|
||||
output = model(betas=betas, expression=expression,
|
||||
return_verts=True)
|
||||
vertices = output.vertices.detach().cpu().numpy().squeeze()
|
||||
joints = output.joints.detach().cpu().numpy().squeeze()
|
||||
|
||||
print('Vertices shape =', vertices.shape)
|
||||
print('Joints shape =', joints.shape)
|
||||
|
||||
mesh = o3d.geometry.TriangleMesh()
|
||||
mesh.vertices = o3d.utility.Vector3dVector(vertices)
|
||||
mesh.triangles = o3d.utility.Vector3iVector(model.faces)
|
||||
mesh.compute_vertex_normals()
|
||||
|
||||
colors = np.ones_like(vertices) * [0.3, 0.3, 0.3]
|
||||
colors[hand_idxs] = hand_color
|
||||
|
||||
mesh.vertex_colors = o3d.utility.Vector3dVector(colors)
|
||||
|
||||
o3d.visualization.draw_geometries([mesh])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='SMPL-X Demo')
|
||||
|
||||
parser.add_argument('--model-folder', required=True, type=str,
|
||||
help='The path to the model folder')
|
||||
parser.add_argument('--corr-fname', required=True, type=str,
|
||||
dest='corr_fname',
|
||||
help='Filename with the hand correspondences')
|
||||
parser.add_argument('--gender', type=str, default='neutral',
|
||||
help='The gender of the model')
|
||||
parser.add_argument('--ext', type=str, default='npz',
|
||||
help='Which extension to use for loading')
|
||||
parser.add_argument('--hand', default='right',
|
||||
choices=['right', 'left', 'both'],
|
||||
type=str, help='Which hand to plot')
|
||||
parser.add_argument('--hand-color', type=float, nargs=3, dest='hand_color',
|
||||
default=(0.3, 0.3, 0.6),
|
||||
help='Color for the hand vertices')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
model_folder = osp.expanduser(osp.expandvars(args.model_folder))
|
||||
corr_fname = args.corr_fname
|
||||
gender = args.gender
|
||||
ext = args.ext
|
||||
hand = args.hand
|
||||
hand_color = args.hand_color
|
||||
|
||||
main(model_folder, corr_fname, ext=ext,
|
||||
hand_color=hand_color,
|
||||
gender=gender, hand=hand
|
||||
)
|
||||
@@ -0,0 +1,79 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems and the Max Planck Institute for Biological
|
||||
# Cybernetics. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
import io
|
||||
import os
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
# Package meta-data.
|
||||
NAME = 'smplx'
|
||||
DESCRIPTION = 'PyTorch module for loading the SMPLX body model'
|
||||
URL = 'http://smpl-x.is.tuebingen.mpg.de'
|
||||
EMAIL = 'vassilis.choutas@tuebingen.mpg.de'
|
||||
AUTHOR = 'Vassilis Choutas'
|
||||
REQUIRES_PYTHON = '>=3.6.0'
|
||||
VERSION = '0.1.21'
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
try:
|
||||
FileNotFoundError
|
||||
except NameError:
|
||||
FileNotFoundError = IOError
|
||||
|
||||
# Import the README and use it as the long-description.
|
||||
# Note: this will only work if 'README.md' is present in your MANIFEST.in file!
|
||||
try:
|
||||
with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
|
||||
long_description = '\n' + f.read()
|
||||
except FileNotFoundError:
|
||||
long_description = DESCRIPTION
|
||||
|
||||
# Load the package's __version__.py module as a dictionary.
|
||||
about = {}
|
||||
if not VERSION:
|
||||
with open(os.path.join(here, NAME, '__version__.py')) as f:
|
||||
exec(f.read(), about)
|
||||
else:
|
||||
about['__version__'] = VERSION
|
||||
|
||||
pyrender_reqs = ['pyrender>=0.1.23', 'trimesh>=2.37.6', 'shapely']
|
||||
matplotlib_reqs = ['matplotlib']
|
||||
open3d_reqs = ['open3d-python']
|
||||
|
||||
setup(name=NAME,
|
||||
version=about['__version__'],
|
||||
description=DESCRIPTION,
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
author=AUTHOR,
|
||||
author_email=EMAIL,
|
||||
python_requires=REQUIRES_PYTHON,
|
||||
url=URL,
|
||||
install_requires=[
|
||||
'numpy>=1.16.2',
|
||||
'torch>=1.0.1.post2',
|
||||
'torchgeometry>=0.1.2'
|
||||
],
|
||||
extras_require={
|
||||
'pyrender': pyrender_reqs,
|
||||
'open3d': open3d_reqs,
|
||||
'matplotlib': matplotlib_reqs,
|
||||
'all': pyrender_reqs + matplotlib_reqs + open3d_reqs
|
||||
},
|
||||
packages=['smplx', 'tools'])
|
||||
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from .body_models import (
|
||||
create,
|
||||
SMPL,
|
||||
SMPLH,
|
||||
SMPLX,
|
||||
MANO,
|
||||
FLAME,
|
||||
build_layer,
|
||||
SMPLLayer,
|
||||
SMPLHLayer,
|
||||
SMPLXLayer,
|
||||
MANOLayer,
|
||||
FLAMELayer,
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,163 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
JOINT_NAMES = [
|
||||
'pelvis',
|
||||
'left_hip',
|
||||
'right_hip',
|
||||
'spine1',
|
||||
'left_knee',
|
||||
'right_knee',
|
||||
'spine2',
|
||||
'left_ankle',
|
||||
'right_ankle',
|
||||
'spine3',
|
||||
'left_foot',
|
||||
'right_foot',
|
||||
'neck',
|
||||
'left_collar',
|
||||
'right_collar',
|
||||
'head',
|
||||
'left_shoulder',
|
||||
'right_shoulder',
|
||||
'left_elbow',
|
||||
'right_elbow',
|
||||
'left_wrist',
|
||||
'right_wrist',
|
||||
'jaw',
|
||||
'left_eye_smplhf',
|
||||
'right_eye_smplhf',
|
||||
'left_index1',
|
||||
'left_index2',
|
||||
'left_index3',
|
||||
'left_middle1',
|
||||
'left_middle2',
|
||||
'left_middle3',
|
||||
'left_pinky1',
|
||||
'left_pinky2',
|
||||
'left_pinky3',
|
||||
'left_ring1',
|
||||
'left_ring2',
|
||||
'left_ring3',
|
||||
'left_thumb1',
|
||||
'left_thumb2',
|
||||
'left_thumb3',
|
||||
'right_index1',
|
||||
'right_index2',
|
||||
'right_index3',
|
||||
'right_middle1',
|
||||
'right_middle2',
|
||||
'right_middle3',
|
||||
'right_pinky1',
|
||||
'right_pinky2',
|
||||
'right_pinky3',
|
||||
'right_ring1',
|
||||
'right_ring2',
|
||||
'right_ring3',
|
||||
'right_thumb1',
|
||||
'right_thumb2',
|
||||
'right_thumb3',
|
||||
'nose',
|
||||
'right_eye',
|
||||
'left_eye',
|
||||
'right_ear',
|
||||
'left_ear',
|
||||
'left_big_toe',
|
||||
'left_small_toe',
|
||||
'left_heel',
|
||||
'right_big_toe',
|
||||
'right_small_toe',
|
||||
'right_heel',
|
||||
'left_thumb',
|
||||
'left_index',
|
||||
'left_middle',
|
||||
'left_ring',
|
||||
'left_pinky',
|
||||
'right_thumb',
|
||||
'right_index',
|
||||
'right_middle',
|
||||
'right_ring',
|
||||
'right_pinky',
|
||||
'right_eye_brow1',
|
||||
'right_eye_brow2',
|
||||
'right_eye_brow3',
|
||||
'right_eye_brow4',
|
||||
'right_eye_brow5',
|
||||
'left_eye_brow5',
|
||||
'left_eye_brow4',
|
||||
'left_eye_brow3',
|
||||
'left_eye_brow2',
|
||||
'left_eye_brow1',
|
||||
'nose1',
|
||||
'nose2',
|
||||
'nose3',
|
||||
'nose4',
|
||||
'right_nose_2',
|
||||
'right_nose_1',
|
||||
'nose_middle',
|
||||
'left_nose_1',
|
||||
'left_nose_2',
|
||||
'right_eye1',
|
||||
'right_eye2',
|
||||
'right_eye3',
|
||||
'right_eye4',
|
||||
'right_eye5',
|
||||
'right_eye6',
|
||||
'left_eye4',
|
||||
'left_eye3',
|
||||
'left_eye2',
|
||||
'left_eye1',
|
||||
'left_eye6',
|
||||
'left_eye5',
|
||||
'right_mouth_1',
|
||||
'right_mouth_2',
|
||||
'right_mouth_3',
|
||||
'mouth_top',
|
||||
'left_mouth_3',
|
||||
'left_mouth_2',
|
||||
'left_mouth_1',
|
||||
'left_mouth_5', # 59 in OpenPose output
|
||||
'left_mouth_4', # 58 in OpenPose output
|
||||
'mouth_bottom',
|
||||
'right_mouth_4',
|
||||
'right_mouth_5',
|
||||
'right_lip_1',
|
||||
'right_lip_2',
|
||||
'lip_top',
|
||||
'left_lip_2',
|
||||
'left_lip_1',
|
||||
'left_lip_3',
|
||||
'lip_bottom',
|
||||
'right_lip_3',
|
||||
# Face contour
|
||||
'right_contour_1',
|
||||
'right_contour_2',
|
||||
'right_contour_3',
|
||||
'right_contour_4',
|
||||
'right_contour_5',
|
||||
'right_contour_6',
|
||||
'right_contour_7',
|
||||
'right_contour_8',
|
||||
'contour_middle',
|
||||
'left_contour_8',
|
||||
'left_contour_7',
|
||||
'left_contour_6',
|
||||
'left_contour_5',
|
||||
'left_contour_4',
|
||||
'left_contour_3',
|
||||
'left_contour_2',
|
||||
'left_contour_1',
|
||||
]
|
||||
@@ -0,0 +1,404 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
|
||||
from typing import Tuple, List
|
||||
import numpy as np
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from .utils import rot_mat_to_euler, Tensor
|
||||
|
||||
|
||||
def find_dynamic_lmk_idx_and_bcoords(
|
||||
vertices: Tensor,
|
||||
pose: Tensor,
|
||||
dynamic_lmk_faces_idx: Tensor,
|
||||
dynamic_lmk_b_coords: Tensor,
|
||||
neck_kin_chain: List[int],
|
||||
pose2rot: bool = True,
|
||||
) -> Tuple[Tensor, Tensor]:
|
||||
''' Compute the faces, barycentric coordinates for the dynamic landmarks
|
||||
|
||||
|
||||
To do so, we first compute the rotation of the neck around the y-axis
|
||||
and then use a pre-computed look-up table to find the faces and the
|
||||
barycentric coordinates that will be used.
|
||||
|
||||
Special thanks to Soubhik Sanyal (soubhik.sanyal@tuebingen.mpg.de)
|
||||
for providing the original TensorFlow implementation and for the LUT.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
vertices: torch.tensor BxVx3, dtype = torch.float32
|
||||
The tensor of input vertices
|
||||
pose: torch.tensor Bx(Jx3), dtype = torch.float32
|
||||
The current pose of the body model
|
||||
dynamic_lmk_faces_idx: torch.tensor L, dtype = torch.long
|
||||
The look-up table from neck rotation to faces
|
||||
dynamic_lmk_b_coords: torch.tensor Lx3, dtype = torch.float32
|
||||
The look-up table from neck rotation to barycentric coordinates
|
||||
neck_kin_chain: list
|
||||
A python list that contains the indices of the joints that form the
|
||||
kinematic chain of the neck.
|
||||
dtype: torch.dtype, optional
|
||||
|
||||
Returns
|
||||
-------
|
||||
dyn_lmk_faces_idx: torch.tensor, dtype = torch.long
|
||||
A tensor of size BxL that contains the indices of the faces that
|
||||
will be used to compute the current dynamic landmarks.
|
||||
dyn_lmk_b_coords: torch.tensor, dtype = torch.float32
|
||||
A tensor of size BxL that contains the indices of the faces that
|
||||
will be used to compute the current dynamic landmarks.
|
||||
'''
|
||||
|
||||
dtype = vertices.dtype
|
||||
batch_size = vertices.shape[0]
|
||||
|
||||
if pose2rot:
|
||||
aa_pose = torch.index_select(pose.view(batch_size, -1, 3), 1,
|
||||
neck_kin_chain)
|
||||
rot_mats = batch_rodrigues(
|
||||
aa_pose.view(-1, 3)).view(batch_size, -1, 3, 3)
|
||||
else:
|
||||
rot_mats = torch.index_select(
|
||||
pose.view(batch_size, -1, 3, 3), 1, neck_kin_chain)
|
||||
|
||||
rel_rot_mat = torch.eye(
|
||||
3, device=vertices.device, dtype=dtype).unsqueeze_(dim=0).repeat(
|
||||
batch_size, 1, 1)
|
||||
for idx in range(len(neck_kin_chain)):
|
||||
rel_rot_mat = torch.bmm(rot_mats[:, idx], rel_rot_mat)
|
||||
|
||||
y_rot_angle = torch.round(
|
||||
torch.clamp(-rot_mat_to_euler(rel_rot_mat) * 180.0 / np.pi,
|
||||
max=39)).to(dtype=torch.long)
|
||||
neg_mask = y_rot_angle.lt(0).to(dtype=torch.long)
|
||||
mask = y_rot_angle.lt(-39).to(dtype=torch.long)
|
||||
neg_vals = mask * 78 + (1 - mask) * (39 - y_rot_angle)
|
||||
y_rot_angle = (neg_mask * neg_vals +
|
||||
(1 - neg_mask) * y_rot_angle)
|
||||
|
||||
dyn_lmk_faces_idx = torch.index_select(dynamic_lmk_faces_idx,
|
||||
0, y_rot_angle)
|
||||
dyn_lmk_b_coords = torch.index_select(dynamic_lmk_b_coords,
|
||||
0, y_rot_angle)
|
||||
|
||||
return dyn_lmk_faces_idx, dyn_lmk_b_coords
|
||||
|
||||
|
||||
def vertices2landmarks(
|
||||
vertices: Tensor,
|
||||
faces: Tensor,
|
||||
lmk_faces_idx: Tensor,
|
||||
lmk_bary_coords: Tensor
|
||||
) -> Tensor:
|
||||
''' Calculates landmarks by barycentric interpolation
|
||||
|
||||
Parameters
|
||||
----------
|
||||
vertices: torch.tensor BxVx3, dtype = torch.float32
|
||||
The tensor of input vertices
|
||||
faces: torch.tensor Fx3, dtype = torch.long
|
||||
The faces of the mesh
|
||||
lmk_faces_idx: torch.tensor L, dtype = torch.long
|
||||
The tensor with the indices of the faces used to calculate the
|
||||
landmarks.
|
||||
lmk_bary_coords: torch.tensor Lx3, dtype = torch.float32
|
||||
The tensor of barycentric coordinates that are used to interpolate
|
||||
the landmarks
|
||||
|
||||
Returns
|
||||
-------
|
||||
landmarks: torch.tensor BxLx3, dtype = torch.float32
|
||||
The coordinates of the landmarks for each mesh in the batch
|
||||
'''
|
||||
# Extract the indices of the vertices for each face
|
||||
# BxLx3
|
||||
batch_size, num_verts = vertices.shape[:2]
|
||||
device = vertices.device
|
||||
|
||||
lmk_faces = torch.index_select(faces, 0, lmk_faces_idx.view(-1)).view(
|
||||
batch_size, -1, 3)
|
||||
|
||||
lmk_faces += torch.arange(
|
||||
batch_size, dtype=torch.long, device=device).view(-1, 1, 1) * num_verts
|
||||
|
||||
lmk_vertices = vertices.view(-1, 3)[lmk_faces].view(
|
||||
batch_size, -1, 3, 3)
|
||||
|
||||
landmarks = torch.einsum('blfi,blf->bli', [lmk_vertices, lmk_bary_coords])
|
||||
return landmarks
|
||||
|
||||
|
||||
def lbs(
|
||||
betas: Tensor,
|
||||
pose: Tensor,
|
||||
v_template: Tensor,
|
||||
shapedirs: Tensor,
|
||||
posedirs: Tensor,
|
||||
J_regressor: Tensor,
|
||||
parents: Tensor,
|
||||
lbs_weights: Tensor,
|
||||
pose2rot: bool = True,
|
||||
) -> Tuple[Tensor, Tensor]:
|
||||
''' Performs Linear Blend Skinning with the given shape and pose parameters
|
||||
|
||||
Parameters
|
||||
----------
|
||||
betas : torch.tensor BxNB
|
||||
The tensor of shape parameters
|
||||
pose : torch.tensor Bx(J + 1) * 3
|
||||
The pose parameters in axis-angle format
|
||||
v_template torch.tensor BxVx3
|
||||
The template mesh that will be deformed
|
||||
shapedirs : torch.tensor 1xNB
|
||||
The tensor of PCA shape displacements
|
||||
posedirs : torch.tensor Px(V * 3)
|
||||
The pose PCA coefficients
|
||||
J_regressor : torch.tensor JxV
|
||||
The regressor array that is used to calculate the joints from
|
||||
the position of the vertices
|
||||
parents: torch.tensor J
|
||||
The array that describes the kinematic tree for the model
|
||||
lbs_weights: torch.tensor N x V x (J + 1)
|
||||
The linear blend skinning weights that represent how much the
|
||||
rotation matrix of each part affects each vertex
|
||||
pose2rot: bool, optional
|
||||
Flag on whether to convert the input pose tensor to rotation
|
||||
matrices. The default value is True. If False, then the pose tensor
|
||||
should already contain rotation matrices and have a size of
|
||||
Bx(J + 1)x9
|
||||
dtype: torch.dtype, optional
|
||||
|
||||
Returns
|
||||
-------
|
||||
verts: torch.tensor BxVx3
|
||||
The vertices of the mesh after applying the shape and pose
|
||||
displacements.
|
||||
joints: torch.tensor BxJx3
|
||||
The joints of the model
|
||||
'''
|
||||
|
||||
batch_size = max(betas.shape[0], pose.shape[0])
|
||||
device, dtype = betas.device, betas.dtype
|
||||
|
||||
# Add shape contribution
|
||||
v_shaped = v_template + blend_shapes(betas, shapedirs)
|
||||
|
||||
# Get the joints
|
||||
# NxJx3 array
|
||||
J = vertices2joints(J_regressor, v_shaped)
|
||||
|
||||
# 3. Add pose blend shapes
|
||||
# N x J x 3 x 3
|
||||
ident = torch.eye(3, dtype=dtype, device=device)
|
||||
if pose2rot:
|
||||
rot_mats = batch_rodrigues(pose.view(-1, 3)).view(
|
||||
[batch_size, -1, 3, 3])
|
||||
|
||||
pose_feature = (rot_mats[:, 1:, :, :] - ident).view([batch_size, -1])
|
||||
# (N x P) x (P, V * 3) -> N x V x 3
|
||||
pose_offsets = torch.matmul(
|
||||
pose_feature, posedirs).view(batch_size, -1, 3)
|
||||
else:
|
||||
pose_feature = pose[:, 1:].view(batch_size, -1, 3, 3) - ident
|
||||
rot_mats = pose.view(batch_size, -1, 3, 3)
|
||||
|
||||
pose_offsets = torch.matmul(pose_feature.view(batch_size, -1),
|
||||
posedirs).view(batch_size, -1, 3)
|
||||
|
||||
v_posed = pose_offsets + v_shaped
|
||||
# 4. Get the global joint location
|
||||
J_transformed, A = batch_rigid_transform(rot_mats, J, parents, dtype=dtype)
|
||||
|
||||
# 5. Do skinning:
|
||||
# W is N x V x (J + 1)
|
||||
W = lbs_weights.unsqueeze(dim=0).expand([batch_size, -1, -1])
|
||||
# (N x V x (J + 1)) x (N x (J + 1) x 16)
|
||||
num_joints = J_regressor.shape[0]
|
||||
T = torch.matmul(W, A.view(batch_size, num_joints, 16)) \
|
||||
.view(batch_size, -1, 4, 4)
|
||||
|
||||
homogen_coord = torch.ones([batch_size, v_posed.shape[1], 1],
|
||||
dtype=dtype, device=device)
|
||||
v_posed_homo = torch.cat([v_posed, homogen_coord], dim=2)
|
||||
v_homo = torch.matmul(T, torch.unsqueeze(v_posed_homo, dim=-1))
|
||||
|
||||
verts = v_homo[:, :, :3, 0]
|
||||
|
||||
return verts, J_transformed
|
||||
|
||||
|
||||
def vertices2joints(J_regressor: Tensor, vertices: Tensor) -> Tensor:
|
||||
''' Calculates the 3D joint locations from the vertices
|
||||
|
||||
Parameters
|
||||
----------
|
||||
J_regressor : torch.tensor JxV
|
||||
The regressor array that is used to calculate the joints from the
|
||||
position of the vertices
|
||||
vertices : torch.tensor BxVx3
|
||||
The tensor of mesh vertices
|
||||
|
||||
Returns
|
||||
-------
|
||||
torch.tensor BxJx3
|
||||
The location of the joints
|
||||
'''
|
||||
|
||||
return torch.einsum('bik,ji->bjk', [vertices, J_regressor])
|
||||
|
||||
|
||||
def blend_shapes(betas: Tensor, shape_disps: Tensor) -> Tensor:
|
||||
''' Calculates the per vertex displacement due to the blend shapes
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
betas : torch.tensor Bx(num_betas)
|
||||
Blend shape coefficients
|
||||
shape_disps: torch.tensor Vx3x(num_betas)
|
||||
Blend shapes
|
||||
|
||||
Returns
|
||||
-------
|
||||
torch.tensor BxVx3
|
||||
The per-vertex displacement due to shape deformation
|
||||
'''
|
||||
|
||||
# Displacement[b, m, k] = sum_{l} betas[b, l] * shape_disps[m, k, l]
|
||||
# i.e. Multiply each shape displacement by its corresponding beta and
|
||||
# then sum them.
|
||||
blend_shape = torch.einsum('bl,mkl->bmk', [betas, shape_disps])
|
||||
return blend_shape
|
||||
|
||||
|
||||
def batch_rodrigues(
|
||||
rot_vecs: Tensor,
|
||||
epsilon: float = 1e-8,
|
||||
) -> Tensor:
|
||||
''' Calculates the rotation matrices for a batch of rotation vectors
|
||||
Parameters
|
||||
----------
|
||||
rot_vecs: torch.tensor Nx3
|
||||
array of N axis-angle vectors
|
||||
Returns
|
||||
-------
|
||||
R: torch.tensor Nx3x3
|
||||
The rotation matrices for the given axis-angle parameters
|
||||
'''
|
||||
|
||||
batch_size = rot_vecs.shape[0]
|
||||
device, dtype = rot_vecs.device, rot_vecs.dtype
|
||||
|
||||
angle = torch.norm(rot_vecs + 1e-8, dim=1, keepdim=True)
|
||||
rot_dir = rot_vecs / angle
|
||||
|
||||
cos = torch.unsqueeze(torch.cos(angle), dim=1)
|
||||
sin = torch.unsqueeze(torch.sin(angle), dim=1)
|
||||
|
||||
# Bx1 arrays
|
||||
rx, ry, rz = torch.split(rot_dir, 1, dim=1)
|
||||
K = torch.zeros((batch_size, 3, 3), dtype=dtype, device=device)
|
||||
|
||||
zeros = torch.zeros((batch_size, 1), dtype=dtype, device=device)
|
||||
K = torch.cat([zeros, -rz, ry, rz, zeros, -rx, -ry, rx, zeros], dim=1) \
|
||||
.view((batch_size, 3, 3))
|
||||
|
||||
ident = torch.eye(3, dtype=dtype, device=device).unsqueeze(dim=0)
|
||||
rot_mat = ident + sin * K + (1 - cos) * torch.bmm(K, K)
|
||||
return rot_mat
|
||||
|
||||
|
||||
def transform_mat(R: Tensor, t: Tensor) -> Tensor:
|
||||
''' Creates a batch of transformation matrices
|
||||
Args:
|
||||
- R: Bx3x3 array of a batch of rotation matrices
|
||||
- t: Bx3x1 array of a batch of translation vectors
|
||||
Returns:
|
||||
- T: Bx4x4 Transformation matrix
|
||||
'''
|
||||
# No padding left or right, only add an extra row
|
||||
return torch.cat([F.pad(R, [0, 0, 0, 1]),
|
||||
F.pad(t, [0, 0, 0, 1], value=1)], dim=2)
|
||||
|
||||
|
||||
def batch_rigid_transform(
|
||||
rot_mats: Tensor,
|
||||
joints: Tensor,
|
||||
parents: Tensor,
|
||||
dtype=torch.float32
|
||||
) -> Tensor:
|
||||
"""
|
||||
Applies a batch of rigid transformations to the joints
|
||||
|
||||
Parameters
|
||||
----------
|
||||
rot_mats : torch.tensor BxNx3x3
|
||||
Tensor of rotation matrices
|
||||
joints : torch.tensor BxNx3
|
||||
Locations of joints
|
||||
parents : torch.tensor BxN
|
||||
The kinematic tree of each object
|
||||
dtype : torch.dtype, optional:
|
||||
The data type of the created tensors, the default is torch.float32
|
||||
|
||||
Returns
|
||||
-------
|
||||
posed_joints : torch.tensor BxNx3
|
||||
The locations of the joints after applying the pose rotations
|
||||
rel_transforms : torch.tensor BxNx4x4
|
||||
The relative (with respect to the root joint) rigid transformations
|
||||
for all the joints
|
||||
"""
|
||||
|
||||
joints = torch.unsqueeze(joints, dim=-1)
|
||||
|
||||
rel_joints = joints.clone()
|
||||
rel_joints[:, 1:] -= joints[:, parents[1:]]
|
||||
|
||||
transforms_mat = transform_mat(
|
||||
rot_mats.reshape(-1, 3, 3),
|
||||
rel_joints.reshape(-1, 3, 1)).reshape(-1, joints.shape[1], 4, 4)
|
||||
|
||||
transform_chain = [transforms_mat[:, 0]]
|
||||
for i in range(1, parents.shape[0]):
|
||||
# Subtract the joint location at the rest pose
|
||||
# No need for rotation, since it's identity when at rest
|
||||
curr_res = torch.matmul(transform_chain[parents[i]],
|
||||
transforms_mat[:, i])
|
||||
transform_chain.append(curr_res)
|
||||
|
||||
transforms = torch.stack(transform_chain, dim=1)
|
||||
|
||||
# The last column of the transformations contains the posed joints
|
||||
posed_joints = transforms[:, :, :3, 3]
|
||||
|
||||
# The last column of the transformations contains the posed joints
|
||||
posed_joints = transforms[:, :, :3, 3]
|
||||
|
||||
joints_homogen = F.pad(joints, [0, 0, 0, 1])
|
||||
|
||||
rel_transforms = transforms - F.pad(
|
||||
torch.matmul(transforms, joints_homogen), [3, 0, 0, 0, 0, 0, 0, 0])
|
||||
|
||||
return posed_joints, rel_transforms
|
||||
@@ -0,0 +1,125 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from typing import NewType, Union, Optional
|
||||
from dataclasses import dataclass, asdict, fields
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
Tensor = NewType('Tensor', torch.Tensor)
|
||||
Array = NewType('Array', np.ndarray)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModelOutput:
|
||||
vertices: Optional[Tensor] = None
|
||||
joints: Optional[Tensor] = None
|
||||
full_pose: Optional[Tensor] = None
|
||||
global_orient: Optional[Tensor] = None
|
||||
transl: Optional[Tensor] = None
|
||||
|
||||
def __getitem__(self, key):
|
||||
return getattr(self, key)
|
||||
|
||||
def get(self, key, default=None):
|
||||
return getattr(self, key, default)
|
||||
|
||||
def __iter__(self):
|
||||
return self.keys()
|
||||
|
||||
def keys(self):
|
||||
keys = [t.name for t in fields(self)]
|
||||
return iter(keys)
|
||||
|
||||
def values(self):
|
||||
values = [getattr(self, t.name) for t in fields(self)]
|
||||
return iter(values)
|
||||
|
||||
def items(self):
|
||||
data = [(t.name, getattr(self, t.name)) for t in fields(self)]
|
||||
return iter(data)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SMPLOutput(ModelOutput):
|
||||
betas: Optional[Tensor] = None
|
||||
body_pose: Optional[Tensor] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class SMPLHOutput(SMPLOutput):
|
||||
left_hand_pose: Optional[Tensor] = None
|
||||
right_hand_pose: Optional[Tensor] = None
|
||||
transl: Optional[Tensor] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class SMPLXOutput(SMPLHOutput):
|
||||
expression: Optional[Tensor] = None
|
||||
jaw_pose: Optional[Tensor] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class MANOOutput(ModelOutput):
|
||||
betas: Optional[Tensor] = None
|
||||
hand_pose: Optional[Tensor] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class FLAMEOutput(ModelOutput):
|
||||
betas: Optional[Tensor] = None
|
||||
expression: Optional[Tensor] = None
|
||||
jaw_pose: Optional[Tensor] = None
|
||||
neck_pose: Optional[Tensor] = None
|
||||
|
||||
|
||||
def find_joint_kin_chain(joint_id, kinematic_tree):
|
||||
kin_chain = []
|
||||
curr_idx = joint_id
|
||||
while curr_idx != -1:
|
||||
kin_chain.append(curr_idx)
|
||||
curr_idx = kinematic_tree[curr_idx]
|
||||
return kin_chain
|
||||
|
||||
|
||||
def to_tensor(
|
||||
array: Union[Array, Tensor], dtype=torch.float32
|
||||
) -> Tensor:
|
||||
if torch.is_tensor(array):
|
||||
return array
|
||||
else:
|
||||
return torch.tensor(array, dtype=dtype)
|
||||
|
||||
|
||||
class Struct(object):
|
||||
def __init__(self, **kwargs):
|
||||
for key, val in kwargs.items():
|
||||
setattr(self, key, val)
|
||||
|
||||
|
||||
def to_np(array, dtype=np.float32):
|
||||
if 'scipy.sparse' in str(type(array)):
|
||||
array = array.todense()
|
||||
return np.array(array, dtype=dtype)
|
||||
|
||||
|
||||
def rot_mat_to_euler(rot_mats):
|
||||
# Calculates rotation matrix to euler angles
|
||||
# Careful for extreme cases of eular angles like [0.0, pi, 0.0]
|
||||
|
||||
sy = torch.sqrt(rot_mats[:, 0, 0] * rot_mats[:, 0, 0] +
|
||||
rot_mats[:, 1, 0] * rot_mats[:, 1, 0])
|
||||
return torch.atan2(-rot_mats[:, 2, 0], sy)
|
||||
@@ -0,0 +1,77 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
# Joint name to vertex mapping. SMPL/SMPL-H/SMPL-X vertices that correspond to
|
||||
# MSCOCO and OpenPose joints
|
||||
vertex_ids = {
|
||||
'smplh': {
|
||||
'nose': 332,
|
||||
'reye': 6260,
|
||||
'leye': 2800,
|
||||
'rear': 4071,
|
||||
'lear': 583,
|
||||
'rthumb': 6191,
|
||||
'rindex': 5782,
|
||||
'rmiddle': 5905,
|
||||
'rring': 6016,
|
||||
'rpinky': 6133,
|
||||
'lthumb': 2746,
|
||||
'lindex': 2319,
|
||||
'lmiddle': 2445,
|
||||
'lring': 2556,
|
||||
'lpinky': 2673,
|
||||
'LBigToe': 3216,
|
||||
'LSmallToe': 3226,
|
||||
'LHeel': 3387,
|
||||
'RBigToe': 6617,
|
||||
'RSmallToe': 6624,
|
||||
'RHeel': 6787
|
||||
},
|
||||
'smplx': {
|
||||
'nose': 9120,
|
||||
'reye': 9929,
|
||||
'leye': 9448,
|
||||
'rear': 616,
|
||||
'lear': 6,
|
||||
'rthumb': 8079,
|
||||
'rindex': 7669,
|
||||
'rmiddle': 7794,
|
||||
'rring': 7905,
|
||||
'rpinky': 8022,
|
||||
'lthumb': 5361,
|
||||
'lindex': 4933,
|
||||
'lmiddle': 5058,
|
||||
'lring': 5169,
|
||||
'lpinky': 5286,
|
||||
'LBigToe': 5770,
|
||||
'LSmallToe': 5780,
|
||||
'LHeel': 8846,
|
||||
'RBigToe': 8463,
|
||||
'RSmallToe': 8474,
|
||||
'RHeel': 8635
|
||||
},
|
||||
'mano': {
|
||||
'thumb': 744,
|
||||
'index': 320,
|
||||
'middle': 443,
|
||||
'ring': 554,
|
||||
'pinky': 671,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
|
||||
import numpy as np
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from .utils import to_tensor
|
||||
|
||||
|
||||
class VertexJointSelector(nn.Module):
|
||||
|
||||
def __init__(self, vertex_ids=None,
|
||||
use_hands=True,
|
||||
use_feet_keypoints=True, **kwargs):
|
||||
super(VertexJointSelector, self).__init__()
|
||||
|
||||
extra_joints_idxs = []
|
||||
|
||||
face_keyp_idxs = np.array([
|
||||
vertex_ids['nose'],
|
||||
vertex_ids['reye'],
|
||||
vertex_ids['leye'],
|
||||
vertex_ids['rear'],
|
||||
vertex_ids['lear']], dtype=np.int64)
|
||||
|
||||
extra_joints_idxs = np.concatenate([extra_joints_idxs,
|
||||
face_keyp_idxs])
|
||||
|
||||
if use_feet_keypoints:
|
||||
feet_keyp_idxs = np.array([vertex_ids['LBigToe'],
|
||||
vertex_ids['LSmallToe'],
|
||||
vertex_ids['LHeel'],
|
||||
vertex_ids['RBigToe'],
|
||||
vertex_ids['RSmallToe'],
|
||||
vertex_ids['RHeel']], dtype=np.int32)
|
||||
|
||||
extra_joints_idxs = np.concatenate(
|
||||
[extra_joints_idxs, feet_keyp_idxs])
|
||||
|
||||
if use_hands:
|
||||
self.tip_names = ['thumb', 'index', 'middle', 'ring', 'pinky']
|
||||
|
||||
tips_idxs = []
|
||||
for hand_id in ['l', 'r']:
|
||||
for tip_name in self.tip_names:
|
||||
tips_idxs.append(vertex_ids[hand_id + tip_name])
|
||||
|
||||
extra_joints_idxs = np.concatenate(
|
||||
[extra_joints_idxs, tips_idxs])
|
||||
|
||||
self.register_buffer('extra_joints_idxs',
|
||||
to_tensor(extra_joints_idxs, dtype=torch.long))
|
||||
|
||||
def forward(self, vertices, joints):
|
||||
extra_joints = torch.index_select(vertices, 1, self.extra_joints_idxs)
|
||||
joints = torch.cat([joints, extra_joints], dim=1)
|
||||
|
||||
return joints
|
||||
@@ -0,0 +1,20 @@
|
||||
## Removing Chumpy objects
|
||||
|
||||
In a Python 2 virtual environment with [Chumpy](https://github.com/mattloper/chumpy) installed run the following to remove any Chumpy objects from the model data:
|
||||
|
||||
```bash
|
||||
python tools/clean_ch.py --input-models path-to-models/*.pkl --output-folder output-folder
|
||||
```
|
||||
|
||||
## Merging SMPL-H and MANO parameters
|
||||
|
||||
In order to use the given PyTorch SMPL-H module we first need to merge the SMPL-H and MANO parameters in a single file. After agreeing to the license and downloading the models, run the following command:
|
||||
|
||||
```bash
|
||||
python tools/merge_smplh_mano.py --smplh-fn SMPLH_FOLDER/SMPLH_GENDER.pkl \
|
||||
--mano-left-fn MANO_FOLDER/MANO_LEFT.pkl \
|
||||
--mano-right-fn MANO_FOLDER/MANO_RIGHT.pkl \
|
||||
--output-folder OUTPUT_FOLDER
|
||||
```
|
||||
|
||||
where SMPLH_FOLDER is the folder with the SMPL-H files and MANO_FOLDER the one for the MANO files.
|
||||
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems and the Max Planck Institute for Biological
|
||||
# Cybernetics. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
import clean_ch
|
||||
import merge_smplh_mano
|
||||
@@ -0,0 +1,68 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems and the Max Planck Institute for Biological
|
||||
# Cybernetics. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import os.path as osp
|
||||
|
||||
import pickle
|
||||
|
||||
from tqdm import tqdm
|
||||
import numpy as np
|
||||
|
||||
|
||||
def clean_fn(fn, output_folder='output'):
|
||||
with open(fn, 'rb') as body_file:
|
||||
body_data = pickle.load(body_file)
|
||||
|
||||
output_dict = {}
|
||||
for key, data in body_data.iteritems():
|
||||
if 'chumpy' in str(type(data)):
|
||||
output_dict[key] = np.array(data)
|
||||
else:
|
||||
output_dict[key] = data
|
||||
|
||||
out_fn = osp.split(fn)[1]
|
||||
|
||||
out_path = osp.join(output_folder, out_fn)
|
||||
with open(out_path, 'wb') as out_file:
|
||||
pickle.dump(output_dict, out_file)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--input-models', dest='input_models', nargs='+',
|
||||
required=True, type=str,
|
||||
help='The path to the model that will be processed')
|
||||
parser.add_argument('--output-folder', dest='output_folder',
|
||||
required=True, type=str,
|
||||
help='The path to the output folder')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
input_models = args.input_models
|
||||
output_folder = args.output_folder
|
||||
if not osp.exists(output_folder):
|
||||
print('Creating directory: {}'.format(output_folder))
|
||||
os.makedirs(output_folder)
|
||||
|
||||
for input_model in input_models:
|
||||
clean_fn(input_model, output_folder=output_folder)
|
||||
@@ -0,0 +1,89 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems and the Max Planck Institute for Biological
|
||||
# Cybernetics. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import os.path as osp
|
||||
import pickle
|
||||
|
||||
import argparse
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def merge_models(smplh_fn, mano_left_fn, mano_right_fn,
|
||||
output_folder='output'):
|
||||
|
||||
with open(smplh_fn, 'rb') as body_file:
|
||||
body_data = pickle.load(body_file)
|
||||
|
||||
with open(mano_left_fn, 'rb') as lhand_file:
|
||||
lhand_data = pickle.load(lhand_file)
|
||||
|
||||
with open(mano_right_fn, 'rb') as rhand_file:
|
||||
rhand_data = pickle.load(rhand_file)
|
||||
|
||||
out_fn = osp.split(smplh_fn)[1]
|
||||
|
||||
output_data = body_data.copy()
|
||||
output_data['hands_componentsl'] = lhand_data['hands_components']
|
||||
output_data['hands_componentsr'] = rhand_data['hands_components']
|
||||
|
||||
output_data['hands_coeffsl'] = lhand_data['hands_coeffs']
|
||||
output_data['hands_coeffsr'] = rhand_data['hands_coeffs']
|
||||
|
||||
output_data['hands_meanl'] = lhand_data['hands_mean']
|
||||
output_data['hands_meanr'] = rhand_data['hands_mean']
|
||||
|
||||
for key, data in output_data.iteritems():
|
||||
if 'chumpy' in str(type(data)):
|
||||
output_data[key] = np.array(data)
|
||||
else:
|
||||
output_data[key] = data
|
||||
|
||||
out_path = osp.join(output_folder, out_fn)
|
||||
print(out_path)
|
||||
print('Saving to {}'.format(out_path))
|
||||
with open(out_path, 'wb') as output_file:
|
||||
pickle.dump(output_data, output_file)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--smplh-fn', dest='smplh_fn', required=True,
|
||||
type=str, help='The path to the SMPLH model')
|
||||
parser.add_argument('--mano-left-fn', dest='mano_left_fn', required=True,
|
||||
type=str, help='The path to the left hand MANO model')
|
||||
parser.add_argument('--mano-right-fn', dest='mano_right_fn', required=True,
|
||||
type=str, help='The path to the right hand MANO model')
|
||||
parser.add_argument('--output-folder', dest='output_folder',
|
||||
required=True, type=str,
|
||||
help='The path to the output folder')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
smplh_fn = args.smplh_fn
|
||||
mano_left_fn = args.mano_left_fn
|
||||
mano_right_fn = args.mano_right_fn
|
||||
output_folder = args.output_folder
|
||||
|
||||
if not osp.exists(output_folder):
|
||||
print('Creating directory: {}'.format(output_folder))
|
||||
os.makedirs(output_folder)
|
||||
|
||||
merge_models(smplh_fn, mano_left_fn, mano_right_fn, output_folder)
|
||||
@@ -0,0 +1,172 @@
|
||||
import torch
|
||||
import numpy as np
|
||||
import scipy
|
||||
from config import cfg
|
||||
from torch.nn import functional as F
|
||||
import torchgeometry as tgm
|
||||
|
||||
|
||||
def cam2pixel(cam_coord, f, c):
|
||||
x = cam_coord[:, 0] / cam_coord[:, 2] * f[0] + c[0]
|
||||
y = cam_coord[:, 1] / cam_coord[:, 2] * f[1] + c[1]
|
||||
z = cam_coord[:, 2]
|
||||
return np.stack((x, y, z), 1)
|
||||
|
||||
|
||||
def pixel2cam(pixel_coord, f, c):
|
||||
x = (pixel_coord[:, 0] - c[0]) / f[0] * pixel_coord[:, 2]
|
||||
y = (pixel_coord[:, 1] - c[1]) / f[1] * pixel_coord[:, 2]
|
||||
z = pixel_coord[:, 2]
|
||||
return np.stack((x, y, z), 1)
|
||||
|
||||
|
||||
def world2cam(world_coord, R, t):
|
||||
cam_coord = np.dot(R, world_coord.transpose(1, 0)).transpose(1, 0) + t.reshape(1, 3)
|
||||
return cam_coord
|
||||
|
||||
|
||||
def cam2world(cam_coord, R, t):
|
||||
world_coord = np.dot(np.linalg.inv(R), (cam_coord - t.reshape(1, 3)).transpose(1, 0)).transpose(1, 0)
|
||||
return world_coord
|
||||
|
||||
|
||||
def rigid_transform_3D(A, B):
|
||||
n, dim = A.shape
|
||||
centroid_A = np.mean(A, axis=0)
|
||||
centroid_B = np.mean(B, axis=0)
|
||||
H = np.dot(np.transpose(A - centroid_A), B - centroid_B) / n
|
||||
U, s, V = np.linalg.svd(H)
|
||||
R = np.dot(np.transpose(V), np.transpose(U))
|
||||
if np.linalg.det(R) < 0:
|
||||
s[-1] = -s[-1]
|
||||
V[2] = -V[2]
|
||||
R = np.dot(np.transpose(V), np.transpose(U))
|
||||
|
||||
varP = np.var(A, axis=0).sum()
|
||||
c = 1 / varP * np.sum(s)
|
||||
|
||||
t = -np.dot(c * R, np.transpose(centroid_A)) + np.transpose(centroid_B)
|
||||
return c, R, t
|
||||
|
||||
|
||||
def rigid_align(A, B):
|
||||
c, R, t = rigid_transform_3D(A, B)
|
||||
A2 = np.transpose(np.dot(c * R, np.transpose(A))) + t
|
||||
return A2
|
||||
|
||||
|
||||
def transform_joint_to_other_db(src_joint, src_name, dst_name):
|
||||
src_joint_num = len(src_name)
|
||||
dst_joint_num = len(dst_name)
|
||||
|
||||
new_joint = np.zeros(((dst_joint_num,) + src_joint.shape[1:]), dtype=np.float32)
|
||||
for src_idx in range(len(src_name)):
|
||||
name = src_name[src_idx]
|
||||
if name in dst_name:
|
||||
dst_idx = dst_name.index(name)
|
||||
new_joint[dst_idx] = src_joint[src_idx]
|
||||
|
||||
return new_joint
|
||||
|
||||
|
||||
def rot6d_to_axis_angle(x):
|
||||
batch_size = x.shape[0]
|
||||
|
||||
x = x.view(-1, 3, 2)
|
||||
a1 = x[:, :, 0]
|
||||
a2 = x[:, :, 1]
|
||||
b1 = F.normalize(a1)
|
||||
b2 = F.normalize(a2 - torch.einsum('bi,bi->b', b1, a2).unsqueeze(-1) * b1)
|
||||
b3 = torch.cross(b1, b2)
|
||||
rot_mat = torch.stack((b1, b2, b3), dim=-1) # 3x3 rotation matrix
|
||||
|
||||
rot_mat = torch.cat([rot_mat, torch.zeros((batch_size, 3, 1)).cuda().float()], 2) # 3x4 rotation matrix
|
||||
axis_angle = tgm.rotation_matrix_to_angle_axis(rot_mat).reshape(-1, 3) # axis-angle
|
||||
axis_angle[torch.isnan(axis_angle)] = 0.0
|
||||
return axis_angle
|
||||
|
||||
|
||||
def sample_joint_features(img_feat, joint_xy):
|
||||
height, width = img_feat.shape[2:]
|
||||
x = joint_xy[:, :, 0] / (width - 1) * 2 - 1
|
||||
y = joint_xy[:, :, 1] / (height - 1) * 2 - 1
|
||||
grid = torch.stack((x, y), 2)[:, :, None, :]
|
||||
img_feat = F.grid_sample(img_feat, grid, align_corners=True)[:, :, :, 0] # batch_size, channel_dim, joint_num
|
||||
img_feat = img_feat.permute(0, 2, 1).contiguous() # batch_size, joint_num, channel_dim
|
||||
return img_feat
|
||||
|
||||
|
||||
def soft_argmax_2d(heatmap2d):
|
||||
batch_size = heatmap2d.shape[0]
|
||||
height, width = heatmap2d.shape[2:]
|
||||
heatmap2d = heatmap2d.reshape((batch_size, -1, height * width))
|
||||
heatmap2d = F.softmax(heatmap2d, 2)
|
||||
heatmap2d = heatmap2d.reshape((batch_size, -1, height, width))
|
||||
|
||||
accu_x = heatmap2d.sum(dim=(2))
|
||||
accu_y = heatmap2d.sum(dim=(3))
|
||||
|
||||
accu_x = accu_x * torch.arange(width).float().cuda()[None, None, :]
|
||||
accu_y = accu_y * torch.arange(height).float().cuda()[None, None, :]
|
||||
|
||||
accu_x = accu_x.sum(dim=2, keepdim=True)
|
||||
accu_y = accu_y.sum(dim=2, keepdim=True)
|
||||
|
||||
coord_out = torch.cat((accu_x, accu_y), dim=2)
|
||||
return coord_out
|
||||
|
||||
|
||||
def soft_argmax_3d(heatmap3d):
|
||||
batch_size = heatmap3d.shape[0]
|
||||
depth, height, width = heatmap3d.shape[2:]
|
||||
heatmap3d = heatmap3d.reshape((batch_size, -1, depth * height * width))
|
||||
heatmap3d = F.softmax(heatmap3d, 2)
|
||||
heatmap3d = heatmap3d.reshape((batch_size, -1, depth, height, width))
|
||||
|
||||
accu_x = heatmap3d.sum(dim=(2, 3))
|
||||
accu_y = heatmap3d.sum(dim=(2, 4))
|
||||
accu_z = heatmap3d.sum(dim=(3, 4))
|
||||
|
||||
accu_x = accu_x * torch.arange(width).float().cuda()[None, None, :]
|
||||
accu_y = accu_y * torch.arange(height).float().cuda()[None, None, :]
|
||||
accu_z = accu_z * torch.arange(depth).float().cuda()[None, None, :]
|
||||
|
||||
accu_x = accu_x.sum(dim=2, keepdim=True)
|
||||
accu_y = accu_y.sum(dim=2, keepdim=True)
|
||||
accu_z = accu_z.sum(dim=2, keepdim=True)
|
||||
|
||||
coord_out = torch.cat((accu_x, accu_y, accu_z), dim=2)
|
||||
return coord_out
|
||||
|
||||
|
||||
def restore_bbox(bbox_center, bbox_size, aspect_ratio, extension_ratio):
|
||||
bbox = bbox_center.view(-1, 1, 2) + torch.cat((-bbox_size.view(-1, 1, 2) / 2., bbox_size.view(-1, 1, 2) / 2.),
|
||||
1) # xyxy in (cfg.output_hm_shape[2], cfg.output_hm_shape[1]) space
|
||||
bbox[:, :, 0] = bbox[:, :, 0] / cfg.output_hm_shape[2] * cfg.input_body_shape[1]
|
||||
bbox[:, :, 1] = bbox[:, :, 1] / cfg.output_hm_shape[1] * cfg.input_body_shape[0]
|
||||
bbox = bbox.view(-1, 4)
|
||||
|
||||
# xyxy -> xywh
|
||||
bbox[:, 2] = bbox[:, 2] - bbox[:, 0]
|
||||
bbox[:, 3] = bbox[:, 3] - bbox[:, 1]
|
||||
|
||||
# aspect ratio preserving bbox
|
||||
w = bbox[:, 2]
|
||||
h = bbox[:, 3]
|
||||
c_x = bbox[:, 0] + w / 2.
|
||||
c_y = bbox[:, 1] + h / 2.
|
||||
|
||||
mask1 = w > (aspect_ratio * h)
|
||||
mask2 = w < (aspect_ratio * h)
|
||||
h[mask1] = w[mask1] / aspect_ratio
|
||||
w[mask2] = h[mask2] * aspect_ratio
|
||||
|
||||
bbox[:, 2] = w * extension_ratio
|
||||
bbox[:, 3] = h * extension_ratio
|
||||
bbox[:, 0] = c_x - bbox[:, 2] / 2.
|
||||
bbox[:, 1] = c_y - bbox[:, 3] / 2.
|
||||
|
||||
# xywh -> xyxy
|
||||
bbox[:, 2] = bbox[:, 2] + bbox[:, 0]
|
||||
bbox[:, 3] = bbox[:, 3] + bbox[:, 1]
|
||||
return bbox
|
||||
@@ -0,0 +1,183 @@
|
||||
import os
|
||||
import cv2
|
||||
import numpy as np
|
||||
from mpl_toolkits.mplot3d import Axes3D
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib as mpl
|
||||
import os
|
||||
os.environ["PYOPENGL_PLATFORM"] = "egl"
|
||||
import pyrender
|
||||
import trimesh
|
||||
from config import cfg
|
||||
|
||||
def vis_keypoints_with_skeleton(img, kps, kps_lines, kp_thresh=0.4, alpha=1):
|
||||
# Convert from plt 0-1 RGBA colors to 0-255 BGR colors for opencv.
|
||||
cmap = plt.get_cmap('rainbow')
|
||||
colors = [cmap(i) for i in np.linspace(0, 1, len(kps_lines) + 2)]
|
||||
colors = [(c[2] * 255, c[1] * 255, c[0] * 255) for c in colors]
|
||||
|
||||
# Perform the drawing on a copy of the image, to allow for blending.
|
||||
kp_mask = np.copy(img)
|
||||
|
||||
# Draw the keypoints.
|
||||
for l in range(len(kps_lines)):
|
||||
i1 = kps_lines[l][0]
|
||||
i2 = kps_lines[l][1]
|
||||
p1 = kps[0, i1].astype(np.int32), kps[1, i1].astype(np.int32)
|
||||
p2 = kps[0, i2].astype(np.int32), kps[1, i2].astype(np.int32)
|
||||
if kps[2, i1] > kp_thresh and kps[2, i2] > kp_thresh:
|
||||
cv2.line(
|
||||
kp_mask, p1, p2,
|
||||
color=colors[l], thickness=2, lineType=cv2.LINE_AA)
|
||||
if kps[2, i1] > kp_thresh:
|
||||
cv2.circle(
|
||||
kp_mask, p1,
|
||||
radius=3, color=colors[l], thickness=-1, lineType=cv2.LINE_AA)
|
||||
if kps[2, i2] > kp_thresh:
|
||||
cv2.circle(
|
||||
kp_mask, p2,
|
||||
radius=3, color=colors[l], thickness=-1, lineType=cv2.LINE_AA)
|
||||
|
||||
# Blend the keypoints.
|
||||
return cv2.addWeighted(img, 1.0 - alpha, kp_mask, alpha, 0)
|
||||
|
||||
def vis_keypoints(img, kps, alpha=1, radius=3, color=None):
|
||||
# Convert from plt 0-1 RGBA colors to 0-255 BGR colors for opencv.
|
||||
cmap = plt.get_cmap('rainbow')
|
||||
if color is None:
|
||||
colors = [cmap(i) for i in np.linspace(0, 1, len(kps) + 2)]
|
||||
colors = [(c[2] * 255, c[1] * 255, c[0] * 255) for c in colors]
|
||||
|
||||
# Perform the drawing on a copy of the image, to allow for blending.
|
||||
kp_mask = np.copy(img)
|
||||
|
||||
# Draw the keypoints.
|
||||
for i in range(len(kps)):
|
||||
p = kps[i][0].astype(np.int32), kps[i][1].astype(np.int32)
|
||||
if color is None:
|
||||
cv2.circle(kp_mask, p, radius=radius, color=colors[i], thickness=-1, lineType=cv2.LINE_AA)
|
||||
else:
|
||||
cv2.circle(kp_mask, p, radius=radius, color=color, thickness=-1, lineType=cv2.LINE_AA)
|
||||
|
||||
# Blend the keypoints.
|
||||
return cv2.addWeighted(img, 1.0 - alpha, kp_mask, alpha, 0)
|
||||
|
||||
def vis_mesh(img, mesh_vertex, alpha=0.5):
|
||||
# Convert from plt 0-1 RGBA colors to 0-255 BGR colors for opencv.
|
||||
cmap = plt.get_cmap('rainbow')
|
||||
colors = [cmap(i) for i in np.linspace(0, 1, len(mesh_vertex))]
|
||||
colors = [(c[2] * 255, c[1] * 255, c[0] * 255) for c in colors]
|
||||
|
||||
# Perform the drawing on a copy of the image, to allow for blending.
|
||||
mask = np.copy(img)
|
||||
|
||||
# Draw the mesh
|
||||
for i in range(len(mesh_vertex)):
|
||||
p = mesh_vertex[i][0].astype(np.int32), mesh_vertex[i][1].astype(np.int32)
|
||||
cv2.circle(mask, p, radius=1, color=colors[i], thickness=-1, lineType=cv2.LINE_AA)
|
||||
|
||||
# Blend the keypoints.
|
||||
return cv2.addWeighted(img, 1.0 - alpha, mask, alpha, 0)
|
||||
|
||||
def vis_3d_skeleton(kpt_3d, kpt_3d_vis, kps_lines, filename=None):
|
||||
|
||||
fig = plt.figure()
|
||||
ax = fig.add_subplot(111, projection='3d')
|
||||
|
||||
# Convert from plt 0-1 RGBA colors to 0-255 BGR colors for opencv.
|
||||
cmap = plt.get_cmap('rainbow')
|
||||
colors = [cmap(i) for i in np.linspace(0, 1, len(kps_lines) + 2)]
|
||||
colors = [np.array((c[2], c[1], c[0])) for c in colors]
|
||||
|
||||
for l in range(len(kps_lines)):
|
||||
i1 = kps_lines[l][0]
|
||||
i2 = kps_lines[l][1]
|
||||
x = np.array([kpt_3d[i1,0], kpt_3d[i2,0]])
|
||||
y = np.array([kpt_3d[i1,1], kpt_3d[i2,1]])
|
||||
z = np.array([kpt_3d[i1,2], kpt_3d[i2,2]])
|
||||
|
||||
if kpt_3d_vis[i1,0] > 0 and kpt_3d_vis[i2,0] > 0:
|
||||
ax.plot(x, z, -y, c=colors[l], linewidth=2)
|
||||
if kpt_3d_vis[i1,0] > 0:
|
||||
ax.scatter(kpt_3d[i1,0], kpt_3d[i1,2], -kpt_3d[i1,1], c=colors[l], marker='o')
|
||||
if kpt_3d_vis[i2,0] > 0:
|
||||
ax.scatter(kpt_3d[i2,0], kpt_3d[i2,2], -kpt_3d[i2,1], c=colors[l], marker='o')
|
||||
|
||||
x_r = np.array([0, cfg.input_shape[1]], dtype=np.float32)
|
||||
y_r = np.array([0, cfg.input_shape[0]], dtype=np.float32)
|
||||
z_r = np.array([0, 1], dtype=np.float32)
|
||||
|
||||
if filename is None:
|
||||
ax.set_title('3D vis')
|
||||
else:
|
||||
ax.set_title(filename)
|
||||
|
||||
ax.set_xlabel('X Label')
|
||||
ax.set_ylabel('Z Label')
|
||||
ax.set_zlabel('Y Label')
|
||||
ax.legend()
|
||||
|
||||
plt.show()
|
||||
cv2.waitKey(0)
|
||||
|
||||
def save_obj(v, f, file_name='output.obj'):
|
||||
obj_file = open(file_name, 'w')
|
||||
for i in range(len(v)):
|
||||
obj_file.write('v ' + str(v[i][0]) + ' ' + str(v[i][1]) + ' ' + str(v[i][2]) + '\n')
|
||||
for i in range(len(f)):
|
||||
obj_file.write('f ' + str(f[i][0]+1) + '/' + str(f[i][0]+1) + ' ' + str(f[i][1]+1) + '/' + str(f[i][1]+1) + ' ' + str(f[i][2]+1) + '/' + str(f[i][2]+1) + '\n')
|
||||
obj_file.close()
|
||||
|
||||
|
||||
def perspective_projection(vertices, cam_param):
|
||||
# vertices: [N, 3]
|
||||
# cam_param: [3]
|
||||
fx, fy= cam_param['focal']
|
||||
cx, cy = cam_param['princpt']
|
||||
vertices[:, 0] = vertices[:, 0] * fx / vertices[:, 2] + cx
|
||||
vertices[:, 1] = vertices[:, 1] * fy / vertices[:, 2] + cy
|
||||
return vertices
|
||||
|
||||
|
||||
def render_mesh(img, mesh, face, cam_param, mesh_as_vertices=False):
|
||||
if mesh_as_vertices:
|
||||
# to run on cluster where headless pyrender is not supported for A100/V100
|
||||
vertices_2d = perspective_projection(mesh, cam_param)
|
||||
img = vis_keypoints(img, vertices_2d, alpha=0.8, radius=2, color=(0, 0, 255))
|
||||
else:
|
||||
# mesh
|
||||
mesh = trimesh.Trimesh(mesh, face)
|
||||
rot = trimesh.transformations.rotation_matrix(
|
||||
np.radians(180), [1, 0, 0])
|
||||
mesh.apply_transform(rot)
|
||||
material = pyrender.MetallicRoughnessMaterial(metallicFactor=0.0, alphaMode='OPAQUE', baseColorFactor=(1.0, 1.0, 0.9, 1.0))
|
||||
mesh = pyrender.Mesh.from_trimesh(mesh, material=material, smooth=False)
|
||||
scene = pyrender.Scene(ambient_light=(0.3, 0.3, 0.3))
|
||||
scene.add(mesh, 'mesh')
|
||||
|
||||
focal, princpt = cam_param['focal'], cam_param['princpt']
|
||||
camera = pyrender.IntrinsicsCamera(fx=focal[0], fy=focal[1], cx=princpt[0], cy=princpt[1])
|
||||
scene.add(camera)
|
||||
|
||||
# renderer
|
||||
renderer = pyrender.OffscreenRenderer(viewport_width=img.shape[1], viewport_height=img.shape[0], point_size=1.0)
|
||||
|
||||
# light
|
||||
light = pyrender.DirectionalLight(color=[1.0, 1.0, 1.0], intensity=0.8)
|
||||
light_pose = np.eye(4)
|
||||
light_pose[:3, 3] = np.array([0, -1, 1])
|
||||
scene.add(light, pose=light_pose)
|
||||
light_pose[:3, 3] = np.array([0, 1, 1])
|
||||
scene.add(light, pose=light_pose)
|
||||
light_pose[:3, 3] = np.array([1, 1, 2])
|
||||
scene.add(light, pose=light_pose)
|
||||
|
||||
# render
|
||||
rgb, depth = renderer.render(scene, flags=pyrender.RenderFlags.RGBA)
|
||||
rgb = rgb[:,:,:3].astype(np.float32)
|
||||
valid_mask = (depth > 0)[:,:,None]
|
||||
|
||||
# save to image
|
||||
img = rgb * valid_mask + img * (1-valid_mask)
|
||||
|
||||
return img
|
||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,853 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
from glob import glob
|
||||
import numpy as np
|
||||
from config import cfg
|
||||
import copy
|
||||
import json
|
||||
import pickle
|
||||
import cv2
|
||||
import torch
|
||||
from pycocotools.coco import COCO
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, sanitize_bbox, process_bbox, augmentation, process_db_coord, \
|
||||
process_human_model_output, load_ply, load_obj
|
||||
from utils.transforms import rigid_align
|
||||
import tqdm
|
||||
import random
|
||||
from humandata import Cache
|
||||
|
||||
class AGORA(torch.utils.data.Dataset):
|
||||
def __init__(self, transform, data_split):
|
||||
self.transform = transform
|
||||
self.data_split = data_split
|
||||
if getattr(cfg, 'eval_on_train', False):
|
||||
self.data_split = 'eval_train'
|
||||
print("Evaluate on train set.")
|
||||
self.data_path = osp.join(cfg.data_dir, 'AGORA', 'data')
|
||||
self.save_idx = 0
|
||||
self.resolution = (2160, 3840) # height, width. one of (720, 1280) and (2160, 3840)
|
||||
if cfg.agora_benchmark == 'agora_model_test' or cfg.agora_benchmark == 'test_only':
|
||||
self.test_set = 'test'
|
||||
else:
|
||||
self.test_set = 'val' # val, test
|
||||
|
||||
# AGORA joint set
|
||||
self.joint_set = {
|
||||
'joint_num': 127,
|
||||
'joints_name': \
|
||||
('Pelvis', 'L_Hip', 'R_Hip', 'Spine_1', 'L_Knee', 'R_Knee', 'Spine_2', 'L_Ankle', 'R_Ankle', 'Spine_3',
|
||||
'L_Foot', 'R_Foot', 'Neck', 'L_Collar', 'R_Collar', 'Head', 'L_Shoulder', 'R_Shoulder', 'L_Elbow',
|
||||
'R_Elbow', 'L_Wrist', 'R_Wrist', # body
|
||||
'Jaw', 'L_Eye_SMPLH', 'R_Eye_SMPLH', # SMPLH
|
||||
'L_Index_1', 'L_Index_2', 'L_Index_3', 'L_Middle_1', 'L_Middle_2', 'L_Middle_3', 'L_Pinky_1',
|
||||
'L_Pinky_2', 'L_Pinky_3', 'L_Ring_1', 'L_Ring_2', 'L_Ring_3', 'L_Thumb_1', 'L_Thumb_2', 'L_Thumb_3',
|
||||
# fingers
|
||||
'R_Index_1', 'R_Index_2', 'R_Index_3', 'R_Middle_1', 'R_Middle_2', 'R_Middle_3', 'R_Pinky_1',
|
||||
'R_Pinky_2', 'R_Pinky_3', 'R_Ring_1', 'R_Ring_2', 'R_Ring_3', 'R_Thumb_1', 'R_Thumb_2', 'R_Thumb_3',
|
||||
# fingers
|
||||
'Nose', 'R_Eye', 'L_Eye', 'R_Ear', 'L_Ear', # face in body
|
||||
'L_Big_toe', 'L_Small_toe', 'L_Heel', 'R_Big_toe', 'R_Small_toe', 'R_Heel', # feet
|
||||
'L_Thumb_4', 'L_Index_4', 'L_Middle_4', 'L_Ring_4', 'L_Pinky_4', # finger tips
|
||||
'R_Thumb_4', 'R_Index_4', 'R_Middle_4', 'R_Ring_4', 'R_Pinky_4', # finger tips
|
||||
*['Face_' + str(i) for i in range(5, 56)] # face
|
||||
),
|
||||
'flip_pairs': \
|
||||
((1, 2), (4, 5), (7, 8), (10, 11), (13, 14), (16, 17), (18, 19), (20, 21), # body
|
||||
(23, 24), # SMPLH
|
||||
(25, 40), (26, 41), (27, 42), (28, 43), (29, 44), (30, 45), (31, 46), (32, 47), (33, 48), (34, 49),
|
||||
(35, 50), (36, 51), (37, 52), (38, 53), (39, 54), # fingers
|
||||
(56, 57), (58, 59), # face in body
|
||||
(60, 63), (61, 64), (62, 65), # feet
|
||||
(66, 71), (67, 72), (68, 73), (69, 74), (70, 75), # fingertips
|
||||
(76, 85), (77, 84), (78, 83), (79, 82), (80, 81), # face eyebrow
|
||||
(90, 94), (91, 93), # face below nose
|
||||
(95, 104), (96, 103), (97, 102), (98, 101), (99, 106), (100, 105), # face eyes
|
||||
(107, 113), (108, 112), (109, 111), (114, 118), (115, 117), # face mouth
|
||||
(119, 123), (120, 122), (124, 126) # face lip
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
self.joint_set['joint_part'] = {
|
||||
'body': list(range(self.joint_set['joints_name'].index('Pelvis'),
|
||||
self.joint_set['joints_name'].index('R_Eye_SMPLH') + 1)) + list(
|
||||
range(self.joint_set['joints_name'].index('Nose'), self.joint_set['joints_name'].index('R_Heel') + 1)),
|
||||
'lhand': list(range(self.joint_set['joints_name'].index('L_Index_1'),
|
||||
self.joint_set['joints_name'].index('L_Thumb_3') + 1)) + list(
|
||||
range(self.joint_set['joints_name'].index('L_Thumb_4'),
|
||||
self.joint_set['joints_name'].index('L_Pinky_4') + 1)),
|
||||
'rhand': list(range(self.joint_set['joints_name'].index('R_Index_1'),
|
||||
self.joint_set['joints_name'].index('R_Thumb_3') + 1)) + list(
|
||||
range(self.joint_set['joints_name'].index('R_Thumb_4'),
|
||||
self.joint_set['joints_name'].index('R_Pinky_4') + 1)),
|
||||
'face': list(range(self.joint_set['joints_name'].index('Face_5'),
|
||||
self.joint_set['joints_name'].index('Face_55') + 1))}
|
||||
self.joint_set['root_joint_idx'] = self.joint_set['joints_name'].index('Pelvis')
|
||||
self.joint_set['lwrist_idx'] = self.joint_set['joints_name'].index('L_Wrist')
|
||||
self.joint_set['rwrist_idx'] = self.joint_set['joints_name'].index('R_Wrist')
|
||||
self.joint_set['neck_idx'] = self.joint_set['joints_name'].index('Neck')
|
||||
|
||||
# self.datalist = self.load_data()
|
||||
|
||||
# load data or cache
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
if 'train'in self.data_split or (self.data_split == 'test' and self.test_set == 'val'):
|
||||
if 'train' in self.data_split:
|
||||
if getattr(cfg, 'agora_fix_betas', False):
|
||||
assert getattr(cfg, 'agora_fix_global_orient_transl')
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', f'AGORA_{self.data_split}_fix_betas.npz')
|
||||
elif getattr(cfg, 'agora_fix_global_orient_transl', False):
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', f'AGORA_{self.data_split}_fix_global_orient_transl.npz')
|
||||
else:
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', f'AGORA_{self.data_split}.npz')
|
||||
else:
|
||||
if getattr(cfg, 'agora_fix_betas', False):
|
||||
assert getattr(cfg, 'agora_fix_global_orient_transl')
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', 'AGORA_validation_fix_betas.npz')
|
||||
elif getattr(cfg, 'agora_fix_global_orient_transl', False):
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', 'AGORA_validation_fix_global_orient_transl.npz')
|
||||
else:
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', 'AGORA_validation.npz')
|
||||
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
datalist = Cache(self.annot_path_cache)
|
||||
assert datalist.data_strategy == getattr(cfg, 'data_strategy', None), \
|
||||
f'Cache data strategy {datalist.data_strategy} does not match current data strategy ' \
|
||||
f'{getattr(cfg, "data_strategy", None)}'
|
||||
self.datalist = datalist
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data()
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Caching datalist to {self.annot_path_cache}...')
|
||||
Cache.save(
|
||||
self.annot_path_cache,
|
||||
self.datalist,
|
||||
data_strategy=getattr(cfg, 'data_strategy', None)
|
||||
)
|
||||
|
||||
else: # test
|
||||
self.datalist = self.load_data()
|
||||
|
||||
def load_data(self):
|
||||
datalist = []
|
||||
if 'train' in self.data_split or (self.data_split == 'test' and self.test_set == 'val'):
|
||||
print('dataset settings:')
|
||||
print('agora_fix_betas', getattr(cfg, 'agora_fix_betas', False))
|
||||
print('agora_fix_global_orient_transl', getattr(cfg, 'agora_fix_global_orient_transl', False))
|
||||
print('agora_valid_root_pose', getattr(cfg, 'agora_valid_root_pose', False))
|
||||
|
||||
if 'train' in self.data_split:
|
||||
if getattr(cfg, 'agora_fix_betas', False):
|
||||
assert getattr(cfg, 'agora_fix_global_orient_transl')
|
||||
db = COCO(osp.join(self.data_path, 'AGORA_train_fix_betas.json'))
|
||||
elif getattr(cfg, 'agora_fix_global_orient_transl', False):
|
||||
db = COCO(osp.join(self.data_path, 'AGORA_train_fix_global_orient_transl.json'))
|
||||
else:
|
||||
db = COCO(osp.join(self.data_path, 'AGORA_train.json'))
|
||||
else:
|
||||
if getattr(cfg, 'agora_fix_betas', False):
|
||||
assert getattr(cfg, 'agora_fix_global_orient_transl')
|
||||
db = COCO(osp.join(self.data_path, 'AGORA_validation_fix_betas.json'))
|
||||
elif getattr(cfg, 'agora_fix_global_orient_transl', False):
|
||||
db = COCO(osp.join(self.data_path, 'AGORA_validation_fix_global_orient_transl.json'))
|
||||
else:
|
||||
db = COCO(osp.join(self.data_path, 'AGORA_validation.json'))
|
||||
|
||||
### HARDCODE vis for debug
|
||||
# count = 0
|
||||
i = 0
|
||||
for aid in tqdm.tqdm(list(db.anns.keys())):
|
||||
# if count > 50:
|
||||
# continue
|
||||
# count += 1
|
||||
|
||||
i += 1
|
||||
if self.data_split == 'train' and i % getattr(cfg, 'AGORA_train_sample_interval', 1) != 0:
|
||||
continue
|
||||
|
||||
ann = db.anns[aid]
|
||||
image_id = ann['image_id']
|
||||
img = db.loadImgs(image_id)[0]
|
||||
if not ann['is_valid']:
|
||||
continue
|
||||
|
||||
joints_2d_path = osp.join(self.data_path, ann['smplx_joints_2d_path'])
|
||||
joints_3d_path = osp.join(self.data_path, ann['smplx_joints_3d_path'])
|
||||
verts_path = osp.join(self.data_path, ann['smplx_verts_path'])
|
||||
smplx_param_path = osp.join(self.data_path, ann['smplx_param_path'])
|
||||
kid = ann['kid']
|
||||
gender = ann['gender']
|
||||
if not osp.exists(smplx_param_path): print(smplx_param_path)
|
||||
|
||||
if self.resolution == (720, 1280):
|
||||
img_shape = self.resolution
|
||||
img_path = osp.join(self.data_path, img['file_name_1280x720'])
|
||||
|
||||
# convert to current resolution
|
||||
bbox = np.array(ann['bbox']).reshape(2, 2)
|
||||
bbox[:, 0] = bbox[:, 0] / 3840 * 1280
|
||||
bbox[:, 1] = bbox[:, 1] / 2160 * 720
|
||||
bbox = bbox.reshape(4)
|
||||
if hasattr(cfg, 'bbox_ratio'):
|
||||
bbox_ratio = cfg.bbox_ratio * 0.833 # agora preprocess is giving 1.2 box padding
|
||||
else:
|
||||
bbox_ratio = 1.25
|
||||
bbox = process_bbox(bbox, img_shape[1], img_shape[0], ratio=bbox_ratio)
|
||||
if bbox is None:
|
||||
continue
|
||||
|
||||
lhand_bbox = np.array(ann['lhand_bbox']).reshape(2, 2)
|
||||
lhand_bbox[:, 0] = lhand_bbox[:, 0] / 3840 * 1280
|
||||
lhand_bbox[:, 1] = lhand_bbox[:, 1] / 2160 * 720
|
||||
lhand_bbox = lhand_bbox.reshape(4)
|
||||
lhand_bbox = sanitize_bbox(lhand_bbox, img_shape[1], img_shape[0])
|
||||
if lhand_bbox is not None:
|
||||
lhand_bbox[2:] += lhand_bbox[:2] # xywh -> xyxy
|
||||
|
||||
rhand_bbox = np.array(ann['rhand_bbox']).reshape(2, 2)
|
||||
rhand_bbox[:, 0] = rhand_bbox[:, 0] / 3840 * 1280
|
||||
rhand_bbox[:, 1] = rhand_bbox[:, 1] / 2160 * 720
|
||||
rhand_bbox = rhand_bbox.reshape(4)
|
||||
rhand_bbox = sanitize_bbox(rhand_bbox, img_shape[1], img_shape[0])
|
||||
if rhand_bbox is not None:
|
||||
rhand_bbox[2:] += rhand_bbox[:2] # xywh -> xyxy
|
||||
|
||||
face_bbox = np.array(ann['face_bbox']).reshape(2, 2)
|
||||
face_bbox[:, 0] = face_bbox[:, 0] / 3840 * 1280
|
||||
face_bbox[:, 1] = face_bbox[:, 1] / 2160 * 720
|
||||
face_bbox = face_bbox.reshape(4)
|
||||
face_bbox = sanitize_bbox(face_bbox, img_shape[1], img_shape[0])
|
||||
if face_bbox is not None:
|
||||
face_bbox[2:] += face_bbox[:2] # xywh -> xyxy
|
||||
|
||||
data_dict = {'img_path': img_path, 'img_shape': img_shape, 'bbox': bbox, 'lhand_bbox': lhand_bbox,
|
||||
'rhand_bbox': rhand_bbox, 'face_bbox': face_bbox, 'joints_2d_path': joints_2d_path,
|
||||
'joints_3d_path': joints_3d_path, 'verts_path': verts_path,
|
||||
'smplx_param_path': smplx_param_path, 'ann_id': str(aid), 'kid': kid, 'gender': gender}
|
||||
datalist.append(data_dict)
|
||||
|
||||
elif self.resolution == (2160,
|
||||
3840): # use cropped and resized images. loading 4K images in pytorch dataloader takes too much time...
|
||||
img_path = osp.join(self.data_path, '3840x2160',
|
||||
img['file_name_3840x2160'].split('/')[-2] + '_crop',
|
||||
img['file_name_3840x2160'].split('/')[-1][:-4] + '_ann_id_' + str(aid) + '.png')
|
||||
json_path = osp.join(self.data_path, '3840x2160',
|
||||
img['file_name_3840x2160'].split('/')[-2] + '_crop',
|
||||
img['file_name_3840x2160'].split('/')[-1][:-4] + '_ann_id_' + str(
|
||||
aid) + '.json')
|
||||
if not osp.isfile(json_path):
|
||||
continue
|
||||
with open(json_path) as f:
|
||||
crop_resize_info = json.load(f)
|
||||
img2bb_trans_from_orig = np.array(crop_resize_info['img2bb_trans'], dtype=np.float32)
|
||||
resized_height, resized_width = crop_resize_info['resized_height'], crop_resize_info[
|
||||
'resized_width']
|
||||
img_shape = (resized_height, resized_width)
|
||||
bbox = np.array([0, 0, resized_width, resized_height], dtype=np.float32)
|
||||
|
||||
# transform from original image to crop_and_resize image
|
||||
lhand_bbox = np.array(ann['lhand_bbox']).reshape(2, 2)
|
||||
lhand_bbox[1] += lhand_bbox[0] # xywh -> xyxy
|
||||
lhand_bbox = np.dot(img2bb_trans_from_orig,
|
||||
np.concatenate((lhand_bbox, np.ones_like(lhand_bbox[:, :1])), 1).transpose(1,
|
||||
0)).transpose(
|
||||
1, 0)
|
||||
lhand_bbox[1] -= lhand_bbox[0] # xyxy -> xywh
|
||||
lhand_bbox = lhand_bbox.reshape(4)
|
||||
lhand_bbox = sanitize_bbox(lhand_bbox, self.resolution[1], self.resolution[0])
|
||||
if lhand_bbox is not None:
|
||||
lhand_bbox[2:] += lhand_bbox[:2] # xywh -> xyxy
|
||||
|
||||
# transform from original image to crop_and_resize image
|
||||
rhand_bbox = np.array(ann['rhand_bbox']).reshape(2, 2)
|
||||
rhand_bbox[1] += rhand_bbox[0] # xywh -> xyxy
|
||||
rhand_bbox = np.dot(img2bb_trans_from_orig,
|
||||
np.concatenate((rhand_bbox, np.ones_like(rhand_bbox[:, :1])), 1).transpose(1,
|
||||
0)).transpose(
|
||||
1, 0)
|
||||
rhand_bbox[1] -= rhand_bbox[0] # xyxy -> xywh
|
||||
rhand_bbox = rhand_bbox.reshape(4)
|
||||
rhand_bbox = sanitize_bbox(rhand_bbox, self.resolution[1], self.resolution[0])
|
||||
if rhand_bbox is not None:
|
||||
rhand_bbox[2:] += rhand_bbox[:2] # xywh -> xyxy
|
||||
|
||||
# transform from original image to crop_and_resize image
|
||||
face_bbox = np.array(ann['face_bbox']).reshape(2, 2)
|
||||
face_bbox[1] += face_bbox[0] # xywh -> xyxy
|
||||
face_bbox = np.dot(img2bb_trans_from_orig,
|
||||
np.concatenate((face_bbox, np.ones_like(face_bbox[:, :1])), 1).transpose(1,
|
||||
0)).transpose(
|
||||
1, 0)
|
||||
face_bbox[1] -= face_bbox[0] # xyxy -> xywh
|
||||
face_bbox = face_bbox.reshape(4)
|
||||
face_bbox = sanitize_bbox(face_bbox, self.resolution[1], self.resolution[0])
|
||||
if face_bbox is not None:
|
||||
face_bbox[2:] += face_bbox[:2] # xywh -> xyxy
|
||||
|
||||
data_dict = {'img_path': img_path, 'img_shape': img_shape, 'bbox': bbox, 'lhand_bbox': lhand_bbox,
|
||||
'rhand_bbox': rhand_bbox, 'face_bbox': face_bbox,
|
||||
'img2bb_trans_from_orig': img2bb_trans_from_orig, 'joints_2d_path': joints_2d_path,
|
||||
'joints_3d_path': joints_3d_path, 'verts_path': verts_path,
|
||||
'smplx_param_path': smplx_param_path, 'ann_id': str(aid), 'kid': kid, 'gender': gender}
|
||||
datalist.append(data_dict)
|
||||
|
||||
print('[AGORA train] original size:', len(db.anns.keys()),
|
||||
'. Sample interval:', getattr(cfg, 'AGORA_train_sample_interval', 1),
|
||||
'. Sampled size:', len(datalist))
|
||||
|
||||
elif self.data_split == 'test' and self.test_set == 'test':
|
||||
with open(osp.join(self.data_path, 'AGORA_test_bbox.json')) as f:
|
||||
bboxs = json.load(f)
|
||||
|
||||
for filename in tqdm.tqdm(bboxs.keys()):
|
||||
if self.resolution == (720, 1280):
|
||||
img_path = osp.join(self.data_path, 'test', filename)
|
||||
img_shape = self.resolution
|
||||
person_num = len(bboxs[filename])
|
||||
for pid in range(person_num):
|
||||
# change bbox from (2160,3840) to target resoution
|
||||
bbox = np.array(bboxs[filename][pid]['bbox']).reshape(2, 2)
|
||||
bbox[:, 0] = bbox[:, 0] / 3840 * 1280
|
||||
bbox[:, 1] = bbox[:, 1] / 2160 * 720
|
||||
bbox = bbox.reshape(4)
|
||||
if hasattr(cfg, 'bbox_ratio'):
|
||||
bbox_ratio = cfg.bbox_ratio * 0.833 # agora preprocess is giving 1.2 box padding
|
||||
else:
|
||||
bbox_ratio = 1.25
|
||||
bbox = process_bbox(bbox, img_shape[1], img_shape[0], ratio=bbox_ratio)
|
||||
if bbox is None:
|
||||
continue
|
||||
datalist.append({'img_path': img_path, 'img_shape': img_shape, 'bbox': bbox, 'person_idx': pid})
|
||||
|
||||
elif self.resolution == (2160,
|
||||
3840): # use cropped and resized images. loading 4K images in pytorch dataloader takes too much time...
|
||||
person_num = len(bboxs[filename])
|
||||
for pid in range(person_num):
|
||||
img_path = osp.join(self.data_path, '3840x2160', 'test_crop',
|
||||
filename[:-4] + '_pid_' + str(pid) + '.png')
|
||||
json_path = osp.join(self.data_path, '3840x2160', 'test_crop',
|
||||
filename[:-4] + '_pid_' + str(pid) + '.json')
|
||||
if not osp.isfile(json_path):
|
||||
continue
|
||||
with open(json_path) as f:
|
||||
crop_resize_info = json.load(f)
|
||||
img2bb_trans_from_orig = np.array(crop_resize_info['img2bb_trans'], dtype=np.float32)
|
||||
resized_height, resized_width = crop_resize_info['resized_height'], crop_resize_info[
|
||||
'resized_width']
|
||||
img_shape = (resized_height, resized_width)
|
||||
bbox = np.array([0, 0, resized_width, resized_height], dtype=np.float32)
|
||||
datalist.append({'img_path': img_path, 'img_shape': img_shape,
|
||||
'img2bb_trans_from_orig': img2bb_trans_from_orig, 'bbox': bbox,
|
||||
'person_idx': pid})
|
||||
|
||||
if (getattr(cfg, 'data_strategy', None) == 'balance' and self.data_split == 'train') or \
|
||||
self.data_split == 'eval_train':
|
||||
print(f"[Agora] Using [balance] strategy with datalist shuffled...")
|
||||
random.seed(2023)
|
||||
random.shuffle(datalist)
|
||||
|
||||
if self.data_split == "eval_train":
|
||||
return datalist[:10000]
|
||||
|
||||
return datalist
|
||||
|
||||
def process_hand_face_bbox(self, bbox, do_flip, img_shape, img2bb_trans):
|
||||
if bbox is None:
|
||||
bbox = np.array([0, 0, 1, 1], dtype=np.float32).reshape(2, 2) # dummy value
|
||||
bbox_valid = float(False) # dummy value
|
||||
else:
|
||||
# reshape to top-left (x,y) and bottom-right (x,y)
|
||||
bbox = bbox.reshape(2, 2)
|
||||
|
||||
# flip augmentation
|
||||
if do_flip:
|
||||
bbox[:, 0] = img_shape[1] - bbox[:, 0] - 1
|
||||
bbox[0, 0], bbox[1, 0] = bbox[1, 0].copy(), bbox[0, 0].copy() # xmin <-> xmax swap
|
||||
|
||||
# make four points of the bbox
|
||||
bbox = bbox.reshape(4).tolist()
|
||||
xmin, ymin, xmax, ymax = bbox
|
||||
bbox = np.array([[xmin, ymin], [xmax, ymin], [xmax, ymax], [xmin, ymax]], dtype=np.float32).reshape(4, 2)
|
||||
|
||||
# affine transformation (crop, rotation, scale)
|
||||
bbox_xy1 = np.concatenate((bbox, np.ones_like(bbox[:, :1])), 1)
|
||||
bbox = np.dot(img2bb_trans, bbox_xy1.transpose(1, 0)).transpose(1, 0)[:, :2]
|
||||
bbox[:, 0] = bbox[:, 0] / cfg.input_img_shape[1] * cfg.output_hm_shape[2]
|
||||
bbox[:, 1] = bbox[:, 1] / cfg.input_img_shape[0] * cfg.output_hm_shape[1]
|
||||
|
||||
# make box a rectangle without rotation
|
||||
xmin = np.min(bbox[:, 0]);
|
||||
xmax = np.max(bbox[:, 0]);
|
||||
ymin = np.min(bbox[:, 1]);
|
||||
ymax = np.max(bbox[:, 1]);
|
||||
bbox = np.array([xmin, ymin, xmax, ymax], dtype=np.float32)
|
||||
|
||||
bbox_valid = float(True)
|
||||
bbox = bbox.reshape(2, 2)
|
||||
|
||||
return bbox, bbox_valid
|
||||
|
||||
def __len__(self):
|
||||
return len(self.datalist)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
data = copy.deepcopy(self.datalist[idx])
|
||||
img_path, img_shape, bbox = data['img_path'], data['img_shape'], data['bbox']
|
||||
|
||||
# image load
|
||||
img = load_img(img_path)
|
||||
|
||||
# affine transform
|
||||
img, img2bb_trans, bb2img_trans, rot, do_flip = augmentation(img, bbox, self.data_split)
|
||||
img = self.transform(img.astype(np.float32)) / 255.
|
||||
|
||||
if self.data_split == 'train':
|
||||
# gt load
|
||||
with open(data['joints_2d_path']) as f:
|
||||
joint_img = np.array(json.load(f)).reshape(-1, 2)
|
||||
if self.resolution == (2160, 3840):
|
||||
joint_img[:, :2] = np.dot(data['img2bb_trans_from_orig'],
|
||||
np.concatenate((joint_img, np.ones_like(joint_img[:, :1])), 1).transpose(
|
||||
1, 0)).transpose(1,
|
||||
0) # transform from original image to crop_and_resize image
|
||||
joint_img[:, 0] = joint_img[:, 0] / 3840 * self.resolution[1]
|
||||
joint_img[:, 1] = joint_img[:, 1] / 2160 * self.resolution[0]
|
||||
with open(data['joints_3d_path']) as f:
|
||||
joint_cam = np.array(json.load(f)).reshape(-1, 3)
|
||||
### HARDCODE vis for debug
|
||||
# joint_cam_orig = joint_cam.copy()
|
||||
with open(data['smplx_param_path'], 'rb') as f:
|
||||
smplx_param = pickle.load(f, encoding='latin1')
|
||||
|
||||
# hand and face bbox transform
|
||||
lhand_bbox, rhand_bbox, face_bbox = data['lhand_bbox'], data['rhand_bbox'], data['face_bbox']
|
||||
lhand_bbox, lhand_bbox_valid = self.process_hand_face_bbox(lhand_bbox, do_flip, img_shape, img2bb_trans)
|
||||
rhand_bbox, rhand_bbox_valid = self.process_hand_face_bbox(rhand_bbox, do_flip, img_shape, img2bb_trans)
|
||||
face_bbox, face_bbox_valid = self.process_hand_face_bbox(face_bbox, do_flip, img_shape, img2bb_trans)
|
||||
if do_flip:
|
||||
lhand_bbox, rhand_bbox = rhand_bbox, lhand_bbox
|
||||
lhand_bbox_valid, rhand_bbox_valid = rhand_bbox_valid, lhand_bbox_valid
|
||||
lhand_bbox_center = (lhand_bbox[0] + lhand_bbox[1]) / 2.;
|
||||
rhand_bbox_center = (rhand_bbox[0] + rhand_bbox[1]) / 2.;
|
||||
face_bbox_center = (face_bbox[0] + face_bbox[1]) / 2.
|
||||
lhand_bbox_size = lhand_bbox[1] - lhand_bbox[0];
|
||||
rhand_bbox_size = rhand_bbox[1] - rhand_bbox[0];
|
||||
face_bbox_size = face_bbox[1] - face_bbox[0];
|
||||
|
||||
"""
|
||||
# for debug
|
||||
_img = img.numpy().transpose(1,2,0)[:,:,::-1].copy() * 255
|
||||
if lhand_bbox_valid:
|
||||
_tmp = lhand_bbox.copy().reshape(2,2)
|
||||
_tmp[:,0] = _tmp[:,0] / cfg.output_hm_shape[2] * cfg.input_img_shape[1]
|
||||
_tmp[:,1] = _tmp[:,1] / cfg.output_hm_shape[1] * cfg.input_img_shape[0]
|
||||
cv2.rectangle(_img, (int(_tmp[0,0]), int(_tmp[0,1])), (int(_tmp[1,0]), int(_tmp[1,1])), (255,0,0), 3)
|
||||
cv2.imwrite('agora_' + str(idx) + '_lhand.jpg', _img)
|
||||
if rhand_bbox_valid:
|
||||
_tmp = rhand_bbox.copy().reshape(2,2)
|
||||
_tmp[:,0] = _tmp[:,0] / cfg.output_hm_shape[2] * cfg.input_img_shape[1]
|
||||
_tmp[:,1] = _tmp[:,1] / cfg.output_hm_shape[1] * cfg.input_img_shape[0]
|
||||
cv2.rectangle(_img, (int(_tmp[0,0]), int(_tmp[0,1])), (int(_tmp[1,0]), int(_tmp[1,1])), (255,0,0), 3)
|
||||
cv2.imwrite('agora_' + str(idx) + '_rhand.jpg', _img)
|
||||
if face_bbox_valid:
|
||||
_tmp = face_bbox.copy().reshape(2,2)
|
||||
_tmp[:,0] = _tmp[:,0] / cfg.output_hm_shape[2] * cfg.input_img_shape[1]
|
||||
_tmp[:,1] = _tmp[:,1] / cfg.output_hm_shape[1] * cfg.input_img_shape[0]
|
||||
cv2.rectangle(_img, (int(_tmp[0,0]), int(_tmp[0,1])), (int(_tmp[1,0]), int(_tmp[1,1])), (255,0,0), 3)
|
||||
cv2.imwrite('agora_' + str(idx) + '_face.jpg', _img)
|
||||
#cv2.imwrite('agora_' + str(idx) + '.jpg', _img)
|
||||
"""
|
||||
|
||||
# coordinates
|
||||
joint_cam = joint_cam - joint_cam[self.joint_set['root_joint_idx'], None, :] # root-relative
|
||||
joint_cam[self.joint_set['joint_part']['lhand'], :] = joint_cam[self.joint_set['joint_part']['lhand'],
|
||||
:] - joint_cam[self.joint_set['lwrist_idx'], None,
|
||||
:] # left hand root-relative
|
||||
joint_cam[self.joint_set['joint_part']['rhand'], :] = joint_cam[self.joint_set['joint_part']['rhand'],
|
||||
:] - joint_cam[self.joint_set['rwrist_idx'], None,
|
||||
:] # right hand root-relative
|
||||
joint_cam[self.joint_set['joint_part']['face'], :] = joint_cam[self.joint_set['joint_part']['face'],
|
||||
:] - joint_cam[self.joint_set['neck_idx'], None,
|
||||
:] # face root-relative
|
||||
joint_img = np.concatenate((joint_img[:, :2], joint_cam[:, 2:]), 1) # x, y, depth
|
||||
joint_img[self.joint_set['joint_part']['body'], 2] = (joint_cam[self.joint_set['joint_part'][
|
||||
'body'], 2].copy() / (
|
||||
cfg.body_3d_size / 2) + 1) / 2. * \
|
||||
cfg.output_hm_shape[0] # body depth discretize
|
||||
joint_img[self.joint_set['joint_part']['lhand'], 2] = (joint_cam[self.joint_set['joint_part'][
|
||||
'lhand'], 2].copy() / (
|
||||
cfg.hand_3d_size / 2) + 1) / 2. * \
|
||||
cfg.output_hm_shape[0] # left hand depth discretize
|
||||
joint_img[self.joint_set['joint_part']['rhand'], 2] = (joint_cam[self.joint_set['joint_part'][
|
||||
'rhand'], 2].copy() / (
|
||||
cfg.hand_3d_size / 2) + 1) / 2. * \
|
||||
cfg.output_hm_shape[0] # right hand depth discretize
|
||||
joint_img[self.joint_set['joint_part']['face'], 2] = (joint_cam[self.joint_set['joint_part'][
|
||||
'face'], 2].copy() / (
|
||||
cfg.face_3d_size / 2) + 1) / 2. * \
|
||||
cfg.output_hm_shape[0] # face depth discretize
|
||||
joint_valid = np.ones_like(joint_img[:, :1])
|
||||
# alr ra when passed into this function
|
||||
joint_img, joint_cam_ra, _, joint_valid, joint_trunc = process_db_coord(joint_img, joint_cam, joint_valid,
|
||||
do_flip, img_shape,
|
||||
self.joint_set['flip_pairs'],
|
||||
img2bb_trans, rot,
|
||||
self.joint_set['joints_name'],
|
||||
smpl_x.joints_name)
|
||||
# reverse ra
|
||||
joint_cam_wo_ra = joint_cam_ra.copy()
|
||||
joint_cam_wo_ra[smpl_x.joint_part['lhand'], :] = joint_cam_wo_ra[smpl_x.joint_part['lhand'], :] \
|
||||
+ joint_cam_wo_ra[smpl_x.lwrist_idx, None, :] # left hand root-relative
|
||||
joint_cam_wo_ra[smpl_x.joint_part['rhand'], :] = joint_cam_wo_ra[smpl_x.joint_part['rhand'], :] \
|
||||
+ joint_cam_wo_ra[smpl_x.rwrist_idx, None, :] # right hand root-relative
|
||||
joint_cam_wo_ra[smpl_x.joint_part['face'], :] = joint_cam_wo_ra[smpl_x.joint_part['face'], :] \
|
||||
+ joint_cam_wo_ra[smpl_x.neck_idx, None,: ] # face root-relative
|
||||
|
||||
|
||||
"""
|
||||
# for debug
|
||||
_tmp = joint_img.copy()
|
||||
_tmp[:,0] = _tmp[:,0] / cfg.output_hm_shape[2] * cfg.input_img_shape[1]
|
||||
_tmp[:,1] = _tmp[:,1] / cfg.output_hm_shape[1] * cfg.input_img_shape[0]
|
||||
_img = img.numpy().transpose(1,2,0)[:,:,::-1] * 255
|
||||
_img = vis_keypoints(_img.copy(), _tmp)
|
||||
cv2.imwrite('agora_' + str(idx) + '.jpg', _img)
|
||||
"""
|
||||
|
||||
"""
|
||||
# for debug
|
||||
_tmp = joint_cam.copy()[:,:2]
|
||||
_tmp[:,0] = _tmp[:,0] / (cfg.body_3d_size / 2) * cfg.input_img_shape[1] + cfg.input_img_shape[1]/2
|
||||
_tmp[:,1] = _tmp[:,1] / (cfg.body_3d_size / 2) * cfg.input_img_shape[0] + cfg.input_img_shape[0]/2
|
||||
_img = np.zeros((cfg.input_img_shape[0], cfg.input_img_shape[1], 3), dtype=np.float32)
|
||||
_img = vis_keypoints(_img.copy(), _tmp)
|
||||
cv2.imwrite('agora_' + str(idx) + '_cam.jpg', _img)
|
||||
"""
|
||||
|
||||
# smplx parameters
|
||||
root_pose = np.array(smplx_param['global_orient'], dtype=np.float32).reshape(
|
||||
-1) # rotation to world coordinate
|
||||
body_pose = np.array(smplx_param['body_pose'], dtype=np.float32).reshape(-1)
|
||||
|
||||
# use adapted shape for adults
|
||||
if getattr(cfg, 'agora_fix_betas', False) and not data['kid']:
|
||||
shape = np.array(smplx_param['betas_neutral'], dtype=np.float32).reshape(-1)[:10]
|
||||
else:
|
||||
shape = np.array(smplx_param['betas'], dtype=np.float32).reshape(-1)[:10] # bug?
|
||||
|
||||
lhand_pose = np.array(smplx_param['left_hand_pose'], dtype=np.float32).reshape(-1)
|
||||
rhand_pose = np.array(smplx_param['right_hand_pose'], dtype=np.float32).reshape(-1)
|
||||
jaw_pose = np.array(smplx_param['jaw_pose'], dtype=np.float32).reshape(-1)
|
||||
expr = np.array(smplx_param['expression'], dtype=np.float32).reshape(-1)
|
||||
trans = np.array(smplx_param['transl'], dtype=np.float32).reshape(-1) # translation to world coordinate
|
||||
cam_param = {'focal': cfg.focal,
|
||||
'princpt': cfg.princpt} # put random camera paraemter as we do not use coordinates from smplx parameters
|
||||
smplx_param = {'root_pose': root_pose, 'body_pose': body_pose, 'shape': shape,
|
||||
'lhand_pose': lhand_pose, 'lhand_valid': True,
|
||||
'rhand_pose': rhand_pose, 'rhand_valid': True,
|
||||
'jaw_pose': jaw_pose, 'expr': expr, 'face_valid': True,
|
||||
'trans': trans}
|
||||
_, _, _, smplx_pose, smplx_shape, smplx_expr, smplx_pose_valid, _, smplx_expr_valid, _ = process_human_model_output(
|
||||
smplx_param, cam_param, do_flip, img_shape, img2bb_trans, rot, 'smplx')
|
||||
### HARDCODE vis for debug
|
||||
# mesh_rot_, joint_cam_, _, smplx_pose, smplx_shape, smplx_expr, smplx_pose_valid, _, smplx_expr_valid, mesh_orig, joint_cam_orig_ = process_human_model_output(
|
||||
# smplx_param, cam_param, do_flip, img_shape, img2bb_trans, rot, 'smplx')
|
||||
smplx_pose_valid = np.tile(smplx_pose_valid[:, None], (1, 3)).reshape(-1)
|
||||
|
||||
if not getattr(cfg, 'agora_valid_root_pose', False):
|
||||
smplx_pose_valid[:3] = 0 # global orient of the provided parameter is a rotation to world coordinate system. I want camera coordinate system.
|
||||
smplx_shape_valid = True
|
||||
inputs = {'img': img}
|
||||
targets = {'joint_img': joint_img, 'joint_cam': joint_cam_wo_ra, #from annot
|
||||
'smplx_joint_img': joint_img, 'smplx_joint_cam': joint_cam_ra, #_smplx_joint_cam, # from smplx param w/ ra
|
||||
'smplx_pose': smplx_pose, 'smplx_shape': smplx_shape, 'smplx_expr': smplx_expr,
|
||||
'lhand_bbox_center': lhand_bbox_center, 'lhand_bbox_size': lhand_bbox_size,
|
||||
'rhand_bbox_center': rhand_bbox_center, 'rhand_bbox_size': rhand_bbox_size,
|
||||
'face_bbox_center': face_bbox_center, 'face_bbox_size': face_bbox_size}
|
||||
meta_info = {'joint_valid': joint_valid, 'joint_trunc': joint_trunc,
|
||||
'smplx_joint_valid': joint_valid, 'smplx_joint_trunc': joint_trunc,
|
||||
'smplx_pose_valid': smplx_pose_valid, 'smplx_shape_valid': float(smplx_shape_valid),
|
||||
'smplx_expr_valid': float(smplx_expr_valid), 'is_3D': float(True),
|
||||
'lhand_bbox_valid': lhand_bbox_valid, 'rhand_bbox_valid': rhand_bbox_valid, 'face_bbox_valid': face_bbox_valid}
|
||||
### HARDCODE vis for debug
|
||||
# 'gt_3d_path': data['joints_3d_path'], 'smplx_path': data['smplx_param_path'], 'id': idx}
|
||||
return inputs, targets, meta_info
|
||||
else:
|
||||
# load crop and resize information (for the 4K setting)
|
||||
if self.resolution == (2160, 3840):
|
||||
img2bb_trans = np.dot(
|
||||
np.concatenate((img2bb_trans,
|
||||
np.array([0, 0, 1], dtype=np.float32).reshape(1, 3))),
|
||||
np.concatenate((data['img2bb_trans_from_orig'],
|
||||
np.array([0, 0, 1], dtype=np.float32).reshape(1, 3)))
|
||||
)
|
||||
bb2img_trans = np.linalg.inv(img2bb_trans)[:2, :]
|
||||
img2bb_trans = img2bb_trans[:2, :]
|
||||
|
||||
if self.test_set == 'val':
|
||||
# gt load
|
||||
with open(data['verts_path']) as f:
|
||||
verts = np.array(json.load(f)).reshape(-1, 3)
|
||||
|
||||
with open(data['smplx_param_path'], 'rb') as f:
|
||||
smplx_param = pickle.load(f, encoding='latin1')
|
||||
transl = np.array(smplx_param['transl'], dtype=np.float32).reshape(-1)
|
||||
|
||||
inputs = {'img': img}
|
||||
targets = {'smplx_mesh_cam': verts}
|
||||
meta_info = {'bb2img_trans': bb2img_trans, 'img_path': img_path, 'gt_smplx_transl':transl}
|
||||
else:
|
||||
inputs = {'img': img}
|
||||
targets = {'smplx_mesh_cam': np.zeros((smpl_x.vertex_num, 3), dtype=np.float32)} # dummy vertex
|
||||
meta_info = {'bb2img_trans': bb2img_trans, 'img_path': img_path}
|
||||
|
||||
return inputs, targets, meta_info
|
||||
|
||||
def evaluate(self, outs, cur_sample_idx):
|
||||
annots = self.datalist
|
||||
sample_num = len(outs)
|
||||
eval_result = {'pa_mpvpe_all': [], 'pa_mpvpe_l_hand': [], 'pa_mpvpe_r_hand': [], 'pa_mpvpe_hand': [], 'pa_mpvpe_face': [],
|
||||
'mpvpe_all': [], 'mpvpe_l_hand': [], 'mpvpe_r_hand': [], 'mpvpe_hand': [], 'mpvpe_face': []}
|
||||
|
||||
vis = getattr(cfg, 'vis', False)
|
||||
vis_save_dir = cfg.vis_dir
|
||||
|
||||
if getattr(cfg, 'vis', False):
|
||||
import csv
|
||||
csv_file = f'{cfg.vis_dir}/agora_smplx_error.csv'
|
||||
file = open(csv_file, 'a', newline='')
|
||||
writer = csv.writer(file)
|
||||
|
||||
for n in range(sample_num):
|
||||
annot = annots[cur_sample_idx + n]
|
||||
out = outs[n]
|
||||
mesh_gt = out['smplx_mesh_cam_target']
|
||||
mesh_out = out['smplx_mesh_cam']
|
||||
|
||||
# MPVPE from all vertices
|
||||
mesh_out_align = mesh_out - np.dot(smpl_x.J_regressor, mesh_out)[smpl_x.J_regressor_idx['pelvis'], None,
|
||||
:] + np.dot(smpl_x.J_regressor, mesh_gt)[smpl_x.J_regressor_idx['pelvis'], None,
|
||||
:]
|
||||
mpvpe_all = np.sqrt(np.sum((mesh_out_align - mesh_gt) ** 2, 1)).mean() * 1000
|
||||
eval_result['mpvpe_all'].append(mpvpe_all)
|
||||
mesh_out_align = rigid_align(mesh_out, mesh_gt)
|
||||
pa_mpvpe_all = np.sqrt(np.sum((mesh_out_align - mesh_gt) ** 2, 1)).mean() * 1000
|
||||
eval_result['pa_mpvpe_all'].append(pa_mpvpe_all)
|
||||
|
||||
# MPVPE from hand vertices
|
||||
mesh_gt_lhand = mesh_gt[smpl_x.hand_vertex_idx['left_hand'], :]
|
||||
mesh_out_lhand = mesh_out[smpl_x.hand_vertex_idx['left_hand'], :]
|
||||
mesh_gt_rhand = mesh_gt[smpl_x.hand_vertex_idx['right_hand'], :]
|
||||
mesh_out_rhand = mesh_out[smpl_x.hand_vertex_idx['right_hand'], :]
|
||||
mesh_out_lhand_align = mesh_out_lhand - np.dot(smpl_x.J_regressor, mesh_out)[
|
||||
smpl_x.J_regressor_idx['lwrist'], None, :] + np.dot(
|
||||
smpl_x.J_regressor, mesh_gt)[smpl_x.J_regressor_idx['lwrist'], None, :]
|
||||
mesh_out_rhand_align = mesh_out_rhand - np.dot(smpl_x.J_regressor, mesh_out)[
|
||||
smpl_x.J_regressor_idx['rwrist'], None, :] + np.dot(
|
||||
smpl_x.J_regressor, mesh_gt)[smpl_x.J_regressor_idx['rwrist'], None, :]
|
||||
eval_result['mpvpe_l_hand'].append(np.sqrt(
|
||||
np.sum((mesh_out_lhand_align - mesh_gt_lhand) ** 2, 1)).mean() * 1000)
|
||||
eval_result['mpvpe_r_hand'].append(np.sqrt(
|
||||
np.sum((mesh_out_rhand_align - mesh_gt_rhand) ** 2, 1)).mean() * 1000)
|
||||
eval_result['mpvpe_hand'].append((np.sqrt(
|
||||
np.sum((mesh_out_lhand_align - mesh_gt_lhand) ** 2, 1)).mean() * 1000 + np.sqrt(
|
||||
np.sum((mesh_out_rhand_align - mesh_gt_rhand) ** 2, 1)).mean() * 1000) / 2.)
|
||||
mesh_out_lhand_align = rigid_align(mesh_out_lhand, mesh_gt_lhand)
|
||||
mesh_out_rhand_align = rigid_align(mesh_out_rhand, mesh_gt_rhand)
|
||||
eval_result['pa_mpvpe_l_hand'].append(np.sqrt(
|
||||
np.sum((mesh_out_lhand_align - mesh_gt_lhand) ** 2, 1)).mean() * 1000)
|
||||
eval_result['pa_mpvpe_r_hand'].append(np.sqrt(
|
||||
np.sum((mesh_out_rhand_align - mesh_gt_rhand) ** 2, 1)).mean() * 1000)
|
||||
eval_result['pa_mpvpe_hand'].append((np.sqrt(
|
||||
np.sum((mesh_out_lhand_align - mesh_gt_lhand) ** 2, 1)).mean() * 1000 + np.sqrt(
|
||||
np.sum((mesh_out_rhand_align - mesh_gt_rhand) ** 2, 1)).mean() * 1000) / 2.)
|
||||
|
||||
# MPVPE from face vertices
|
||||
mesh_gt_face = mesh_gt[smpl_x.face_vertex_idx, :]
|
||||
mesh_out_face = mesh_out[smpl_x.face_vertex_idx, :]
|
||||
mesh_out_face_align = mesh_out_face - np.dot(smpl_x.J_regressor, mesh_out)[smpl_x.J_regressor_idx['neck'],
|
||||
None, :] + np.dot(smpl_x.J_regressor, mesh_gt)[
|
||||
smpl_x.J_regressor_idx['neck'], None, :]
|
||||
eval_result['mpvpe_face'].append(
|
||||
np.sqrt(np.sum((mesh_out_face_align - mesh_gt_face) ** 2, 1)).mean() * 1000)
|
||||
mesh_out_face_align = rigid_align(mesh_out_face, mesh_gt_face)
|
||||
eval_result['pa_mpvpe_face'].append(
|
||||
np.sqrt(np.sum((mesh_out_face_align - mesh_gt_face) ** 2, 1)).mean() * 1000)
|
||||
|
||||
### HARDCODE
|
||||
if vis:
|
||||
|
||||
# from utils.vis import vis_keypoints, vis_mesh, save_obj, render_mesh
|
||||
# # img = (out['img'].transpose(1,2,0)[:,:,::-1] * 255).copy()
|
||||
# # joint_img = out['joint_img'].copy()
|
||||
# # joint_img[:,0] = joint_img[:,0] / cfg.output_hm_shape[2] * cfg.input_img_shape[1]
|
||||
# # joint_img[:,1] = joint_img[:,1] / cfg.output_hm_shape[1] * cfg.input_img_shape[0]
|
||||
# # for j in range(len(joint_img)):
|
||||
# # cv2.circle(img, (int(joint_img[j][0]), int(joint_img[j][1])), 3, (0,0,255), -1)
|
||||
# # cv2.imwrite(str(cur_sample_idx + n) + '.jpg', img)
|
||||
|
||||
# img_path = annot['img_path']
|
||||
# img_id = img_path.split('/')[-1][:-4]
|
||||
# ann_id = 0
|
||||
# # ann_id = annot['ann_id']
|
||||
# img = load_img(img_path)[:, :, ::-1]
|
||||
# bbox = annot['bbox']
|
||||
# focal = list(cfg.focal)
|
||||
# princpt = list(cfg.princpt)
|
||||
# focal[0] = focal[0] / cfg.input_body_shape[1] * bbox[2]
|
||||
# focal[1] = focal[1] / cfg.input_body_shape[0] * bbox[3]
|
||||
# princpt[0] = princpt[0] / cfg.input_body_shape[1] * bbox[2] + bbox[0]
|
||||
# princpt[1] = princpt[1] / cfg.input_body_shape[0] * bbox[3] + bbox[1]
|
||||
# img = render_mesh(img, out['smplx_mesh_cam'], smpl_x.face, {'focal': focal, 'princpt': princpt}, mesh_as_vertices=True)
|
||||
# # img = cv2.resize(img, (512,512))
|
||||
# cv2.imwrite(osp.join(vis_save_dir, img_id + '_' + str(ann_id) + '.jpg'), img)
|
||||
|
||||
# vis_mesh_out = out['smplx_mesh_cam']
|
||||
# vis_mesh_out = vis_mesh_out - np.dot(smpl_x.layer['neutral'].J_regressor, vis_mesh_out)[
|
||||
# smpl_x.J_regressor_idx['pelvis'], None, :]
|
||||
# # vis_mesh_gt = out['smplx_mesh_cam_target']
|
||||
# # vis_mesh_gt = vis_mesh_gt - np.dot(smpl_x.layer['neutral'].J_regressor, vis_mesh_gt)[smpl_x.J_regressor_idx['pelvis'],None,:]
|
||||
# # save_obj(vis_mesh_out, smpl_x.face, osp.join(img_id + '_' + str(ann_id) + '.obj'))
|
||||
# # save_obj(vis_mesh_gt, smpl_x.face, str(cur_sample_idx + n) + '_gt.obj')
|
||||
img_path = out['img_path']
|
||||
rel_img_path = img_path.split('..')[-1]
|
||||
smplx_pred = {}
|
||||
smplx_pred['global_orient'] = out['smplx_root_pose'].reshape(-1,3)
|
||||
smplx_pred['body_pose'] = out['smplx_body_pose'].reshape(-1,3)
|
||||
smplx_pred['left_hand_pose'] = out['smplx_lhand_pose'].reshape(-1,3)
|
||||
smplx_pred['right_hand_pose'] = out['smplx_rhand_pose'].reshape(-1,3)
|
||||
smplx_pred['jaw_pose'] = out['smplx_jaw_pose'].reshape(-1,3)
|
||||
smplx_pred['leye_pose'] = np.zeros((1, 3))
|
||||
smplx_pred['reye_pose'] = np.zeros((1, 3))
|
||||
smplx_pred['betas'] = out['smplx_shape'].reshape(-1,10)
|
||||
smplx_pred['expression'] = out['smplx_expr'].reshape(-1,10)
|
||||
smplx_pred['transl'] = out['gt_smplx_transl'].reshape(-1,3)
|
||||
smplx_pred['img_path'] = rel_img_path
|
||||
|
||||
npz_path = os.path.join(cfg.vis_dir, f'{self.save_idx}.npz')
|
||||
np.savez(npz_path, **smplx_pred)
|
||||
|
||||
# save img path and error
|
||||
new_line = [self.save_idx, rel_img_path, mpvpe_all, pa_mpvpe_all]
|
||||
# Append the new line to the CSV file
|
||||
writer.writerow(new_line)
|
||||
self.save_idx += 1
|
||||
|
||||
# save_obj(out['smplx_mesh_cam'], smpl_x.face, str(cur_sample_idx + n) + '.obj')
|
||||
|
||||
# save results for the official evaluation codes/server
|
||||
save_name = annot['img_path'].split('/')[-1][:-4]
|
||||
if self.data_split == 'test' and self.test_set == 'test':
|
||||
if self.resolution == (2160, 3840):
|
||||
save_name = save_name.split('_pid')[0]
|
||||
elif self.data_split == 'test' and self.test_set == 'val':
|
||||
if self.resolution == (2160, 3840):
|
||||
save_name = save_name.split('_ann_id')[0]
|
||||
else:
|
||||
save_name = save_name.split('_1280x720')[0]
|
||||
if 'person_idx' in annot:
|
||||
person_idx = annot['person_idx']
|
||||
else:
|
||||
exist_result_path = glob(osp.join(cfg.result_dir, 'AGORA', save_name + '*'))
|
||||
if len(exist_result_path) == 0:
|
||||
person_idx = 0
|
||||
else:
|
||||
last_person_idx = max(
|
||||
[int(name.split('personId_')[1].split('.pkl')[0]) for name in exist_result_path])
|
||||
person_idx = last_person_idx + 1
|
||||
save_name += '_personId_' + str(person_idx) + '.pkl'
|
||||
|
||||
joint_proj = out['smplx_joint_proj']
|
||||
joint_proj[:, 0] = joint_proj[:, 0] / cfg.output_hm_shape[2] * cfg.input_img_shape[1]
|
||||
joint_proj[:, 1] = joint_proj[:, 1] / cfg.output_hm_shape[1] * cfg.input_img_shape[0]
|
||||
joint_proj = np.concatenate((joint_proj, np.ones_like(joint_proj[:, :1])), 1)
|
||||
joint_proj = np.dot(out['bb2img_trans'], joint_proj.transpose(1, 0)).transpose(1, 0)
|
||||
joint_proj[:, 0] = joint_proj[:, 0] / self.resolution[1] * 3840 # restore to original resolution
|
||||
joint_proj[:, 1] = joint_proj[:, 1] / self.resolution[0] * 2160 # restore to original resolution
|
||||
save_dict = {'params':
|
||||
{'transl': out['cam_trans'].reshape(1, -1),
|
||||
'global_orient': out['smplx_root_pose'].reshape(1, -1),
|
||||
'body_pose': out['smplx_body_pose'].reshape(1, -1),
|
||||
'left_hand_pose': out['smplx_lhand_pose'].reshape(1, -1),
|
||||
'right_hand_pose': out['smplx_rhand_pose'].reshape(1, -1),
|
||||
'reye_pose': np.zeros((1, 3)),
|
||||
'leye_pose': np.zeros((1, 3)),
|
||||
'jaw_pose': out['smplx_jaw_pose'].reshape(1, -1),
|
||||
'expression': out['smplx_expr'].reshape(1, -1),
|
||||
'betas': out['smplx_shape'].reshape(1, -1)},
|
||||
'joints': joint_proj.reshape(1, -1, 2)
|
||||
}
|
||||
os.makedirs(osp.join(cfg.result_dir, 'predictions'), exist_ok=True)
|
||||
with open(osp.join(cfg.result_dir, 'predictions', save_name), 'wb') as f:
|
||||
pickle.dump(save_dict, f)
|
||||
|
||||
"""
|
||||
# for debug
|
||||
img_path = annot['img_path']
|
||||
img_path = osp.join(self.data_path, '3840x2160', 'test', img_path.split('/')[-1].split('_')[0] + '.png')
|
||||
img = cv2.imread(img_path)
|
||||
img = vis_keypoints(img.copy(), joint_proj)
|
||||
cv2.imwrite(img_path.split('/')[-1], img)
|
||||
"""
|
||||
if getattr(cfg, 'vis', False):
|
||||
file.close()
|
||||
|
||||
return eval_result
|
||||
|
||||
def print_eval_result(self, eval_result):
|
||||
|
||||
print('AGORA test results are dumped at: ' + osp.join(cfg.result_dir, 'predictions'))
|
||||
|
||||
if self.data_split == 'test' and self.test_set == 'test': # do not print. just submit the results to the official evaluation server
|
||||
return
|
||||
|
||||
print('======AGORA-val======')
|
||||
print(f'{cfg.vis_dir}')
|
||||
print('PA MPVPE (All): %.2f mm' % np.mean(eval_result['pa_mpvpe_all']))
|
||||
print('PA MPVPE (L-Hands): %.2f mm' % np.mean(eval_result['pa_mpvpe_l_hand']))
|
||||
print('PA MPVPE (R-Hands): %.2f mm' % np.mean(eval_result['pa_mpvpe_r_hand']))
|
||||
print('PA MPVPE (Hands): %.2f mm' % np.mean(eval_result['pa_mpvpe_hand']))
|
||||
print('PA MPVPE (Face): %.2f mm' % np.mean(eval_result['pa_mpvpe_face']))
|
||||
print()
|
||||
|
||||
print('MPVPE (All): %.2f mm' % np.mean(eval_result['mpvpe_all']))
|
||||
print('MPVPE (L-Hands): %.2f mm' % np.mean(eval_result['mpvpe_l_hand']))
|
||||
print('MPVPE (R-Hands): %.2f mm' % np.mean(eval_result['mpvpe_r_hand']))
|
||||
print('MPVPE (Hands): %.2f mm' % np.mean(eval_result['mpvpe_hand']))
|
||||
print('MPVPE (Face): %.2f mm' % np.mean(eval_result['mpvpe_face']))
|
||||
print()
|
||||
|
||||
print(f"{np.mean(eval_result['pa_mpvpe_all'])},{np.mean(eval_result['pa_mpvpe_l_hand'])},{np.mean(eval_result['pa_mpvpe_r_hand'])},{np.mean(eval_result['pa_mpvpe_hand'])},{np.mean(eval_result['pa_mpvpe_face'])},"
|
||||
f"{np.mean(eval_result['mpvpe_all'])},{np.mean(eval_result['mpvpe_l_hand'])},{np.mean(eval_result['mpvpe_r_hand'])},{np.mean(eval_result['mpvpe_hand'])},{np.mean(eval_result['mpvpe_face'])}")
|
||||
print()
|
||||
|
||||
f = open(os.path.join(cfg.result_dir, 'result.txt'), 'w')
|
||||
f.write(f'AGORA-val dataset: \n')
|
||||
f.write('PA MPVPE (All): %.2f mm\n' % np.mean(eval_result['pa_mpvpe_all']))
|
||||
f.write('PA MPVPE (L-Hands): %.2f mm' % np.mean(eval_result['pa_mpvpe_l_hand']))
|
||||
f.write('PA MPVPE (R-Hands): %.2f mm' % np.mean(eval_result['pa_mpvpe_r_hand']))
|
||||
f.write('PA MPVPE (Hands): %.2f mm\n' % np.mean(eval_result['pa_mpvpe_hand']))
|
||||
f.write('PA MPVPE (Face): %.2f mm\n' % np.mean(eval_result['pa_mpvpe_face']))
|
||||
f.write('MPVPE (All): %.2f mm\n' % np.mean(eval_result['mpvpe_all']))
|
||||
f.write('MPVPE (L-Hands): %.2f mm' % np.mean(eval_result['mpvpe_l_hand']))
|
||||
f.write('MPVPE (R-Hands): %.2f mm' % np.mean(eval_result['mpvpe_r_hand']))
|
||||
f.write('MPVPE (Hands): %.2f mm' % np.mean(eval_result['mpvpe_hand']))
|
||||
f.write('MPVPE (Face): %.2f mm\n' % np.mean(eval_result['mpvpe_face']))
|
||||
f.write(f"{np.mean(eval_result['pa_mpvpe_all'])},{np.mean(eval_result['pa_mpvpe_l_hand'])},{np.mean(eval_result['pa_mpvpe_r_hand'])},{np.mean(eval_result['pa_mpvpe_hand'])},{np.mean(eval_result['pa_mpvpe_face'])},"
|
||||
f"{np.mean(eval_result['mpvpe_all'])},{np.mean(eval_result['mpvpe_l_hand'])},{np.mean(eval_result['mpvpe_r_hand'])},{np.mean(eval_result['mpvpe_hand'])},{np.mean(eval_result['mpvpe_face'])}")
|
||||
|
||||
if getattr(cfg, 'eval_on_train', False):
|
||||
import csv
|
||||
csv_file = f'{cfg.root_dir}/output/agora_eval_on_train.csv'
|
||||
exp_id = cfg.exp_name.split('_')[1]
|
||||
new_line = [exp_id,np.mean(eval_result['pa_mpvpe_all']),np.mean(eval_result['pa_mpvpe_l_hand']),np.mean(eval_result['pa_mpvpe_r_hand']),np.mean(eval_result['pa_mpvpe_hand']),np.mean(eval_result['pa_mpvpe_face']),
|
||||
np.mean(eval_result['mpvpe_all']),np.mean(eval_result['mpvpe_l_hand']),np.mean(eval_result['mpvpe_r_hand']),np.mean(eval_result['mpvpe_hand']),np.mean(eval_result['mpvpe_face'])]
|
||||
|
||||
# Append the new line to the CSV file
|
||||
with open(csv_file, 'a', newline='') as file:
|
||||
writer = csv.writer(file)
|
||||
writer.writerow(new_line)
|
||||
@@ -0,0 +1,51 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class ARCTIC(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(ARCTIC, self).__init__(transform, data_split)
|
||||
|
||||
if getattr(cfg, 'eval_on_train', False):
|
||||
self.data_split = 'eval_train'
|
||||
print("Evaluate on train set.")
|
||||
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', f'arctic_{self.data_split}.npz')
|
||||
self.img_shape = None # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
|
||||
if 'train' in data_split:
|
||||
filename = getattr(cfg, 'filename', 'p1_train.npz')
|
||||
else:
|
||||
filename = getattr(cfg, 'filename', 'p1_val.npz')
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'ARCTIC')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
|
||||
# load data
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1),
|
||||
test_sample_interval=getattr(cfg, f'{self.__class__.__name__}_test_sample_interval', 10))
|
||||
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,45 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class BEDLAM(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(BEDLAM, self).__init__(transform, data_split)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', 'bedlam_train.npz')
|
||||
self.img_shape = None # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', 'bedlam_train.npz')
|
||||
else:
|
||||
raise ValueError('BEDLAM test set is not support')
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'BEDLAM')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
|
||||
# load data
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,50 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class Behave(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(Behave, self).__init__(transform, data_split)
|
||||
|
||||
pre_prc_file_train = 'behave_train_230516_231_downsampled.npz'
|
||||
pre_prc_file_test = 'behave_test_230516_090_downsampled.npz'
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', pre_prc_file_train)
|
||||
else:
|
||||
filename = getattr(cfg, 'filename', pre_prc_file_test)
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'Behave')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', filename)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.img_shape = (1536, 2048) # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
# check image shape
|
||||
img_path = osp.join(self.img_dir, np.load(self.annot_path)['image_path'][0])
|
||||
img_shape = cv2.imread(img_path).shape[:2]
|
||||
assert self.img_shape == img_shape, 'image shape is incorrect: {} vs {}'.format(self.img_shape, img_shape)
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,56 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class CHI3D(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(CHI3D, self).__init__(transform, data_split)
|
||||
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', 'CHI3D_train_230511_1492.npz')
|
||||
self.img_shape = (900, 900) # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
|
||||
self.datalist = []
|
||||
for pre_prc_file in ['CHI3D_train_230511_1492_0.npz','CHI3D_train_230511_1492_1.npz']:
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', pre_prc_file)
|
||||
else:
|
||||
raise ValueError('CHI3D test set is not support')
|
||||
|
||||
self.img_dir = cfg.data_dir # CHI3D included
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
|
||||
# check image shape
|
||||
img_path = osp.join(self.img_dir, np.load(self.annot_path)['image_path'][0])
|
||||
img_shape = cv2.imread(img_path).shape[:2]
|
||||
assert self.img_shape == img_shape, 'image shape is incorrect: {} vs {}'.format(self.img_shape, img_shape)
|
||||
|
||||
# load data
|
||||
datalist_slice = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
self.datalist.extend(datalist_slice)
|
||||
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class CrowdPose(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(CrowdPose, self).__init__(transform, data_split)
|
||||
|
||||
self.datalist = []
|
||||
|
||||
pre_prc_file = 'crowdpose_neural_annot_train_new.npz'
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', pre_prc_file)
|
||||
else:
|
||||
raise ValueError('CrowdPose test set is not support')
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'CrowdPose')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', filename)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.img_shape = None
|
||||
self.cam_param = {}
|
||||
print("Various image shape in CrowdPose dataset.")
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
+373
@@ -0,0 +1,373 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
from glob import glob
|
||||
import numpy as np
|
||||
from config import cfg
|
||||
import copy
|
||||
import json
|
||||
import cv2
|
||||
import torch
|
||||
from pycocotools.coco import COCO
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, load_ply
|
||||
from utils.transforms import rigid_align
|
||||
|
||||
|
||||
class EHF(torch.utils.data.Dataset):
|
||||
def __init__(self, transform, data_split):
|
||||
self.transform = transform
|
||||
self.data_split = data_split
|
||||
# self.data_path = osp.join('..', 'data', 'EHF', 'data')
|
||||
self.data_path = osp.join(cfg.data_dir, 'EHF', 'data')
|
||||
self.datalist = self.load_data()
|
||||
self.cam_param = {'R': [-2.98747896, 0.01172457, -0.05704687]}
|
||||
self.cam_param['R'], _ = cv2.Rodrigues(np.array(self.cam_param['R']))
|
||||
self.save_idx = 0
|
||||
|
||||
def load_data(self):
|
||||
datalist = []
|
||||
db = COCO(osp.join(self.data_path, 'EHF.json'))
|
||||
for aid in db.anns.keys():
|
||||
ann = db.anns[aid]
|
||||
image_id = ann['image_id']
|
||||
img = db.loadImgs(image_id)[0]
|
||||
img_shape = (img['height'], img['width'])
|
||||
img_path = osp.join(self.data_path, img['file_name'])
|
||||
|
||||
bbox = ann['body_bbox']
|
||||
bbox = process_bbox(bbox, img['width'], img['height'], ratio=getattr(cfg, 'bbox_ratio', 1.25))
|
||||
if bbox is None:
|
||||
continue
|
||||
|
||||
lhand_bbox = np.array(ann['lefthand_bbox']).reshape(4)
|
||||
if hasattr(cfg, 'bbox_ratio'):
|
||||
lhand_bbox = process_bbox(lhand_bbox, img['width'], img['height'], ratio=cfg.bbox_ratio)
|
||||
lhand_bbox[2:] += lhand_bbox[:2] # xywh -> xyxy
|
||||
|
||||
rhand_bbox = np.array(ann['righthand_bbox']).reshape(4)
|
||||
if hasattr(cfg, 'bbox_ratio'):
|
||||
rhand_bbox = process_bbox(rhand_bbox, img['width'], img['height'], ratio=cfg.bbox_ratio)
|
||||
rhand_bbox[2:] += rhand_bbox[:2] # xywh -> xyxy
|
||||
|
||||
face_bbox = np.array(ann['face_bbox']).reshape(4)
|
||||
if hasattr(cfg, 'bbox_ratio'):
|
||||
face_bbox = process_bbox(face_bbox, img['width'], img['height'], ratio=cfg.bbox_ratio)
|
||||
face_bbox[2:] += face_bbox[:2] # xywh -> xyxy
|
||||
|
||||
mesh_gt_path = osp.join(self.data_path, img['file_name'].split('_')[0] + '_align.ply')
|
||||
|
||||
data_dict = {'img_path': img_path, 'img_shape': img_shape, 'bbox': bbox, 'lhand_bbox': lhand_bbox,
|
||||
'rhand_bbox': rhand_bbox, 'face_bbox': face_bbox, 'mesh_gt_path': mesh_gt_path}
|
||||
datalist.append(data_dict)
|
||||
|
||||
return datalist
|
||||
|
||||
def process_hand_face_bbox(self, bbox, do_flip, img_shape, img2bb_trans):
|
||||
if bbox is None:
|
||||
bbox = np.zeros((2, 2), dtype=np.float32) # dummy value
|
||||
bbox_valid = float(False) # dummy value
|
||||
else:
|
||||
# reshape to top-left (x,y) and bottom-right (x,y)
|
||||
bbox = bbox.reshape(2, 2)
|
||||
|
||||
# flip augmentation
|
||||
if do_flip:
|
||||
bbox[:, 0] = img_shape[1] - bbox[:, 0] - 1
|
||||
bbox[0, 0], bbox[1, 0] = bbox[1, 0].copy(), bbox[0, 0].copy() # xmin <-> xmax swap
|
||||
|
||||
# make four points of the bbox
|
||||
bbox = bbox.reshape(4).tolist()
|
||||
xmin, ymin, xmax, ymax = bbox
|
||||
bbox = np.array([[xmin, ymin], [xmax, ymin], [xmax, ymax], [xmin, ymax]], dtype=np.float32).reshape(4, 2)
|
||||
|
||||
# affine transformation (crop, rotation, scale)
|
||||
bbox_xy1 = np.concatenate((bbox, np.ones_like(bbox[:, :1])), 1)
|
||||
bbox = np.dot(img2bb_trans, bbox_xy1.transpose(1, 0)).transpose(1, 0)[:, :2]
|
||||
bbox[:, 0] = bbox[:, 0] / cfg.input_img_shape[1] * cfg.output_hm_shape[1]
|
||||
bbox[:, 1] = bbox[:, 1] / cfg.input_img_shape[0] * cfg.output_hm_shape[0]
|
||||
|
||||
# make box a rectangle without rotation
|
||||
xmin = np.min(bbox[:, 0]);
|
||||
xmax = np.max(bbox[:, 0]);
|
||||
ymin = np.min(bbox[:, 1]);
|
||||
ymax = np.max(bbox[:, 1]);
|
||||
bbox = np.array([xmin, ymin, xmax, ymax], dtype=np.float32)
|
||||
|
||||
bbox_valid = float(True)
|
||||
bbox = bbox.reshape(2, 2)
|
||||
|
||||
return bbox, bbox_valid
|
||||
|
||||
def __len__(self):
|
||||
return len(self.datalist)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
data = copy.deepcopy(self.datalist[idx])
|
||||
img_path, img_shape, bbox, mesh_gt_path = data['img_path'], data['img_shape'], data['bbox'], data[
|
||||
'mesh_gt_path']
|
||||
|
||||
# image load
|
||||
img = load_img(img_path)
|
||||
|
||||
# affine transform
|
||||
img, img2bb_trans, bb2img_trans, rot, do_flip = augmentation(img, bbox, self.data_split)
|
||||
img = self.transform(img.astype(np.float32)) / 255.
|
||||
|
||||
# hand and face bbox transform
|
||||
lhand_bbox, lhand_bbox_valid = self.process_hand_face_bbox(data['lhand_bbox'], do_flip, img_shape, img2bb_trans)
|
||||
rhand_bbox, rhand_bbox_valid = self.process_hand_face_bbox(data['rhand_bbox'], do_flip, img_shape, img2bb_trans)
|
||||
face_bbox, face_bbox_valid = self.process_hand_face_bbox(data['face_bbox'], do_flip, img_shape, img2bb_trans)
|
||||
if do_flip:
|
||||
lhand_bbox, rhand_bbox = rhand_bbox, lhand_bbox
|
||||
lhand_bbox_valid, rhand_bbox_valid = rhand_bbox_valid, lhand_bbox_valid
|
||||
lhand_bbox_center = (lhand_bbox[0] + lhand_bbox[1]) / 2.;
|
||||
rhand_bbox_center = (rhand_bbox[0] + rhand_bbox[1]) / 2.;
|
||||
face_bbox_center = (face_bbox[0] + face_bbox[1]) / 2.
|
||||
lhand_bbox_size = lhand_bbox[1] - lhand_bbox[0];
|
||||
rhand_bbox_size = rhand_bbox[1] - rhand_bbox[0];
|
||||
face_bbox_size = face_bbox[1] - face_bbox[0];
|
||||
|
||||
# mesh gt load
|
||||
mesh_gt = load_ply(mesh_gt_path)
|
||||
|
||||
inputs = {'img': img}
|
||||
targets = {'smplx_mesh_cam': mesh_gt, 'lhand_bbox_center': lhand_bbox_center,
|
||||
'rhand_bbox_center': rhand_bbox_center, 'face_bbox_center': face_bbox_center,
|
||||
'lhand_bbox_size': lhand_bbox_size, 'rhand_bbox_size': rhand_bbox_size,
|
||||
'face_bbox_size': face_bbox_size}
|
||||
meta_info = {'bb2img_trans': bb2img_trans, 'lhand_bbox_valid': float(True), 'rhand_bbox_valid': float(True),
|
||||
'face_bbox_valid': float(True),
|
||||
'img_path': img_path}
|
||||
return inputs, targets, meta_info
|
||||
|
||||
def evaluate(self, outs, cur_sample_idx):
|
||||
annots = self.datalist
|
||||
sample_num = len(outs)
|
||||
eval_result = {'pa_mpvpe_all': [], 'pa_mpvpe_l_hand': [], 'pa_mpvpe_r_hand': [], 'pa_mpvpe_hand': [], 'pa_mpvpe_face': [],
|
||||
'mpvpe_all': [], 'mpvpe_l_hand': [], 'mpvpe_r_hand': [], 'mpvpe_hand': [], 'mpvpe_face': [],
|
||||
'pa_mpjpe_body': [], 'pa_mpjpe_l_hand': [], 'pa_mpjpe_r_hand': [], 'pa_mpjpe_hand': []}
|
||||
|
||||
if getattr(cfg, 'vis', False):
|
||||
import csv
|
||||
csv_file = f'{cfg.vis_dir}/ehf_smplx_error.csv'
|
||||
file = open(csv_file, 'a', newline='')
|
||||
writer = csv.writer(file)
|
||||
|
||||
for n in range(sample_num):
|
||||
annot = annots[cur_sample_idx + n]
|
||||
ann_id = annot['img_path'].split('/')[-1].split('_')[0]
|
||||
# print(annot['img_path'])
|
||||
# ann_id = annot['ann_id']
|
||||
out = outs[n]
|
||||
|
||||
# MPVPE from all vertices
|
||||
mesh_gt = np.dot(self.cam_param['R'], out['smplx_mesh_cam_target'].transpose(1, 0)).transpose(1, 0)
|
||||
mesh_out = out['smplx_mesh_cam']
|
||||
|
||||
# mesh_gt_align = rigid_align(mesh_gt, mesh_out)
|
||||
|
||||
# print(mesh_out.shape)
|
||||
mesh_out_align = rigid_align(mesh_out, mesh_gt)
|
||||
pa_mpvpe_all = np.sqrt(np.sum((mesh_out_align - mesh_gt) ** 2, 1)).mean() * 1000
|
||||
eval_result['pa_mpvpe_all'].append(pa_mpvpe_all)
|
||||
mesh_out_align = mesh_out - np.dot(smpl_x.J_regressor, mesh_out)[smpl_x.J_regressor_idx['pelvis'], None,
|
||||
:] + np.dot(smpl_x.J_regressor, mesh_gt)[smpl_x.J_regressor_idx['pelvis'], None,
|
||||
:]
|
||||
mpvpe_all = np.sqrt(np.sum((mesh_out_align - mesh_gt) ** 2, 1)).mean() * 1000
|
||||
eval_result['mpvpe_all'].append(mpvpe_all)
|
||||
|
||||
# MPVPE from hand vertices
|
||||
mesh_gt_lhand = mesh_gt[smpl_x.hand_vertex_idx['left_hand'], :]
|
||||
mesh_out_lhand = mesh_out[smpl_x.hand_vertex_idx['left_hand'], :]
|
||||
mesh_out_lhand_align = rigid_align(mesh_out_lhand, mesh_gt_lhand)
|
||||
mesh_gt_rhand = mesh_gt[smpl_x.hand_vertex_idx['right_hand'], :]
|
||||
mesh_out_rhand = mesh_out[smpl_x.hand_vertex_idx['right_hand'], :]
|
||||
mesh_out_rhand_align = rigid_align(mesh_out_rhand, mesh_gt_rhand)
|
||||
eval_result['pa_mpvpe_l_hand'].append(np.sqrt(
|
||||
np.sum((mesh_out_lhand_align - mesh_gt_lhand) ** 2, 1)).mean() * 1000)
|
||||
eval_result['pa_mpvpe_r_hand'].append(np.sqrt(
|
||||
np.sum((mesh_out_rhand_align - mesh_gt_rhand) ** 2, 1)).mean() * 1000)
|
||||
eval_result['pa_mpvpe_hand'].append((np.sqrt(
|
||||
np.sum((mesh_out_lhand_align - mesh_gt_lhand) ** 2, 1)).mean() * 1000 + np.sqrt(
|
||||
np.sum((mesh_out_rhand_align - mesh_gt_rhand) ** 2, 1)).mean() * 1000) / 2.)
|
||||
|
||||
mesh_out_lhand_align = mesh_out_lhand - np.dot(smpl_x.J_regressor, mesh_out)[
|
||||
smpl_x.J_regressor_idx['lwrist'], None, :] + np.dot(
|
||||
smpl_x.J_regressor, mesh_gt)[smpl_x.J_regressor_idx['lwrist'], None, :]
|
||||
mesh_out_rhand_align = mesh_out_rhand - np.dot(smpl_x.J_regressor, mesh_out)[
|
||||
smpl_x.J_regressor_idx['rwrist'], None, :] + np.dot(
|
||||
smpl_x.J_regressor, mesh_gt)[smpl_x.J_regressor_idx['rwrist'], None, :]
|
||||
|
||||
eval_result['mpvpe_l_hand'].append(np.sqrt(
|
||||
np.sum((mesh_out_lhand_align - mesh_gt_lhand) ** 2, 1)).mean() * 1000)
|
||||
eval_result['mpvpe_r_hand'].append(np.sqrt(
|
||||
np.sum((mesh_out_rhand_align - mesh_gt_rhand) ** 2, 1)).mean() * 1000)
|
||||
eval_result['mpvpe_hand'].append((np.sqrt(
|
||||
np.sum((mesh_out_lhand_align - mesh_gt_lhand) ** 2, 1)).mean() * 1000 + np.sqrt(
|
||||
np.sum((mesh_out_rhand_align - mesh_gt_rhand) ** 2, 1)).mean() * 1000) / 2.)
|
||||
|
||||
# MPVPE from face vertices
|
||||
mesh_gt_face = mesh_gt[smpl_x.face_vertex_idx, :]
|
||||
mesh_out_face = mesh_out[smpl_x.face_vertex_idx, :]
|
||||
mesh_out_face_align = rigid_align(mesh_out_face, mesh_gt_face)
|
||||
eval_result['pa_mpvpe_face'].append(
|
||||
np.sqrt(np.sum((mesh_out_face_align - mesh_gt_face) ** 2, 1)).mean() * 1000)
|
||||
mesh_out_face_align = mesh_out_face - np.dot(smpl_x.J_regressor, mesh_out)[smpl_x.J_regressor_idx['neck'],
|
||||
None, :] + np.dot(smpl_x.J_regressor, mesh_gt)[
|
||||
smpl_x.J_regressor_idx['neck'], None, :]
|
||||
eval_result['mpvpe_face'].append(
|
||||
np.sqrt(np.sum((mesh_out_face_align - mesh_gt_face) ** 2, 1)).mean() * 1000)
|
||||
|
||||
# MPJPE from body joints
|
||||
joint_gt_body = np.dot(smpl_x.j14_regressor, mesh_gt)
|
||||
joint_out_body = np.dot(smpl_x.j14_regressor, mesh_out)
|
||||
joint_out_body_align = rigid_align(joint_out_body, joint_gt_body)
|
||||
eval_result['pa_mpjpe_body'].append(
|
||||
np.sqrt(np.sum((joint_out_body_align - joint_gt_body) ** 2, 1)).mean() * 1000)
|
||||
|
||||
# MPJPE from hand joints
|
||||
joint_gt_lhand = np.dot(smpl_x.orig_hand_regressor['left'], mesh_gt)
|
||||
joint_out_lhand = np.dot(smpl_x.orig_hand_regressor['left'], mesh_out)
|
||||
joint_out_lhand_align = rigid_align(joint_out_lhand, joint_gt_lhand)
|
||||
joint_gt_rhand = np.dot(smpl_x.orig_hand_regressor['right'], mesh_gt)
|
||||
joint_out_rhand = np.dot(smpl_x.orig_hand_regressor['right'], mesh_out)
|
||||
joint_out_rhand_align = rigid_align(joint_out_rhand, joint_gt_rhand)
|
||||
eval_result['pa_mpjpe_l_hand'].append(np.sqrt(
|
||||
np.sum((joint_out_lhand_align - joint_gt_lhand) ** 2, 1)).mean() * 1000)
|
||||
eval_result['pa_mpjpe_r_hand'].append(np.sqrt(
|
||||
np.sum((joint_out_rhand_align - joint_gt_rhand) ** 2, 1)).mean() * 1000)
|
||||
eval_result['pa_mpjpe_hand'].append((np.sqrt(
|
||||
np.sum((joint_out_lhand_align - joint_gt_lhand) ** 2, 1)).mean() * 1000 + np.sqrt(
|
||||
np.sum((joint_out_rhand_align - joint_gt_rhand) ** 2, 1)).mean() * 1000) / 2.)
|
||||
|
||||
vis = cfg.vis
|
||||
if vis:
|
||||
# save_folder = cfg.vis_dir
|
||||
# kpt_save_folder = os.path.join(save_folder, 'KPT')
|
||||
# os.makedirs(kpt_save_folder, exist_ok=True)
|
||||
# mesh_save_folder = os.path.join(save_folder, 'mesh_origin')
|
||||
# os.makedirs(mesh_save_folder, exist_ok=True)
|
||||
# # from utils.vis import vis_keypoints, render_mesh, save_obj
|
||||
# img = (out['img'].transpose(1, 2, 0)[:, :, ::-1] * 255).copy()
|
||||
# joint_img = out['joint_img'].copy()
|
||||
# joint_img[:, 0] = joint_img[:, 0] / cfg.output_hm_shape[2] * cfg.input_img_shape[1]
|
||||
# joint_img[:, 1] = joint_img[:, 1] / cfg.output_hm_shape[1] * cfg.input_img_shape[0]
|
||||
# for j in range(len(joint_img)):
|
||||
# cv2.circle(img, (int(joint_img[j][0]), int(joint_img[j][1])), 3, (0, 0, 255), -1)
|
||||
# lhand_bbox = out['lhand_bbox'].reshape(2, 2).copy()
|
||||
# cv2.rectangle(img, (int(lhand_bbox[0][0]), int(lhand_bbox[0][1])),
|
||||
# (int(lhand_bbox[1][0]), int(lhand_bbox[1][1])), (255, 0, 0), 3)
|
||||
# rhand_bbox = out['rhand_bbox'].reshape(2, 2).copy()
|
||||
# cv2.rectangle(img, (int(rhand_bbox[0][0]), int(rhand_bbox[0][1])),
|
||||
# (int(rhand_bbox[1][0]), int(rhand_bbox[1][1])), (255, 0, 0), 3)
|
||||
# face_bbox = out['face_bbox'].reshape(2, 2).copy()
|
||||
# cv2.rectangle(img, (int(face_bbox[0][0]), int(face_bbox[0][1])),
|
||||
# (int(face_bbox[1][0]), int(face_bbox[1][1])), (255, 0, 0), 3)
|
||||
# cv2.imwrite(os.path.join(kpt_save_folder, str(cur_sample_idx + n) + '.jpg'), img)
|
||||
|
||||
# vis_img = img.copy()
|
||||
# focal = [cfg.focal[0] / cfg.input_body_shape[1] * cfg.input_img_shape[1],
|
||||
# cfg.focal[1] / cfg.input_body_shape[0] * cfg.input_img_shape[0]]
|
||||
# princpt = [cfg.princpt[0] / cfg.input_body_shape[1] * cfg.input_img_shape[1],
|
||||
# cfg.princpt[1] / cfg.input_body_shape[0] * cfg.input_img_shape[0]]
|
||||
# rendered_img = render_mesh(vis_img, mesh_out, smpl_x.face, {'focal': focal, 'princpt': princpt})
|
||||
# vis_img = img.copy()
|
||||
# # rendered_img_gt = render_mesh(vis_img, mesh_gt_align, smpl_x.face, {'focal': focal, 'princpt': princpt})
|
||||
# cv2.imwrite(os.path.join(mesh_save_folder, f'{ann_id}_render.jpg'), rendered_img)
|
||||
# # cv2.imwrite(os.path.join(mesh_save_folder, f'{ann_id}_render_gt.jpg'), rendered_img_gt)
|
||||
# cv2.imwrite(os.path.join(mesh_save_folder, f'{ann_id}.jpg'), vis_img)
|
||||
# np.save(os.path.join(mesh_save_folder, f'{ann_id}.npy'), mesh_out)
|
||||
|
||||
# save smplx param
|
||||
smplx_pred = {}
|
||||
smplx_pred['global_orient'] = out['smplx_root_pose'].reshape(-1,3)
|
||||
smplx_pred['body_pose'] = out['smplx_body_pose'].reshape(-1,3)
|
||||
smplx_pred['left_hand_pose'] = out['smplx_lhand_pose'].reshape(-1,3)
|
||||
smplx_pred['right_hand_pose'] = out['smplx_rhand_pose'].reshape(-1,3)
|
||||
smplx_pred['jaw_pose'] = out['smplx_jaw_pose'].reshape(-1,3)
|
||||
smplx_pred['leye_pose'] = np.zeros((1, 3))
|
||||
smplx_pred['reye_pose'] = np.zeros((1, 3))
|
||||
smplx_pred['betas'] = out['smplx_shape'].reshape(-1,10)
|
||||
smplx_pred['expression'] = out['smplx_expr'].reshape(-1,10)
|
||||
smplx_pred['transl'] = out['cam_trans'].reshape(-1,3)
|
||||
|
||||
# import pdb; pdb.set_trace()
|
||||
np.savez(os.path.join(cfg.vis_dir, f'{self.save_idx}.npz'), **smplx_pred)
|
||||
|
||||
# save img path and error
|
||||
img_path = out['img_path']
|
||||
rel_img_path = img_path.split('..')[-1]
|
||||
new_line = [self.save_idx, rel_img_path, mpvpe_all, pa_mpvpe_all]
|
||||
# Append the new line to the CSV file
|
||||
writer.writerow(new_line)
|
||||
self.save_idx += 1
|
||||
|
||||
# save_obj(out['smplx_mesh_cam'], smpl_x.face, str(cur_sample_idx + n) + '.obj')
|
||||
|
||||
if getattr(cfg, 'vis', False):
|
||||
file.close()
|
||||
|
||||
|
||||
return eval_result
|
||||
|
||||
def print_eval_result(self, eval_result):
|
||||
print('======EHF======')
|
||||
print('PA MPVPE (All): %.2f mm' % np.mean(eval_result['pa_mpvpe_all']))
|
||||
print('PA MPVPE (L-Hands): %.2f mm' % np.mean(eval_result['pa_mpvpe_l_hand']))
|
||||
print('PA MPVPE (R-Hands): %.2f mm' % np.mean(eval_result['pa_mpvpe_r_hand']))
|
||||
print('PA MPVPE (Hands): %.2f mm' % np.mean(eval_result['pa_mpvpe_hand']))
|
||||
print('PA MPVPE (Face): %.2f mm' % np.mean(eval_result['pa_mpvpe_face']))
|
||||
print()
|
||||
|
||||
print('MPVPE (All): %.2f mm' % np.mean(eval_result['mpvpe_all']))
|
||||
print('MPVPE (L-Hands): %.2f mm' % np.mean(eval_result['mpvpe_l_hand']))
|
||||
print('MPVPE (R-Hands): %.2f mm' % np.mean(eval_result['mpvpe_r_hand']))
|
||||
print('MPVPE (Hands): %.2f mm' % np.mean(eval_result['mpvpe_hand']))
|
||||
print('MPVPE (Face): %.2f mm' % np.mean(eval_result['mpvpe_face']))
|
||||
print()
|
||||
|
||||
print('PA MPJPE (Body): %.2f mm' % np.mean(eval_result['pa_mpjpe_body']))
|
||||
print('PA MPJPE (L-Hands): %.2f mm' % np.mean(eval_result['pa_mpjpe_l_hand']))
|
||||
print('PA MPJPE (R-Hands): %.2f mm' % np.mean(eval_result['pa_mpjpe_r_hand']))
|
||||
print('PA MPJPE (Hands): %.2f mm' % np.mean(eval_result['pa_mpjpe_hand']))
|
||||
print()
|
||||
|
||||
print(f"{np.mean(eval_result['pa_mpvpe_all'])},{np.mean(eval_result['pa_mpvpe_l_hand'])},{np.mean(eval_result['pa_mpvpe_r_hand'])},{np.mean(eval_result['pa_mpvpe_hand'])},{np.mean(eval_result['pa_mpvpe_face'])},"
|
||||
f"{np.mean(eval_result['mpvpe_all'])},{np.mean(eval_result['mpvpe_l_hand'])},{np.mean(eval_result['mpvpe_r_hand'])},{np.mean(eval_result['mpvpe_hand'])},{np.mean(eval_result['mpvpe_face'])}")
|
||||
print()
|
||||
|
||||
|
||||
f = open(os.path.join(cfg.result_dir, 'result.txt'), 'w')
|
||||
f.write(f'EHF dataset: \n')
|
||||
f.write('PA MPVPE (All): %.2f mm\n' % np.mean(eval_result['pa_mpvpe_all']))
|
||||
f.write('PA MPVPE (L-Hands): %.2f mm' % np.mean(eval_result['pa_mpvpe_l_hand']))
|
||||
f.write('PA MPVPE (R-Hands): %.2f mm' % np.mean(eval_result['pa_mpvpe_r_hand']))
|
||||
f.write('PA MPVPE (Hands): %.2f mm\n' % np.mean(eval_result['pa_mpvpe_hand']))
|
||||
f.write('PA MPVPE (Face): %.2f mm\n' % np.mean(eval_result['pa_mpvpe_face']))
|
||||
f.write('MPVPE (All): %.2f mm\n' % np.mean(eval_result['mpvpe_all']))
|
||||
f.write('MPVPE (L-Hands): %.2f mm' % np.mean(eval_result['mpvpe_l_hand']))
|
||||
f.write('MPVPE (R-Hands): %.2f mm' % np.mean(eval_result['mpvpe_r_hand']))
|
||||
f.write('MPVPE (Hands): %.2f mm' % np.mean(eval_result['mpvpe_hand']))
|
||||
f.write('MPVPE (Face): %.2f mm\n' % np.mean(eval_result['mpvpe_face']))
|
||||
f.write('PA MPJPE (Body): %.2f mm\n' % np.mean(eval_result['pa_mpjpe_body']))
|
||||
f.write('PA MPJPE (L-Hands): %.2f mm' % np.mean(eval_result['pa_mpjpe_l_hand']))
|
||||
f.write('PA MPJPE (R-Hands): %.2f mm' % np.mean(eval_result['pa_mpjpe_r_hand']))
|
||||
f.write('PA MPJPE (Hands): %.2f mm\n' % np.mean(eval_result['pa_mpjpe_hand']))
|
||||
|
||||
f.write(f"{np.mean(eval_result['pa_mpvpe_all'])},{np.mean(eval_result['pa_mpvpe_l_hand'])},{np.mean(eval_result['pa_mpvpe_r_hand'])},{np.mean(eval_result['pa_mpvpe_hand'])},{np.mean(eval_result['pa_mpvpe_face'])},"
|
||||
f"{np.mean(eval_result['mpvpe_all'])},{np.mean(eval_result['mpvpe_l_hand'])},{np.mean(eval_result['mpvpe_r_hand'])},{np.mean(eval_result['mpvpe_hand'])},{np.mean(eval_result['mpvpe_face'])}")
|
||||
|
||||
|
||||
|
||||
# for i in range(len(eval_result['pa_mpvpe_all'])):
|
||||
# f.write(f'{i+1:02d}.jpg\n')
|
||||
# f.write('PA MPVPE (All): %.2f mm\n' % eval_result['pa_mpvpe_all'][i])
|
||||
# f.write('PA MPVPE (Hands): %.2f mm\n' % eval_result['pa_mpvpe_hand'][i])
|
||||
# f.write('PA MPVPE (Face): %.2f mm\n' % eval_result['pa_mpvpe_face'][i])
|
||||
# f.write('MPVPE (All): %.2f mm\n' % eval_result['mpvpe_all'][i])
|
||||
# f.write('MPVPE (Hands): %.2f mm\n' % eval_result['mpvpe_hand'][i])
|
||||
# f.write('MPVPE (Face): %.2f mm\n' % eval_result['mpvpe_face'][i])
|
||||
# f.write('PA MPJPE (Body): %.2f mm\n' % eval_result['pa_mpjpe_body'][i])
|
||||
# f.write('PA MPJPE (Hands): %.2f mm\n' % eval_result['pa_mpjpe_hand'][i])
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class EgoBody_Egocentric(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(EgoBody_Egocentric, self).__init__(transform, data_split)
|
||||
if getattr(cfg, 'eval_on_train', False):
|
||||
self.data_split = 'eval_train'
|
||||
print("Evaluate on train set.")
|
||||
|
||||
if 'train' in self.data_split:
|
||||
filename = getattr(cfg, 'filename', 'egobody_egocentric_train_230425_065_fix_betas.npz')
|
||||
else:
|
||||
filename = getattr(cfg, 'filename', 'egobody_egocentric_test_230425_043_fix_betas.npz')
|
||||
|
||||
self.use_betas_neutral = getattr(cfg, 'egobody_fix_betas', False)
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'EgoBody')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', filename)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.img_shape = (1080, 1920) # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
# check image shape
|
||||
img_path = osp.join(self.img_dir, np.load(self.annot_path)['image_path'][0])
|
||||
img_shape = cv2.imread(img_path).shape[:2]
|
||||
assert self.img_shape == img_shape, 'image shape is incorrect: {} vs {}'.format(self.img_shape, img_shape)
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,50 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class EgoBody_Kinect(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(EgoBody_Kinect, self).__init__(transform, data_split)
|
||||
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', 'egobody_kinect_train_230503_065_fix_betas.npz')
|
||||
else:
|
||||
filename = getattr(cfg, 'filename', 'egobody_kinect_test_230503_043_fix_betas.npz')
|
||||
|
||||
self.use_betas_neutral = getattr(cfg, 'egobody_fix_betas', False)
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'EgoBody')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', filename)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.img_shape = (1080, 1920) # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
# check image shape
|
||||
img_path = osp.join(self.img_dir, np.load(self.annot_path)['image_path'][0])
|
||||
img_shape = cv2.imread(img_path).shape[:2]
|
||||
assert self.img_shape == img_shape, 'image shape is incorrect: {} vs {}'.format(self.img_shape, img_shape)
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_{self.data_split}_sample_interval', 1))
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,58 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class FIT3D(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(FIT3D, self).__init__(transform, data_split)
|
||||
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', 'FIT3D_train_230511_1504.npz')
|
||||
self.img_shape = (900, 900) # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
|
||||
self.datalist = []
|
||||
for pre_prc_file in ['FIT3D_train_230511_1504_0.npz','FIT3D_train_230511_1504_1.npz',
|
||||
'FIT3D_train_230511_1504_2.npz', 'FIT3D_train_230511_1504_3.npz',
|
||||
'FIT3D_train_230511_1504_4.npz', 'FIT3D_train_230511_1504_5.npz']:
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', pre_prc_file)
|
||||
else:
|
||||
raise ValueError('FIT3D test set is not support')
|
||||
|
||||
self.img_dir = cfg.data_dir # FIT3D included
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
|
||||
# check image shape
|
||||
img_path = osp.join(self.img_dir, np.load(self.annot_path)['image_path'][0])
|
||||
img_shape = cv2.imread(img_path).shape[:2]
|
||||
assert self.img_shape == img_shape, 'image shape is incorrect: {} vs {}'.format(self.img_shape, img_shape)
|
||||
|
||||
# load data
|
||||
datalist_slice = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
self.datalist.extend(datalist_slice)
|
||||
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class GTA_Human2(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(GTA_Human2, self).__init__(transform, data_split)
|
||||
|
||||
filename = 'gta_human2multiple_230406_04000_0.npz'
|
||||
self.img_dir = osp.join(cfg.data_dir, 'GTA_Human2')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', filename)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.img_shape = (1080, 1920) # (h, w)
|
||||
self.cam_param = {
|
||||
'focal': (1158.0337, 1158.0337), # (fx, fy)
|
||||
'princpt': (960, 540) # (cx, cy)
|
||||
}
|
||||
|
||||
# check image shape
|
||||
img_path = osp.join(self.img_dir, np.load(self.annot_path)['image_path'][0])
|
||||
img_shape = cv2.imread(img_path).shape[:2]
|
||||
assert self.img_shape == img_shape, 'image shape is incorrect: {} vs {}'.format(self.img_shape, img_shape)
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,286 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
import random
|
||||
from humandata import Cache
|
||||
|
||||
class Human36M(torch.utils.data.Dataset):
|
||||
def __init__(self, transform, data_split):
|
||||
self.transform = transform
|
||||
self.data_split = data_split
|
||||
self.img_dir = osp.join(cfg.data_dir, 'Human36M', 'images')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'Human36M', 'annotations')
|
||||
self.action_name = ['Directions', 'Discussion', 'Eating', 'Greeting', 'Phoning', 'Posing', 'Purchases', 'Sitting', 'SittingDown', 'Smoking', 'Photo', 'Waiting', 'Walking', 'WalkDog', 'WalkTogether']
|
||||
# H36M joint set
|
||||
self.joint_set = {'joint_num': 17,
|
||||
'joints_name': ('Pelvis', 'R_Hip', 'R_Knee', 'R_Ankle', 'L_Hip', 'L_Knee', 'L_Ankle', 'Torso', 'Neck', 'Head', 'Head_top', 'L_Shoulder', 'L_Elbow', 'L_Wrist', 'R_Shoulder', 'R_Elbow', 'R_Wrist'),
|
||||
'flip_pairs': ( (1, 4), (2, 5), (3, 6), (14, 11), (15, 12), (16, 13) ),
|
||||
'eval_joint': (1, 2, 3, 4, 5, 6, 8, 10, 11, 12, 13, 14, 15, 16),
|
||||
'regressor': np.load(osp.join(cfg.data_dir, 'Human36M', 'J_regressor_h36m_smplx.npy'))
|
||||
}
|
||||
self.joint_set['root_joint_idx'] = self.joint_set['joints_name'].index('Pelvis')
|
||||
|
||||
# self.datalist = self.load_data()
|
||||
|
||||
# load data or cache
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', f'Human36M_{data_split}.npz')
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
datalist = Cache(self.annot_path_cache)
|
||||
assert datalist.data_strategy == getattr(cfg, 'data_strategy', None), \
|
||||
f'Cache data strategy {datalist.data_strategy} does not match current data strategy ' \
|
||||
f'{getattr(cfg, "data_strategy", None)}'
|
||||
self.datalist = datalist
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data()
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Caching datalist to {self.annot_path_cache}...')
|
||||
Cache.save(
|
||||
self.annot_path_cache,
|
||||
self.datalist,
|
||||
data_strategy=getattr(cfg, 'data_strategy', None)
|
||||
)
|
||||
|
||||
def get_subsampling_ratio(self):
|
||||
if self.data_split == 'train':
|
||||
return 5
|
||||
elif self.data_split == 'test':
|
||||
return 64
|
||||
else:
|
||||
assert 0, print('Unknown subset')
|
||||
|
||||
def get_subject(self):
|
||||
if self.data_split == 'train':
|
||||
subject = [1,5,6,7,8]
|
||||
elif self.data_split == 'test':
|
||||
subject = [9,11]
|
||||
else:
|
||||
assert 0, print("Unknown subset")
|
||||
|
||||
return subject
|
||||
|
||||
def load_data(self):
|
||||
subject_list = self.get_subject()
|
||||
sampling_ratio = self.get_subsampling_ratio()
|
||||
|
||||
# aggregate annotations from each subject
|
||||
db = COCO()
|
||||
cameras = {}
|
||||
joints = {}
|
||||
smplx_params = {}
|
||||
for subject in subject_list:
|
||||
# data load
|
||||
with open(osp.join(self.annot_path, 'Human36M_subject' + str(subject) + '_data.json'),'r') as f:
|
||||
annot = json.load(f)
|
||||
if len(db.dataset) == 0:
|
||||
for k,v in annot.items():
|
||||
db.dataset[k] = v
|
||||
else:
|
||||
for k,v in annot.items():
|
||||
db.dataset[k] += v
|
||||
# camera load
|
||||
with open(osp.join(self.annot_path, 'Human36M_subject' + str(subject) + '_camera.json'),'r') as f:
|
||||
cameras[str(subject)] = json.load(f)
|
||||
# joint coordinate load
|
||||
with open(osp.join(self.annot_path, 'Human36M_subject' + str(subject) + '_joint_3d.json'),'r') as f:
|
||||
joints[str(subject)] = json.load(f)
|
||||
# smplx parameter load
|
||||
with open(osp.join(self.annot_path, 'Human36M_subject' + str(subject) + '_SMPLX_NeuralAnnot.json'),'r') as f:
|
||||
smplx_params[str(subject)] = json.load(f)
|
||||
|
||||
db.createIndex()
|
||||
|
||||
datalist = []
|
||||
i = 0
|
||||
for aid in db.anns.keys():
|
||||
|
||||
i += 1
|
||||
if self.data_split == 'train' and i % getattr(cfg, 'Human36M_train_sample_interval', 1) != 0:
|
||||
continue
|
||||
|
||||
ann = db.anns[aid]
|
||||
image_id = ann['image_id']
|
||||
img = db.loadImgs(image_id)[0]
|
||||
img_path = osp.join(self.img_dir, img['file_name'])
|
||||
img_shape = (img['height'], img['width'])
|
||||
|
||||
# check subject and frame_idx
|
||||
frame_idx = img['frame_idx'];
|
||||
if frame_idx % sampling_ratio != 0:
|
||||
continue
|
||||
|
||||
# smplx parameter
|
||||
subject = img['subject']; action_idx = img['action_idx']; subaction_idx = img['subaction_idx']; frame_idx = img['frame_idx']; cam_idx = img['cam_idx'];
|
||||
smplx_param = smplx_params[str(subject)][str(action_idx)][str(subaction_idx)][str(frame_idx)]
|
||||
|
||||
# camera parameter
|
||||
cam_param = cameras[str(subject)][str(cam_idx)]
|
||||
R,t,f,c = np.array(cam_param['R'], dtype=np.float32), np.array(cam_param['t'], dtype=np.float32), np.array(cam_param['f'], dtype=np.float32), np.array(cam_param['c'], dtype=np.float32)
|
||||
cam_param = {'R': R, 't': t, 'focal': f, 'princpt': c}
|
||||
|
||||
# only use frontal camera following previous works (HMR and SPIN)
|
||||
if self.data_split == 'test' and str(cam_idx) != '4':
|
||||
continue
|
||||
|
||||
# project world coordinate to cam, image coordinate space
|
||||
joint_world = np.array(joints[str(subject)][str(action_idx)][str(subaction_idx)][str(frame_idx)], dtype=np.float32)
|
||||
joint_cam = world2cam(joint_world, R, t)
|
||||
joint_img = cam2pixel(joint_cam, f, c)[:,:2]
|
||||
joint_valid = np.ones((self.joint_set['joint_num'],1))
|
||||
|
||||
bbox = process_bbox(np.array(ann['bbox']), img['width'], img['height'], ratio=getattr(cfg, 'bbox_ratio', 1.25))
|
||||
if bbox is None: continue
|
||||
|
||||
datalist.append({
|
||||
'img_path': img_path,
|
||||
'img_shape': img_shape,
|
||||
'bbox': bbox,
|
||||
'joint_img': joint_img,
|
||||
'joint_cam': joint_cam,
|
||||
'joint_valid': joint_valid,
|
||||
'smplx_param': smplx_param,
|
||||
'cam_param': cam_param})
|
||||
|
||||
if self.data_split == 'train':
|
||||
print('[Human36M train] original size:', len(db.anns.keys()),
|
||||
'. Sample interval:', getattr(cfg, 'Human36M_train_sample_interval', 1),
|
||||
'. Sampled size:', len(datalist))
|
||||
|
||||
if getattr(cfg, 'data_strategy', None) == 'balance' and self.data_split == 'train':
|
||||
print(f'[Human36M] Using [balance] strategy with datalist shuffled...')
|
||||
random.shuffle(datalist)
|
||||
|
||||
return datalist
|
||||
|
||||
def __len__(self):
|
||||
return len(self.datalist)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
data = copy.deepcopy(self.datalist[idx])
|
||||
img_path, img_shape, bbox, cam_param = data['img_path'], data['img_shape'], data['bbox'], data['cam_param']
|
||||
|
||||
# img
|
||||
img = load_img(img_path)
|
||||
img, img2bb_trans, bb2img_trans, rot, do_flip = augmentation(img, bbox, self.data_split)
|
||||
img = self.transform(img.astype(np.float32))/255.
|
||||
|
||||
if self.data_split == 'train':
|
||||
# h36m gt
|
||||
joint_cam = data['joint_cam']
|
||||
joint_cam = (joint_cam - joint_cam[self.joint_set['root_joint_idx'],None,:]) / 1000 # root-relative. milimeter to meter.
|
||||
joint_img = data['joint_img']
|
||||
joint_img = np.concatenate((joint_img[:,:2], joint_cam[:,2:]),1) # x, y, depth
|
||||
joint_img[:,2] = (joint_img[:,2] / (cfg.body_3d_size / 2) + 1)/2. * cfg.output_hm_shape[0] # discretize depth
|
||||
joint_img, joint_cam, joint_cam_ra, joint_valid, joint_trunc = process_db_coord(joint_img, joint_cam, data['joint_valid'], do_flip, img_shape, self.joint_set['flip_pairs'], img2bb_trans, rot, self.joint_set['joints_name'], smpl_x.joints_name)
|
||||
|
||||
# smplx coordinates and parameters
|
||||
smplx_param = data['smplx_param']
|
||||
cam_param['t'] /= 1000 # milimeter to meter
|
||||
smplx_joint_img, smplx_joint_cam, smplx_joint_trunc, smplx_pose, smplx_shape, smplx_expr, \
|
||||
smplx_pose_valid, smplx_joint_valid, smplx_expr_valid, smplx_mesh_cam_orig = \
|
||||
process_human_model_output(smplx_param, cam_param, do_flip, img_shape, img2bb_trans, rot, 'smplx')
|
||||
|
||||
"""
|
||||
# for debug
|
||||
_tmp = joint_img.copy()
|
||||
_tmp[:,0] = _tmp[:,0] / cfg.output_hm_shape[2] * cfg.input_img_shape[1]
|
||||
_tmp[:,1] = _tmp[:,1] / cfg.output_hm_shape[1] * cfg.input_img_shape[0]
|
||||
_img = img.numpy().transpose(1,2,0)[:,:,::-1] * 255
|
||||
_img = vis_keypoints(_img, _tmp)
|
||||
cv2.imwrite('h36m_' + str(idx) + '.jpg', _img)
|
||||
"""
|
||||
# reverse ra
|
||||
smplx_joint_cam_wo_ra = smplx_joint_cam.copy()
|
||||
smplx_joint_cam_wo_ra[smpl_x.joint_part['lhand'], :] = smplx_joint_cam_wo_ra[smpl_x.joint_part['lhand'], :] \
|
||||
+ smplx_joint_cam_wo_ra[smpl_x.lwrist_idx, None, :] # left hand root-relative
|
||||
smplx_joint_cam_wo_ra[smpl_x.joint_part['rhand'], :] = smplx_joint_cam_wo_ra[smpl_x.joint_part['rhand'], :] \
|
||||
+ smplx_joint_cam_wo_ra[smpl_x.rwrist_idx, None, :] # right hand root-relative
|
||||
smplx_joint_cam_wo_ra[smpl_x.joint_part['face'], :] = smplx_joint_cam_wo_ra[smpl_x.joint_part['face'], :] \
|
||||
+ smplx_joint_cam_wo_ra[smpl_x.neck_idx, None,: ] # face root-relative
|
||||
|
||||
|
||||
# SMPLX pose parameter validity
|
||||
for name in ('L_Ankle', 'R_Ankle', 'L_Wrist', 'R_Wrist'):
|
||||
smplx_pose_valid[smpl_x.orig_joints_name.index(name)] = 0
|
||||
smplx_pose_valid = np.tile(smplx_pose_valid[:,None], (1,3)).reshape(-1)
|
||||
# SMPLX joint coordinate validity
|
||||
for name in ('L_Big_toe', 'L_Small_toe', 'L_Heel', 'R_Big_toe', 'R_Small_toe', 'R_Heel'):
|
||||
smplx_joint_valid[smpl_x.joints_name.index(name)] = 0
|
||||
smplx_joint_valid = smplx_joint_valid[:,None]
|
||||
smplx_joint_trunc = smplx_joint_valid * smplx_joint_trunc
|
||||
smplx_shape_valid = True
|
||||
|
||||
# dummy hand/face bbox
|
||||
dummy_center = np.zeros((2), dtype=np.float32)
|
||||
dummy_size = np.zeros((2), dtype=np.float32)
|
||||
|
||||
inputs = {'img': img}
|
||||
targets = {'joint_img': smplx_joint_img, 'smplx_joint_img': smplx_joint_img,
|
||||
'joint_cam': smplx_joint_cam_wo_ra, 'smplx_joint_cam': smplx_joint_cam,
|
||||
'smplx_pose': smplx_pose, 'smplx_shape': smplx_shape, 'smplx_expr': smplx_expr,
|
||||
'lhand_bbox_center': dummy_center, 'lhand_bbox_size': dummy_size,
|
||||
'rhand_bbox_center': dummy_center, 'rhand_bbox_size': dummy_size,
|
||||
'face_bbox_center': dummy_center, 'face_bbox_size': dummy_size}
|
||||
meta_info = {'joint_valid': smplx_joint_valid, 'joint_trunc': smplx_joint_trunc,
|
||||
'smplx_joint_valid': smplx_joint_valid, 'smplx_joint_trunc': smplx_joint_trunc,
|
||||
'smplx_pose_valid': smplx_pose_valid, 'smplx_shape_valid': float(smplx_shape_valid),
|
||||
'smplx_expr_valid': float(smplx_expr_valid), 'is_3D': float(True),
|
||||
'lhand_bbox_valid': float(False), 'rhand_bbox_valid': float(False),
|
||||
'face_bbox_valid': float(False)}
|
||||
return inputs, targets, meta_info
|
||||
else:
|
||||
inputs = {'img': img}
|
||||
targets = {}
|
||||
meta_info = {}
|
||||
return inputs, targets, meta_info
|
||||
|
||||
def evaluate(self, outs, cur_sample_idx):
|
||||
|
||||
annots = self.datalist
|
||||
sample_num = len(outs)
|
||||
eval_result = {'mpjpe': [], 'pa_mpjpe': []}
|
||||
for n in range(sample_num):
|
||||
annot = annots[cur_sample_idx + n]
|
||||
out = outs[n]
|
||||
|
||||
# h36m joint from gt mesh
|
||||
joint_gt = annot['joint_cam']
|
||||
joint_gt = joint_gt - joint_gt[self.joint_set['root_joint_idx'],None] # root-relative
|
||||
joint_gt = joint_gt[self.joint_set['eval_joint'],:]
|
||||
|
||||
# h36m joint from param mesh
|
||||
mesh_out = out['smpl_mesh_cam'] * 1000 # meter to milimeter
|
||||
joint_out = np.dot(self.joint_set['regressor'], mesh_out) # meter to milimeter
|
||||
joint_out = joint_out - joint_out[self.joint_set['root_joint_idx'],None] # root-relative
|
||||
joint_out = joint_out[self.joint_set['eval_joint'],:]
|
||||
joint_out_aligned = rigid_align(joint_out, joint_gt)
|
||||
eval_result['mpjpe'].append(np.sqrt(np.sum((joint_out - joint_gt)**2,1)).mean())
|
||||
eval_result['pa_mpjpe'].append(np.sqrt(np.sum((joint_out_aligned - joint_gt)**2,1)).mean())
|
||||
|
||||
vis = False
|
||||
if vis:
|
||||
from utils.vis import vis_keypoints, vis_mesh, save_obj
|
||||
filename = annot['img_path'].split('/')[-1][:-4]
|
||||
|
||||
img = load_img(annot['img_path'])[:,:,::-1]
|
||||
img = vis_mesh(img, mesh_out_img, 0.5)
|
||||
cv2.imwrite(filename + '.jpg', img)
|
||||
save_obj(mesh_out, smpl_x.face, filename + '.obj')
|
||||
|
||||
return eval_result
|
||||
|
||||
def print_eval_result(self, eval_result):
|
||||
print('MPJPE: %.2f mm' % np.mean(eval_result['mpjpe']))
|
||||
print('PA MPJPE: %.2f mm' % np.mean(eval_result['pa_mpjpe']))
|
||||
@@ -0,0 +1,57 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class HumanSC3D(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(HumanSC3D, self).__init__(transform, data_split)
|
||||
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', 'HumanSC3D_train_230511_2752.npz')
|
||||
self.img_shape = (900, 900) # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
|
||||
self.datalist = []
|
||||
for pre_prc_file in ['HumanSC3D_train_230511_2752_0.npz','HumanSC3D_train_230511_2752_1.npz',
|
||||
'HumanSC3D_train_230511_2752_2.npz']:
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', pre_prc_file)
|
||||
else:
|
||||
raise ValueError('HumanSC3D test set is not support')
|
||||
|
||||
self.img_dir = cfg.data_dir # HumanSC3D included
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
|
||||
# check image shape
|
||||
img_path = osp.join(self.img_dir, np.load(self.annot_path)['image_path'][0])
|
||||
img_shape = cv2.imread(img_path).shape[:2]
|
||||
assert self.img_shape == img_shape, 'image shape is incorrect: {} vs {}'.format(self.img_shape, img_shape)
|
||||
|
||||
# load data
|
||||
datalist_slice = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
self.datalist.extend(datalist_slice)
|
||||
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,51 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class InstaVariety(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(InstaVariety, self).__init__(transform, data_split)
|
||||
|
||||
self.datalist = []
|
||||
|
||||
pre_prc_file = 'insta_variety_neural_annot_train.npz'
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', pre_prc_file)
|
||||
else:
|
||||
raise ValueError('InstaVariety test set is not support')
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'InstaVariety')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', filename)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.img_shape = (224,224) # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
# check image shape
|
||||
img_path = osp.join(self.img_dir, np.load(self.annot_path)['image_path'][0])
|
||||
img_shape = cv2.imread(img_path).shape[:2]
|
||||
assert self.img_shape == img_shape, 'image shape is incorrect: {} vs {}'.format(self.img_shape, img_shape)
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,43 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class LSPET(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(LSPET, self).__init__(transform, data_split)
|
||||
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', 'eft_lspet.npz')
|
||||
else:
|
||||
raise ValueError('LSPET test set is not support')
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'LSPET')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', filename)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.img_shape = None # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,195 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
from config import cfg
|
||||
import copy
|
||||
import json
|
||||
import cv2
|
||||
import torch
|
||||
from pycocotools.coco import COCO
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, \
|
||||
process_human_model_output
|
||||
import random
|
||||
from humandata import Cache
|
||||
# from utils.vis import vis_keypoints, vis_mesh, save_obj
|
||||
|
||||
class MPII(torch.utils.data.Dataset):
|
||||
def __init__(self, transform, data_split):
|
||||
self.transform = transform
|
||||
self.data_split = data_split
|
||||
self.img_path = osp.join(cfg.data_dir, 'MPII', 'data')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'MPII', 'data', 'annotations')
|
||||
|
||||
# mpii skeleton
|
||||
self.joint_set = {
|
||||
'joint_num': 16,
|
||||
'joints_name': ('R_Ankle', 'R_Knee', 'R_Hip', 'L_Hip', 'L_Knee', 'L_Ankle', 'Pelvis', 'Thorax', 'Neck', 'Head_top', 'R_Wrist', 'R_Elbow', 'R_Shoulder', 'L_Shoulder', 'L_Elbow', 'L_Wrist'),
|
||||
'flip_pairs': ( (0,5), (1,4), (2,3), (10,15), (11,14), (12,13) ),
|
||||
}
|
||||
|
||||
# self.datalist = self.load_data()
|
||||
|
||||
# load data or cache
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', f'MPII_{data_split}.npz')
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
datalist = Cache(self.annot_path_cache)
|
||||
assert datalist.data_strategy == getattr(cfg, 'data_strategy', None), \
|
||||
f'Cache data strategy {datalist.data_strategy} does not match current data strategy ' \
|
||||
f'{getattr(cfg, "data_strategy", None)}'
|
||||
self.datalist = datalist
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data()
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Caching datalist to {self.annot_path_cache}...')
|
||||
Cache.save(
|
||||
self.annot_path_cache,
|
||||
self.datalist,
|
||||
data_strategy=getattr(cfg, 'data_strategy', None)
|
||||
)
|
||||
|
||||
|
||||
|
||||
def load_data(self):
|
||||
db = COCO(osp.join(self.annot_path, 'train.json'))
|
||||
with open(osp.join(self.annot_path, 'MPII_train_SMPLX_NeuralAnnot.json')) as f:
|
||||
smplx_params = json.load(f)
|
||||
|
||||
datalist = []
|
||||
i = 0
|
||||
for aid in db.anns.keys():
|
||||
|
||||
i += 1
|
||||
if self.data_split == 'train' and i % getattr(cfg, 'MPII_train_sample_interval', 1) != 0:
|
||||
continue
|
||||
|
||||
ann = db.anns[aid]
|
||||
img = db.loadImgs(ann['image_id'])[0]
|
||||
imgname = img['file_name']
|
||||
img_path = osp.join(self.img_path, imgname)
|
||||
|
||||
# bbox
|
||||
bbox = process_bbox(ann['bbox'], img['width'], img['height'], ratio=getattr(cfg, 'bbox_ratio', 1.25))
|
||||
if bbox is None: continue
|
||||
|
||||
# joint coordinates
|
||||
joint_img = np.array(ann['keypoints'], dtype=np.float32).reshape(-1,3)
|
||||
joint_valid = joint_img[:,2:].copy()
|
||||
joint_img[:,2] = 0
|
||||
|
||||
# smplx parameter
|
||||
if str(aid) in smplx_params:
|
||||
smplx_param = smplx_params[str(aid)]
|
||||
else:
|
||||
smplx_param = None
|
||||
|
||||
datalist.append({
|
||||
'img_path': img_path,
|
||||
'img_shape': (img['height'], img['width']),
|
||||
'bbox': bbox,
|
||||
'joint_img': joint_img,
|
||||
'joint_valid': joint_valid,
|
||||
'smplx_param': smplx_param
|
||||
})
|
||||
|
||||
if self.data_split == 'train':
|
||||
print('[MPII train] original size:', len(db.anns.keys()),
|
||||
'. Sample interval:', getattr(cfg, 'MPII_train_sample_interval', 1),
|
||||
'. Sampled size:', len(datalist))
|
||||
|
||||
if getattr(cfg, 'data_strategy', None) == 'balance' and self.data_split == 'train':
|
||||
print(f'[MPII] Using [balance] strategy with datalist shuffled...')
|
||||
random.shuffle(datalist)
|
||||
|
||||
return datalist
|
||||
|
||||
def __len__(self):
|
||||
return len(self.datalist)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
data = copy.deepcopy(self.datalist[idx])
|
||||
img_path, img_shape, bbox = data['img_path'], data['img_shape'], data['bbox']
|
||||
|
||||
# image load and affine transform
|
||||
img = load_img(img_path)
|
||||
img, img2bb_trans, bb2img_trans, rot, do_flip = augmentation(img, bbox, self.data_split)
|
||||
img = self.transform(img.astype(np.float32))/255.
|
||||
|
||||
# mpii gt
|
||||
dummy_coord = np.zeros((self.joint_set['joint_num'],3), dtype=np.float32)
|
||||
joint_img = data['joint_img']
|
||||
joint_img = np.concatenate((joint_img[:,:2], np.zeros_like(joint_img[:,:1])),1) # x, y, dummy depth
|
||||
joint_img, joint_cam, joint_cam_ra, joint_valid, joint_trunc = process_db_coord(joint_img, dummy_coord, data['joint_valid'], do_flip, img_shape, self.joint_set['flip_pairs'], img2bb_trans, rot, self.joint_set['joints_name'], smpl_x.joints_name)
|
||||
|
||||
# smplx coordinates and parameters
|
||||
smplx_param = data['smplx_param']
|
||||
if smplx_param is not None:
|
||||
smplx_joint_img, smplx_joint_cam, smplx_joint_trunc, smplx_pose, smplx_shape, smplx_expr, smplx_pose_valid, smplx_joint_valid, smplx_expr_valid, smplx_mesh_cam_orig = process_human_model_output(smplx_param['smplx_param'], smplx_param['cam_param'], do_flip, img_shape, img2bb_trans, rot, 'smplx')
|
||||
is_valid_fit = True
|
||||
|
||||
"""
|
||||
# for debug
|
||||
_tmp = joint_img.copy()
|
||||
_tmp[:,0] = _tmp[:,0] / cfg.output_hm_shape[2] * cfg.input_img_shape[1]
|
||||
_tmp[:,1] = _tmp[:,1] / cfg.output_hm_shape[1] * cfg.input_img_shape[0]
|
||||
_img = img.numpy().transpose(1,2,0)[:,:,::-1] * 255
|
||||
_img = vis_keypoints(_img.copy(), _tmp)
|
||||
cv2.imwrite('mpii_' + str(idx) + '.jpg', _img)
|
||||
"""
|
||||
|
||||
else:
|
||||
# dummy values
|
||||
smplx_joint_img = np.zeros((smpl_x.joint_num,3), dtype=np.float32)
|
||||
smplx_joint_cam = np.zeros((smpl_x.joint_num,3), dtype=np.float32)
|
||||
smplx_joint_trunc = np.zeros((smpl_x.joint_num,1), dtype=np.float32)
|
||||
smplx_joint_valid = np.zeros((smpl_x.joint_num), dtype=np.float32)
|
||||
smplx_pose = np.zeros((smpl_x.orig_joint_num*3), dtype=np.float32)
|
||||
smplx_shape = np.zeros((smpl_x.shape_param_dim), dtype=np.float32)
|
||||
smplx_expr = np.zeros((smpl_x.expr_code_dim), dtype=np.float32)
|
||||
smplx_pose_valid = np.zeros((smpl_x.orig_joint_num), dtype=np.float32)
|
||||
smplx_expr_valid = False
|
||||
is_valid_fit = False
|
||||
|
||||
# SMPLX pose parameter validity
|
||||
for name in ('L_Ankle', 'R_Ankle', 'L_Wrist', 'R_Wrist'):
|
||||
smplx_pose_valid[smpl_x.orig_joints_name.index(name)] = 0
|
||||
smplx_pose_valid = np.tile(smplx_pose_valid[:,None], (1,3)).reshape(-1)
|
||||
# SMPLX joint coordinate validity
|
||||
for name in ('L_Big_toe', 'L_Small_toe', 'L_Heel', 'R_Big_toe', 'R_Small_toe', 'R_Heel'):
|
||||
smplx_joint_valid[smpl_x.joints_name.index(name)] = 0
|
||||
smplx_joint_valid = smplx_joint_valid[:,None]
|
||||
smplx_joint_trunc = smplx_joint_valid * smplx_joint_trunc
|
||||
|
||||
# make zero mask for invalid fit
|
||||
if not is_valid_fit:
|
||||
smplx_pose_valid[:] = 0
|
||||
smplx_joint_valid[:] = 0
|
||||
smplx_joint_trunc[:] = 0
|
||||
smplx_shape_valid = False
|
||||
else:
|
||||
smplx_shape_valid = True
|
||||
|
||||
# dummy hand/face bbox
|
||||
dummy_center = np.zeros((2), dtype=np.float32)
|
||||
dummy_size = np.zeros((2), dtype=np.float32)
|
||||
|
||||
inputs = {'img': img}
|
||||
targets = {'joint_img': joint_img, 'smplx_joint_img': smplx_joint_img,
|
||||
'joint_cam': joint_cam, 'smplx_joint_cam': smplx_joint_cam,
|
||||
'smplx_pose': smplx_pose, 'smplx_shape': smplx_shape, 'smplx_expr': smplx_expr,
|
||||
'lhand_bbox_center': dummy_center, 'lhand_bbox_size': dummy_size,
|
||||
'rhand_bbox_center': dummy_center, 'rhand_bbox_size': dummy_size,
|
||||
'face_bbox_center': dummy_center, 'face_bbox_size': dummy_size}
|
||||
meta_info = {'joint_valid': joint_valid, 'joint_trunc': joint_trunc,
|
||||
'smplx_joint_valid': smplx_joint_valid,
|
||||
'smplx_joint_trunc': smplx_joint_trunc, 'smplx_pose_valid': smplx_pose_valid,
|
||||
'smplx_shape_valid': float(smplx_shape_valid),
|
||||
'smplx_expr_valid': float(smplx_expr_valid), 'is_3D': float(False),
|
||||
'lhand_bbox_valid': float(False), 'rhand_bbox_valid': float(False),
|
||||
'face_bbox_valid': float(False)}
|
||||
return inputs, targets, meta_info
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class MPI_INF_3DHP(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(MPI_INF_3DHP, self).__init__(transform, data_split)
|
||||
|
||||
if data_split != 'train':
|
||||
raise NotImplementedError('MPI_INF_3DHP test set is not supported')
|
||||
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', 'mpi_inf_3dhp_neural_annot.npz')
|
||||
self.img_shape = None # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
|
||||
self.datalist = []
|
||||
for pre_prc_file in ['mpi_inf_3dhp_neural_annot_part1.npz', 'mpi_inf_3dhp_neural_annot_part2.npz',
|
||||
'mpi_inf_3dhp_neural_annot_part3.npz']:
|
||||
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', pre_prc_file)
|
||||
else:
|
||||
raise ValueError('MPI_INF_3DHP test set is not support')
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'MPI_INF_3DHP')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
|
||||
# load data
|
||||
datalist_slice = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
self.datalist.extend(datalist_slice)
|
||||
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,478 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
from config import cfg
|
||||
import copy
|
||||
import json
|
||||
import cv2
|
||||
import torch
|
||||
from pycocotools.coco import COCO
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output
|
||||
import random
|
||||
from humandata import Cache
|
||||
|
||||
class MSCOCO(torch.utils.data.Dataset):
|
||||
def __init__(self, transform, data_split):
|
||||
self.transform = transform
|
||||
self.data_split = data_split
|
||||
if os.path.exists(osp.join(cfg.data_dir, 'MSCOCO', 'images')):
|
||||
self.img_path = osp.join(cfg.data_dir, 'MSCOCO', 'images')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'MSCOCO', 'annotations')
|
||||
else:
|
||||
self.img_path = osp.join(cfg.data_dir, 'coco')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'coco', 'annotations')
|
||||
|
||||
# mscoco joint set
|
||||
self.joint_set = {
|
||||
'joint_num': 134, # body 24 (23 + pelvis), lhand 21, rhand 21, face 68
|
||||
'joints_name': \
|
||||
(
|
||||
'Nose', 'L_Eye', 'R_Eye', 'L_Ear', 'R_Ear', 'L_Shoulder', 'R_Shoulder', 'L_Elbow', 'R_Elbow', 'L_Wrist',
|
||||
'R_Wrist', 'L_Hip', 'R_Hip', 'L_Knee', 'R_Knee', 'L_Ankle', 'R_Ankle', 'Pelvis', 'L_Big_toe',
|
||||
'L_Small_toe', 'L_Heel', 'R_Big_toe', 'R_Small_toe', 'R_Heel', # body part
|
||||
'L_Wrist_Hand', 'L_Thumb_1', 'L_Thumb_2', 'L_Thumb_3', 'L_Thumb_4', 'L_Index_1', 'L_Index_2',
|
||||
'L_Index_3', 'L_Index_4', 'L_Middle_1', 'L_Middle_2', 'L_Middle_3', 'L_Middle_4', 'L_Ring_1',
|
||||
'L_Ring_2', 'L_Ring_3', 'L_Ring_4', 'L_Pinky_1', 'L_Pinky_2', 'L_Pinky_3', 'L_Pinky_4', # left hand
|
||||
'R_Wrist_Hand', 'R_Thumb_1', 'R_Thumb_2', 'R_Thumb_3', 'R_Thumb_4', 'R_Index_1', 'R_Index_2',
|
||||
'R_Index_3', 'R_Index_4', 'R_Middle_1', 'R_Middle_2', 'R_Middle_3', 'R_Middle_4', 'R_Ring_1',
|
||||
'R_Ring_2', 'R_Ring_3', 'R_Ring_4', 'R_Pinky_1', 'R_Pinky_2', 'R_Pinky_3', 'R_Pinky_4', # right hand
|
||||
*['Face_' + str(i) for i in range(56, 73)], # face contour
|
||||
*['Face_' + str(i) for i in range(5, 56)] # face
|
||||
),
|
||||
'flip_pairs': \
|
||||
((1, 2), (3, 4), (5, 6), (7, 8), (9, 10), (11, 12), (13, 14), (15, 16), (18, 21), (19, 22), (20, 23),
|
||||
# body part
|
||||
(24, 45), (25, 46), (26, 47), (27, 48), (28, 49), (29, 50), (30, 51), (31, 52), (32, 53), (33, 54),
|
||||
(34, 55), (35, 56), (36, 57), (37, 58), (38, 59), (39, 60), (40, 61), (41, 62), (42, 63), (43, 64),
|
||||
(44, 65), # hand part
|
||||
(66, 82), (67, 81), (68, 80), (69, 79), (70, 78), (71, 77), (72, 76), (73, 75), # face contour
|
||||
(83, 92), (84, 91), (85, 90), (86, 89), (87, 88), # face eyebrow
|
||||
(97, 101), (98, 100), # face below nose
|
||||
(102, 111), (103, 110), (104, 109), (105, 108), (106, 113), (107, 112), # face eyes
|
||||
(114, 120), (115, 119), (116, 118), (121, 125), (122, 124), # face mouth
|
||||
(126, 130), (127, 129), (131, 133) # face lip
|
||||
)
|
||||
}
|
||||
|
||||
# self.datalist = self.load_data()
|
||||
|
||||
# load data or cache
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', f'MSCOCO_{data_split}.npz')
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
datalist = Cache(self.annot_path_cache)
|
||||
assert datalist.data_strategy == getattr(cfg, 'data_strategy', None), \
|
||||
f'Cache data strategy {datalist.data_strategy} does not match current data strategy ' \
|
||||
f'{getattr(cfg, "data_strategy", None)}'
|
||||
self.datalist = datalist
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data()
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Caching datalist to {self.annot_path_cache}...')
|
||||
Cache.save(
|
||||
self.annot_path_cache,
|
||||
self.datalist,
|
||||
data_strategy=getattr(cfg, 'data_strategy', None)
|
||||
)
|
||||
|
||||
|
||||
def merge_joint(self, joint_img, feet_img, lhand_img, rhand_img, face_img):
|
||||
# pelvis
|
||||
lhip_idx = self.joint_set['joints_name'].index('L_Hip')
|
||||
rhip_idx = self.joint_set['joints_name'].index('R_Hip')
|
||||
pelvis = (joint_img[lhip_idx, :] + joint_img[rhip_idx, :]) * 0.5
|
||||
pelvis[2] = joint_img[lhip_idx, 2] * joint_img[rhip_idx, 2] # joint_valid
|
||||
pelvis = pelvis.reshape(1, 3)
|
||||
|
||||
# feet
|
||||
lfoot = feet_img[:3, :]
|
||||
rfoot = feet_img[3:, :]
|
||||
|
||||
joint_img = np.concatenate((joint_img, pelvis, lfoot, rfoot, lhand_img, rhand_img, face_img)).astype(
|
||||
np.float32) # self.joint_set['joint_num'], 3
|
||||
return joint_img
|
||||
|
||||
def load_data(self):
|
||||
if self.data_split == 'train':
|
||||
db = COCO(osp.join(self.annot_path, 'coco_wholebody_train_v1.0.json'))
|
||||
smplx_json_path = osp.join(self.annot_path, 'MSCOCO_train_SMPLX_all_NeuralAnnot.json') # MSCOCO_train_SMPLX.json
|
||||
with open(smplx_json_path) as f:
|
||||
print(f'load SMPLX parameters from {smplx_json_path}')
|
||||
smplx_params = json.load(f)
|
||||
else:
|
||||
db = COCO(osp.join(self.annot_path, 'coco_wholebody_val_v1.0.json'))
|
||||
|
||||
# train mode
|
||||
if self.data_split == 'train':
|
||||
datalist = []
|
||||
i = 0
|
||||
for aid in db.anns.keys():
|
||||
|
||||
i += 1
|
||||
if self.data_split == 'train' and i % getattr(cfg, 'MSCOCO_train_sample_interval', 1) != 0:
|
||||
continue
|
||||
|
||||
ann = db.anns[aid]
|
||||
img = db.loadImgs(ann['image_id'])[0]
|
||||
imgname = osp.join('train2017', img['file_name'])
|
||||
img_path = osp.join(self.img_path, imgname)
|
||||
|
||||
# exclude the samples that are crowd or have few visible keypoints
|
||||
if ann['iscrowd'] or (ann['num_keypoints'] == 0): continue
|
||||
|
||||
# bbox
|
||||
bbox = process_bbox(ann['bbox'], img['width'], img['height'], ratio=getattr(cfg, 'bbox_ratio', 1.25))
|
||||
if bbox is None: continue
|
||||
|
||||
# joint coordinates
|
||||
joint_img = np.array(ann['keypoints'], dtype=np.float32).reshape(-1, 3)
|
||||
foot_img = np.array(ann['foot_kpts'], dtype=np.float32).reshape(-1, 3)
|
||||
lhand_img = np.array(ann['lefthand_kpts'], dtype=np.float32).reshape(-1, 3)
|
||||
rhand_img = np.array(ann['righthand_kpts'], dtype=np.float32).reshape(-1, 3)
|
||||
face_img = np.array(ann['face_kpts'], dtype=np.float32).reshape(-1, 3)
|
||||
joint_img = self.merge_joint(joint_img, foot_img, lhand_img, rhand_img, face_img)
|
||||
|
||||
joint_valid = (joint_img[:, 2].copy().reshape(-1, 1) > 0).astype(np.float32)
|
||||
joint_img[:, 2] = 0
|
||||
|
||||
# use body annotation to fill hand/face annotation
|
||||
for body_name, part_name in (
|
||||
('L_Wrist', 'L_Wrist_Hand'), ('R_Wrist', 'R_Wrist_Hand'), ('Nose', 'Face_18')):
|
||||
if joint_valid[self.joint_set['joints_name'].index(part_name), 0] == 0:
|
||||
joint_img[self.joint_set['joints_name'].index(part_name)] = joint_img[
|
||||
self.joint_set['joints_name'].index(body_name)]
|
||||
joint_valid[self.joint_set['joints_name'].index(part_name)] = joint_valid[
|
||||
self.joint_set['joints_name'].index(body_name)]
|
||||
|
||||
# hand/face bbox
|
||||
if ann['lefthand_valid']:
|
||||
lhand_bbox = np.array(ann['lefthand_box']).reshape(4)
|
||||
if hasattr(cfg, 'bbox_ratio'):
|
||||
lhand_bbox = process_bbox(lhand_bbox, img['width'], img['height'], ratio=cfg.bbox_ratio)
|
||||
if lhand_bbox is not None:
|
||||
lhand_bbox[2:] += lhand_bbox[:2] # xywh -> xyxy
|
||||
else:
|
||||
lhand_bbox = None
|
||||
if ann['righthand_valid']:
|
||||
rhand_bbox = np.array(ann['righthand_box']).reshape(4)
|
||||
if hasattr(cfg, 'bbox_ratio'):
|
||||
rhand_bbox = process_bbox(rhand_bbox, img['width'], img['height'], ratio=cfg.bbox_ratio)
|
||||
if rhand_bbox is not None:
|
||||
rhand_bbox[2:] += rhand_bbox[:2] # xywh -> xyxy
|
||||
else:
|
||||
rhand_bbox = None
|
||||
if ann['face_valid']:
|
||||
face_bbox = np.array(ann['face_box']).reshape(4)
|
||||
if hasattr(cfg, 'bbox_ratio'):
|
||||
face_bbox = process_bbox(face_bbox, img['width'], img['height'], ratio=cfg.bbox_ratio)
|
||||
if face_bbox is not None:
|
||||
face_bbox[2:] += face_bbox[:2] # xywh -> xyxy
|
||||
else:
|
||||
face_bbox = None
|
||||
|
||||
if str(aid) in smplx_params:
|
||||
smplx_param = smplx_params[str(aid)]
|
||||
if 'lhand_valid' not in smplx_param['smplx_param']:
|
||||
smplx_param['smplx_param']['lhand_valid'] = ann['lefthand_valid']
|
||||
smplx_param['smplx_param']['rhand_valid'] = ann['righthand_valid']
|
||||
smplx_param['smplx_param']['face_valid'] = ann['face_valid']
|
||||
else:
|
||||
smplx_param = None
|
||||
|
||||
data_dict = {'img_path': img_path, 'img_shape': (img['height'], img['width']), 'bbox': bbox,
|
||||
'joint_img': joint_img, 'joint_valid': joint_valid, 'smplx_param': smplx_param,
|
||||
'lhand_bbox': lhand_bbox, 'rhand_bbox': rhand_bbox, 'face_bbox': face_bbox}
|
||||
datalist.append(data_dict)
|
||||
|
||||
print('[MSCOCO train] original size:', len(db.anns.keys()),
|
||||
'. Sample interval:', getattr(cfg, 'MSCOCO_train_sample_interval', 1),
|
||||
'. Sampled size:', len(datalist))
|
||||
|
||||
if getattr(cfg, 'data_strategy', None) == 'balance':
|
||||
print(f"[MSCOCO] Using [balance] strategy with datalist shuffled...")
|
||||
random.shuffle(datalist)
|
||||
|
||||
return datalist
|
||||
|
||||
# test mode
|
||||
else:
|
||||
datalist = []
|
||||
for aid in db.anns.keys():
|
||||
ann = db.anns[aid]
|
||||
img = db.loadImgs(ann['image_id'])[0]
|
||||
imgname = osp.join('val2017', img['file_name'])
|
||||
img_path = osp.join(self.img_path, imgname)
|
||||
|
||||
# bbox
|
||||
bbox = process_bbox(ann['bbox'], img['width'], img['height'], ratio=getattr(cfg, 'bbox_ratio', 1.25))
|
||||
if bbox is None: continue
|
||||
|
||||
# hand/face bbox
|
||||
if ann['lefthand_valid']:
|
||||
lhand_bbox = np.array(ann['lefthand_box']).reshape(4)
|
||||
lhand_bbox[2:] += lhand_bbox[:2] # xywh -> xyxy
|
||||
else:
|
||||
lhand_bbox = None
|
||||
if ann['righthand_valid']:
|
||||
rhand_bbox = np.array(ann['righthand_box']).reshape(4)
|
||||
rhand_bbox[2:] += rhand_bbox[:2] # xywh -> xyxy
|
||||
else:
|
||||
rhand_bbox = None
|
||||
if ann['face_valid']:
|
||||
face_bbox = np.array(ann['face_box']).reshape(4)
|
||||
face_bbox[2:] += face_bbox[:2] # xywh -> xyxy
|
||||
else:
|
||||
face_bbox = None
|
||||
|
||||
data_dict = {'img_path': img_path, 'ann_id': aid, 'img_shape': (img['height'], img['width']),
|
||||
'bbox': bbox, 'lhand_bbox': lhand_bbox, 'rhand_bbox': rhand_bbox, 'face_bbox': face_bbox}
|
||||
datalist.append(data_dict)
|
||||
return datalist
|
||||
|
||||
def process_hand_face_bbox(self, bbox, do_flip, img_shape, img2bb_trans):
|
||||
if bbox is None:
|
||||
bbox = np.array([0, 0, 1, 1], dtype=np.float32).reshape(2, 2) # dummy value
|
||||
bbox_valid = float(False) # dummy value
|
||||
else:
|
||||
# reshape to top-left (x,y) and bottom-right (x,y)
|
||||
bbox = bbox.reshape(2, 2)
|
||||
|
||||
# flip augmentation
|
||||
if do_flip:
|
||||
bbox[:, 0] = img_shape[1] - bbox[:, 0] - 1
|
||||
bbox[0, 0], bbox[1, 0] = bbox[1, 0].copy(), bbox[0, 0].copy() # xmin <-> xmax swap
|
||||
|
||||
# make four points of the bbox
|
||||
bbox = bbox.reshape(4).tolist()
|
||||
xmin, ymin, xmax, ymax = bbox
|
||||
bbox = np.array([[xmin, ymin], [xmax, ymin], [xmax, ymax], [xmin, ymax]], dtype=np.float32).reshape(4, 2)
|
||||
|
||||
# affine transformation (crop, rotation, scale)
|
||||
bbox_xy1 = np.concatenate((bbox, np.ones_like(bbox[:, :1])), 1)
|
||||
bbox = np.dot(img2bb_trans, bbox_xy1.transpose(1, 0)).transpose(1, 0)[:, :2]
|
||||
bbox[:, 0] = bbox[:, 0] / cfg.input_img_shape[1] * cfg.output_hm_shape[2]
|
||||
bbox[:, 1] = bbox[:, 1] / cfg.input_img_shape[0] * cfg.output_hm_shape[1]
|
||||
|
||||
# make box a rectangle without rotation
|
||||
xmin = np.min(bbox[:, 0]);
|
||||
xmax = np.max(bbox[:, 0]);
|
||||
ymin = np.min(bbox[:, 1]);
|
||||
ymax = np.max(bbox[:, 1]);
|
||||
bbox = np.array([xmin, ymin, xmax, ymax], dtype=np.float32)
|
||||
|
||||
bbox_valid = float(True)
|
||||
bbox = bbox.reshape(2, 2)
|
||||
|
||||
return bbox, bbox_valid
|
||||
|
||||
def __len__(self):
|
||||
return len(self.datalist)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
data = copy.deepcopy(self.datalist[idx])
|
||||
|
||||
# train mode
|
||||
if self.data_split == 'train':
|
||||
img_path, img_shape = data['img_path'], data['img_shape']
|
||||
|
||||
# image load
|
||||
img = load_img(img_path)
|
||||
bbox = data['bbox']
|
||||
img, img2bb_trans, bb2img_trans, rot, do_flip = augmentation(img, bbox, self.data_split)
|
||||
img = self.transform(img.astype(np.float32)) / 255.
|
||||
|
||||
# hand and face bbox transform
|
||||
lhand_bbox, lhand_bbox_valid = self.process_hand_face_bbox(data['lhand_bbox'], do_flip, img_shape,
|
||||
img2bb_trans)
|
||||
rhand_bbox, rhand_bbox_valid = self.process_hand_face_bbox(data['rhand_bbox'], do_flip, img_shape,
|
||||
img2bb_trans)
|
||||
face_bbox, face_bbox_valid = self.process_hand_face_bbox(data['face_bbox'], do_flip, img_shape,
|
||||
img2bb_trans)
|
||||
if do_flip:
|
||||
lhand_bbox, rhand_bbox = rhand_bbox, lhand_bbox
|
||||
lhand_bbox_valid, rhand_bbox_valid = rhand_bbox_valid, lhand_bbox_valid
|
||||
lhand_bbox_center = (lhand_bbox[0] + lhand_bbox[1]) / 2.;
|
||||
rhand_bbox_center = (rhand_bbox[0] + rhand_bbox[1]) / 2.;
|
||||
face_bbox_center = (face_bbox[0] + face_bbox[1]) / 2.
|
||||
lhand_bbox_size = lhand_bbox[1] - lhand_bbox[0];
|
||||
rhand_bbox_size = rhand_bbox[1] - rhand_bbox[0];
|
||||
face_bbox_size = face_bbox[1] - face_bbox[0];
|
||||
|
||||
# coco gt
|
||||
dummy_coord = np.zeros((self.joint_set['joint_num'], 3), dtype=np.float32)
|
||||
joint_img = data['joint_img']
|
||||
joint_img = np.concatenate((joint_img[:, :2], np.zeros_like(joint_img[:, :1])), 1) # x, y, dummy depth
|
||||
joint_img, joint_cam, joint_cam_ra, joint_valid, joint_trunc = process_db_coord(joint_img, dummy_coord,
|
||||
data['joint_valid'], do_flip, img_shape,
|
||||
self.joint_set['flip_pairs'],
|
||||
img2bb_trans, rot,
|
||||
self.joint_set['joints_name'],
|
||||
smpl_x.joints_name)
|
||||
|
||||
# smplx coordinates and parameters
|
||||
smplx_param = data['smplx_param']
|
||||
if smplx_param is not None:
|
||||
smplx_joint_img, smplx_joint_cam, smplx_joint_trunc, smplx_pose, smplx_shape, smplx_expr, smplx_pose_valid, smplx_joint_valid, smplx_expr_valid, smplx_mesh_cam_orig \
|
||||
= process_human_model_output(smplx_param['smplx_param'], smplx_param['cam_param'], do_flip,
|
||||
img_shape, img2bb_trans, rot, 'smplx')
|
||||
is_valid_fit = True
|
||||
|
||||
"""
|
||||
# for debug
|
||||
_tmp = joint_img.copy()
|
||||
_tmp[:,0] = _tmp[:,0] / cfg.output_hm_shape[2] * cfg.input_img_shape[1]
|
||||
_tmp[:,1] = _tmp[:,1] / cfg.output_hm_shape[1] * cfg.input_img_shape[0]
|
||||
_img = img.numpy().transpose(1,2,0)[:,:,::-1] * 255
|
||||
_img = vis_keypoints(_img, _tmp)
|
||||
cv2.imwrite('coco_' + str(idx) + '.jpg', _img)
|
||||
"""
|
||||
|
||||
else:
|
||||
# dummy values
|
||||
smplx_joint_img = np.zeros((smpl_x.joint_num, 3), dtype=np.float32)
|
||||
smplx_joint_cam = np.zeros((smpl_x.joint_num, 3), dtype=np.float32)
|
||||
smplx_joint_trunc = np.zeros((smpl_x.joint_num, 1), dtype=np.float32)
|
||||
smplx_joint_valid = np.zeros((smpl_x.joint_num), dtype=np.float32)
|
||||
smplx_pose = np.zeros((smpl_x.orig_joint_num * 3), dtype=np.float32)
|
||||
smplx_shape = np.zeros((smpl_x.shape_param_dim), dtype=np.float32)
|
||||
smplx_expr = np.zeros((smpl_x.expr_code_dim), dtype=np.float32)
|
||||
smplx_pose_valid = np.zeros((smpl_x.orig_joint_num), dtype=np.float32)
|
||||
smplx_expr_valid = False
|
||||
is_valid_fit = False
|
||||
|
||||
# SMPLX pose parameter validity
|
||||
smplx_pose_valid = np.tile(smplx_pose_valid[:, None], (1, 3)).reshape(-1)
|
||||
# SMPLX joint coordinate validity
|
||||
smplx_joint_valid = smplx_joint_valid[:, None]
|
||||
smplx_joint_trunc = smplx_joint_valid * smplx_joint_trunc
|
||||
|
||||
# make zero mask for invalid fit
|
||||
if not is_valid_fit:
|
||||
smplx_pose_valid[:] = 0
|
||||
smplx_joint_valid[:] = 0
|
||||
smplx_joint_trunc[:] = 0
|
||||
smplx_shape_valid = False
|
||||
else:
|
||||
smplx_shape_valid = True
|
||||
|
||||
inputs = {'img': img}
|
||||
targets = {'joint_img': joint_img, 'joint_cam': joint_cam, 'smplx_joint_img': smplx_joint_img,
|
||||
'smplx_joint_cam': smplx_joint_cam,
|
||||
'smplx_pose': smplx_pose, 'smplx_shape': smplx_shape, 'smplx_expr': smplx_expr,
|
||||
'lhand_bbox_center': lhand_bbox_center,
|
||||
'lhand_bbox_size': lhand_bbox_size, 'rhand_bbox_center': rhand_bbox_center,
|
||||
'rhand_bbox_size': rhand_bbox_size,
|
||||
'face_bbox_center': face_bbox_center, 'face_bbox_size': face_bbox_size}
|
||||
meta_info = {'joint_valid': joint_valid, 'joint_trunc': joint_trunc, 'smplx_joint_valid': smplx_joint_valid,
|
||||
'smplx_joint_trunc': smplx_joint_trunc,
|
||||
'smplx_pose_valid': smplx_pose_valid, 'smplx_shape_valid': float(smplx_shape_valid),
|
||||
'smplx_expr_valid': float(smplx_expr_valid), 'is_3D': float(False),
|
||||
# 'lhand_bbox_valid': float(False), 'rhand_bbox_valid': float(False),
|
||||
# 'face_bbox_valid': float(False)}
|
||||
'lhand_bbox_valid': lhand_bbox_valid,
|
||||
'rhand_bbox_valid': rhand_bbox_valid, 'face_bbox_valid': face_bbox_valid}
|
||||
return inputs, targets, meta_info
|
||||
|
||||
# test mode
|
||||
else:
|
||||
img_path, img_shape = data['img_path'], data['img_shape']
|
||||
|
||||
# image load
|
||||
img = load_img(img_path)
|
||||
bbox = data['bbox']
|
||||
img, img2bb_trans, bb2img_trans, rot, do_flip = augmentation(img, bbox, self.data_split)
|
||||
img = self.transform(img.astype(np.float32)) / 255.
|
||||
|
||||
inputs = {'img': img}
|
||||
targets = {}
|
||||
meta_info = {'bb2img_trans': bb2img_trans}
|
||||
return inputs, targets, meta_info
|
||||
|
||||
def evaluate(self, outs, cur_sample_idx):
|
||||
annots = self.datalist
|
||||
sample_num = len(outs)
|
||||
|
||||
for n in range(sample_num):
|
||||
annot = annots[cur_sample_idx + n]
|
||||
ann_id = annot['ann_id']
|
||||
out = outs[n]
|
||||
|
||||
if annot['lhand_bbox'] is not None:
|
||||
lhand_bbox = out['lhand_bbox'].copy().reshape(2, 2)
|
||||
lhand_bbox = np.concatenate((lhand_bbox, np.ones((2, 1))), 1)
|
||||
lhand_bbox = np.dot(out['bb2img_trans'], lhand_bbox.transpose(1, 0)).transpose(1, 0)[:, :2]
|
||||
|
||||
if annot['rhand_bbox'] is not None:
|
||||
rhand_bbox = out['rhand_bbox'].copy().reshape(2, 2)
|
||||
rhand_bbox = np.concatenate((rhand_bbox, np.ones((2, 1))), 1)
|
||||
rhand_bbox = np.dot(out['bb2img_trans'], rhand_bbox.transpose(1, 0)).transpose(1, 0)[:, :2]
|
||||
|
||||
if annot['face_bbox'] is not None:
|
||||
face_bbox = out['face_bbox'].copy().reshape(2, 2)
|
||||
face_bbox = np.concatenate((face_bbox, np.ones((2, 1))), 1)
|
||||
face_bbox = np.dot(out['bb2img_trans'], face_bbox.transpose(1, 0)).transpose(1, 0)[:, :2]
|
||||
|
||||
vis = False
|
||||
if vis:
|
||||
img_path = annot['img_path']
|
||||
|
||||
"""
|
||||
img = (out['img'].transpose(1,2,0)[:,:,::-1] * 255).copy()
|
||||
joint_img = out['joint_img'].copy()
|
||||
joint_img[:,0] = joint_img[:,0] / cfg.output_hm_shape[2] * cfg.input_img_shape[1]
|
||||
joint_img[:,1] = joint_img[:,1] / cfg.output_hm_shape[1] * cfg.input_img_shape[0]
|
||||
for j in range(len(joint_img)):
|
||||
if j in smpl_x.pos_joint_part['body']:
|
||||
cv2.circle(img, (int(joint_img[j][0]), int(joint_img[j][1])), 3, (0,0,255), -1)
|
||||
lhand_bbox = out['lhand_bbox'].reshape(2,2).copy()
|
||||
cv2.rectangle(img, (int(lhand_bbox[0][0]), int(lhand_bbox[0][1])), (int(lhand_bbox[1][0]), int(lhand_bbox[1][1])), (255,0,0), 3)
|
||||
rhand_bbox = out['rhand_bbox'].reshape(2,2).copy()
|
||||
cv2.rectangle(img, (int(rhand_bbox[0][0]), int(rhand_bbox[0][1])), (int(rhand_bbox[1][0]), int(rhand_bbox[1][1])), (255,0,0), 3)
|
||||
face_bbox = out['face_bbox'].reshape(2,2).copy()
|
||||
cv2.rectangle(img, (int(face_bbox[0][0]), int(face_bbox[0][1])), (int(face_bbox[1][0]), int(face_bbox[1][1])), (255,0,0), 3)
|
||||
cv2.imwrite(str(ann_id) + '.jpg', img)
|
||||
"""
|
||||
|
||||
# save_obj(out['smplx_mesh_cam'], smpl_x.face, img_id + '_' + str(ann_id) + '.obj')
|
||||
|
||||
"""
|
||||
img = load_img(img_path)[:,:,::-1]
|
||||
bbox = annot['bbox']
|
||||
focal = list(cfg.focal)
|
||||
princpt = list(cfg.princpt)
|
||||
focal[0] = focal[0] / cfg.input_body_shape[1] * bbox[2]
|
||||
focal[1] = focal[1] / cfg.input_body_shape[0] * bbox[3]
|
||||
princpt[0] = princpt[0] / cfg.input_body_shape[1] * bbox[2] + bbox[0]
|
||||
princpt[1] = princpt[1] / cfg.input_body_shape[0] * bbox[3] + bbox[1]
|
||||
img = render_mesh(img, out['smplx_mesh_cam'], smpl_x.face, {'focal': focal, 'princpt': princpt})
|
||||
#img = cv2.resize(img, (512,512))
|
||||
cv2.imwrite(img_id + '_' + str(ann_id) + '.jpg', img)
|
||||
"""
|
||||
|
||||
bbox = annot['bbox']
|
||||
focal = list(cfg.focal)
|
||||
princpt = list(cfg.princpt)
|
||||
focal[0] = focal[0] / cfg.input_body_shape[1] * bbox[2]
|
||||
focal[1] = focal[1] / cfg.input_body_shape[0] * bbox[3]
|
||||
princpt[0] = princpt[0] / cfg.input_body_shape[1] * bbox[2] + bbox[0]
|
||||
princpt[1] = princpt[1] / cfg.input_body_shape[0] * bbox[3] + bbox[1]
|
||||
param_save = {'smplx_param': {'root_pose': out['smplx_root_pose'].tolist(),
|
||||
'body_pose': out['smplx_body_pose'].tolist(),
|
||||
'lhand_pose': out['smplx_lhand_pose'].tolist(),
|
||||
'rhand_pose': out['smplx_rhand_pose'].tolist(),
|
||||
'jaw_pose': out['smplx_jaw_pose'].tolist(),
|
||||
'shape': out['smplx_shape'].tolist(), 'expr': out['smplx_expr'].tolist(),
|
||||
'trans': out['cam_trans'].tolist()},
|
||||
'cam_param': {'focal': focal, 'princpt': princpt}
|
||||
}
|
||||
with open(str(ann_id) + '.json', 'w') as f:
|
||||
json.dump(param_save, f)
|
||||
|
||||
return {}
|
||||
|
||||
def print_eval_result(self, eval_result):
|
||||
return
|
||||
@@ -0,0 +1,45 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class MTP(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(MTP, self).__init__(transform, data_split)
|
||||
|
||||
pre_prc_file = 'mtp_smplx_train.npz'
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', pre_prc_file)
|
||||
else:
|
||||
raise ValueError('MTP test set is not support')
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'MTP')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', filename)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.img_shape = None
|
||||
self.cam_param = {}
|
||||
print("Various image shape in MTP dataset.")
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,48 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class MuCo(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(MuCo, self).__init__(transform, data_split)
|
||||
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', 'muco3dhp_train.npz')
|
||||
else:
|
||||
raise ValueError('MoCo test set is not support')
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'MuCo')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', filename)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.img_shape = (1024, 1024) # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
# check image shape
|
||||
img_path = osp.join(self.img_dir, np.load(self.annot_path)['image_path'][0])
|
||||
img_shape = cv2.imread(img_path).shape[:2]
|
||||
assert self.img_shape == img_shape, 'image shape is incorrect: {} vs {}'.format(self.img_shape, img_shape)
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class OCHuman(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(OCHuman, self).__init__(transform, data_split)
|
||||
|
||||
self.datalist = []
|
||||
|
||||
pre_prc_file = 'eft_ochuman.npz'
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', pre_prc_file)
|
||||
else:
|
||||
raise ValueError('OCHuman test set is not support')
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'OCHuman')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', filename)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.img_shape = None
|
||||
self.cam_param = {}
|
||||
print("Various image shape in OCHuman dataset.")
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,48 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class PROX(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(PROX, self).__init__(transform, data_split)
|
||||
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', 'prox_train_smplx_new.npz')
|
||||
else:
|
||||
raise ValueError('PROX test set is not support')
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'PROX')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', filename)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.img_shape = (1080, 1920) # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
# check image shape
|
||||
img_path = osp.join(self.img_dir, np.load(self.annot_path)['image_path'][0])
|
||||
img_shape = cv2.imread(img_path).shape[:2]
|
||||
assert self.img_shape == img_shape, 'image shape is incorrect: {} vs {}'.format(self.img_shape, img_shape)
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,244 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x, smpl
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_human_model_output, process_db_coord
|
||||
from utils.transforms import rigid_align
|
||||
import numpy as np
|
||||
import random
|
||||
|
||||
|
||||
class PW3D(torch.utils.data.Dataset):
|
||||
def __init__(self, transform, data_split):
|
||||
self.transform = transform
|
||||
self.data_split = data_split
|
||||
self.data_path = osp.join(cfg.data_dir, 'PW3D', 'data')
|
||||
# 3dpw skeleton
|
||||
self.joint_set = {
|
||||
'joint_num': smpl_x.joint_num,
|
||||
'joints_name': smpl_x.joints_name,
|
||||
'flip_pairs': smpl_x.flip_pairs}
|
||||
self.datalist = self.load_data()
|
||||
|
||||
def load_data(self):
|
||||
db = COCO(osp.join(self.data_path, '3DPW_' + self.data_split + '.json'))
|
||||
|
||||
datalist = []
|
||||
i = 0
|
||||
if getattr(cfg, 'eval_on_train', False):
|
||||
self.data_split = 'eval_train'
|
||||
print("Evaluate on train set.")
|
||||
|
||||
for aid in db.anns.keys():
|
||||
i += 1
|
||||
if 'train' in self.data_split and i % getattr(cfg, 'PW3D_train_sample_interval', 1) != 0:
|
||||
continue
|
||||
|
||||
ann = db.anns[aid]
|
||||
image_id = ann['image_id']
|
||||
img = db.loadImgs(image_id)[0]
|
||||
sequence_name = img['sequence']
|
||||
img_name = img['file_name']
|
||||
img_path = osp.join(self.data_path, 'imageFiles', sequence_name, img_name)
|
||||
cam_param = {k: np.array(v, dtype=np.float32) for k,v in img['cam_param'].items()}
|
||||
|
||||
smpl_param = ann['smpl_param']
|
||||
bbox = process_bbox(np.array(ann['bbox']), img['width'], img['height'], ratio=getattr(cfg, 'bbox_ratio', 1.25))
|
||||
if bbox is None: continue
|
||||
data_dict = {'img_path': img_path, 'ann_id': aid, 'img_shape': (img['height'], img['width']),
|
||||
'bbox': bbox, 'smpl_param': smpl_param, 'cam_param': cam_param}
|
||||
datalist.append(data_dict)
|
||||
|
||||
if self.data_split == 'train':
|
||||
print('[PW3D train] original size:', len(db.anns.keys()),
|
||||
'. Sample interval:', getattr(cfg, 'PW3D_train_sample_interval', 1),
|
||||
'. Sampled size:', len(datalist))
|
||||
|
||||
if (getattr(cfg, 'data_strategy', None) == 'balance' and self.data_split == 'train') or \
|
||||
self.data_split == 'eval_train':
|
||||
print(f'[PW3D] Using [balance] strategy with datalist shuffled...')
|
||||
random.seed(2023)
|
||||
random.shuffle(datalist)
|
||||
|
||||
if self.data_split == "eval_train":
|
||||
return datalist[:10000]
|
||||
|
||||
return datalist
|
||||
|
||||
def __len__(self):
|
||||
return len(self.datalist)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
data = copy.deepcopy(self.datalist[idx])
|
||||
img_path, img_shape = data['img_path'], data['img_shape']
|
||||
|
||||
# img
|
||||
img = load_img(img_path)
|
||||
bbox, smpl_param, cam_param = data['bbox'], data['smpl_param'], data['cam_param']
|
||||
img, img2bb_trans, bb2img_trans, rot, do_flip = augmentation(img, bbox, self.data_split)
|
||||
img = self.transform(img.astype(np.float32))/255.
|
||||
cam_param = data['cam_param']
|
||||
|
||||
if self.data_split == 'train':
|
||||
|
||||
smplx_param = {}
|
||||
smplx_param['root_pose'] = np.array(smpl_param['pose']).reshape(-1,3)[:1, :]
|
||||
smplx_param['body_pose'] = np.array(smpl_param['pose']).reshape(-1,3)[1:22, :]
|
||||
smplx_param['trans'] = np.array(smpl_param['trans']).reshape(-1,3)
|
||||
smplx_param['shape'] = np.zeros(10, dtype=np.float32) # drop smpl betas for smplx
|
||||
|
||||
|
||||
# smpl coordinates
|
||||
smplx_joint_img, smplx_joint_cam, smplx_joint_trunc, smplx_pose, smplx_shape, smplx_expr, \
|
||||
smplx_pose_valid, smplx_joint_valid, smplx_expr_valid, smplx_mesh_cam_orig = process_human_model_output(
|
||||
smplx_param, cam_param, do_flip, img_shape, img2bb_trans, rot, 'smplx',
|
||||
joint_img=None)
|
||||
|
||||
# reverse ra
|
||||
smplx_joint_cam_wo_ra = smplx_joint_cam.copy()
|
||||
smplx_joint_cam_wo_ra[smpl_x.joint_part['lhand'], :] = smplx_joint_cam_wo_ra[smpl_x.joint_part['lhand'], :] \
|
||||
+ smplx_joint_cam_wo_ra[smpl_x.lwrist_idx, None, :] # left hand root-relative
|
||||
smplx_joint_cam_wo_ra[smpl_x.joint_part['rhand'], :] = smplx_joint_cam_wo_ra[smpl_x.joint_part['rhand'], :] \
|
||||
+ smplx_joint_cam_wo_ra[smpl_x.rwrist_idx, None, :] # right hand root-relative
|
||||
smplx_joint_cam_wo_ra[smpl_x.joint_part['face'], :] = smplx_joint_cam_wo_ra[smpl_x.joint_part['face'], :] \
|
||||
+ smplx_joint_cam_wo_ra[smpl_x.neck_idx, None,: ] # face root-relative
|
||||
|
||||
|
||||
|
||||
smplx_pose_valid = np.tile(smplx_pose_valid[:, None], (1, 3)).reshape(-1)
|
||||
smplx_joint_valid = smplx_joint_valid[:, None]
|
||||
smplx_joint_trunc = smplx_joint_valid * smplx_joint_trunc
|
||||
|
||||
# smpl coordinates
|
||||
smpl_joint_img, _, _, _, _, _ = process_human_model_output(
|
||||
smpl_param, cam_param, do_flip, img_shape, img2bb_trans, rot, 'smpl',
|
||||
joint_img=None)
|
||||
|
||||
joint_img = np.zeros_like(smplx_joint_img)
|
||||
joint_img[:22] = smpl_joint_img[:22, :]
|
||||
|
||||
# dummy hand/face bbox
|
||||
dummy_center = np.zeros((2), dtype=np.float32)
|
||||
dummy_size = np.zeros((2), dtype=np.float32)
|
||||
|
||||
|
||||
inputs = {'img': img}
|
||||
targets = {'joint_img': joint_img, 'smplx_joint_img': joint_img,
|
||||
'joint_cam': smplx_joint_cam_wo_ra, 'smplx_joint_cam': smplx_joint_cam,
|
||||
'smplx_pose': smplx_pose, 'smplx_shape': smplx_shape, 'smplx_expr': smplx_expr,
|
||||
'lhand_bbox_center': dummy_center, 'lhand_bbox_size': dummy_size,
|
||||
'rhand_bbox_center': dummy_center, 'rhand_bbox_size': dummy_size,
|
||||
'face_bbox_center': dummy_center, 'face_bbox_size': dummy_size}
|
||||
meta_info = {'joint_valid': smplx_joint_valid, 'joint_trunc': smplx_joint_trunc,
|
||||
'smplx_joint_valid': smplx_joint_valid, 'smplx_joint_trunc': smplx_joint_trunc,
|
||||
'smplx_pose_valid': smplx_pose_valid, 'smplx_shape_valid': float(False),
|
||||
'smplx_expr_valid': float(smplx_expr_valid), 'is_3D': float(True),
|
||||
'lhand_bbox_valid': float(False), 'rhand_bbox_valid': float(False),
|
||||
'face_bbox_valid': float(False)}
|
||||
return inputs, targets, meta_info
|
||||
|
||||
else:
|
||||
|
||||
# smpl coordinates
|
||||
smpl_joint_img, smpl_joint_cam, smpl_joint_trunc, smpl_pose, smpl_shape, smpl_mesh_cam_orig = process_human_model_output(smpl_param, cam_param, do_flip, img_shape, img2bb_trans, rot, 'smpl')
|
||||
|
||||
inputs = {'img': img}
|
||||
targets = {'smpl_mesh_cam': smpl_mesh_cam_orig}
|
||||
meta_info = {}
|
||||
return inputs, targets, meta_info
|
||||
|
||||
|
||||
def evaluate(self, outs, cur_sample_idx):
|
||||
annots = self.datalist
|
||||
sample_num = len(outs)
|
||||
eval_result = {'mpjpe_body': [], 'pa_mpjpe_body': [], }
|
||||
|
||||
## smpl/smplx -> lsp
|
||||
# ['left_hip', 'right_hip', 'left_knee', 'right_knee', 'left_ankle',
|
||||
# 'right_ankle', 'neck', 'head', 'left_shoulder', 'right_shoulder',
|
||||
# 'left_elbow', 'right_elbow', 'left_wrist', 'right_wrist']
|
||||
joint_mapper = [1, 2, 4, 5, 7, 8, 12, 15, 16, 17, 18, 19, 20, 21]
|
||||
|
||||
### Save vis for debug
|
||||
# joint_gt_body_to_save = np.zeros((sample_num, len(joint_mapper), 3))
|
||||
# joint_out_body_root_align_to_save = np.zeros((sample_num, len(joint_mapper), 3))
|
||||
# joint_out_body_pa_align_to_save = np.zeros((sample_num, len(joint_mapper), 3))
|
||||
|
||||
for n in range(sample_num):
|
||||
|
||||
out = outs[n]
|
||||
|
||||
# MPVPE from all vertices
|
||||
mesh_gt = out['smpl_mesh_cam_target']
|
||||
mesh_out = out['smplx_mesh_cam']
|
||||
|
||||
# MPJPE from body joints
|
||||
mesh_out_align = mesh_out - np.dot(smpl_x.J_regressor, mesh_out)[smpl_x.J_regressor_idx['pelvis'], None, :] \
|
||||
+ np.dot(smpl.joint_regressor, mesh_gt)[smpl.root_joint_idx, None, :]
|
||||
|
||||
# only eval point0-21 since only smpl gt is given
|
||||
# joint_gt_body = np.dot(smpl.joint_regressor, mesh_gt)[:22, :]
|
||||
# joint_out_body = np.dot(smpl_x.J_regressor, mesh_out)[:22, :]
|
||||
# joint_out_body_root_align = np.dot(smpl_x.J_regressor, mesh_out_align)[:22, :]
|
||||
|
||||
# only test 14 keypoints
|
||||
joint_gt_body = np.dot(smpl.joint_regressor, mesh_gt)[joint_mapper, :]
|
||||
joint_out_body = np.dot(smpl_x.J_regressor, mesh_out)[joint_mapper, :]
|
||||
joint_out_body_root_align = np.dot(smpl_x.J_regressor, mesh_out_align)[joint_mapper, :]
|
||||
|
||||
eval_result['mpjpe_body'].append(
|
||||
np.sqrt(np.sum((joint_out_body_root_align - joint_gt_body) ** 2, 1)).mean() * 1000)
|
||||
|
||||
# PAMPJPE from body joints
|
||||
joint_out_body_pa_align = rigid_align(joint_out_body, joint_gt_body)
|
||||
eval_result['pa_mpjpe_body'].append(
|
||||
np.sqrt(np.sum((joint_out_body_pa_align - joint_gt_body) ** 2, 1)).mean() * 1000)
|
||||
|
||||
### Save vis for debug
|
||||
# joint_gt_body_to_save[n, ...] = joint_gt_body
|
||||
# joint_out_body_root_align_to_save[n, ...] = joint_out_body_root_align
|
||||
# joint_out_body_pa_align_to_save[n, ...] = joint_out_body_pa_align
|
||||
|
||||
### Save vis for debug
|
||||
# import numpy as np
|
||||
# np.save(f'./vis/val_0509_joint_gt_body.npy', joint_gt_body_to_save)
|
||||
# np.save(f'./vis/val_0509_joint_out_body_root_align.npy', joint_out_body_root_align_to_save)
|
||||
# np.save(f'./vis/val_0509_joint_out_body_pa_align.npy', joint_out_body_pa_align_to_save)
|
||||
# import pdb; pdb.set_trace()
|
||||
|
||||
return eval_result
|
||||
|
||||
def print_eval_result(self, eval_result):
|
||||
print('======3DPW-test======')
|
||||
print('MPJPE (Body): %.2f mm' % np.mean(eval_result['mpjpe_body']))
|
||||
print('PA MPJPE (Body): %.2f mm' % np.mean(eval_result['pa_mpjpe_body']))
|
||||
print()
|
||||
print(f"{np.mean(eval_result['mpjpe_body'])},{np.mean(eval_result['pa_mpjpe_body'])}")
|
||||
print()
|
||||
|
||||
f = open(os.path.join(cfg.result_dir, 'result.txt'), 'w')
|
||||
f.write(f'3DPW-test dataset: \n')
|
||||
f.write('MPJPE (Body): %.2f mm\n' % np.mean(eval_result['mpjpe_body']))
|
||||
f.write('PA MPJPE (Body): %.2f mm\n' % np.mean(eval_result['pa_mpjpe_body']))
|
||||
|
||||
f.write(f"{np.mean(eval_result['mpjpe_body'])},{np.mean(eval_result['pa_mpjpe_body'])}")
|
||||
|
||||
if getattr(cfg, 'eval_on_train', False):
|
||||
import csv
|
||||
csv_file = f'{cfg.root_dir}/output/pw3d_eval_on_train.csv'
|
||||
exp_id = cfg.exp_name.split('_')[1]
|
||||
new_line = [exp_id,np.mean(eval_result['mpjpe_body']), np.mean(eval_result['pa_mpjpe_body'])]
|
||||
|
||||
# Append the new line to the CSV file
|
||||
with open(csv_file, 'a', newline='') as file:
|
||||
writer = csv.writer(file)
|
||||
writer.writerow(new_line)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class PoseTrack(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(PoseTrack, self).__init__(transform, data_split)
|
||||
|
||||
self.datalist = []
|
||||
|
||||
pre_prc_file = 'eft_posetrack.npz'
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', pre_prc_file)
|
||||
else:
|
||||
raise ValueError('PoseTrack test set is not support')
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'PoseTrack/data/images')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', filename)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.img_shape = None
|
||||
self.cam_param = {}
|
||||
print("Various image shape in PoseTrack dataset.")
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print('loading cache from {}'.format(self.annot_path_cache))
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,46 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class RICH(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(RICH, self).__init__(transform, data_split)
|
||||
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', 'rich_train_fix_betas.npz')
|
||||
self.img_shape = None # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', 'rich_train_fix_betas.npz')
|
||||
else:
|
||||
raise ValueError('RICH test set is not support')
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'RICH')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
|
||||
# load data
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,60 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
# issue: 4 IndexError: index 432000 is out of bounds for axis 0 with size 432000 (bbox = bbox_xywh[i][:4])
|
||||
class RenBody(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(RenBody, self).__init__(transform, data_split)
|
||||
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
if self.data_split == 'train':
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', 'renbody_train_230525_399_ds10_fix_betas.npz')
|
||||
else:
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', 'renbody_test_230525_78_ds10_fix_betas.npz')
|
||||
self.img_shape = None # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
# load data or cache
|
||||
self.datalist = []
|
||||
for idx in range(10):
|
||||
if self.data_split == 'train':
|
||||
pre_prc_file_train = f'renbody_train_230525_399_{idx}.npz'
|
||||
filename = getattr(cfg, 'filename', pre_prc_file_train)
|
||||
else:
|
||||
if idx > 1: continue
|
||||
pre_prc_file_test = f'renbody_test_230525_78_{idx}.npz'
|
||||
filename = getattr(cfg, 'filename', pre_prc_file_test)
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'RenBody')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
|
||||
# load data
|
||||
datalist_slice = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1),
|
||||
test_sample_interval=getattr(cfg, f'{self.__class__.__name__}_test_sample_interval', 1))
|
||||
|
||||
self.datalist.extend(datalist_slice)
|
||||
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class RenBody_HiRes(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(RenBody_HiRes, self).__init__(transform, data_split)
|
||||
self.datalist = []
|
||||
if getattr(cfg, 'eval_on_train', False):
|
||||
self.data_split = 'eval_train'
|
||||
print("Evaluate on train set.")
|
||||
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', f'renbody_{self.data_split}_highrescam_230517_399_fix_betas.npz')
|
||||
self.img_shape = None # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
|
||||
for idx in range(2):
|
||||
if 'train' in self.data_split:
|
||||
pre_prc_file_train = f'renbody_train_highrescam_230517_399_{idx}_fix_betas.npz'
|
||||
filename = getattr(cfg, 'filename', pre_prc_file_train)
|
||||
else:
|
||||
if idx > 0: continue
|
||||
pre_prc_file_test = f'renbody_test_highrescam_230517_78_{idx}_fix_betas.npz'
|
||||
filename = getattr(cfg, 'filename', pre_prc_file_test)
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'RenBody')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
|
||||
data_split = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_{self.data_split}_sample_interval', 1),
|
||||
test_sample_interval=getattr(cfg, f'{self.__class__.__name__}_{self.data_split}_sample_interval', 10))
|
||||
self.datalist.extend(data_split)
|
||||
|
||||
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,3 @@
|
||||
__pycache__/
|
||||
build/
|
||||
*.so
|
||||
@@ -0,0 +1,218 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
import pickle
|
||||
from body_measurements import BodyMeasurements
|
||||
import smplx
|
||||
from test_submission_format import test_submission_file_format
|
||||
|
||||
|
||||
def point_error(x, y, align=True):
|
||||
""" Ref: https://github.com/muelea/shapy/blob/master/regressor/hbw_evaluation/evaluate_hbw.py#LL44C1-L58C31 """
|
||||
t = 0.0
|
||||
if align:
|
||||
t = x.mean(0, keepdims=True) - y.mean(0, keepdims=True)
|
||||
x_hat = x - t
|
||||
error = np.sqrt(np.power(x_hat - y, 2).sum(axis=-1))
|
||||
return error.mean().item()
|
||||
|
||||
|
||||
class SHAPY(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(SHAPY, self).__init__(transform, data_split)
|
||||
|
||||
self.eval_split = getattr(cfg, 'shapy_eval_split')
|
||||
if self.data_split == 'train':
|
||||
raise NotImplementedError('Shapy train not implemented yet. Need to consider invalid parameters')
|
||||
if self.data_split == 'test' and self.eval_split == 'test':
|
||||
filename = getattr(cfg, 'filename', 'shapy_test_230512_1631.npz')
|
||||
elif self.data_split == 'test' and self.eval_split == 'val':
|
||||
filename = getattr(cfg, 'filename', 'shapy_val_230512_705.npz')
|
||||
else:
|
||||
raise ValueError(f'Undefined. data split: {self.data_split}; eval_split: {self.test_set}')
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'SHAPY')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
self.v_shape_load_dir = osp.join(cfg.data_dir, 'SHAPY', 'HBW', 'smplx', 'val')
|
||||
self.img_shape = None # variable img_shape
|
||||
self.cam_param = {}
|
||||
|
||||
# load data
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
|
||||
### SHAPY utils
|
||||
### ref: https://github.com/muelea/shapy/blob/master/regressor/hbw_evaluation/evaluate_hbw.py#L28
|
||||
|
||||
# load body model
|
||||
# ref: common/utils/human_models.py
|
||||
self.layer_arg = {'create_global_orient': False, 'create_body_pose': False, 'create_left_hand_pose': False,
|
||||
'create_right_hand_pose': False, 'create_jaw_pose': False, 'create_leye_pose': False,
|
||||
'create_reye_pose': False, 'create_betas': False, 'create_expression': False,
|
||||
'create_transl': False}
|
||||
self.smplx_layer = smplx.create(cfg.human_model_path,
|
||||
'smplx',
|
||||
gender='NEUTRAL',
|
||||
use_pca=False,
|
||||
use_face_contour=True,
|
||||
flat_hand_mean=True, # critical!
|
||||
**self.layer_arg).cuda()
|
||||
# self.smplx_layer = copy.deepcopy(smpl_x.layer['neutral']).cuda()
|
||||
self.faces_tensor_smplx = self.smplx_layer.faces_tensor.detach().cpu().numpy()
|
||||
|
||||
# load files to compute P2P-20K Error
|
||||
point_reg = osp.join(cfg.data_dir, 'SHAPY', 'utility_files', 'evaluation', 'eval_point_set', 'HD_SMPLX_from_SMPL.pkl')
|
||||
with open(point_reg, 'rb') as f:
|
||||
self.point_regressor = pickle.load(f)
|
||||
|
||||
# load files to compute Measurements Error
|
||||
body_measurement_folder = osp.join(cfg.data_dir, 'SHAPY', 'utility_files', 'measurements')
|
||||
meas_def_path = osp.join(body_measurement_folder, 'measurement_defitions.yaml')
|
||||
meas_verts_path_gt = osp.join(body_measurement_folder, 'smplx_measurements.yaml')
|
||||
self.body_measurements = BodyMeasurements(
|
||||
{'meas_definition_path': meas_def_path,
|
||||
'meas_vertices_path': meas_verts_path_gt},
|
||||
).to('cuda')
|
||||
|
||||
self.v_shaped_gt = {}
|
||||
|
||||
# to save preditions
|
||||
self.images_names = []
|
||||
self.v_shaped = []
|
||||
|
||||
def evaluate(self, outs, cur_sample_idx):
|
||||
annots = self.datalist
|
||||
sample_num = len(outs)
|
||||
eval_result = {'v2v_t_errors': [], 'point_t_errors': [], 'height': [], 'chest': [], 'waist': [], 'hips': [], 'mass': []}
|
||||
|
||||
# sample_num = sample_num // 10 # TODO: debug only
|
||||
|
||||
for n in range(sample_num):
|
||||
annot = annots[cur_sample_idx + n]
|
||||
out = outs[n]
|
||||
|
||||
betas_fit = out['smplx_shape']
|
||||
img_path = out['img_path']
|
||||
|
||||
# compute v_shaped
|
||||
betas_fit = torch.tensor(betas_fit.reshape(-1, 10)).cuda()
|
||||
# betas_fit = torch.tensor(out['smplx_shape_target'].reshape(-1, 10)).cuda() # TODO: debug only
|
||||
# betas_fit = torch.zeros((1, 10)).cuda() # TODO: debug only
|
||||
output = self.smplx_layer(
|
||||
betas=betas_fit,
|
||||
body_pose=torch.zeros((1, 63)).to(betas_fit.device),
|
||||
global_orient=torch.zeros((1, 3)).to(betas_fit.device),
|
||||
right_hand_pose=torch.zeros((1, 45)).to(betas_fit.device),
|
||||
left_hand_pose=torch.zeros((1, 45)).to(betas_fit.device),
|
||||
jaw_pose=torch.zeros((1, 3)).to(betas_fit.device),
|
||||
leye_pose=torch.zeros((1, 3)).to(betas_fit.device),
|
||||
reye_pose=torch.zeros((1, 3)).to(betas_fit.device),
|
||||
expression=torch.zeros((1, 10)).to(betas_fit.device),
|
||||
return_verts=True
|
||||
)
|
||||
v_shaped_fit = output.vertices.detach().cpu().numpy().squeeze()
|
||||
# v_shaped_gt = v_shaped_fit # TODO: debug only
|
||||
# v_shaped_fit = self.smplx_layer.forward_shape(betas=betas_fit)
|
||||
|
||||
image_name = '/'.join(img_path.split('/')[-4:])
|
||||
self.images_names.append(image_name)
|
||||
self.v_shaped.append(v_shaped_fit)
|
||||
|
||||
if self.eval_split == 'val':
|
||||
# load gt vertices
|
||||
subject = img_path.split('/')[-3]
|
||||
subject_id_npy = subject.split('_')[0] + '.npy'
|
||||
v_shaped_gt_path = osp.join(self.v_shape_load_dir, subject_id_npy)
|
||||
if v_shaped_gt_path not in self.v_shaped_gt:
|
||||
v_shaped_gt = np.load(v_shaped_gt_path)
|
||||
self.v_shaped_gt[v_shaped_gt_path] = v_shaped_gt
|
||||
else:
|
||||
v_shaped_gt = self.v_shaped_gt[v_shaped_gt_path]
|
||||
|
||||
# compute vertex-to-vertex error (SMPL-X only)
|
||||
# ref: https://github.com/muelea/shapy/blob/master/regressor/hbw_evaluation/evaluate_hbw.py#LL142C1-L171C48
|
||||
v2v_error = point_error(v_shaped_fit, v_shaped_gt, align=True)
|
||||
eval_result['v2v_t_errors'].append(v2v_error)
|
||||
|
||||
# compute P2P-20k error
|
||||
points_gt = self.point_regressor.dot(v_shaped_gt)
|
||||
points_fit = self.point_regressor.dot(v_shaped_fit)
|
||||
p2p_error = point_error(points_gt, points_fit, align=True)
|
||||
eval_result['point_t_errors'].append(p2p_error)
|
||||
|
||||
# compute height/chest/waist/hip error
|
||||
shaped_triangles_gt = v_shaped_gt[self.faces_tensor_smplx]
|
||||
shaped_triangles_gt = torch.from_numpy(shaped_triangles_gt).unsqueeze(0).to('cuda')
|
||||
measurements_gt = self.body_measurements(shaped_triangles_gt)['measurements']
|
||||
|
||||
shaped_triangles_fit = v_shaped_fit[self.faces_tensor_smplx]
|
||||
shaped_triangles_fit = torch.from_numpy(shaped_triangles_fit).unsqueeze(0).to('cuda')
|
||||
measurements_fit = self.body_measurements(shaped_triangles_fit)['measurements']
|
||||
|
||||
for k in ['height', 'chest', 'waist', 'hips', 'mass']:
|
||||
error = abs(measurements_gt[k]['tensor'].item() - measurements_fit[k]['tensor'].item())
|
||||
eval_result[k].append(error)
|
||||
|
||||
|
||||
return eval_result
|
||||
|
||||
|
||||
def print_eval_result(self, eval_result):
|
||||
|
||||
# print('SHAPY results are dumped at: ' + osp.join(cfg.result_dir, 'predictions'))
|
||||
|
||||
if self.data_split == 'test' and self.eval_split == 'test': # do not print. just submit the results to the official evaluation server
|
||||
# save predictions in the format of HBW challenge
|
||||
# ref: https://github.com/muelea/shapy/blob/master/regressor/hbw_evaluation/README_HBW_EVAL.md#hbw-challenge
|
||||
save_dir = osp.join(cfg.result_dir, 'predictions')
|
||||
os.makedirs(save_dir, exist_ok=True)
|
||||
save_name = osp.join(save_dir, 'hbw_prediction')
|
||||
images_names = np.array(self.images_names).reshape(1631, )
|
||||
v_shaped = np.array(self.v_shaped).reshape(1631, 10475, 3)
|
||||
np.savez(save_name,
|
||||
image_name=images_names,
|
||||
v_shaped=v_shaped)
|
||||
print('predictions saved at: ' + save_name + '.npz')
|
||||
|
||||
# run format test
|
||||
test_submission_file_format(save_name + '.npz')
|
||||
return
|
||||
|
||||
v2v_t_errors = np.mean(eval_result['v2v_t_errors']) * 1000
|
||||
point_t_errors = np.mean(eval_result['point_t_errors']) * 1000
|
||||
chest = np.mean(eval_result['chest']) * 1000
|
||||
waist = np.mean(eval_result['waist']) * 1000
|
||||
hips = np.mean(eval_result['hips']) * 1000
|
||||
height = np.mean(eval_result['height']) * 1000
|
||||
mass = np.mean(eval_result['mass'])
|
||||
|
||||
print('======SHAPY-val======')
|
||||
print('Height Error: %.2f mm' % height)
|
||||
print('Chest Error: %.2f mm' % chest)
|
||||
print('Waist Error: %.2f mm' % waist)
|
||||
print('Hips Error: %.2f mm' % hips)
|
||||
print('P2P-20k Error: %.2f mm' % point_t_errors)
|
||||
print('V2V Error: %.2f mm' % v2v_t_errors)
|
||||
print('Mass Error: %.2f kg' % mass)
|
||||
|
||||
f = open(os.path.join(cfg.result_dir, 'result.txt'), 'w')
|
||||
f.write(f'SHAPY-val dataset: \n')
|
||||
f.write('Height Error: %.2f mm\n' % height)
|
||||
f.write('Chest Error: %.2f mm' % chest)
|
||||
f.write('Waist Error: %.2f mm\n' % waist)
|
||||
f.write('Hips Error: %.2f mm\n' % hips)
|
||||
f.write('P2P-20k Error: %.2f mm' % point_t_errors)
|
||||
f.write('V2V Error: %.2f mm\n' % v2v_t_errors)
|
||||
f.write('Mass Error: %.2f kg\n' % mass)
|
||||
f.close()
|
||||
@@ -0,0 +1,58 @@
|
||||
License
|
||||
|
||||
Software Copyright License for non-commercial scientific research purposes
|
||||
Please read carefully the following terms and conditions and any accompanying documentation before you download and/or use the SMPL-X/SMPLify-X model, data and software, (the "Model & Software"), including 3D meshes, blend weights, blend shapes, textures, software, scripts, and animations. By downloading and/or using the Model & Software (including downloading, cloning, installing, and any other use of this github repository), you acknowledge that you have read these terms and conditions, understand them, and agree to be bound by them. If you do not agree with these terms and conditions, you must not download and/or use the Model & Software. Any infringement of the terms of this agreement will automatically terminate your rights under this License
|
||||
|
||||
Ownership / Licensees
|
||||
The Software and the associated materials has been developed at the
|
||||
|
||||
Max Planck Institute for Intelligent Systems (hereinafter "MPI").
|
||||
|
||||
Any copyright or patent right is owned by and proprietary material of the
|
||||
|
||||
Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (hereinafter “MPG”; MPI and MPG hereinafter collectively “Max-Planck”)
|
||||
|
||||
hereinafter the “Licensor”.
|
||||
|
||||
License Grant
|
||||
Licensor grants you (Licensee) personally a single-user, non-exclusive, non-transferable, free of charge right:
|
||||
|
||||
To install the Model & Software on computers owned, leased or otherwise controlled by you and/or your organization;
|
||||
To use the Model & Software for the sole purpose of performing non-commercial scientific research, non-commercial education, or non-commercial artistic projects;
|
||||
Any other use, in particular any use for commercial purposes, is prohibited. This includes, without limitation, incorporation in a commercial product, use in a commercial service, or production of other artifacts for commercial purposes. The Model & Software may not be reproduced, modified and/or made available in any form to any third party without Max-Planck’s prior written permission.
|
||||
|
||||
The Model & Software may not be used for pornographic purposes or to generate pornographic material whether commercial or not. This license also prohibits the use of the Model & Software to train methods/algorithms/neural networks/etc. for commercial use of any kind. By downloading the Model & Software, you agree not to reverse engineer it.
|
||||
|
||||
No Distribution
|
||||
The Model & Software and the license herein granted shall not be copied, shared, distributed, re-sold, offered for re-sale, transferred or sub-licensed in whole or in part except that you may make one copy for archive purposes only.
|
||||
|
||||
Disclaimer of Representations and Warranties
|
||||
You expressly acknowledge and agree that the Model & Software results from basic research, is provided “AS IS”, may contain errors, and that any use of the Model & Software is at your sole risk. LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE MODEL & SOFTWARE, NEITHER EXPRESS NOR IMPLIED, AND THE ABSENCE OF ANY LEGAL OR ACTUAL DEFECTS, WHETHER DISCOVERABLE OR NOT. Specifically, and not to limit the foregoing, licensor makes no representations or warranties (i) regarding the merchantability or fitness for a particular purpose of the Model & Software, (ii) that the use of the Model & Software will not infringe any patents, copyrights or other intellectual property rights of a third party, and (iii) that the use of the Model & Software will not cause any damage of any kind to you or a third party.
|
||||
|
||||
Limitation of Liability
|
||||
Because this Model & Software License Agreement qualifies as a donation, according to Section 521 of the German Civil Code (Bürgerliches Gesetzbuch – BGB) Licensor as a donor is liable for intent and gross negligence only. If the Licensor fraudulently conceals a legal or material defect, they are obliged to compensate the Licensee for the resulting damage.
|
||||
Licensor shall be liable for loss of data only up to the amount of typical recovery costs which would have arisen had proper and regular data backup measures been taken. For the avoidance of doubt Licensor shall be liable in accordance with the German Product Liability Act in the event of product liability. The foregoing applies also to Licensor’s legal representatives or assistants in performance. Any further liability shall be excluded.
|
||||
Patent claims generated through the usage of the Model & Software cannot be directed towards the copyright holders.
|
||||
The Model & Software is provided in the state of development the licensor defines. If modified or extended by Licensee, the Licensor makes no claims about the fitness of the Model & Software and is not responsible for any problems such modifications cause.
|
||||
|
||||
No Maintenance Services
|
||||
You understand and agree that Licensor is under no obligation to provide either maintenance services, update services, notices of latent defects, or corrections of defects with regard to the Model & Software. Licensor nevertheless reserves the right to update, modify, or discontinue the Model & Software at any time.
|
||||
|
||||
Defects of the Model & Software must be notified in writing to the Licensor with a comprehensible description of the error symptoms. The notification of the defect should enable the reproduction of the error. The Licensee is encouraged to communicate any use, results, modification or publication.
|
||||
|
||||
Publications using the Model & Software
|
||||
You acknowledge that the Model & Software is a valuable scientific resource and agree to appropriately reference the following paper in any publication making use of the Model & Software.
|
||||
|
||||
Citation:
|
||||
|
||||
|
||||
@inproceedings{SMPL-X:2019,
|
||||
title = {Expressive Body Capture: 3D Hands, Face, and Body from a Single Image},
|
||||
author = {Pavlakos, Georgios and Choutas, Vasileios and Ghorbani, Nima and Bolkart, Timo and Osman, Ahmed A. A. and Tzionas, Dimitrios and Black, Michael J.},
|
||||
booktitle = {Proceedings IEEE Conf. on Computer Vision and Pattern Recognition (CVPR)},
|
||||
year = {2019}
|
||||
}
|
||||
Commercial licensing opportunities
|
||||
For commercial uses of the Software, please send email to ps-license@tue.mpg.de
|
||||
|
||||
This Agreement shall be governed by the laws of the Federal Republic of Germany except for the UN Sales Convention.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Computing mesh-mesh intersection
|
||||
|
||||
This package provides a PyTorch module that can efficiently compute mesh-mesh
|
||||
intersections using a BVH.
|
||||
|
||||
|
||||
## Table of Contents
|
||||
* [Description](#description)
|
||||
* [Installation](#installation)
|
||||
* [Examples](#examples)
|
||||
* [Citation](#citation)
|
||||
* [License](#license)
|
||||
* [Contact](#contact)
|
||||
|
||||
## Description
|
||||
|
||||
This repository provides a PyTorch wrapper around a CUDA kernel that implements
|
||||
the method described in [Maximizing parallelism in the construction of BVHs,
|
||||
octrees, and k-d trees](https://dl.acm.org/citation.cfm?id=2383801). More
|
||||
specifically, given an input mesh it builds a
|
||||
BVH tree for each one and queries it for self-intersections.
|
||||
|
||||
## Installation
|
||||
|
||||
See the instructions [here](docs/install.md) on how to install the package.
|
||||
|
||||
## Examples
|
||||
|
||||
### Fitting to measurements
|
||||
|
||||
To fit a 3D human body model to height, weight and circumenference measurements
|
||||
use the following command:
|
||||
```python
|
||||
python examples/fit_measurements.py --model-folder PATH_TO_BODY_MODELS \
|
||||
--model-type [smpl/smplh/star/smplx] --gender neutral/female/male --num-betas 30 \
|
||||
--meas-vertices-path data/smpl_measurement_vertices.yaml
|
||||
```
|
||||
If you are using SMPL-X then set `--meas-vertices-path data/smplx_measurements.yaml`.
|
||||
|
||||
## Citation
|
||||
|
||||
If you find this code useful in your research please cite the relevant work(s) of the following list, for detecting and penalizing mesh intersections accordingly:
|
||||
|
||||
```
|
||||
@inproceedings{Karras:2012:MPC:2383795.2383801,
|
||||
author = {Karras, Tero},
|
||||
title = {Maximizing Parallelism in the Construction of BVHs, Octrees, and K-d Trees},
|
||||
booktitle = {Proceedings of the Fourth ACM SIGGRAPH / Eurographics Conference on High-Performance Graphics},
|
||||
year = {2012},
|
||||
pages = {33--37},
|
||||
numpages = {5},
|
||||
url = {https://doi.org/10.2312/EGGH/HPG12/033-037},
|
||||
doi = {10.2312/EGGH/HPG12/033-037},
|
||||
publisher = {Eurographics Association}
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Software Copyright License for **non-commercial scientific research purposes**.
|
||||
Please read carefully the [terms and
|
||||
conditions](https://github.com/vchoutas/mesh-mesh-intersection/blob/master/LICENSE) and any
|
||||
accompanying documentation before you download and/or use the SMPL-X/SMPLify-X
|
||||
model, data and software, (the "Model & Software"), including 3D meshes, blend
|
||||
weights, blend shapes, textures, software, scripts, and animations. By
|
||||
downloading and/or using the Model & Software (including downloading, cloning,
|
||||
installing, and any other use of this github repository), you acknowledge that
|
||||
you have read these terms and conditions, understand them, and agree to be bound
|
||||
by them. If you do not agree with these terms and conditions, you must not
|
||||
download and/or use the Model & Software. Any infringement of the terms of this
|
||||
agreement will automatically terminate your rights under this
|
||||
[License](./LICENSE).
|
||||
|
||||
|
||||
|
||||
|
||||
## Contact
|
||||
The code of this repository was implemented by [Vassilis Choutas](vassilis.choutas@tuebingen.mpg.de).
|
||||
|
||||
For questions, please contact [smplx@tue.mpg.de](smplx@tue.mpg.de).
|
||||
|
||||
For commercial licensing (and all related questions for business applications), please contact [ps-licensing@tue.mpg.de](ps-licensing@tue.mpg.de). Please note that the method for this component has been [patented by NVidia](https://patents.google.com/patent/US9396512B2/en) and a license needs to be obtained also by them.
|
||||
@@ -0,0 +1,2 @@
|
||||
from .body_measurements import BodyMeasurements
|
||||
from .cwh_measurements import ChestWaistHipsMeasurements
|
||||
@@ -0,0 +1,246 @@
|
||||
from typing import NewType, Dict, Tuple
|
||||
import os.path as osp
|
||||
import yaml
|
||||
import numpy as np
|
||||
|
||||
from mesh_mesh_intersection import MeshMeshIntersection
|
||||
import torch
|
||||
import torch.autograd as autograd
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from scipy.spatial import ConvexHull
|
||||
# from loguru import logger
|
||||
|
||||
Tensor = NewType('Tensor', torch.Tensor)
|
||||
|
||||
|
||||
class BodyMeasurements(nn.Module):
|
||||
|
||||
# The density of the human body is 985 kg / m^3
|
||||
DENSITY = 985
|
||||
|
||||
def __init__(self, cfg, **kwargs):
|
||||
''' Loss that penalizes deviations in weight and height
|
||||
'''
|
||||
super(BodyMeasurements, self).__init__()
|
||||
|
||||
meas_definition_path = cfg.get('meas_definition_path', '')
|
||||
meas_definition_path = osp.expanduser(
|
||||
osp.expandvars(meas_definition_path))
|
||||
meas_vertices_path = cfg.get('meas_vertices_path', '')
|
||||
meas_vertices_path = osp.expanduser(
|
||||
osp.expandvars(meas_vertices_path))
|
||||
|
||||
with open(meas_definition_path, 'r') as f:
|
||||
measurements_definitions = yaml.safe_load(f, )
|
||||
|
||||
with open(meas_vertices_path, 'r') as f:
|
||||
meas_vertices = yaml.safe_load(f)
|
||||
|
||||
head_top = meas_vertices['HeadTop']
|
||||
left_heel = meas_vertices['HeelLeft']
|
||||
|
||||
left_heel_bc = left_heel['bc']
|
||||
self.left_heel_face_idx = left_heel['face_idx']
|
||||
|
||||
left_heel_bc = torch.tensor(left_heel['bc'], dtype=torch.float32)
|
||||
self.register_buffer('left_heel_bc', left_heel_bc)
|
||||
|
||||
head_top_bc = torch.tensor(head_top['bc'], dtype=torch.float32)
|
||||
self.register_buffer('head_top_bc', head_top_bc)
|
||||
|
||||
self.head_top_face_idx = head_top['face_idx']
|
||||
|
||||
action = measurements_definitions['CW_p']
|
||||
chest_periphery_data = meas_vertices[action[0]]
|
||||
|
||||
self.chest_face_index = chest_periphery_data['face_idx']
|
||||
chest_bcs = torch.tensor(
|
||||
chest_periphery_data['bc'], dtype=torch.float32)
|
||||
self.register_buffer('chest_bcs', chest_bcs)
|
||||
|
||||
action = measurements_definitions['BW_p']
|
||||
belly_periphery_data = meas_vertices[action[0]]
|
||||
|
||||
self.belly_face_index = belly_periphery_data['face_idx']
|
||||
belly_bcs = torch.tensor(
|
||||
belly_periphery_data['bc'], dtype=torch.float32)
|
||||
self.register_buffer('belly_bcs', belly_bcs)
|
||||
|
||||
action = measurements_definitions['IW_p']
|
||||
hips_periphery_data = meas_vertices[action[0]]
|
||||
|
||||
self.hips_face_index = hips_periphery_data['face_idx']
|
||||
hips_bcs = torch.tensor(
|
||||
hips_periphery_data['bc'], dtype=torch.float32)
|
||||
self.register_buffer('hips_bcs', hips_bcs)
|
||||
|
||||
max_collisions = cfg.get('max_collisions', 256)
|
||||
self.isect_module = MeshMeshIntersection(max_collisions=max_collisions)
|
||||
|
||||
def extra_repr(self) -> str:
|
||||
msg = []
|
||||
msg.append(f'Human Body Density: {self.DENSITY}')
|
||||
return '\n'.join(msg)
|
||||
|
||||
def _get_plane_at_heights(self, height: Tensor) -> Tuple[Tensor, Tensor, Tensor]:
|
||||
device = height.device
|
||||
batch_size = height.shape[0]
|
||||
|
||||
verts = torch.tensor(
|
||||
[[-1., 0, -1], [1, 0, -1], [1, 0, 1], [-1, 0, 1]],
|
||||
device=device).unsqueeze(dim=0).expand(batch_size, -1, -1).clone()
|
||||
verts[:, :, 1] = height.reshape(batch_size, -1)
|
||||
faces = torch.tensor([[0, 1, 2], [0, 2, 3]], device=device,
|
||||
dtype=torch.long)
|
||||
|
||||
return verts, faces, verts[:, faces]
|
||||
|
||||
def compute_peripheries(
|
||||
self,
|
||||
triangles: Tensor,
|
||||
compute_chest: bool = True,
|
||||
compute_waist: bool = True,
|
||||
compute_hips: bool = True,
|
||||
) -> Dict[str, Tensor]:
|
||||
'''
|
||||
Parameters
|
||||
----------
|
||||
triangles: BxFx3x3 torch.Tensor
|
||||
Contains the triangle coordinates for a batch of meshes with
|
||||
the same topology
|
||||
'''
|
||||
|
||||
batch_size, num_triangles = triangles.shape[:2]
|
||||
device = triangles.device
|
||||
|
||||
batch_indices = torch.arange(
|
||||
batch_size, dtype=torch.long,
|
||||
device=device).reshape(-1, 1) * num_triangles
|
||||
|
||||
meas_data = {}
|
||||
if compute_chest:
|
||||
meas_data['chest'] = (self.chest_face_index, self.chest_bcs)
|
||||
if compute_waist:
|
||||
meas_data['waist'] = (self.belly_face_index, self.belly_bcs)
|
||||
if compute_hips:
|
||||
meas_data['hips'] = (self.hips_face_index, self.hips_bcs)
|
||||
|
||||
output = {}
|
||||
for name, (face_index, bcs) in meas_data.items():
|
||||
|
||||
vertex = (
|
||||
triangles[:, face_index] * bcs.reshape(1, 3, 1)).sum(axis=1)
|
||||
|
||||
_, _, plane_tris = self._get_plane_at_heights(vertex[:, 1])
|
||||
|
||||
with torch.no_grad():
|
||||
collision_faces, collision_bcs = self.isect_module(
|
||||
plane_tris, triangles)
|
||||
|
||||
selected_triangles = triangles.view(-1, 3, 3)[
|
||||
(collision_faces + batch_indices).view(-1)].reshape(
|
||||
batch_size, -1, 3, 3)
|
||||
points = (
|
||||
selected_triangles[:, :, None] *
|
||||
collision_bcs[:, :, :, :, None]).sum(
|
||||
axis=-2).reshape(batch_size, -1, 2, 3)
|
||||
|
||||
np_points = points.detach().cpu().numpy()
|
||||
collision_faces = collision_faces.detach().cpu().numpy()
|
||||
collision_bcs = collision_bcs.detach().cpu().numpy()
|
||||
|
||||
output[name] = {
|
||||
'points': [],
|
||||
'valid_points': [],
|
||||
'value': [],
|
||||
'plane_height': vertex[:, 1],
|
||||
}
|
||||
|
||||
for ii in range(batch_size):
|
||||
valid_face_idxs = np.where(collision_faces[ii] > 0)[0]
|
||||
points_in_plane = np_points[
|
||||
ii, valid_face_idxs, :, ][:, :, [0, 2]].reshape(
|
||||
-1, 2)
|
||||
hull = ConvexHull(points_in_plane)
|
||||
point_indices = hull.simplices.reshape(-1)
|
||||
|
||||
hull_points = points[ii][valid_face_idxs].view(
|
||||
-1, 3)[point_indices].reshape(-1, 2, 3)
|
||||
|
||||
meas_value = (
|
||||
hull_points[:, 1] - hull_points[:, 0]).pow(2).sum(
|
||||
dim=-1).sqrt().sum()
|
||||
|
||||
output[name]['valid_points'].append(
|
||||
np_points[ii, valid_face_idxs])
|
||||
output[name]['points'].append(hull_points)
|
||||
output[name]['value'].append(meas_value)
|
||||
output[name]['tensor'] = torch.stack(output[name]['value'])
|
||||
return output
|
||||
|
||||
def compute_height(self, shaped_triangles: Tensor) -> Tuple[Tensor, Tensor]:
|
||||
''' Compute the height using the heel and the top of the head
|
||||
'''
|
||||
head_top_tri = shaped_triangles[:, self.head_top_face_idx]
|
||||
head_top = (head_top_tri[:, 0, :] * self.head_top_bc[0] +
|
||||
head_top_tri[:, 1, :] * self.head_top_bc[1] +
|
||||
head_top_tri[:, 2, :] * self.head_top_bc[2])
|
||||
head_top = (
|
||||
head_top_tri * self.head_top_bc.reshape(1, 3, 1)
|
||||
).sum(dim=1)
|
||||
left_heel_tri = shaped_triangles[:, self.left_heel_face_idx]
|
||||
left_heel = (
|
||||
left_heel_tri * self.left_heel_bc.reshape(1, 3, 1)
|
||||
).sum(dim=1)
|
||||
|
||||
return (torch.abs(head_top[:, 1] - left_heel[:, 1]),
|
||||
torch.stack([head_top, left_heel], axis=0)
|
||||
)
|
||||
|
||||
def compute_mass(self, tris: Tensor) -> Tensor:
|
||||
''' Computes the mass from volume and average body density
|
||||
'''
|
||||
x = tris[:, :, :, 0]
|
||||
y = tris[:, :, :, 1]
|
||||
z = tris[:, :, :, 2]
|
||||
volume = (
|
||||
-x[:, :, 2] * y[:, :, 1] * z[:, :, 0] +
|
||||
x[:, :, 1] * y[:, :, 2] * z[:, :, 0] +
|
||||
x[:, :, 2] * y[:, :, 0] * z[:, :, 1] -
|
||||
x[:, :, 0] * y[:, :, 2] * z[:, :, 1] -
|
||||
x[:, :, 1] * y[:, :, 0] * z[:, :, 2] +
|
||||
x[:, :, 0] * y[:, :, 1] * z[:, :, 2]
|
||||
).sum(dim=1).abs() / 6.0
|
||||
return volume * self.DENSITY
|
||||
|
||||
def forward(
|
||||
self,
|
||||
triangles: Tensor,
|
||||
compute_mass: bool = True,
|
||||
compute_height: bool = True,
|
||||
compute_chest: bool = True,
|
||||
compute_waist: bool = True,
|
||||
compute_hips: bool = True,
|
||||
**kwargs
|
||||
):
|
||||
measurements = {}
|
||||
if compute_mass:
|
||||
measurements['mass'] = {}
|
||||
mesh_mass = self.compute_mass(triangles)
|
||||
measurements['mass']['tensor'] = mesh_mass
|
||||
|
||||
if compute_height:
|
||||
measurements['height'] = {}
|
||||
mesh_height, points = self.compute_height(triangles)
|
||||
measurements['height']['tensor'] = mesh_height
|
||||
measurements['height']['points'] = points
|
||||
|
||||
output = self.compute_peripheries(triangles,
|
||||
compute_chest=compute_chest,
|
||||
compute_waist=compute_waist,
|
||||
compute_hips=compute_hips,
|
||||
)
|
||||
measurements.update(output)
|
||||
|
||||
return {'measurements': measurements}
|
||||
@@ -0,0 +1,182 @@
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
import sys
|
||||
import os
|
||||
import os.path as osp
|
||||
|
||||
from typing import NewType, Dict
|
||||
import time
|
||||
|
||||
import yaml
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.autograd as autograd
|
||||
# from loguru import logger
|
||||
|
||||
from mesh_mesh_intersection import MeshMeshIntersection
|
||||
from scipy.spatial import ConvexHull
|
||||
|
||||
Tensor = NewType('Tensor', torch.Tensor)
|
||||
|
||||
|
||||
class ChestWaistHipsMeasurements(nn.Module):
|
||||
def __init__(
|
||||
self, meas_definition_path: str, meas_vertices_path: str,
|
||||
max_collisions=256,
|
||||
*args, **kwargs
|
||||
) -> None:
|
||||
super(ChestWaistHipsMeasurements, self).__init__()
|
||||
meas_definition_path = osp.expanduser(
|
||||
osp.expandvars(meas_definition_path))
|
||||
meas_vertices_path = osp.expanduser(
|
||||
osp.expandvars(meas_vertices_path))
|
||||
|
||||
assert osp.exists(meas_definition_path), (
|
||||
'Measurement definition path does not exist:'
|
||||
f' {meas_definition_path}'
|
||||
)
|
||||
assert osp.exists(meas_definition_path), (
|
||||
'Measurement vertex path does not exist:'
|
||||
f' {meas_vertices_path}'
|
||||
)
|
||||
|
||||
with open(meas_definition_path, 'r') as f:
|
||||
measurements_definitions = yaml.load(f)
|
||||
|
||||
with open(meas_vertices_path, 'r') as f:
|
||||
meas_vertices = yaml.load(f)
|
||||
|
||||
action = measurements_definitions['CW_p']
|
||||
chest_periphery_data = meas_vertices[action[0]]
|
||||
|
||||
self.chest_face_index = chest_periphery_data['face_idx']
|
||||
chest_bcs = torch.tensor(
|
||||
chest_periphery_data['bc'], dtype=torch.float32)
|
||||
self.register_buffer('chest_bcs', chest_bcs)
|
||||
|
||||
action = measurements_definitions['BW_p']
|
||||
belly_periphery_data = meas_vertices[action[0]]
|
||||
|
||||
self.belly_face_index = belly_periphery_data['face_idx']
|
||||
belly_bcs = torch.tensor(
|
||||
belly_periphery_data['bc'], dtype=torch.float32)
|
||||
self.register_buffer('belly_bcs', belly_bcs)
|
||||
|
||||
action = measurements_definitions['IW_p']
|
||||
hips_periphery_data = meas_vertices[action[0]]
|
||||
|
||||
self.hips_face_index = hips_periphery_data['face_idx']
|
||||
hips_bcs = torch.tensor(
|
||||
hips_periphery_data['bc'], dtype=torch.float32)
|
||||
self.register_buffer('hips_bcs', hips_bcs)
|
||||
|
||||
self.isect_module = MeshMeshIntersection(max_collisions=max_collisions)
|
||||
|
||||
def _get_plane_at_heights(self, height: Tensor):
|
||||
device = height.device
|
||||
batch_size = height.shape[0]
|
||||
|
||||
verts = torch.tensor(
|
||||
[[-1., 0, -1], [1, 0, -1], [1, 0, 1], [-1, 0, 1]],
|
||||
device=device).unsqueeze(dim=0).expand(batch_size, -1, -1).clone()
|
||||
verts[:, :, 1] = height.reshape(batch_size, -1)
|
||||
faces = torch.tensor([[0, 1, 2], [0, 2, 3]], device=device,
|
||||
dtype=torch.long)
|
||||
|
||||
return verts, faces, verts[:, faces]
|
||||
|
||||
def forward(
|
||||
self,
|
||||
triangles: Tensor
|
||||
) -> Dict[str, Tensor]:
|
||||
'''
|
||||
Parameters
|
||||
----------
|
||||
triangles: BxFx3x3 torch.Tensor
|
||||
Contains the triangle coordinates for a batch of meshes with
|
||||
the same topology
|
||||
'''
|
||||
|
||||
batch_size, num_triangles = triangles.shape[:2]
|
||||
device = triangles.device
|
||||
|
||||
batch_indices = torch.arange(
|
||||
batch_size, dtype=torch.long,
|
||||
device=device).reshape(-1, 1) * num_triangles
|
||||
|
||||
meas_data = {
|
||||
'chest': (self.chest_face_index, self.chest_bcs),
|
||||
'belly': (self.belly_face_index, self.belly_bcs),
|
||||
'hips': (self.hips_face_index, self.hips_bcs),
|
||||
}
|
||||
|
||||
output = {}
|
||||
for name, (face_index, bcs) in meas_data.items():
|
||||
|
||||
vertex = (
|
||||
triangles[:, face_index] * bcs.reshape(1, 3, 1)).sum(axis=1)
|
||||
|
||||
_, _, plane_tris = self._get_plane_at_heights(vertex[:, 1])
|
||||
|
||||
with torch.no_grad():
|
||||
collision_faces, collision_bcs = self.isect_module(
|
||||
plane_tris, triangles)
|
||||
|
||||
selected_triangles = triangles.view(-1, 3, 3)[
|
||||
(collision_faces + batch_indices).view(-1)].reshape(
|
||||
batch_size, -1, 3, 3)
|
||||
points = (
|
||||
selected_triangles[:, :, None] *
|
||||
collision_bcs[:, :, :, :, None]).sum(
|
||||
axis=-2).reshape(batch_size, -1, 2, 3)
|
||||
|
||||
np_points = points.detach().cpu().numpy()
|
||||
collision_faces = collision_faces.detach().cpu().numpy()
|
||||
collision_bcs = collision_bcs.detach().cpu().numpy()
|
||||
|
||||
output[name] = {
|
||||
'points': [],
|
||||
'valid_points': [],
|
||||
'value': [],
|
||||
'plane_height': vertex[:, 1],
|
||||
}
|
||||
|
||||
for ii in range(batch_size):
|
||||
valid_face_idxs = np.where(collision_faces[ii] > 0)[0]
|
||||
points_in_plane = np_points[
|
||||
ii, valid_face_idxs, :, ][:, :, [0, 2]].reshape(
|
||||
-1, 2)
|
||||
hull = ConvexHull(points_in_plane)
|
||||
point_indices = hull.simplices.reshape(-1)
|
||||
|
||||
hull_points = points[ii][valid_face_idxs].view(
|
||||
-1, 3)[point_indices]
|
||||
|
||||
meas_value = (
|
||||
hull_points[1::2] - hull_points[:-1:2]).pow(2).sum(
|
||||
dim=-1).sqrt().sum()
|
||||
# logger.info(f'{ii}: {name}, {meas_value}')
|
||||
|
||||
output[name]['valid_points'].append(
|
||||
np_points[ii, valid_face_idxs])
|
||||
output[name]['points'].append(hull_points)
|
||||
output[name]['value'].append(meas_value)
|
||||
# values.append(
|
||||
# )
|
||||
return output
|
||||
@@ -0,0 +1,111 @@
|
||||
A:
|
||||
- FingerTipRight
|
||||
- FingerTipLeft
|
||||
- 0
|
||||
- - 0
|
||||
- 0
|
||||
- 0.2
|
||||
BW:
|
||||
- BellyButton
|
||||
- 0
|
||||
- - 0.0
|
||||
- 0
|
||||
- 0.15
|
||||
BW_p:
|
||||
- BellyButton
|
||||
- 0
|
||||
- - 0.0
|
||||
- 0
|
||||
- 0
|
||||
CW:
|
||||
- NippleRight
|
||||
- 0
|
||||
- - 0.0
|
||||
- 0
|
||||
- 0.2
|
||||
CW_p:
|
||||
- NippleRight
|
||||
- 0
|
||||
- - 0.0
|
||||
- 0
|
||||
- 0
|
||||
DBB:
|
||||
- BackBellyButton
|
||||
- BellyButton
|
||||
- 2
|
||||
- - 0.15
|
||||
- 0
|
||||
- 0
|
||||
H:
|
||||
- HeelLeft
|
||||
- HeadTop
|
||||
- 1
|
||||
- - -0.15
|
||||
- 0
|
||||
- 0.0
|
||||
HB:
|
||||
- HeelLeft
|
||||
- NippleRight
|
||||
- NippleLeft
|
||||
- 1
|
||||
- - -0.1
|
||||
- 0
|
||||
- 0.0
|
||||
HBB:
|
||||
- HeelLeft
|
||||
- BellyButton
|
||||
- 1
|
||||
- - -0.05
|
||||
- 0
|
||||
- 0.0
|
||||
HI:
|
||||
- HeelLeft
|
||||
- Crotch
|
||||
- 1
|
||||
- - 0
|
||||
- 0
|
||||
- 0
|
||||
IW:
|
||||
- Crotch
|
||||
- 0
|
||||
- - 0.0
|
||||
- 0
|
||||
- 0.15
|
||||
IW_p:
|
||||
- Crotch
|
||||
- 0
|
||||
- - 0.0
|
||||
- 0
|
||||
- 0
|
||||
SW:
|
||||
- ShoulderApose
|
||||
- 0
|
||||
- - 0
|
||||
- 0
|
||||
- 0.1
|
||||
V: []
|
||||
W2E:
|
||||
- ElbowRight
|
||||
- WristRight
|
||||
- ElbowLeft
|
||||
- WristLeft
|
||||
- 0
|
||||
- - 0
|
||||
- 0.0
|
||||
- 0.1
|
||||
W2S:
|
||||
- ShoulderRight
|
||||
- WristRight
|
||||
- ShoulderLeft
|
||||
- WristLeft
|
||||
- 0
|
||||
- - 0
|
||||
- 0.0
|
||||
- 0.1
|
||||
W2W:
|
||||
- WristRight
|
||||
- WristLeft
|
||||
- 0
|
||||
- - 0
|
||||
- -0.0
|
||||
- 0.15
|
||||
@@ -0,0 +1,112 @@
|
||||
BackBellyButton:
|
||||
bc:
|
||||
- 1.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
face_idx: 4971
|
||||
vertex_id: 3022
|
||||
BellyButton:
|
||||
bc:
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
face_idx: 6833
|
||||
vertex_id: 3501
|
||||
Crotch:
|
||||
bc:
|
||||
- 0.0
|
||||
- 1.0
|
||||
- 0.0
|
||||
face_idx: 1341
|
||||
vertex_id: 1210
|
||||
ElbowLeft:
|
||||
bc:
|
||||
- 1.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
face_idx: 1867
|
||||
vertex_id: 1658
|
||||
ElbowRight:
|
||||
bc:
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
face_idx: 8756
|
||||
vertex_id: 5129
|
||||
FingerTipLeft:
|
||||
bc:
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
face_idx: 3259
|
||||
vertex_id: 2445
|
||||
FingerTipRight:
|
||||
bc:
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
face_idx: 10147
|
||||
vertex_id: 5905
|
||||
HeadTop:
|
||||
bc:
|
||||
- 0.0
|
||||
- 1.0
|
||||
- 0.0
|
||||
face_idx: 435
|
||||
vertex_id: 411
|
||||
HeelLeft:
|
||||
bc:
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
face_idx: 5975
|
||||
vertex_id: 3466
|
||||
NippleLeft:
|
||||
bc:
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
face_idx: 4997
|
||||
vertex_id: 3042
|
||||
NippleRight:
|
||||
bc:
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
face_idx: 11885
|
||||
vertex_id: 6489
|
||||
ShoulderApose:
|
||||
bc:
|
||||
- 1.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
face_idx: 11937
|
||||
vertex_id: 6496
|
||||
ShoulderLeft:
|
||||
bc:
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
face_idx: 4572
|
||||
vertex_id: 2893
|
||||
ShoulderRight:
|
||||
bc:
|
||||
- 1.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
face_idx: 9117
|
||||
vertex_id: 5291
|
||||
WristLeft:
|
||||
bc:
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
face_idx: 2603
|
||||
vertex_id: 2099
|
||||
WristRight:
|
||||
bc:
|
||||
- 0.0
|
||||
- 1.0
|
||||
- 0.0
|
||||
face_idx: 9491
|
||||
vertex_id: 5559
|
||||
@@ -0,0 +1,160 @@
|
||||
BackBellyButton:
|
||||
bc:
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
closest_points:
|
||||
- 0.0
|
||||
- -0.261382
|
||||
- -0.102003
|
||||
face_idx: 7861
|
||||
BellyButton:
|
||||
bc:
|
||||
- 0.0
|
||||
- 1.0
|
||||
- 0.0
|
||||
closest_points:
|
||||
- -0.0
|
||||
- -0.271119
|
||||
- 0.144329
|
||||
face_idx: 19229
|
||||
Crotch:
|
||||
bc:
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
closest_points:
|
||||
- 0.0
|
||||
- -0.53204
|
||||
- 0.036471
|
||||
face_idx: 6194
|
||||
ElbowLeft:
|
||||
bc:
|
||||
- 0.0
|
||||
- 1.0
|
||||
- 0.0
|
||||
closest_points:
|
||||
- 0.447234
|
||||
- 0.075995
|
||||
- -0.099974
|
||||
face_idx: 3959
|
||||
ElbowRight:
|
||||
bc:
|
||||
- 0.0
|
||||
- 1.0
|
||||
- 0.0
|
||||
closest_points:
|
||||
- -0.447233
|
||||
- 0.075995
|
||||
- -0.099973
|
||||
face_idx: 7846
|
||||
FingerTipLeft:
|
||||
bc:
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
closest_points:
|
||||
- 0.918755
|
||||
- 0.085413
|
||||
- -0.084745
|
||||
face_idx: 3469
|
||||
FingerTipRight:
|
||||
bc:
|
||||
- 1.3918288479186636e-05
|
||||
- 0.9998084353210817
|
||||
- 0.00017764639043920215
|
||||
closest_points:
|
||||
- -0.9187545563631815
|
||||
- 0.08541207136535614
|
||||
- -0.08474471930198169
|
||||
face_idx: 17602
|
||||
HeadTop:
|
||||
bc:
|
||||
- 0.8277337276382795
|
||||
- 0.1422200962169292
|
||||
- 0.030046176144791284
|
||||
closest_points:
|
||||
- -0.0017716260945737938
|
||||
- 0.4363661265424736
|
||||
- -0.015488867245138805
|
||||
face_idx: 2581
|
||||
HeelLeft:
|
||||
bc:
|
||||
- 0.0
|
||||
- 1.0
|
||||
- 0.0
|
||||
closest_points:
|
||||
- 0.103005
|
||||
- -1.346656
|
||||
- -0.082664
|
||||
face_idx: 15605
|
||||
NippleLeft:
|
||||
bc:
|
||||
- 1.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
closest_points:
|
||||
- 0.09747
|
||||
- -0.032798
|
||||
- 0.103687
|
||||
face_idx: 16306
|
||||
NippleRight:
|
||||
bc:
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
closest_points:
|
||||
- -0.09747
|
||||
- -0.032798
|
||||
- 0.103687
|
||||
face_idx: 18402
|
||||
ShoulderApose:
|
||||
bc:
|
||||
- 1.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
closest_points:
|
||||
- -0.013092
|
||||
- 0.117681
|
||||
- 0.03777
|
||||
face_idx: 18412
|
||||
ShoulderLeft:
|
||||
bc:
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
closest_points:
|
||||
- 0.183378
|
||||
- 0.139588
|
||||
- -0.066495
|
||||
face_idx: 3865
|
||||
ShoulderRight:
|
||||
bc:
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
closest_points:
|
||||
- -0.196498
|
||||
- 0.140399
|
||||
- -0.063292
|
||||
face_idx: 18186
|
||||
WristLeft:
|
||||
bc:
|
||||
- 1.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
closest_points:
|
||||
- 0.72226
|
||||
- 0.065775
|
||||
- -0.070361
|
||||
face_idx: 3363
|
||||
WristRight:
|
||||
bc:
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
closest_points:
|
||||
- -0.72226
|
||||
- 0.065775
|
||||
- -0.070361
|
||||
face_idx: 6722
|
||||
@@ -0,0 +1,31 @@
|
||||
# Installation
|
||||
|
||||
Before installing anything please make sure to set the environment variable
|
||||
*$CUDA_SAMPLES_INC* to the path that contains the header `helper_math.h`, which
|
||||
can be found in the repo [CUDA Samples repository](https://github.com/NVIDIA/cuda-samples).
|
||||
To install the module run the following commands:
|
||||
|
||||
**1. Clone this repository**
|
||||
```Shell
|
||||
git clone https://github.com/vchoutas/torch-mesh-isect
|
||||
cd torch-mesh-isect
|
||||
```
|
||||
**2. Install the dependencies**
|
||||
```Shell
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
**3. Run the *setup.py* script**
|
||||
```Shell
|
||||
python setup.py install
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
1. [PyTorch](https://pytorch.org)
|
||||
|
||||
### Optional Dependencies
|
||||
|
||||
1. [Trimesh](https://trimsh.org) for loading triangular meshes
|
||||
2. [open3d](http://www.open3d.org/) for visualization
|
||||
|
||||
The code has been tested with Python 3.6, CUDA 10.0, CuDNN 7.3 and PyTorch 1.0.
|
||||
@@ -0,0 +1,304 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
import sys
|
||||
import os.path as osp
|
||||
import argparse
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
import torch.optim as optim
|
||||
|
||||
import smplx
|
||||
import open3d as o3d
|
||||
import time
|
||||
import cv2
|
||||
from tqdm import tqdm
|
||||
|
||||
import trimesh
|
||||
from loguru import logger
|
||||
from star.pytorch.star import STAR
|
||||
from star.config import cfg as star_cfg
|
||||
|
||||
from body_measurements import BodyMeasurements
|
||||
from torchtrustncg import TrustRegion
|
||||
|
||||
|
||||
def get_plane_at_height(h):
|
||||
verts = np.array([[-1., h, -1], [1, h, -1], [1, h, 1], [-1, h, 1]])
|
||||
faces = np.array([[0, 1, 2], [0, 2, 3]])
|
||||
|
||||
normal = np.array([0.0, 1.0, 0.0])
|
||||
return verts, faces, (verts[0], normal)
|
||||
|
||||
|
||||
def main(
|
||||
model_folder,
|
||||
height: float = 1.76,
|
||||
mass: float = -1,
|
||||
chest: float = 1.12,
|
||||
waist: float = 0.93,
|
||||
hips: float = 1.14,
|
||||
model_type='smplx',
|
||||
ext='npz',
|
||||
gender='neutral',
|
||||
num_betas=10,
|
||||
meas_definition_path: str = 'data/measurement_defitions.yaml',
|
||||
meas_vertices_path: str = 'data/smpl_measurement_vertices.yaml',
|
||||
summary_steps: int = 50,
|
||||
num_iterations: int = 500,
|
||||
betas_weight: float = 0.0,
|
||||
):
|
||||
|
||||
device = torch.device('cuda')
|
||||
dtype = torch.float32
|
||||
|
||||
cfg = {
|
||||
'meas_definition_path': meas_definition_path,
|
||||
'meas_vertices_path': meas_vertices_path,
|
||||
}
|
||||
meas_module = BodyMeasurements(cfg)
|
||||
meas_module = meas_module.to(device=device)
|
||||
|
||||
num_samples = 1
|
||||
|
||||
trans, pose = None, None
|
||||
logger.info(f'Model type: {model_type}')
|
||||
if 'star' in model_type:
|
||||
star_cfg.path_male_star = osp.expandvars(
|
||||
osp.join(model_folder, 'star', 'STAR_MALE.npz'))
|
||||
star_cfg.path_female_star = osp.expandvars(
|
||||
osp.join(model_folder, 'star', 'STAR_FEMALE.npz'))
|
||||
model = STAR(gender=gender, num_betas=num_betas)
|
||||
trans = torch.zeros([num_samples, 3], dtype=dtype, device=device)
|
||||
pose = torch.zeros([num_samples, 72], dtype=dtype, device=device)
|
||||
else:
|
||||
model = smplx.build_layer(
|
||||
model_folder, model_type=model_type,
|
||||
gender=gender,
|
||||
num_betas=num_betas,
|
||||
ext=ext)
|
||||
|
||||
logger.info(model)
|
||||
model = model.to(device=device)
|
||||
|
||||
betas = torch.zeros(
|
||||
[num_samples, model.num_betas],
|
||||
requires_grad=True, dtype=torch.float32, device=device)
|
||||
|
||||
dtype = torch.float32
|
||||
gt = {
|
||||
'height': torch.tensor(height, dtype=dtype, device=device),
|
||||
'mass': torch.tensor(mass, dtype=dtype, device=device),
|
||||
'chest': torch.tensor(chest, dtype=dtype, device=device),
|
||||
'waist': torch.tensor(waist, dtype=dtype, device=device),
|
||||
'hips': torch.tensor(hips, dtype=dtype, device=device),
|
||||
}
|
||||
weights = {
|
||||
'height': 100.0 if height > 0 else 0.0,
|
||||
'mass': 1.0 if mass > 0 else 0.0,
|
||||
'chest': 2000.0 if chest > 0 else 0.0,
|
||||
'waist': 1000.0 if waist > 0 else 0.0,
|
||||
'hips': 1000.0 if hips > 0 else 0.0,
|
||||
}
|
||||
|
||||
optimizer = TrustRegion([betas])
|
||||
|
||||
def compute_loss(gt, output, weights):
|
||||
losses = {}
|
||||
for key, gt_val in gt.items():
|
||||
if weights[key] <= 1e-3 or gt_val.item() < 0:
|
||||
continue
|
||||
est_val = output[key]['tensor']
|
||||
if isinstance(est_val, (tuple, list)):
|
||||
est_val = torch.stack(output[key]['value'])
|
||||
curr_loss = (gt_val - est_val).pow(2).sum() * weights[key]
|
||||
losses[key] = curr_loss
|
||||
|
||||
losses['betas'] = betas_weight * betas.pow(2).sum()
|
||||
return losses
|
||||
|
||||
def closure(backward=True):
|
||||
if backward:
|
||||
optimizer.zero_grad()
|
||||
|
||||
if model_type == 'star':
|
||||
vertices = model(pose=pose, trans=trans, betas=betas)
|
||||
model_tris = vertices[:, model.faces]
|
||||
else:
|
||||
output = model(betas=betas, return_verts=True)
|
||||
model_tris = output.vertices[:, model.faces_tensor]
|
||||
|
||||
output = meas_module(model_tris)['measurements']
|
||||
|
||||
losses = compute_loss(gt, output, weights)
|
||||
|
||||
loss = sum(losses.values())
|
||||
if backward:
|
||||
loss.backward(create_graph=True)
|
||||
|
||||
return loss
|
||||
|
||||
Y_OFFSET = -1.10
|
||||
|
||||
for n in tqdm(range(num_iterations)):
|
||||
loss = optimizer.step(closure)
|
||||
|
||||
if n % summary_steps == 0:
|
||||
if model_type == 'star':
|
||||
vertices = model(pose=pose, trans=trans, betas=betas)
|
||||
model_tris = vertices[:, model.faces]
|
||||
vertices = vertices.detach().cpu().numpy().squeeze()
|
||||
faces = model.faces.detach().cpu().numpy()
|
||||
else:
|
||||
output = model(betas=betas, return_verts=True)
|
||||
vertices = output.vertices.detach().cpu().numpy().squeeze()
|
||||
faces = model.faces
|
||||
model_tris = output.vertices[:, model.faces_tensor]
|
||||
|
||||
y_offset = - vertices[:, 1].min() + Y_OFFSET
|
||||
vertices[:, 1] = vertices[:, 1] + y_offset
|
||||
|
||||
# for key, val in losses.items():
|
||||
mesh = o3d.geometry.TriangleMesh()
|
||||
mesh.vertices = o3d.utility.Vector3dVector(vertices)
|
||||
mesh.triangles = o3d.utility.Vector3iVector(faces)
|
||||
mesh.compute_vertex_normals()
|
||||
|
||||
colors = np.ones_like(vertices) * [0.3, 0.3, 0.3]
|
||||
mesh.vertex_colors = o3d.utility.Vector3dVector(colors)
|
||||
|
||||
geometry = []
|
||||
geometry.append(mesh)
|
||||
|
||||
output = meas_module(model_tris)['measurements']
|
||||
for key, val in gt.items():
|
||||
est_val = output[key]["tensor"][0].item()
|
||||
logger.info(
|
||||
f'[{n:04d}]: {key}: est = {est_val}, gt = {val}')
|
||||
|
||||
losses = compute_loss(gt, output, weights)
|
||||
for key, val in losses.items():
|
||||
logger.info(f'[{n:04d}]: {key} loss = {val:.3f}')
|
||||
|
||||
for meas_name in output:
|
||||
pcl = o3d.geometry.PointCloud()
|
||||
if 'points' not in output[meas_name]:
|
||||
continue
|
||||
|
||||
points = output[meas_name]['points']
|
||||
if isinstance(points, (tuple, list)):
|
||||
points = torch.stack(points)
|
||||
if torch.is_tensor(points):
|
||||
points = points.detach().cpu().numpy()
|
||||
points = points.reshape(-1, 3)
|
||||
|
||||
points[:, 1] = points[:, 1] + y_offset
|
||||
|
||||
pcl.points = o3d.utility.Vector3dVector(points)
|
||||
pcl.paint_uniform_color([1.0, 0.0, 0.0])
|
||||
geometry.append(pcl)
|
||||
|
||||
lineset = o3d.geometry.LineSet()
|
||||
line_ids = np.arange(len(points)).reshape(-1, 2)
|
||||
lineset.points = o3d.utility.Vector3dVector(points)
|
||||
lineset.lines = o3d.utility.Vector2iVector(line_ids)
|
||||
lineset.paint_uniform_color([0.0, 0.0, 0.0])
|
||||
geometry.append(lineset)
|
||||
|
||||
o3d.visualization.draw_geometries(
|
||||
geometry,
|
||||
lookat=np.array([0.0, 0.0, 0.0]).reshape(3, 1),
|
||||
up=np.array([0.0, 1.0, 0.0]).reshape(3, 1),
|
||||
front=np.array([0.0, 0.0, 1.0]).reshape(3, 1),
|
||||
zoom=1.0,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger.remove()
|
||||
logger.add(lambda x: tqdm.write(x, end=''), colorize=True)
|
||||
parser = argparse.ArgumentParser(description='SMPL-X Demo')
|
||||
|
||||
parser.add_argument('--model-folder', required=True, type=str,
|
||||
help='The path to the model folder')
|
||||
parser.add_argument('--model-type', default='smpl', type=str,
|
||||
choices=['smpl', 'smplh', 'smplx', 'mano', 'flame',
|
||||
'star', ],
|
||||
help='The type of model to load')
|
||||
parser.add_argument('--gender', type=str, default='neutral',
|
||||
help='The gender of the model')
|
||||
parser.add_argument('--num-betas', default=10, type=int,
|
||||
dest='num_betas',
|
||||
help='Number of shape coefficients.')
|
||||
parser.add_argument('--ext', type=str, default='npz',
|
||||
help='Which extension to use for loading')
|
||||
parser.add_argument('--height', type=float, default=1.80,
|
||||
help='Height of the subject in meters')
|
||||
parser.add_argument('--mass', type=float, default=-1,
|
||||
help='Mass of the subject in kilograms')
|
||||
parser.add_argument('--chest', type=float, default=-1,
|
||||
help='Chest circumference in meters')
|
||||
parser.add_argument('--waist', type=float, default=-1,
|
||||
help='Waist circumference in meters')
|
||||
parser.add_argument('--hips', type=float, default=-1,
|
||||
help='Hips circumference in meters')
|
||||
parser.add_argument('--meas-definition-path',
|
||||
dest='meas_definition_path',
|
||||
default='data/measurement_defitions.yaml',
|
||||
type=str,
|
||||
help='The definitions of the measurements')
|
||||
parser.add_argument('--meas-vertices-path', dest='meas_vertices_path',
|
||||
type=str,
|
||||
default='data/smpl_measurement_vertices.yaml',
|
||||
help='The indices of the vertices used for the'
|
||||
' the measurements')
|
||||
parser.add_argument('--betas-weight', dest='betas_weight', default=0.0,
|
||||
type=float,
|
||||
help='The weight of the shape prior term.')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
model_folder = osp.expanduser(osp.expandvars(args.model_folder))
|
||||
model_type = args.model_type
|
||||
gender = args.gender
|
||||
ext = args.ext
|
||||
num_betas = args.num_betas
|
||||
|
||||
height = args.height
|
||||
mass = args.mass
|
||||
chest = args.chest
|
||||
waist = args.waist
|
||||
hips = args.hips
|
||||
meas_definition_path = args.meas_definition_path
|
||||
meas_vertices_path = args.meas_vertices_path
|
||||
betas_weight = args.betas_weight
|
||||
|
||||
main(model_folder,
|
||||
height=height,
|
||||
mass=mass,
|
||||
chest=chest,
|
||||
waist=waist,
|
||||
hips=hips,
|
||||
model_type=model_type,
|
||||
ext=ext,
|
||||
gender=gender,
|
||||
num_betas=num_betas,
|
||||
meas_definition_path=meas_definition_path,
|
||||
meas_vertices_path=meas_vertices_path,
|
||||
betas_weight=betas_weight,
|
||||
)
|
||||
@@ -0,0 +1,245 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
import sys
|
||||
import os.path as osp
|
||||
import argparse
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
import smplx
|
||||
import open3d as o3d
|
||||
import time
|
||||
import cv2
|
||||
from scipy.spatial import ConvexHull
|
||||
|
||||
import trimesh
|
||||
# from meas_definitions import measurements_definitions, measures_vertex
|
||||
from loguru import logger
|
||||
from star.pytorch.star import STAR
|
||||
from star.config import cfg as star_cfg
|
||||
|
||||
from mesh_mesh_intersection import MeshMeshIntersection
|
||||
from body_measurements import ChestWaistHipsMeasurements
|
||||
|
||||
|
||||
def get_plane_at_height(h):
|
||||
verts = np.array([[-1., h, -1], [1, h, -1], [1, h, 1], [-1, h, 1]])
|
||||
faces = np.array([[0, 1, 2], [0, 2, 3]])
|
||||
|
||||
normal = np.array([0.0, 1.0, 0.0])
|
||||
return verts, faces, (verts[0], normal)
|
||||
|
||||
|
||||
def main(
|
||||
model_folder,
|
||||
model_type='smplx',
|
||||
ext='npz',
|
||||
gender='neutral',
|
||||
plot_joints=False,
|
||||
num_betas=10,
|
||||
sample_shape=False,
|
||||
num_expression_coeffs=10,
|
||||
plotting_module='pyrender',
|
||||
num_samples=1,
|
||||
use_face_contour=False,
|
||||
meas_definition_path: str = 'data/measurement_defitions.yaml',
|
||||
meas_vertices_path: str = 'data/smpl_measurement_vertices.yaml',
|
||||
):
|
||||
|
||||
device = torch.device('cuda')
|
||||
|
||||
meas_module = ChestWaistHipsMeasurements(
|
||||
meas_definition_path=meas_definition_path,
|
||||
meas_vertices_path=meas_vertices_path,
|
||||
# '$HOME/workspace/caesar_betas_exps/measurement_defitions.yaml',
|
||||
# '$HOME/workspace/caesar_betas_exps/smpl_measurement_vertices.yaml',
|
||||
)
|
||||
# meas_module = ChestWaistHipsMeasurements(
|
||||
# '$HOME/workspace/caesar_betas_exps/measurement_defitions.yaml',
|
||||
# '$HOME/workspace/caesar_betas_exps/smplx_measurements.yaml',
|
||||
# )
|
||||
meas_module = meas_module.to(device=device)
|
||||
dtype = torch.float32
|
||||
|
||||
trans, pose = None, None
|
||||
if model_type == 'star':
|
||||
star_cfg.path_male_star = osp.expandvars(
|
||||
'$HOME/workspace/body_models/star/STAR_MALE.npz')
|
||||
star_cfg.path_female_star = osp.expandvars(
|
||||
'$HOME/workspace/body_models/star/STAR_FEMALE.npz')
|
||||
model = STAR(gender=gender, num_betas=num_betas)
|
||||
trans = torch.zeros([num_samples, 3], dtype=dtype, device=device)
|
||||
pose = torch.zeros([num_samples, 72], dtype=dtype, device=device)
|
||||
else:
|
||||
model = smplx.build_layer(
|
||||
model_folder, model_type=model_type,
|
||||
gender=gender, use_face_contour=use_face_contour,
|
||||
num_betas=num_betas,
|
||||
num_expression_coeffs=num_expression_coeffs,
|
||||
ext=ext)
|
||||
|
||||
model = model.to(device=device)
|
||||
|
||||
# meas_to_vis = ['CW_p', 'BW_p', 'IW_p']
|
||||
# meas_to_vis = ['CW_p']
|
||||
|
||||
if sample_shape:
|
||||
# betas = shape_dist.sample().reshape(1, -1)
|
||||
betas = torch.randn(
|
||||
[num_samples, model.num_betas],
|
||||
requires_grad=True,
|
||||
dtype=torch.float32, device=device)
|
||||
else:
|
||||
betas = torch.zeros(
|
||||
[num_samples, model.num_betas],
|
||||
requires_grad=True, dtype=torch.float32, device=device)
|
||||
|
||||
model.zero_grad()
|
||||
|
||||
if model_type == 'star':
|
||||
vertices = model(pose=pose, trans=trans, betas=betas)
|
||||
model_tris = vertices[:, model.faces]
|
||||
vertices = vertices.detach().cpu().numpy()
|
||||
faces = model.faces.detach().cpu().numpy()
|
||||
else:
|
||||
output = model(betas=betas, return_verts=True)
|
||||
vertices = output.vertices.detach().cpu().numpy()
|
||||
model_tris = output.vertices[:, model.faces_tensor]
|
||||
faces = model.faces
|
||||
output = meas_module(model_tris)
|
||||
|
||||
# loss = sum(v.pow(2) for v in output['chest']['value'])
|
||||
|
||||
for n in range(num_samples):
|
||||
|
||||
mesh = o3d.geometry.TriangleMesh()
|
||||
mesh.vertices = o3d.utility.Vector3dVector(vertices[n])
|
||||
mesh.triangles = o3d.utility.Vector3iVector(faces)
|
||||
mesh.compute_vertex_normals()
|
||||
|
||||
colors = np.ones_like(vertices[n]) * [0.3, 0.3, 0.3]
|
||||
mesh.vertex_colors = o3d.utility.Vector3dVector(colors)
|
||||
|
||||
geometry = []
|
||||
geometry.append(mesh)
|
||||
|
||||
for meas_name in output:
|
||||
pcl = o3d.geometry.PointCloud()
|
||||
if 'points' not in output[meas_name]:
|
||||
continue
|
||||
|
||||
points = output[meas_name]['points']
|
||||
if isinstance(points, (tuple, list)):
|
||||
points = torch.stack(points)
|
||||
if torch.is_tensor(points):
|
||||
points = points.detach().cpu().numpy()
|
||||
points = points.reshape(-1, 3)
|
||||
|
||||
pcl.points = o3d.utility.Vector3dVector(points)
|
||||
pcl.paint_uniform_color([1.0, 0.0, 0.0])
|
||||
geometry.append(pcl)
|
||||
|
||||
lineset = o3d.geometry.LineSet()
|
||||
line_ids = np.arange(len(points)).reshape(-1, 2)
|
||||
lineset.points = o3d.utility.Vector3dVector(points)
|
||||
lineset.lines = o3d.utility.Vector2iVector(line_ids)
|
||||
lineset.paint_uniform_color([0.0, 0.0, 0.0])
|
||||
geometry.append(lineset)
|
||||
|
||||
o3d.visualization.draw_geometries(geometry)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='SMPL-X Demo')
|
||||
|
||||
parser.add_argument('--model-folder', required=True, type=str,
|
||||
help='The path to the model folder')
|
||||
parser.add_argument('--model-type', default='smplx', type=str,
|
||||
choices=['smpl', 'smplh', 'smplx', 'mano', 'flame',
|
||||
'star', ],
|
||||
help='The type of model to load')
|
||||
parser.add_argument('--gender', type=str, default='neutral',
|
||||
help='The gender of the model')
|
||||
parser.add_argument('--num-betas', default=10, type=int,
|
||||
dest='num_betas',
|
||||
help='Number of shape coefficients.')
|
||||
parser.add_argument('--num-expression-coeffs', default=10, type=int,
|
||||
dest='num_expression_coeffs',
|
||||
help='Number of expression coefficients.')
|
||||
parser.add_argument('--plotting-module', type=str, default='pyrender',
|
||||
dest='plotting_module',
|
||||
choices=['pyrender', 'matplotlib', 'open3d'],
|
||||
help='The module to use for plotting the result')
|
||||
parser.add_argument('--ext', type=str, default='npz',
|
||||
help='Which extension to use for loading')
|
||||
parser.add_argument('--plot-joints', default=False,
|
||||
type=lambda arg: arg.lower() in ['true', '1'],
|
||||
help='The path to the model folder')
|
||||
parser.add_argument('--sample-shape', default=False,
|
||||
dest='sample_shape',
|
||||
type=lambda arg: arg.lower() in ['true', '1'],
|
||||
help='Sample a random shape')
|
||||
parser.add_argument('--sample-expression', default=True,
|
||||
dest='sample_expression',
|
||||
type=lambda arg: arg.lower() in ['true', '1'],
|
||||
help='Sample a random expression')
|
||||
parser.add_argument('--use-face-contour', default=False,
|
||||
type=lambda arg: arg.lower() in ['true', '1'],
|
||||
help='Compute the contour of the face')
|
||||
parser.add_argument('--num-samples', default=1, type=int,
|
||||
dest='num_samples',
|
||||
help='Number of samples to draw.')
|
||||
parser.add_argument('--meas-definition-path',
|
||||
dest='meas_definition_path',
|
||||
default='data/measurement_defitions.yaml',
|
||||
type=str,
|
||||
help='The definitions of the measurements')
|
||||
parser.add_argument('--meas-vertices-path', dest='meas_vertices_path',
|
||||
type=str,
|
||||
default='data/smpl_measurement_vertices.yaml',
|
||||
help='The indices of the vertices used for the'
|
||||
' the measurements')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
model_folder = osp.expanduser(osp.expandvars(args.model_folder))
|
||||
model_type = args.model_type
|
||||
plot_joints = args.plot_joints
|
||||
use_face_contour = args.use_face_contour
|
||||
gender = args.gender
|
||||
ext = args.ext
|
||||
plotting_module = args.plotting_module
|
||||
num_betas = args.num_betas
|
||||
num_expression_coeffs = args.num_expression_coeffs
|
||||
sample_shape = args.sample_shape
|
||||
sample_expression = args.sample_expression
|
||||
num_samples = args.num_samples
|
||||
meas_definition_path = args.meas_definition_path
|
||||
meas_vertices_path = args.meas_vertices_path
|
||||
|
||||
main(model_folder, model_type, ext=ext,
|
||||
gender=gender, plot_joints=plot_joints,
|
||||
num_betas=num_betas,
|
||||
num_samples=num_samples,
|
||||
num_expression_coeffs=num_expression_coeffs,
|
||||
sample_shape=sample_shape,
|
||||
plotting_module=plotting_module,
|
||||
meas_definition_path=meas_definition_path,
|
||||
meas_vertices_path=meas_vertices_path,
|
||||
use_face_contour=use_face_contour,
|
||||
)
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
* holder of all proprietary rights on this computer program.
|
||||
* You can only use this computer program if you have closed
|
||||
* a license agreement with MPG or you get the right to use the computer
|
||||
* program from someone who is authorized to grant you that right.
|
||||
* Any use of the computer program without a valid license is prohibited and
|
||||
* liable to prosecution.
|
||||
*
|
||||
* Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
* der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
* for Intelligent Systems. All rights reserved.
|
||||
*
|
||||
* @author Vasileios Choutas
|
||||
* Contact: vassilis.choutas@tuebingen.mpg.de
|
||||
* Contact: ps-license@tuebingen.mpg.de
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AABB_H
|
||||
#define AABB_H
|
||||
|
||||
#include <cuda.h>
|
||||
#include "device_launch_parameters.h"
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
#include "defs.hpp"
|
||||
#include "math_utils.hpp"
|
||||
#include "double_vec_ops.hpp"
|
||||
#include "helper_math.h"
|
||||
|
||||
|
||||
template <typename T>
|
||||
__align__(32)
|
||||
struct AABB {
|
||||
public:
|
||||
__host__ __device__ AABB() {
|
||||
min_t.x = std::is_same<T, float>::value ? FLT_MAX : DBL_MAX;
|
||||
min_t.y = std::is_same<T, float>::value ? FLT_MAX : DBL_MAX;
|
||||
min_t.z = std::is_same<T, float>::value ? FLT_MAX : DBL_MAX;
|
||||
|
||||
max_t.x = std::is_same<T, float>::value ? -FLT_MAX : -DBL_MAX;
|
||||
max_t.y = std::is_same<T, float>::value ? -FLT_MAX : -DBL_MAX;
|
||||
max_t.z = std::is_same<T, float>::value ? -FLT_MAX : -DBL_MAX;
|
||||
};
|
||||
|
||||
__host__ __device__ AABB(const vec3<T> &min_t, const vec3<T> &max_t)
|
||||
: min_t(min_t), max_t(max_t){};
|
||||
__host__ __device__ ~AABB(){};
|
||||
|
||||
__host__ __device__ AABB(T min_t_x, T min_t_y, T min_t_z, T max_t_x,
|
||||
T max_t_y, T max_t_z) {
|
||||
min_t.x = min_t_x;
|
||||
min_t.y = min_t_y;
|
||||
min_t.z = min_t_z;
|
||||
max_t.x = max_t_x;
|
||||
max_t.y = max_t_y;
|
||||
max_t.z = max_t_z;
|
||||
}
|
||||
|
||||
__host__ __device__ AABB<T> operator+(const AABB<T> &bbox2) const {
|
||||
return AABB<T>(
|
||||
min(this->min_t.x, bbox2.min_t.x), min(this->min_t.y, bbox2.min_t.y),
|
||||
min(this->min_t.z, bbox2.min_t.z), max(this->max_t.x, bbox2.max_t.x),
|
||||
max(this->max_t.y, bbox2.max_t.y), max(this->max_t.z, bbox2.max_t.z));
|
||||
};
|
||||
|
||||
__host__ __device__ T distance(const vec3<T> point) const {
|
||||
};
|
||||
|
||||
__host__ __device__ T operator*(const AABB<T> &bbox2) const {
|
||||
return (min(this->max_t.x, bbox2.max_t.x) -
|
||||
max(this->min_t.x, bbox2.min_t.x)) *
|
||||
(min(this->max_t.y, bbox2.max_t.y) -
|
||||
max(this->min_t.y, bbox2.min_t.y)) *
|
||||
(min(this->max_t.z, bbox2.max_t.z) -
|
||||
max(this->min_t.z, bbox2.min_t.z));
|
||||
};
|
||||
|
||||
vec3<T> min_t;
|
||||
vec3<T> max_t;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
std::ostream &operator<<(std::ostream &os, const AABB<T> &x) {
|
||||
os << x.min_t << std::endl;
|
||||
os << x.max_t << std::endl;
|
||||
return os;
|
||||
}
|
||||
|
||||
template <typename T> struct MergeAABB {
|
||||
|
||||
public:
|
||||
__host__ __device__ MergeAABB(){};
|
||||
|
||||
// Create an operator Struct that will be used by thrust::reduce
|
||||
// to calculate the bounding box of the scene.
|
||||
__host__ __device__ AABB<T> operator()(const AABB<T> &bbox1,
|
||||
const AABB<T> &bbox2) {
|
||||
return bbox1 + bbox2;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
__forceinline__
|
||||
__host__ __device__ T pointToAABBDistance(vec3<T> point, const AABB<T>& bbox ) {
|
||||
T diff_x = point.x - clamp<T>(point.x, bbox.min_t.x, bbox.max_t.x);
|
||||
T diff_y = point.y - clamp<T>(point.y, bbox.min_t.y, bbox.max_t.y);
|
||||
T diff_z = point.z - clamp<T>(point.z, bbox.min_t.z, bbox.max_t.z);
|
||||
|
||||
return diff_x * diff_x + diff_y * diff_y + diff_z * diff_z;
|
||||
}
|
||||
|
||||
|
||||
#endif // ifndef AABB_H
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
* holder of all proprietary rights on this computer program.
|
||||
* You can only use this computer program if you have closed
|
||||
* a license agreement with MPG or you get the right to use the computer
|
||||
* program from someone who is authorized to grant you that right.
|
||||
* Any use of the computer program without a valid license is prohibited and
|
||||
* liable to prosecution.
|
||||
*
|
||||
* Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
* der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
* for Intelligent Systems. All rights reserved.
|
||||
*
|
||||
* @author Vasileios Choutas
|
||||
* Contact: vassilis.choutas@tuebingen.mpg.de
|
||||
* Contact: ps-license@tuebingen.mpg.de
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DEFINITIONS_H
|
||||
#define DEFINITIONS_H
|
||||
|
||||
#include <cuda.h>
|
||||
#include "device_launch_parameters.h"
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
template <typename T>
|
||||
using vec3 = typename std::conditional<std::is_same<T, float>::value, float3,
|
||||
double3>::type;
|
||||
|
||||
template <typename T>
|
||||
using vec2 = typename std::conditional<std::is_same<T, float>::value, float2,
|
||||
double2>::type;
|
||||
|
||||
float3 make_float3(double3 vec) {
|
||||
return make_float3(vec.x, vec.y, vec.z);
|
||||
}
|
||||
|
||||
float3 make_float3(double x, double y, double z) {
|
||||
return make_float3(x, y, z);
|
||||
}
|
||||
|
||||
double3 make_double3(float3 vec) {
|
||||
return make_double3(vec.x, vec.y, vec.z);
|
||||
}
|
||||
|
||||
double3 make_double3(float x, float y, float z) {
|
||||
return make_double3(x, y, z);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__host__ __device__
|
||||
vec3<T> make_vec3(T x, T y, T z) {
|
||||
}
|
||||
|
||||
template <>
|
||||
__host__ __device__
|
||||
vec3<float> make_vec3(float x, float y, float z) {
|
||||
return make_float3(static_cast<float>(x), static_cast<float>(y), static_cast<float>(z));
|
||||
}
|
||||
|
||||
template <>
|
||||
__host__ __device__
|
||||
vec3<double> make_vec3(double x, double y, double z) {
|
||||
return make_double3(static_cast<double>(x), static_cast<double>(y), static_cast<double>(z));
|
||||
}
|
||||
|
||||
#endif // ifndef DEFINITIONS_H
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
* holder of all proprietary rights on this computer program.
|
||||
* You can only use this computer program if you have closed
|
||||
* a license agreement with MPG or you get the right to use the computer
|
||||
* program from someone who is authorized to grant you that right.
|
||||
* Any use of the computer program without a valid license is prohibited and
|
||||
* liable to prosecution.
|
||||
*
|
||||
* Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
* der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
* for Intelligent Systems. All rights reserved.
|
||||
*
|
||||
* @author Vasileios Choutas
|
||||
* Contact: vassilis.choutas@tuebingen.mpg.de
|
||||
* Contact: ps-license@tuebingen.mpg.de
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DOUBLE_VEC_OPS_H
|
||||
#define DOUBLE_VEC_OPS_H
|
||||
|
||||
#include "cuda_runtime.h"
|
||||
|
||||
inline __host__ __device__ double2 operator+(double2 a, double2 b) {
|
||||
return make_double2(a.x + b.x, a.y + b.y);
|
||||
}
|
||||
|
||||
|
||||
inline __host__ __device__ double3 operator+(double3 a, double3 b) {
|
||||
return make_double3(a.x + b.x, a.y + b.y, a.z + b.z);
|
||||
}
|
||||
|
||||
inline __host__ __device__ void operator/=(double2 &a, double2 b) {
|
||||
a.x /= b.x;
|
||||
a.y /= b.y;
|
||||
}
|
||||
|
||||
inline __host__ __device__ double2 operator/(double2 a, double b) {
|
||||
return make_double2(a.x / b, a.y / b);
|
||||
}
|
||||
|
||||
inline __host__ __device__ double3 operator/(double3 a, double3 b) {
|
||||
return make_double3(a.x / b.x, a.y / b.y, a.z / b.z);
|
||||
}
|
||||
|
||||
|
||||
inline __host__ __device__ double3 operator*(double a, double3 b) {
|
||||
return make_double3(a * b.x, a * b.y, a * b.z);
|
||||
}
|
||||
|
||||
inline __host__ __device__ double3 operator*(double3 a, double3 b) {
|
||||
return make_double3(a.x * b.x, a.y * b.y, a.z * b.z);
|
||||
}
|
||||
|
||||
inline __host__ __device__ void operator/=(double3 &a, double3 b) {
|
||||
a.x /= b.x;
|
||||
a.y /= b.y;
|
||||
a.z /= b.z;
|
||||
}
|
||||
|
||||
inline __host__ __device__ double3 operator/(double3 a, double b) {
|
||||
return make_double3(a.x / b, a.y / b, a.z / b);
|
||||
}
|
||||
|
||||
inline __host__ __device__ double dot(double2 a, double2 b) {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
inline __host__ __device__ double dot(double3 a, double3 b) {
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z;
|
||||
}
|
||||
|
||||
inline __host__ __device__ double3 cross(double3 a, double3 b)
|
||||
{
|
||||
return make_double3(a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x);
|
||||
}
|
||||
|
||||
inline __host__ __device__ double2 operator-(double2 a, double2 b)
|
||||
{
|
||||
return make_double2(a.x - b.x, a.y - b.y);
|
||||
}
|
||||
inline __host__ __device__ void operator-=(double2 &a, double2 b)
|
||||
{
|
||||
a.x -= b.x;
|
||||
a.y -= b.y;
|
||||
}
|
||||
inline __host__ __device__ double2 operator-(double2 a, double b)
|
||||
{
|
||||
return make_double2(a.x - b, a.y - b);
|
||||
}
|
||||
inline __host__ __device__ double2 operator-(double b, double2 a)
|
||||
{
|
||||
return make_double2(b - a.x, b - a.y);
|
||||
}
|
||||
|
||||
inline __host__ __device__ double3 operator-(double3 a, double3 b)
|
||||
{
|
||||
return make_double3(a.x - b.x, a.y - b.y, a.z - b.z);
|
||||
}
|
||||
inline __host__ __device__ void operator-=(double3 &a, double3 b)
|
||||
{
|
||||
a.x -= b.x;
|
||||
a.y -= b.y;
|
||||
a.z -= b.z;
|
||||
}
|
||||
inline __host__ __device__ double3 operator-(double3 a, double b)
|
||||
{
|
||||
return make_double3(a.x - b, a.y - b, a.z - b);
|
||||
}
|
||||
inline __host__ __device__ double3 operator-(double b, double3 a)
|
||||
{
|
||||
return make_double3(b - a.x, b - a.y, b - a.z);
|
||||
}
|
||||
|
||||
#endif // ifndef DOUBLE_VEC_OPS_H
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
* holder of all proprietary rights on this computer program.
|
||||
* You can only use this computer program if you have closed
|
||||
* a license agreement with MPG or you get the right to use the computer
|
||||
* program from someone who is authorized to grant you that right.
|
||||
* Any use of the computer program without a valid license is prohibited and
|
||||
* liable to prosecution.
|
||||
*
|
||||
* Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
* der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
* for Intelligent Systems. All rights reserved.
|
||||
*
|
||||
* @author Vasileios Choutas
|
||||
* Contact: vassilis.choutas@tuebingen.mpg.de
|
||||
* Contact: ps-license@tuebingen.mpg.de
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DOUBLE_VEC_OPS_H
|
||||
#define DOUBLE_VEC_OPS_H
|
||||
|
||||
#include "cuda_runtime.h"
|
||||
|
||||
inline __host__ __device__ double2 operator+(double2 a, double2 b) {
|
||||
return make_double2(a.x + b.x, a.y + b.y);
|
||||
}
|
||||
|
||||
|
||||
inline __host__ __device__ double3 operator+(double3 a, double3 b) {
|
||||
return make_double3(a.x + b.x, a.y + b.y, a.z + b.z);
|
||||
}
|
||||
|
||||
inline __host__ __device__ void operator/=(double2 &a, double2 b) {
|
||||
a.x /= b.x;
|
||||
a.y /= b.y;
|
||||
}
|
||||
|
||||
inline __host__ __device__ double2 operator/(double2 a, double b) {
|
||||
return make_double2(a.x / b, a.y / b);
|
||||
}
|
||||
|
||||
inline __host__ __device__ double3 operator/(double3 a, double3 b) {
|
||||
return make_double3(a.x / b.x, a.y / b.y, a.z / b.z);
|
||||
}
|
||||
|
||||
|
||||
inline __host__ __device__ double3 operator*(double a, double3 b) {
|
||||
return make_double3(a * b.x, a * b.y, a * b.z);
|
||||
}
|
||||
|
||||
inline __host__ __device__ double3 operator*(double3 a, double3 b) {
|
||||
return make_double3(a.x * b.x, a.y * b.y, a.z * b.z);
|
||||
}
|
||||
|
||||
inline __host__ __device__ void operator/=(double3 &a, double3 b) {
|
||||
a.x /= b.x;
|
||||
a.y /= b.y;
|
||||
a.z /= b.z;
|
||||
}
|
||||
|
||||
inline __host__ __device__ double3 operator/(double3 a, double b) {
|
||||
return make_double3(a.x / b, a.y / b, a.z / b);
|
||||
}
|
||||
|
||||
inline __host__ __device__ double dot(double2 a, double2 b) {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
inline __host__ __device__ double dot(double3 a, double3 b) {
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z;
|
||||
}
|
||||
|
||||
inline __host__ __device__ double3 cross(double3 a, double3 b)
|
||||
{
|
||||
return make_double3(a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x);
|
||||
}
|
||||
|
||||
inline __host__ __device__ double2 operator-(double2 a, double2 b)
|
||||
{
|
||||
return make_double2(a.x - b.x, a.y - b.y);
|
||||
}
|
||||
inline __host__ __device__ void operator-=(double2 &a, double2 b)
|
||||
{
|
||||
a.x -= b.x;
|
||||
a.y -= b.y;
|
||||
}
|
||||
inline __host__ __device__ double2 operator-(double2 a, double b)
|
||||
{
|
||||
return make_double2(a.x - b, a.y - b);
|
||||
}
|
||||
inline __host__ __device__ double2 operator-(double b, double2 a)
|
||||
{
|
||||
return make_double2(b - a.x, b - a.y);
|
||||
}
|
||||
|
||||
inline __host__ __device__ double3 operator-(double3 a, double3 b)
|
||||
{
|
||||
return make_double3(a.x - b.x, a.y - b.y, a.z - b.z);
|
||||
}
|
||||
inline __host__ __device__ void operator-=(double3 &a, double3 b)
|
||||
{
|
||||
a.x -= b.x;
|
||||
a.y -= b.y;
|
||||
a.z -= b.z;
|
||||
}
|
||||
inline __host__ __device__ double3 operator-(double3 a, double b)
|
||||
{
|
||||
return make_double3(a.x - b, a.y - b, a.z - b);
|
||||
}
|
||||
inline __host__ __device__ double3 operator-(double b, double3 a)
|
||||
{
|
||||
return make_double3(b - a.x, b - a.y, b - a.z);
|
||||
}
|
||||
|
||||
#endif // ifndef DOUBLE_VEC_OPS_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
* holder of all proprietary rights on this computer program.
|
||||
* You can only use this computer program if you have closed
|
||||
* a license agreement with MPG or you get the right to use the computer
|
||||
* program from someone who is authorized to grant you that right.
|
||||
* Any use of the computer program without a valid license is prohibited and
|
||||
* liable to prosecution.
|
||||
*
|
||||
* Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
* der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
* for Intelligent Systems. All rights reserved.
|
||||
*
|
||||
* @author Vasileios Choutas
|
||||
* Contact: vassilis.choutas@tuebingen.mpg.de
|
||||
* Contact: ps-license@tuebingen.mpg.de
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MATH_UTILS_H
|
||||
#define MATH_UTILS_H
|
||||
|
||||
#include <cuda.h>
|
||||
#include "device_launch_parameters.h"
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
#include "defs.hpp"
|
||||
#include "double_vec_ops.hpp"
|
||||
#include "helper_math.h"
|
||||
|
||||
|
||||
template <typename T>
|
||||
__host__ __device__ T sign(T x) {
|
||||
return x > 0 ? 1 : (x < 0 ? -1 : 0);
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
__host__ __device__ __forceinline__ float vec_abs_diff(const vec3<T> &vec1,
|
||||
const vec3<T> &vec2) {
|
||||
return fabs(vec1.x - vec2.x) + fabs(vec1.y - vec2.y) + fabs(vec1.z - vec2.z);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__host__ __device__ __forceinline__ float vec_sq_diff(const vec3<T> &vec1,
|
||||
const vec3<T> &vec2) {
|
||||
return dot(vec1 - vec2, vec1 - vec2);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__host__ __device__ __forceinline__ T clamp(T value, T min_value, T max_value) {
|
||||
return min(max(value, min_value), max_value);
|
||||
}
|
||||
|
||||
template <typename T> __host__ __device__ T dot2(vec3<T> v) {
|
||||
return dot(v, v);
|
||||
}
|
||||
#endif // ifndef MATH_UTILS_H
|
||||
@@ -0,0 +1,704 @@
|
||||
/* Triangle/triangle intersection test routine,
|
||||
* by Tomas Moller, 1997.
|
||||
* See article "A Fast Triangle-Triangle Intersection Test",
|
||||
* Journal of Graphics Tools, 2(2), 1997
|
||||
* updated: 2001-06-20 (added line of intersection)
|
||||
*
|
||||
* int tri_tri_intersect(float V0[3],float V1[3],float V2[3],
|
||||
* float U0[3],float U1[3],float U2[3])
|
||||
*
|
||||
* parameters: vertices of triangle 1: V0,V1,V2
|
||||
* vertices of triangle 2: U0,U1,U2
|
||||
* result : returns 1 if the triangles intersect, otherwise 0
|
||||
*
|
||||
* Here is a version withouts divisions (a little faster)
|
||||
* int NoDivTriTriIsect(float V0[3],float V1[3],float V2[3],
|
||||
* float U0[3],float U1[3],float U2[3]);
|
||||
*
|
||||
* This version computes the line of intersection as well (if they are not
|
||||
*coplanar): int tri_tri_intersect_with_isectline(float V0[3],float V1[3],float
|
||||
*V2[3], float U0[3],float U1[3],float U2[3],int *coplanar, float
|
||||
*isect_point1[3],float isect_point2[3]); coplanar returns whether the tris are
|
||||
*coplanar isect_point1, isect_point2 are the endpoints of the line of
|
||||
*intersection
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "defs.h"
|
||||
#include "double_vec_ops.hpp"
|
||||
#include "helper_math.h"
|
||||
|
||||
#define FABS(x) ((float)fabs(x)) /* implement as is fastest on your machine */
|
||||
/* if USE_EPSILON_TEST is true then we do a check:
|
||||
if |dv|<EPSILON then dv=0.0;
|
||||
else no check is done (which is less robust)
|
||||
*/
|
||||
#define USE_EPSILON_TEST TRUE
|
||||
#define EPSILON 0.000001
|
||||
|
||||
template <typename T> __host__ __device__ inline void sort(T *a, T *b) {
|
||||
if (a > b) {
|
||||
T c;
|
||||
c = *a;
|
||||
*a = *b;
|
||||
b = *c;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
template <typename T> __host__ __device__ inline int sort(T *a, T *b) {
|
||||
if (a > b) {
|
||||
T c;
|
||||
c = *a;
|
||||
*a = *b;
|
||||
b = *c;
|
||||
return 1;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define ISECT(VV0, VV1, VV2, D0, D1, D2, isect0, isect1) \
|
||||
isect0 = VV0 + (VV1 - VV0) * D0 / (D0 - D1); \
|
||||
isect1 = VV0 + (VV2 - VV0) * D0 / (D0 - D2);
|
||||
|
||||
#define COMPUTE_INTERVALS(VV0, VV1, VV2, D0, D1, D2, D0D1, D0D2, isect0, \
|
||||
isect1) \
|
||||
if (D0D1 > 0.0f) { \
|
||||
/* here we know that D0D2<=0.0 */ \
|
||||
/* that is D0, D1 are on the same side, D2 on the other or on the plane */ \
|
||||
ISECT(VV2, VV0, VV1, D2, D0, D1, isect0, isect1); \
|
||||
} else if (D0D2 > 0.0f) { \
|
||||
/* here we know that d0d1<=0.0 */ \
|
||||
ISECT(VV1, VV0, VV2, D1, D0, D2, isect0, isect1); \
|
||||
} else if (D1 * D2 > 0.0f || D0 != 0.0f) { \
|
||||
/* here we know that d0d1<=0.0 or that D0!=0.0 */ \
|
||||
ISECT(VV0, VV1, VV2, D0, D1, D2, isect0, isect1); \
|
||||
} else if (D1 != 0.0f) { \
|
||||
ISECT(VV1, VV0, VV2, D1, D0, D2, isect0, isect1); \
|
||||
} else if (D2 != 0.0f) { \
|
||||
ISECT(VV2, VV0, VV1, D2, D0, D1, isect0, isect1); \
|
||||
} else { \
|
||||
/* triangles are coplanar */ \
|
||||
return coplanar_tri_tri(N1, V0, V1, V2, U0, U1, U2); \
|
||||
}
|
||||
template <typename T>
|
||||
inline void compute_intervals(
|
||||
)
|
||||
|
||||
/* this edge to edge test is based on Franlin Antonio's gem:
|
||||
"Faster Line Segment Intersection", in Graphics Gems III,
|
||||
pp. 199-202 */
|
||||
#define EDGE_EDGE_TEST(V0, U0, U1) \
|
||||
Bx = U0[i0] - U1[i0]; \
|
||||
By = U0[i1] - U1[i1]; \
|
||||
Cx = V0[i0] - U0[i0]; \
|
||||
Cy = V0[i1] - U0[i1]; \
|
||||
f = Ay * Bx - Ax * By; \
|
||||
d = By * Cx - Bx * Cy; \
|
||||
if ((f > 0 && d >= 0 && d <= f) || (f < 0 && d <= 0 && d >= f)) { \
|
||||
e = Ax * Cy - Ay * Cx; \
|
||||
if (f > 0) { \
|
||||
if (e >= 0 && e <= f) \
|
||||
return 1; \
|
||||
} else { \
|
||||
if (e <= 0 && e >= f) \
|
||||
return 1; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define EDGE_AGAINST_TRI_EDGES(V0, V1, U0, U1, U2) \
|
||||
{ \
|
||||
float Ax, Ay, Bx, By, Cx, Cy, e, d, f; \
|
||||
Ax = V1[i0] - V0[i0]; \
|
||||
Ay = V1[i1] - V0[i1]; \
|
||||
/* test edge U0,U1 against V0,V1 */ \
|
||||
EDGE_EDGE_TEST(V0, U0, U1); \
|
||||
/* test edge U1,U2 against V0,V1 */ \
|
||||
EDGE_EDGE_TEST(V0, U1, U2); \
|
||||
/* test edge U2,U1 against V0,V1 */ \
|
||||
EDGE_EDGE_TEST(V0, U2, U0); \
|
||||
}
|
||||
|
||||
#define POINT_IN_TRI(V0, U0, U1, U2) \
|
||||
{ \
|
||||
float a, b, c, d0, d1, d2; \
|
||||
/* is T1 completly inside T2? */ \
|
||||
/* check if V0 is inside tri(U0,U1,U2) */ \
|
||||
a = U1[i1] - U0[i1]; \
|
||||
b = -(U1[i0] - U0[i0]); \
|
||||
c = -a * U0[i0] - b * U0[i1]; \
|
||||
d0 = a * V0[i0] + b * V0[i1] + c; \
|
||||
\
|
||||
a = U2[i1] - U1[i1]; \
|
||||
b = -(U2[i0] - U1[i0]); \
|
||||
c = -a * U1[i0] - b * U1[i1]; \
|
||||
d1 = a * V0[i0] + b * V0[i1] + c; \
|
||||
\
|
||||
a = U0[i1] - U2[i1]; \
|
||||
b = -(U0[i0] - U2[i0]); \
|
||||
c = -a * U2[i0] - b * U2[i1]; \
|
||||
d2 = a * V0[i0] + b * V0[i1] + c; \
|
||||
if (d0 * d1 > 0.0) { \
|
||||
if (d0 * d2 > 0.0) \
|
||||
return 1; \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
__host__ __device__
|
||||
bool point_in_tri(vec3<T> V0, vec3<T>U0, vec3<T>U1, vec3<T>U2)
|
||||
{
|
||||
T a, b, c, d0, d1, d2;
|
||||
/* is T1 completly inside T2? */
|
||||
/* check if V0 is inside tri(U0,U1,U2) */
|
||||
a = U1[i1] - U0[i1];
|
||||
b = -(U1[i0] - U0[i0]);
|
||||
c = -a * U0[i0] - b * U0[i1];
|
||||
d0 = a * V0[i0] + b * V0[i1] + c;
|
||||
|
||||
a = U2[i1] - U1[i1];
|
||||
b = -(U2[i0] - U1[i0]);
|
||||
c = -a * U1[i0] - b * U1[i1];
|
||||
d1 = a * V0[i0] + b * V0[i1] + c;
|
||||
|
||||
a = U0[i1] - U2[i1];
|
||||
b = -(U0[i0] - U2[i0]);
|
||||
c = -a * U2[i0] - b * U2[i1];
|
||||
d2 = a * V0[i0] + b * V0[i1] + c;
|
||||
if (d0 * d1 > 0.0) {
|
||||
if (d0 * d2 > 0.0)
|
||||
return true;
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
__host__ __device__
|
||||
bool coplanar_tri_tri(
|
||||
vec3<T> N, vec3<T> V0, vec3<T> V1, vec3<T> V2,
|
||||
vec3<T> U0, vec3<T> U1, vec3<T> U2) {
|
||||
vec3<T> A;
|
||||
short i0, i1;
|
||||
/* first project onto an axis-aligned plane, that maximizes the area */
|
||||
/* of the triangles, compute indices: i0,i1. */
|
||||
A.x = fabs(N.x);
|
||||
A.y = fabs(N.y);
|
||||
A.z = fabs(N.z);
|
||||
if (A.x > A.y) {
|
||||
if (A.x > A.z) {
|
||||
i0 = 1; /* A[0] is greatest */
|
||||
i1 = 2;
|
||||
} else {
|
||||
i0 = 0; /* A[2] is greatest */
|
||||
i1 = 1;
|
||||
}
|
||||
} else /* A[0]<=A[1] */
|
||||
{
|
||||
if (A.z > A.y) {
|
||||
i0 = 0; /* A[2] is greatest */
|
||||
i1 = 1;
|
||||
} else {
|
||||
i0 = 0; /* A[1] is greatest */
|
||||
i1 = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* test all edges of triangle 1 against the edges of triangle 2 */
|
||||
EDGE_AGAINST_TRI_EDGES(V0, V1, U0, U1, U2);
|
||||
EDGE_AGAINST_TRI_EDGES(V1, V2, U0, U1, U2);
|
||||
EDGE_AGAINST_TRI_EDGES(V2, V0, U0, U1, U2);
|
||||
|
||||
/* finally, test if tri1 is totally contained in tri2 or vice versa */
|
||||
POINT_IN_TRI(V0, U0, U1, U2);
|
||||
POINT_IN_TRI(U0, V0, V1, V2);
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
int tri_tri_intersect(float V0[3], float V1[3], float V2[3], float U0[3],
|
||||
float U1[3], float U2[3]) {
|
||||
float E1[3], E2[3];
|
||||
float N1[3], N2[3], d1, d2;
|
||||
float du0, du1, du2, dv0, dv1, dv2;
|
||||
float D[3];
|
||||
float isect1[2], isect2[2];
|
||||
float du0du1, du0du2, dv0dv1, dv0dv2;
|
||||
short index;
|
||||
float vp0, vp1, vp2;
|
||||
float up0, up1, up2;
|
||||
float b, c, max;
|
||||
|
||||
/* compute plane equation of triangle(V0,V1,V2) */
|
||||
SUB(E1, V1, V0);
|
||||
SUB(E2, V2, V0);
|
||||
CROSS(N1, E1, E2);
|
||||
d1 = -DOT(N1, V0);
|
||||
/* plane equation 1: N1.X+d1=0 */
|
||||
|
||||
/* put U0,U1,U2 into plane equation 1 to compute signed distances to the
|
||||
* plane*/
|
||||
du0 = DOT(N1, U0) + d1;
|
||||
du1 = DOT(N1, U1) + d1;
|
||||
du2 = DOT(N1, U2) + d1;
|
||||
|
||||
/* coplanarity robustness check */
|
||||
#if USE_EPSILON_TEST == TRUE
|
||||
if (fabs(du0) < EPSILON)
|
||||
du0 = 0.0;
|
||||
if (fabs(du1) < EPSILON)
|
||||
du1 = 0.0;
|
||||
if (fabs(du2) < EPSILON)
|
||||
du2 = 0.0;
|
||||
#endif
|
||||
du0du1 = du0 * du1;
|
||||
du0du2 = du0 * du2;
|
||||
|
||||
if (du0du1 > 0.0f &&
|
||||
du0du2 > 0.0f) /* same sign on all of them + not equal 0 ? */
|
||||
return 0; /* no intersection occurs */
|
||||
|
||||
/* compute plane of triangle (U0,U1,U2) */
|
||||
SUB(E1, U1, U0);
|
||||
SUB(E2, U2, U0);
|
||||
CROSS(N2, E1, E2);
|
||||
d2 = -DOT(N2, U0);
|
||||
/* plane equation 2: N2.X+d2=0 */
|
||||
|
||||
/* put V0,V1,V2 into plane equation 2 */
|
||||
dv0 = DOT(N2, V0) + d2;
|
||||
dv1 = DOT(N2, V1) + d2;
|
||||
dv2 = DOT(N2, V2) + d2;
|
||||
|
||||
#if USE_EPSILON_TEST == TRUE
|
||||
if (fabs(dv0) < EPSILON)
|
||||
dv0 = 0.0;
|
||||
if (fabs(dv1) < EPSILON)
|
||||
dv1 = 0.0;
|
||||
if (fabs(dv2) < EPSILON)
|
||||
dv2 = 0.0;
|
||||
#endif
|
||||
|
||||
dv0dv1 = dv0 * dv1;
|
||||
dv0dv2 = dv0 * dv2;
|
||||
|
||||
if (dv0dv1 > 0.0f &&
|
||||
dv0dv2 > 0.0f) /* same sign on all of them + not equal 0 ? */
|
||||
return 0; /* no intersection occurs */
|
||||
|
||||
/* compute direction of intersection line */
|
||||
CROSS(D, N1, N2);
|
||||
|
||||
/* compute and index to the largest component of D */
|
||||
max = fabs(D[0]);
|
||||
index = 0;
|
||||
b = fabs(D[1]);
|
||||
c = fabs(D[2]);
|
||||
if (b > max)
|
||||
max = b, index = 1;
|
||||
if (c > max)
|
||||
max = c, index = 2;
|
||||
|
||||
/* this is the simplified projection onto L*/
|
||||
vp0 = V0[index];
|
||||
vp1 = V1[index];
|
||||
vp2 = V2[index];
|
||||
|
||||
up0 = U0[index];
|
||||
up1 = U1[index];
|
||||
up2 = U2[index];
|
||||
|
||||
/* compute interval for triangle 1 */
|
||||
COMPUTE_INTERVALS(vp0, vp1, vp2, dv0, dv1, dv2, dv0dv1, dv0dv2, isect1[0],
|
||||
isect1[1]);
|
||||
|
||||
/* compute interval for triangle 2 */
|
||||
COMPUTE_INTERVALS(up0, up1, up2, du0, du1, du2, du0du1, du0du2, isect2[0],
|
||||
isect2[1]);
|
||||
|
||||
sort(&isect1[0], &isect1[1]);
|
||||
sort(&isect2[0], &isect2[1]);
|
||||
|
||||
if (isect1[1] < isect2[0] || isect2[1] < isect1[0])
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define NEWCOMPUTE_INTERVALS(VV0, VV1, VV2, D0, D1, D2, D0D1, D0D2, A, B, C, \
|
||||
X0, X1) \
|
||||
{ \
|
||||
if (D0D1 > 0.0f) { \
|
||||
/* here we know that D0D2<=0.0 */ \
|
||||
/* that is D0, D1 are on the same side, D2 on the other or on the plane \
|
||||
*/ \
|
||||
A = VV2; \
|
||||
B = (VV0 - VV2) * D2; \
|
||||
C = (VV1 - VV2) * D2; \
|
||||
X0 = D2 - D0; \
|
||||
X1 = D2 - D1; \
|
||||
} else if (D0D2 > 0.0f) { \
|
||||
/* here we know that d0d1<=0.0 */ \
|
||||
A = VV1; \
|
||||
B = (VV0 - VV1) * D1; \
|
||||
C = (VV2 - VV1) * D1; \
|
||||
X0 = D1 - D0; \
|
||||
X1 = D1 - D2; \
|
||||
} else if (D1 * D2 > 0.0f || D0 != 0.0f) { \
|
||||
/* here we know that d0d1<=0.0 or that D0!=0.0 */ \
|
||||
A = VV0; \
|
||||
B = (VV1 - VV0) * D0; \
|
||||
C = (VV2 - VV0) * D0; \
|
||||
X0 = D0 - D1; \
|
||||
X1 = D0 - D2; \
|
||||
} else if (D1 != 0.0f) { \
|
||||
A = VV1; \
|
||||
B = (VV0 - VV1) * D1; \
|
||||
C = (VV2 - VV1) * D1; \
|
||||
X0 = D1 - D0; \
|
||||
X1 = D1 - D2; \
|
||||
} else if (D2 != 0.0f) { \
|
||||
A = VV2; \
|
||||
B = (VV0 - VV2) * D2; \
|
||||
C = (VV1 - VV2) * D2; \
|
||||
X0 = D2 - D0; \
|
||||
X1 = D2 - D1; \
|
||||
} else { \
|
||||
/* triangles are coplanar */ \
|
||||
return coplanar_tri_tri(N1, V0, V1, V2, U0, U1, U2); \
|
||||
} \
|
||||
}
|
||||
|
||||
int NoDivTriTriIsect(float V0[3], float V1[3], float V2[3], float U0[3],
|
||||
float U1[3], float U2[3]) {
|
||||
float E1[3], E2[3];
|
||||
float N1[3], N2[3], d1, d2;
|
||||
float du0, du1, du2, dv0, dv1, dv2;
|
||||
float D[3];
|
||||
float isect1[2], isect2[2];
|
||||
float du0du1, du0du2, dv0dv1, dv0dv2;
|
||||
short index;
|
||||
float vp0, vp1, vp2;
|
||||
float up0, up1, up2;
|
||||
float bb, cc, max;
|
||||
float a, b, c, x0, x1;
|
||||
float d, e, f, y0, y1;
|
||||
float xx, yy, xxyy, tmp;
|
||||
|
||||
/* compute plane equation of triangle(V0,V1,V2) */
|
||||
SUB(E1, V1, V0);
|
||||
SUB(E2, V2, V0);
|
||||
CROSS(N1, E1, E2);
|
||||
d1 = -DOT(N1, V0);
|
||||
/* plane equation 1: N1.X+d1=0 */
|
||||
|
||||
/* put U0,U1,U2 into plane equation 1 to compute signed distances to the
|
||||
* plane*/
|
||||
du0 = DOT(N1, U0) + d1;
|
||||
du1 = DOT(N1, U1) + d1;
|
||||
du2 = DOT(N1, U2) + d1;
|
||||
|
||||
/* coplanarity robustness check */
|
||||
#if USE_EPSILON_TEST == TRUE
|
||||
if (FABS(du0) < EPSILON)
|
||||
du0 = 0.0;
|
||||
if (FABS(du1) < EPSILON)
|
||||
du1 = 0.0;
|
||||
if (FABS(du2) < EPSILON)
|
||||
du2 = 0.0;
|
||||
#endif
|
||||
du0du1 = du0 * du1;
|
||||
du0du2 = du0 * du2;
|
||||
|
||||
if (du0du1 > 0.0f &&
|
||||
du0du2 > 0.0f) /* same sign on all of them + not equal 0 ? */
|
||||
return 0; /* no intersection occurs */
|
||||
|
||||
/* compute plane of triangle (U0,U1,U2) */
|
||||
SUB(E1, U1, U0);
|
||||
SUB(E2, U2, U0);
|
||||
CROSS(N2, E1, E2);
|
||||
d2 = -DOT(N2, U0);
|
||||
/* plane equation 2: N2.X+d2=0 */
|
||||
|
||||
/* put V0,V1,V2 into plane equation 2 */
|
||||
dv0 = DOT(N2, V0) + d2;
|
||||
dv1 = DOT(N2, V1) + d2;
|
||||
dv2 = DOT(N2, V2) + d2;
|
||||
|
||||
#if USE_EPSILON_TEST == TRUE
|
||||
if (FABS(dv0) < EPSILON)
|
||||
dv0 = 0.0;
|
||||
if (FABS(dv1) < EPSILON)
|
||||
dv1 = 0.0;
|
||||
if (FABS(dv2) < EPSILON)
|
||||
dv2 = 0.0;
|
||||
#endif
|
||||
|
||||
dv0dv1 = dv0 * dv1;
|
||||
dv0dv2 = dv0 * dv2;
|
||||
|
||||
if (dv0dv1 > 0.0f &&
|
||||
dv0dv2 > 0.0f) /* same sign on all of them + not equal 0 ? */
|
||||
return 0; /* no intersection occurs */
|
||||
|
||||
/* compute direction of intersection line */
|
||||
CROSS(D, N1, N2);
|
||||
|
||||
/* compute and index to the largest component of D */
|
||||
max = (float)FABS(D[0]);
|
||||
index = 0;
|
||||
bb = (float)FABS(D[1]);
|
||||
cc = (float)FABS(D[2]);
|
||||
if (bb > max)
|
||||
max = bb, index = 1;
|
||||
if (cc > max)
|
||||
max = cc, index = 2;
|
||||
|
||||
/* this is the simplified projection onto L*/
|
||||
vp0 = V0[index];
|
||||
vp1 = V1[index];
|
||||
vp2 = V2[index];
|
||||
|
||||
up0 = U0[index];
|
||||
up1 = U1[index];
|
||||
up2 = U2[index];
|
||||
|
||||
/* compute interval for triangle 1 */
|
||||
NEWCOMPUTE_INTERVALS(vp0, vp1, vp2, dv0, dv1, dv2, dv0dv1, dv0dv2, a, b, c,
|
||||
x0, x1);
|
||||
|
||||
/* compute interval for triangle 2 */
|
||||
NEWCOMPUTE_INTERVALS(up0, up1, up2, du0, du1, du2, du0du1, du0du2, d, e, f,
|
||||
y0, y1);
|
||||
|
||||
xx = x0 * x1;
|
||||
yy = y0 * y1;
|
||||
xxyy = xx * yy;
|
||||
|
||||
tmp = a * xxyy;
|
||||
isect1[0] = tmp + b * x1 * yy;
|
||||
isect1[1] = tmp + c * x0 * yy;
|
||||
|
||||
tmp = d * xxyy;
|
||||
isect2[0] = tmp + e * xx * y1;
|
||||
isect2[1] = tmp + f * xx * y0;
|
||||
|
||||
SORT(isect1[0], isect1[1]);
|
||||
SORT(isect2[0], isect2[1]);
|
||||
|
||||
if (isect1[1] < isect2[0] || isect2[1] < isect1[0])
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void isect2(vec3<T> VTX0, vec3<T> VTX1, vec3<T> VTX2, T VV0, T VV1,
|
||||
T VV2, T D0, T D1, T D2, T *isect0, T *isect1,
|
||||
vec3<T> isectpoint0, vec3<T> isectpoint1) {
|
||||
T tmp = D0 / (D0 - D1);
|
||||
T diff[3];
|
||||
*isect0 = VV0 + (VV1 - VV0) * tmp;
|
||||
SUB(diff, VTX1, VTX0);
|
||||
MULT(diff, diff, tmp);
|
||||
ADD(isectpoint0, diff, VTX0);
|
||||
tmp = D0 / (D0 - D2);
|
||||
*isect1 = VV0 + (VV2 - VV0) * tmp;
|
||||
SUB(diff, VTX2, VTX0);
|
||||
MULT(diff, diff, tmp);
|
||||
ADD(isectpoint1, VTX0, diff);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__host__ __device__ inline bool
|
||||
compute_intervals_isectline(vec3<T> VERT0, vec3<T> VERT1, vec3<T> VERT2, T VV0,
|
||||
T VV1, T VV2, T D0, T D1, T D2, T D0D1, T D0D2,
|
||||
T *isect0, T *isect1, vec3<T> isectpoint0,
|
||||
vec3<T> isectpoint1) {
|
||||
if (D0D1 > 0.0f) {
|
||||
/* here we know that D0D2<=0.0 */
|
||||
/* that is D0, D1 are on the same side, D2 on the other or on the plane */
|
||||
isect2(VERT2, VERT0, VERT1, VV2, VV0, VV1, D2, D0, D1, isect0, isect1,
|
||||
isectpoint0, isectpoint1);
|
||||
} else if (D0D2 > 0.0f) {
|
||||
/* here we know that d0d1<=0.0 */
|
||||
isect2(VERT1, VERT0, VERT2, VV1, VV0, VV2, D1, D0, D2, isect0, isect1,
|
||||
isectpoint0, isectpoint1);
|
||||
} else if (D1 * D2 > 0.0f || D0 != 0.0f) {
|
||||
/* here we know that d0d1<=0.0 or that D0!=0.0 */
|
||||
isect2(VERT0, VERT1, VERT2, VV0, VV1, VV2, D0, D1, D2, isect0, isect1,
|
||||
isectpoint0, isectpoint1);
|
||||
} else if (D1 != 0.0f) {
|
||||
isect2(VERT1, VERT0, VERT2, VV1, VV0, VV2, D1, D0, D2, isect0, isect1,
|
||||
isectpoint0, isectpoint1);
|
||||
} else if (D2 != 0.0f) {
|
||||
isect2(VERT2, VERT0, VERT1, VV2, VV0, VV1, D2, D0, D1, isect0, isect1,
|
||||
isectpoint0, isectpoint1);
|
||||
} else {
|
||||
/* triangles are coplanar */
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__host__ __device__ inline bool tri_tri_intersect_with_isectline(
|
||||
vec3<T> V0, vec3<T> V1, vec3<T> V2, vec3<T> U0, vec3<T> U1, vec3<T> U2,
|
||||
bool *coplanar, vec3<T> isect_point1[3], vec3<T> isect_point2[3]) {
|
||||
vec3<T> E1, E2;
|
||||
vec3<T> N1, N2, d1, d2;
|
||||
vec3<T> D;
|
||||
T du0, du1, du2, dv0, dv1, dv2;
|
||||
vec2<T> isect1, isect2;
|
||||
vec3<T> isectpointA1, isectpointA2;
|
||||
vec3<T> isectpointB1, isectpointB2;
|
||||
T du0du1, du0du2, dv0dv1, dv0dv2;
|
||||
short index;
|
||||
T vp0, vp1, vp2;
|
||||
T up0, up1, up2;
|
||||
T b, c, max;
|
||||
T tmp, diff[3];
|
||||
int smallest1, smallest2;
|
||||
|
||||
/* compute plane equation of triangle(V0,V1,V2) */
|
||||
E1 = V1 - V0;
|
||||
E2 = V2 - V0;
|
||||
N1 = cross(E1, E2) d1 = -dot(N1, V0);
|
||||
/* plane equation 1: N1.X+d1=0 */
|
||||
|
||||
/* put U0,U1,U2 into plane equation 1 to compute signed distances to the
|
||||
* plane*/
|
||||
du0 = dot(N1, U0) + d1;
|
||||
du1 = dot(N1, U1) + d1;
|
||||
du2 = dot(N1, U2) + d1;
|
||||
|
||||
/* coplanarity robustness check */
|
||||
#if USE_EPSILON_TEST == TRUE
|
||||
if (fabs(du0) < EPSILON)
|
||||
du0 = 0.0;
|
||||
if (fabs(du1) < EPSILON)
|
||||
du1 = 0.0;
|
||||
if (fabs(du2) < EPSILON)
|
||||
du2 = 0.0;
|
||||
#endif
|
||||
du0du1 = du0 * du1;
|
||||
du0du2 = du0 * du2;
|
||||
|
||||
if (du0du1 > 0.0f &&
|
||||
du0du2 > 0.0f) /* same sign on all of them + not equal 0 ? */
|
||||
return 0; /* no intersection occurs */
|
||||
|
||||
/* compute plane of triangle (U0,U1,U2) */
|
||||
E1 = U1 - U0;
|
||||
E2 = U2 - U0;
|
||||
N2 = cross(E1, E2);
|
||||
d2 = -dot(N2, U0);
|
||||
/* plane equation 2: N2.X+d2=0 */
|
||||
|
||||
/* put V0,V1,V2 into plane equation 2 */
|
||||
dv0 = dot(N2, V0) + d2;
|
||||
dv1 = dot(N2, V1) + d2;
|
||||
dv2 = dot(N2, V2) + d2;
|
||||
|
||||
#if USE_EPSILON_TEST == TRUE
|
||||
if (fabs(dv0) < EPSILON)
|
||||
dv0 = 0.0;
|
||||
if (fabs(dv1) < EPSILON)
|
||||
dv1 = 0.0;
|
||||
if (fabs(dv2) < EPSILON)
|
||||
dv2 = 0.0;
|
||||
#endif
|
||||
|
||||
dv0dv1 = dv0 * dv1;
|
||||
dv0dv2 = dv0 * dv2;
|
||||
|
||||
/* same sign on all of them + not equal 0 ? */
|
||||
if (dv0dv1 > 0.0f && dv0dv2 > 0.0f)
|
||||
return 0; /* no intersection occurs */
|
||||
|
||||
/* compute direction of intersection line */
|
||||
D = cross(N1, N2);
|
||||
|
||||
/* compute and index to the largest component of D */
|
||||
max = fabs(D.x);
|
||||
index = 0;
|
||||
b = fabs(D.y);
|
||||
c = fabs(D.z);
|
||||
if (b > max)
|
||||
max = b, index = 1;
|
||||
if (c > max)
|
||||
max = c, index = 2;
|
||||
|
||||
/* this is the simplified projection onto L*/
|
||||
vp0 = V0[index];
|
||||
vp1 = V1[index];
|
||||
vp2 = V2[index];
|
||||
|
||||
up0 = U0[index];
|
||||
up1 = U1[index];
|
||||
up2 = U2[index];
|
||||
|
||||
/* compute interval for triangle 1 */
|
||||
*coplanar = compute_intervals_isectline(
|
||||
V0, V1, V2, vp0, vp1, vp2, dv0, dv1, dv2, dv0dv1, dv0dv2, &isect1[0],
|
||||
&isect1[1], isectpointA1, isectpointA2);
|
||||
if (*coplanar)
|
||||
return coplanar_tri_tri(N1, V0, V1, V2, U0, U1, U2);
|
||||
|
||||
/* compute interval for triangle 2 */
|
||||
compute_intervals_isectline(U0, U1, U2, up0, up1, up2, du0, du1, du2, du0du1,
|
||||
du0du2, &isect2[0], &isect2[1], isectpointB1,
|
||||
isectpointB2);
|
||||
|
||||
smallest1 = sort(&isect1[0], &isect1[1]);
|
||||
smallest2 = sort(&isect2[0], &isect2[1]);
|
||||
|
||||
if (isect1[1] < isect2[0] || isect2[1] < isect1[0])
|
||||
return 0;
|
||||
|
||||
/* at this point, we know that the triangles intersect */
|
||||
|
||||
if (isect2[0] < isect1[0]) {
|
||||
if (smallest1 == 0) {
|
||||
SET(isect_point1, isectpointA1);
|
||||
} else {
|
||||
SET(isect_point1, isectpointA2);
|
||||
}
|
||||
|
||||
if (isect2[1] < isect1[1]) {
|
||||
if (smallest2 == 0) {
|
||||
SET(isect_point2, isectpointB2);
|
||||
} else {
|
||||
SET(isect_point2, isectpointB1);
|
||||
}
|
||||
} else {
|
||||
if (smallest1 == 0) {
|
||||
SET(isect_point2, isectpointA2);
|
||||
} else {
|
||||
SET(isect_point2, isectpointA1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (smallest2 == 0) {
|
||||
SET(isect_point1, isectpointB1);
|
||||
} else {
|
||||
SET(isect_point1, isectpointB2);
|
||||
}
|
||||
|
||||
if (isect2[1] > isect1[1]) {
|
||||
if (smallest1 == 0) {
|
||||
SET(isect_point2, isectpointA2);
|
||||
} else {
|
||||
SET(isect_point2, isectpointA1);
|
||||
}
|
||||
} else {
|
||||
if (smallest2 == 0) {
|
||||
SET(isect_point2, isectpointB2);
|
||||
} else {
|
||||
SET(isect_point2, isectpointB1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
* holder of all proprietary rights on this computer program.
|
||||
* You can only use this computer program if you have closed
|
||||
* a license agreement with MPG or you get the right to use the computer
|
||||
* program from someone who is authorized to grant you that right.
|
||||
* Any use of the computer program without a valid license is prohibited and
|
||||
* liable to prosecution.
|
||||
*
|
||||
* Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
* der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
* for Intelligent Systems. All rights reserved.
|
||||
*
|
||||
* @author Vasileios Choutas
|
||||
* Contact: vassilis.choutas@tuebingen.mpg.de
|
||||
* Contact: ps-license@tuebingen.mpg.de
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PRIORITY_QUEUE_H
|
||||
#define PRIORITY_QUEUE_H
|
||||
|
||||
#include <float.h>
|
||||
#include <stdio.h>
|
||||
#include <utility>
|
||||
|
||||
#include <cuda.h>
|
||||
#include "device_launch_parameters.h"
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
template<typename T>
|
||||
__host__ __device__
|
||||
void swap_array_els(T* array, int i, int j) {
|
||||
T tmp = array[i];
|
||||
array[i] = array[j];
|
||||
array[j] = tmp;
|
||||
}
|
||||
|
||||
template <typename T, typename Obj, int QueueSize = 128, bool recursive = false>
|
||||
class PriorityQueue {
|
||||
public:
|
||||
__host__ __device__
|
||||
PriorityQueue() : heap_size(0) {}
|
||||
|
||||
inline
|
||||
__host__ __device__
|
||||
int get_size() {
|
||||
return heap_size;
|
||||
}
|
||||
|
||||
inline
|
||||
__host__ __device__
|
||||
int parent(int i) {
|
||||
return (i - 1) / 2;
|
||||
}
|
||||
|
||||
inline
|
||||
__host__ __device__
|
||||
int left_child(int i) {
|
||||
return 2 * i + 1;
|
||||
}
|
||||
|
||||
inline
|
||||
__host__ __device__
|
||||
int right_child(int i) {
|
||||
return 2 * i + 2;
|
||||
}
|
||||
|
||||
__host__ __device__
|
||||
std::pair<T, Obj> get_min() {
|
||||
if (heap_size > 0) {
|
||||
return std::pair<T, Obj>(priority_heap[0], obj_heap[0]);
|
||||
}
|
||||
else {
|
||||
return std::pair<T, Obj>(
|
||||
std::is_same<T, float>::value ? FLT_MAX : DBL_MAX, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__
|
||||
void min_heapify(int index) {
|
||||
if (recursive) {
|
||||
int left = left_child(index);
|
||||
int right = right_child(index);
|
||||
int smallest = index;
|
||||
if (left < heap_size && priority_heap[left] < priority_heap[index])
|
||||
smallest = left;
|
||||
if (right < heap_size && priority_heap[right] < priority_heap[index])
|
||||
smallest = right;
|
||||
if (smallest != index) {
|
||||
swap_array_els(priority_heap, index, smallest);
|
||||
swap_array_els(obj_heap, index, smallest);
|
||||
min_heapify(smallest);
|
||||
}
|
||||
} else {
|
||||
int ii = index;
|
||||
int smallest;
|
||||
while (true) {
|
||||
int left = left_child(ii);
|
||||
int right = right_child(ii);
|
||||
smallest = ii;
|
||||
|
||||
if (left < heap_size && priority_heap[left] < priority_heap[ii])
|
||||
smallest = left;
|
||||
if (right < heap_size && priority_heap[right] < priority_heap[ii])
|
||||
smallest = right;
|
||||
|
||||
if (smallest != ii) {
|
||||
swap_array_els(priority_heap, ii, smallest);
|
||||
swap_array_els(obj_heap, ii, smallest);
|
||||
ii = smallest;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__
|
||||
void insert_key(T key, Obj obj) {
|
||||
if (heap_size == QueueSize) {
|
||||
printf("The queue has exceed its maximum size\n");
|
||||
return;
|
||||
}
|
||||
heap_size++;
|
||||
int ii = heap_size - 1;
|
||||
priority_heap[ii] = key;
|
||||
obj_heap[ii] = obj;
|
||||
|
||||
// Fix the min heap property if it is violated
|
||||
min_heapify(0);
|
||||
// while (ii != 0 && priority_heap[parent(ii)] > priority_heap[ii]) {
|
||||
// swap_array_els(priority_heap, ii, parent(ii));
|
||||
// swap_array_els(obj_heap, ii, parent(ii));
|
||||
// ii = parent(ii);
|
||||
// }
|
||||
}
|
||||
|
||||
// void print() {
|
||||
// for (int i = 0; i < heap_size; i++) {
|
||||
// std::cout << i << ": " << heap[i] << std::endl;
|
||||
// }
|
||||
// }
|
||||
|
||||
__host__ __device__
|
||||
std::pair<T, Obj> extract() {
|
||||
if (heap_size <= 0)
|
||||
return std::pair<T, Obj>(
|
||||
std::is_same<T, float>::value ? FLT_MAX : DBL_MAX, nullptr);
|
||||
|
||||
T root_prio = priority_heap[0];
|
||||
Obj root_obj = obj_heap[0];
|
||||
// Replace the root with the last element
|
||||
priority_heap[0] = priority_heap[heap_size - 1];
|
||||
obj_heap[0] = obj_heap[heap_size - 1];
|
||||
// Decrease the size of the heap
|
||||
heap_size--;
|
||||
|
||||
min_heapify(0);
|
||||
return std::pair<T, Obj>(root_prio, root_obj);
|
||||
}
|
||||
|
||||
private:
|
||||
T priority_heap[QueueSize];
|
||||
Obj obj_heap[QueueSize];
|
||||
int heap_size;
|
||||
};
|
||||
|
||||
#endif // #ifndef PRIORITY_QUEUE_H
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
* holder of all proprietary rights on this computer program.
|
||||
* You can only use this computer program if you have closed
|
||||
* a license agreement with MPG or you get the right to use the computer
|
||||
* program from someone who is authorized to grant you that right.
|
||||
* Any use of the computer program without a valid license is prohibited and
|
||||
* liable to prosecution.
|
||||
*
|
||||
* Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
* der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
* for Intelligent Systems. All rights reserved.
|
||||
*
|
||||
* @author Vasileios Choutas
|
||||
* Contact: vassilis.choutas@tuebingen.mpg.de
|
||||
* Contact: ps-license@tuebingen.mpg.de
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TRIANGLE_H
|
||||
#define TRIANGLE_H
|
||||
|
||||
#include "defs.hpp"
|
||||
#include "double_vec_ops.hpp"
|
||||
#include "helper_math.h"
|
||||
|
||||
#include "math_utils.hpp"
|
||||
#include <cuda.h>
|
||||
#include <cuda_profiler_api.h>
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
template <typename T>
|
||||
__align__(48)
|
||||
struct Triangle {
|
||||
public:
|
||||
vec3<T> v0;
|
||||
vec3<T> v1;
|
||||
vec3<T> v2;
|
||||
|
||||
__host__ __device__ Triangle() {}
|
||||
__host__ __device__ Triangle(vec3<T> vertex0, vec3<T> vertex1,
|
||||
vec3<T> vertex2)
|
||||
: v0(vertex0), v1(vertex1), v2(vertex2){};
|
||||
__host__ __device__ Triangle(const vec3<T> &vertex0, const vec3<T> &vertex1,
|
||||
const vec3<T> &vertex2)
|
||||
: v0(vertex0), v1(vertex1), v2(vertex2){};
|
||||
|
||||
__host__ __device__ AABB<T> bbox() const {
|
||||
return AABB<T>(min(v0.x, min(v1.x, v2.x)), min(v0.y, min(v1.y, v2.y)),
|
||||
min(v0.z, min(v1.z, v2.z)), max(v0.x, max(v1.x, v2.x)),
|
||||
max(v0.y, max(v1.y, v2.y)), max(v0.z, max(v1.z, v2.z)));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> using TrianglePtr = Triangle<T> *;
|
||||
|
||||
template <typename T>
|
||||
std::ostream &operator<<(std::ostream &os, const Triangle<T> &x) {
|
||||
os << x.v0 << std::endl;
|
||||
os << x.v1 << std::endl;
|
||||
os << x.v2 << std::endl;
|
||||
return os;
|
||||
}
|
||||
|
||||
|
||||
#endif // TRIANGLE_H
|
||||
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2020 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
import torch
|
||||
from .mesh_mesh_intersection import MeshMeshIntersection
|
||||
@@ -0,0 +1,317 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems and the Max Planck Institute for Biological
|
||||
# Cybernetics. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
|
||||
import sys
|
||||
|
||||
import time
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
def calc_circumcircle(triangles, edge_cross_prod, idx=None):
|
||||
''' Calculate the circumscribed circle for the given triangles
|
||||
|
||||
Args:
|
||||
- triangles (torch.tensor BxTx3x3): The tensor that contains the
|
||||
coordinates of the triangle vertices
|
||||
- edge_cross_prod (torch.tensor BxCx3): Contains the unnormalized
|
||||
perpendicular vector to the surface of the triangle.
|
||||
Returns:
|
||||
- circumradius (torch.tensor BxTx1): The radius of the
|
||||
circumscribed circle
|
||||
- circumcenter (torch.tensor BxTx3): The center of the
|
||||
circumscribed circel
|
||||
'''
|
||||
|
||||
alpha = triangles[:, :, 0] - triangles[:, :, 2]
|
||||
beta = triangles[:, :, 1] - triangles[:, :, 2]
|
||||
|
||||
# Calculate the radius of the circumscribed circle
|
||||
# Should be BxF
|
||||
circumradius = (torch.norm(alpha - beta, dim=2, keepdim=True) /
|
||||
(2 * torch.norm(edge_cross_prod, dim=2, keepdim=True)) *
|
||||
torch.norm(alpha, dim=2, keepdim=True) *
|
||||
torch.norm(beta, dim=2, keepdim=True))
|
||||
|
||||
# Calculate the coordinates of the circumcenter of each triangle
|
||||
# Should BxFx3
|
||||
circumcenter = torch.cross(
|
||||
torch.sum(alpha ** 2, dim=2, keepdim=True) * beta -
|
||||
torch.sum(beta ** 2, dim=2, keepdim=True) * alpha,
|
||||
torch.cross(alpha, beta, dim=-1), dim=2)
|
||||
circumcenter /= (2 * torch.sum(edge_cross_prod ** 2, dim=2, keepdim=True))
|
||||
|
||||
return circumradius, circumcenter + triangles[:, :, 2]
|
||||
|
||||
|
||||
def repulsion_intensity(x, sigma=0.5, penalize_outside=True, linear_max=1000):
|
||||
''' Penalizer function '''
|
||||
quad_penalty = (-(1.0 - 2.0 * sigma) / (4.0 * sigma ** 2) *
|
||||
x ** 2 - 1 / (2.0 * sigma) * x +
|
||||
0.25 * (3 - 2 * sigma))
|
||||
linear_region_mask = (x.le(-sigma) * x.gt(-linear_max)).to(dtype=x.dtype)
|
||||
if penalize_outside:
|
||||
quad_region_mask = (x.gt(-sigma) * x.lt(sigma)).to(dtype=x.dtype)
|
||||
else:
|
||||
quad_region_mask = (x.gt(-sigma) * x.lt(0)).to(dtype=x.dtype)
|
||||
|
||||
return (linear_region_mask * (-x + 1 - sigma) +
|
||||
quad_region_mask * quad_penalty)
|
||||
|
||||
|
||||
def dist_to_cone_axis(points_rel, dot_prod, cone_axis, cone_radius,
|
||||
sigma=0.5, epsilon=1e-6, vectorized=True):
|
||||
''' Computes the distance of each point to the axis
|
||||
|
||||
This function projects the points on the plane of the base of the cone
|
||||
and computes the distance to the axis. This is subsequently normalized
|
||||
by the radius of the cone at the height level of the point, so that
|
||||
points with distance < 1 are in the code, distance == 1 means that the
|
||||
point is on the surface and distance > 1 means that the point is
|
||||
outside the cone.
|
||||
|
||||
Args:
|
||||
- points_rel (torch.tensor BxCxNx3): The coordinates of the points
|
||||
relative to the center of the cone
|
||||
- dot_prod (torch.tensor BxCxN): The dot product of the points (in
|
||||
relative coordinates with respect to the cone center) with the
|
||||
axis of the cone
|
||||
- cone_axis (torch.tensor BxCx3): The axis of the cone
|
||||
- cone_radius (torch.tensor BxCx1): The radius of the cone
|
||||
Keyword args:
|
||||
- sigma (float = 0.5): The height of the cone
|
||||
- epsilon (float = 1e-6): Numerical stability constant for the
|
||||
float division
|
||||
- vectorized (bool = True): Whether to use an iterative or a
|
||||
vectorized version of the function
|
||||
'''
|
||||
|
||||
if vectorized:
|
||||
batch_size, num_collisions = cone_radius.shape[:2]
|
||||
numerator = torch.norm(points_rel - dot_prod.unsqueeze(dim=-1) *
|
||||
cone_axis.unsqueeze(dim=-2),
|
||||
p=2, dim=-1)
|
||||
denominator = -cone_radius / sigma * dot_prod + cone_radius
|
||||
else:
|
||||
batch_size, num_collisions = cone_radius.shape[:2]
|
||||
numerator = torch.norm(points_rel - dot_prod.unsqueeze(-1) * cone_axis,
|
||||
p=2, dim=-1)
|
||||
denominator = -cone_radius.view(batch_size, num_collisions) / sigma * \
|
||||
dot_prod + cone_radius.view(batch_size, num_collisions)
|
||||
|
||||
return numerator / (denominator + epsilon)
|
||||
|
||||
|
||||
def conical_distance_field(triangle_points, cone_center, cone_radius,
|
||||
cone_axis, sigma=0.5, vectorized=True,
|
||||
penalize_outside=True, linear_max=1000):
|
||||
''' Distance field calculation for a cone
|
||||
|
||||
Args:
|
||||
- triangle_points (torch.tensor (BxCxNx3): Contains
|
||||
the points whose distance from the cone we want to calculate.
|
||||
- cone_center (torch.tensor (BxCx3)): The coordinates of the center
|
||||
of the cone
|
||||
- cone_radius (torch.tensor (BxC)): The radius of the base of the
|
||||
cone
|
||||
- cone_axis (torch.tensor(BxCx3)): The unit vector that represents
|
||||
the axis of the cone
|
||||
Keyword Arguments
|
||||
- sigma (float = 0.5): The float value of the height of the cone
|
||||
- vectorized (bool = True): Whether to use an iterative or a
|
||||
vectorized version of the function
|
||||
Returns:
|
||||
- (torch.tensor BxCxN): The distance field values at the N points
|
||||
for the cone
|
||||
'''
|
||||
|
||||
if vectorized:
|
||||
# Calculate the coordinates of the points relative to the center of
|
||||
# the cone
|
||||
points_rel = triangle_points - cone_center.unsqueeze(dim=-2)
|
||||
# Calculate the dot product between the relative point coordinates and
|
||||
# the axis (normal) of the cone. Essentially, it is the length of the
|
||||
# projection of the relative vector on the axis of the cone
|
||||
dot_prod = torch.sum(points_rel * cone_axis.unsqueeze(dim=-2), dim=-1)
|
||||
|
||||
# Calculate the distance of the projections of the points on the cone
|
||||
# base plane to the center of cone, normalized by the height
|
||||
axis_dist = dist_to_cone_axis(points_rel, dot_prod,
|
||||
cone_axis, cone_radius,
|
||||
sigma=sigma, vectorized=True)
|
||||
|
||||
circumcenter_dist = repulsion_intensity(
|
||||
dot_prod, sigma=sigma, penalize_outside=penalize_outside,
|
||||
linear_max=linear_max)
|
||||
|
||||
# Ignore the points with axis_dist > 1, since they are out of the cone
|
||||
mask = axis_dist.lt(1).to(dtype=triangle_points.dtype)
|
||||
|
||||
distance_field = mask * ((1 - axis_dist) * circumcenter_dist).pow(2)
|
||||
else:
|
||||
batch_size, num_collisions, num_points = triangle_points.shape[:3]
|
||||
distance_field = torch.zeros([batch_size, num_collisions, 3],
|
||||
dtype=triangle_points.dtype,
|
||||
device=triangle_points.device)
|
||||
for idx in range(num_points):
|
||||
# The relative coordinates of each point to the center of the cone
|
||||
# BxCx3
|
||||
points_rel = triangle_points[:, :, idx, :] - cone_center
|
||||
|
||||
# Calculate the dot product between the relative point coordinates
|
||||
# and the axis (normal) of the cone. Essentially, it is the length
|
||||
# of the projection of the relative vector on the axis of the cone
|
||||
dot_prod = torch.sum(points_rel * cone_axis, dim=-1)
|
||||
|
||||
axis_dist = dist_to_cone_axis(points_rel, dot_prod,
|
||||
cone_axis, cone_radius,
|
||||
sigma=sigma,
|
||||
vectorized=False)
|
||||
|
||||
circumcenter_dist = repulsion_intensity(
|
||||
dot_prod, sigma=sigma, penalize_outside=penalize_outside)
|
||||
mask = (axis_dist < 1).to(dtype=triangle_points.dtype)
|
||||
|
||||
distance_field[:, :, idx] = (1 - axis_dist) * mask * \
|
||||
circumcenter_dist
|
||||
|
||||
return torch.pow(distance_field, 2)
|
||||
|
||||
|
||||
class DistanceFieldPenetrationLoss(nn.Module):
|
||||
def __init__(self, sigma=0.5, point2plane=False, vectorized=True,
|
||||
penalize_outside=True, linear_max=1000):
|
||||
super(DistanceFieldPenetrationLoss, self).__init__()
|
||||
self.sigma = sigma
|
||||
self.point2plane = point2plane
|
||||
self.vectorized = vectorized
|
||||
self.penalize_outside = penalize_outside
|
||||
self.linear_max = linear_max
|
||||
|
||||
def forward(self, triangles, collision_idxs):
|
||||
'''
|
||||
Args:
|
||||
- triangles: A torch tensor of size BxFx3x3 that contains the
|
||||
coordinates of the triangle vertices
|
||||
- collision_idxs: A torch tensor of size Bx(-1)x2 that contains the
|
||||
indices of the colliding pairs
|
||||
Returns:
|
||||
A tensor with size B that contains the self penetration loss for
|
||||
each mesh in the batch
|
||||
'''
|
||||
|
||||
coll_idxs = collision_idxs[:, :, 0].ge(0).nonzero()
|
||||
if len(coll_idxs) < 1:
|
||||
return torch.zeros([triangles.shape[0]],
|
||||
dtype=triangles.dtype,
|
||||
device=triangles.device,
|
||||
requires_grad=triangles.requires_grad)
|
||||
|
||||
receiver_faces = collision_idxs[coll_idxs[:, 0], coll_idxs[:, 1], 0]
|
||||
intruder_faces = collision_idxs[coll_idxs[:, 0], coll_idxs[:, 1], 1]
|
||||
|
||||
batch_idxs = coll_idxs[:, 0]
|
||||
num_collisions = coll_idxs.shape[0]
|
||||
|
||||
batch_size = triangles.shape[0]
|
||||
|
||||
if len(intruder_faces) < 1:
|
||||
return torch.tensor(0.0, dtype=triangles.dtype,
|
||||
device=triangles.device,
|
||||
requires_grad=triangles.requires_grad)
|
||||
# Calculate the edges of the triangles
|
||||
# Size: BxFx3
|
||||
edge0 = triangles[:, :, 1] - triangles[:, :, 0]
|
||||
edge1 = triangles[:, :, 2] - triangles[:, :, 0]
|
||||
# Compute the cross product of the edges to find the normal vector of
|
||||
# the triangle
|
||||
aCrossb = torch.cross(edge0, edge1, dim=2)
|
||||
|
||||
circumradius, circumcenter = calc_circumcircle(triangles, aCrossb)
|
||||
|
||||
# Normalize the result to get a unit vector
|
||||
normals = aCrossb / torch.norm(aCrossb, 2, dim=2, keepdim=True)
|
||||
|
||||
recv_triangles = triangles[batch_idxs, receiver_faces]
|
||||
intr_triangles = triangles[batch_idxs, intruder_faces]
|
||||
|
||||
recv_normals = normals[batch_idxs, receiver_faces]
|
||||
recv_circumradius = circumradius[batch_idxs, receiver_faces]
|
||||
recv_circumcenter = circumcenter[batch_idxs, receiver_faces]
|
||||
|
||||
intr_normals = normals[batch_idxs, intruder_faces]
|
||||
intr_circumradius = circumradius[batch_idxs, intruder_faces]
|
||||
intr_circumcenter = circumcenter[batch_idxs, intruder_faces]
|
||||
|
||||
# Compute the distance field for the intruding triangles
|
||||
# B x NUM_COLLISIONS x 3
|
||||
# For each batch element, for each collision pair, 3 distance values
|
||||
# for the vertices of the intruding triangle
|
||||
phi_receivers = conical_distance_field(
|
||||
intr_triangles,
|
||||
recv_circumcenter, recv_circumradius,
|
||||
recv_normals,
|
||||
sigma=self.sigma,
|
||||
vectorized=self.vectorized,
|
||||
penalize_outside=self.penalize_outside,
|
||||
linear_max=self.linear_max)
|
||||
|
||||
# Compute the distance field for the intruding triangles
|
||||
# B x NUM_COLLISIONS x 3
|
||||
# For each batch element, for each collision pair, 3 distance values
|
||||
# for the vertices of the intruding triangle
|
||||
# Same as above, but now the receiver is the "intruder".
|
||||
phi_intruders = conical_distance_field(
|
||||
recv_triangles,
|
||||
intr_circumcenter,
|
||||
intr_circumradius,
|
||||
intr_normals,
|
||||
sigma=self.sigma,
|
||||
vectorized=self.vectorized,
|
||||
penalize_outside=self.penalize_outside,
|
||||
linear_max=self.linear_max)
|
||||
|
||||
receiver_loss = torch.tensor(0, device=triangles.device,
|
||||
dtype=torch.float32)
|
||||
intruder_loss = torch.tensor(0, device=triangles.device,
|
||||
dtype=torch.float32)
|
||||
|
||||
if self.point2plane:
|
||||
receiver_loss = (-phi_receivers).pow(2).sum(dim=-1)
|
||||
intruder_loss = (-phi_intruders).pow(2).sum(dim=-1)
|
||||
else:
|
||||
receiver_loss = torch.norm(-phi_receivers.unsqueeze(dim=-1) *
|
||||
intr_normals.unsqueeze(dim=-2), p=2,
|
||||
dim=-1).pow(2).sum(dim=-1)
|
||||
intruder_loss = torch.norm(-phi_intruders.unsqueeze(dim=-1) *
|
||||
recv_normals.unsqueeze(dim=-2), p=2,
|
||||
dim=-1).pow(2).sum(dim=-1)
|
||||
|
||||
batch_ind = torch.arange(0, batch_size, dtype=batch_idxs.dtype,
|
||||
device=triangles.device).unsqueeze(dim=1)
|
||||
batch_mask = batch_ind.repeat([1, num_collisions]).eq(batch_idxs)\
|
||||
.to(receiver_loss.dtype)
|
||||
|
||||
loss = torch.matmul(batch_mask, receiver_loss) + \
|
||||
torch.matmul(batch_mask, intruder_loss)
|
||||
return loss
|
||||
@@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.de
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
|
||||
import sys
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.autograd as autograd
|
||||
# from loguru import logger
|
||||
|
||||
import mesh_mesh_intersection
|
||||
import mesh_mesh_intersect_cuda
|
||||
|
||||
|
||||
class MeshMeshIntersectionFunction(autograd.Function):
|
||||
|
||||
@staticmethod
|
||||
@torch.no_grad()
|
||||
def forward(ctx, query_triangles, target_triangles, print_timings=False,
|
||||
max_collisions=32,
|
||||
*args, **kwargs):
|
||||
outputs = mesh_mesh_intersect_cuda.mesh_to_mesh_forward(
|
||||
query_triangles, target_triangles, print_timings=print_timings,
|
||||
max_collisions=max_collisions)
|
||||
# ctx.save_for_backward(query_triangles, outputs)
|
||||
collision_faces, collision_bcs = outputs
|
||||
return collision_faces, collision_bcs
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_output, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class MeshMeshIntersection(nn.Module):
|
||||
|
||||
def __init__(self, max_collisions=32):
|
||||
super(MeshMeshIntersection, self).__init__()
|
||||
self.max_collisions = max_collisions
|
||||
# MeshMeshIntersectionFunction.max_collisions = self.max_collisions
|
||||
|
||||
def forward(self, query_triangles, target_triangles,
|
||||
print_timings=False):
|
||||
return MeshMeshIntersectionFunction.apply(
|
||||
query_triangles, target_triangles, print_timings,
|
||||
self.max_collisions)
|
||||
@@ -0,0 +1,4 @@
|
||||
pyrender>=0.1.23
|
||||
shapely
|
||||
trimesh>=2.37.6
|
||||
smplx
|
||||
@@ -0,0 +1,2 @@
|
||||
numpy>=1.16.2
|
||||
torch>=1.0
|
||||
@@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
# holder of all proprietary rights on this computer program.
|
||||
# You can only use this computer program if you have closed
|
||||
# a license agreement with MPG or you get the right to use the computer
|
||||
# program from someone who is authorized to grant you that right.
|
||||
# Any use of the computer program without a valid license is prohibited and
|
||||
# liable to prosecution.
|
||||
#
|
||||
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
# for Intelligent Systems and the Max Planck Institute for Biological
|
||||
# Cybernetics. All rights reserved.
|
||||
#
|
||||
# Contact: ps-license@tuebingen.mpg.deimport io
|
||||
|
||||
import io
|
||||
import os
|
||||
import os.path as osp
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
import torch
|
||||
from torch.utils.cpp_extension import BuildExtension, CUDAExtension
|
||||
|
||||
# Package meta-data.
|
||||
NAME = 'mesh_mesh_intersection'
|
||||
DESCRIPTION = 'PyTorch module for Mesh-Mesh intersection detection'
|
||||
URL = ''
|
||||
EMAIL = 'vassilis.choutas@tuebingen.mpg.de'
|
||||
AUTHOR = 'Vassilis Choutas'
|
||||
REQUIRES_PYTHON = '>=3.6.0'
|
||||
VERSION = '0.2.0'
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
try:
|
||||
FileNotFoundError
|
||||
except NameError:
|
||||
FileNotFoundError = IOError
|
||||
# Import the README and use it as the long-description.
|
||||
# Note: this will only work if 'README.md' is present in your MANIFEST.in file!
|
||||
try:
|
||||
with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
|
||||
long_description = '\n' + f.read()
|
||||
except FileNotFoundError:
|
||||
long_description = DESCRIPTION
|
||||
|
||||
# Load the package's __version__.py module as a dictionary.
|
||||
about = {}
|
||||
if not VERSION:
|
||||
with open(os.path.join(here, NAME, '__version__.py')) as f:
|
||||
exec(f.read(), about)
|
||||
else:
|
||||
about['__version__'] = VERSION
|
||||
|
||||
mesh_mesh_intersect_src_files = [
|
||||
'src/mesh_mesh_intersect.cpp', 'src/mesh_mesh_intersect_cuda_op.cu']
|
||||
mesh_mesh_intersect_include_dirs = torch.utils.cpp_extension.include_paths() + [
|
||||
osp.abspath('include'),
|
||||
osp.abspath(osp.expandvars('$CUDA_SAMPLES_INC'))]
|
||||
|
||||
mesh_mesh_intersect_extra_compile_args = {
|
||||
'nvcc': ['-DPRINT_TIMINGS=0', '-DDEBUG_PRINT=0',
|
||||
'-DERROR_CHECKING=1',
|
||||
'-DCOLLISION_ORDERING=1'],
|
||||
'cxx': []}
|
||||
mesh_mesh_intersect_extension = CUDAExtension(
|
||||
'mesh_mesh_intersect_cuda', mesh_mesh_intersect_src_files,
|
||||
include_dirs=mesh_mesh_intersect_include_dirs,
|
||||
extra_compile_args=mesh_mesh_intersect_extra_compile_args)
|
||||
|
||||
render_reqs = ['pyrender>=0.1.23', 'trimesh>=2.37.6', 'shapely']
|
||||
|
||||
setup(name=NAME,
|
||||
version=about['__version__'],
|
||||
description=DESCRIPTION,
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
author=AUTHOR,
|
||||
author_email=EMAIL,
|
||||
python_requires=REQUIRES_PYTHON,
|
||||
url=URL,
|
||||
packages=find_packages(),
|
||||
ext_modules=[mesh_mesh_intersect_extension],
|
||||
classifiers=[
|
||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||
"Environment :: Console",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7"],
|
||||
install_requires=[
|
||||
'torch>=1.6.0',
|
||||
],
|
||||
extras_require={
|
||||
'render': render_reqs,
|
||||
'all': render_reqs
|
||||
},
|
||||
cmdclass={'build_ext': BuildExtension})
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
||||
holder of all proprietary rights on this computer program.
|
||||
You can only use this computer program if you have closed
|
||||
a license agreement with MPG or you get the right to use the computer
|
||||
program from someone who is authorized to grant you that right.
|
||||
Any use of the computer program without a valid license is prohibited and
|
||||
liable to prosecution.
|
||||
|
||||
Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
||||
der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
||||
for Intelligent Systems. All rights reserved.
|
||||
|
||||
Contact: ps-license@tuebingen.mpg.de
|
||||
*/
|
||||
|
||||
#include <torch/extension.h>
|
||||
#include <vector>
|
||||
|
||||
#define CHECK_CUDA(x) \
|
||||
TORCH_CHECK(x.device().type() == torch::kCUDA, #x " must be a CUDA tensor")
|
||||
#define CHECK_CONTIGUOUS(x) \
|
||||
TORCH_CHECK(x.is_contiguous(), #x " must be contiguous")
|
||||
#define CHECK_INPUT(x) \
|
||||
CHECK_CUDA(x); \
|
||||
CHECK_CONTIGUOUS(x)
|
||||
|
||||
|
||||
void mesh_mesh_intersection_forward(const torch::Tensor &query_triangles,
|
||||
const torch::Tensor &target_triangles,
|
||||
torch::Tensor &collision_faces,
|
||||
torch::Tensor &collision_bcs,
|
||||
int max_collisions = 16,
|
||||
bool print_timings = false);
|
||||
|
||||
std::vector<torch::Tensor>
|
||||
mesh_to_mesh_intersection(torch::Tensor query_triangles,
|
||||
torch::Tensor target_triangles,
|
||||
int max_collisions = 16, bool print_timings = false) {
|
||||
CHECK_INPUT(query_triangles);
|
||||
CHECK_INPUT(target_triangles);
|
||||
torch::Tensor collision_faces =
|
||||
-1 * torch::ones({query_triangles.size(0),
|
||||
query_triangles.size(1) * max_collisions},
|
||||
torch::device(query_triangles.device())
|
||||
.dtype(torch::ScalarType::Long));
|
||||
torch::Tensor collision_bcs = torch::zeros(
|
||||
{query_triangles.size(0), query_triangles.size(1) * max_collisions, 2, 3},
|
||||
torch::device(query_triangles.device()).dtype(query_triangles.dtype()));
|
||||
|
||||
mesh_mesh_intersection_forward(query_triangles, target_triangles,
|
||||
collision_faces, collision_bcs,
|
||||
max_collisions);
|
||||
|
||||
return {torch::autograd::make_variable(collision_faces, false),
|
||||
torch::autograd::make_variable(collision_bcs, false)};
|
||||
}
|
||||
|
||||
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
|
||||
m.def("mesh_to_mesh_forward", &mesh_to_mesh_intersection,
|
||||
"BVH mesh-to-mesh intersection forward (CUDA)",
|
||||
py::arg("query_triangles"), py::arg("target_triangles"),
|
||||
py::arg("max_collisions") = 16, py::arg("print_timings") = false);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
""" Ref: https://github.com/muelea/shapy/blob/master/regressor/hbw_evaluation/test_submission_format.py """
|
||||
|
||||
import argparse
|
||||
import numpy as np
|
||||
|
||||
|
||||
def test_submission_file_format(
|
||||
npz_file: str,
|
||||
model_type: str = 'smplx'
|
||||
):
|
||||
submission = np.load(npz_file)
|
||||
|
||||
# check if keys are named correctly
|
||||
keys = [x for x in submission.keys()]
|
||||
assert 'image_name' in keys and 'v_shaped' in keys, \
|
||||
f"Keys are not correct. Got {keys}, but expected ['image_name', 'v_shaped']"
|
||||
|
||||
image_names = submission['image_name']
|
||||
v_shapeds = submission['v_shaped']
|
||||
|
||||
# check if shape and type are correct
|
||||
assert type(image_names) == np.ndarray, \
|
||||
f"Type of key image_name is not correct. {type(image_names)} given, but np.ndarray expected."
|
||||
assert image_names.shape == (1631,), \
|
||||
f"Shape of key image_name is not correct. {image_names.shape} given, but (1631,) expected."
|
||||
|
||||
assert type(v_shapeds) == np.ndarray, \
|
||||
f"Type of key v_shaped is not correct. {type(image_names)} given, but np.ndarray expected."
|
||||
|
||||
if model_type == 'smplx':
|
||||
assert v_shapeds.shape == (1631, 10475, 3), \
|
||||
f"Shape of key v_shaped is not correct. {v_shapeds.shape} given, but (1631, 10475, 3) expected."
|
||||
else:
|
||||
assert v_shapeds.shape == (1631, 6890, 3), \
|
||||
f"Shape of key v_shaped is not correct. {v_shapeds.shape} given, but (1631, 6890, 3) expected."
|
||||
|
||||
# check if each image has a prediction
|
||||
hbw_images_gt = np.load('../data/SHAPY/hbw_testset_image_names.npy')
|
||||
check_prediction_available = np.isin(hbw_images_gt, image_names)
|
||||
assert np.all(check_prediction_available), \
|
||||
f"Images without predition exist! Missing predictions: \
|
||||
\n {hbw_images_gt[~check_prediction_available]}"
|
||||
|
||||
print(f'Your submission file passed the test.')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('--input-npz-file',
|
||||
dest='input_npz_file', type=str, required=True,
|
||||
help='npz containing labels and body shape parameters.')
|
||||
parser.add_argument('--model-type', choices=['smpl', 'smplx'], type=str,
|
||||
default='smplx',
|
||||
help='The model type used for body shape prediction. ')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
test_submission_file_format(
|
||||
npz_file=args.input_npz_file,
|
||||
model_type=args.model_type
|
||||
)
|
||||
@@ -0,0 +1,51 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class SPEC(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(SPEC, self).__init__(transform, data_split)
|
||||
|
||||
pre_prc_file_train = 'spec_train_smpl.npz'
|
||||
pre_prc_file_test = 'spec_test_smpl.npz'
|
||||
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', pre_prc_file_train)
|
||||
else:
|
||||
filename = getattr(cfg, 'filename', pre_prc_file_test)
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'SPEC')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', filename)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.img_shape = (1080, 1920) # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
# check image shape
|
||||
img_path = osp.join(self.img_dir, np.load(self.annot_path)['image_path'][0])
|
||||
img_shape = cv2.imread(img_path).shape[:2]
|
||||
assert self.img_shape == img_shape, 'image shape is incorrect: {} vs {}'.format(self.img_shape, img_shape)
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,52 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class SSP3D(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(SSP3D, self).__init__(transform, data_split)
|
||||
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', 'ssp3d_230525_311.npz')
|
||||
self.img_shape = (512, 512) # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', 'ssp3d_230525_311.npz')
|
||||
else:
|
||||
raise ValueError('SSP3D test set is not support')
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'SSP3D')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
|
||||
# check image shape
|
||||
img_path = osp.join(self.img_dir, np.load(self.annot_path)['image_path'][0])
|
||||
img_shape = cv2.imread(img_path).shape[:2]
|
||||
assert self.img_shape == img_shape, 'image shape is incorrect: {} vs {}'.format(self.img_shape, img_shape)
|
||||
|
||||
# load data or cache
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class SynBody(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(SynBody, self).__init__(transform, data_split)
|
||||
|
||||
filename = 'synbody_train_230521_04000_fix_betas.npz'
|
||||
self.img_dir = osp.join(cfg.data_dir, 'SynBody')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', filename)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.img_shape = (720, 1280) # (h, w)
|
||||
self.cam_param = {
|
||||
'focal': (540, 540), # (fx, fy)
|
||||
'princpt': (640, 360) # (cx, cy)
|
||||
}
|
||||
|
||||
# check image shape
|
||||
img_path = osp.join(self.img_dir, np.load(self.annot_path)['image_path'][0])
|
||||
img_shape = cv2.imread(img_path).shape[:2]
|
||||
assert self.img_shape == img_shape, 'image shape is incorrect: {} vs {}'.format(self.img_shape, img_shape)
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
@@ -0,0 +1,59 @@
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
# 'talkshow_smplx_chemistry_path.npz' zipfile.BadZipFile: File is not a zip file
|
||||
# ['talkshow_smplx_conan.npz',
|
||||
# 'talkshow_smplx_oliver_path.npz', 'talkshow_smplx_seth.npz']:
|
||||
|
||||
class Talkshow(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(Talkshow, self).__init__(transform, data_split)
|
||||
sample_rate = getattr(cfg, 'Talkshow_train_sample_interval', 1)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', 'talkshow_smplx.npz')
|
||||
self.img_shape = None # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
|
||||
self.datalist = []
|
||||
for pre_prc_file in ['talkshow_smplx_chemistry.npz', 'talkshow_smplx_conan.npz',
|
||||
'talkshow_smplx_oliver.npz', 'talkshow_smplx_seth.npz']:
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', pre_prc_file)
|
||||
else:
|
||||
raise ValueError('Talkshow test set is not support')
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'Talkshow')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
|
||||
# check image shape
|
||||
# img_path = osp.join(self.img_dir, np.load(self.annot_path)['image_path'][0])
|
||||
# img_shape = cv2.imread(img_path).shape[:2]
|
||||
# assert self.img_shape == img_shape, 'image shape is incorrect: {} vs {}'.format(self.img_shape, img_shape)
|
||||
|
||||
# load data
|
||||
datalist_slice = self.load_data(sample_rate)
|
||||
self.datalist.extend(datalist_slice)
|
||||
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
+1147
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
||||
|
||||
import os
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torch
|
||||
import cv2
|
||||
import json
|
||||
import copy
|
||||
from pycocotools.coco import COCO
|
||||
from config import cfg
|
||||
from utils.human_models import smpl_x
|
||||
from utils.preprocessing import load_img, process_bbox, augmentation, process_db_coord, process_human_model_output, \
|
||||
get_fitting_error_3D
|
||||
from utils.transforms import world2cam, cam2pixel, rigid_align
|
||||
from humandata import HumanDataset
|
||||
|
||||
|
||||
class UP3D(HumanDataset):
|
||||
def __init__(self, transform, data_split):
|
||||
super(UP3D, self).__init__(transform, data_split)
|
||||
|
||||
if self.data_split == 'train':
|
||||
filename = getattr(cfg, 'filename', 'up3d_trainval.npz')
|
||||
else:
|
||||
raise ValueError('UP3D test set is not support')
|
||||
|
||||
self.img_dir = osp.join(cfg.data_dir, 'UP3D')
|
||||
self.annot_path = osp.join(cfg.data_dir, 'preprocessed_datasets', filename)
|
||||
self.annot_path_cache = osp.join(cfg.data_dir, 'cache', filename)
|
||||
self.use_cache = getattr(cfg, 'use_cache', False)
|
||||
self.img_shape = None # (h, w)
|
||||
self.cam_param = {}
|
||||
|
||||
# load data or cache
|
||||
if self.use_cache and osp.isfile(self.annot_path_cache):
|
||||
print(f'[{self.__class__.__name__}] loading cache from {self.annot_path_cache}')
|
||||
self.datalist = self.load_cache(self.annot_path_cache)
|
||||
else:
|
||||
if self.use_cache:
|
||||
print(f'[{self.__class__.__name__}] Cache not found, generating cache...')
|
||||
self.datalist = self.load_data(
|
||||
train_sample_interval=getattr(cfg, f'{self.__class__.__name__}_train_sample_interval', 1))
|
||||
if self.use_cache:
|
||||
self.save_cache(self.annot_path_cache, self.datalist)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user