Initial snapshot: hand motion pipeline (Dyn-HaMR + dex-retargeting + SPIDER)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .builder import DATASETS, PIPELINES, build_dataloader, build_dataset
|
||||
from .dataset_info import DatasetInfo
|
||||
from .pipelines import Compose
|
||||
from .samplers import DistributedSampler
|
||||
|
||||
from .datasets import ( # isort:skip
|
||||
AnimalATRWDataset, AnimalFlyDataset, AnimalHorse10Dataset,
|
||||
AnimalLocustDataset, AnimalMacaqueDataset, AnimalPoseDataset,
|
||||
AnimalZebraDataset, Body3DH36MDataset, BottomUpAicDataset,
|
||||
BottomUpCocoDataset, BottomUpCocoWholeBodyDataset,
|
||||
BottomUpCrowdPoseDataset, BottomUpMhpDataset, DeepFashionDataset,
|
||||
Face300WDataset, FaceAFLWDataset, FaceCocoWholeBodyDataset,
|
||||
FaceCOFWDataset, FaceWFLWDataset, FreiHandDataset,
|
||||
HandCocoWholeBodyDataset, InterHand2DDataset, InterHand3DDataset,
|
||||
MeshAdversarialDataset, MeshH36MDataset, MeshMixDataset, MoshDataset,
|
||||
OneHand10KDataset, PanopticDataset, TopDownAicDataset, TopDownCocoDataset,
|
||||
TopDownCocoWholeBodyDataset, TopDownCrowdPoseDataset,
|
||||
TopDownFreiHandDataset, TopDownH36MDataset, TopDownJhmdbDataset,
|
||||
TopDownMhpDataset, TopDownMpiiDataset, TopDownMpiiTrbDataset,
|
||||
TopDownOCHumanDataset, TopDownOneHand10KDataset, TopDownPanopticDataset,
|
||||
TopDownPoseTrack18Dataset, TopDownPoseTrack18VideoDataset)
|
||||
|
||||
__all__ = [
|
||||
'TopDownCocoDataset', 'BottomUpCocoDataset', 'BottomUpMhpDataset',
|
||||
'BottomUpAicDataset', 'BottomUpCocoWholeBodyDataset', 'TopDownMpiiDataset',
|
||||
'TopDownMpiiTrbDataset', 'OneHand10KDataset', 'PanopticDataset',
|
||||
'HandCocoWholeBodyDataset', 'FreiHandDataset', 'InterHand2DDataset',
|
||||
'InterHand3DDataset', 'TopDownOCHumanDataset', 'TopDownAicDataset',
|
||||
'TopDownCocoWholeBodyDataset', 'MeshH36MDataset', 'MeshMixDataset',
|
||||
'MoshDataset', 'MeshAdversarialDataset', 'TopDownCrowdPoseDataset',
|
||||
'BottomUpCrowdPoseDataset', 'TopDownFreiHandDataset',
|
||||
'TopDownOneHand10KDataset', 'TopDownPanopticDataset',
|
||||
'TopDownPoseTrack18Dataset', 'TopDownJhmdbDataset', 'TopDownMhpDataset',
|
||||
'DeepFashionDataset', 'Face300WDataset', 'FaceAFLWDataset',
|
||||
'FaceWFLWDataset', 'FaceCOFWDataset', 'FaceCocoWholeBodyDataset',
|
||||
'Body3DH36MDataset', 'AnimalHorse10Dataset', 'AnimalMacaqueDataset',
|
||||
'AnimalFlyDataset', 'AnimalLocustDataset', 'AnimalZebraDataset',
|
||||
'AnimalATRWDataset', 'AnimalPoseDataset', 'TopDownH36MDataset',
|
||||
'TopDownPoseTrack18VideoDataset', 'build_dataloader', 'build_dataset',
|
||||
'Compose', 'DistributedSampler', 'DATASETS', 'PIPELINES', 'DatasetInfo'
|
||||
]
|
||||
@@ -0,0 +1,162 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
import platform
|
||||
import random
|
||||
from functools import partial
|
||||
|
||||
import numpy as np
|
||||
from mmcv.parallel import collate
|
||||
from mmcv.runner import get_dist_info
|
||||
from mmcv.utils import Registry, build_from_cfg, is_seq_of
|
||||
from mmcv.utils.parrots_wrapper import _get_dataloader
|
||||
from torch.utils.data.dataset import ConcatDataset
|
||||
|
||||
from .samplers import DistributedSampler
|
||||
|
||||
if platform.system() != 'Windows':
|
||||
# https://github.com/pytorch/pytorch/issues/973
|
||||
import resource
|
||||
rlimit = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||||
base_soft_limit = rlimit[0]
|
||||
hard_limit = rlimit[1]
|
||||
soft_limit = min(max(4096, base_soft_limit), hard_limit)
|
||||
resource.setrlimit(resource.RLIMIT_NOFILE, (soft_limit, hard_limit))
|
||||
|
||||
DATASETS = Registry('dataset')
|
||||
PIPELINES = Registry('pipeline')
|
||||
|
||||
|
||||
def _concat_dataset(cfg, default_args=None):
|
||||
types = cfg['type']
|
||||
ann_files = cfg['ann_file']
|
||||
img_prefixes = cfg.get('img_prefix', None)
|
||||
dataset_infos = cfg.get('dataset_info', None)
|
||||
|
||||
num_joints = cfg['data_cfg'].get('num_joints', None)
|
||||
dataset_channel = cfg['data_cfg'].get('dataset_channel', None)
|
||||
|
||||
datasets = []
|
||||
num_dset = len(ann_files)
|
||||
for i in range(num_dset):
|
||||
cfg_copy = copy.deepcopy(cfg)
|
||||
cfg_copy['ann_file'] = ann_files[i]
|
||||
|
||||
if isinstance(types, (list, tuple)):
|
||||
cfg_copy['type'] = types[i]
|
||||
if isinstance(img_prefixes, (list, tuple)):
|
||||
cfg_copy['img_prefix'] = img_prefixes[i]
|
||||
if isinstance(dataset_infos, (list, tuple)):
|
||||
cfg_copy['dataset_info'] = dataset_infos[i]
|
||||
|
||||
if isinstance(num_joints, (list, tuple)):
|
||||
cfg_copy['data_cfg']['num_joints'] = num_joints[i]
|
||||
|
||||
if is_seq_of(dataset_channel, list):
|
||||
cfg_copy['data_cfg']['dataset_channel'] = dataset_channel[i]
|
||||
|
||||
datasets.append(build_dataset(cfg_copy, default_args))
|
||||
|
||||
return ConcatDataset(datasets)
|
||||
|
||||
|
||||
def build_dataset(cfg, default_args=None):
|
||||
"""Build a dataset from config dict.
|
||||
|
||||
Args:
|
||||
cfg (dict): Config dict. It should at least contain the key "type".
|
||||
default_args (dict, optional): Default initialization arguments.
|
||||
Default: None.
|
||||
|
||||
Returns:
|
||||
Dataset: The constructed dataset.
|
||||
"""
|
||||
from .dataset_wrappers import RepeatDataset
|
||||
|
||||
if isinstance(cfg, (list, tuple)):
|
||||
dataset = ConcatDataset([build_dataset(c, default_args) for c in cfg])
|
||||
elif cfg['type'] == 'ConcatDataset':
|
||||
dataset = ConcatDataset(
|
||||
[build_dataset(c, default_args) for c in cfg['datasets']])
|
||||
elif cfg['type'] == 'RepeatDataset':
|
||||
dataset = RepeatDataset(
|
||||
build_dataset(cfg['dataset'], default_args), cfg['times'])
|
||||
elif isinstance(cfg.get('ann_file'), (list, tuple)):
|
||||
dataset = _concat_dataset(cfg, default_args)
|
||||
else:
|
||||
dataset = build_from_cfg(cfg, DATASETS, default_args)
|
||||
return dataset
|
||||
|
||||
|
||||
def build_dataloader(dataset,
|
||||
samples_per_gpu,
|
||||
workers_per_gpu,
|
||||
num_gpus=1,
|
||||
dist=True,
|
||||
shuffle=True,
|
||||
seed=None,
|
||||
drop_last=True,
|
||||
pin_memory=True,
|
||||
**kwargs):
|
||||
"""Build PyTorch DataLoader.
|
||||
|
||||
In distributed training, each GPU/process has a dataloader.
|
||||
In non-distributed training, there is only one dataloader for all GPUs.
|
||||
|
||||
Args:
|
||||
dataset (Dataset): A PyTorch dataset.
|
||||
samples_per_gpu (int): Number of training samples on each GPU, i.e.,
|
||||
batch size of each GPU.
|
||||
workers_per_gpu (int): How many subprocesses to use for data loading
|
||||
for each GPU.
|
||||
num_gpus (int): Number of GPUs. Only used in non-distributed training.
|
||||
dist (bool): Distributed training/test or not. Default: True.
|
||||
shuffle (bool): Whether to shuffle the data at every epoch.
|
||||
Default: True.
|
||||
drop_last (bool): Whether to drop the last incomplete batch in epoch.
|
||||
Default: True
|
||||
pin_memory (bool): Whether to use pin_memory in DataLoader.
|
||||
Default: True
|
||||
kwargs: any keyword argument to be used to initialize DataLoader
|
||||
|
||||
Returns:
|
||||
DataLoader: A PyTorch dataloader.
|
||||
"""
|
||||
rank, world_size = get_dist_info()
|
||||
if dist:
|
||||
sampler = DistributedSampler(
|
||||
dataset, world_size, rank, shuffle=shuffle, seed=seed)
|
||||
shuffle = False
|
||||
batch_size = samples_per_gpu
|
||||
num_workers = workers_per_gpu
|
||||
else:
|
||||
sampler = None
|
||||
batch_size = num_gpus * samples_per_gpu
|
||||
num_workers = num_gpus * workers_per_gpu
|
||||
|
||||
init_fn = partial(
|
||||
worker_init_fn, num_workers=num_workers, rank=rank,
|
||||
seed=seed) if seed is not None else None
|
||||
|
||||
_, DataLoader = _get_dataloader()
|
||||
data_loader = DataLoader(
|
||||
dataset,
|
||||
batch_size=batch_size,
|
||||
sampler=sampler,
|
||||
num_workers=num_workers,
|
||||
collate_fn=partial(collate, samples_per_gpu=samples_per_gpu),
|
||||
pin_memory=pin_memory,
|
||||
shuffle=shuffle,
|
||||
worker_init_fn=init_fn,
|
||||
drop_last=drop_last,
|
||||
**kwargs)
|
||||
|
||||
return data_loader
|
||||
|
||||
|
||||
def worker_init_fn(worker_id, num_workers, rank, seed):
|
||||
"""Init the random seed for various workers."""
|
||||
# The seed of each worker equals to
|
||||
# num_worker * rank + worker_id + user_seed
|
||||
worker_seed = num_workers * rank + worker_id + seed
|
||||
np.random.seed(worker_seed)
|
||||
random.seed(worker_seed)
|
||||
@@ -0,0 +1,104 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import numpy as np
|
||||
|
||||
|
||||
class DatasetInfo:
|
||||
|
||||
def __init__(self, dataset_info):
|
||||
self._dataset_info = dataset_info
|
||||
self.dataset_name = self._dataset_info['dataset_name']
|
||||
self.paper_info = self._dataset_info['paper_info']
|
||||
self.keypoint_info = self._dataset_info['keypoint_info']
|
||||
self.skeleton_info = self._dataset_info['skeleton_info']
|
||||
self.joint_weights = np.array(
|
||||
self._dataset_info['joint_weights'], dtype=np.float32)[:, None]
|
||||
|
||||
self.sigmas = np.array(self._dataset_info['sigmas'])
|
||||
|
||||
self._parse_keypoint_info()
|
||||
self._parse_skeleton_info()
|
||||
|
||||
def _parse_skeleton_info(self):
|
||||
"""Parse skeleton information.
|
||||
|
||||
- link_num (int): number of links.
|
||||
- skeleton (list((2,))): list of links (id).
|
||||
- skeleton_name (list((2,))): list of links (name).
|
||||
- pose_link_color (np.ndarray): the color of the link for
|
||||
visualization.
|
||||
"""
|
||||
self.link_num = len(self.skeleton_info.keys())
|
||||
self.pose_link_color = []
|
||||
|
||||
self.skeleton_name = []
|
||||
self.skeleton = []
|
||||
for skid in self.skeleton_info.keys():
|
||||
link = self.skeleton_info[skid]['link']
|
||||
self.skeleton_name.append(link)
|
||||
self.skeleton.append([
|
||||
self.keypoint_name2id[link[0]], self.keypoint_name2id[link[1]]
|
||||
])
|
||||
self.pose_link_color.append(self.skeleton_info[skid].get(
|
||||
'color', [255, 128, 0]))
|
||||
self.pose_link_color = np.array(self.pose_link_color)
|
||||
|
||||
def _parse_keypoint_info(self):
|
||||
"""Parse keypoint information.
|
||||
|
||||
- keypoint_num (int): number of keypoints.
|
||||
- keypoint_id2name (dict): mapping keypoint id to keypoint name.
|
||||
- keypoint_name2id (dict): mapping keypoint name to keypoint id.
|
||||
- upper_body_ids (list): a list of keypoints that belong to the
|
||||
upper body.
|
||||
- lower_body_ids (list): a list of keypoints that belong to the
|
||||
lower body.
|
||||
- flip_index (list): list of flip index (id)
|
||||
- flip_pairs (list((2,))): list of flip pairs (id)
|
||||
- flip_index_name (list): list of flip index (name)
|
||||
- flip_pairs_name (list((2,))): list of flip pairs (name)
|
||||
- pose_kpt_color (np.ndarray): the color of the keypoint for
|
||||
visualization.
|
||||
"""
|
||||
|
||||
self.keypoint_num = len(self.keypoint_info.keys())
|
||||
self.keypoint_id2name = {}
|
||||
self.keypoint_name2id = {}
|
||||
|
||||
self.pose_kpt_color = []
|
||||
self.upper_body_ids = []
|
||||
self.lower_body_ids = []
|
||||
|
||||
self.flip_index_name = []
|
||||
self.flip_pairs_name = []
|
||||
|
||||
for kid in self.keypoint_info.keys():
|
||||
|
||||
keypoint_name = self.keypoint_info[kid]['name']
|
||||
self.keypoint_id2name[kid] = keypoint_name
|
||||
self.keypoint_name2id[keypoint_name] = kid
|
||||
self.pose_kpt_color.append(self.keypoint_info[kid].get(
|
||||
'color', [255, 128, 0]))
|
||||
|
||||
type = self.keypoint_info[kid].get('type', '')
|
||||
if type == 'upper':
|
||||
self.upper_body_ids.append(kid)
|
||||
elif type == 'lower':
|
||||
self.lower_body_ids.append(kid)
|
||||
else:
|
||||
pass
|
||||
|
||||
swap_keypoint = self.keypoint_info[kid].get('swap', '')
|
||||
if swap_keypoint == keypoint_name or swap_keypoint == '':
|
||||
self.flip_index_name.append(keypoint_name)
|
||||
else:
|
||||
self.flip_index_name.append(swap_keypoint)
|
||||
if [swap_keypoint, keypoint_name] not in self.flip_pairs_name:
|
||||
self.flip_pairs_name.append([keypoint_name, swap_keypoint])
|
||||
|
||||
self.flip_pairs = [[
|
||||
self.keypoint_name2id[pair[0]], self.keypoint_name2id[pair[1]]
|
||||
] for pair in self.flip_pairs_name]
|
||||
self.flip_index = [
|
||||
self.keypoint_name2id[name] for name in self.flip_index_name
|
||||
]
|
||||
self.pose_kpt_color = np.array(self.pose_kpt_color)
|
||||
@@ -0,0 +1,31 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .builder import DATASETS
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class RepeatDataset:
|
||||
"""A wrapper of repeated dataset.
|
||||
|
||||
The length of repeated dataset will be `times` larger than the original
|
||||
dataset. This is useful when the data loading time is long but the dataset
|
||||
is small. Using RepeatDataset can reduce the data loading time between
|
||||
epochs.
|
||||
|
||||
Args:
|
||||
dataset (:obj:`Dataset`): The dataset to be repeated.
|
||||
times (int): Repeat times.
|
||||
"""
|
||||
|
||||
def __init__(self, dataset, times):
|
||||
self.dataset = dataset
|
||||
self.times = times
|
||||
|
||||
self._ori_len = len(self.dataset)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
"""Get data."""
|
||||
return self.dataset[idx % self._ori_len]
|
||||
|
||||
def __len__(self):
|
||||
"""Length after repetition."""
|
||||
return self.times * self._ori_len
|
||||
@@ -0,0 +1,45 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from ...deprecated import (TopDownFreiHandDataset, TopDownOneHand10KDataset,
|
||||
TopDownPanopticDataset)
|
||||
from .animal import (AnimalATRWDataset, AnimalFlyDataset, AnimalHorse10Dataset,
|
||||
AnimalLocustDataset, AnimalMacaqueDataset,
|
||||
AnimalPoseDataset, AnimalZebraDataset)
|
||||
from .body3d import Body3DH36MDataset, Body3DMviewDirectPanopticDataset
|
||||
from .bottom_up import (BottomUpAicDataset, BottomUpCocoDataset,
|
||||
BottomUpCocoWholeBodyDataset, BottomUpCrowdPoseDataset,
|
||||
BottomUpMhpDataset)
|
||||
from .face import (Face300WDataset, FaceAFLWDataset, FaceCocoWholeBodyDataset,
|
||||
FaceCOFWDataset, FaceWFLWDataset)
|
||||
from .fashion import DeepFashionDataset
|
||||
from .hand import (FreiHandDataset, HandCocoWholeBodyDataset,
|
||||
InterHand2DDataset, InterHand3DDataset, OneHand10KDataset,
|
||||
PanopticDataset)
|
||||
from .mesh import (MeshAdversarialDataset, MeshH36MDataset, MeshMixDataset,
|
||||
MoshDataset)
|
||||
from .top_down import (TopDownAicDataset, TopDownCocoDataset,
|
||||
TopDownCocoWholeBodyDataset, TopDownCrowdPoseDataset,
|
||||
TopDownH36MDataset, TopDownHalpeDataset,
|
||||
TopDownJhmdbDataset, TopDownMhpDataset,
|
||||
TopDownMpiiDataset, TopDownMpiiTrbDataset,
|
||||
TopDownOCHumanDataset, TopDownPoseTrack18Dataset,
|
||||
TopDownPoseTrack18VideoDataset)
|
||||
|
||||
__all__ = [
|
||||
'TopDownCocoDataset', 'BottomUpCocoDataset', 'BottomUpMhpDataset',
|
||||
'BottomUpAicDataset', 'BottomUpCocoWholeBodyDataset', 'TopDownMpiiDataset',
|
||||
'TopDownMpiiTrbDataset', 'OneHand10KDataset', 'PanopticDataset',
|
||||
'HandCocoWholeBodyDataset', 'FreiHandDataset', 'InterHand2DDataset',
|
||||
'InterHand3DDataset', 'TopDownOCHumanDataset', 'TopDownAicDataset',
|
||||
'TopDownCocoWholeBodyDataset', 'MeshH36MDataset', 'MeshMixDataset',
|
||||
'MoshDataset', 'MeshAdversarialDataset', 'TopDownCrowdPoseDataset',
|
||||
'BottomUpCrowdPoseDataset', 'TopDownFreiHandDataset',
|
||||
'TopDownOneHand10KDataset', 'TopDownPanopticDataset',
|
||||
'TopDownPoseTrack18Dataset', 'TopDownJhmdbDataset', 'TopDownMhpDataset',
|
||||
'DeepFashionDataset', 'Face300WDataset', 'FaceAFLWDataset',
|
||||
'FaceWFLWDataset', 'FaceCOFWDataset', 'FaceCocoWholeBodyDataset',
|
||||
'Body3DH36MDataset', 'AnimalHorse10Dataset', 'AnimalMacaqueDataset',
|
||||
'AnimalFlyDataset', 'AnimalLocustDataset', 'AnimalZebraDataset',
|
||||
'AnimalATRWDataset', 'AnimalPoseDataset', 'TopDownH36MDataset',
|
||||
'TopDownHalpeDataset', 'TopDownPoseTrack18VideoDataset',
|
||||
'Body3DMviewDirectPanopticDataset'
|
||||
]
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .animal_ap10k_dataset import AnimalAP10KDataset
|
||||
from .animal_atrw_dataset import AnimalATRWDataset
|
||||
from .animal_fly_dataset import AnimalFlyDataset
|
||||
from .animal_horse10_dataset import AnimalHorse10Dataset
|
||||
from .animal_locust_dataset import AnimalLocustDataset
|
||||
from .animal_macaque_dataset import AnimalMacaqueDataset
|
||||
from .animal_pose_dataset import AnimalPoseDataset
|
||||
from .animal_zebra_dataset import AnimalZebraDataset
|
||||
|
||||
__all__ = [
|
||||
'AnimalHorse10Dataset', 'AnimalMacaqueDataset', 'AnimalFlyDataset',
|
||||
'AnimalLocustDataset', 'AnimalZebraDataset', 'AnimalATRWDataset',
|
||||
'AnimalPoseDataset', 'AnimalAP10KDataset'
|
||||
]
|
||||
Vendored
+367
@@ -0,0 +1,367 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
from xtcocotools.cocoeval import COCOeval
|
||||
|
||||
from ....core.post_processing import oks_nms, soft_oks_nms
|
||||
from ...builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class AnimalAP10KDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""AP-10K dataset for animal pose estimation.
|
||||
|
||||
"AP-10K: A Benchmark for Animal Pose Estimation in the Wild"
|
||||
Neurips Dataset Track'2021.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/2108.12617>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
AP-10K keypoint indexes::
|
||||
|
||||
0: 'L_Eye',
|
||||
1: 'R_Eye',
|
||||
2: 'Nose',
|
||||
3: 'Neck',
|
||||
4: 'root of tail',
|
||||
5: 'L_Shoulder',
|
||||
6: 'L_Elbow',
|
||||
7: 'L_F_Paw',
|
||||
8: 'R_Shoulder',
|
||||
9: 'R_Elbow',
|
||||
10: 'R_F_Paw,
|
||||
11: 'L_Hip',
|
||||
12: 'L_Knee',
|
||||
13: 'L_B_Paw',
|
||||
14: 'R_Hip',
|
||||
15: 'R_Knee',
|
||||
16: 'R_B_Paw'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/ap10k.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.use_gt_bbox = data_cfg['use_gt_bbox']
|
||||
self.bbox_file = data_cfg['bbox_file']
|
||||
self.det_bbox_thr = data_cfg.get('det_bbox_thr', 0.0)
|
||||
|
||||
self.use_nms = data_cfg.get('use_nms', True)
|
||||
self.soft_nms = data_cfg['soft_nms']
|
||||
self.nms_thr = data_cfg['nms_thr']
|
||||
self.oks_thr = data_cfg['oks_thr']
|
||||
self.vis_thr = data_cfg['vis_thr']
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db, self.id2Cat = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
assert self.use_gt_bbox
|
||||
gt_db, id2Cat = self._load_coco_keypoint_annotations()
|
||||
return gt_db, id2Cat
|
||||
|
||||
def _load_coco_keypoint_annotations(self):
|
||||
"""Ground truth bbox and keypoints."""
|
||||
gt_db, id2Cat = [], dict()
|
||||
for img_id in self.img_ids:
|
||||
db_tmp, id2Cat_tmp = self._load_coco_keypoint_annotation_kernel(
|
||||
img_id)
|
||||
gt_db.extend(db_tmp)
|
||||
id2Cat.update({img_id: id2Cat_tmp})
|
||||
return gt_db, id2Cat
|
||||
|
||||
def _load_coco_keypoint_annotation_kernel(self, img_id):
|
||||
"""load annotation from COCOAPI.
|
||||
|
||||
Note:
|
||||
bbox:[x1, y1, w, h]
|
||||
Args:
|
||||
img_id: coco image id
|
||||
Returns:
|
||||
dict: db entry
|
||||
"""
|
||||
img_ann = self.coco.loadImgs(img_id)[0]
|
||||
width = img_ann['width']
|
||||
height = img_ann['height']
|
||||
num_joints = self.ann_info['num_joints']
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
# sanitize bboxes
|
||||
valid_objs = []
|
||||
for obj in objs:
|
||||
if 'bbox' not in obj:
|
||||
continue
|
||||
x, y, w, h = obj['bbox']
|
||||
x1 = max(0, x)
|
||||
y1 = max(0, y)
|
||||
x2 = min(width - 1, x1 + max(0, w - 1))
|
||||
y2 = min(height - 1, y1 + max(0, h - 1))
|
||||
if ('area' not in obj or obj['area'] > 0) and x2 > x1 and y2 > y1:
|
||||
obj['clean_bbox'] = [x1, y1, x2 - x1, y2 - y1]
|
||||
valid_objs.append(obj)
|
||||
objs = valid_objs
|
||||
|
||||
bbox_id = 0
|
||||
rec = []
|
||||
id2Cat = []
|
||||
for obj in objs:
|
||||
if 'keypoints' not in obj:
|
||||
continue
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
if 'num_keypoints' in obj and obj['num_keypoints'] == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
center, scale = self._xywh2cs(*obj['clean_bbox'][:4])
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
rec.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'bbox': obj['clean_bbox'][:4],
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
category = obj['category_id']
|
||||
id2Cat.append({
|
||||
'image_file': image_file,
|
||||
'bbox_id': bbox_id,
|
||||
'category': category,
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
|
||||
return rec, id2Cat
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='mAP', **kwargs):
|
||||
"""Evaluate coco keypoint results. The pose prediction results will be
|
||||
saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['data/coco/val2017\
|
||||
/000000393226.jpg']
|
||||
- heatmap (np.ndarray[N, K, H, W]): model output heatmap
|
||||
- bbox_id (list(int)).
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed. Defaults: 'mAP'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['mAP']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = defaultdict(list)
|
||||
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
cat = self.id2Cat[image_id][bbox_ids[i]]['category']
|
||||
kpts[image_id].append({
|
||||
'keypoints': preds[i],
|
||||
'center': boxes[i][0:2],
|
||||
'scale': boxes[i][2:4],
|
||||
'area': boxes[i][4],
|
||||
'score': boxes[i][5],
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i],
|
||||
'category': cat
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
# rescoring and oks nms
|
||||
num_joints = self.ann_info['num_joints']
|
||||
vis_thr = self.vis_thr
|
||||
oks_thr = self.oks_thr
|
||||
valid_kpts = []
|
||||
for image_id in kpts.keys():
|
||||
img_kpts = kpts[image_id]
|
||||
for n_p in img_kpts:
|
||||
box_score = n_p['score']
|
||||
kpt_score = 0
|
||||
valid_num = 0
|
||||
for n_jt in range(0, num_joints):
|
||||
t_s = n_p['keypoints'][n_jt][2]
|
||||
if t_s > vis_thr:
|
||||
kpt_score = kpt_score + t_s
|
||||
valid_num = valid_num + 1
|
||||
if valid_num != 0:
|
||||
kpt_score = kpt_score / valid_num
|
||||
# rescoring
|
||||
n_p['score'] = kpt_score * box_score
|
||||
|
||||
if self.use_nms:
|
||||
nms = soft_oks_nms if self.soft_nms else oks_nms
|
||||
keep = nms(list(img_kpts), oks_thr, sigmas=self.sigmas)
|
||||
valid_kpts.append([img_kpts[_keep] for _keep in keep])
|
||||
else:
|
||||
valid_kpts.append(img_kpts)
|
||||
|
||||
self._write_coco_keypoint_results(valid_kpts, res_file)
|
||||
|
||||
info_str = self._do_python_keypoint_eval(res_file)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
|
||||
def _write_coco_keypoint_results(self, keypoints, res_file):
|
||||
"""Write results into a json file."""
|
||||
data_pack = [{
|
||||
'cat_id': self._class_to_coco_ind[cls],
|
||||
'cls_ind': cls_ind,
|
||||
'cls': cls,
|
||||
'ann_type': 'keypoints',
|
||||
'keypoints': keypoints
|
||||
} for cls_ind, cls in enumerate(self.classes)
|
||||
if not cls == '__background__']
|
||||
|
||||
results = self._coco_keypoint_results_one_category_kernel(data_pack[0])
|
||||
|
||||
with open(res_file, 'w') as f:
|
||||
json.dump(results, f, sort_keys=True, indent=4)
|
||||
|
||||
def _coco_keypoint_results_one_category_kernel(self, data_pack):
|
||||
"""Get coco keypoint results."""
|
||||
keypoints = data_pack['keypoints']
|
||||
cat_results = []
|
||||
|
||||
for img_kpts in keypoints:
|
||||
if len(img_kpts) == 0:
|
||||
continue
|
||||
|
||||
_key_points = np.array(
|
||||
[img_kpt['keypoints'] for img_kpt in img_kpts])
|
||||
key_points = _key_points.reshape(-1,
|
||||
self.ann_info['num_joints'] * 3)
|
||||
|
||||
result = [{
|
||||
'image_id': img_kpt['image_id'],
|
||||
'category_id': img_kpt['category'],
|
||||
'keypoints': key_point.tolist(),
|
||||
'score': float(img_kpt['score']),
|
||||
'center': img_kpt['center'].tolist(),
|
||||
'scale': img_kpt['scale'].tolist()
|
||||
} for img_kpt, key_point in zip(img_kpts, key_points)]
|
||||
|
||||
cat_results.extend(result)
|
||||
|
||||
return cat_results
|
||||
|
||||
def _do_python_keypoint_eval(self, res_file):
|
||||
"""Keypoint evaluation using COCOAPI."""
|
||||
coco_det = self.coco.loadRes(res_file)
|
||||
coco_eval = COCOeval(self.coco, coco_det, 'keypoints', self.sigmas)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
stats_names = [
|
||||
'AP', 'AP .5', 'AP .75', 'AP (M)', 'AP (L)', 'AR', 'AR .5',
|
||||
'AR .75', 'AR (M)', 'AR (L)'
|
||||
]
|
||||
|
||||
info_str = list(zip(stats_names, coco_eval.stats))
|
||||
|
||||
return info_str
|
||||
|
||||
def _sort_and_unique_bboxes(self, kpts, key='bbox_id'):
|
||||
"""sort kpts and remove the repeated ones."""
|
||||
for img_id, persons in kpts.items():
|
||||
num = len(persons)
|
||||
kpts[img_id] = sorted(kpts[img_id], key=lambda x: x[key])
|
||||
for i in range(num - 1, 0, -1):
|
||||
if kpts[img_id][i][key] == kpts[img_id][i - 1][key]:
|
||||
del kpts[img_id][i]
|
||||
|
||||
return kpts
|
||||
Vendored
+353
@@ -0,0 +1,353 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
from xtcocotools.cocoeval import COCOeval
|
||||
|
||||
from ....core.post_processing import oks_nms, soft_oks_nms
|
||||
from ...builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class AnimalATRWDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""ATRW dataset for animal pose estimation.
|
||||
|
||||
"ATRW: A Benchmark for Amur Tiger Re-identification in the Wild"
|
||||
ACM MM'2020.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1906.05586>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
ATRW keypoint indexes::
|
||||
|
||||
0: "left_ear",
|
||||
1: "right_ear",
|
||||
2: "nose",
|
||||
3: "right_shoulder",
|
||||
4: "right_front_paw",
|
||||
5: "left_shoulder",
|
||||
6: "left_front_paw",
|
||||
7: "right_hip",
|
||||
8: "right_knee",
|
||||
9: "right_back_paw",
|
||||
10: "left_hip",
|
||||
11: "left_knee",
|
||||
12: "left_back_paw",
|
||||
13: "tail",
|
||||
14: "center"
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/atrw.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.use_gt_bbox = data_cfg['use_gt_bbox']
|
||||
self.bbox_file = data_cfg['bbox_file']
|
||||
self.det_bbox_thr = data_cfg.get('det_bbox_thr', 0.0)
|
||||
self.use_nms = data_cfg.get('use_nms', True)
|
||||
self.soft_nms = data_cfg['soft_nms']
|
||||
self.nms_thr = data_cfg['nms_thr']
|
||||
self.oks_thr = data_cfg['oks_thr']
|
||||
self.vis_thr = data_cfg['vis_thr']
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
assert self.use_gt_bbox
|
||||
gt_db = self._load_coco_keypoint_annotations()
|
||||
return gt_db
|
||||
|
||||
def _load_coco_keypoint_annotations(self):
|
||||
"""Ground truth bbox and keypoints."""
|
||||
gt_db = []
|
||||
for img_id in self.img_ids:
|
||||
gt_db.extend(self._load_coco_keypoint_annotation_kernel(img_id))
|
||||
return gt_db
|
||||
|
||||
def _load_coco_keypoint_annotation_kernel(self, img_id):
|
||||
"""load annotation from COCOAPI.
|
||||
|
||||
Note:
|
||||
bbox:[x1, y1, w, h]
|
||||
Args:
|
||||
img_id: coco image id
|
||||
Returns:
|
||||
dict: db entry
|
||||
"""
|
||||
img_ann = self.coco.loadImgs(img_id)[0]
|
||||
width = img_ann['width']
|
||||
height = img_ann['height']
|
||||
num_joints = self.ann_info['num_joints']
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
# sanitize bboxes
|
||||
valid_objs = []
|
||||
for obj in objs:
|
||||
if 'bbox' not in obj:
|
||||
continue
|
||||
x, y, w, h = obj['bbox']
|
||||
x1 = max(0, x)
|
||||
y1 = max(0, y)
|
||||
x2 = min(width - 1, x1 + max(0, w - 1))
|
||||
y2 = min(height - 1, y1 + max(0, h - 1))
|
||||
if ('area' not in obj or obj['area'] > 0) and x2 > x1 and y2 > y1:
|
||||
obj['clean_bbox'] = [x1, y1, x2 - x1, y2 - y1]
|
||||
valid_objs.append(obj)
|
||||
objs = valid_objs
|
||||
|
||||
bbox_id = 0
|
||||
rec = []
|
||||
for obj in objs:
|
||||
if 'keypoints' not in obj:
|
||||
continue
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
if 'num_keypoints' in obj and obj['num_keypoints'] == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
center, scale = self._xywh2cs(*obj['clean_bbox'][:4], padding=1.0)
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
rec.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'bbox': obj['clean_bbox'][:4],
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
|
||||
return rec
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='mAP', **kwargs):
|
||||
"""Evaluate coco keypoint results. The pose prediction results will be
|
||||
saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['data/coco/val2017\
|
||||
/000000393226.jpg']
|
||||
- heatmap (np.ndarray[N, K, H, W]): model output heatmap
|
||||
- bbox_id (list(int)).
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed. Defaults: 'mAP'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['mAP']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = defaultdict(list)
|
||||
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
kpts[image_id].append({
|
||||
'keypoints': preds[i],
|
||||
'center': boxes[i][0:2],
|
||||
'scale': boxes[i][2:4],
|
||||
'area': boxes[i][4],
|
||||
'score': boxes[i][5],
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
# rescoring and oks nms
|
||||
num_joints = self.ann_info['num_joints']
|
||||
vis_thr = self.vis_thr
|
||||
oks_thr = self.oks_thr
|
||||
valid_kpts = []
|
||||
for image_id in kpts.keys():
|
||||
img_kpts = kpts[image_id]
|
||||
for n_p in img_kpts:
|
||||
box_score = n_p['score']
|
||||
kpt_score = 0
|
||||
valid_num = 0
|
||||
for n_jt in range(0, num_joints):
|
||||
t_s = n_p['keypoints'][n_jt][2]
|
||||
if t_s > vis_thr:
|
||||
kpt_score = kpt_score + t_s
|
||||
valid_num = valid_num + 1
|
||||
if valid_num != 0:
|
||||
kpt_score = kpt_score / valid_num
|
||||
# rescoring
|
||||
n_p['score'] = kpt_score * box_score
|
||||
|
||||
if self.use_nms:
|
||||
nms = soft_oks_nms if self.soft_nms else oks_nms
|
||||
keep = nms(list(img_kpts), oks_thr, sigmas=self.sigmas)
|
||||
valid_kpts.append([img_kpts[_keep] for _keep in keep])
|
||||
else:
|
||||
valid_kpts.append(img_kpts)
|
||||
|
||||
self._write_coco_keypoint_results(valid_kpts, res_file)
|
||||
|
||||
info_str = self._do_python_keypoint_eval(res_file)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
|
||||
def _write_coco_keypoint_results(self, keypoints, res_file):
|
||||
"""Write results into a json file."""
|
||||
data_pack = [{
|
||||
'cat_id': self._class_to_coco_ind[cls],
|
||||
'cls_ind': cls_ind,
|
||||
'cls': cls,
|
||||
'ann_type': 'keypoints',
|
||||
'keypoints': keypoints
|
||||
} for cls_ind, cls in enumerate(self.classes)
|
||||
if not cls == '__background__']
|
||||
|
||||
results = self._coco_keypoint_results_one_category_kernel(data_pack[0])
|
||||
|
||||
with open(res_file, 'w') as f:
|
||||
json.dump(results, f, sort_keys=True, indent=4)
|
||||
|
||||
def _coco_keypoint_results_one_category_kernel(self, data_pack):
|
||||
"""Get coco keypoint results."""
|
||||
cat_id = data_pack['cat_id']
|
||||
keypoints = data_pack['keypoints']
|
||||
cat_results = []
|
||||
|
||||
for img_kpts in keypoints:
|
||||
if len(img_kpts) == 0:
|
||||
continue
|
||||
|
||||
_key_points = np.array(
|
||||
[img_kpt['keypoints'] for img_kpt in img_kpts])
|
||||
key_points = _key_points.reshape(-1,
|
||||
self.ann_info['num_joints'] * 3)
|
||||
|
||||
result = [{
|
||||
'image_id': img_kpt['image_id'],
|
||||
'category_id': cat_id,
|
||||
'keypoints': key_point.tolist(),
|
||||
'score': float(img_kpt['score']),
|
||||
'center': img_kpt['center'].tolist(),
|
||||
'scale': img_kpt['scale'].tolist()
|
||||
} for img_kpt, key_point in zip(img_kpts, key_points)]
|
||||
|
||||
cat_results.extend(result)
|
||||
|
||||
return cat_results
|
||||
|
||||
def _do_python_keypoint_eval(self, res_file):
|
||||
"""Keypoint evaluation using COCOAPI."""
|
||||
coco_det = self.coco.loadRes(res_file)
|
||||
coco_eval = COCOeval(self.coco, coco_det, 'keypoints', self.sigmas)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
stats_names = [
|
||||
'AP', 'AP .5', 'AP .75', 'AP (M)', 'AP (L)', 'AR', 'AR .5',
|
||||
'AR .75', 'AR (M)', 'AR (L)'
|
||||
]
|
||||
|
||||
info_str = list(zip(stats_names, coco_eval.stats))
|
||||
|
||||
return info_str
|
||||
|
||||
def _sort_and_unique_bboxes(self, kpts, key='bbox_id'):
|
||||
"""sort kpts and remove the repeated ones."""
|
||||
for img_id, persons in kpts.items():
|
||||
num = len(persons)
|
||||
kpts[img_id] = sorted(kpts[img_id], key=lambda x: x[key])
|
||||
for i in range(num - 1, 0, -1):
|
||||
if kpts[img_id][i][key] == kpts[img_id][i - 1][key]:
|
||||
del kpts[img_id][i]
|
||||
|
||||
return kpts
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from abc import ABCMeta
|
||||
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
|
||||
class AnimalBaseDataset(Dataset, metaclass=ABCMeta):
|
||||
"""This class has been deprecated and replaced by
|
||||
Kpt2dSviewRgbImgTopDownDataset."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
raise (ImportError(
|
||||
'AnimalBaseDataset has been replaced by '
|
||||
'Kpt2dSviewRgbImgTopDownDataset,'
|
||||
'check https://github.com/open-mmlab/mmpose/pull/663 for details.')
|
||||
)
|
||||
Vendored
+215
@@ -0,0 +1,215 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from ...builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class AnimalFlyDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""AnimalFlyDataset for animal pose estimation.
|
||||
|
||||
"Fast animal pose estimation using deep neural networks"
|
||||
Nature methods'2019. More details can be found in the `paper
|
||||
<https://www.biorxiv.org/content/biorxiv/\
|
||||
early/2018/05/25/331181.full.pdf>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
Vinegar Fly keypoint indexes::
|
||||
|
||||
0: "head",
|
||||
1: "eyeL",
|
||||
2: "eyeR",
|
||||
3: "neck",
|
||||
4: "thorax",
|
||||
5: "abdomen",
|
||||
6: "forelegR1",
|
||||
7: "forelegR2",
|
||||
8: "forelegR3",
|
||||
9: "forelegR4",
|
||||
10: "midlegR1",
|
||||
11: "midlegR2",
|
||||
12: "midlegR3",
|
||||
13: "midlegR4",
|
||||
14: "hindlegR1",
|
||||
15: "hindlegR2",
|
||||
16: "hindlegR3",
|
||||
17: "hindlegR4",
|
||||
18: "forelegL1",
|
||||
19: "forelegL2",
|
||||
20: "forelegL3",
|
||||
21: "forelegL4",
|
||||
22: "midlegL1",
|
||||
23: "midlegL2",
|
||||
24: "midlegL3",
|
||||
25: "midlegL4",
|
||||
26: "hindlegL1",
|
||||
27: "hindlegL2",
|
||||
28: "hindlegL3",
|
||||
29: "hindlegL4",
|
||||
30: "wingL",
|
||||
31: "wingR"
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/fly.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
num_joints = self.ann_info['num_joints']
|
||||
for img_id in self.img_ids:
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
for obj in objs:
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
# the ori image is 192x192
|
||||
center, scale = self._xywh2cs(0, 0, 192, 192, 0.8)
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': obj['bbox'],
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='PCK', **kwargs):
|
||||
"""Evaluate Fly keypoint results. The pose prediction results will be
|
||||
saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['Test/source/0.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
|
||||
res_folder (str): Path of directory to save the results.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'PCK', 'AUC', 'EPE'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['PCK', 'AUC', 'EPE']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
Vendored
+220
@@ -0,0 +1,220 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from ...builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class AnimalHorse10Dataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""AnimalHorse10Dataset for animal pose estimation.
|
||||
|
||||
"Pretraining boosts out-of-domain robustness for pose estimation"
|
||||
WACV'2021. More details can be found in the `paper
|
||||
<https://arxiv.org/pdf/1909.11229.pdf>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
Horse-10 keypoint indexes::
|
||||
|
||||
0: 'Nose',
|
||||
1: 'Eye',
|
||||
2: 'Nearknee',
|
||||
3: 'Nearfrontfetlock',
|
||||
4: 'Nearfrontfoot',
|
||||
5: 'Offknee',
|
||||
6: 'Offfrontfetlock',
|
||||
7: 'Offfrontfoot',
|
||||
8: 'Shoulder',
|
||||
9: 'Midshoulder',
|
||||
10: 'Elbow',
|
||||
11: 'Girth',
|
||||
12: 'Wither',
|
||||
13: 'Nearhindhock',
|
||||
14: 'Nearhindfetlock',
|
||||
15: 'Nearhindfoot',
|
||||
16: 'Hip',
|
||||
17: 'Stifle',
|
||||
18: 'Offhindhock',
|
||||
19: 'Offhindfetlock',
|
||||
20: 'Offhindfoot',
|
||||
21: 'Ischium'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/horse10.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
num_joints = self.ann_info['num_joints']
|
||||
for img_id in self.img_ids:
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
for obj in objs:
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
# use 1.25 padded bbox as input
|
||||
center, scale = self._xywh2cs(*obj['bbox'][:4], 1.25)
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': obj['bbox'],
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
def _get_normalize_factor(self, gts):
|
||||
"""Get inter-ocular distance as the normalize factor, measured as the
|
||||
Euclidean distance between the outer corners of the eyes.
|
||||
|
||||
Args:
|
||||
gts (np.ndarray[N, K, 2]): Groundtruth keypoint location.
|
||||
|
||||
Returns:
|
||||
np.ndarray[N, 2]: normalized factor
|
||||
"""
|
||||
|
||||
interocular = np.linalg.norm(
|
||||
gts[:, 0, :] - gts[:, 1, :], axis=1, keepdims=True)
|
||||
return np.tile(interocular, [1, 2])
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='PCK', **kwargs):
|
||||
"""Evaluate horse-10 keypoint results. The pose prediction results will
|
||||
be saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['Test/source/0.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'PCK', 'NME'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['PCK', 'NME']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
Vendored
+218
@@ -0,0 +1,218 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from ...builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class AnimalLocustDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""AnimalLocustDataset for animal pose estimation.
|
||||
|
||||
"DeepPoseKit, a software toolkit for fast and robust animal
|
||||
pose estimation using deep learning" Elife'2019.
|
||||
More details can be found in the paper.
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
Desert Locust keypoint indexes::
|
||||
|
||||
0: "head",
|
||||
1: "neck",
|
||||
2: "thorax",
|
||||
3: "abdomen1",
|
||||
4: "abdomen2",
|
||||
5: "anttipL",
|
||||
6: "antbaseL",
|
||||
7: "eyeL",
|
||||
8: "forelegL1",
|
||||
9: "forelegL2",
|
||||
10: "forelegL3",
|
||||
11: "forelegL4",
|
||||
12: "midlegL1",
|
||||
13: "midlegL2",
|
||||
14: "midlegL3",
|
||||
15: "midlegL4",
|
||||
16: "hindlegL1",
|
||||
17: "hindlegL2",
|
||||
18: "hindlegL3",
|
||||
19: "hindlegL4",
|
||||
20: "anttipR",
|
||||
21: "antbaseR",
|
||||
22: "eyeR",
|
||||
23: "forelegR1",
|
||||
24: "forelegR2",
|
||||
25: "forelegR3",
|
||||
26: "forelegR4",
|
||||
27: "midlegR1",
|
||||
28: "midlegR2",
|
||||
29: "midlegR3",
|
||||
30: "midlegR4",
|
||||
31: "hindlegR1",
|
||||
32: "hindlegR2",
|
||||
33: "hindlegR3",
|
||||
34: "hindlegR4"
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/locust.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
num_joints = self.ann_info['num_joints']
|
||||
for img_id in self.img_ids:
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
for obj in objs:
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
# the ori image is 160x160
|
||||
center, scale = self._xywh2cs(0, 0, 160, 160, 0.8)
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': obj['bbox'],
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='PCK', **kwargs):
|
||||
"""Evaluate Fly keypoint results. The pose prediction results will be
|
||||
saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['Test/source/0.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'PCK', 'AUC', 'EPE'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['PCK', 'AUC', 'EPE']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
Vendored
+355
@@ -0,0 +1,355 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
from xtcocotools.cocoeval import COCOeval
|
||||
|
||||
from ....core.post_processing import oks_nms, soft_oks_nms
|
||||
from ...builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class AnimalMacaqueDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""MacaquePose dataset for animal pose estimation.
|
||||
|
||||
"MacaquePose: A novel ‘in the wild’ macaque monkey pose dataset
|
||||
for markerless motion capture" bioRxiv'2020.
|
||||
More details can be found in the `paper
|
||||
<https://www.biorxiv.org/content/10.1101/2020.07.30.229989v1>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
Macaque keypoint indexes::
|
||||
|
||||
0: 'nose',
|
||||
1: 'left_eye',
|
||||
2: 'right_eye',
|
||||
3: 'left_ear',
|
||||
4: 'right_ear',
|
||||
5: 'left_shoulder',
|
||||
6: 'right_shoulder',
|
||||
7: 'left_elbow',
|
||||
8: 'right_elbow',
|
||||
9: 'left_wrist',
|
||||
10: 'right_wrist',
|
||||
11: 'left_hip',
|
||||
12: 'right_hip',
|
||||
13: 'left_knee',
|
||||
14: 'right_knee',
|
||||
15: 'left_ankle',
|
||||
16: 'right_ankle'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/macaque.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.use_gt_bbox = data_cfg['use_gt_bbox']
|
||||
self.bbox_file = data_cfg['bbox_file']
|
||||
self.det_bbox_thr = data_cfg.get('det_bbox_thr', 0.0)
|
||||
self.use_nms = data_cfg.get('use_nms', True)
|
||||
self.soft_nms = data_cfg['soft_nms']
|
||||
self.nms_thr = data_cfg['nms_thr']
|
||||
self.oks_thr = data_cfg['oks_thr']
|
||||
self.vis_thr = data_cfg['vis_thr']
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
assert self.use_gt_bbox
|
||||
gt_db = self._load_coco_keypoint_annotations()
|
||||
return gt_db
|
||||
|
||||
def _load_coco_keypoint_annotations(self):
|
||||
"""Ground truth bbox and keypoints."""
|
||||
gt_db = []
|
||||
for img_id in self.img_ids:
|
||||
gt_db.extend(self._load_coco_keypoint_annotation_kernel(img_id))
|
||||
return gt_db
|
||||
|
||||
def _load_coco_keypoint_annotation_kernel(self, img_id):
|
||||
"""load annotation from COCOAPI.
|
||||
|
||||
Note:
|
||||
bbox:[x1, y1, w, h]
|
||||
Args:
|
||||
img_id: coco image id
|
||||
Returns:
|
||||
dict: db entry
|
||||
"""
|
||||
img_ann = self.coco.loadImgs(img_id)[0]
|
||||
width = img_ann['width']
|
||||
height = img_ann['height']
|
||||
num_joints = self.ann_info['num_joints']
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
# sanitize bboxes
|
||||
valid_objs = []
|
||||
for obj in objs:
|
||||
if 'bbox' not in obj:
|
||||
continue
|
||||
x, y, w, h = obj['bbox']
|
||||
x1 = max(0, x)
|
||||
y1 = max(0, y)
|
||||
x2 = min(width - 1, x1 + max(0, w - 1))
|
||||
y2 = min(height - 1, y1 + max(0, h - 1))
|
||||
if ('area' not in obj or obj['area'] > 0) and x2 > x1 and y2 > y1:
|
||||
obj['clean_bbox'] = [x1, y1, x2 - x1, y2 - y1]
|
||||
valid_objs.append(obj)
|
||||
objs = valid_objs
|
||||
|
||||
bbox_id = 0
|
||||
rec = []
|
||||
for obj in objs:
|
||||
if 'keypoints' not in obj:
|
||||
continue
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
if 'num_keypoints' in obj and obj['num_keypoints'] == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
center, scale = self._xywh2cs(*obj['clean_bbox'][:4])
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
rec.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'bbox': obj['clean_bbox'][:4],
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
|
||||
return rec
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='mAP', **kwargs):
|
||||
"""Evaluate coco keypoint results. The pose prediction results will be
|
||||
saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
heatmap height: H
|
||||
heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['data/coco/val2017\
|
||||
/000000393226.jpg']
|
||||
- heatmap (np.ndarray[N, K, H, W]): model output heatmap
|
||||
- bbox_id (list(int)).
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed. Defaults: 'mAP'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['mAP']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = defaultdict(list)
|
||||
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
kpts[image_id].append({
|
||||
'keypoints': preds[i],
|
||||
'center': boxes[i][0:2],
|
||||
'scale': boxes[i][2:4],
|
||||
'area': boxes[i][4],
|
||||
'score': boxes[i][5],
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
# rescoring and oks nms
|
||||
num_joints = self.ann_info['num_joints']
|
||||
vis_thr = self.vis_thr
|
||||
oks_thr = self.oks_thr
|
||||
valid_kpts = []
|
||||
for image_id in kpts.keys():
|
||||
img_kpts = kpts[image_id]
|
||||
for n_p in img_kpts:
|
||||
box_score = n_p['score']
|
||||
kpt_score = 0
|
||||
valid_num = 0
|
||||
for n_jt in range(0, num_joints):
|
||||
t_s = n_p['keypoints'][n_jt][2]
|
||||
if t_s > vis_thr:
|
||||
kpt_score = kpt_score + t_s
|
||||
valid_num = valid_num + 1
|
||||
if valid_num != 0:
|
||||
kpt_score = kpt_score / valid_num
|
||||
# rescoring
|
||||
n_p['score'] = kpt_score * box_score
|
||||
|
||||
if self.use_nms:
|
||||
nms = soft_oks_nms if self.soft_nms else oks_nms
|
||||
keep = nms(list(img_kpts), oks_thr, sigmas=self.sigmas)
|
||||
valid_kpts.append([img_kpts[_keep] for _keep in keep])
|
||||
else:
|
||||
valid_kpts.append(img_kpts)
|
||||
|
||||
self._write_coco_keypoint_results(valid_kpts, res_file)
|
||||
|
||||
info_str = self._do_python_keypoint_eval(res_file)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
|
||||
def _write_coco_keypoint_results(self, keypoints, res_file):
|
||||
"""Write results into a json file."""
|
||||
data_pack = [{
|
||||
'cat_id': self._class_to_coco_ind[cls],
|
||||
'cls_ind': cls_ind,
|
||||
'cls': cls,
|
||||
'ann_type': 'keypoints',
|
||||
'keypoints': keypoints
|
||||
} for cls_ind, cls in enumerate(self.classes)
|
||||
if not cls == '__background__']
|
||||
|
||||
results = self._coco_keypoint_results_one_category_kernel(data_pack[0])
|
||||
|
||||
with open(res_file, 'w') as f:
|
||||
json.dump(results, f, sort_keys=True, indent=4)
|
||||
|
||||
def _coco_keypoint_results_one_category_kernel(self, data_pack):
|
||||
"""Get coco keypoint results."""
|
||||
cat_id = data_pack['cat_id']
|
||||
keypoints = data_pack['keypoints']
|
||||
cat_results = []
|
||||
|
||||
for img_kpts in keypoints:
|
||||
if len(img_kpts) == 0:
|
||||
continue
|
||||
|
||||
_key_points = np.array(
|
||||
[img_kpt['keypoints'] for img_kpt in img_kpts])
|
||||
key_points = _key_points.reshape(-1,
|
||||
self.ann_info['num_joints'] * 3)
|
||||
|
||||
result = [{
|
||||
'image_id': img_kpt['image_id'],
|
||||
'category_id': cat_id,
|
||||
'keypoints': key_point.tolist(),
|
||||
'score': float(img_kpt['score']),
|
||||
'center': img_kpt['center'].tolist(),
|
||||
'scale': img_kpt['scale'].tolist()
|
||||
} for img_kpt, key_point in zip(img_kpts, key_points)]
|
||||
|
||||
cat_results.extend(result)
|
||||
|
||||
return cat_results
|
||||
|
||||
def _do_python_keypoint_eval(self, res_file):
|
||||
"""Keypoint evaluation using COCOAPI."""
|
||||
coco_det = self.coco.loadRes(res_file)
|
||||
coco_eval = COCOeval(self.coco, coco_det, 'keypoints', self.sigmas)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
stats_names = [
|
||||
'AP', 'AP .5', 'AP .75', 'AP (M)', 'AP (L)', 'AR', 'AR .5',
|
||||
'AR .75', 'AR (M)', 'AR (L)'
|
||||
]
|
||||
|
||||
info_str = list(zip(stats_names, coco_eval.stats))
|
||||
|
||||
return info_str
|
||||
|
||||
def _sort_and_unique_bboxes(self, kpts, key='bbox_id'):
|
||||
"""sort kpts and remove the repeated ones."""
|
||||
for img_id, persons in kpts.items():
|
||||
num = len(persons)
|
||||
kpts[img_id] = sorted(kpts[img_id], key=lambda x: x[key])
|
||||
for i in range(num - 1, 0, -1):
|
||||
if kpts[img_id][i][key] == kpts[img_id][i - 1][key]:
|
||||
del kpts[img_id][i]
|
||||
|
||||
return kpts
|
||||
Vendored
+359
@@ -0,0 +1,359 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
from xtcocotools.cocoeval import COCOeval
|
||||
|
||||
from ....core.post_processing import oks_nms, soft_oks_nms
|
||||
from ...builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class AnimalPoseDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""Animal-Pose dataset for animal pose estimation.
|
||||
|
||||
"Cross-domain Adaptation For Animal Pose Estimation" ICCV'2019
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1908.05806>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
Animal-Pose keypoint indexes::
|
||||
|
||||
0: 'L_Eye',
|
||||
1: 'R_Eye',
|
||||
2: 'L_EarBase',
|
||||
3: 'R_EarBase',
|
||||
4: 'Nose',
|
||||
5: 'Throat',
|
||||
6: 'TailBase',
|
||||
7: 'Withers',
|
||||
8: 'L_F_Elbow',
|
||||
9: 'R_F_Elbow',
|
||||
10: 'L_B_Elbow',
|
||||
11: 'R_B_Elbow',
|
||||
12: 'L_F_Knee',
|
||||
13: 'R_F_Knee',
|
||||
14: 'L_B_Knee',
|
||||
15: 'R_B_Knee',
|
||||
16: 'L_F_Paw',
|
||||
17: 'R_F_Paw',
|
||||
18: 'L_B_Paw',
|
||||
19: 'R_B_Paw'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/animalpose.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.use_gt_bbox = data_cfg['use_gt_bbox']
|
||||
self.bbox_file = data_cfg['bbox_file']
|
||||
self.det_bbox_thr = data_cfg.get('det_bbox_thr', 0.0)
|
||||
self.use_nms = data_cfg.get('use_nms', True)
|
||||
self.soft_nms = data_cfg['soft_nms']
|
||||
self.nms_thr = data_cfg['nms_thr']
|
||||
self.oks_thr = data_cfg['oks_thr']
|
||||
self.vis_thr = data_cfg['vis_thr']
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
assert self.use_gt_bbox
|
||||
gt_db = self._load_coco_keypoint_annotations()
|
||||
return gt_db
|
||||
|
||||
def _load_coco_keypoint_annotations(self):
|
||||
"""Ground truth bbox and keypoints."""
|
||||
gt_db = []
|
||||
for img_id in self.img_ids:
|
||||
gt_db.extend(self._load_coco_keypoint_annotation_kernel(img_id))
|
||||
return gt_db
|
||||
|
||||
def _load_coco_keypoint_annotation_kernel(self, img_id):
|
||||
"""load annotation from COCOAPI.
|
||||
|
||||
Note:
|
||||
bbox:[x1, y1, w, h]
|
||||
|
||||
Args:
|
||||
img_id: coco image id
|
||||
|
||||
Returns:
|
||||
dict: db entry
|
||||
"""
|
||||
img_ann = self.coco.loadImgs(img_id)[0]
|
||||
width = img_ann['width']
|
||||
height = img_ann['height']
|
||||
num_joints = self.ann_info['num_joints']
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
# sanitize bboxes
|
||||
valid_objs = []
|
||||
for obj in objs:
|
||||
if 'bbox' not in obj:
|
||||
continue
|
||||
x, y, w, h = obj['bbox']
|
||||
x1 = max(0, x)
|
||||
y1 = max(0, y)
|
||||
x2 = min(width - 1, x1 + max(0, w - 1))
|
||||
y2 = min(height - 1, y1 + max(0, h - 1))
|
||||
if ('area' not in obj or obj['area'] > 0) and x2 > x1 and y2 > y1:
|
||||
obj['clean_bbox'] = [x1, y1, x2 - x1, y2 - y1]
|
||||
valid_objs.append(obj)
|
||||
objs = valid_objs
|
||||
|
||||
bbox_id = 0
|
||||
rec = []
|
||||
for obj in objs:
|
||||
if 'keypoints' not in obj:
|
||||
continue
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
if 'num_keypoints' in obj and obj['num_keypoints'] == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
center, scale = self._xywh2cs(*obj['clean_bbox'][:4])
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
rec.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'bbox': obj['clean_bbox'][:4],
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
|
||||
return rec
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='mAP', **kwargs):
|
||||
"""Evaluate coco keypoint results. The pose prediction results will be
|
||||
saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['data/coco/val2017\
|
||||
/000000393226.jpg']
|
||||
- heatmap (np.ndarray[N, K, H, W]): model output heatmap
|
||||
- bbox_id (list(int)).
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed. Defaults: 'mAP'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['mAP']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = defaultdict(list)
|
||||
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
kpts[image_id].append({
|
||||
'keypoints': preds[i],
|
||||
'center': boxes[i][0:2],
|
||||
'scale': boxes[i][2:4],
|
||||
'area': boxes[i][4],
|
||||
'score': boxes[i][5],
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
# rescoring and oks nms
|
||||
num_joints = self.ann_info['num_joints']
|
||||
vis_thr = self.vis_thr
|
||||
oks_thr = self.oks_thr
|
||||
valid_kpts = []
|
||||
for image_id in kpts.keys():
|
||||
img_kpts = kpts[image_id]
|
||||
for n_p in img_kpts:
|
||||
box_score = n_p['score']
|
||||
kpt_score = 0
|
||||
valid_num = 0
|
||||
for n_jt in range(0, num_joints):
|
||||
t_s = n_p['keypoints'][n_jt][2]
|
||||
if t_s > vis_thr:
|
||||
kpt_score = kpt_score + t_s
|
||||
valid_num = valid_num + 1
|
||||
if valid_num != 0:
|
||||
kpt_score = kpt_score / valid_num
|
||||
# rescoring
|
||||
n_p['score'] = kpt_score * box_score
|
||||
|
||||
if self.use_nms:
|
||||
nms = soft_oks_nms if self.soft_nms else oks_nms
|
||||
keep = nms(list(img_kpts), oks_thr, sigmas=self.sigmas)
|
||||
valid_kpts.append([img_kpts[_keep] for _keep in keep])
|
||||
else:
|
||||
valid_kpts.append(img_kpts)
|
||||
|
||||
self._write_coco_keypoint_results(valid_kpts, res_file)
|
||||
|
||||
info_str = self._do_python_keypoint_eval(res_file)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
|
||||
def _write_coco_keypoint_results(self, keypoints, res_file):
|
||||
"""Write results into a json file."""
|
||||
data_pack = [{
|
||||
'cat_id': self._class_to_coco_ind[cls],
|
||||
'cls_ind': cls_ind,
|
||||
'cls': cls,
|
||||
'ann_type': 'keypoints',
|
||||
'keypoints': keypoints
|
||||
} for cls_ind, cls in enumerate(self.classes)
|
||||
if not cls == '__background__']
|
||||
|
||||
results = self._coco_keypoint_results_one_category_kernel(data_pack[0])
|
||||
|
||||
with open(res_file, 'w') as f:
|
||||
json.dump(results, f, sort_keys=True, indent=4)
|
||||
|
||||
def _coco_keypoint_results_one_category_kernel(self, data_pack):
|
||||
"""Get coco keypoint results."""
|
||||
cat_id = data_pack['cat_id']
|
||||
keypoints = data_pack['keypoints']
|
||||
cat_results = []
|
||||
|
||||
for img_kpts in keypoints:
|
||||
if len(img_kpts) == 0:
|
||||
continue
|
||||
|
||||
_key_points = np.array(
|
||||
[img_kpt['keypoints'] for img_kpt in img_kpts])
|
||||
key_points = _key_points.reshape(-1,
|
||||
self.ann_info['num_joints'] * 3)
|
||||
|
||||
result = [{
|
||||
'image_id': img_kpt['image_id'],
|
||||
'category_id': cat_id,
|
||||
'keypoints': key_point.tolist(),
|
||||
'score': float(img_kpt['score']),
|
||||
'center': img_kpt['center'].tolist(),
|
||||
'scale': img_kpt['scale'].tolist()
|
||||
} for img_kpt, key_point in zip(img_kpts, key_points)]
|
||||
|
||||
cat_results.extend(result)
|
||||
|
||||
return cat_results
|
||||
|
||||
def _do_python_keypoint_eval(self, res_file):
|
||||
"""Keypoint evaluation using COCOAPI."""
|
||||
coco_det = self.coco.loadRes(res_file)
|
||||
coco_eval = COCOeval(self.coco, coco_det, 'keypoints', self.sigmas)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
stats_names = [
|
||||
'AP', 'AP .5', 'AP .75', 'AP (M)', 'AP (L)', 'AR', 'AR .5',
|
||||
'AR .75', 'AR (M)', 'AR (L)'
|
||||
]
|
||||
|
||||
info_str = list(zip(stats_names, coco_eval.stats))
|
||||
|
||||
return info_str
|
||||
|
||||
def _sort_and_unique_bboxes(self, kpts, key='bbox_id'):
|
||||
"""sort kpts and remove the repeated ones."""
|
||||
for img_id, persons in kpts.items():
|
||||
num = len(persons)
|
||||
kpts[img_id] = sorted(kpts[img_id], key=lambda x: x[key])
|
||||
for i in range(num - 1, 0, -1):
|
||||
if kpts[img_id][i][key] == kpts[img_id][i - 1][key]:
|
||||
del kpts[img_id][i]
|
||||
|
||||
return kpts
|
||||
Vendored
+193
@@ -0,0 +1,193 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from ...builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class AnimalZebraDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""AnimalZebraDataset for animal pose estimation.
|
||||
|
||||
"DeepPoseKit, a software toolkit for fast and robust animal
|
||||
pose estimation using deep learning" Elife'2019.
|
||||
More details can be found in the paper.
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
Desert Locust keypoint indexes::
|
||||
|
||||
0: "snout",
|
||||
1: "head",
|
||||
2: "neck",
|
||||
3: "forelegL1",
|
||||
4: "forelegR1",
|
||||
5: "hindlegL1",
|
||||
6: "hindlegR1",
|
||||
7: "tailbase",
|
||||
8: "tailtip"
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/zebra.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
num_joints = self.ann_info['num_joints']
|
||||
for img_id in self.img_ids:
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
for obj in objs:
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
# the ori image is 160x160
|
||||
center, scale = self._xywh2cs(0, 0, 160, 160, 0.8)
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': obj['bbox'],
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='PCK', **kwargs):
|
||||
"""Evaluate Fly keypoint results. The pose prediction results will be
|
||||
saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['Test/source/0.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'PCK', 'AUC', 'EPE'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['PCK', 'AUC', 'EPE']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .kpt_2d_sview_rgb_img_bottom_up_dataset import \
|
||||
Kpt2dSviewRgbImgBottomUpDataset
|
||||
from .kpt_2d_sview_rgb_img_top_down_dataset import \
|
||||
Kpt2dSviewRgbImgTopDownDataset
|
||||
from .kpt_2d_sview_rgb_vid_top_down_dataset import \
|
||||
Kpt2dSviewRgbVidTopDownDataset
|
||||
from .kpt_3d_mview_rgb_img_direct_dataset import Kpt3dMviewRgbImgDirectDataset
|
||||
from .kpt_3d_sview_kpt_2d_dataset import Kpt3dSviewKpt2dDataset
|
||||
from .kpt_3d_sview_rgb_img_top_down_dataset import \
|
||||
Kpt3dSviewRgbImgTopDownDataset
|
||||
|
||||
__all__ = [
|
||||
'Kpt3dMviewRgbImgDirectDataset', 'Kpt2dSviewRgbImgTopDownDataset',
|
||||
'Kpt3dSviewRgbImgTopDownDataset', 'Kpt2dSviewRgbImgBottomUpDataset',
|
||||
'Kpt3dSviewKpt2dDataset', 'Kpt2dSviewRgbVidTopDownDataset'
|
||||
]
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import numpy as np
|
||||
import xtcocotools
|
||||
from torch.utils.data import Dataset
|
||||
from xtcocotools.coco import COCO
|
||||
|
||||
from mmpose.datasets import DatasetInfo
|
||||
from mmpose.datasets.pipelines import Compose
|
||||
|
||||
|
||||
class Kpt2dSviewRgbImgBottomUpDataset(Dataset, metaclass=ABCMeta):
|
||||
"""Base class for bottom-up datasets.
|
||||
|
||||
All datasets should subclass it.
|
||||
All subclasses should overwrite:
|
||||
Methods:`_get_single`
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
coco_style (bool): Whether the annotation json is coco-style.
|
||||
Default: True
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
coco_style=True,
|
||||
test_mode=False):
|
||||
|
||||
self.image_info = {}
|
||||
self.ann_info = {}
|
||||
|
||||
self.ann_file = ann_file
|
||||
self.img_prefix = img_prefix
|
||||
self.pipeline = pipeline
|
||||
self.test_mode = test_mode
|
||||
|
||||
# bottom-up
|
||||
self.base_size = data_cfg['base_size']
|
||||
self.base_sigma = data_cfg['base_sigma']
|
||||
self.int_sigma = False
|
||||
|
||||
self.ann_info['image_size'] = np.array(data_cfg['image_size'])
|
||||
self.ann_info['heatmap_size'] = np.array(data_cfg['heatmap_size'])
|
||||
self.ann_info['num_joints'] = data_cfg['num_joints']
|
||||
self.ann_info['num_scales'] = data_cfg['num_scales']
|
||||
self.ann_info['scale_aware_sigma'] = data_cfg['scale_aware_sigma']
|
||||
|
||||
self.ann_info['inference_channel'] = data_cfg['inference_channel']
|
||||
self.ann_info['dataset_channel'] = data_cfg['dataset_channel']
|
||||
|
||||
self.use_nms = data_cfg.get('use_nms', False)
|
||||
self.soft_nms = data_cfg.get('soft_nms', True)
|
||||
self.oks_thr = data_cfg.get('oks_thr', 0.9)
|
||||
|
||||
if dataset_info is None:
|
||||
raise ValueError(
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.')
|
||||
|
||||
dataset_info = DatasetInfo(dataset_info)
|
||||
|
||||
assert self.ann_info['num_joints'] == dataset_info.keypoint_num
|
||||
self.ann_info['flip_pairs'] = dataset_info.flip_pairs
|
||||
self.ann_info['flip_index'] = dataset_info.flip_index
|
||||
self.ann_info['upper_body_ids'] = dataset_info.upper_body_ids
|
||||
self.ann_info['lower_body_ids'] = dataset_info.lower_body_ids
|
||||
self.ann_info['joint_weights'] = dataset_info.joint_weights
|
||||
self.ann_info['skeleton'] = dataset_info.skeleton
|
||||
self.sigmas = dataset_info.sigmas
|
||||
self.dataset_name = dataset_info.dataset_name
|
||||
|
||||
if coco_style:
|
||||
self.coco = COCO(ann_file)
|
||||
if 'categories' in self.coco.dataset:
|
||||
cats = [
|
||||
cat['name']
|
||||
for cat in self.coco.loadCats(self.coco.getCatIds())
|
||||
]
|
||||
self.classes = ['__background__'] + cats
|
||||
self.num_classes = len(self.classes)
|
||||
self._class_to_ind = dict(
|
||||
zip(self.classes, range(self.num_classes)))
|
||||
self._class_to_coco_ind = dict(
|
||||
zip(cats, self.coco.getCatIds()))
|
||||
self._coco_ind_to_class_ind = dict(
|
||||
(self._class_to_coco_ind[cls], self._class_to_ind[cls])
|
||||
for cls in self.classes[1:])
|
||||
self.img_ids = self.coco.getImgIds()
|
||||
if not test_mode:
|
||||
self.img_ids = [
|
||||
img_id for img_id in self.img_ids if
|
||||
len(self.coco.getAnnIds(imgIds=img_id, iscrowd=None)) > 0
|
||||
]
|
||||
self.num_images = len(self.img_ids)
|
||||
self.id2name, self.name2id = self._get_mapping_id_name(
|
||||
self.coco.imgs)
|
||||
|
||||
self.pipeline = Compose(self.pipeline)
|
||||
|
||||
@staticmethod
|
||||
def _get_mapping_id_name(imgs):
|
||||
"""
|
||||
Args:
|
||||
imgs (dict): dict of image info.
|
||||
|
||||
Returns:
|
||||
tuple: Image name & id mapping dicts.
|
||||
|
||||
- id2name (dict): Mapping image id to name.
|
||||
- name2id (dict): Mapping image name to id.
|
||||
"""
|
||||
id2name = {}
|
||||
name2id = {}
|
||||
for image_id, image in imgs.items():
|
||||
file_name = image['file_name']
|
||||
id2name[image_id] = file_name
|
||||
name2id[file_name] = image_id
|
||||
|
||||
return id2name, name2id
|
||||
|
||||
def _get_mask(self, anno, idx):
|
||||
"""Get ignore masks to mask out losses."""
|
||||
coco = self.coco
|
||||
img_info = coco.loadImgs(self.img_ids[idx])[0]
|
||||
|
||||
m = np.zeros((img_info['height'], img_info['width']), dtype=np.float32)
|
||||
|
||||
for obj in anno:
|
||||
if 'segmentation' in obj:
|
||||
if obj['iscrowd']:
|
||||
rle = xtcocotools.mask.frPyObjects(obj['segmentation'],
|
||||
img_info['height'],
|
||||
img_info['width'])
|
||||
m += xtcocotools.mask.decode(rle)
|
||||
elif obj['num_keypoints'] == 0:
|
||||
rles = xtcocotools.mask.frPyObjects(
|
||||
obj['segmentation'], img_info['height'],
|
||||
img_info['width'])
|
||||
for rle in rles:
|
||||
m += xtcocotools.mask.decode(rle)
|
||||
|
||||
return m < 0.5
|
||||
|
||||
@abstractmethod
|
||||
def _get_single(self, idx):
|
||||
"""Get anno for a single image."""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def evaluate(self, results, *args, **kwargs):
|
||||
"""Evaluate keypoint results."""
|
||||
|
||||
def prepare_train_img(self, idx):
|
||||
"""Prepare image for training given the index."""
|
||||
results = copy.deepcopy(self._get_single(idx))
|
||||
results['ann_info'] = self.ann_info
|
||||
return self.pipeline(results)
|
||||
|
||||
def prepare_test_img(self, idx):
|
||||
"""Prepare image for testing given the index."""
|
||||
results = copy.deepcopy(self._get_single(idx))
|
||||
results['ann_info'] = self.ann_info
|
||||
return self.pipeline(results)
|
||||
|
||||
def __len__(self):
|
||||
"""Get dataset length."""
|
||||
return len(self.img_ids)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
"""Get the sample for either training or testing given index."""
|
||||
if self.test_mode:
|
||||
return self.prepare_test_img(idx)
|
||||
|
||||
return self.prepare_train_img(idx)
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
from torch.utils.data import Dataset
|
||||
from xtcocotools.coco import COCO
|
||||
|
||||
from mmpose.core.evaluation.top_down_eval import (keypoint_auc, keypoint_epe,
|
||||
keypoint_nme,
|
||||
keypoint_pck_accuracy)
|
||||
from mmpose.datasets import DatasetInfo
|
||||
from mmpose.datasets.pipelines import Compose
|
||||
|
||||
|
||||
class Kpt2dSviewRgbImgTopDownDataset(Dataset, metaclass=ABCMeta):
|
||||
"""Base class for keypoint 2D top-down pose estimation with single-view RGB
|
||||
image as the input.
|
||||
|
||||
All fashion datasets should subclass it.
|
||||
All subclasses should overwrite:
|
||||
Methods:`_get_db`, 'evaluate'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
coco_style (bool): Whether the annotation json is coco-style.
|
||||
Default: True
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
coco_style=True,
|
||||
test_mode=False):
|
||||
|
||||
self.image_info = {}
|
||||
self.ann_info = {}
|
||||
|
||||
self.ann_file = ann_file
|
||||
self.img_prefix = img_prefix
|
||||
self.pipeline = pipeline
|
||||
self.test_mode = test_mode
|
||||
|
||||
self.ann_info['image_size'] = np.array(data_cfg['image_size'])
|
||||
self.ann_info['heatmap_size'] = np.array(data_cfg['heatmap_size'])
|
||||
self.ann_info['num_joints'] = data_cfg['num_joints']
|
||||
|
||||
self.ann_info['inference_channel'] = data_cfg['inference_channel']
|
||||
self.ann_info['num_output_channels'] = data_cfg['num_output_channels']
|
||||
self.ann_info['dataset_channel'] = data_cfg['dataset_channel']
|
||||
|
||||
self.ann_info['max_num_joints'] = data_cfg.get('max_num_joints', None)
|
||||
self.ann_info['dataset_idx'] = data_cfg.get('dataset_idx', 0)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = data_cfg.get(
|
||||
'use_different_joint_weights', False)
|
||||
|
||||
if dataset_info is None:
|
||||
raise ValueError(
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.')
|
||||
|
||||
dataset_info = DatasetInfo(dataset_info)
|
||||
|
||||
assert self.ann_info['num_joints'] == dataset_info.keypoint_num
|
||||
self.ann_info['flip_pairs'] = dataset_info.flip_pairs
|
||||
self.ann_info['flip_index'] = dataset_info.flip_index
|
||||
self.ann_info['upper_body_ids'] = dataset_info.upper_body_ids
|
||||
self.ann_info['lower_body_ids'] = dataset_info.lower_body_ids
|
||||
self.ann_info['joint_weights'] = dataset_info.joint_weights
|
||||
self.ann_info['skeleton'] = dataset_info.skeleton
|
||||
self.sigmas = dataset_info.sigmas
|
||||
self.dataset_name = dataset_info.dataset_name
|
||||
|
||||
if coco_style:
|
||||
self.coco = COCO(ann_file)
|
||||
if 'categories' in self.coco.dataset:
|
||||
cats = [
|
||||
cat['name']
|
||||
for cat in self.coco.loadCats(self.coco.getCatIds())
|
||||
]
|
||||
self.classes = ['__background__'] + cats
|
||||
self.num_classes = len(self.classes)
|
||||
self._class_to_ind = dict(
|
||||
zip(self.classes, range(self.num_classes)))
|
||||
self._class_to_coco_ind = dict(
|
||||
zip(cats, self.coco.getCatIds()))
|
||||
self._coco_ind_to_class_ind = dict(
|
||||
(self._class_to_coco_ind[cls], self._class_to_ind[cls])
|
||||
for cls in self.classes[1:])
|
||||
self.img_ids = self.coco.getImgIds()
|
||||
self.num_images = len(self.img_ids)
|
||||
self.id2name, self.name2id = self._get_mapping_id_name(
|
||||
self.coco.imgs)
|
||||
|
||||
self.db = []
|
||||
|
||||
self.pipeline = Compose(self.pipeline)
|
||||
|
||||
@staticmethod
|
||||
def _get_mapping_id_name(imgs):
|
||||
"""
|
||||
Args:
|
||||
imgs (dict): dict of image info.
|
||||
|
||||
Returns:
|
||||
tuple: Image name & id mapping dicts.
|
||||
|
||||
- id2name (dict): Mapping image id to name.
|
||||
- name2id (dict): Mapping image name to id.
|
||||
"""
|
||||
id2name = {}
|
||||
name2id = {}
|
||||
for image_id, image in imgs.items():
|
||||
file_name = image['file_name']
|
||||
id2name[image_id] = file_name
|
||||
name2id[file_name] = image_id
|
||||
|
||||
return id2name, name2id
|
||||
|
||||
def _xywh2cs(self, x, y, w, h, padding=1.25):
|
||||
"""This encodes bbox(x,y,w,h) into (center, scale)
|
||||
|
||||
Args:
|
||||
x, y, w, h (float): left, top, width and height
|
||||
padding (float): bounding box padding factor
|
||||
|
||||
Returns:
|
||||
center (np.ndarray[float32](2,)): center of the bbox (x, y).
|
||||
scale (np.ndarray[float32](2,)): scale of the bbox w & h.
|
||||
"""
|
||||
aspect_ratio = self.ann_info['image_size'][0] / self.ann_info[
|
||||
'image_size'][1]
|
||||
center = np.array([x + w * 0.5, y + h * 0.5], dtype=np.float32)
|
||||
|
||||
if (not self.test_mode) and np.random.rand() < 0.3:
|
||||
center += 0.4 * (np.random.rand(2) - 0.5) * [w, h]
|
||||
|
||||
if w > aspect_ratio * h:
|
||||
h = w * 1.0 / aspect_ratio
|
||||
elif w < aspect_ratio * h:
|
||||
w = h * aspect_ratio
|
||||
|
||||
# pixel std is 200.0
|
||||
scale = np.array([w / 200.0, h / 200.0], dtype=np.float32)
|
||||
# padding to include proper amount of context
|
||||
scale = scale * padding
|
||||
|
||||
return center, scale
|
||||
|
||||
def _get_normalize_factor(self, gts, *args, **kwargs):
|
||||
"""Get the normalize factor. generally inter-ocular distance measured
|
||||
as the Euclidean distance between the outer corners of the eyes is
|
||||
used. This function should be overrode, to measure NME.
|
||||
|
||||
Args:
|
||||
gts (np.ndarray[N, K, 2]): Groundtruth keypoint location.
|
||||
|
||||
Returns:
|
||||
np.ndarray[N, 2]: normalized factor
|
||||
"""
|
||||
return np.ones([gts.shape[0], 2], dtype=np.float32)
|
||||
|
||||
@abstractmethod
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def evaluate(self, results, *args, **kwargs):
|
||||
"""Evaluate keypoint results."""
|
||||
|
||||
@staticmethod
|
||||
def _write_keypoint_results(keypoints, res_file):
|
||||
"""Write results into a json file."""
|
||||
|
||||
with open(res_file, 'w') as f:
|
||||
json.dump(keypoints, f, sort_keys=True, indent=4)
|
||||
|
||||
def _report_metric(self,
|
||||
res_file,
|
||||
metrics,
|
||||
pck_thr=0.2,
|
||||
pckh_thr=0.7,
|
||||
auc_nor=30):
|
||||
"""Keypoint evaluation.
|
||||
|
||||
Args:
|
||||
res_file (str): Json file stored prediction results.
|
||||
metrics (str | list[str]): Metric to be performed.
|
||||
Options: 'PCK', 'PCKh', 'AUC', 'EPE', 'NME'.
|
||||
pck_thr (float): PCK threshold, default as 0.2.
|
||||
pckh_thr (float): PCKh threshold, default as 0.7.
|
||||
auc_nor (float): AUC normalization factor, default as 30 pixel.
|
||||
|
||||
Returns:
|
||||
List: Evaluation results for evaluation metric.
|
||||
"""
|
||||
info_str = []
|
||||
|
||||
with open(res_file, 'r') as fin:
|
||||
preds = json.load(fin)
|
||||
assert len(preds) == len(self.db)
|
||||
|
||||
outputs = []
|
||||
gts = []
|
||||
masks = []
|
||||
box_sizes = []
|
||||
threshold_bbox = []
|
||||
threshold_head_box = []
|
||||
|
||||
for pred, item in zip(preds, self.db):
|
||||
outputs.append(np.array(pred['keypoints'])[:, :-1])
|
||||
gts.append(np.array(item['joints_3d'])[:, :-1])
|
||||
masks.append((np.array(item['joints_3d_visible'])[:, 0]) > 0)
|
||||
if 'PCK' in metrics:
|
||||
bbox = np.array(item['bbox'])
|
||||
bbox_thr = np.max(bbox[2:])
|
||||
threshold_bbox.append(np.array([bbox_thr, bbox_thr]))
|
||||
if 'PCKh' in metrics:
|
||||
head_box_thr = item['head_size']
|
||||
threshold_head_box.append(
|
||||
np.array([head_box_thr, head_box_thr]))
|
||||
box_sizes.append(item.get('box_size', 1))
|
||||
|
||||
outputs = np.array(outputs)
|
||||
gts = np.array(gts)
|
||||
masks = np.array(masks)
|
||||
threshold_bbox = np.array(threshold_bbox)
|
||||
threshold_head_box = np.array(threshold_head_box)
|
||||
box_sizes = np.array(box_sizes).reshape([-1, 1])
|
||||
|
||||
if 'PCK' in metrics:
|
||||
_, pck, _ = keypoint_pck_accuracy(outputs, gts, masks, pck_thr,
|
||||
threshold_bbox)
|
||||
info_str.append(('PCK', pck))
|
||||
|
||||
if 'PCKh' in metrics:
|
||||
_, pckh, _ = keypoint_pck_accuracy(outputs, gts, masks, pckh_thr,
|
||||
threshold_head_box)
|
||||
info_str.append(('PCKh', pckh))
|
||||
|
||||
if 'AUC' in metrics:
|
||||
info_str.append(('AUC', keypoint_auc(outputs, gts, masks,
|
||||
auc_nor)))
|
||||
|
||||
if 'EPE' in metrics:
|
||||
info_str.append(('EPE', keypoint_epe(outputs, gts, masks)))
|
||||
|
||||
if 'NME' in metrics:
|
||||
normalize_factor = self._get_normalize_factor(
|
||||
gts=gts, box_sizes=box_sizes)
|
||||
info_str.append(
|
||||
('NME', keypoint_nme(outputs, gts, masks, normalize_factor)))
|
||||
|
||||
return info_str
|
||||
|
||||
def __len__(self):
|
||||
"""Get the size of the dataset."""
|
||||
return len(self.db)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
"""Get the sample given index."""
|
||||
results = copy.deepcopy(self.db[idx])
|
||||
results['ann_info'] = self.ann_info
|
||||
return self.pipeline(results)
|
||||
|
||||
def _sort_and_unique_bboxes(self, kpts, key='bbox_id'):
|
||||
"""sort kpts and remove the repeated ones."""
|
||||
kpts = sorted(kpts, key=lambda x: x[key])
|
||||
num = len(kpts)
|
||||
for i in range(num - 1, 0, -1):
|
||||
if kpts[i][key] == kpts[i - 1][key]:
|
||||
del kpts[i]
|
||||
|
||||
return kpts
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import numpy as np
|
||||
from torch.utils.data import Dataset
|
||||
from xtcocotools.coco import COCO
|
||||
|
||||
from mmpose.datasets import DatasetInfo
|
||||
from mmpose.datasets.pipelines import Compose
|
||||
|
||||
|
||||
class Kpt2dSviewRgbVidTopDownDataset(Dataset, metaclass=ABCMeta):
|
||||
"""Base class for keypoint 2D top-down pose estimation with single-view RGB
|
||||
video as the input.
|
||||
|
||||
All fashion datasets should subclass it.
|
||||
All subclasses should overwrite:
|
||||
Methods:`_get_db`, 'evaluate'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where videos/images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
coco_style (bool): Whether the annotation json is coco-style.
|
||||
Default: True
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
coco_style=True,
|
||||
test_mode=False):
|
||||
|
||||
self.image_info = {}
|
||||
self.ann_info = {}
|
||||
|
||||
self.ann_file = ann_file
|
||||
self.img_prefix = img_prefix
|
||||
self.pipeline = pipeline
|
||||
self.test_mode = test_mode
|
||||
|
||||
self.ann_info['image_size'] = np.array(data_cfg['image_size'])
|
||||
self.ann_info['heatmap_size'] = np.array(data_cfg['heatmap_size'])
|
||||
self.ann_info['num_joints'] = data_cfg['num_joints']
|
||||
|
||||
self.ann_info['inference_channel'] = data_cfg['inference_channel']
|
||||
self.ann_info['num_output_channels'] = data_cfg['num_output_channels']
|
||||
self.ann_info['dataset_channel'] = data_cfg['dataset_channel']
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = data_cfg.get(
|
||||
'use_different_joint_weights', False)
|
||||
|
||||
if dataset_info is None:
|
||||
raise ValueError(
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.')
|
||||
|
||||
dataset_info = DatasetInfo(dataset_info)
|
||||
|
||||
assert self.ann_info['num_joints'] == dataset_info.keypoint_num
|
||||
self.ann_info['flip_pairs'] = dataset_info.flip_pairs
|
||||
self.ann_info['flip_index'] = dataset_info.flip_index
|
||||
self.ann_info['upper_body_ids'] = dataset_info.upper_body_ids
|
||||
self.ann_info['lower_body_ids'] = dataset_info.lower_body_ids
|
||||
self.ann_info['joint_weights'] = dataset_info.joint_weights
|
||||
self.ann_info['skeleton'] = dataset_info.skeleton
|
||||
self.sigmas = dataset_info.sigmas
|
||||
self.dataset_name = dataset_info.dataset_name
|
||||
|
||||
if coco_style:
|
||||
self.coco = COCO(ann_file)
|
||||
if 'categories' in self.coco.dataset:
|
||||
cats = [
|
||||
cat['name']
|
||||
for cat in self.coco.loadCats(self.coco.getCatIds())
|
||||
]
|
||||
self.classes = ['__background__'] + cats
|
||||
self.num_classes = len(self.classes)
|
||||
self._class_to_ind = dict(
|
||||
zip(self.classes, range(self.num_classes)))
|
||||
self._class_to_coco_ind = dict(
|
||||
zip(cats, self.coco.getCatIds()))
|
||||
self._coco_ind_to_class_ind = dict(
|
||||
(self._class_to_coco_ind[cls], self._class_to_ind[cls])
|
||||
for cls in self.classes[1:])
|
||||
self.img_ids = self.coco.getImgIds()
|
||||
self.num_images = len(self.img_ids)
|
||||
self.id2name, self.name2id = self._get_mapping_id_name(
|
||||
self.coco.imgs)
|
||||
|
||||
self.db = []
|
||||
|
||||
self.pipeline = Compose(self.pipeline)
|
||||
|
||||
@staticmethod
|
||||
def _get_mapping_id_name(imgs):
|
||||
"""
|
||||
Args:
|
||||
imgs (dict): dict of image info.
|
||||
|
||||
Returns:
|
||||
tuple: Image name & id mapping dicts.
|
||||
|
||||
- id2name (dict): Mapping image id to name.
|
||||
- name2id (dict): Mapping image name to id.
|
||||
"""
|
||||
id2name = {}
|
||||
name2id = {}
|
||||
for image_id, image in imgs.items():
|
||||
file_name = image['file_name']
|
||||
id2name[image_id] = file_name
|
||||
name2id[file_name] = image_id
|
||||
|
||||
return id2name, name2id
|
||||
|
||||
def _xywh2cs(self, x, y, w, h, padding=1.25):
|
||||
"""This encodes bbox(x,y,w,h) into (center, scale)
|
||||
|
||||
Args:
|
||||
x, y, w, h (float): left, top, width and height
|
||||
padding (float): bounding box padding factor
|
||||
|
||||
Returns:
|
||||
center (np.ndarray[float32](2,)): center of the bbox (x, y).
|
||||
scale (np.ndarray[float32](2,)): scale of the bbox w & h.
|
||||
"""
|
||||
aspect_ratio = self.ann_info['image_size'][0] / self.ann_info[
|
||||
'image_size'][1]
|
||||
center = np.array([x + w * 0.5, y + h * 0.5], dtype=np.float32)
|
||||
|
||||
if (not self.test_mode) and np.random.rand() < 0.3:
|
||||
center += 0.4 * (np.random.rand(2) - 0.5) * [w, h]
|
||||
|
||||
if w > aspect_ratio * h:
|
||||
h = w * 1.0 / aspect_ratio
|
||||
elif w < aspect_ratio * h:
|
||||
w = h * aspect_ratio
|
||||
|
||||
# pixel std is 200.0
|
||||
scale = np.array([w / 200.0, h / 200.0], dtype=np.float32)
|
||||
# padding to include proper amount of context
|
||||
scale = scale * padding
|
||||
|
||||
return center, scale
|
||||
|
||||
@abstractmethod
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
|
||||
@abstractmethod
|
||||
def evaluate(self, results, *args, **kwargs):
|
||||
"""Evaluate keypoint results."""
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def _write_keypoint_results(keypoint_results, gt_folder, pred_folder):
|
||||
"""Write results into a json file."""
|
||||
|
||||
@abstractmethod
|
||||
def _do_keypoint_eval(self, gt_folder, pred_folder):
|
||||
"""Keypoint evaluation.
|
||||
Args:
|
||||
gt_folder (str): The folder of the json files storing
|
||||
ground truth keypoint annotations.
|
||||
pred_folder (str): The folder of the json files storing
|
||||
prediction results.
|
||||
|
||||
Returns:
|
||||
List: Evaluation results for evaluation metric.
|
||||
"""
|
||||
|
||||
def __len__(self):
|
||||
"""Get the size of the dataset."""
|
||||
return len(self.db)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
"""Get the sample given index."""
|
||||
results = copy.deepcopy(self.db[idx])
|
||||
results['ann_info'] = self.ann_info
|
||||
return self.pipeline(results)
|
||||
|
||||
def _sort_and_unique_bboxes(self, kpts, key='bbox_id'):
|
||||
"""sort kpts and remove the repeated ones."""
|
||||
for img_id, persons in kpts.items():
|
||||
num = len(persons)
|
||||
kpts[img_id] = sorted(kpts[img_id], key=lambda x: x[key])
|
||||
for i in range(num - 1, 0, -1):
|
||||
if kpts[img_id][i][key] == kpts[img_id][i - 1][key]:
|
||||
del kpts[img_id][i]
|
||||
|
||||
return kpts
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
from mmpose.datasets import DatasetInfo
|
||||
from mmpose.datasets.pipelines import Compose
|
||||
|
||||
|
||||
class Kpt3dMviewRgbImgDirectDataset(Dataset, metaclass=ABCMeta):
|
||||
"""Base class for keypoint 3D top-down pose estimation with multi-view RGB
|
||||
images as the input.
|
||||
|
||||
All subclasses should overwrite:
|
||||
Methods:`_get_db`, 'evaluate'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
self.image_info = {}
|
||||
self.ann_info = {}
|
||||
|
||||
self.ann_file = ann_file
|
||||
self.img_prefix = img_prefix
|
||||
self.pipeline = pipeline
|
||||
self.test_mode = test_mode
|
||||
|
||||
self.ann_info['image_size'] = np.array(data_cfg['image_size'])
|
||||
self.ann_info['heatmap_size'] = np.array(data_cfg['heatmap_size'])
|
||||
self.ann_info['num_joints'] = data_cfg['num_joints']
|
||||
|
||||
self.ann_info['space_size'] = data_cfg['space_size']
|
||||
self.ann_info['space_center'] = data_cfg['space_center']
|
||||
self.ann_info['cube_size'] = data_cfg['cube_size']
|
||||
self.ann_info['scale_aware_sigma'] = data_cfg.get(
|
||||
'scale_aware_sigma', False)
|
||||
|
||||
if dataset_info is None:
|
||||
raise ValueError(
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.')
|
||||
|
||||
dataset_info = DatasetInfo(dataset_info)
|
||||
|
||||
assert self.ann_info['num_joints'] <= dataset_info.keypoint_num
|
||||
self.ann_info['flip_pairs'] = dataset_info.flip_pairs
|
||||
self.ann_info['num_scales'] = 1
|
||||
self.ann_info['flip_index'] = dataset_info.flip_index
|
||||
self.ann_info['upper_body_ids'] = dataset_info.upper_body_ids
|
||||
self.ann_info['lower_body_ids'] = dataset_info.lower_body_ids
|
||||
self.ann_info['joint_weights'] = dataset_info.joint_weights
|
||||
self.ann_info['skeleton'] = dataset_info.skeleton
|
||||
self.sigmas = dataset_info.sigmas
|
||||
self.dataset_name = dataset_info.dataset_name
|
||||
|
||||
self.load_config(data_cfg)
|
||||
|
||||
self.db = []
|
||||
|
||||
self.pipeline = Compose(self.pipeline)
|
||||
|
||||
def load_config(self, data_cfg):
|
||||
"""Initialize dataset attributes according to the config.
|
||||
|
||||
Override this method to set dataset specific attributes.
|
||||
"""
|
||||
self.num_joints = data_cfg['num_joints']
|
||||
self.num_cameras = data_cfg['num_cameras']
|
||||
self.seq_frame_interval = data_cfg.get('seq_frame_interval', 1)
|
||||
self.subset = data_cfg.get('subset', 'train')
|
||||
self.need_2d_label = data_cfg.get('need_2d_label', False)
|
||||
self.need_camera_param = True
|
||||
|
||||
@staticmethod
|
||||
def _get_mapping_id_name(imgs):
|
||||
"""
|
||||
Args:
|
||||
imgs (dict): dict of image info.
|
||||
|
||||
Returns:
|
||||
tuple: Image name & id mapping dicts.
|
||||
|
||||
- id2name (dict): Mapping image id to name.
|
||||
- name2id (dict): Mapping image name to id.
|
||||
"""
|
||||
id2name = {}
|
||||
name2id = {}
|
||||
for image_id, image in imgs.items():
|
||||
file_name = image['file_name']
|
||||
id2name[image_id] = file_name
|
||||
name2id[file_name] = image_id
|
||||
|
||||
return id2name, name2id
|
||||
|
||||
@abstractmethod
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def evaluate(self, results, *args, **kwargs):
|
||||
"""Evaluate keypoint results."""
|
||||
|
||||
@staticmethod
|
||||
def _write_keypoint_results(keypoints, res_file):
|
||||
"""Write results into a json file."""
|
||||
|
||||
with open(res_file, 'w') as f:
|
||||
json.dump(keypoints, f, sort_keys=True, indent=4)
|
||||
|
||||
def __len__(self):
|
||||
"""Get the size of the dataset."""
|
||||
return len(self.db) // self.num_cameras
|
||||
|
||||
def __getitem__(self, idx):
|
||||
"""Get the sample given index."""
|
||||
results = {}
|
||||
# return self.pipeline(results)
|
||||
for c in range(self.num_cameras):
|
||||
result = copy.deepcopy(self.db[self.num_cameras * idx + c])
|
||||
result['ann_info'] = self.ann_info
|
||||
results[c] = result
|
||||
|
||||
return self.pipeline(results)
|
||||
Vendored
+226
@@ -0,0 +1,226 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import numpy as np
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
from mmpose.datasets import DatasetInfo
|
||||
from mmpose.datasets.pipelines import Compose
|
||||
|
||||
|
||||
class Kpt3dSviewKpt2dDataset(Dataset, metaclass=ABCMeta):
|
||||
"""Base class for 3D human pose datasets.
|
||||
|
||||
Subclasses should consider overwriting following methods:
|
||||
- load_config
|
||||
- load_annotations
|
||||
- build_sample_indices
|
||||
- evaluate
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
- num_joints: Number of joints.
|
||||
- seq_len: Number of frames in a sequence. Default: 1.
|
||||
- seq_frame_interval: Extract frames from the video at certain
|
||||
intervals. Default: 1.
|
||||
- causal: If set to True, the rightmost input frame will be the
|
||||
target frame. Otherwise, the middle input frame will be the
|
||||
target frame. Default: True.
|
||||
- temporal_padding: Whether to pad the video so that poses will be
|
||||
predicted for every frame in the video. Default: False
|
||||
- subset: Reduce dataset size by fraction. Default: 1.
|
||||
- need_2d_label: Whether need 2D joint labels or not.
|
||||
Default: False.
|
||||
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
self.ann_file = ann_file
|
||||
self.img_prefix = img_prefix
|
||||
self.data_cfg = copy.deepcopy(data_cfg)
|
||||
self.pipeline = pipeline
|
||||
self.test_mode = test_mode
|
||||
self.ann_info = {}
|
||||
|
||||
if dataset_info is None:
|
||||
raise ValueError(
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.')
|
||||
|
||||
dataset_info = DatasetInfo(dataset_info)
|
||||
|
||||
self.load_config(self.data_cfg)
|
||||
|
||||
self.ann_info['num_joints'] = data_cfg['num_joints']
|
||||
assert self.ann_info['num_joints'] == dataset_info.keypoint_num
|
||||
self.ann_info['flip_pairs'] = dataset_info.flip_pairs
|
||||
self.ann_info['upper_body_ids'] = dataset_info.upper_body_ids
|
||||
self.ann_info['lower_body_ids'] = dataset_info.lower_body_ids
|
||||
self.ann_info['joint_weights'] = dataset_info.joint_weights
|
||||
self.ann_info['skeleton'] = dataset_info.skeleton
|
||||
self.sigmas = dataset_info.sigmas
|
||||
self.dataset_name = dataset_info.dataset_name
|
||||
|
||||
self.data_info = self.load_annotations()
|
||||
self.sample_indices = self.build_sample_indices()
|
||||
self.pipeline = Compose(pipeline)
|
||||
|
||||
self.name2id = {
|
||||
name: i
|
||||
for i, name in enumerate(self.data_info['imgnames'])
|
||||
}
|
||||
|
||||
def load_config(self, data_cfg):
|
||||
"""Initialize dataset attributes according to the config.
|
||||
|
||||
Override this method to set dataset specific attributes.
|
||||
"""
|
||||
|
||||
self.num_joints = data_cfg['num_joints']
|
||||
self.seq_len = data_cfg.get('seq_len', 1)
|
||||
self.seq_frame_interval = data_cfg.get('seq_frame_interval', 1)
|
||||
self.causal = data_cfg.get('causal', True)
|
||||
self.temporal_padding = data_cfg.get('temporal_padding', False)
|
||||
self.subset = data_cfg.get('subset', 1)
|
||||
self.need_2d_label = data_cfg.get('need_2d_label', False)
|
||||
self.need_camera_param = False
|
||||
|
||||
def load_annotations(self):
|
||||
"""Load data annotation."""
|
||||
data = np.load(self.ann_file)
|
||||
|
||||
# get image info
|
||||
_imgnames = data['imgname']
|
||||
num_imgs = len(_imgnames)
|
||||
num_joints = self.ann_info['num_joints']
|
||||
|
||||
if 'scale' in data:
|
||||
_scales = data['scale'].astype(np.float32)
|
||||
else:
|
||||
_scales = np.zeros(num_imgs, dtype=np.float32)
|
||||
|
||||
if 'center' in data:
|
||||
_centers = data['center'].astype(np.float32)
|
||||
else:
|
||||
_centers = np.zeros((num_imgs, 2), dtype=np.float32)
|
||||
|
||||
# get 3D pose
|
||||
if 'S' in data.keys():
|
||||
_joints_3d = data['S'].astype(np.float32)
|
||||
else:
|
||||
_joints_3d = np.zeros((num_imgs, num_joints, 4), dtype=np.float32)
|
||||
|
||||
# get 2D pose
|
||||
if 'part' in data.keys():
|
||||
_joints_2d = data['part'].astype(np.float32)
|
||||
else:
|
||||
_joints_2d = np.zeros((num_imgs, num_joints, 3), dtype=np.float32)
|
||||
|
||||
data_info = {
|
||||
'imgnames': _imgnames,
|
||||
'joints_3d': _joints_3d,
|
||||
'joints_2d': _joints_2d,
|
||||
'scales': _scales,
|
||||
'centers': _centers,
|
||||
}
|
||||
|
||||
return data_info
|
||||
|
||||
def build_sample_indices(self):
|
||||
"""Build sample indices.
|
||||
|
||||
The default method creates sample indices that each sample is a single
|
||||
frame (i.e. seq_len=1). Override this method in the subclass to define
|
||||
how frames are sampled to form data samples.
|
||||
|
||||
Outputs:
|
||||
sample_indices [list(tuple)]: the frame indices of each sample.
|
||||
For a sample, all frames will be treated as an input sequence,
|
||||
and the ground-truth pose of the last frame will be the target.
|
||||
"""
|
||||
sample_indices = []
|
||||
if self.seq_len == 1:
|
||||
num_imgs = len(self.ann_info['imgnames'])
|
||||
sample_indices = [(idx, ) for idx in range(num_imgs)]
|
||||
else:
|
||||
raise NotImplementedError('Multi-frame data sample unsupported!')
|
||||
return sample_indices
|
||||
|
||||
@abstractmethod
|
||||
def evaluate(self, results, *args, **kwargs):
|
||||
"""Evaluate keypoint results."""
|
||||
|
||||
def prepare_data(self, idx):
|
||||
"""Get data sample."""
|
||||
data = self.data_info
|
||||
|
||||
frame_ids = self.sample_indices[idx]
|
||||
assert len(frame_ids) == self.seq_len
|
||||
|
||||
# get the 3D/2D pose sequence
|
||||
_joints_3d = data['joints_3d'][frame_ids]
|
||||
_joints_2d = data['joints_2d'][frame_ids]
|
||||
|
||||
# get the image info
|
||||
_imgnames = data['imgnames'][frame_ids]
|
||||
_centers = data['centers'][frame_ids]
|
||||
_scales = data['scales'][frame_ids]
|
||||
if _scales.ndim == 1:
|
||||
_scales = np.stack([_scales, _scales], axis=1)
|
||||
|
||||
target_idx = -1 if self.causal else int(self.seq_len) // 2
|
||||
|
||||
results = {
|
||||
'input_2d': _joints_2d[:, :, :2],
|
||||
'input_2d_visible': _joints_2d[:, :, -1:],
|
||||
'input_3d': _joints_3d[:, :, :3],
|
||||
'input_3d_visible': _joints_3d[:, :, -1:],
|
||||
'target': _joints_3d[target_idx, :, :3],
|
||||
'target_visible': _joints_3d[target_idx, :, -1:],
|
||||
'image_paths': _imgnames,
|
||||
'target_image_path': _imgnames[target_idx],
|
||||
'scales': _scales,
|
||||
'centers': _centers,
|
||||
}
|
||||
|
||||
if self.need_2d_label:
|
||||
results['target_2d'] = _joints_2d[target_idx, :, :2]
|
||||
|
||||
if self.need_camera_param:
|
||||
_cam_param = self.get_camera_param(_imgnames[0])
|
||||
results['camera_param'] = _cam_param
|
||||
# get image size from camera parameters
|
||||
if 'w' in _cam_param and 'h' in _cam_param:
|
||||
results['image_width'] = _cam_param['w']
|
||||
results['image_height'] = _cam_param['h']
|
||||
|
||||
return results
|
||||
|
||||
def __len__(self):
|
||||
"""Get the size of the dataset."""
|
||||
return len(self.sample_indices)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
"""Get a sample with given index."""
|
||||
results = copy.deepcopy(self.prepare_data(idx))
|
||||
results['ann_info'] = self.ann_info
|
||||
return self.pipeline(results)
|
||||
|
||||
def get_camera_param(self, imgname):
|
||||
"""Get camera parameters of a frame by its image name."""
|
||||
raise NotImplementedError
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
from torch.utils.data import Dataset
|
||||
from xtcocotools.coco import COCO
|
||||
|
||||
from mmpose.datasets import DatasetInfo
|
||||
from mmpose.datasets.pipelines import Compose
|
||||
|
||||
|
||||
class Kpt3dSviewRgbImgTopDownDataset(Dataset, metaclass=ABCMeta):
|
||||
"""Base class for keypoint 3D top-down pose estimation with single-view RGB
|
||||
image as the input.
|
||||
|
||||
All fashion datasets should subclass it.
|
||||
All subclasses should overwrite:
|
||||
Methods:`_get_db`, 'evaluate'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
coco_style (bool): Whether the annotation json is coco-style.
|
||||
Default: True
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
coco_style=True,
|
||||
test_mode=False):
|
||||
|
||||
self.image_info = {}
|
||||
self.ann_info = {}
|
||||
|
||||
self.ann_file = ann_file
|
||||
self.img_prefix = img_prefix
|
||||
self.pipeline = pipeline
|
||||
self.test_mode = test_mode
|
||||
|
||||
self.ann_info['image_size'] = np.array(data_cfg['image_size'])
|
||||
self.ann_info['heatmap_size'] = np.array(data_cfg['heatmap_size'])
|
||||
self.ann_info['num_joints'] = data_cfg['num_joints']
|
||||
|
||||
self.ann_info['inference_channel'] = data_cfg['inference_channel']
|
||||
self.ann_info['num_output_channels'] = data_cfg['num_output_channels']
|
||||
self.ann_info['dataset_channel'] = data_cfg['dataset_channel']
|
||||
|
||||
if dataset_info is None:
|
||||
raise ValueError(
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.')
|
||||
|
||||
dataset_info = DatasetInfo(dataset_info)
|
||||
|
||||
assert self.ann_info['num_joints'] == dataset_info.keypoint_num
|
||||
self.ann_info['flip_pairs'] = dataset_info.flip_pairs
|
||||
self.ann_info['flip_index'] = dataset_info.flip_index
|
||||
self.ann_info['upper_body_ids'] = dataset_info.upper_body_ids
|
||||
self.ann_info['lower_body_ids'] = dataset_info.lower_body_ids
|
||||
self.ann_info['joint_weights'] = dataset_info.joint_weights
|
||||
self.ann_info['skeleton'] = dataset_info.skeleton
|
||||
self.sigmas = dataset_info.sigmas
|
||||
self.dataset_name = dataset_info.dataset_name
|
||||
|
||||
if coco_style:
|
||||
self.coco = COCO(ann_file)
|
||||
if 'categories' in self.coco.dataset:
|
||||
cats = [
|
||||
cat['name']
|
||||
for cat in self.coco.loadCats(self.coco.getCatIds())
|
||||
]
|
||||
self.classes = ['__background__'] + cats
|
||||
self.num_classes = len(self.classes)
|
||||
self._class_to_ind = dict(
|
||||
zip(self.classes, range(self.num_classes)))
|
||||
self._class_to_coco_ind = dict(
|
||||
zip(cats, self.coco.getCatIds()))
|
||||
self._coco_ind_to_class_ind = dict(
|
||||
(self._class_to_coco_ind[cls], self._class_to_ind[cls])
|
||||
for cls in self.classes[1:])
|
||||
self.img_ids = self.coco.getImgIds()
|
||||
self.num_images = len(self.img_ids)
|
||||
self.id2name, self.name2id = self._get_mapping_id_name(
|
||||
self.coco.imgs)
|
||||
|
||||
self.db = []
|
||||
|
||||
self.pipeline = Compose(self.pipeline)
|
||||
|
||||
@staticmethod
|
||||
def _cam2pixel(cam_coord, f, c):
|
||||
"""Transform the joints from their camera coordinates to their pixel
|
||||
coordinates.
|
||||
|
||||
Note:
|
||||
N: number of joints
|
||||
|
||||
Args:
|
||||
cam_coord (ndarray[N, 3]): 3D joints coordinates
|
||||
in the camera coordinate system
|
||||
f (ndarray[2]): focal length of x and y axis
|
||||
c (ndarray[2]): principal point of x and y axis
|
||||
|
||||
Returns:
|
||||
img_coord (ndarray[N, 3]): the coordinates (x, y, 0)
|
||||
in the image plane.
|
||||
"""
|
||||
x = cam_coord[:, 0] / (cam_coord[:, 2] + 1e-8) * f[0] + c[0]
|
||||
y = cam_coord[:, 1] / (cam_coord[:, 2] + 1e-8) * f[1] + c[1]
|
||||
z = np.zeros_like(x)
|
||||
img_coord = np.concatenate((x[:, None], y[:, None], z[:, None]), 1)
|
||||
return img_coord
|
||||
|
||||
@staticmethod
|
||||
def _world2cam(world_coord, R, T):
|
||||
"""Transform the joints from their world coordinates to their camera
|
||||
coordinates.
|
||||
|
||||
Note:
|
||||
N: number of joints
|
||||
|
||||
Args:
|
||||
world_coord (ndarray[3, N]): 3D joints coordinates
|
||||
in the world coordinate system
|
||||
R (ndarray[3, 3]): camera rotation matrix
|
||||
T (ndarray[3, 1]): camera position (x, y, z)
|
||||
|
||||
Returns:
|
||||
cam_coord (ndarray[3, N]): 3D joints coordinates
|
||||
in the camera coordinate system
|
||||
"""
|
||||
cam_coord = np.dot(R, world_coord - T)
|
||||
return cam_coord
|
||||
|
||||
@staticmethod
|
||||
def _pixel2cam(pixel_coord, f, c):
|
||||
"""Transform the joints from their pixel coordinates to their camera
|
||||
coordinates.
|
||||
|
||||
Note:
|
||||
N: number of joints
|
||||
|
||||
Args:
|
||||
pixel_coord (ndarray[N, 3]): 3D joints coordinates
|
||||
in the pixel coordinate system
|
||||
f (ndarray[2]): focal length of x and y axis
|
||||
c (ndarray[2]): principal point of x and y axis
|
||||
|
||||
Returns:
|
||||
cam_coord (ndarray[N, 3]): 3D joints coordinates
|
||||
in the camera coordinate system
|
||||
"""
|
||||
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]
|
||||
cam_coord = np.concatenate((x[:, None], y[:, None], z[:, None]), 1)
|
||||
return cam_coord
|
||||
|
||||
@staticmethod
|
||||
def _get_mapping_id_name(imgs):
|
||||
"""
|
||||
Args:
|
||||
imgs (dict): dict of image info.
|
||||
|
||||
Returns:
|
||||
tuple: Image name & id mapping dicts.
|
||||
|
||||
- id2name (dict): Mapping image id to name.
|
||||
- name2id (dict): Mapping image name to id.
|
||||
"""
|
||||
id2name = {}
|
||||
name2id = {}
|
||||
for image_id, image in imgs.items():
|
||||
file_name = image['file_name']
|
||||
id2name[image_id] = file_name
|
||||
name2id[file_name] = image_id
|
||||
|
||||
return id2name, name2id
|
||||
|
||||
def _xywh2cs(self, x, y, w, h, padding=1.25):
|
||||
"""This encodes bbox(x,y,w,h) into (center, scale)
|
||||
|
||||
Args:
|
||||
x, y, w, h (float): left, top, width and height
|
||||
padding (float): bounding box padding factor
|
||||
|
||||
Returns:
|
||||
center (np.ndarray[float32](2,)): center of the bbox (x, y).
|
||||
scale (np.ndarray[float32](2,)): scale of the bbox w & h.
|
||||
"""
|
||||
aspect_ratio = self.ann_info['image_size'][0] / self.ann_info[
|
||||
'image_size'][1]
|
||||
center = np.array([x + w * 0.5, y + h * 0.5], dtype=np.float32)
|
||||
|
||||
if (not self.test_mode) and np.random.rand() < 0.3:
|
||||
center += 0.4 * (np.random.rand(2) - 0.5) * [w, h]
|
||||
|
||||
if w > aspect_ratio * h:
|
||||
h = w * 1.0 / aspect_ratio
|
||||
elif w < aspect_ratio * h:
|
||||
w = h * aspect_ratio
|
||||
|
||||
# pixel std is 200.0
|
||||
scale = np.array([w / 200.0, h / 200.0], dtype=np.float32)
|
||||
# padding to include proper amount of context
|
||||
scale = scale * padding
|
||||
|
||||
return center, scale
|
||||
|
||||
@abstractmethod
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def evaluate(self, results, *args, **kwargs):
|
||||
"""Evaluate keypoint results."""
|
||||
|
||||
@staticmethod
|
||||
def _write_keypoint_results(keypoints, res_file):
|
||||
"""Write results into a json file."""
|
||||
|
||||
with open(res_file, 'w') as f:
|
||||
json.dump(keypoints, f, sort_keys=True, indent=4)
|
||||
|
||||
def __len__(self):
|
||||
"""Get the size of the dataset."""
|
||||
return len(self.db)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
"""Get the sample given index."""
|
||||
results = copy.deepcopy(self.db[idx])
|
||||
results['ann_info'] = self.ann_info
|
||||
return self.pipeline(results)
|
||||
|
||||
def _sort_and_unique_bboxes(self, kpts, key='bbox_id'):
|
||||
"""sort kpts and remove the repeated ones."""
|
||||
kpts = sorted(kpts, key=lambda x: x[key])
|
||||
num = len(kpts)
|
||||
for i in range(num - 1, 0, -1):
|
||||
if kpts[i][key] == kpts[i - 1][key]:
|
||||
del kpts[i]
|
||||
|
||||
return kpts
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .body3d_h36m_dataset import Body3DH36MDataset
|
||||
from .body3d_mpi_inf_3dhp_dataset import Body3DMpiInf3dhpDataset
|
||||
from .body3d_mview_direct_panoptic_dataset import \
|
||||
Body3DMviewDirectPanopticDataset
|
||||
from .body3d_semi_supervision_dataset import Body3DSemiSupervisionDataset
|
||||
|
||||
__all__ = [
|
||||
'Body3DH36MDataset', 'Body3DSemiSupervisionDataset',
|
||||
'Body3DMpiInf3dhpDataset', 'Body3DMviewDirectPanopticDataset'
|
||||
]
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from abc import ABCMeta
|
||||
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
|
||||
class Body3DBaseDataset(Dataset, metaclass=ABCMeta):
|
||||
"""This class has been deprecated and replaced by
|
||||
Kpt3dSviewKpt2dDataset."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
raise (ImportError(
|
||||
'Body3DBaseDataset has been replaced by '
|
||||
'Kpt3dSviewKpt2dDataset'
|
||||
'check https://github.com/open-mmlab/mmpose/pull/663 for details.')
|
||||
)
|
||||
Vendored
+343
@@ -0,0 +1,343 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
import mmcv
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.core.evaluation import keypoint_mpjpe
|
||||
from mmpose.datasets.datasets.base import Kpt3dSviewKpt2dDataset
|
||||
from ...builder import DATASETS
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class Body3DH36MDataset(Kpt3dSviewKpt2dDataset):
|
||||
"""Human3.6M dataset for 3D human pose estimation.
|
||||
|
||||
"Human3.6M: Large Scale Datasets and Predictive Methods for 3D Human
|
||||
Sensing in Natural Environments", TPAMI`2014.
|
||||
More details can be found in the `paper
|
||||
<http://vision.imar.ro/human3.6m/pami-h36m.pdf>`__.
|
||||
|
||||
Human3.6M keypoint indexes::
|
||||
|
||||
0: 'root (pelvis)',
|
||||
1: 'right_hip',
|
||||
2: 'right_knee',
|
||||
3: 'right_foot',
|
||||
4: 'left_hip',
|
||||
5: 'left_knee',
|
||||
6: 'left_foot',
|
||||
7: 'spine',
|
||||
8: 'thorax',
|
||||
9: 'neck_base',
|
||||
10: 'head',
|
||||
11: 'left_shoulder',
|
||||
12: 'left_elbow',
|
||||
13: 'left_wrist',
|
||||
14: 'right_shoulder',
|
||||
15: 'right_elbow',
|
||||
16: 'right_wrist'
|
||||
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
JOINT_NAMES = [
|
||||
'Root', 'RHip', 'RKnee', 'RFoot', 'LHip', 'LKnee', 'LFoot', 'Spine',
|
||||
'Thorax', 'NeckBase', 'Head', 'LShoulder', 'LElbow', 'LWrist',
|
||||
'RShoulder', 'RElbow', 'RWrist'
|
||||
]
|
||||
|
||||
# 2D joint source options:
|
||||
# "gt": from the annotation file
|
||||
# "detection": from a detection result file of 2D keypoint
|
||||
# "pipeline": will be generate by the pipeline
|
||||
SUPPORTED_JOINT_2D_SRC = {'gt', 'detection', 'pipeline'}
|
||||
|
||||
# metric
|
||||
ALLOWED_METRICS = {'mpjpe', 'p-mpjpe', 'n-mpjpe'}
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/h36m.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
def load_config(self, data_cfg):
|
||||
super().load_config(data_cfg)
|
||||
# h36m specific attributes
|
||||
self.joint_2d_src = data_cfg.get('joint_2d_src', 'gt')
|
||||
if self.joint_2d_src not in self.SUPPORTED_JOINT_2D_SRC:
|
||||
raise ValueError(
|
||||
f'Unsupported joint_2d_src "{self.joint_2d_src}". '
|
||||
f'Supported options are {self.SUPPORTED_JOINT_2D_SRC}')
|
||||
|
||||
self.joint_2d_det_file = data_cfg.get('joint_2d_det_file', None)
|
||||
|
||||
self.need_camera_param = data_cfg.get('need_camera_param', False)
|
||||
if self.need_camera_param:
|
||||
assert 'camera_param_file' in data_cfg
|
||||
self.camera_param = self._load_camera_param(
|
||||
data_cfg['camera_param_file'])
|
||||
|
||||
# h36m specific annotation info
|
||||
ann_info = {}
|
||||
ann_info['use_different_joint_weights'] = False
|
||||
# action filter
|
||||
actions = data_cfg.get('actions', '_all_')
|
||||
self.actions = set(
|
||||
actions if isinstance(actions, (list, tuple)) else [actions])
|
||||
|
||||
# subject filter
|
||||
subjects = data_cfg.get('subjects', '_all_')
|
||||
self.subjects = set(
|
||||
subjects if isinstance(subjects, (list, tuple)) else [subjects])
|
||||
|
||||
self.ann_info.update(ann_info)
|
||||
|
||||
def load_annotations(self):
|
||||
data_info = super().load_annotations()
|
||||
|
||||
# get 2D joints
|
||||
if self.joint_2d_src == 'gt':
|
||||
data_info['joints_2d'] = data_info['joints_2d']
|
||||
elif self.joint_2d_src == 'detection':
|
||||
data_info['joints_2d'] = self._load_joint_2d_detection(
|
||||
self.joint_2d_det_file)
|
||||
assert data_info['joints_2d'].shape[0] == data_info[
|
||||
'joints_3d'].shape[0]
|
||||
assert data_info['joints_2d'].shape[2] == 3
|
||||
elif self.joint_2d_src == 'pipeline':
|
||||
# joint_2d will be generated in the pipeline
|
||||
pass
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f'Unhandled joint_2d_src option {self.joint_2d_src}')
|
||||
|
||||
return data_info
|
||||
|
||||
@staticmethod
|
||||
def _parse_h36m_imgname(imgname):
|
||||
"""Parse imgname to get information of subject, action and camera.
|
||||
|
||||
A typical h36m image filename is like:
|
||||
S1_Directions_1.54138969_000001.jpg
|
||||
"""
|
||||
subj, rest = osp.basename(imgname).split('_', 1)
|
||||
action, rest = rest.split('.', 1)
|
||||
camera, rest = rest.split('_', 1)
|
||||
|
||||
return subj, action, camera
|
||||
|
||||
def build_sample_indices(self):
|
||||
"""Split original videos into sequences and build frame indices.
|
||||
|
||||
This method overrides the default one in the base class.
|
||||
"""
|
||||
|
||||
# Group frames into videos. Assume that self.data_info is
|
||||
# chronological.
|
||||
video_frames = defaultdict(list)
|
||||
for idx, imgname in enumerate(self.data_info['imgnames']):
|
||||
subj, action, camera = self._parse_h36m_imgname(imgname)
|
||||
|
||||
if '_all_' not in self.actions and action not in self.actions:
|
||||
continue
|
||||
|
||||
if '_all_' not in self.subjects and subj not in self.subjects:
|
||||
continue
|
||||
|
||||
video_frames[(subj, action, camera)].append(idx)
|
||||
|
||||
# build sample indices
|
||||
sample_indices = []
|
||||
_len = (self.seq_len - 1) * self.seq_frame_interval + 1
|
||||
_step = self.seq_frame_interval
|
||||
for _, _indices in sorted(video_frames.items()):
|
||||
n_frame = len(_indices)
|
||||
|
||||
if self.temporal_padding:
|
||||
# Pad the sequence so that every frame in the sequence will be
|
||||
# predicted.
|
||||
if self.causal:
|
||||
frames_left = self.seq_len - 1
|
||||
frames_right = 0
|
||||
else:
|
||||
frames_left = (self.seq_len - 1) // 2
|
||||
frames_right = frames_left
|
||||
for i in range(n_frame):
|
||||
pad_left = max(0, frames_left - i // _step)
|
||||
pad_right = max(0,
|
||||
frames_right - (n_frame - 1 - i) // _step)
|
||||
start = max(i % _step, i - frames_left * _step)
|
||||
end = min(n_frame - (n_frame - 1 - i) % _step,
|
||||
i + frames_right * _step + 1)
|
||||
sample_indices.append([_indices[0]] * pad_left +
|
||||
_indices[start:end:_step] +
|
||||
[_indices[-1]] * pad_right)
|
||||
else:
|
||||
seqs_from_video = [
|
||||
_indices[i:(i + _len):_step]
|
||||
for i in range(0, n_frame - _len + 1)
|
||||
]
|
||||
sample_indices.extend(seqs_from_video)
|
||||
|
||||
# reduce dataset size if self.subset < 1
|
||||
assert 0 < self.subset <= 1
|
||||
subset_size = int(len(sample_indices) * self.subset)
|
||||
start = np.random.randint(0, len(sample_indices) - subset_size + 1)
|
||||
end = start + subset_size
|
||||
|
||||
return sample_indices[start:end]
|
||||
|
||||
def _load_joint_2d_detection(self, det_file):
|
||||
""""Load 2D joint detection results from file."""
|
||||
joints_2d = np.load(det_file).astype(np.float32)
|
||||
|
||||
return joints_2d
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='mpjpe', **kwargs):
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
for _metric in metrics:
|
||||
if _metric not in self.ALLOWED_METRICS:
|
||||
raise ValueError(
|
||||
f'Unsupported metric "{_metric}" for human3.6 dataset.'
|
||||
f'Supported metrics are {self.ALLOWED_METRICS}')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
image_paths = result['target_image_paths']
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
target_id = self.name2id[image_paths[i]]
|
||||
kpts.append({
|
||||
'keypoints': preds[i],
|
||||
'target_id': target_id,
|
||||
})
|
||||
|
||||
mmcv.dump(kpts, res_file)
|
||||
|
||||
name_value_tuples = []
|
||||
for _metric in metrics:
|
||||
if _metric == 'mpjpe':
|
||||
_nv_tuples = self._report_mpjpe(kpts)
|
||||
elif _metric == 'p-mpjpe':
|
||||
_nv_tuples = self._report_mpjpe(kpts, mode='p-mpjpe')
|
||||
elif _metric == 'n-mpjpe':
|
||||
_nv_tuples = self._report_mpjpe(kpts, mode='n-mpjpe')
|
||||
else:
|
||||
raise NotImplementedError
|
||||
name_value_tuples.extend(_nv_tuples)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return OrderedDict(name_value_tuples)
|
||||
|
||||
def _report_mpjpe(self, keypoint_results, mode='mpjpe'):
|
||||
"""Cauculate mean per joint position error (MPJPE) or its variants like
|
||||
P-MPJPE or N-MPJPE.
|
||||
|
||||
Args:
|
||||
keypoint_results (list): Keypoint predictions. See
|
||||
'Body3DH36MDataset.evaluate' for details.
|
||||
mode (str): Specify mpjpe variants. Supported options are:
|
||||
|
||||
- ``'mpjpe'``: Standard MPJPE.
|
||||
- ``'p-mpjpe'``: MPJPE after aligning prediction to groundtruth
|
||||
via a rigid transformation (scale, rotation and
|
||||
translation).
|
||||
- ``'n-mpjpe'``: MPJPE after aligning prediction to groundtruth
|
||||
in scale only.
|
||||
"""
|
||||
|
||||
preds = []
|
||||
gts = []
|
||||
masks = []
|
||||
action_category_indices = defaultdict(list)
|
||||
for idx, result in enumerate(keypoint_results):
|
||||
pred = result['keypoints']
|
||||
target_id = result['target_id']
|
||||
gt, gt_visible = np.split(
|
||||
self.data_info['joints_3d'][target_id], [3], axis=-1)
|
||||
preds.append(pred)
|
||||
gts.append(gt)
|
||||
masks.append(gt_visible)
|
||||
|
||||
action = self._parse_h36m_imgname(
|
||||
self.data_info['imgnames'][target_id])[1]
|
||||
action_category = action.split('_')[0]
|
||||
action_category_indices[action_category].append(idx)
|
||||
|
||||
preds = np.stack(preds)
|
||||
gts = np.stack(gts)
|
||||
masks = np.stack(masks).squeeze(-1) > 0
|
||||
|
||||
err_name = mode.upper()
|
||||
if mode == 'mpjpe':
|
||||
alignment = 'none'
|
||||
elif mode == 'p-mpjpe':
|
||||
alignment = 'procrustes'
|
||||
elif mode == 'n-mpjpe':
|
||||
alignment = 'scale'
|
||||
else:
|
||||
raise ValueError(f'Invalid mode: {mode}')
|
||||
|
||||
error = keypoint_mpjpe(preds, gts, masks, alignment)
|
||||
name_value_tuples = [(err_name, error)]
|
||||
|
||||
for action_category, indices in action_category_indices.items():
|
||||
_error = keypoint_mpjpe(preds[indices], gts[indices],
|
||||
masks[indices])
|
||||
name_value_tuples.append((f'{err_name}_{action_category}', _error))
|
||||
|
||||
return name_value_tuples
|
||||
|
||||
def _load_camera_param(self, camera_param_file):
|
||||
"""Load camera parameters from file."""
|
||||
return mmcv.load(camera_param_file)
|
||||
|
||||
def get_camera_param(self, imgname):
|
||||
"""Get camera parameters of a frame by its image name."""
|
||||
assert hasattr(self, 'camera_param')
|
||||
subj, _, camera = self._parse_h36m_imgname(imgname)
|
||||
return self.camera_param[(subj, camera)]
|
||||
third_party/hamer/third-party/ViTPose/mmpose/datasets/datasets/body3d/body3d_mpi_inf_3dhp_dataset.py
Vendored
+417
@@ -0,0 +1,417 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
import mmcv
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.core.evaluation import (keypoint_3d_auc, keypoint_3d_pck,
|
||||
keypoint_mpjpe)
|
||||
from mmpose.datasets.datasets.base import Kpt3dSviewKpt2dDataset
|
||||
from ...builder import DATASETS
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class Body3DMpiInf3dhpDataset(Kpt3dSviewKpt2dDataset):
|
||||
"""MPI-INF-3DHP dataset for 3D human pose estimation.
|
||||
|
||||
"Monocular 3D Human Pose Estimation In The Wild Using Improved CNN
|
||||
Supervision", 3DV'2017.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/pdf/1611.09813>`__.
|
||||
|
||||
MPI-INF-3DHP keypoint indexes:
|
||||
|
||||
0: 'head_top',
|
||||
1: 'neck',
|
||||
2: 'right_shoulder',
|
||||
3: 'right_elbow',
|
||||
4: 'right_wrist',
|
||||
5: 'left_shoulder;,
|
||||
6: 'left_elbow',
|
||||
7: 'left_wrist',
|
||||
8: 'right_hip',
|
||||
9: 'right_knee',
|
||||
10: 'right_ankle',
|
||||
11: 'left_hip',
|
||||
12: 'left_knee',
|
||||
13: 'left_ankle',
|
||||
14: 'root (pelvis)',
|
||||
15: 'spine',
|
||||
16: 'head'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): Data configurations. Please refer to the docstring of
|
||||
Body3DBaseDataset for common data attributes. Here are MPI-INF-3DHP
|
||||
specific attributes.
|
||||
- joint_2d_src: 2D joint source. Options include:
|
||||
"gt": from the annotation file
|
||||
"detection": from a detection result file of 2D keypoint
|
||||
"pipeline": will be generate by the pipeline
|
||||
Default: "gt".
|
||||
- joint_2d_det_file: Path to the detection result file of 2D
|
||||
keypoint. Only used when joint_2d_src == "detection".
|
||||
- need_camera_param: Whether need camera parameters or not.
|
||||
Default: False.
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
JOINT_NAMES = [
|
||||
'HeadTop', 'Neck', 'RShoulder', 'RElbow', 'RWrist', 'LShoulder',
|
||||
'LElbow', 'LWrist', 'RHip', 'RKnee', 'RAnkle', 'LHip', 'LKnee',
|
||||
'LAnkle', 'Root', 'Spine', 'Head'
|
||||
]
|
||||
|
||||
# 2D joint source options:
|
||||
# "gt": from the annotation file
|
||||
# "detection": from a detection result file of 2D keypoint
|
||||
# "pipeline": will be generate by the pipeline
|
||||
SUPPORTED_JOINT_2D_SRC = {'gt', 'detection', 'pipeline'}
|
||||
|
||||
# metric
|
||||
ALLOWED_METRICS = {
|
||||
'mpjpe', 'p-mpjpe', '3dpck', 'p-3dpck', '3dauc', 'p-3dauc'
|
||||
}
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/mpi_inf_3dhp.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
def load_config(self, data_cfg):
|
||||
super().load_config(data_cfg)
|
||||
# mpi-inf-3dhp specific attributes
|
||||
self.joint_2d_src = data_cfg.get('joint_2d_src', 'gt')
|
||||
if self.joint_2d_src not in self.SUPPORTED_JOINT_2D_SRC:
|
||||
raise ValueError(
|
||||
f'Unsupported joint_2d_src "{self.joint_2d_src}". '
|
||||
f'Supported options are {self.SUPPORTED_JOINT_2D_SRC}')
|
||||
|
||||
self.joint_2d_det_file = data_cfg.get('joint_2d_det_file', None)
|
||||
|
||||
self.need_camera_param = data_cfg.get('need_camera_param', False)
|
||||
if self.need_camera_param:
|
||||
assert 'camera_param_file' in data_cfg
|
||||
self.camera_param = self._load_camera_param(
|
||||
data_cfg['camera_param_file'])
|
||||
|
||||
# mpi-inf-3dhp specific annotation info
|
||||
ann_info = {}
|
||||
ann_info['use_different_joint_weights'] = False
|
||||
|
||||
self.ann_info.update(ann_info)
|
||||
|
||||
def load_annotations(self):
|
||||
data_info = super().load_annotations()
|
||||
|
||||
# get 2D joints
|
||||
if self.joint_2d_src == 'gt':
|
||||
data_info['joints_2d'] = data_info['joints_2d']
|
||||
elif self.joint_2d_src == 'detection':
|
||||
data_info['joints_2d'] = self._load_joint_2d_detection(
|
||||
self.joint_2d_det_file)
|
||||
assert data_info['joints_2d'].shape[0] == data_info[
|
||||
'joints_3d'].shape[0]
|
||||
assert data_info['joints_2d'].shape[2] == 3
|
||||
elif self.joint_2d_src == 'pipeline':
|
||||
# joint_2d will be generated in the pipeline
|
||||
pass
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f'Unhandled joint_2d_src option {self.joint_2d_src}')
|
||||
|
||||
return data_info
|
||||
|
||||
@staticmethod
|
||||
def _parse_mpi_inf_3dhp_imgname(imgname):
|
||||
"""Parse imgname to get information of subject, sequence and camera.
|
||||
|
||||
A typical mpi-inf-3dhp training image filename is like:
|
||||
S1_Seq1_Cam0_000001.jpg. A typical mpi-inf-3dhp testing image filename
|
||||
is like: TS1_000001.jpg
|
||||
"""
|
||||
if imgname[0] == 'S':
|
||||
subj, rest = imgname.split('_', 1)
|
||||
seq, rest = rest.split('_', 1)
|
||||
camera, rest = rest.split('_', 1)
|
||||
return subj, seq, camera
|
||||
else:
|
||||
subj, rest = imgname.split('_', 1)
|
||||
return subj, None, None
|
||||
|
||||
def build_sample_indices(self):
|
||||
"""Split original videos into sequences and build frame indices.
|
||||
|
||||
This method overrides the default one in the base class.
|
||||
"""
|
||||
|
||||
# Group frames into videos. Assume that self.data_info is
|
||||
# chronological.
|
||||
video_frames = defaultdict(list)
|
||||
for idx, imgname in enumerate(self.data_info['imgnames']):
|
||||
subj, seq, camera = self._parse_mpi_inf_3dhp_imgname(imgname)
|
||||
if seq is not None:
|
||||
video_frames[(subj, seq, camera)].append(idx)
|
||||
else:
|
||||
video_frames[subj].append(idx)
|
||||
|
||||
# build sample indices
|
||||
sample_indices = []
|
||||
_len = (self.seq_len - 1) * self.seq_frame_interval + 1
|
||||
_step = self.seq_frame_interval
|
||||
for _, _indices in sorted(video_frames.items()):
|
||||
n_frame = len(_indices)
|
||||
|
||||
if self.temporal_padding:
|
||||
# Pad the sequence so that every frame in the sequence will be
|
||||
# predicted.
|
||||
if self.causal:
|
||||
frames_left = self.seq_len - 1
|
||||
frames_right = 0
|
||||
else:
|
||||
frames_left = (self.seq_len - 1) // 2
|
||||
frames_right = frames_left
|
||||
for i in range(n_frame):
|
||||
pad_left = max(0, frames_left - i // _step)
|
||||
pad_right = max(0,
|
||||
frames_right - (n_frame - 1 - i) // _step)
|
||||
start = max(i % _step, i - frames_left * _step)
|
||||
end = min(n_frame - (n_frame - 1 - i) % _step,
|
||||
i + frames_right * _step + 1)
|
||||
sample_indices.append([_indices[0]] * pad_left +
|
||||
_indices[start:end:_step] +
|
||||
[_indices[-1]] * pad_right)
|
||||
else:
|
||||
seqs_from_video = [
|
||||
_indices[i:(i + _len):_step]
|
||||
for i in range(0, n_frame - _len + 1)
|
||||
]
|
||||
sample_indices.extend(seqs_from_video)
|
||||
|
||||
# reduce dataset size if self.subset < 1
|
||||
assert 0 < self.subset <= 1
|
||||
subset_size = int(len(sample_indices) * self.subset)
|
||||
start = np.random.randint(0, len(sample_indices) - subset_size + 1)
|
||||
end = start + subset_size
|
||||
|
||||
return sample_indices[start:end]
|
||||
|
||||
def _load_joint_2d_detection(self, det_file):
|
||||
""""Load 2D joint detection results from file."""
|
||||
joints_2d = np.load(det_file).astype(np.float32)
|
||||
|
||||
return joints_2d
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='mpjpe', **kwargs):
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
for _metric in metrics:
|
||||
if _metric not in self.ALLOWED_METRICS:
|
||||
raise ValueError(
|
||||
f'Unsupported metric "{_metric}" for mpi-inf-3dhp dataset.'
|
||||
f'Supported metrics are {self.ALLOWED_METRICS}')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
image_paths = result['target_image_paths']
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
target_id = self.name2id[image_paths[i]]
|
||||
kpts.append({
|
||||
'keypoints': preds[i],
|
||||
'target_id': target_id,
|
||||
})
|
||||
|
||||
mmcv.dump(kpts, res_file)
|
||||
|
||||
name_value_tuples = []
|
||||
for _metric in metrics:
|
||||
if _metric == 'mpjpe':
|
||||
_nv_tuples = self._report_mpjpe(kpts)
|
||||
elif _metric == 'p-mpjpe':
|
||||
_nv_tuples = self._report_mpjpe(kpts, mode='p-mpjpe')
|
||||
elif _metric == '3dpck':
|
||||
_nv_tuples = self._report_3d_pck(kpts)
|
||||
elif _metric == 'p-3dpck':
|
||||
_nv_tuples = self._report_3d_pck(kpts, mode='p-3dpck')
|
||||
elif _metric == '3dauc':
|
||||
_nv_tuples = self._report_3d_auc(kpts)
|
||||
elif _metric == 'p-3dauc':
|
||||
_nv_tuples = self._report_3d_auc(kpts, mode='p-3dauc')
|
||||
else:
|
||||
raise NotImplementedError
|
||||
name_value_tuples.extend(_nv_tuples)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return OrderedDict(name_value_tuples)
|
||||
|
||||
def _report_mpjpe(self, keypoint_results, mode='mpjpe'):
|
||||
"""Cauculate mean per joint position error (MPJPE) or its variants
|
||||
P-MPJPE.
|
||||
|
||||
Args:
|
||||
keypoint_results (list): Keypoint predictions. See
|
||||
'Body3DMpiInf3dhpDataset.evaluate' for details.
|
||||
mode (str): Specify mpjpe variants. Supported options are:
|
||||
- ``'mpjpe'``: Standard MPJPE.
|
||||
- ``'p-mpjpe'``: MPJPE after aligning prediction to groundtruth
|
||||
via a rigid transformation (scale, rotation and
|
||||
translation).
|
||||
"""
|
||||
|
||||
preds = []
|
||||
gts = []
|
||||
for idx, result in enumerate(keypoint_results):
|
||||
pred = result['keypoints']
|
||||
target_id = result['target_id']
|
||||
gt, gt_visible = np.split(
|
||||
self.data_info['joints_3d'][target_id], [3], axis=-1)
|
||||
preds.append(pred)
|
||||
gts.append(gt)
|
||||
|
||||
preds = np.stack(preds)
|
||||
gts = np.stack(gts)
|
||||
masks = np.ones_like(gts[:, :, 0], dtype=bool)
|
||||
|
||||
err_name = mode.upper()
|
||||
if mode == 'mpjpe':
|
||||
alignment = 'none'
|
||||
elif mode == 'p-mpjpe':
|
||||
alignment = 'procrustes'
|
||||
else:
|
||||
raise ValueError(f'Invalid mode: {mode}')
|
||||
|
||||
error = keypoint_mpjpe(preds, gts, masks, alignment)
|
||||
name_value_tuples = [(err_name, error)]
|
||||
|
||||
return name_value_tuples
|
||||
|
||||
def _report_3d_pck(self, keypoint_results, mode='3dpck'):
|
||||
"""Cauculate Percentage of Correct Keypoints (3DPCK) w. or w/o
|
||||
Procrustes alignment.
|
||||
|
||||
Args:
|
||||
keypoint_results (list): Keypoint predictions. See
|
||||
'Body3DMpiInf3dhpDataset.evaluate' for details.
|
||||
mode (str): Specify mpjpe variants. Supported options are:
|
||||
- ``'3dpck'``: Standard 3DPCK.
|
||||
- ``'p-3dpck'``: 3DPCK after aligning prediction to groundtruth
|
||||
via a rigid transformation (scale, rotation and
|
||||
translation).
|
||||
"""
|
||||
|
||||
preds = []
|
||||
gts = []
|
||||
for idx, result in enumerate(keypoint_results):
|
||||
pred = result['keypoints']
|
||||
target_id = result['target_id']
|
||||
gt, gt_visible = np.split(
|
||||
self.data_info['joints_3d'][target_id], [3], axis=-1)
|
||||
preds.append(pred)
|
||||
gts.append(gt)
|
||||
|
||||
preds = np.stack(preds)
|
||||
gts = np.stack(gts)
|
||||
masks = np.ones_like(gts[:, :, 0], dtype=bool)
|
||||
|
||||
err_name = mode.upper()
|
||||
if mode == '3dpck':
|
||||
alignment = 'none'
|
||||
elif mode == 'p-3dpck':
|
||||
alignment = 'procrustes'
|
||||
else:
|
||||
raise ValueError(f'Invalid mode: {mode}')
|
||||
|
||||
error = keypoint_3d_pck(preds, gts, masks, alignment)
|
||||
name_value_tuples = [(err_name, error)]
|
||||
|
||||
return name_value_tuples
|
||||
|
||||
def _report_3d_auc(self, keypoint_results, mode='3dauc'):
|
||||
"""Cauculate the Area Under the Curve (AUC) computed for a range of
|
||||
3DPCK thresholds.
|
||||
|
||||
Args:
|
||||
keypoint_results (list): Keypoint predictions. See
|
||||
'Body3DMpiInf3dhpDataset.evaluate' for details.
|
||||
mode (str): Specify mpjpe variants. Supported options are:
|
||||
|
||||
- ``'3dauc'``: Standard 3DAUC.
|
||||
- ``'p-3dauc'``: 3DAUC after aligning prediction to
|
||||
groundtruth via a rigid transformation (scale, rotation and
|
||||
translation).
|
||||
"""
|
||||
|
||||
preds = []
|
||||
gts = []
|
||||
for idx, result in enumerate(keypoint_results):
|
||||
pred = result['keypoints']
|
||||
target_id = result['target_id']
|
||||
gt, gt_visible = np.split(
|
||||
self.data_info['joints_3d'][target_id], [3], axis=-1)
|
||||
preds.append(pred)
|
||||
gts.append(gt)
|
||||
|
||||
preds = np.stack(preds)
|
||||
gts = np.stack(gts)
|
||||
masks = np.ones_like(gts[:, :, 0], dtype=bool)
|
||||
|
||||
err_name = mode.upper()
|
||||
if mode == '3dauc':
|
||||
alignment = 'none'
|
||||
elif mode == 'p-3dauc':
|
||||
alignment = 'procrustes'
|
||||
else:
|
||||
raise ValueError(f'Invalid mode: {mode}')
|
||||
|
||||
error = keypoint_3d_auc(preds, gts, masks, alignment)
|
||||
name_value_tuples = [(err_name, error)]
|
||||
|
||||
return name_value_tuples
|
||||
|
||||
def _load_camera_param(self, camear_param_file):
|
||||
"""Load camera parameters from file."""
|
||||
return mmcv.load(camear_param_file)
|
||||
|
||||
def get_camera_param(self, imgname):
|
||||
"""Get camera parameters of a frame by its image name."""
|
||||
assert hasattr(self, 'camera_param')
|
||||
return self.camera_param[imgname[:-11]]
|
||||
+493
@@ -0,0 +1,493 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
import glob
|
||||
import json
|
||||
import os.path as osp
|
||||
import pickle
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import mmcv
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.core.camera import SimpleCamera
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from mmpose.datasets.datasets.base import Kpt3dMviewRgbImgDirectDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class Body3DMviewDirectPanopticDataset(Kpt3dMviewRgbImgDirectDataset):
|
||||
"""Panoptic dataset for direct multi-view human pose estimation.
|
||||
|
||||
`Panoptic Studio: A Massively Multiview System for Social Motion
|
||||
Capture' ICCV'2015
|
||||
More details can be found in the `paper
|
||||
<https://openaccess.thecvf.com/content_iccv_2015/papers/
|
||||
Joo_Panoptic_Studio_A_ICCV_2015_paper.pdf>`__ .
|
||||
|
||||
The dataset loads both 2D and 3D annotations as well as camera parameters.
|
||||
|
||||
Panoptic keypoint indexes::
|
||||
|
||||
'neck': 0,
|
||||
'nose': 1,
|
||||
'mid-hip': 2,
|
||||
'l-shoulder': 3,
|
||||
'l-elbow': 4,
|
||||
'l-wrist': 5,
|
||||
'l-hip': 6,
|
||||
'l-knee': 7,
|
||||
'l-ankle': 8,
|
||||
'r-shoulder': 9,
|
||||
'r-elbow': 10,
|
||||
'r-wrist': 11,
|
||||
'r-hip': 12,
|
||||
'r-knee': 13,
|
||||
'r-ankle': 14,
|
||||
'l-eye': 15,
|
||||
'l-ear': 16,
|
||||
'r-eye': 17,
|
||||
'r-ear': 18,
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
ALLOWED_METRICS = {'mpjpe', 'mAP'}
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/panoptic_body3d.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.load_config(data_cfg)
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
|
||||
if ann_file is None:
|
||||
self.db_file = osp.join(
|
||||
img_prefix, f'group_{self.subset}_cam{self.num_cameras}.pkl')
|
||||
else:
|
||||
self.db_file = ann_file
|
||||
|
||||
if osp.exists(self.db_file):
|
||||
with open(self.db_file, 'rb') as f:
|
||||
info = pickle.load(f)
|
||||
assert info['sequence_list'] == self.seq_list
|
||||
assert info['interval'] == self.seq_frame_interval
|
||||
assert info['cam_list'] == self.cam_list
|
||||
self.db = info['db']
|
||||
else:
|
||||
self.db = self._get_db()
|
||||
info = {
|
||||
'sequence_list': self.seq_list,
|
||||
'interval': self.seq_frame_interval,
|
||||
'cam_list': self.cam_list,
|
||||
'db': self.db
|
||||
}
|
||||
with open(self.db_file, 'wb') as f:
|
||||
pickle.dump(info, f)
|
||||
|
||||
self.db_size = len(self.db)
|
||||
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def load_config(self, data_cfg):
|
||||
"""Initialize dataset attributes according to the config.
|
||||
|
||||
Override this method to set dataset specific attributes.
|
||||
"""
|
||||
self.num_joints = data_cfg['num_joints']
|
||||
assert self.num_joints <= 19
|
||||
self.seq_list = data_cfg['seq_list']
|
||||
self.cam_list = data_cfg['cam_list']
|
||||
self.num_cameras = data_cfg['num_cameras']
|
||||
assert self.num_cameras == len(self.cam_list)
|
||||
self.seq_frame_interval = data_cfg.get('seq_frame_interval', 1)
|
||||
self.subset = data_cfg.get('subset', 'train')
|
||||
self.need_camera_param = True
|
||||
self.root_id = data_cfg.get('root_id', 0)
|
||||
self.max_persons = data_cfg.get('max_num', 10)
|
||||
|
||||
def _get_scale(self, raw_image_size):
|
||||
heatmap_size = self.ann_info['heatmap_size']
|
||||
image_size = self.ann_info['image_size']
|
||||
assert heatmap_size[0][0] / heatmap_size[0][1] \
|
||||
== image_size[0] / image_size[1]
|
||||
w, h = raw_image_size
|
||||
w_resized, h_resized = image_size
|
||||
if w / w_resized < h / h_resized:
|
||||
w_pad = h / h_resized * w_resized
|
||||
h_pad = h
|
||||
else:
|
||||
w_pad = w
|
||||
h_pad = w / w_resized * h_resized
|
||||
|
||||
scale = np.array([w_pad, h_pad], dtype=np.float32)
|
||||
|
||||
return scale
|
||||
|
||||
def _get_cam(self, seq):
|
||||
"""Get camera parameters.
|
||||
|
||||
Args:
|
||||
seq (str): Sequence name.
|
||||
|
||||
Returns: Camera parameters.
|
||||
"""
|
||||
cam_file = osp.join(self.img_prefix, seq,
|
||||
'calibration_{:s}.json'.format(seq))
|
||||
with open(cam_file) as cfile:
|
||||
calib = json.load(cfile)
|
||||
|
||||
M = np.array([[1.0, 0.0, 0.0], [0.0, 0.0, -1.0], [0.0, 1.0, 0.0]])
|
||||
cameras = {}
|
||||
for cam in calib['cameras']:
|
||||
if (cam['panel'], cam['node']) in self.cam_list:
|
||||
sel_cam = {}
|
||||
R_w2c = np.array(cam['R']).dot(M)
|
||||
T_w2c = np.array(cam['t']).reshape((3, 1)) * 10.0 # cm to mm
|
||||
R_c2w = R_w2c.T
|
||||
T_c2w = -R_w2c.T @ T_w2c
|
||||
sel_cam['R'] = R_c2w.tolist()
|
||||
sel_cam['T'] = T_c2w.tolist()
|
||||
sel_cam['K'] = cam['K'][:2]
|
||||
distCoef = cam['distCoef']
|
||||
sel_cam['k'] = [distCoef[0], distCoef[1], distCoef[4]]
|
||||
sel_cam['p'] = [distCoef[2], distCoef[3]]
|
||||
cameras[(cam['panel'], cam['node'])] = sel_cam
|
||||
|
||||
return cameras
|
||||
|
||||
def _get_db(self):
|
||||
"""Get dataset base.
|
||||
|
||||
Returns:
|
||||
dict: the dataset base (2D and 3D information)
|
||||
"""
|
||||
width = 1920
|
||||
height = 1080
|
||||
db = []
|
||||
sample_id = 0
|
||||
for seq in self.seq_list:
|
||||
cameras = self._get_cam(seq)
|
||||
curr_anno = osp.join(self.img_prefix, seq,
|
||||
'hdPose3d_stage1_coco19')
|
||||
anno_files = sorted(glob.iglob('{:s}/*.json'.format(curr_anno)))
|
||||
print(f'load sequence: {seq}', flush=True)
|
||||
for i, file in enumerate(anno_files):
|
||||
if i % self.seq_frame_interval == 0:
|
||||
with open(file) as dfile:
|
||||
bodies = json.load(dfile)['bodies']
|
||||
if len(bodies) == 0:
|
||||
continue
|
||||
|
||||
for k, cam_param in cameras.items():
|
||||
single_view_camera = SimpleCamera(cam_param)
|
||||
postfix = osp.basename(file).replace('body3DScene', '')
|
||||
prefix = '{:02d}_{:02d}'.format(k[0], k[1])
|
||||
image_file = osp.join(seq, 'hdImgs', prefix,
|
||||
prefix + postfix)
|
||||
image_file = image_file.replace('json', 'jpg')
|
||||
|
||||
all_poses_3d = np.zeros(
|
||||
(self.max_persons, self.num_joints, 3),
|
||||
dtype=np.float32)
|
||||
all_poses_vis_3d = np.zeros(
|
||||
(self.max_persons, self.num_joints, 3),
|
||||
dtype=np.float32)
|
||||
all_roots_3d = np.zeros((self.max_persons, 3),
|
||||
dtype=np.float32)
|
||||
all_poses = np.zeros(
|
||||
(self.max_persons, self.num_joints, 3),
|
||||
dtype=np.float32)
|
||||
|
||||
cnt = 0
|
||||
person_ids = -np.ones(self.max_persons, dtype=np.int)
|
||||
for body in bodies:
|
||||
if cnt >= self.max_persons:
|
||||
break
|
||||
pose3d = np.array(body['joints19']).reshape(
|
||||
(-1, 4))
|
||||
pose3d = pose3d[:self.num_joints]
|
||||
|
||||
joints_vis = pose3d[:, -1] > 0.1
|
||||
|
||||
if not joints_vis[self.root_id]:
|
||||
continue
|
||||
|
||||
# Coordinate transformation
|
||||
M = np.array([[1.0, 0.0, 0.0], [0.0, 0.0, -1.0],
|
||||
[0.0, 1.0, 0.0]])
|
||||
pose3d[:, 0:3] = pose3d[:, 0:3].dot(M) * 10.0
|
||||
|
||||
all_poses_3d[cnt] = pose3d[:, :3]
|
||||
all_roots_3d[cnt] = pose3d[self.root_id, :3]
|
||||
all_poses_vis_3d[cnt] = np.repeat(
|
||||
np.reshape(joints_vis, (-1, 1)), 3, axis=1)
|
||||
|
||||
pose2d = np.zeros((pose3d.shape[0], 3))
|
||||
# get pose_2d from pose_3d
|
||||
pose2d[:, :2] = single_view_camera.world_to_pixel(
|
||||
pose3d[:, :3])
|
||||
x_check = np.bitwise_and(pose2d[:, 0] >= 0,
|
||||
pose2d[:, 0] <= width - 1)
|
||||
y_check = np.bitwise_and(
|
||||
pose2d[:, 1] >= 0, pose2d[:, 1] <= height - 1)
|
||||
check = np.bitwise_and(x_check, y_check)
|
||||
joints_vis[np.logical_not(check)] = 0
|
||||
pose2d[:, -1] = joints_vis
|
||||
|
||||
all_poses[cnt] = pose2d
|
||||
person_ids[cnt] = body['id']
|
||||
cnt += 1
|
||||
|
||||
if cnt > 0:
|
||||
db.append({
|
||||
'image_file':
|
||||
osp.join(self.img_prefix, image_file),
|
||||
'joints_3d':
|
||||
all_poses_3d,
|
||||
'person_ids':
|
||||
person_ids,
|
||||
'joints_3d_visible':
|
||||
all_poses_vis_3d,
|
||||
'joints': [all_poses],
|
||||
'roots_3d':
|
||||
all_roots_3d,
|
||||
'camera':
|
||||
cam_param,
|
||||
'num_persons':
|
||||
cnt,
|
||||
'sample_id':
|
||||
sample_id,
|
||||
'center':
|
||||
np.array((width / 2, height / 2),
|
||||
dtype=np.float32),
|
||||
'scale':
|
||||
self._get_scale((width, height))
|
||||
})
|
||||
sample_id += 1
|
||||
return db
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='mpjpe', **kwargs):
|
||||
"""
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
- pose_3d (np.ndarray): predicted 3D human pose
|
||||
- sample_id (np.ndarray): sample id of a frame.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Defaults: 'mpjpe'.
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
pose_3ds = np.concatenate([result['pose_3d'] for result in results],
|
||||
axis=0)
|
||||
sample_ids = []
|
||||
for result in results:
|
||||
sample_ids.extend(result['sample_id'])
|
||||
|
||||
_results = [
|
||||
dict(sample_id=sample_id, pose_3d=pose_3d)
|
||||
for (sample_id, pose_3d) in zip(sample_ids, pose_3ds)
|
||||
]
|
||||
_results = self._sort_and_unique_outputs(_results, key='sample_id')
|
||||
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
for _metric in metrics:
|
||||
if _metric not in self.ALLOWED_METRICS:
|
||||
raise ValueError(
|
||||
f'Unsupported metric "{_metric}"'
|
||||
f'Supported metrics are {self.ALLOWED_METRICS}')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
mmcv.dump(_results, res_file)
|
||||
|
||||
eval_list = []
|
||||
gt_num = self.db_size // self.num_cameras
|
||||
assert len(
|
||||
_results) == gt_num, f'number mismatch: {len(_results)}, {gt_num}'
|
||||
|
||||
total_gt = 0
|
||||
for i in range(gt_num):
|
||||
index = self.num_cameras * i
|
||||
db_rec = copy.deepcopy(self.db[index])
|
||||
joints_3d = db_rec['joints_3d']
|
||||
joints_3d_vis = db_rec['joints_3d_visible']
|
||||
|
||||
if joints_3d_vis.sum() < 1:
|
||||
continue
|
||||
|
||||
pred = _results[i]['pose_3d'].copy()
|
||||
pred = pred[pred[:, 0, 3] >= 0]
|
||||
for pose in pred:
|
||||
mpjpes = []
|
||||
for (gt, gt_vis) in zip(joints_3d, joints_3d_vis):
|
||||
vis = gt_vis[:, 0] > 0
|
||||
if vis.sum() < 1:
|
||||
break
|
||||
mpjpe = np.mean(
|
||||
np.sqrt(
|
||||
np.sum((pose[vis, 0:3] - gt[vis])**2, axis=-1)))
|
||||
mpjpes.append(mpjpe)
|
||||
min_gt = np.argmin(mpjpes)
|
||||
min_mpjpe = np.min(mpjpes)
|
||||
score = pose[0, 4]
|
||||
eval_list.append({
|
||||
'mpjpe': float(min_mpjpe),
|
||||
'score': float(score),
|
||||
'gt_id': int(total_gt + min_gt)
|
||||
})
|
||||
|
||||
total_gt += (joints_3d_vis[:, :, 0].sum(-1) >= 1).sum()
|
||||
|
||||
mpjpe_threshold = np.arange(25, 155, 25)
|
||||
aps = []
|
||||
ars = []
|
||||
for t in mpjpe_threshold:
|
||||
ap, ar = self._eval_list_to_ap(eval_list, total_gt, t)
|
||||
aps.append(ap)
|
||||
ars.append(ar)
|
||||
|
||||
name_value_tuples = []
|
||||
for _metric in metrics:
|
||||
if _metric == 'mpjpe':
|
||||
stats_names = ['RECALL 500mm', 'MPJPE 500mm']
|
||||
info_str = list(
|
||||
zip(stats_names, [
|
||||
self._eval_list_to_recall(eval_list, total_gt),
|
||||
self._eval_list_to_mpjpe(eval_list)
|
||||
]))
|
||||
elif _metric == 'mAP':
|
||||
stats_names = [
|
||||
'AP 25', 'AP 50', 'AP 75', 'AP 100', 'AP 125', 'AP 150',
|
||||
'mAP', 'AR 25', 'AR 50', 'AR 75', 'AR 100', 'AR 125',
|
||||
'AR 150', 'mAR'
|
||||
]
|
||||
mAP = np.array(aps).mean()
|
||||
mAR = np.array(ars).mean()
|
||||
info_str = list(zip(stats_names, aps + [mAP] + ars + [mAR]))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
name_value_tuples.extend(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return OrderedDict(name_value_tuples)
|
||||
|
||||
@staticmethod
|
||||
def _eval_list_to_ap(eval_list, total_gt, threshold):
|
||||
"""Get Average Precision (AP) and Average Recall at a certain
|
||||
threshold."""
|
||||
|
||||
eval_list.sort(key=lambda k: k['score'], reverse=True)
|
||||
total_num = len(eval_list)
|
||||
|
||||
tp = np.zeros(total_num)
|
||||
fp = np.zeros(total_num)
|
||||
gt_det = []
|
||||
for i, item in enumerate(eval_list):
|
||||
if item['mpjpe'] < threshold and item['gt_id'] not in gt_det:
|
||||
tp[i] = 1
|
||||
gt_det.append(item['gt_id'])
|
||||
else:
|
||||
fp[i] = 1
|
||||
tp = np.cumsum(tp)
|
||||
fp = np.cumsum(fp)
|
||||
recall = tp / (total_gt + 1e-5)
|
||||
precise = tp / (tp + fp + 1e-5)
|
||||
for n in range(total_num - 2, -1, -1):
|
||||
precise[n] = max(precise[n], precise[n + 1])
|
||||
|
||||
precise = np.concatenate(([0], precise, [0]))
|
||||
recall = np.concatenate(([0], recall, [1]))
|
||||
index = np.where(recall[1:] != recall[:-1])[0]
|
||||
ap = np.sum((recall[index + 1] - recall[index]) * precise[index + 1])
|
||||
|
||||
return ap, recall[-2]
|
||||
|
||||
@staticmethod
|
||||
def _eval_list_to_mpjpe(eval_list, threshold=500):
|
||||
"""Get MPJPE within a certain threshold."""
|
||||
eval_list.sort(key=lambda k: k['score'], reverse=True)
|
||||
gt_det = []
|
||||
|
||||
mpjpes = []
|
||||
for i, item in enumerate(eval_list):
|
||||
if item['mpjpe'] < threshold and item['gt_id'] not in gt_det:
|
||||
mpjpes.append(item['mpjpe'])
|
||||
gt_det.append(item['gt_id'])
|
||||
|
||||
return np.mean(mpjpes) if len(mpjpes) > 0 else np.inf
|
||||
|
||||
@staticmethod
|
||||
def _eval_list_to_recall(eval_list, total_gt, threshold=500):
|
||||
"""Get Recall at a certain threshold."""
|
||||
gt_ids = [e['gt_id'] for e in eval_list if e['mpjpe'] < threshold]
|
||||
|
||||
return len(np.unique(gt_ids)) / total_gt
|
||||
|
||||
def __getitem__(self, idx):
|
||||
"""Get the sample given index."""
|
||||
results = {}
|
||||
for c in range(self.num_cameras):
|
||||
result = copy.deepcopy(self.db[self.num_cameras * idx + c])
|
||||
result['ann_info'] = self.ann_info
|
||||
width = 1920
|
||||
height = 1080
|
||||
result['mask'] = [np.ones((height, width), dtype=np.float32)]
|
||||
results[c] = result
|
||||
|
||||
return self.pipeline(results)
|
||||
|
||||
@staticmethod
|
||||
def _sort_and_unique_outputs(outputs, key='sample_id'):
|
||||
"""sort outputs and remove the repeated ones."""
|
||||
outputs = sorted(outputs, key=lambda x: x[key])
|
||||
num_outputs = len(outputs)
|
||||
for i in range(num_outputs - 1, 0, -1):
|
||||
if outputs[i][key] == outputs[i - 1][key]:
|
||||
del outputs[i]
|
||||
|
||||
return outputs
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import numpy as np
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
from mmpose.datasets.builder import DATASETS, build_dataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class Body3DSemiSupervisionDataset(Dataset):
|
||||
"""Mix Dataset for semi-supervised training in 3D human pose estimation
|
||||
task.
|
||||
|
||||
The dataset combines data from two datasets (a labeled one and an unlabeled
|
||||
one) and return a dict containing data from two datasets.
|
||||
|
||||
Args:
|
||||
labeled_dataset (Dataset): Dataset with 3D keypoint annotations.
|
||||
unlabeled_dataset (Dataset): Dataset without 3D keypoint annotations.
|
||||
"""
|
||||
|
||||
def __init__(self, labeled_dataset, unlabeled_dataset):
|
||||
super().__init__()
|
||||
self.labeled_dataset = build_dataset(labeled_dataset)
|
||||
self.unlabeled_dataset = build_dataset(unlabeled_dataset)
|
||||
self.length = len(self.unlabeled_dataset)
|
||||
|
||||
def __len__(self):
|
||||
"""Get the size of the dataset."""
|
||||
return self.length
|
||||
|
||||
def __getitem__(self, i):
|
||||
"""Given index, get the data from unlabeled dataset and randomly sample
|
||||
an item from labeled dataset.
|
||||
|
||||
Return a dict containing data from labeled and unlabeled dataset.
|
||||
"""
|
||||
data = self.unlabeled_dataset[i]
|
||||
rand_ind = np.random.randint(0, len(self.labeled_dataset))
|
||||
labeled_data = self.labeled_dataset[rand_ind]
|
||||
data.update(labeled_data)
|
||||
return data
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .bottom_up_aic import BottomUpAicDataset
|
||||
from .bottom_up_coco import BottomUpCocoDataset
|
||||
from .bottom_up_coco_wholebody import BottomUpCocoWholeBodyDataset
|
||||
from .bottom_up_crowdpose import BottomUpCrowdPoseDataset
|
||||
from .bottom_up_mhp import BottomUpMhpDataset
|
||||
|
||||
__all__ = [
|
||||
'BottomUpCocoDataset', 'BottomUpCrowdPoseDataset', 'BottomUpMhpDataset',
|
||||
'BottomUpAicDataset', 'BottomUpCocoWholeBodyDataset'
|
||||
]
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
import json_tricks as json
|
||||
from mmcv import Config
|
||||
from xtcocotools.cocoeval import COCOeval
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from .bottom_up_coco import BottomUpCocoDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class BottomUpAicDataset(BottomUpCocoDataset):
|
||||
"""Aic dataset for bottom-up pose estimation.
|
||||
|
||||
"AI Challenger : A Large-scale Dataset for Going Deeper
|
||||
in Image Understanding", arXiv'2017.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1711.06475>`__
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
AIC keypoint indexes::
|
||||
|
||||
0: "right_shoulder",
|
||||
1: "right_elbow",
|
||||
2: "right_wrist",
|
||||
3: "left_shoulder",
|
||||
4: "left_elbow",
|
||||
5: "left_wrist",
|
||||
6: "right_hip",
|
||||
7: "right_knee",
|
||||
8: "right_ankle",
|
||||
9: "left_hip",
|
||||
10: "left_knee",
|
||||
11: "left_ankle",
|
||||
12: "head_top",
|
||||
13: "neck"
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/aic.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super(BottomUpCocoDataset, self).__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
|
||||
def _do_python_keypoint_eval(self, res_file):
|
||||
"""Keypoint evaluation using COCOAPI."""
|
||||
|
||||
stats_names = [
|
||||
'AP', 'AP .5', 'AP .75', 'AP (M)', 'AP (L)', 'AR', 'AR .5',
|
||||
'AR .75', 'AR (M)', 'AR (L)'
|
||||
]
|
||||
|
||||
with open(res_file, 'r') as file:
|
||||
res_json = json.load(file)
|
||||
if not res_json:
|
||||
info_str = list(zip(stats_names, [
|
||||
0,
|
||||
] * len(stats_names)))
|
||||
return info_str
|
||||
|
||||
coco_det = self.coco.loadRes(res_file)
|
||||
coco_eval = COCOeval(
|
||||
self.coco, coco_det, 'keypoints', self.sigmas, use_area=False)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
info_str = list(zip(stats_names, coco_eval.stats))
|
||||
|
||||
return info_str
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
|
||||
class BottomUpBaseDataset(Dataset):
|
||||
"""This class has been deprecated and replaced by
|
||||
Kpt2dSviewRgbImgBottomUpDataset."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
raise (ImportError(
|
||||
'BottomUpBaseDataset has been replaced by '
|
||||
'Kpt2dSviewRgbImgBottomUpDataset,'
|
||||
'check https://github.com/open-mmlab/mmpose/pull/663 for details.')
|
||||
)
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
from xtcocotools.cocoeval import COCOeval
|
||||
|
||||
from mmpose.core.post_processing import oks_nms, soft_oks_nms
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from mmpose.datasets.datasets.base import Kpt2dSviewRgbImgBottomUpDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class BottomUpCocoDataset(Kpt2dSviewRgbImgBottomUpDataset):
|
||||
"""COCO dataset for bottom-up pose estimation.
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
COCO keypoint indexes::
|
||||
|
||||
0: 'nose',
|
||||
1: 'left_eye',
|
||||
2: 'right_eye',
|
||||
3: 'left_ear',
|
||||
4: 'right_ear',
|
||||
5: 'left_shoulder',
|
||||
6: 'right_shoulder',
|
||||
7: 'left_elbow',
|
||||
8: 'right_elbow',
|
||||
9: 'left_wrist',
|
||||
10: 'right_wrist',
|
||||
11: 'left_hip',
|
||||
12: 'right_hip',
|
||||
13: 'left_knee',
|
||||
14: 'right_knee',
|
||||
15: 'left_ankle',
|
||||
16: 'right_ankle'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/coco.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
|
||||
def _get_single(self, idx):
|
||||
"""Get anno for a single image.
|
||||
|
||||
Args:
|
||||
idx (int): image idx
|
||||
|
||||
Returns:
|
||||
dict: info for model training
|
||||
"""
|
||||
coco = self.coco
|
||||
img_id = self.img_ids[idx]
|
||||
ann_ids = coco.getAnnIds(imgIds=img_id)
|
||||
anno = coco.loadAnns(ann_ids)
|
||||
|
||||
mask = self._get_mask(anno, idx)
|
||||
anno = [
|
||||
obj.copy() for obj in anno
|
||||
if obj['iscrowd'] == 0 or obj['num_keypoints'] > 0
|
||||
]
|
||||
|
||||
joints = self._get_joints(anno)
|
||||
mask_list = [mask.copy() for _ in range(self.ann_info['num_scales'])]
|
||||
joints_list = [
|
||||
joints.copy() for _ in range(self.ann_info['num_scales'])
|
||||
]
|
||||
|
||||
db_rec = {}
|
||||
db_rec['dataset'] = self.dataset_name
|
||||
db_rec['image_file'] = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
db_rec['mask'] = mask_list
|
||||
db_rec['joints'] = joints_list
|
||||
|
||||
return db_rec
|
||||
|
||||
def _get_joints(self, anno):
|
||||
"""Get joints for all people in an image."""
|
||||
num_people = len(anno)
|
||||
|
||||
if self.ann_info['scale_aware_sigma']:
|
||||
joints = np.zeros((num_people, self.ann_info['num_joints'], 4),
|
||||
dtype=np.float32)
|
||||
else:
|
||||
joints = np.zeros((num_people, self.ann_info['num_joints'], 3),
|
||||
dtype=np.float32)
|
||||
|
||||
for i, obj in enumerate(anno):
|
||||
joints[i, :, :3] = \
|
||||
np.array(obj['keypoints']).reshape([-1, 3])
|
||||
if self.ann_info['scale_aware_sigma']:
|
||||
# get person box
|
||||
box = obj['bbox']
|
||||
size = max(box[2], box[3])
|
||||
sigma = size / self.base_size * self.base_sigma
|
||||
if self.int_sigma:
|
||||
sigma = int(np.ceil(sigma))
|
||||
assert sigma > 0, sigma
|
||||
joints[i, :, 3] = sigma
|
||||
|
||||
return joints
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='mAP', **kwargs):
|
||||
"""Evaluate coco keypoint results. The pose prediction results will be
|
||||
saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- num_people: P
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (list[np.ndarray(P, K, 3+tag_num)]): \
|
||||
Pose predictions for all people in images.
|
||||
- scores (list[P]): List of person scores.
|
||||
- image_path (list[str]): For example, ['coco/images/\
|
||||
val2017/000000397133.jpg']
|
||||
- heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed. Defaults: 'mAP'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['mAP']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
preds = []
|
||||
scores = []
|
||||
image_paths = []
|
||||
|
||||
for result in results:
|
||||
preds.append(result['preds'])
|
||||
scores.append(result['scores'])
|
||||
image_paths.append(result['image_paths'][0])
|
||||
|
||||
kpts = defaultdict(list)
|
||||
# iterate over images
|
||||
for idx, _preds in enumerate(preds):
|
||||
str_image_path = image_paths[idx]
|
||||
image_id = self.name2id[osp.basename(str_image_path)]
|
||||
# iterate over people
|
||||
for idx_person, kpt in enumerate(_preds):
|
||||
# use bbox area
|
||||
area = (np.max(kpt[:, 0]) - np.min(kpt[:, 0])) * (
|
||||
np.max(kpt[:, 1]) - np.min(kpt[:, 1]))
|
||||
|
||||
kpts[image_id].append({
|
||||
'keypoints': kpt[:, 0:3],
|
||||
'score': scores[idx][idx_person],
|
||||
'tags': kpt[:, 3],
|
||||
'image_id': image_id,
|
||||
'area': area,
|
||||
})
|
||||
|
||||
valid_kpts = []
|
||||
for img in kpts.keys():
|
||||
img_kpts = kpts[img]
|
||||
if self.use_nms:
|
||||
nms = soft_oks_nms if self.soft_nms else oks_nms
|
||||
keep = nms(img_kpts, self.oks_thr, sigmas=self.sigmas)
|
||||
valid_kpts.append([img_kpts[_keep] for _keep in keep])
|
||||
else:
|
||||
valid_kpts.append(img_kpts)
|
||||
|
||||
self._write_coco_keypoint_results(valid_kpts, res_file)
|
||||
|
||||
info_str = self._do_python_keypoint_eval(res_file)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
|
||||
def _write_coco_keypoint_results(self, keypoints, res_file):
|
||||
"""Write results into a json file."""
|
||||
data_pack = [{
|
||||
'cat_id': self._class_to_coco_ind[cls],
|
||||
'cls_ind': cls_ind,
|
||||
'cls': cls,
|
||||
'ann_type': 'keypoints',
|
||||
'keypoints': keypoints
|
||||
} for cls_ind, cls in enumerate(self.classes)
|
||||
if not cls == '__background__']
|
||||
|
||||
results = self._coco_keypoint_results_one_category_kernel(data_pack[0])
|
||||
|
||||
with open(res_file, 'w') as f:
|
||||
json.dump(results, f, sort_keys=True, indent=4)
|
||||
|
||||
def _coco_keypoint_results_one_category_kernel(self, data_pack):
|
||||
"""Get coco keypoint results."""
|
||||
cat_id = data_pack['cat_id']
|
||||
keypoints = data_pack['keypoints']
|
||||
cat_results = []
|
||||
|
||||
for img_kpts in keypoints:
|
||||
if len(img_kpts) == 0:
|
||||
continue
|
||||
|
||||
_key_points = np.array(
|
||||
[img_kpt['keypoints'] for img_kpt in img_kpts])
|
||||
key_points = _key_points.reshape(-1,
|
||||
self.ann_info['num_joints'] * 3)
|
||||
|
||||
for img_kpt, key_point in zip(img_kpts, key_points):
|
||||
kpt = key_point.reshape((self.ann_info['num_joints'], 3))
|
||||
left_top = np.amin(kpt, axis=0)
|
||||
right_bottom = np.amax(kpt, axis=0)
|
||||
|
||||
w = right_bottom[0] - left_top[0]
|
||||
h = right_bottom[1] - left_top[1]
|
||||
|
||||
cat_results.append({
|
||||
'image_id': img_kpt['image_id'],
|
||||
'category_id': cat_id,
|
||||
'keypoints': key_point.tolist(),
|
||||
'score': img_kpt['score'],
|
||||
'bbox': [left_top[0], left_top[1], w, h]
|
||||
})
|
||||
|
||||
return cat_results
|
||||
|
||||
def _do_python_keypoint_eval(self, res_file):
|
||||
"""Keypoint evaluation using COCOAPI."""
|
||||
|
||||
stats_names = [
|
||||
'AP', 'AP .5', 'AP .75', 'AP (M)', 'AP (L)', 'AR', 'AR .5',
|
||||
'AR .75', 'AR (M)', 'AR (L)'
|
||||
]
|
||||
|
||||
with open(res_file, 'r') as file:
|
||||
res_json = json.load(file)
|
||||
if not res_json:
|
||||
info_str = list(zip(stats_names, [
|
||||
0,
|
||||
] * len(stats_names)))
|
||||
return info_str
|
||||
|
||||
coco_det = self.coco.loadRes(res_file)
|
||||
coco_eval = COCOeval(self.coco, coco_det, 'keypoints', self.sigmas)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
info_str = list(zip(stats_names, coco_eval.stats))
|
||||
|
||||
return info_str
|
||||
third_party/hamer/third-party/ViTPose/mmpose/datasets/datasets/bottom_up/bottom_up_coco_wholebody.py
Vendored
+238
@@ -0,0 +1,238 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config
|
||||
from xtcocotools.cocoeval import COCOeval
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from .bottom_up_coco import BottomUpCocoDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class BottomUpCocoWholeBodyDataset(BottomUpCocoDataset):
|
||||
"""CocoWholeBodyDataset dataset for bottom-up pose estimation.
|
||||
|
||||
`Whole-Body Human Pose Estimation in the Wild', ECCV'2020.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/2007.11858>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
In total, we have 133 keypoints for wholebody pose estimation.
|
||||
|
||||
COCO-WholeBody keypoint indexes::
|
||||
|
||||
0-16: 17 body keypoints,
|
||||
17-22: 6 foot keypoints,
|
||||
23-90: 68 face keypoints,
|
||||
91-132: 42 hand keypoints
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/coco_wholebody.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super(BottomUpCocoDataset, self).__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
|
||||
self.body_num = 17
|
||||
self.foot_num = 6
|
||||
self.face_num = 68
|
||||
self.left_hand_num = 21
|
||||
self.right_hand_num = 21
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
|
||||
def _get_joints(self, anno):
|
||||
"""Get joints for all people in an image."""
|
||||
num_people = len(anno)
|
||||
|
||||
if self.ann_info['scale_aware_sigma']:
|
||||
joints = np.zeros((num_people, self.ann_info['num_joints'], 4),
|
||||
dtype=np.float32)
|
||||
else:
|
||||
joints = np.zeros((num_people, self.ann_info['num_joints'], 3),
|
||||
dtype=np.float32)
|
||||
|
||||
for i, obj in enumerate(anno):
|
||||
keypoints = np.array(obj['keypoints'] + obj['foot_kpts'] +
|
||||
obj['face_kpts'] + obj['lefthand_kpts'] +
|
||||
obj['righthand_kpts']).reshape(-1, 3)
|
||||
|
||||
joints[i, :self.ann_info['num_joints'], :3] = keypoints
|
||||
if self.ann_info['scale_aware_sigma']:
|
||||
# get person box
|
||||
box = obj['bbox']
|
||||
size = max(box[2], box[3])
|
||||
sigma = size / self.base_size * self.base_sigma
|
||||
if self.int_sigma:
|
||||
sigma = int(np.ceil(sigma))
|
||||
assert sigma > 0, sigma
|
||||
joints[i, :, 3] = sigma
|
||||
|
||||
return joints
|
||||
|
||||
def _coco_keypoint_results_one_category_kernel(self, data_pack):
|
||||
"""Get coco keypoint results."""
|
||||
cat_id = data_pack['cat_id']
|
||||
keypoints = data_pack['keypoints']
|
||||
cat_results = []
|
||||
|
||||
for img_kpts in keypoints:
|
||||
if len(img_kpts) == 0:
|
||||
continue
|
||||
|
||||
_key_points = np.array(
|
||||
[img_kpt['keypoints'] for img_kpt in img_kpts])
|
||||
key_points = _key_points.reshape(-1,
|
||||
self.ann_info['num_joints'] * 3)
|
||||
|
||||
cuts = np.cumsum([
|
||||
0, self.body_num, self.foot_num, self.face_num,
|
||||
self.left_hand_num, self.right_hand_num
|
||||
]) * 3
|
||||
|
||||
for img_kpt, key_point in zip(img_kpts, key_points):
|
||||
kpt = key_point.reshape((self.ann_info['num_joints'], 3))
|
||||
left_top = np.amin(kpt, axis=0)
|
||||
right_bottom = np.amax(kpt, axis=0)
|
||||
|
||||
w = right_bottom[0] - left_top[0]
|
||||
h = right_bottom[1] - left_top[1]
|
||||
|
||||
cat_results.append({
|
||||
'image_id':
|
||||
img_kpt['image_id'],
|
||||
'category_id':
|
||||
cat_id,
|
||||
'keypoints':
|
||||
key_point[cuts[0]:cuts[1]].tolist(),
|
||||
'foot_kpts':
|
||||
key_point[cuts[1]:cuts[2]].tolist(),
|
||||
'face_kpts':
|
||||
key_point[cuts[2]:cuts[3]].tolist(),
|
||||
'lefthand_kpts':
|
||||
key_point[cuts[3]:cuts[4]].tolist(),
|
||||
'righthand_kpts':
|
||||
key_point[cuts[4]:cuts[5]].tolist(),
|
||||
'score':
|
||||
img_kpt['score'],
|
||||
'bbox': [left_top[0], left_top[1], w, h]
|
||||
})
|
||||
|
||||
return cat_results
|
||||
|
||||
def _do_python_keypoint_eval(self, res_file):
|
||||
"""Keypoint evaluation using COCOAPI."""
|
||||
coco_det = self.coco.loadRes(res_file)
|
||||
|
||||
cuts = np.cumsum([
|
||||
0, self.body_num, self.foot_num, self.face_num, self.left_hand_num,
|
||||
self.right_hand_num
|
||||
])
|
||||
|
||||
coco_eval = COCOeval(
|
||||
self.coco,
|
||||
coco_det,
|
||||
'keypoints_body',
|
||||
self.sigmas[cuts[0]:cuts[1]],
|
||||
use_area=True)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
coco_eval = COCOeval(
|
||||
self.coco,
|
||||
coco_det,
|
||||
'keypoints_foot',
|
||||
self.sigmas[cuts[1]:cuts[2]],
|
||||
use_area=True)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
coco_eval = COCOeval(
|
||||
self.coco,
|
||||
coco_det,
|
||||
'keypoints_face',
|
||||
self.sigmas[cuts[2]:cuts[3]],
|
||||
use_area=True)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
coco_eval = COCOeval(
|
||||
self.coco,
|
||||
coco_det,
|
||||
'keypoints_lefthand',
|
||||
self.sigmas[cuts[3]:cuts[4]],
|
||||
use_area=True)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
coco_eval = COCOeval(
|
||||
self.coco,
|
||||
coco_det,
|
||||
'keypoints_righthand',
|
||||
self.sigmas[cuts[4]:cuts[5]],
|
||||
use_area=True)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
coco_eval = COCOeval(
|
||||
self.coco,
|
||||
coco_det,
|
||||
'keypoints_wholebody',
|
||||
self.sigmas,
|
||||
use_area=True)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
stats_names = [
|
||||
'AP', 'AP .5', 'AP .75', 'AP (M)', 'AP (L)', 'AR', 'AR .5',
|
||||
'AR .75', 'AR (M)', 'AR (L)'
|
||||
]
|
||||
|
||||
info_str = list(zip(stats_names, coco_eval.stats))
|
||||
|
||||
return info_str
|
||||
Vendored
+109
@@ -0,0 +1,109 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
import json_tricks as json
|
||||
from mmcv import Config
|
||||
from xtcocotools.cocoeval import COCOeval
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from .bottom_up_coco import BottomUpCocoDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class BottomUpCrowdPoseDataset(BottomUpCocoDataset):
|
||||
"""CrowdPose dataset for bottom-up pose estimation.
|
||||
|
||||
"CrowdPose: Efficient Crowded Scenes Pose Estimation and
|
||||
A New Benchmark", CVPR'2019.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1812.00324>`__.
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
CrowdPose keypoint indexes::
|
||||
|
||||
0: 'left_shoulder',
|
||||
1: 'right_shoulder',
|
||||
2: 'left_elbow',
|
||||
3: 'right_elbow',
|
||||
4: 'left_wrist',
|
||||
5: 'right_wrist',
|
||||
6: 'left_hip',
|
||||
7: 'right_hip',
|
||||
8: 'left_knee',
|
||||
9: 'right_knee',
|
||||
10: 'left_ankle',
|
||||
11: 'right_ankle',
|
||||
12: 'top_head',
|
||||
13: 'neck'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/crowdpose.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super(BottomUpCocoDataset, self).__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
|
||||
def _do_python_keypoint_eval(self, res_file):
|
||||
"""Keypoint evaluation using COCOAPI."""
|
||||
|
||||
stats_names = [
|
||||
'AP', 'AP .5', 'AP .75', 'AR', 'AR .5', 'AR .75', 'AP(E)', 'AP(M)',
|
||||
'AP(H)'
|
||||
]
|
||||
|
||||
with open(res_file, 'r') as file:
|
||||
res_json = json.load(file)
|
||||
if not res_json:
|
||||
info_str = list(zip(stats_names, [
|
||||
0,
|
||||
] * len(stats_names)))
|
||||
return info_str
|
||||
|
||||
coco_det = self.coco.loadRes(res_file)
|
||||
coco_eval = COCOeval(
|
||||
self.coco,
|
||||
coco_det,
|
||||
'keypoints_crowd',
|
||||
self.sigmas,
|
||||
use_area=False)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
info_str = list(zip(stats_names, coco_eval.stats))
|
||||
|
||||
return info_str
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
import json_tricks as json
|
||||
from mmcv import Config
|
||||
from xtcocotools.cocoeval import COCOeval
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from .bottom_up_coco import BottomUpCocoDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class BottomUpMhpDataset(BottomUpCocoDataset):
|
||||
"""MHPv2.0 dataset for top-down pose estimation.
|
||||
|
||||
"Understanding Humans in Crowded Scenes: Deep Nested Adversarial
|
||||
Learning and A New Benchmark for Multi-Human Parsing", ACM MM'2018.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1804.03287>`__
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
MHP keypoint indexes::
|
||||
|
||||
0: "right ankle",
|
||||
1: "right knee",
|
||||
2: "right hip",
|
||||
3: "left hip",
|
||||
4: "left knee",
|
||||
5: "left ankle",
|
||||
6: "pelvis",
|
||||
7: "thorax",
|
||||
8: "upper neck",
|
||||
9: "head top",
|
||||
10: "right wrist",
|
||||
11: "right elbow",
|
||||
12: "right shoulder",
|
||||
13: "left shoulder",
|
||||
14: "left elbow",
|
||||
15: "left wrist",
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/mhp.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super(BottomUpCocoDataset, self).__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
|
||||
def _do_python_keypoint_eval(self, res_file):
|
||||
"""Keypoint evaluation using COCOAPI."""
|
||||
|
||||
stats_names = [
|
||||
'AP', 'AP .5', 'AP .75', 'AP (M)', 'AP (L)', 'AR', 'AR .5',
|
||||
'AR .75', 'AR (M)', 'AR (L)'
|
||||
]
|
||||
|
||||
with open(res_file, 'r') as file:
|
||||
res_json = json.load(file)
|
||||
if not res_json:
|
||||
info_str = list(zip(stats_names, [
|
||||
0,
|
||||
] * len(stats_names)))
|
||||
return info_str
|
||||
|
||||
coco_det = self.coco.loadRes(res_file)
|
||||
|
||||
coco_eval = COCOeval(
|
||||
self.coco, coco_det, 'keypoints', self.sigmas, use_area=False)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
info_str = list(zip(stats_names, coco_eval.stats))
|
||||
|
||||
return info_str
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .face_300w_dataset import Face300WDataset
|
||||
from .face_aflw_dataset import FaceAFLWDataset
|
||||
from .face_coco_wholebody_dataset import FaceCocoWholeBodyDataset
|
||||
from .face_cofw_dataset import FaceCOFWDataset
|
||||
from .face_wflw_dataset import FaceWFLWDataset
|
||||
|
||||
__all__ = [
|
||||
'Face300WDataset', 'FaceAFLWDataset', 'FaceWFLWDataset', 'FaceCOFWDataset',
|
||||
'FaceCocoWholeBodyDataset'
|
||||
]
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class Face300WDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""Face300W dataset for top-down face keypoint localization.
|
||||
|
||||
"300 faces In-the-wild challenge: Database and results",
|
||||
Image and Vision Computing (IMAVIS) 2019.
|
||||
|
||||
The dataset loads raw images and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
The landmark annotations follow the 68 points mark-up. The definition
|
||||
can be found in `https://ibug.doc.ic.ac.uk/resources/300-W/`.
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/300w.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
num_joints = self.ann_info['num_joints']
|
||||
for img_id in self.img_ids:
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
for obj in objs:
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
if 'center' in obj and 'scale' in obj:
|
||||
center = np.array(obj['center'])
|
||||
scale = np.array([obj['scale'], obj['scale']]) * 1.25
|
||||
else:
|
||||
center, scale = self._xywh2cs(*obj['bbox'][:4], 1.25)
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': obj['bbox'],
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
def _get_normalize_factor(self, gts, *args, **kwargs):
|
||||
"""Get inter-ocular distance as the normalize factor, measured as the
|
||||
Euclidean distance between the outer corners of the eyes.
|
||||
|
||||
Args:
|
||||
gts (np.ndarray[N, K, 2]): Groundtruth keypoint location.
|
||||
|
||||
Returns:
|
||||
np.ndarray[N, 2]: normalized factor
|
||||
"""
|
||||
|
||||
interocular = np.linalg.norm(
|
||||
gts[:, 36, :] - gts[:, 45, :], axis=1, keepdims=True)
|
||||
return np.tile(interocular, [1, 2])
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='NME', **kwargs):
|
||||
"""Evaluate freihand keypoint results. The pose prediction results will
|
||||
be saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[1,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[1,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_path (list[str]): For example, ['300W/ibug/\
|
||||
image_018.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'NME'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['NME']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class FaceAFLWDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""Face AFLW dataset for top-down face keypoint localization.
|
||||
|
||||
"Annotated Facial Landmarks in the Wild: A Large-scale,
|
||||
Real-world Database for Facial Landmark Localization".
|
||||
In Proc. First IEEE International Workshop on Benchmarking
|
||||
Facial Image Analysis Technologies, 2011.
|
||||
|
||||
The dataset loads raw images and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
The landmark annotations follow the 19 points mark-up. The definition
|
||||
can be found in `https://www.tugraz.at/institute/icg/research`
|
||||
`/team-bischof/lrs/downloads/aflw/`
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/aflw.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
num_joints = self.ann_info['num_joints']
|
||||
for img_id in self.img_ids:
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
for obj in objs:
|
||||
if self.test_mode:
|
||||
# 'box_size' is used as normalization factor
|
||||
assert 'box_size' in obj
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
if 'center' in obj and 'scale' in obj:
|
||||
center = np.array(obj['center'])
|
||||
scale = np.array([obj['scale'], obj['scale']]) * 1.25
|
||||
else:
|
||||
center, scale = self._xywh2cs(*obj['bbox'][:4], 1.25)
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': obj['bbox'],
|
||||
'box_size': obj['box_size'],
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
def _get_normalize_factor(self, box_sizes, *args, **kwargs):
|
||||
"""Get normalize factor for evaluation.
|
||||
|
||||
Args:
|
||||
box_sizes (np.ndarray[N, 1]): box size
|
||||
|
||||
Returns:
|
||||
np.ndarray[N, 2]: normalized factor
|
||||
"""
|
||||
|
||||
return np.tile(box_sizes, [1, 2])
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='NME', **kwargs):
|
||||
"""Evaluate freihand keypoint results. The pose prediction results will
|
||||
be saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[1,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[1,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_path (list[str]): For example, ['aflw/images/flickr/ \
|
||||
0/image00002.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'NME'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['NME']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from abc import ABCMeta
|
||||
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
|
||||
class FaceBaseDataset(Dataset, metaclass=ABCMeta):
|
||||
"""This class has been deprecated and replaced by
|
||||
Kpt2dSviewRgbImgTopDownDataset."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
raise (ImportError(
|
||||
'FaceBaseDataset has been replaced by '
|
||||
'Kpt2dSviewRgbImgTopDownDataset,'
|
||||
'check https://github.com/open-mmlab/mmpose/pull/663 for details.')
|
||||
)
|
||||
Vendored
+198
@@ -0,0 +1,198 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class FaceCocoWholeBodyDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""CocoWholeBodyDataset for face keypoint localization.
|
||||
|
||||
`Whole-Body Human Pose Estimation in the Wild', ECCV'2020.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/2007.11858>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
The face landmark annotations follow the 68 points mark-up.
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/'
|
||||
'coco_wholebody_face.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
num_joints = self.ann_info['num_joints']
|
||||
for img_id in self.img_ids:
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
for obj in objs:
|
||||
if obj['face_valid'] and max(obj['face_kpts']) > 0:
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3),
|
||||
dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['face_kpts']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
center, scale = self._xywh2cs(*obj['face_box'][:4], 1.25)
|
||||
|
||||
image_file = osp.join(self.img_prefix,
|
||||
self.id2name[img_id])
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': obj['face_box'],
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
def _get_normalize_factor(self, gts, *args, **kwargs):
|
||||
"""Get inter-ocular distance as the normalize factor, measured as the
|
||||
Euclidean distance between the outer corners of the eyes.
|
||||
|
||||
Args:
|
||||
gts (np.ndarray[N, K, 2]): Groundtruth keypoint location.
|
||||
|
||||
Returns:
|
||||
np.ndarray[N, 2]: normalized factor
|
||||
"""
|
||||
|
||||
interocular = np.linalg.norm(
|
||||
gts[:, 36, :] - gts[:, 45, :], axis=1, keepdims=True)
|
||||
return np.tile(interocular, [1, 2])
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='NME', **kwargs):
|
||||
"""Evaluate COCO-WholeBody Face keypoint results. The pose prediction
|
||||
results will be saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[1,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[1,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_path (list[str]): For example, ['coco/train2017/\
|
||||
000000000009.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'NME'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['NME']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class FaceCOFWDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""Face COFW dataset for top-down face keypoint localization.
|
||||
|
||||
"Robust face landmark estimation under occlusion", ICCV'2013.
|
||||
|
||||
The dataset loads raw images and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
The landmark annotations follow the 29 points mark-up. The definition
|
||||
can be found in `http://www.vision.caltech.edu/xpburgos/ICCV13/`.
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/cofw.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
num_joints = self.ann_info['num_joints']
|
||||
for img_id in self.img_ids:
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
for obj in objs:
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
if 'center' in obj and 'scale' in obj:
|
||||
center = np.array(obj['center'])
|
||||
scale = np.array([obj['scale'], obj['scale']]) * 1.25
|
||||
else:
|
||||
center, scale = self._xywh2cs(*obj['bbox'][:4], 1.25)
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': obj['bbox'],
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
def _get_normalize_factor(self, gts, *args, **kwargs):
|
||||
"""Get normalize factor for evaluation.
|
||||
|
||||
Args:
|
||||
gts (np.ndarray[N, K, 2]): Groundtruth keypoint location.
|
||||
|
||||
Returns:
|
||||
np.ndarray[N, 2]: normalized factor
|
||||
"""
|
||||
|
||||
interocular = np.linalg.norm(
|
||||
gts[:, 8, :] - gts[:, 9, :], axis=1, keepdims=True)
|
||||
return np.tile(interocular, [1, 2])
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='NME', **kwargs):
|
||||
"""Evaluate freihand keypoint results. The pose prediction results will
|
||||
be saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[1,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[1,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_path (list[str]): For example, ['cofw/images/\
|
||||
000001.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'NME'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['NME']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class FaceWFLWDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""Face WFLW dataset for top-down face keypoint localization.
|
||||
|
||||
"Look at Boundary: A Boundary-Aware Face Alignment Algorithm",
|
||||
CVPR'2018.
|
||||
|
||||
The dataset loads raw images and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
The landmark annotations follow the 98 points mark-up. The definition
|
||||
can be found in `https://wywu.github.io/projects/LAB/WFLW.html`.
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/wflw.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
num_joints = self.ann_info['num_joints']
|
||||
for img_id in self.img_ids:
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
for obj in objs:
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
if 'center' in obj and 'scale' in obj:
|
||||
center = np.array(obj['center'])
|
||||
scale = np.array([obj['scale'], obj['scale']]) * 1.25
|
||||
else:
|
||||
center, scale = self._xywh2cs(*obj['bbox'][:4], 1.25)
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': obj['bbox'],
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
def _get_normalize_factor(self, gts, *args, **kwargs):
|
||||
"""Get normalize factor for evaluation.
|
||||
|
||||
Args:
|
||||
gts (np.ndarray[N, K, 2]): Groundtruth keypoint location.
|
||||
|
||||
Returns:
|
||||
np.ndarray[N, 2]: normalized factor
|
||||
"""
|
||||
|
||||
interocular = np.linalg.norm(
|
||||
gts[:, 60, :] - gts[:, 72, :], axis=1, keepdims=True)
|
||||
return np.tile(interocular, [1, 2])
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='NME', **kwargs):
|
||||
"""Evaluate freihand keypoint results. The pose prediction results will
|
||||
be saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[1,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[1,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_path (list[str]): For example, ['wflw/images/\
|
||||
0--Parade/0_Parade_marchingband_1_1015.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'NME'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['NME']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .deepfashion_dataset import DeepFashionDataset
|
||||
|
||||
__all__ = ['DeepFashionDataset']
|
||||
Vendored
+225
@@ -0,0 +1,225 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class DeepFashionDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""DeepFashion dataset (full-body clothes) for fashion landmark detection.
|
||||
|
||||
"DeepFashion: Powering Robust Clothes Recognition
|
||||
and Retrieval with Rich Annotations", CVPR'2016.
|
||||
"Fashion Landmark Detection in the Wild", ECCV'2016.
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
The dataset contains 3 categories for full-body, upper-body and lower-body.
|
||||
|
||||
Fashion landmark indexes for upper-body clothes::
|
||||
|
||||
0: 'left collar',
|
||||
1: 'right collar',
|
||||
2: 'left sleeve',
|
||||
3: 'right sleeve',
|
||||
4: 'left hem',
|
||||
5: 'right hem'
|
||||
|
||||
Fashion landmark indexes for lower-body clothes::
|
||||
|
||||
0: 'left waistline',
|
||||
1: 'right waistline',
|
||||
2: 'left hem',
|
||||
3: 'right hem'
|
||||
|
||||
Fashion landmark indexes for full-body clothes::
|
||||
|
||||
0: 'left collar',
|
||||
1: 'right collar',
|
||||
2: 'left sleeve',
|
||||
3: 'right sleeve',
|
||||
4: 'left waistline',
|
||||
5: 'right waistline',
|
||||
6: 'left hem',
|
||||
7: 'right hem'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
subset='',
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
if subset != '':
|
||||
warnings.warn(
|
||||
'subset is deprecated.'
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
if subset == 'upper':
|
||||
cfg = Config.fromfile(
|
||||
'configs/_base_/datasets/deepfashion_upper.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
elif subset == 'lower':
|
||||
cfg = Config.fromfile(
|
||||
'configs/_base_/datasets/deepfashion_lower.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
elif subset == 'full':
|
||||
cfg = Config.fromfile(
|
||||
'configs/_base_/datasets/deepfashion_full.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
num_joints = self.ann_info['num_joints']
|
||||
for img_id in self.img_ids:
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
for obj in objs:
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
# use 1.25bbox as input
|
||||
center, scale = self._xywh2cs(*obj['bbox'][:4], 1.25)
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': obj['bbox'],
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='PCK', **kwargs):
|
||||
"""Evaluate freihand keypoint results. The pose prediction results will
|
||||
be saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['img_00000001.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'PCK', 'AUC', 'EPE'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['PCK', 'AUC', 'EPE']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from abc import ABCMeta
|
||||
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
|
||||
class FashionBaseDataset(Dataset, metaclass=ABCMeta):
|
||||
"""This class has been deprecated and replaced by
|
||||
Kpt2dSviewRgbImgTopDownDataset."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
raise (ImportError(
|
||||
'FashionBaseDataset has been replaced by '
|
||||
'Kpt2dSviewRgbImgTopDownDataset,'
|
||||
'check https://github.com/open-mmlab/mmpose/pull/663 for details.')
|
||||
)
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .freihand_dataset import FreiHandDataset
|
||||
from .hand_coco_wholebody_dataset import HandCocoWholeBodyDataset
|
||||
from .interhand2d_dataset import InterHand2DDataset
|
||||
from .interhand3d_dataset import InterHand3DDataset
|
||||
from .onehand10k_dataset import OneHand10KDataset
|
||||
from .panoptic_hand2d_dataset import PanopticDataset
|
||||
from .rhd2d_dataset import Rhd2DDataset
|
||||
|
||||
__all__ = [
|
||||
'FreiHandDataset', 'InterHand2DDataset', 'InterHand3DDataset',
|
||||
'OneHand10KDataset', 'PanopticDataset', 'Rhd2DDataset',
|
||||
'HandCocoWholeBodyDataset'
|
||||
]
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class FreiHandDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""FreiHand dataset for top-down hand pose estimation.
|
||||
|
||||
"FreiHAND: A Dataset for Markerless Capture of Hand Pose
|
||||
and Shape from Single RGB Images", ICCV'2019.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/pdf/1909.04349.pdf>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
FreiHand keypoint indexes::
|
||||
|
||||
0: 'wrist',
|
||||
1: 'thumb1',
|
||||
2: 'thumb2',
|
||||
3: 'thumb3',
|
||||
4: 'thumb4',
|
||||
5: 'forefinger1',
|
||||
6: 'forefinger2',
|
||||
7: 'forefinger3',
|
||||
8: 'forefinger4',
|
||||
9: 'middle_finger1',
|
||||
10: 'middle_finger2',
|
||||
11: 'middle_finger3',
|
||||
12: 'middle_finger4',
|
||||
13: 'ring_finger1',
|
||||
14: 'ring_finger2',
|
||||
15: 'ring_finger3',
|
||||
16: 'ring_finger4',
|
||||
17: 'pinky_finger1',
|
||||
18: 'pinky_finger2',
|
||||
19: 'pinky_finger3',
|
||||
20: 'pinky_finger4'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/freihand2d.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
num_joints = self.ann_info['num_joints']
|
||||
for img_id in self.img_ids:
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
for obj in objs:
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
# the ori image is 224x224
|
||||
center, scale = self._xywh2cs(0, 0, 224, 224, 0.8)
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': obj['bbox'],
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='PCK', **kwargs):
|
||||
"""Evaluate freihand keypoint results. The pose prediction results will
|
||||
be saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['training/rgb/\
|
||||
00031426.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'PCK', 'AUC', 'EPE'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['PCK', 'AUC', 'EPE']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from abc import ABCMeta
|
||||
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
|
||||
class HandBaseDataset(Dataset, metaclass=ABCMeta):
|
||||
"""This class has been deprecated and replaced by
|
||||
Kpt2dSviewRgbImgTopDownDataset."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
raise (ImportError(
|
||||
'HandBaseDataset has been replaced by '
|
||||
'Kpt2dSviewRgbImgTopDownDataset,'
|
||||
'check https://github.com/open-mmlab/mmpose/pull/663 for details.')
|
||||
)
|
||||
Vendored
+211
@@ -0,0 +1,211 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class HandCocoWholeBodyDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""CocoWholeBodyDataset for top-down hand pose estimation.
|
||||
|
||||
"Whole-Body Human Pose Estimation in the Wild", ECCV'2020.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/2007.11858>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
COCO-WholeBody Hand keypoint indexes::
|
||||
|
||||
0: 'wrist',
|
||||
1: 'thumb1',
|
||||
2: 'thumb2',
|
||||
3: 'thumb3',
|
||||
4: 'thumb4',
|
||||
5: 'forefinger1',
|
||||
6: 'forefinger2',
|
||||
7: 'forefinger3',
|
||||
8: 'forefinger4',
|
||||
9: 'middle_finger1',
|
||||
10: 'middle_finger2',
|
||||
11: 'middle_finger3',
|
||||
12: 'middle_finger4',
|
||||
13: 'ring_finger1',
|
||||
14: 'ring_finger2',
|
||||
15: 'ring_finger3',
|
||||
16: 'ring_finger4',
|
||||
17: 'pinky_finger1',
|
||||
18: 'pinky_finger2',
|
||||
19: 'pinky_finger3',
|
||||
20: 'pinky_finger4'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile(
|
||||
'configs/_base_/datasets/coco_wholebody_hand.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
num_joints = self.ann_info['num_joints']
|
||||
for img_id in self.img_ids:
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
for obj in objs:
|
||||
for type in ['left', 'right']:
|
||||
if obj[f'{type}hand_valid'] and max(
|
||||
obj[f'{type}hand_kpts']) > 0:
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3),
|
||||
dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj[f'{type}hand_kpts']).reshape(
|
||||
-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(
|
||||
1, keypoints[:, 2:3])
|
||||
|
||||
# use 1.25 padded bbox as input
|
||||
center, scale = self._xywh2cs(
|
||||
*obj[f'{type}hand_box'][:4], 1.25)
|
||||
|
||||
image_file = osp.join(self.img_prefix,
|
||||
self.id2name[img_id])
|
||||
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': obj[f'{type}hand_box'],
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='PCK', **kwargs):
|
||||
"""Evaluate COCO-WholeBody Hand keypoint results. The pose prediction
|
||||
results will be saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['Test/source/0.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'PCK', 'AUC', 'EPE'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['PCK', 'AUC', 'EPE']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class InterHand2DDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""InterHand2.6M 2D dataset for top-down hand pose estimation.
|
||||
|
||||
"InterHand2.6M: A Dataset and Baseline for 3D Interacting Hand Pose
|
||||
Estimation from a Single RGB Image", ECCV'2020.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/pdf/2008.09309.pdf>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
InterHand2.6M keypoint indexes::
|
||||
|
||||
0: 'thumb4',
|
||||
1: 'thumb3',
|
||||
2: 'thumb2',
|
||||
3: 'thumb1',
|
||||
4: 'forefinger4',
|
||||
5: 'forefinger3',
|
||||
6: 'forefinger2',
|
||||
7: 'forefinger1',
|
||||
8: 'middle_finger4',
|
||||
9: 'middle_finger3',
|
||||
10: 'middle_finger2',
|
||||
11: 'middle_finger1',
|
||||
12: 'ring_finger4',
|
||||
13: 'ring_finger3',
|
||||
14: 'ring_finger2',
|
||||
15: 'ring_finger1',
|
||||
16: 'pinky_finger4',
|
||||
17: 'pinky_finger3',
|
||||
18: 'pinky_finger2',
|
||||
19: 'pinky_finger1',
|
||||
20: 'wrist'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
camera_file (str): Path to the camera file.
|
||||
joint_file (str): Path to the joint file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (str): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
camera_file,
|
||||
joint_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/interhand2d.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.camera_file = camera_file
|
||||
self.joint_file = joint_file
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
@staticmethod
|
||||
def _cam2pixel(cam_coord, f, c):
|
||||
"""Transform the joints from their camera coordinates to their pixel
|
||||
coordinates.
|
||||
|
||||
Note:
|
||||
- N: number of joints
|
||||
|
||||
Args:
|
||||
cam_coord (ndarray[N, 3]): 3D joints coordinates
|
||||
in the camera coordinate system
|
||||
f (ndarray[2]): focal length of x and y axis
|
||||
c (ndarray[2]): principal point of x and y axis
|
||||
|
||||
Returns:
|
||||
img_coord (ndarray[N, 3]): the coordinates (x, y, 0)
|
||||
in the image plane.
|
||||
"""
|
||||
x = cam_coord[:, 0] / (cam_coord[:, 2] + 1e-8) * f[0] + c[0]
|
||||
y = cam_coord[:, 1] / (cam_coord[:, 2] + 1e-8) * f[1] + c[1]
|
||||
z = np.zeros_like(x)
|
||||
img_coord = np.concatenate((x[:, None], y[:, None], z[:, None]), 1)
|
||||
return img_coord
|
||||
|
||||
@staticmethod
|
||||
def _world2cam(world_coord, R, T):
|
||||
"""Transform the joints from their world coordinates to their camera
|
||||
coordinates.
|
||||
|
||||
Note:
|
||||
- N: number of joints
|
||||
|
||||
Args:
|
||||
world_coord (ndarray[3, N]): 3D joints coordinates
|
||||
in the world coordinate system
|
||||
R (ndarray[3, 3]): camera rotation matrix
|
||||
T (ndarray[3]): camera position (x, y, z)
|
||||
|
||||
Returns:
|
||||
cam_coord (ndarray[3, N]): 3D joints coordinates
|
||||
in the camera coordinate system
|
||||
"""
|
||||
cam_coord = np.dot(R, world_coord - T)
|
||||
return cam_coord
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset.
|
||||
|
||||
Adapted from 'https://github.com/facebookresearch/InterHand2.6M/'
|
||||
'blob/master/data/InterHand2.6M/dataset.py'
|
||||
Copyright (c) FaceBook Research, under CC-BY-NC 4.0 license.
|
||||
"""
|
||||
with open(self.camera_file, 'r') as f:
|
||||
cameras = json.load(f)
|
||||
with open(self.joint_file, 'r') as f:
|
||||
joints = json.load(f)
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
for img_id in self.img_ids:
|
||||
num_joints = self.ann_info['num_joints']
|
||||
|
||||
ann_id = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
ann = self.coco.loadAnns(ann_id)[0]
|
||||
img = self.coco.loadImgs(img_id)[0]
|
||||
|
||||
capture_id = str(img['capture'])
|
||||
camera_name = img['camera']
|
||||
frame_idx = str(img['frame_idx'])
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
|
||||
camera_pos, camera_rot = np.array(
|
||||
cameras[capture_id]['campos'][camera_name],
|
||||
dtype=np.float32), np.array(
|
||||
cameras[capture_id]['camrot'][camera_name],
|
||||
dtype=np.float32)
|
||||
focal, principal_pt = np.array(
|
||||
cameras[capture_id]['focal'][camera_name],
|
||||
dtype=np.float32), np.array(
|
||||
cameras[capture_id]['princpt'][camera_name],
|
||||
dtype=np.float32)
|
||||
joint_world = np.array(
|
||||
joints[capture_id][frame_idx]['world_coord'], dtype=np.float32)
|
||||
joint_cam = self._world2cam(
|
||||
joint_world.transpose(1, 0), camera_rot,
|
||||
camera_pos.reshape(3, 1)).transpose(1, 0)
|
||||
joint_img = self._cam2pixel(joint_cam, focal, principal_pt)[:, :2]
|
||||
joint_img = joint_img.reshape(2, -1, 2)
|
||||
|
||||
joint_valid = np.array(
|
||||
ann['joint_valid'], dtype=np.float32).reshape(2, -1)
|
||||
# if root is not valid -> root-relative 3D pose is also not valid.
|
||||
# Therefore, mark all joints as invalid
|
||||
for hand in range(2):
|
||||
joint_valid[hand, :] *= joint_valid[hand][-1]
|
||||
|
||||
if np.sum(joint_valid[hand, :]) > 11:
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3),
|
||||
dtype=np.float32)
|
||||
joints_3d[:, :2] = joint_img[hand, :, :]
|
||||
joints_3d_visible[:, :2] = np.minimum(
|
||||
1, joint_valid[hand, :].reshape(-1, 1))
|
||||
|
||||
# use the tightest bbox enclosing all keypoints as bbox
|
||||
bbox = [img['width'], img['height'], 0, 0]
|
||||
for i in range(num_joints):
|
||||
if joints_3d_visible[i][0]:
|
||||
bbox[0] = min(bbox[0], joints_3d[i][0])
|
||||
bbox[1] = min(bbox[1], joints_3d[i][1])
|
||||
bbox[2] = max(bbox[2], joints_3d[i][0])
|
||||
bbox[3] = max(bbox[3], joints_3d[i][1])
|
||||
|
||||
bbox[2] -= bbox[0]
|
||||
bbox[3] -= bbox[1]
|
||||
|
||||
# use 1.5bbox as input
|
||||
center, scale = self._xywh2cs(*bbox, 1.5)
|
||||
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': bbox,
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='PCK', **kwargs):
|
||||
"""Evaluate interhand2d keypoint results. The pose prediction results
|
||||
will be saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['Capture12/\
|
||||
0390_dh_touchROM/cam410209/image62434.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'PCK', 'AUC', 'EPE'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['PCK', 'AUC', 'EPE']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
+505
@@ -0,0 +1,505 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.core.evaluation.top_down_eval import keypoint_epe
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from ..base import Kpt3dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class InterHand3DDataset(Kpt3dSviewRgbImgTopDownDataset):
|
||||
"""InterHand2.6M 3D dataset for top-down hand pose estimation.
|
||||
|
||||
"InterHand2.6M: A Dataset and Baseline for 3D Interacting Hand Pose
|
||||
Estimation from a Single RGB Image", ECCV'2020.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/pdf/2008.09309.pdf>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
InterHand2.6M keypoint indexes::
|
||||
|
||||
0: 'r_thumb4',
|
||||
1: 'r_thumb3',
|
||||
2: 'r_thumb2',
|
||||
3: 'r_thumb1',
|
||||
4: 'r_index4',
|
||||
5: 'r_index3',
|
||||
6: 'r_index2',
|
||||
7: 'r_index1',
|
||||
8: 'r_middle4',
|
||||
9: 'r_middle3',
|
||||
10: 'r_middle2',
|
||||
11: 'r_middle1',
|
||||
12: 'r_ring4',
|
||||
13: 'r_ring3',
|
||||
14: 'r_ring2',
|
||||
15: 'r_ring1',
|
||||
16: 'r_pinky4',
|
||||
17: 'r_pinky3',
|
||||
18: 'r_pinky2',
|
||||
19: 'r_pinky1',
|
||||
20: 'r_wrist',
|
||||
21: 'l_thumb4',
|
||||
22: 'l_thumb3',
|
||||
23: 'l_thumb2',
|
||||
24: 'l_thumb1',
|
||||
25: 'l_index4',
|
||||
26: 'l_index3',
|
||||
27: 'l_index2',
|
||||
28: 'l_index1',
|
||||
29: 'l_middle4',
|
||||
30: 'l_middle3',
|
||||
31: 'l_middle2',
|
||||
32: 'l_middle1',
|
||||
33: 'l_ring4',
|
||||
34: 'l_ring3',
|
||||
35: 'l_ring2',
|
||||
36: 'l_ring1',
|
||||
37: 'l_pinky4',
|
||||
38: 'l_pinky3',
|
||||
39: 'l_pinky2',
|
||||
40: 'l_pinky1',
|
||||
41: 'l_wrist'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
camera_file (str): Path to the camera file.
|
||||
joint_file (str): Path to the joint file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
use_gt_root_depth (bool): Using the ground truth depth of the wrist
|
||||
or given depth from rootnet_result_file.
|
||||
rootnet_result_file (str): Path to the wrist depth file.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (str): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
camera_file,
|
||||
joint_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
use_gt_root_depth=True,
|
||||
rootnet_result_file=None,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/interhand3d.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['heatmap3d_depth_bound'] = data_cfg[
|
||||
'heatmap3d_depth_bound']
|
||||
self.ann_info['heatmap_size_root'] = data_cfg['heatmap_size_root']
|
||||
self.ann_info['root_depth_bound'] = data_cfg['root_depth_bound']
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
|
||||
self.camera_file = camera_file
|
||||
self.joint_file = joint_file
|
||||
|
||||
self.use_gt_root_depth = use_gt_root_depth
|
||||
if not self.use_gt_root_depth:
|
||||
assert rootnet_result_file is not None
|
||||
self.rootnet_result_file = rootnet_result_file
|
||||
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
@staticmethod
|
||||
def _encode_handtype(hand_type):
|
||||
if hand_type == 'right':
|
||||
return np.array([1, 0], dtype=np.float32)
|
||||
elif hand_type == 'left':
|
||||
return np.array([0, 1], dtype=np.float32)
|
||||
elif hand_type == 'interacting':
|
||||
return np.array([1, 1], dtype=np.float32)
|
||||
else:
|
||||
assert 0, f'Not support hand type: {hand_type}'
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset.
|
||||
|
||||
Adapted from 'https://github.com/facebookresearch/InterHand2.6M/'
|
||||
'blob/master/data/InterHand2.6M/dataset.py'
|
||||
Copyright (c) FaceBook Research, under CC-BY-NC 4.0 license.
|
||||
"""
|
||||
with open(self.camera_file, 'r') as f:
|
||||
cameras = json.load(f)
|
||||
with open(self.joint_file, 'r') as f:
|
||||
joints = json.load(f)
|
||||
|
||||
if not self.use_gt_root_depth:
|
||||
rootnet_result = {}
|
||||
with open(self.rootnet_result_file, 'r') as f:
|
||||
rootnet_annot = json.load(f)
|
||||
for i in range(len(rootnet_annot)):
|
||||
rootnet_result[str(
|
||||
rootnet_annot[i]['annot_id'])] = rootnet_annot[i]
|
||||
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
for img_id in self.img_ids:
|
||||
num_joints = self.ann_info['num_joints']
|
||||
|
||||
ann_id = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
ann = self.coco.loadAnns(ann_id)[0]
|
||||
img = self.coco.loadImgs(img_id)[0]
|
||||
|
||||
capture_id = str(img['capture'])
|
||||
camera_name = img['camera']
|
||||
frame_idx = str(img['frame_idx'])
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
|
||||
camera_pos = np.array(
|
||||
cameras[capture_id]['campos'][camera_name], dtype=np.float32)
|
||||
camera_rot = np.array(
|
||||
cameras[capture_id]['camrot'][camera_name], dtype=np.float32)
|
||||
focal = np.array(
|
||||
cameras[capture_id]['focal'][camera_name], dtype=np.float32)
|
||||
principal_pt = np.array(
|
||||
cameras[capture_id]['princpt'][camera_name], dtype=np.float32)
|
||||
joint_world = np.array(
|
||||
joints[capture_id][frame_idx]['world_coord'], dtype=np.float32)
|
||||
joint_cam = self._world2cam(
|
||||
joint_world.transpose(1, 0), camera_rot,
|
||||
camera_pos.reshape(3, 1)).transpose(1, 0)
|
||||
joint_img = self._cam2pixel(joint_cam, focal, principal_pt)[:, :2]
|
||||
|
||||
joint_valid = np.array(
|
||||
ann['joint_valid'], dtype=np.float32).flatten()
|
||||
hand_type = self._encode_handtype(ann['hand_type'])
|
||||
hand_type_valid = ann['hand_type_valid']
|
||||
|
||||
if self.use_gt_root_depth:
|
||||
bbox = np.array(ann['bbox'], dtype=np.float32)
|
||||
# extend the bbox to include some context
|
||||
center, scale = self._xywh2cs(*bbox, 1.25)
|
||||
abs_depth = [joint_cam[20, 2], joint_cam[41, 2]]
|
||||
else:
|
||||
rootnet_ann_data = rootnet_result[str(ann_id[0])]
|
||||
bbox = np.array(rootnet_ann_data['bbox'], dtype=np.float32)
|
||||
# the bboxes have been extended
|
||||
center, scale = self._xywh2cs(*bbox, 1.0)
|
||||
abs_depth = rootnet_ann_data['abs_depth']
|
||||
# 41: 'l_wrist', left hand root
|
||||
# 20: 'r_wrist', right hand root
|
||||
rel_root_depth = joint_cam[41, 2] - joint_cam[20, 2]
|
||||
# if root is not valid, root-relative 3D depth is also invalid.
|
||||
rel_root_valid = joint_valid[20] * joint_valid[41]
|
||||
|
||||
# if root is not valid -> root-relative 3D pose is also not valid.
|
||||
# Therefore, mark all joints as invalid
|
||||
joint_valid[:20] *= joint_valid[20]
|
||||
joint_valid[21:] *= joint_valid[41]
|
||||
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d[:, :2] = joint_img
|
||||
joints_3d[:21, 2] = joint_cam[:21, 2] - joint_cam[20, 2]
|
||||
joints_3d[21:, 2] = joint_cam[21:, 2] - joint_cam[41, 2]
|
||||
joints_3d_visible[...] = np.minimum(1, joint_valid.reshape(-1, 1))
|
||||
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'hand_type': hand_type,
|
||||
'hand_type_valid': hand_type_valid,
|
||||
'rel_root_depth': rel_root_depth,
|
||||
'rel_root_valid': rel_root_valid,
|
||||
'abs_depth': abs_depth,
|
||||
'joints_cam': joint_cam,
|
||||
'focal': focal,
|
||||
'princpt': principal_pt,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': bbox,
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='MPJPE', **kwargs):
|
||||
"""Evaluate interhand2d keypoint results. The pose prediction results
|
||||
will be saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- hand_type (np.ndarray[N, 4]): The first two dimensions are \
|
||||
hand type, scores is the last two dimensions.
|
||||
- rel_root_depth (np.ndarray[N]): The relative depth of left \
|
||||
wrist and right wrist.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['Capture6/\
|
||||
0012_aokay_upright/cam410061/image4996.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'MRRPE', 'MPJPE', 'Handedness_acc'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['MRRPE', 'MPJPE', 'Handedness_acc']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result.get('preds')
|
||||
if preds is None and 'MPJPE' in metrics:
|
||||
raise KeyError('metric MPJPE is not supported')
|
||||
|
||||
hand_type = result.get('hand_type')
|
||||
if hand_type is None and 'Handedness_acc' in metrics:
|
||||
raise KeyError('metric Handedness_acc is not supported')
|
||||
|
||||
rel_root_depth = result.get('rel_root_depth')
|
||||
if rel_root_depth is None and 'MRRPE' in metrics:
|
||||
raise KeyError('metric MRRPE is not supported')
|
||||
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpt = {
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
}
|
||||
|
||||
if preds is not None:
|
||||
kpt['keypoints'] = preds[i, :, :3].tolist()
|
||||
if hand_type is not None:
|
||||
kpt['hand_type'] = hand_type[i][0:2].tolist()
|
||||
kpt['hand_type_score'] = hand_type[i][2:4].tolist()
|
||||
if rel_root_depth is not None:
|
||||
kpt['rel_root_depth'] = float(rel_root_depth[i])
|
||||
|
||||
kpts.append(kpt)
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
|
||||
@staticmethod
|
||||
def _get_accuracy(outputs, gts, masks):
|
||||
"""Get accuracy of multi-label classification.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- label_num: C
|
||||
|
||||
Args:
|
||||
outputs (np.array[N, C]): predicted multi-label.
|
||||
gts (np.array[N, C]): Groundtruth muti-label.
|
||||
masks (np.array[N, ]): masked outputs will be ignored for
|
||||
accuracy calculation.
|
||||
|
||||
Returns:
|
||||
float: mean accuracy
|
||||
"""
|
||||
acc = (outputs == gts).all(axis=1)
|
||||
return np.mean(acc[masks])
|
||||
|
||||
def _report_metric(self, res_file, metrics):
|
||||
"""Keypoint evaluation.
|
||||
|
||||
Args:
|
||||
res_file (str): Json file stored prediction results.
|
||||
metrics (str | list[str]): Metric to be performed.
|
||||
Options: 'MRRPE', 'MPJPE', 'Handedness_acc'.
|
||||
|
||||
Returns:
|
||||
list: Evaluation results for evaluation metric.
|
||||
"""
|
||||
info_str = []
|
||||
|
||||
with open(res_file, 'r') as fin:
|
||||
preds = json.load(fin)
|
||||
assert len(preds) == len(self.db)
|
||||
|
||||
gts_rel_root = []
|
||||
preds_rel_root = []
|
||||
rel_root_masks = []
|
||||
gts_joint_coord_cam = []
|
||||
preds_joint_coord_cam = []
|
||||
single_masks = []
|
||||
interacting_masks = []
|
||||
all_masks = []
|
||||
gts_hand_type = []
|
||||
preds_hand_type = []
|
||||
hand_type_masks = []
|
||||
|
||||
for pred, item in zip(preds, self.db):
|
||||
# mrrpe
|
||||
if 'MRRPE' in metrics:
|
||||
if item['hand_type'].all() and item['joints_3d_visible'][
|
||||
20, 0] and item['joints_3d_visible'][41, 0]:
|
||||
rel_root_masks.append(True)
|
||||
|
||||
pred_left_root_img = np.array(
|
||||
pred['keypoints'][41], dtype=np.float32)[None, :]
|
||||
pred_left_root_img[:, 2] += item['abs_depth'][0] + pred[
|
||||
'rel_root_depth']
|
||||
pred_left_root_cam = self._pixel2cam(
|
||||
pred_left_root_img, item['focal'], item['princpt'])
|
||||
|
||||
pred_right_root_img = np.array(
|
||||
pred['keypoints'][20], dtype=np.float32)[None, :]
|
||||
pred_right_root_img[:, 2] += item['abs_depth'][0]
|
||||
pred_right_root_cam = self._pixel2cam(
|
||||
pred_right_root_img, item['focal'], item['princpt'])
|
||||
|
||||
preds_rel_root.append(pred_left_root_cam -
|
||||
pred_right_root_cam)
|
||||
gts_rel_root.append(
|
||||
[item['joints_cam'][41] - item['joints_cam'][20]])
|
||||
else:
|
||||
rel_root_masks.append(False)
|
||||
preds_rel_root.append([[0., 0., 0.]])
|
||||
gts_rel_root.append([[0., 0., 0.]])
|
||||
|
||||
if 'MPJPE' in metrics:
|
||||
pred_joint_coord_img = np.array(
|
||||
pred['keypoints'], dtype=np.float32)
|
||||
gt_joint_coord_cam = item['joints_cam'].copy()
|
||||
|
||||
pred_joint_coord_img[:21, 2] += item['abs_depth'][0]
|
||||
pred_joint_coord_img[21:, 2] += item['abs_depth'][1]
|
||||
pred_joint_coord_cam = self._pixel2cam(pred_joint_coord_img,
|
||||
item['focal'],
|
||||
item['princpt'])
|
||||
|
||||
pred_joint_coord_cam[:21] -= pred_joint_coord_cam[20]
|
||||
pred_joint_coord_cam[21:] -= pred_joint_coord_cam[41]
|
||||
gt_joint_coord_cam[:21] -= gt_joint_coord_cam[20]
|
||||
gt_joint_coord_cam[21:] -= gt_joint_coord_cam[41]
|
||||
|
||||
preds_joint_coord_cam.append(pred_joint_coord_cam)
|
||||
gts_joint_coord_cam.append(gt_joint_coord_cam)
|
||||
|
||||
mask = (np.array(item['joints_3d_visible'])[:, 0]) > 0
|
||||
|
||||
if item['hand_type'].all():
|
||||
single_masks.append(
|
||||
np.zeros(self.ann_info['num_joints'], dtype=bool))
|
||||
interacting_masks.append(mask)
|
||||
all_masks.append(mask)
|
||||
else:
|
||||
single_masks.append(mask)
|
||||
interacting_masks.append(
|
||||
np.zeros(self.ann_info['num_joints'], dtype=bool))
|
||||
all_masks.append(mask)
|
||||
|
||||
if 'Handedness_acc' in metrics:
|
||||
pred_hand_type = np.array(pred['hand_type'], dtype=int)
|
||||
preds_hand_type.append(pred_hand_type)
|
||||
gts_hand_type.append(item['hand_type'])
|
||||
hand_type_masks.append(item['hand_type_valid'] > 0)
|
||||
|
||||
gts_rel_root = np.array(gts_rel_root, dtype=np.float32)
|
||||
preds_rel_root = np.array(preds_rel_root, dtype=np.float32)
|
||||
rel_root_masks = np.array(rel_root_masks, dtype=bool)[:, None]
|
||||
gts_joint_coord_cam = np.array(gts_joint_coord_cam, dtype=np.float32)
|
||||
preds_joint_coord_cam = np.array(
|
||||
preds_joint_coord_cam, dtype=np.float32)
|
||||
single_masks = np.array(single_masks, dtype=bool)
|
||||
interacting_masks = np.array(interacting_masks, dtype=bool)
|
||||
all_masks = np.array(all_masks, dtype=bool)
|
||||
gts_hand_type = np.array(gts_hand_type, dtype=int)
|
||||
preds_hand_type = np.array(preds_hand_type, dtype=int)
|
||||
hand_type_masks = np.array(hand_type_masks, dtype=bool)
|
||||
|
||||
if 'MRRPE' in metrics:
|
||||
info_str.append(('MRRPE',
|
||||
keypoint_epe(preds_rel_root, gts_rel_root,
|
||||
rel_root_masks)))
|
||||
|
||||
if 'MPJPE' in metrics:
|
||||
info_str.append(('MPJPE_all',
|
||||
keypoint_epe(preds_joint_coord_cam,
|
||||
gts_joint_coord_cam, all_masks)))
|
||||
info_str.append(('MPJPE_single',
|
||||
keypoint_epe(preds_joint_coord_cam,
|
||||
gts_joint_coord_cam, single_masks)))
|
||||
info_str.append(
|
||||
('MPJPE_interacting',
|
||||
keypoint_epe(preds_joint_coord_cam, gts_joint_coord_cam,
|
||||
interacting_masks)))
|
||||
|
||||
if 'Handedness_acc' in metrics:
|
||||
info_str.append(('Handedness_acc',
|
||||
self._get_accuracy(preds_hand_type, gts_hand_type,
|
||||
hand_type_masks)))
|
||||
|
||||
return info_str
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class OneHand10KDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""OneHand10K dataset for top-down hand pose estimation.
|
||||
|
||||
"Mask-pose Cascaded CNN for 2D Hand Pose Estimation from
|
||||
Single Color Images", TCSVT'2019.
|
||||
More details can be found in the `paper
|
||||
<https://www.yangangwang.com/papers/WANG-MCC-2018-10.pdf>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
OneHand10K keypoint indexes::
|
||||
|
||||
0: 'wrist',
|
||||
1: 'thumb1',
|
||||
2: 'thumb2',
|
||||
3: 'thumb3',
|
||||
4: 'thumb4',
|
||||
5: 'forefinger1',
|
||||
6: 'forefinger2',
|
||||
7: 'forefinger3',
|
||||
8: 'forefinger4',
|
||||
9: 'middle_finger1',
|
||||
10: 'middle_finger2',
|
||||
11: 'middle_finger3',
|
||||
12: 'middle_finger4',
|
||||
13: 'ring_finger1',
|
||||
14: 'ring_finger2',
|
||||
15: 'ring_finger3',
|
||||
16: 'ring_finger4',
|
||||
17: 'pinky_finger1',
|
||||
18: 'pinky_finger2',
|
||||
19: 'pinky_finger3',
|
||||
20: 'pinky_finger4'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/onehand10k.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
num_joints = self.ann_info['num_joints']
|
||||
for img_id in self.img_ids:
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
for obj in objs:
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
# use 1.25 padded bbox as input
|
||||
center, scale = self._xywh2cs(*obj['bbox'][:4], 1.25)
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': obj['bbox'],
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='PCK', **kwargs):
|
||||
"""Evaluate onehand10k keypoint results. The pose prediction results
|
||||
will be saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['Test/source/0.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'PCK', 'AUC', 'EPE'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['PCK', 'AUC', 'EPE']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
Vendored
+208
@@ -0,0 +1,208 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class PanopticDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""Panoptic dataset for top-down hand pose estimation.
|
||||
|
||||
"Hand Keypoint Detection in Single Images using Multiview
|
||||
Bootstrapping", CVPR'2017.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1704.07809>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
Panoptic keypoint indexes::
|
||||
|
||||
0: 'wrist',
|
||||
1: 'thumb1',
|
||||
2: 'thumb2',
|
||||
3: 'thumb3',
|
||||
4: 'thumb4',
|
||||
5: 'forefinger1',
|
||||
6: 'forefinger2',
|
||||
7: 'forefinger3',
|
||||
8: 'forefinger4',
|
||||
9: 'middle_finger1',
|
||||
10: 'middle_finger2',
|
||||
11: 'middle_finger3',
|
||||
12: 'middle_finger4',
|
||||
13: 'ring_finger1',
|
||||
14: 'ring_finger2',
|
||||
15: 'ring_finger3',
|
||||
16: 'ring_finger4',
|
||||
17: 'pinky_finger1',
|
||||
18: 'pinky_finger2',
|
||||
19: 'pinky_finger3',
|
||||
20: 'pinky_finger4'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/panoptic_hand2d.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
num_joints = self.ann_info['num_joints']
|
||||
for img_id in self.img_ids:
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
for obj in objs:
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
# The bbox is the tightest bbox enclosing keypoints.
|
||||
# The paper uses 2.2 bbox as the input, while
|
||||
# we use 1.76 (2.2 * 0.8) bbox as the input.
|
||||
center, scale = self._xywh2cs(*obj['bbox'][:4], 1.76)
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': obj['bbox'],
|
||||
'head_size': obj['head_size'],
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='PCKh', **kwargs):
|
||||
"""Evaluate panoptic keypoint results. The pose prediction results will
|
||||
be saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['hand_labels/\
|
||||
manual_test/000648952_02_l.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'PCKh', 'AUC', 'EPE'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['PCKh', 'AUC', 'EPE']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class Rhd2DDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""Rendered Handpose Dataset for top-down hand pose estimation.
|
||||
|
||||
"Learning to Estimate 3D Hand Pose from Single RGB Images",
|
||||
ICCV'2017.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/pdf/1705.01389.pdf>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
Rhd keypoint indexes::
|
||||
|
||||
0: 'wrist',
|
||||
1: 'thumb1',
|
||||
2: 'thumb2',
|
||||
3: 'thumb3',
|
||||
4: 'thumb4',
|
||||
5: 'forefinger1',
|
||||
6: 'forefinger2',
|
||||
7: 'forefinger3',
|
||||
8: 'forefinger4',
|
||||
9: 'middle_finger1',
|
||||
10: 'middle_finger2',
|
||||
11: 'middle_finger3',
|
||||
12: 'middle_finger4',
|
||||
13: 'ring_finger1',
|
||||
14: 'ring_finger2',
|
||||
15: 'ring_finger3',
|
||||
16: 'ring_finger4',
|
||||
17: 'pinky_finger1',
|
||||
18: 'pinky_finger2',
|
||||
19: 'pinky_finger3',
|
||||
20: 'pinky_finger4'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/rhd2d.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
num_joints = self.ann_info['num_joints']
|
||||
for img_id in self.img_ids:
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
for obj in objs:
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
# the ori image is 224x224
|
||||
center, scale = self._xywh2cs(*obj['bbox'][:4], padding=1.25)
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': obj['bbox'],
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='PCK', **kwargs):
|
||||
"""Evaluate rhd keypoint results. The pose prediction results will be
|
||||
saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1], area, score]
|
||||
- image_paths (list[str]): For example,
|
||||
['training/rgb/00031426.jpg']
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'PCK', 'AUC', 'EPE'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['PCK', 'AUC', 'EPE']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .mesh_adv_dataset import MeshAdversarialDataset
|
||||
from .mesh_h36m_dataset import MeshH36MDataset
|
||||
from .mesh_mix_dataset import MeshMixDataset
|
||||
from .mosh_dataset import MoshDataset
|
||||
|
||||
__all__ = [
|
||||
'MeshH36MDataset', 'MoshDataset', 'MeshMixDataset',
|
||||
'MeshAdversarialDataset'
|
||||
]
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import numpy as np
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
from mmpose.datasets.builder import DATASETS, build_dataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class MeshAdversarialDataset(Dataset):
|
||||
"""Mix Dataset for the adversarial training in 3D human mesh estimation
|
||||
task.
|
||||
|
||||
The dataset combines data from two datasets and
|
||||
return a dict containing data from two datasets.
|
||||
|
||||
Args:
|
||||
train_dataset (Dataset): Dataset for 3D human mesh estimation.
|
||||
adversarial_dataset (Dataset): Dataset for adversarial learning,
|
||||
provides real SMPL parameters.
|
||||
"""
|
||||
|
||||
def __init__(self, train_dataset, adversarial_dataset):
|
||||
super().__init__()
|
||||
self.train_dataset = build_dataset(train_dataset)
|
||||
self.adversarial_dataset = build_dataset(adversarial_dataset)
|
||||
self.length = len(self.train_dataset)
|
||||
|
||||
def __len__(self):
|
||||
"""Get the size of the dataset."""
|
||||
return self.length
|
||||
|
||||
def __getitem__(self, i):
|
||||
"""Given index, get the data from train dataset and randomly sample an
|
||||
item from adversarial dataset.
|
||||
|
||||
Return a dict containing data from train and adversarial dataset.
|
||||
"""
|
||||
data = self.train_dataset[i]
|
||||
ind_adv = np.random.randint(
|
||||
low=0, high=len(self.adversarial_dataset), dtype=int)
|
||||
data.update(self.adversarial_dataset[ind_adv %
|
||||
len(self.adversarial_dataset)])
|
||||
return data
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy as cp
|
||||
import os
|
||||
from abc import ABCMeta
|
||||
|
||||
import numpy as np
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
from mmpose.datasets.pipelines import Compose
|
||||
|
||||
|
||||
class MeshBaseDataset(Dataset, metaclass=ABCMeta):
|
||||
"""Base dataset for 3D human mesh estimation task. In 3D humamesh
|
||||
estimation task, all datasets share this BaseDataset for training and have
|
||||
their own evaluate function.
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
This dataset can only be used for training.
|
||||
For evaluation, subclass should write an extra evaluate function.
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
test_mode=False):
|
||||
|
||||
self.image_info = {}
|
||||
self.ann_info = {}
|
||||
|
||||
self.ann_file = ann_file
|
||||
self.img_prefix = img_prefix
|
||||
self.pipeline = pipeline
|
||||
self.test_mode = test_mode
|
||||
|
||||
self.ann_info['image_size'] = np.array(data_cfg['image_size'])
|
||||
self.ann_info['iuv_size'] = np.array(data_cfg['iuv_size'])
|
||||
self.ann_info['num_joints'] = data_cfg['num_joints']
|
||||
self.ann_info['flip_pairs'] = None
|
||||
self.db = []
|
||||
self.pipeline = Compose(self.pipeline)
|
||||
|
||||
# flip_pairs
|
||||
# For all mesh dataset, we use 24 joints as CMR and SPIN.
|
||||
self.ann_info['flip_pairs'] = [[0, 5], [1, 4], [2, 3], [6, 11],
|
||||
[7, 10], [8, 9], [20, 21], [22, 23]]
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
assert self.ann_info['num_joints'] == 24
|
||||
self.ann_info['joint_weights'] = np.ones([24, 1], dtype=np.float32)
|
||||
|
||||
self.ann_info['uv_type'] = data_cfg['uv_type']
|
||||
self.ann_info['use_IUV'] = data_cfg['use_IUV']
|
||||
uv_type = self.ann_info['uv_type']
|
||||
self.iuv_prefix = os.path.join(self.img_prefix, f'{uv_type}_IUV_gt')
|
||||
self.db = self._get_db(ann_file)
|
||||
|
||||
def _get_db(self, ann_file):
|
||||
"""Load dataset."""
|
||||
data = np.load(ann_file)
|
||||
tmpl = dict(
|
||||
image_file=None,
|
||||
center=None,
|
||||
scale=None,
|
||||
rotation=0,
|
||||
joints_2d=None,
|
||||
joints_2d_visible=None,
|
||||
joints_3d=None,
|
||||
joints_3d_visible=None,
|
||||
gender=None,
|
||||
pose=None,
|
||||
beta=None,
|
||||
has_smpl=0,
|
||||
iuv_file=None,
|
||||
has_iuv=0)
|
||||
gt_db = []
|
||||
|
||||
_imgnames = data['imgname']
|
||||
_scales = data['scale'].astype(np.float32)
|
||||
_centers = data['center'].astype(np.float32)
|
||||
dataset_len = len(_imgnames)
|
||||
|
||||
# Get 2D keypoints
|
||||
if 'part' in data.keys():
|
||||
_keypoints = data['part'].astype(np.float32)
|
||||
else:
|
||||
_keypoints = np.zeros((dataset_len, 24, 3), dtype=np.float32)
|
||||
|
||||
# Get gt 3D joints, if available
|
||||
if 'S' in data.keys():
|
||||
_joints_3d = data['S'].astype(np.float32)
|
||||
else:
|
||||
_joints_3d = np.zeros((dataset_len, 24, 4), dtype=np.float32)
|
||||
|
||||
# Get gt SMPL parameters, if available
|
||||
if 'pose' in data.keys() and 'shape' in data.keys():
|
||||
_poses = data['pose'].astype(np.float32)
|
||||
_betas = data['shape'].astype(np.float32)
|
||||
has_smpl = 1
|
||||
else:
|
||||
_poses = np.zeros((dataset_len, 72), dtype=np.float32)
|
||||
_betas = np.zeros((dataset_len, 10), dtype=np.float32)
|
||||
has_smpl = 0
|
||||
|
||||
# Get gender data, if available
|
||||
if 'gender' in data.keys():
|
||||
_genders = data['gender']
|
||||
_genders = np.array([str(g) != 'm' for g in _genders]).astype(int)
|
||||
else:
|
||||
_genders = -1 * np.ones(dataset_len).astype(int)
|
||||
|
||||
# Get IUV image, if available
|
||||
if 'iuv_names' in data.keys():
|
||||
_iuv_names = data['iuv_names']
|
||||
has_iuv = has_smpl
|
||||
else:
|
||||
_iuv_names = [''] * dataset_len
|
||||
has_iuv = 0
|
||||
|
||||
for i in range(len(_imgnames)):
|
||||
newitem = cp.deepcopy(tmpl)
|
||||
newitem['image_file'] = os.path.join(self.img_prefix, _imgnames[i])
|
||||
newitem['scale'] = np.array([_scales[i], _scales[i]])
|
||||
newitem['center'] = _centers[i]
|
||||
newitem['joints_2d'] = _keypoints[i, :, :2]
|
||||
newitem['joints_2d_visible'] = _keypoints[i, :, -1][:, None]
|
||||
newitem['joints_3d'] = _joints_3d[i, :, :3]
|
||||
newitem['joints_3d_visible'] = _joints_3d[i, :, -1][:, None]
|
||||
newitem['pose'] = _poses[i]
|
||||
newitem['beta'] = _betas[i]
|
||||
newitem['has_smpl'] = has_smpl
|
||||
newitem['gender'] = _genders[i]
|
||||
newitem['iuv_file'] = os.path.join(self.iuv_prefix, _iuv_names[i])
|
||||
newitem['has_iuv'] = has_iuv
|
||||
gt_db.append(newitem)
|
||||
return gt_db
|
||||
|
||||
def __len__(self, ):
|
||||
"""Get the size of the dataset."""
|
||||
return len(self.db)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
"""Get the sample given index."""
|
||||
results = cp.deepcopy(self.db[idx])
|
||||
results['ann_info'] = self.ann_info
|
||||
return self.pipeline(results)
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
|
||||
from mmpose.core.evaluation import keypoint_mpjpe
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from .mesh_base_dataset import MeshBaseDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class MeshH36MDataset(MeshBaseDataset):
|
||||
"""Human3.6M Dataset for 3D human mesh estimation. It inherits all function
|
||||
from MeshBaseDataset and has its own evaluate function.
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def evaluate(self, outputs, res_folder, metric='joint_error', logger=None):
|
||||
"""Evaluate 3D keypoint results."""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['joint_error']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
res_file = os.path.join(res_folder, 'result_keypoints.json')
|
||||
kpts = []
|
||||
for out in outputs:
|
||||
for (keypoints, image_path) in zip(out['keypoints_3d'],
|
||||
out['image_path']):
|
||||
kpts.append({
|
||||
'keypoints': keypoints.tolist(),
|
||||
'image': image_path,
|
||||
})
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file)
|
||||
name_value = OrderedDict(info_str)
|
||||
return name_value
|
||||
|
||||
@staticmethod
|
||||
def _write_keypoint_results(keypoints, res_file):
|
||||
"""Write results into a json file."""
|
||||
|
||||
with open(res_file, 'w') as f:
|
||||
json.dump(keypoints, f, sort_keys=True, indent=4)
|
||||
|
||||
def _report_metric(self, res_file):
|
||||
"""Keypoint evaluation.
|
||||
|
||||
Report mean per joint position error (MPJPE) and mean per joint
|
||||
position error after rigid alignment (MPJPE-PA)
|
||||
"""
|
||||
|
||||
with open(res_file, 'r') as fin:
|
||||
preds = json.load(fin)
|
||||
assert len(preds) == len(self.db)
|
||||
|
||||
pred_joints_3d = [pred['keypoints'] for pred in preds]
|
||||
gt_joints_3d = [item['joints_3d'] for item in self.db]
|
||||
gt_joints_visible = [item['joints_3d_visible'] for item in self.db]
|
||||
|
||||
pred_joints_3d = np.array(pred_joints_3d)
|
||||
gt_joints_3d = np.array(gt_joints_3d)
|
||||
gt_joints_visible = np.array(gt_joints_visible)
|
||||
|
||||
# we only evaluate on 14 lsp joints
|
||||
joint_mapper = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 18]
|
||||
pred_joints_3d = pred_joints_3d[:, joint_mapper, :]
|
||||
pred_pelvis = (pred_joints_3d[:, 2] + pred_joints_3d[:, 3]) / 2
|
||||
pred_joints_3d = pred_joints_3d - pred_pelvis[:, None, :]
|
||||
|
||||
gt_joints_3d = gt_joints_3d[:, joint_mapper, :]
|
||||
gt_pelvis = (gt_joints_3d[:, 2] + gt_joints_3d[:, 3]) / 2
|
||||
gt_joints_3d = gt_joints_3d - gt_pelvis[:, None, :]
|
||||
gt_joints_visible = gt_joints_visible[:, joint_mapper, 0] > 0
|
||||
|
||||
mpjpe = keypoint_mpjpe(pred_joints_3d, gt_joints_3d, gt_joints_visible)
|
||||
mpjpe_pa = keypoint_mpjpe(
|
||||
pred_joints_3d,
|
||||
gt_joints_3d,
|
||||
gt_joints_visible,
|
||||
alignment='procrustes')
|
||||
|
||||
info_str = []
|
||||
info_str.append(('MPJPE', mpjpe * 1000))
|
||||
info_str.append(('MPJPE-PA', mpjpe_pa * 1000))
|
||||
return info_str
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from abc import ABCMeta
|
||||
|
||||
import numpy as np
|
||||
from torch.utils.data import ConcatDataset, Dataset, WeightedRandomSampler
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from .mesh_base_dataset import MeshBaseDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class MeshMixDataset(Dataset, metaclass=ABCMeta):
|
||||
"""Mix Dataset for 3D human mesh estimation.
|
||||
|
||||
The dataset combines data from multiple datasets (MeshBaseDataset) and
|
||||
sample the data from different datasets with the provided proportions.
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
Args:
|
||||
configs (list): List of configs for multiple datasets.
|
||||
partition (list): Sample proportion of multiple datasets. The length
|
||||
of partition should be same with that of configs. The elements
|
||||
of it should be non-negative and is not necessary summing up to
|
||||
one.
|
||||
|
||||
Example:
|
||||
>>> from mmpose.datasets import MeshMixDataset
|
||||
>>> data_cfg = dict(
|
||||
>>> image_size=[256, 256],
|
||||
>>> iuv_size=[64, 64],
|
||||
>>> num_joints=24,
|
||||
>>> use_IUV=True,
|
||||
>>> uv_type='BF')
|
||||
>>>
|
||||
>>> mix_dataset = MeshMixDataset(
|
||||
>>> configs=[
|
||||
>>> dict(
|
||||
>>> ann_file='tests/data/h36m/test_h36m.npz',
|
||||
>>> img_prefix='tests/data/h36m',
|
||||
>>> data_cfg=data_cfg,
|
||||
>>> pipeline=[]),
|
||||
>>> dict(
|
||||
>>> ann_file='tests/data/h36m/test_h36m.npz',
|
||||
>>> img_prefix='tests/data/h36m',
|
||||
>>> data_cfg=data_cfg,
|
||||
>>> pipeline=[]),
|
||||
>>> ],
|
||||
>>> partition=[0.6, 0.4])
|
||||
"""
|
||||
|
||||
def __init__(self, configs, partition):
|
||||
"""Load data from multiple datasets."""
|
||||
assert min(partition) >= 0
|
||||
datasets = [MeshBaseDataset(**cfg) for cfg in configs]
|
||||
self.dataset = ConcatDataset(datasets)
|
||||
self.length = max(len(ds) for ds in datasets)
|
||||
weights = [
|
||||
np.ones(len(ds)) * p / len(ds)
|
||||
for (p, ds) in zip(partition, datasets)
|
||||
]
|
||||
weights = np.concatenate(weights, axis=0)
|
||||
self.sampler = WeightedRandomSampler(weights, 1)
|
||||
|
||||
def __len__(self):
|
||||
"""Get the size of the dataset."""
|
||||
return self.length
|
||||
|
||||
def __getitem__(self, idx):
|
||||
"""Given index, sample the data from multiple datasets with the given
|
||||
proportion."""
|
||||
idx_new = list(self.sampler)[0]
|
||||
return self.dataset[idx_new]
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy as cp
|
||||
from abc import ABCMeta
|
||||
|
||||
import numpy as np
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from mmpose.datasets.pipelines import Compose
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class MoshDataset(Dataset, metaclass=ABCMeta):
|
||||
"""Mosh Dataset for the adversarial training in 3D human mesh estimation
|
||||
task.
|
||||
|
||||
The dataset return a dict containing real-world SMPL parameters.
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self, ann_file, pipeline, test_mode=False):
|
||||
|
||||
self.ann_file = ann_file
|
||||
self.pipeline = pipeline
|
||||
self.test_mode = test_mode
|
||||
|
||||
self.db = self._get_db(ann_file)
|
||||
self.pipeline = Compose(self.pipeline)
|
||||
|
||||
@staticmethod
|
||||
def _get_db(ann_file):
|
||||
"""Load dataset."""
|
||||
data = np.load(ann_file)
|
||||
_betas = data['shape'].astype(np.float32)
|
||||
_poses = data['pose'].astype(np.float32)
|
||||
tmpl = dict(
|
||||
pose=None,
|
||||
beta=None,
|
||||
)
|
||||
gt_db = []
|
||||
dataset_len = len(_betas)
|
||||
|
||||
for i in range(dataset_len):
|
||||
newitem = cp.deepcopy(tmpl)
|
||||
newitem['pose'] = _poses[i]
|
||||
newitem['beta'] = _betas[i]
|
||||
gt_db.append(newitem)
|
||||
return gt_db
|
||||
|
||||
def __len__(self, ):
|
||||
"""Get the size of the dataset."""
|
||||
return len(self.db)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
"""Get the sample given index."""
|
||||
item = cp.deepcopy(self.db[idx])
|
||||
trivial, pose, beta = \
|
||||
np.zeros(3, dtype=np.float32), item['pose'], item['beta']
|
||||
results = {
|
||||
'mosh_theta':
|
||||
np.concatenate((trivial, pose, beta), axis=0).astype(np.float32)
|
||||
}
|
||||
return self.pipeline(results)
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .topdown_aic_dataset import TopDownAicDataset
|
||||
from .topdown_coco_dataset import TopDownCocoDataset
|
||||
from .topdown_coco_wholebody_dataset import TopDownCocoWholeBodyDataset
|
||||
from .topdown_crowdpose_dataset import TopDownCrowdPoseDataset
|
||||
from .topdown_h36m_dataset import TopDownH36MDataset
|
||||
from .topdown_halpe_dataset import TopDownHalpeDataset
|
||||
from .topdown_jhmdb_dataset import TopDownJhmdbDataset
|
||||
from .topdown_mhp_dataset import TopDownMhpDataset
|
||||
from .topdown_mpii_dataset import TopDownMpiiDataset
|
||||
from .topdown_mpii_trb_dataset import TopDownMpiiTrbDataset
|
||||
from .topdown_ochuman_dataset import TopDownOCHumanDataset
|
||||
from .topdown_posetrack18_dataset import TopDownPoseTrack18Dataset
|
||||
from .topdown_posetrack18_video_dataset import TopDownPoseTrack18VideoDataset
|
||||
|
||||
__all__ = [
|
||||
'TopDownAicDataset',
|
||||
'TopDownCocoDataset',
|
||||
'TopDownCocoWholeBodyDataset',
|
||||
'TopDownCrowdPoseDataset',
|
||||
'TopDownMpiiDataset',
|
||||
'TopDownMpiiTrbDataset',
|
||||
'TopDownOCHumanDataset',
|
||||
'TopDownPoseTrack18Dataset',
|
||||
'TopDownJhmdbDataset',
|
||||
'TopDownMhpDataset',
|
||||
'TopDownH36MDataset',
|
||||
'TopDownHalpeDataset',
|
||||
'TopDownPoseTrack18VideoDataset',
|
||||
]
|
||||
Vendored
+112
@@ -0,0 +1,112 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
from mmcv import Config
|
||||
from xtcocotools.cocoeval import COCOeval
|
||||
|
||||
from ...builder import DATASETS
|
||||
from .topdown_coco_dataset import TopDownCocoDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class TopDownAicDataset(TopDownCocoDataset):
|
||||
"""AicDataset dataset for top-down pose estimation.
|
||||
|
||||
"AI Challenger : A Large-scale Dataset for Going Deeper
|
||||
in Image Understanding", arXiv'2017.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1711.06475>`__
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
AIC keypoint indexes::
|
||||
|
||||
0: "right_shoulder",
|
||||
1: "right_elbow",
|
||||
2: "right_wrist",
|
||||
3: "left_shoulder",
|
||||
4: "left_elbow",
|
||||
5: "left_wrist",
|
||||
6: "right_hip",
|
||||
7: "right_knee",
|
||||
8: "right_ankle",
|
||||
9: "left_hip",
|
||||
10: "left_knee",
|
||||
11: "left_ankle",
|
||||
12: "head_top",
|
||||
13: "neck"
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/aic.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super(TopDownCocoDataset, self).__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.use_gt_bbox = data_cfg['use_gt_bbox']
|
||||
self.bbox_file = data_cfg['bbox_file']
|
||||
self.det_bbox_thr = data_cfg.get('det_bbox_thr', 0.0)
|
||||
self.use_nms = data_cfg.get('use_nms', True)
|
||||
self.soft_nms = data_cfg['soft_nms']
|
||||
self.nms_thr = data_cfg['nms_thr']
|
||||
self.oks_thr = data_cfg['oks_thr']
|
||||
self.vis_thr = data_cfg['vis_thr']
|
||||
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
assert self.use_gt_bbox
|
||||
gt_db = self._load_coco_keypoint_annotations()
|
||||
return gt_db
|
||||
|
||||
def _do_python_keypoint_eval(self, res_file):
|
||||
"""Keypoint evaluation using COCOAPI."""
|
||||
coco_det = self.coco.loadRes(res_file)
|
||||
coco_eval = COCOeval(
|
||||
self.coco, coco_det, 'keypoints', self.sigmas, use_area=False)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
stats_names = [
|
||||
'AP', 'AP .5', 'AP .75', 'AP (M)', 'AP (L)', 'AR', 'AR .5',
|
||||
'AR .75', 'AR (M)', 'AR (L)'
|
||||
]
|
||||
|
||||
info_str = list(zip(stats_names, coco_eval.stats))
|
||||
|
||||
return info_str
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from abc import ABCMeta
|
||||
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
|
||||
class TopDownBaseDataset(Dataset, metaclass=ABCMeta):
|
||||
"""This class has been deprecated and replaced by
|
||||
Kpt2dSviewRgbImgTopDownDataset."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
raise (ImportError(
|
||||
'TopDownBaseDataset has been replaced by '
|
||||
'Kpt2dSviewRgbImgTopDownDataset,'
|
||||
'check https://github.com/open-mmlab/mmpose/pull/663 for details.')
|
||||
)
|
||||
Vendored
+405
@@ -0,0 +1,405 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
from xtcocotools.cocoeval import COCOeval
|
||||
|
||||
from ....core.post_processing import oks_nms, soft_oks_nms
|
||||
from ...builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class TopDownCocoDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""CocoDataset dataset for top-down pose estimation.
|
||||
|
||||
"Microsoft COCO: Common Objects in Context", ECCV'2014.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1405.0312>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
COCO keypoint indexes::
|
||||
|
||||
0: 'nose',
|
||||
1: 'left_eye',
|
||||
2: 'right_eye',
|
||||
3: 'left_ear',
|
||||
4: 'right_ear',
|
||||
5: 'left_shoulder',
|
||||
6: 'right_shoulder',
|
||||
7: 'left_elbow',
|
||||
8: 'right_elbow',
|
||||
9: 'left_wrist',
|
||||
10: 'right_wrist',
|
||||
11: 'left_hip',
|
||||
12: 'right_hip',
|
||||
13: 'left_knee',
|
||||
14: 'right_knee',
|
||||
15: 'left_ankle',
|
||||
16: 'right_ankle'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/coco.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.use_gt_bbox = data_cfg['use_gt_bbox']
|
||||
self.bbox_file = data_cfg['bbox_file']
|
||||
self.det_bbox_thr = data_cfg.get('det_bbox_thr', 0.0)
|
||||
self.use_nms = data_cfg.get('use_nms', True)
|
||||
self.soft_nms = data_cfg['soft_nms']
|
||||
self.nms_thr = data_cfg['nms_thr']
|
||||
self.oks_thr = data_cfg['oks_thr']
|
||||
self.vis_thr = data_cfg['vis_thr']
|
||||
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
if (not self.test_mode) or self.use_gt_bbox:
|
||||
# use ground truth bbox
|
||||
gt_db = self._load_coco_keypoint_annotations()
|
||||
else:
|
||||
# use bbox from detection
|
||||
gt_db = self._load_coco_person_detection_results()
|
||||
return gt_db
|
||||
|
||||
def _load_coco_keypoint_annotations(self):
|
||||
"""Ground truth bbox and keypoints."""
|
||||
gt_db = []
|
||||
for img_id in self.img_ids:
|
||||
gt_db.extend(self._load_coco_keypoint_annotation_kernel(img_id))
|
||||
return gt_db
|
||||
|
||||
def _load_coco_keypoint_annotation_kernel(self, img_id):
|
||||
"""load annotation from COCOAPI.
|
||||
|
||||
Note:
|
||||
bbox:[x1, y1, w, h]
|
||||
|
||||
Args:
|
||||
img_id: coco image id
|
||||
|
||||
Returns:
|
||||
dict: db entry
|
||||
"""
|
||||
img_ann = self.coco.loadImgs(img_id)[0]
|
||||
width = img_ann['width']
|
||||
height = img_ann['height']
|
||||
num_joints = self.ann_info['num_joints']
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
# sanitize bboxes
|
||||
valid_objs = []
|
||||
for obj in objs:
|
||||
if 'bbox' not in obj:
|
||||
continue
|
||||
x, y, w, h = obj['bbox']
|
||||
x1 = max(0, x)
|
||||
y1 = max(0, y)
|
||||
x2 = min(width - 1, x1 + max(0, w - 1))
|
||||
y2 = min(height - 1, y1 + max(0, h - 1))
|
||||
if ('area' not in obj or obj['area'] > 0) and x2 > x1 and y2 > y1:
|
||||
obj['clean_bbox'] = [x1, y1, x2 - x1, y2 - y1]
|
||||
valid_objs.append(obj)
|
||||
objs = valid_objs
|
||||
|
||||
bbox_id = 0
|
||||
rec = []
|
||||
for obj in objs:
|
||||
if 'keypoints' not in obj:
|
||||
continue
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
if 'num_keypoints' in obj and obj['num_keypoints'] == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
center, scale = self._xywh2cs(*obj['clean_bbox'][:4])
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
rec.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'bbox': obj['clean_bbox'][:4],
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
|
||||
return rec
|
||||
|
||||
def _load_coco_person_detection_results(self):
|
||||
"""Load coco person detection results."""
|
||||
num_joints = self.ann_info['num_joints']
|
||||
all_boxes = None
|
||||
with open(self.bbox_file, 'r') as f:
|
||||
all_boxes = json.load(f)
|
||||
|
||||
if not all_boxes:
|
||||
raise ValueError('=> Load %s fail!' % self.bbox_file)
|
||||
|
||||
print(f'=> Total boxes: {len(all_boxes)}')
|
||||
|
||||
kpt_db = []
|
||||
bbox_id = 0
|
||||
for det_res in all_boxes:
|
||||
if det_res['category_id'] != 1:
|
||||
continue
|
||||
|
||||
image_file = osp.join(self.img_prefix,
|
||||
self.id2name[det_res['image_id']])
|
||||
box = det_res['bbox']
|
||||
score = det_res['score']
|
||||
|
||||
if score < self.det_bbox_thr:
|
||||
continue
|
||||
|
||||
center, scale = self._xywh2cs(*box[:4])
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.ones((num_joints, 3), dtype=np.float32)
|
||||
kpt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'bbox': box[:4],
|
||||
'bbox_score': score,
|
||||
'dataset': self.dataset_name,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
print(f'=> Total boxes after filter '
|
||||
f'low score@{self.det_bbox_thr}: {bbox_id}')
|
||||
return kpt_db
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='mAP', **kwargs):
|
||||
"""Evaluate coco keypoint results. The pose prediction results will be
|
||||
saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['data/coco/val2017\
|
||||
/000000393226.jpg']
|
||||
- heatmap (np.ndarray[N, K, H, W]): model output heatmap
|
||||
- bbox_id (list(int)).
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed. Defaults: 'mAP'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['mAP']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = defaultdict(list)
|
||||
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
kpts[image_id].append({
|
||||
'keypoints': preds[i],
|
||||
'center': boxes[i][0:2],
|
||||
'scale': boxes[i][2:4],
|
||||
'area': boxes[i][4],
|
||||
'score': boxes[i][5],
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
# rescoring and oks nms
|
||||
num_joints = self.ann_info['num_joints']
|
||||
vis_thr = self.vis_thr
|
||||
oks_thr = self.oks_thr
|
||||
valid_kpts = []
|
||||
for image_id in kpts.keys():
|
||||
img_kpts = kpts[image_id]
|
||||
for n_p in img_kpts:
|
||||
box_score = n_p['score']
|
||||
kpt_score = 0
|
||||
valid_num = 0
|
||||
for n_jt in range(0, num_joints):
|
||||
t_s = n_p['keypoints'][n_jt][2]
|
||||
if t_s > vis_thr:
|
||||
kpt_score = kpt_score + t_s
|
||||
valid_num = valid_num + 1
|
||||
if valid_num != 0:
|
||||
kpt_score = kpt_score / valid_num
|
||||
# rescoring
|
||||
n_p['score'] = kpt_score * box_score
|
||||
|
||||
if self.use_nms:
|
||||
nms = soft_oks_nms if self.soft_nms else oks_nms
|
||||
keep = nms(img_kpts, oks_thr, sigmas=self.sigmas)
|
||||
valid_kpts.append([img_kpts[_keep] for _keep in keep])
|
||||
else:
|
||||
valid_kpts.append(img_kpts)
|
||||
|
||||
self._write_coco_keypoint_results(valid_kpts, res_file)
|
||||
|
||||
info_str = self._do_python_keypoint_eval(res_file)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
|
||||
def _write_coco_keypoint_results(self, keypoints, res_file):
|
||||
"""Write results into a json file."""
|
||||
data_pack = [{
|
||||
'cat_id': self._class_to_coco_ind[cls],
|
||||
'cls_ind': cls_ind,
|
||||
'cls': cls,
|
||||
'ann_type': 'keypoints',
|
||||
'keypoints': keypoints
|
||||
} for cls_ind, cls in enumerate(self.classes)
|
||||
if not cls == '__background__']
|
||||
|
||||
results = self._coco_keypoint_results_one_category_kernel(data_pack[0])
|
||||
|
||||
with open(res_file, 'w') as f:
|
||||
json.dump(results, f, sort_keys=True, indent=4)
|
||||
|
||||
def _coco_keypoint_results_one_category_kernel(self, data_pack):
|
||||
"""Get coco keypoint results."""
|
||||
cat_id = data_pack['cat_id']
|
||||
keypoints = data_pack['keypoints']
|
||||
cat_results = []
|
||||
|
||||
for img_kpts in keypoints:
|
||||
if len(img_kpts) == 0:
|
||||
continue
|
||||
|
||||
_key_points = np.array(
|
||||
[img_kpt['keypoints'] for img_kpt in img_kpts])
|
||||
key_points = _key_points.reshape(-1,
|
||||
self.ann_info['num_joints'] * 3)
|
||||
|
||||
result = [{
|
||||
'image_id': img_kpt['image_id'],
|
||||
'category_id': cat_id,
|
||||
'keypoints': key_point.tolist(),
|
||||
'score': float(img_kpt['score']),
|
||||
'center': img_kpt['center'].tolist(),
|
||||
'scale': img_kpt['scale'].tolist()
|
||||
} for img_kpt, key_point in zip(img_kpts, key_points)]
|
||||
|
||||
cat_results.extend(result)
|
||||
|
||||
return cat_results
|
||||
|
||||
def _do_python_keypoint_eval(self, res_file):
|
||||
"""Keypoint evaluation using COCOAPI."""
|
||||
coco_det = self.coco.loadRes(res_file)
|
||||
coco_eval = COCOeval(self.coco, coco_det, 'keypoints', self.sigmas)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
stats_names = [
|
||||
'AP', 'AP .5', 'AP .75', 'AP (M)', 'AP (L)', 'AR', 'AR .5',
|
||||
'AR .75', 'AR (M)', 'AR (L)'
|
||||
]
|
||||
|
||||
info_str = list(zip(stats_names, coco_eval.stats))
|
||||
|
||||
return info_str
|
||||
|
||||
def _sort_and_unique_bboxes(self, kpts, key='bbox_id'):
|
||||
"""sort kpts and remove the repeated ones."""
|
||||
for img_id, persons in kpts.items():
|
||||
num = len(persons)
|
||||
kpts[img_id] = sorted(kpts[img_id], key=lambda x: x[key])
|
||||
for i in range(num - 1, 0, -1):
|
||||
if kpts[img_id][i][key] == kpts[img_id][i - 1][key]:
|
||||
del kpts[img_id][i]
|
||||
|
||||
return kpts
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config
|
||||
from xtcocotools.cocoeval import COCOeval
|
||||
|
||||
from ...builder import DATASETS
|
||||
from .topdown_coco_dataset import TopDownCocoDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class TopDownCocoWholeBodyDataset(TopDownCocoDataset):
|
||||
"""CocoWholeBodyDataset dataset for top-down pose estimation.
|
||||
|
||||
"Whole-Body Human Pose Estimation in the Wild", ECCV'2020.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/2007.11858>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
COCO-WholeBody keypoint indexes::
|
||||
|
||||
0-16: 17 body keypoints,
|
||||
17-22: 6 foot keypoints,
|
||||
23-90: 68 face keypoints,
|
||||
91-132: 42 hand keypoints
|
||||
|
||||
In total, we have 133 keypoints for wholebody pose estimation.
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/coco_wholebody.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super(TopDownCocoDataset, self).__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.use_gt_bbox = data_cfg['use_gt_bbox']
|
||||
self.bbox_file = data_cfg['bbox_file']
|
||||
self.det_bbox_thr = data_cfg.get('det_bbox_thr', 0.0)
|
||||
self.use_nms = data_cfg.get('use_nms', True)
|
||||
self.soft_nms = data_cfg['soft_nms']
|
||||
self.nms_thr = data_cfg['nms_thr']
|
||||
self.oks_thr = data_cfg['oks_thr']
|
||||
self.vis_thr = data_cfg['vis_thr']
|
||||
|
||||
self.body_num = 17
|
||||
self.foot_num = 6
|
||||
self.face_num = 68
|
||||
self.left_hand_num = 21
|
||||
self.right_hand_num = 21
|
||||
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _load_coco_keypoint_annotation_kernel(self, img_id):
|
||||
"""load annotation from COCOAPI.
|
||||
|
||||
Note:
|
||||
bbox:[x1, y1, w, h]
|
||||
Args:
|
||||
img_id: coco image id
|
||||
Returns:
|
||||
dict: db entry
|
||||
"""
|
||||
img_ann = self.coco.loadImgs(img_id)[0]
|
||||
width = img_ann['width']
|
||||
height = img_ann['height']
|
||||
num_joints = self.ann_info['num_joints']
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
# sanitize bboxes
|
||||
valid_objs = []
|
||||
for obj in objs:
|
||||
if 'bbox' not in obj:
|
||||
continue
|
||||
x, y, w, h = obj['bbox']
|
||||
x1 = max(0, x)
|
||||
y1 = max(0, y)
|
||||
x2 = min(width - 1, x1 + max(0, w - 1))
|
||||
y2 = min(height - 1, y1 + max(0, h - 1))
|
||||
if ('area' not in obj or obj['area'] > 0) and x2 > x1 and y2 > y1:
|
||||
obj['clean_bbox'] = [x1, y1, x2 - x1, y2 - y1]
|
||||
valid_objs.append(obj)
|
||||
objs = valid_objs
|
||||
|
||||
rec = []
|
||||
bbox_id = 0
|
||||
for obj in objs:
|
||||
if 'keypoints' not in obj:
|
||||
continue
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints'] + obj['foot_kpts'] +
|
||||
obj['face_kpts'] + obj['lefthand_kpts'] +
|
||||
obj['righthand_kpts']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3] > 0)
|
||||
|
||||
center, scale = self._xywh2cs(*obj['clean_bbox'][:4])
|
||||
|
||||
image_file = os.path.join(self.img_prefix, self.id2name[img_id])
|
||||
rec.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
|
||||
return rec
|
||||
|
||||
def _coco_keypoint_results_one_category_kernel(self, data_pack):
|
||||
"""Get coco keypoint results."""
|
||||
cat_id = data_pack['cat_id']
|
||||
keypoints = data_pack['keypoints']
|
||||
cat_results = []
|
||||
|
||||
for img_kpts in keypoints:
|
||||
if len(img_kpts) == 0:
|
||||
continue
|
||||
|
||||
_key_points = np.array(
|
||||
[img_kpt['keypoints'] for img_kpt in img_kpts])
|
||||
key_points = _key_points.reshape(-1,
|
||||
self.ann_info['num_joints'] * 3)
|
||||
|
||||
cuts = np.cumsum([
|
||||
0, self.body_num, self.foot_num, self.face_num,
|
||||
self.left_hand_num, self.right_hand_num
|
||||
]) * 3
|
||||
|
||||
result = [{
|
||||
'image_id': img_kpt['image_id'],
|
||||
'category_id': cat_id,
|
||||
'keypoints': key_point[cuts[0]:cuts[1]].tolist(),
|
||||
'foot_kpts': key_point[cuts[1]:cuts[2]].tolist(),
|
||||
'face_kpts': key_point[cuts[2]:cuts[3]].tolist(),
|
||||
'lefthand_kpts': key_point[cuts[3]:cuts[4]].tolist(),
|
||||
'righthand_kpts': key_point[cuts[4]:cuts[5]].tolist(),
|
||||
'score': float(img_kpt['score']),
|
||||
'center': img_kpt['center'].tolist(),
|
||||
'scale': img_kpt['scale'].tolist()
|
||||
} for img_kpt, key_point in zip(img_kpts, key_points)]
|
||||
|
||||
cat_results.extend(result)
|
||||
|
||||
return cat_results
|
||||
|
||||
def _do_python_keypoint_eval(self, res_file):
|
||||
"""Keypoint evaluation using COCOAPI."""
|
||||
coco_det = self.coco.loadRes(res_file)
|
||||
|
||||
cuts = np.cumsum([
|
||||
0, self.body_num, self.foot_num, self.face_num, self.left_hand_num,
|
||||
self.right_hand_num
|
||||
])
|
||||
|
||||
coco_eval = COCOeval(
|
||||
self.coco,
|
||||
coco_det,
|
||||
'keypoints_body',
|
||||
self.sigmas[cuts[0]:cuts[1]],
|
||||
use_area=True)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
coco_eval = COCOeval(
|
||||
self.coco,
|
||||
coco_det,
|
||||
'keypoints_foot',
|
||||
self.sigmas[cuts[1]:cuts[2]],
|
||||
use_area=True)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
coco_eval = COCOeval(
|
||||
self.coco,
|
||||
coco_det,
|
||||
'keypoints_face',
|
||||
self.sigmas[cuts[2]:cuts[3]],
|
||||
use_area=True)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
coco_eval = COCOeval(
|
||||
self.coco,
|
||||
coco_det,
|
||||
'keypoints_lefthand',
|
||||
self.sigmas[cuts[3]:cuts[4]],
|
||||
use_area=True)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
coco_eval = COCOeval(
|
||||
self.coco,
|
||||
coco_det,
|
||||
'keypoints_righthand',
|
||||
self.sigmas[cuts[4]:cuts[5]],
|
||||
use_area=True)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
coco_eval = COCOeval(
|
||||
self.coco,
|
||||
coco_det,
|
||||
'keypoints_wholebody',
|
||||
self.sigmas,
|
||||
use_area=True)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
stats_names = [
|
||||
'AP', 'AP .5', 'AP .75', 'AP (M)', 'AP (L)', 'AR', 'AR .5',
|
||||
'AR .75', 'AR (M)', 'AR (L)'
|
||||
]
|
||||
|
||||
info_str = list(zip(stats_names, coco_eval.stats))
|
||||
|
||||
return info_str
|
||||
third_party/hamer/third-party/ViTPose/mmpose/datasets/datasets/top_down/topdown_crowdpose_dataset.py
Vendored
+110
@@ -0,0 +1,110 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
from mmcv import Config
|
||||
from xtcocotools.cocoeval import COCOeval
|
||||
|
||||
from ...builder import DATASETS
|
||||
from .topdown_coco_dataset import TopDownCocoDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class TopDownCrowdPoseDataset(TopDownCocoDataset):
|
||||
"""CrowdPoseDataset dataset for top-down pose estimation.
|
||||
|
||||
"CrowdPose: Efficient Crowded Scenes Pose Estimation and
|
||||
A New Benchmark", CVPR'2019.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1812.00324>`__.
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
CrowdPose keypoint indexes::
|
||||
|
||||
0: 'left_shoulder',
|
||||
1: 'right_shoulder',
|
||||
2: 'left_elbow',
|
||||
3: 'right_elbow',
|
||||
4: 'left_wrist',
|
||||
5: 'right_wrist',
|
||||
6: 'left_hip',
|
||||
7: 'right_hip',
|
||||
8: 'left_knee',
|
||||
9: 'right_knee',
|
||||
10: 'left_ankle',
|
||||
11: 'right_ankle',
|
||||
12: 'top_head',
|
||||
13: 'neck'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/crowdpose.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super(TopDownCocoDataset, self).__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.use_gt_bbox = data_cfg['use_gt_bbox']
|
||||
self.bbox_file = data_cfg['bbox_file']
|
||||
self.det_bbox_thr = data_cfg.get('det_bbox_thr', 0.0)
|
||||
self.use_nms = data_cfg.get('use_nms', True)
|
||||
self.soft_nms = data_cfg['soft_nms']
|
||||
self.nms_thr = data_cfg['nms_thr']
|
||||
self.oks_thr = data_cfg['oks_thr']
|
||||
self.vis_thr = data_cfg['vis_thr']
|
||||
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _do_python_keypoint_eval(self, res_file):
|
||||
"""Keypoint evaluation using COCOAPI."""
|
||||
coco_det = self.coco.loadRes(res_file)
|
||||
coco_eval = COCOeval(
|
||||
self.coco,
|
||||
coco_det,
|
||||
'keypoints_crowd',
|
||||
self.sigmas,
|
||||
use_area=False)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
stats_names = [
|
||||
'AP', 'AP .5', 'AP .75', 'AR', 'AR .5', 'AR .75', 'AP(E)', 'AP(M)',
|
||||
'AP(H)'
|
||||
]
|
||||
|
||||
info_str = list(zip(stats_names, coco_eval.stats))
|
||||
|
||||
return info_str
|
||||
Vendored
+206
@@ -0,0 +1,206 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from ...builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class TopDownH36MDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""Human3.6M dataset for top-down 2D pose estimation.
|
||||
|
||||
"Human3.6M: Large Scale Datasets and Predictive Methods for 3D Human
|
||||
Sensing in Natural Environments", TPAMI`2014.
|
||||
More details can be found in the `paper
|
||||
<http://vision.imar.ro/human3.6m/pami-h36m.pdf>`__.
|
||||
|
||||
Human3.6M keypoint indexes::
|
||||
|
||||
0: 'root (pelvis)',
|
||||
1: 'right_hip',
|
||||
2: 'right_knee',
|
||||
3: 'right_foot',
|
||||
4: 'left_hip',
|
||||
5: 'left_knee',
|
||||
6: 'left_foot',
|
||||
7: 'spine',
|
||||
8: 'thorax',
|
||||
9: 'neck_base',
|
||||
10: 'head',
|
||||
11: 'left_shoulder',
|
||||
12: 'left_elbow',
|
||||
13: 'left_wrist',
|
||||
14: 'right_shoulder',
|
||||
15: 'right_elbow',
|
||||
16: 'right_wrist'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/h36m.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
num_joints = self.ann_info['num_joints']
|
||||
for img_id in self.img_ids:
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
for obj in objs:
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
# use 1.25 padded bbox as input
|
||||
center, scale = self._xywh2cs(*obj['bbox'][:4])
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox': obj['bbox'],
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='PCK', **kwargs):
|
||||
"""Evaluate human3.6m 2d keypoint results. The pose prediction results
|
||||
will be saved in `${res_folder}/result_keypoints.json`.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0],
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['data/coco/val2017
|
||||
/000000393226.jpg']
|
||||
- heatmap (np.ndarray[N, K, H, W]): model output heatmap
|
||||
- bbox_id (list(int)).
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed. Defaults: 'PCK'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['PCK', 'EPE']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
|
||||
@staticmethod
|
||||
def _write_keypoint_results(keypoints, res_file):
|
||||
"""Write results into a json file."""
|
||||
|
||||
with open(res_file, 'w') as f:
|
||||
json.dump(keypoints, f, sort_keys=True, indent=4)
|
||||
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
from mmcv import Config
|
||||
|
||||
from ...builder import DATASETS
|
||||
from .topdown_coco_dataset import TopDownCocoDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class TopDownHalpeDataset(TopDownCocoDataset):
|
||||
"""HalpeDataset for top-down pose estimation.
|
||||
|
||||
'https://github.com/Fang-Haoshu/Halpe-FullBody'
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
Halpe keypoint indexes::
|
||||
|
||||
0-19: 20 body keypoints,
|
||||
20-25: 6 foot keypoints,
|
||||
26-93: 68 face keypoints,
|
||||
94-135: 42 hand keypoints
|
||||
|
||||
In total, we have 136 keypoints for wholebody pose estimation.
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/halpe.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super(TopDownCocoDataset, self).__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.use_gt_bbox = data_cfg['use_gt_bbox']
|
||||
self.bbox_file = data_cfg['bbox_file']
|
||||
self.det_bbox_thr = data_cfg.get('det_bbox_thr', 0.0)
|
||||
self.use_nms = data_cfg.get('use_nms', True)
|
||||
self.soft_nms = data_cfg['soft_nms']
|
||||
self.nms_thr = data_cfg['nms_thr']
|
||||
self.oks_thr = data_cfg['oks_thr']
|
||||
self.vis_thr = data_cfg['vis_thr']
|
||||
|
||||
self.ann_info['use_different_joint_weights'] = False
|
||||
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
Vendored
+361
@@ -0,0 +1,361 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.core.evaluation.top_down_eval import keypoint_pck_accuracy
|
||||
from ...builder import DATASETS
|
||||
from .topdown_coco_dataset import TopDownCocoDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class TopDownJhmdbDataset(TopDownCocoDataset):
|
||||
"""JhmdbDataset dataset for top-down pose estimation.
|
||||
|
||||
"Towards understanding action recognition", ICCV'2013.
|
||||
More details can be found in the `paper
|
||||
<https://openaccess.thecvf.com/content_iccv_2013/papers/\
|
||||
Jhuang_Towards_Understanding_Action_2013_ICCV_paper.pdf>`__
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
sub-JHMDB keypoint indexes::
|
||||
|
||||
0: "neck",
|
||||
1: "belly",
|
||||
2: "head",
|
||||
3: "right_shoulder",
|
||||
4: "left_shoulder",
|
||||
5: "right_hip",
|
||||
6: "left_hip",
|
||||
7: "right_elbow",
|
||||
8: "left_elbow",
|
||||
9: "right_knee",
|
||||
10: "left_knee",
|
||||
11: "right_wrist",
|
||||
12: "left_wrist",
|
||||
13: "right_ankle",
|
||||
14: "left_ankle"
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/jhmdb.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super(TopDownCocoDataset, self).__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.use_gt_bbox = data_cfg['use_gt_bbox']
|
||||
self.bbox_file = data_cfg['bbox_file']
|
||||
self.det_bbox_thr = data_cfg.get('det_bbox_thr', 0.0)
|
||||
self.soft_nms = data_cfg['soft_nms']
|
||||
self.nms_thr = data_cfg['nms_thr']
|
||||
self.oks_thr = data_cfg['oks_thr']
|
||||
self.vis_thr = data_cfg['vis_thr']
|
||||
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
assert self.use_gt_bbox
|
||||
gt_db = self._load_coco_keypoint_annotations()
|
||||
return gt_db
|
||||
|
||||
def _load_coco_keypoint_annotation_kernel(self, img_id):
|
||||
"""load annotation from COCOAPI.
|
||||
|
||||
Note:
|
||||
bbox:[x1, y1, w, h]
|
||||
Args:
|
||||
img_id: coco image id
|
||||
Returns:
|
||||
dict: db entry
|
||||
"""
|
||||
img_ann = self.coco.loadImgs(img_id)[0]
|
||||
width = img_ann['width']
|
||||
height = img_ann['height']
|
||||
num_joints = self.ann_info['num_joints']
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
# sanitize bboxes
|
||||
valid_objs = []
|
||||
for obj in objs:
|
||||
if 'bbox' not in obj:
|
||||
continue
|
||||
x, y, w, h = obj['bbox']
|
||||
# JHMDB uses matlab format, index is 1-based,
|
||||
# we should first convert to 0-based index
|
||||
x -= 1
|
||||
y -= 1
|
||||
x1 = max(0, x)
|
||||
y1 = max(0, y)
|
||||
x2 = min(width - 1, x1 + max(0, w - 1))
|
||||
y2 = min(height - 1, y1 + max(0, h - 1))
|
||||
if ('area' not in obj or obj['area'] > 0) and x2 > x1 and y2 > y1:
|
||||
obj['clean_bbox'] = [x1, y1, x2 - x1, y2 - y1]
|
||||
valid_objs.append(obj)
|
||||
objs = valid_objs
|
||||
|
||||
rec = []
|
||||
bbox_id = 0
|
||||
for obj in objs:
|
||||
if 'keypoints' not in obj:
|
||||
continue
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
if 'num_keypoints' in obj and obj['num_keypoints'] == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
|
||||
# JHMDB uses matlab format, index is 1-based,
|
||||
# we should first convert to 0-based index
|
||||
joints_3d[:, :2] = keypoints[:, :2] - 1
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
center, scale = self._xywh2cs(*obj['clean_bbox'][:4])
|
||||
|
||||
image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
rec.append({
|
||||
'image_file': image_file,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'bbox': obj['clean_bbox'][:4],
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox_score': 1,
|
||||
'bbox_id': f'{img_id}_{bbox_id:03}'
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
|
||||
return rec
|
||||
|
||||
def _write_keypoint_results(self, keypoints, res_file):
|
||||
"""Write results into a json file."""
|
||||
|
||||
with open(res_file, 'w') as f:
|
||||
json.dump(keypoints, f, sort_keys=True, indent=4)
|
||||
|
||||
def _report_metric(self, res_file, metrics, pck_thr=0.2):
|
||||
"""Keypoint evaluation.
|
||||
|
||||
Args:
|
||||
res_file (str): Json file stored prediction results.
|
||||
metrics (str | list[str]): Metric to be performed.
|
||||
Options: 'PCK', 'PCKh', 'AUC', 'EPE'.
|
||||
pck_thr (float): PCK threshold, default as 0.2.
|
||||
pckh_thr (float): PCKh threshold, default as 0.7.
|
||||
auc_nor (float): AUC normalization factor, default as 30 pixel.
|
||||
|
||||
Returns:
|
||||
List: Evaluation results for evaluation metric.
|
||||
"""
|
||||
info_str = []
|
||||
|
||||
with open(res_file, 'r') as fin:
|
||||
preds = json.load(fin)
|
||||
assert len(preds) == len(self.db)
|
||||
|
||||
outputs = []
|
||||
gts = []
|
||||
masks = []
|
||||
threshold_bbox = []
|
||||
threshold_torso = []
|
||||
|
||||
for pred, item in zip(preds, self.db):
|
||||
outputs.append(np.array(pred['keypoints'])[:, :-1])
|
||||
gts.append(np.array(item['joints_3d'])[:, :-1])
|
||||
masks.append((np.array(item['joints_3d_visible'])[:, 0]) > 0)
|
||||
if 'PCK' in metrics:
|
||||
bbox = np.array(item['bbox'])
|
||||
bbox_thr = np.max(bbox[2:])
|
||||
threshold_bbox.append(np.array([bbox_thr, bbox_thr]))
|
||||
|
||||
if 'tPCK' in metrics:
|
||||
torso_thr = np.linalg.norm(item['joints_3d'][4, :2] -
|
||||
item['joints_3d'][5, :2])
|
||||
if torso_thr < 1:
|
||||
torso_thr = np.linalg.norm(
|
||||
np.array(pred['keypoints'])[4, :2] -
|
||||
np.array(pred['keypoints'])[5, :2])
|
||||
warnings.warn('Torso Size < 1.')
|
||||
threshold_torso.append(np.array([torso_thr, torso_thr]))
|
||||
|
||||
outputs = np.array(outputs)
|
||||
gts = np.array(gts)
|
||||
masks = np.array(masks)
|
||||
threshold_bbox = np.array(threshold_bbox)
|
||||
threshold_torso = np.array(threshold_torso)
|
||||
|
||||
if 'PCK' in metrics:
|
||||
pck_p, pck, _ = keypoint_pck_accuracy(outputs, gts, masks, pck_thr,
|
||||
threshold_bbox)
|
||||
|
||||
stats_names = [
|
||||
'Head PCK', 'Sho PCK', 'Elb PCK', 'Wri PCK', 'Hip PCK',
|
||||
'Knee PCK', 'Ank PCK', 'Mean PCK'
|
||||
]
|
||||
|
||||
stats = [
|
||||
pck_p[2], 0.5 * pck_p[3] + 0.5 * pck_p[4],
|
||||
0.5 * pck_p[7] + 0.5 * pck_p[8],
|
||||
0.5 * pck_p[11] + 0.5 * pck_p[12],
|
||||
0.5 * pck_p[5] + 0.5 * pck_p[6],
|
||||
0.5 * pck_p[9] + 0.5 * pck_p[10],
|
||||
0.5 * pck_p[13] + 0.5 * pck_p[14], pck
|
||||
]
|
||||
|
||||
info_str.extend(list(zip(stats_names, stats)))
|
||||
|
||||
if 'tPCK' in metrics:
|
||||
pck_p, pck, _ = keypoint_pck_accuracy(outputs, gts, masks, pck_thr,
|
||||
threshold_torso)
|
||||
|
||||
stats_names = [
|
||||
'Head tPCK', 'Sho tPCK', 'Elb tPCK', 'Wri tPCK', 'Hip tPCK',
|
||||
'Knee tPCK', 'Ank tPCK', 'Mean tPCK'
|
||||
]
|
||||
|
||||
stats = [
|
||||
pck_p[2], 0.5 * pck_p[3] + 0.5 * pck_p[4],
|
||||
0.5 * pck_p[7] + 0.5 * pck_p[8],
|
||||
0.5 * pck_p[11] + 0.5 * pck_p[12],
|
||||
0.5 * pck_p[5] + 0.5 * pck_p[6],
|
||||
0.5 * pck_p[9] + 0.5 * pck_p[10],
|
||||
0.5 * pck_p[13] + 0.5 * pck_p[14], pck
|
||||
]
|
||||
|
||||
info_str.extend(list(zip(stats_names, stats)))
|
||||
|
||||
return info_str
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='PCK', **kwargs):
|
||||
"""Evaluate onehand10k keypoint results. The pose prediction results
|
||||
will be saved in `${res_folder}/result_keypoints.json`.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_path (list[str])
|
||||
- output_heatmap (np.ndarray[N, K, H, W]): model outputs.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed.
|
||||
Options: 'PCK', 'tPCK'.
|
||||
PCK means normalized by the bounding boxes, while tPCK
|
||||
means normalized by the torso size.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['PCK', 'tPCK']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
# convert 0-based index to 1-based index,
|
||||
# and get the first two dimensions.
|
||||
preds[..., :2] += 1.0
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
kpts.append({
|
||||
'keypoints': preds[i],
|
||||
'center': boxes[i][0:2],
|
||||
'scale': boxes[i][2:4],
|
||||
'area': boxes[i][4],
|
||||
'score': boxes[i][5],
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file, metrics)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
|
||||
def _sort_and_unique_bboxes(self, kpts, key='bbox_id'):
|
||||
"""sort kpts and remove the repeated ones."""
|
||||
kpts = sorted(kpts, key=lambda x: x[key])
|
||||
num = len(kpts)
|
||||
for i in range(num - 1, 0, -1):
|
||||
if kpts[i][key] == kpts[i - 1][key]:
|
||||
del kpts[i]
|
||||
|
||||
return kpts
|
||||
Vendored
+125
@@ -0,0 +1,125 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
from mmcv import Config
|
||||
from xtcocotools.cocoeval import COCOeval
|
||||
|
||||
from ...builder import DATASETS
|
||||
from .topdown_coco_dataset import TopDownCocoDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class TopDownMhpDataset(TopDownCocoDataset):
|
||||
"""MHPv2.0 dataset for top-down pose estimation.
|
||||
|
||||
"Understanding Humans in Crowded Scenes: Deep Nested Adversarial
|
||||
Learning and A New Benchmark for Multi-Human Parsing", ACM MM'2018.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1804.03287>`__
|
||||
|
||||
Note that, the evaluation metric used here is mAP (adapted from COCO),
|
||||
which may be different from the official evaluation codes.
|
||||
'https://github.com/ZhaoJ9014/Multi-Human-Parsing/tree/master/'
|
||||
'Evaluation/Multi-Human-Pose'
|
||||
Please be cautious if you use the results in papers.
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
MHP keypoint indexes::
|
||||
|
||||
0: "right ankle",
|
||||
1: "right knee",
|
||||
2: "right hip",
|
||||
3: "left hip",
|
||||
4: "left knee",
|
||||
5: "left ankle",
|
||||
6: "pelvis",
|
||||
7: "thorax",
|
||||
8: "upper neck",
|
||||
9: "head top",
|
||||
10: "right wrist",
|
||||
11: "right elbow",
|
||||
12: "right shoulder",
|
||||
13: "left shoulder",
|
||||
14: "left elbow",
|
||||
15: "left wrist",
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/mhp.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super(TopDownCocoDataset, self).__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.use_gt_bbox = data_cfg['use_gt_bbox']
|
||||
self.bbox_file = data_cfg['bbox_file']
|
||||
self.det_bbox_thr = data_cfg.get('det_bbox_thr', 0.0)
|
||||
if 'image_thr' in data_cfg:
|
||||
warnings.warn(
|
||||
'image_thr is deprecated, '
|
||||
'please use det_bbox_thr instead', DeprecationWarning)
|
||||
self.det_bbox_thr = data_cfg['image_thr']
|
||||
self.use_nms = data_cfg.get('use_nms', True)
|
||||
self.soft_nms = data_cfg['soft_nms']
|
||||
self.nms_thr = data_cfg['nms_thr']
|
||||
self.oks_thr = data_cfg['oks_thr']
|
||||
self.vis_thr = data_cfg['vis_thr']
|
||||
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
assert self.use_gt_bbox
|
||||
gt_db = self._load_coco_keypoint_annotations()
|
||||
return gt_db
|
||||
|
||||
def _do_python_keypoint_eval(self, res_file):
|
||||
"""Keypoint evaluation using COCOAPI."""
|
||||
coco_det = self.coco.loadRes(res_file)
|
||||
coco_eval = COCOeval(
|
||||
self.coco, coco_det, 'keypoints', self.sigmas, use_area=False)
|
||||
coco_eval.params.useSegm = None
|
||||
coco_eval.evaluate()
|
||||
coco_eval.accumulate()
|
||||
coco_eval.summarize()
|
||||
|
||||
stats_names = [
|
||||
'AP', 'AP .5', 'AP .75', 'AP (M)', 'AP (L)', 'AR', 'AR .5',
|
||||
'AR .75', 'AR (M)', 'AR (L)'
|
||||
]
|
||||
|
||||
info_str = list(zip(stats_names, coco_eval.stats))
|
||||
|
||||
return info_str
|
||||
Vendored
+275
@@ -0,0 +1,275 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import json
|
||||
import os.path as osp
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
from scipy.io import loadmat, savemat
|
||||
|
||||
from ...builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class TopDownMpiiDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""MPII Dataset for top-down pose estimation.
|
||||
|
||||
"2D Human Pose Estimation: New Benchmark and State of the Art Analysis"
|
||||
,CVPR'2014. More details can be found in the `paper
|
||||
<http://human-pose.mpi-inf.mpg.de/contents/andriluka14cvpr.pdf>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
MPII keypoint indexes::
|
||||
|
||||
0: 'right_ankle'
|
||||
1: 'right_knee',
|
||||
2: 'right_hip',
|
||||
3: 'left_hip',
|
||||
4: 'left_knee',
|
||||
5: 'left_ankle',
|
||||
6: 'pelvis',
|
||||
7: 'thorax',
|
||||
8: 'upper_neck',
|
||||
9: 'head_top',
|
||||
10: 'right_wrist',
|
||||
11: 'right_elbow',
|
||||
12: 'right_shoulder',
|
||||
13: 'left_shoulder',
|
||||
14: 'left_elbow',
|
||||
15: 'left_wrist'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/mpii.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
coco_style=False,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.db = self._get_db()
|
||||
self.image_set = set(x['image_file'] for x in self.db)
|
||||
self.num_images = len(self.image_set)
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
# create train/val split
|
||||
with open(self.ann_file) as anno_file:
|
||||
anno = json.load(anno_file)
|
||||
|
||||
gt_db = []
|
||||
bbox_id = 0
|
||||
for a in anno:
|
||||
image_name = a['image']
|
||||
|
||||
center = np.array(a['center'], dtype=np.float32)
|
||||
scale = np.array([a['scale'], a['scale']], dtype=np.float32)
|
||||
|
||||
# Adjust center/scale slightly to avoid cropping limbs
|
||||
if center[0] != -1:
|
||||
center[1] = center[1] + 15 * scale[1]
|
||||
# padding to include proper amount of context
|
||||
scale = scale * 1.25
|
||||
|
||||
# MPII uses matlab format, index is 1-based,
|
||||
# we should first convert to 0-based index
|
||||
center = center - 1
|
||||
|
||||
joints_3d = np.zeros((self.ann_info['num_joints'], 3),
|
||||
dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((self.ann_info['num_joints'], 3),
|
||||
dtype=np.float32)
|
||||
if not self.test_mode:
|
||||
joints = np.array(a['joints'])
|
||||
joints_vis = np.array(a['joints_vis'])
|
||||
assert len(joints) == self.ann_info['num_joints'], \
|
||||
f'joint num diff: {len(joints)}' + \
|
||||
f' vs {self.ann_info["num_joints"]}'
|
||||
|
||||
joints_3d[:, 0:2] = joints[:, 0:2] - 1
|
||||
joints_3d_visible[:, :2] = joints_vis[:, None]
|
||||
image_file = osp.join(self.img_prefix, image_name)
|
||||
gt_db.append({
|
||||
'image_file': image_file,
|
||||
'bbox_id': bbox_id,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox_score': 1
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='PCKh', **kwargs):
|
||||
"""Evaluate PCKh for MPII dataset. Adapted from
|
||||
https://github.com/leoxiaobin/deep-high-resolution-net.pytorch
|
||||
Copyright (c) Microsoft, under the MIT License.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['/val2017/000000\
|
||||
397133.jpg']
|
||||
- heatmap (np.ndarray[N, K, H, W]): model output heatmap.
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. Default: None.
|
||||
metric (str | list[str]): Metrics to be performed.
|
||||
Defaults: 'PCKh'.
|
||||
|
||||
Returns:
|
||||
dict: PCKh for each joint
|
||||
"""
|
||||
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['PCKh']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
bbox_ids = result['bbox_ids']
|
||||
batch_size = len(bbox_ids)
|
||||
for i in range(batch_size):
|
||||
kpts.append({'keypoints': preds[i], 'bbox_id': bbox_ids[i]})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
preds = np.stack([kpt['keypoints'] for kpt in kpts])
|
||||
|
||||
# convert 0-based index to 1-based index,
|
||||
# and get the first two dimensions.
|
||||
preds = preds[..., :2] + 1.0
|
||||
|
||||
if res_folder:
|
||||
pred_file = osp.join(res_folder, 'pred.mat')
|
||||
savemat(pred_file, mdict={'preds': preds})
|
||||
|
||||
SC_BIAS = 0.6
|
||||
threshold = 0.5
|
||||
|
||||
gt_file = osp.join(osp.dirname(self.ann_file), 'mpii_gt_val.mat')
|
||||
gt_dict = loadmat(gt_file)
|
||||
dataset_joints = gt_dict['dataset_joints']
|
||||
jnt_missing = gt_dict['jnt_missing']
|
||||
pos_gt_src = gt_dict['pos_gt_src']
|
||||
headboxes_src = gt_dict['headboxes_src']
|
||||
|
||||
pos_pred_src = np.transpose(preds, [1, 2, 0])
|
||||
|
||||
head = np.where(dataset_joints == 'head')[1][0]
|
||||
lsho = np.where(dataset_joints == 'lsho')[1][0]
|
||||
lelb = np.where(dataset_joints == 'lelb')[1][0]
|
||||
lwri = np.where(dataset_joints == 'lwri')[1][0]
|
||||
lhip = np.where(dataset_joints == 'lhip')[1][0]
|
||||
lkne = np.where(dataset_joints == 'lkne')[1][0]
|
||||
lank = np.where(dataset_joints == 'lank')[1][0]
|
||||
|
||||
rsho = np.where(dataset_joints == 'rsho')[1][0]
|
||||
relb = np.where(dataset_joints == 'relb')[1][0]
|
||||
rwri = np.where(dataset_joints == 'rwri')[1][0]
|
||||
rkne = np.where(dataset_joints == 'rkne')[1][0]
|
||||
rank = np.where(dataset_joints == 'rank')[1][0]
|
||||
rhip = np.where(dataset_joints == 'rhip')[1][0]
|
||||
|
||||
jnt_visible = 1 - jnt_missing
|
||||
uv_error = pos_pred_src - pos_gt_src
|
||||
uv_err = np.linalg.norm(uv_error, axis=1)
|
||||
headsizes = headboxes_src[1, :, :] - headboxes_src[0, :, :]
|
||||
headsizes = np.linalg.norm(headsizes, axis=0)
|
||||
headsizes *= SC_BIAS
|
||||
scale = headsizes * np.ones((len(uv_err), 1), dtype=np.float32)
|
||||
scaled_uv_err = uv_err / scale
|
||||
scaled_uv_err = scaled_uv_err * jnt_visible
|
||||
jnt_count = np.sum(jnt_visible, axis=1)
|
||||
less_than_threshold = (scaled_uv_err <= threshold) * jnt_visible
|
||||
PCKh = 100. * np.sum(less_than_threshold, axis=1) / jnt_count
|
||||
|
||||
# save
|
||||
rng = np.arange(0, 0.5 + 0.01, 0.01)
|
||||
pckAll = np.zeros((len(rng), 16), dtype=np.float32)
|
||||
|
||||
for r, threshold in enumerate(rng):
|
||||
less_than_threshold = (scaled_uv_err <= threshold) * jnt_visible
|
||||
pckAll[r, :] = 100. * np.sum(
|
||||
less_than_threshold, axis=1) / jnt_count
|
||||
|
||||
PCKh = np.ma.array(PCKh, mask=False)
|
||||
PCKh.mask[6:8] = True
|
||||
|
||||
jnt_count = np.ma.array(jnt_count, mask=False)
|
||||
jnt_count.mask[6:8] = True
|
||||
jnt_ratio = jnt_count / np.sum(jnt_count).astype(np.float64)
|
||||
|
||||
name_value = [('Head', PCKh[head]),
|
||||
('Shoulder', 0.5 * (PCKh[lsho] + PCKh[rsho])),
|
||||
('Elbow', 0.5 * (PCKh[lelb] + PCKh[relb])),
|
||||
('Wrist', 0.5 * (PCKh[lwri] + PCKh[rwri])),
|
||||
('Hip', 0.5 * (PCKh[lhip] + PCKh[rhip])),
|
||||
('Knee', 0.5 * (PCKh[lkne] + PCKh[rkne])),
|
||||
('Ankle', 0.5 * (PCKh[lank] + PCKh[rank])),
|
||||
('PCKh', np.sum(PCKh * jnt_ratio)),
|
||||
('PCKh@0.1', np.sum(pckAll[10, :] * jnt_ratio))]
|
||||
name_value = OrderedDict(name_value)
|
||||
|
||||
return name_value
|
||||
|
||||
def _sort_and_unique_bboxes(self, kpts, key='bbox_id'):
|
||||
"""sort kpts and remove the repeated ones."""
|
||||
kpts = sorted(kpts, key=lambda x: x[key])
|
||||
num = len(kpts)
|
||||
for i in range(num - 1, 0, -1):
|
||||
if kpts[i][key] == kpts[i - 1][key]:
|
||||
del kpts[i]
|
||||
|
||||
return kpts
|
||||
Vendored
+310
@@ -0,0 +1,310 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy as cp
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from mmpose.datasets.builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbImgTopDownDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class TopDownMpiiTrbDataset(Kpt2dSviewRgbImgTopDownDataset):
|
||||
"""MPII-TRB Dataset dataset for top-down pose estimation.
|
||||
|
||||
"TRB: A Novel Triplet Representation for Understanding 2D Human Body",
|
||||
ICCV'2019. More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1910.11535>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
MPII-TRB keypoint indexes::
|
||||
|
||||
0: 'left_shoulder'
|
||||
1: 'right_shoulder'
|
||||
2: 'left_elbow'
|
||||
3: 'right_elbow'
|
||||
4: 'left_wrist'
|
||||
5: 'right_wrist'
|
||||
6: 'left_hip'
|
||||
7: 'right_hip'
|
||||
8: 'left_knee'
|
||||
9: 'right_knee'
|
||||
10: 'left_ankle'
|
||||
11: 'right_ankle'
|
||||
12: 'head'
|
||||
13: 'neck'
|
||||
|
||||
14: 'right_neck'
|
||||
15: 'left_neck'
|
||||
16: 'medial_right_shoulder'
|
||||
17: 'lateral_right_shoulder'
|
||||
18: 'medial_right_bow'
|
||||
19: 'lateral_right_bow'
|
||||
20: 'medial_right_wrist'
|
||||
21: 'lateral_right_wrist'
|
||||
22: 'medial_left_shoulder'
|
||||
23: 'lateral_left_shoulder'
|
||||
24: 'medial_left_bow'
|
||||
25: 'lateral_left_bow'
|
||||
26: 'medial_left_wrist'
|
||||
27: 'lateral_left_wrist'
|
||||
28: 'medial_right_hip'
|
||||
29: 'lateral_right_hip'
|
||||
30: 'medial_right_knee'
|
||||
31: 'lateral_right_knee'
|
||||
32: 'medial_right_ankle'
|
||||
33: 'lateral_right_ankle'
|
||||
34: 'medial_left_hip'
|
||||
35: 'lateral_left_hip'
|
||||
36: 'medial_left_knee'
|
||||
37: 'lateral_left_knee'
|
||||
38: 'medial_left_ankle'
|
||||
39: 'lateral_left_ankle'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/mpii_trb.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.db = self._get_db(ann_file)
|
||||
self.image_set = set(x['image_file'] for x in self.db)
|
||||
self.num_images = len(self.image_set)
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self, ann_file):
|
||||
"""Load dataset."""
|
||||
with open(ann_file, 'r') as f:
|
||||
data = json.load(f)
|
||||
tmpl = dict(
|
||||
image_file=None,
|
||||
bbox_id=None,
|
||||
center=None,
|
||||
scale=None,
|
||||
rotation=0,
|
||||
joints_3d=None,
|
||||
joints_3d_visible=None,
|
||||
dataset=self.dataset_name)
|
||||
|
||||
imid2info = {
|
||||
int(osp.splitext(x['file_name'])[0]): x
|
||||
for x in data['images']
|
||||
}
|
||||
|
||||
num_joints = self.ann_info['num_joints']
|
||||
gt_db = []
|
||||
|
||||
for anno in data['annotations']:
|
||||
newitem = cp.deepcopy(tmpl)
|
||||
image_id = anno['image_id']
|
||||
newitem['bbox_id'] = anno['id']
|
||||
newitem['image_file'] = osp.join(self.img_prefix,
|
||||
imid2info[image_id]['file_name'])
|
||||
|
||||
if max(anno['keypoints']) == 0:
|
||||
continue
|
||||
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
for ipt in range(num_joints):
|
||||
joints_3d[ipt, 0] = anno['keypoints'][ipt * 3 + 0]
|
||||
joints_3d[ipt, 1] = anno['keypoints'][ipt * 3 + 1]
|
||||
joints_3d[ipt, 2] = 0
|
||||
t_vis = min(anno['keypoints'][ipt * 3 + 2], 1)
|
||||
joints_3d_visible[ipt, :] = (t_vis, t_vis, 0)
|
||||
|
||||
center = np.array(anno['center'], dtype=np.float32)
|
||||
scale = self.ann_info['image_size'] / anno['scale'] / 200.0
|
||||
newitem['center'] = center
|
||||
newitem['scale'] = scale
|
||||
newitem['joints_3d'] = joints_3d
|
||||
newitem['joints_3d_visible'] = joints_3d_visible
|
||||
if 'headbox' in anno:
|
||||
newitem['headbox'] = anno['headbox']
|
||||
gt_db.append(newitem)
|
||||
gt_db = sorted(gt_db, key=lambda x: x['bbox_id'])
|
||||
|
||||
return gt_db
|
||||
|
||||
def _evaluate_kernel(self, pred, joints_3d, joints_3d_visible, headbox):
|
||||
"""Evaluate one example."""
|
||||
num_joints = self.ann_info['num_joints']
|
||||
headbox = np.array(headbox)
|
||||
threshold = np.linalg.norm(headbox[:2] - headbox[2:]) * 0.3
|
||||
hit = np.zeros(num_joints, dtype=np.float32)
|
||||
exist = np.zeros(num_joints, dtype=np.float32)
|
||||
|
||||
for i in range(num_joints):
|
||||
pred_pt = pred[i]
|
||||
gt_pt = joints_3d[i]
|
||||
vis = joints_3d_visible[i][0]
|
||||
if vis:
|
||||
exist[i] = 1
|
||||
else:
|
||||
continue
|
||||
distance = np.linalg.norm(pred_pt[:2] - gt_pt[:2])
|
||||
if distance < threshold:
|
||||
hit[i] = 1
|
||||
return hit, exist
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='PCKh', **kwargs):
|
||||
"""Evaluate PCKh for MPII-TRB dataset.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['/val2017/\
|
||||
000000397133.jpg']
|
||||
- heatmap (np.ndarray[N, K, H, W]): model output heatmap.
|
||||
- bbox_ids (list[str]): For example, ['27407'].
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metrics to be performed.
|
||||
Defaults: 'PCKh'.
|
||||
|
||||
Returns:
|
||||
dict: PCKh for each joint
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['PCKh']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
res_file = osp.join(res_folder, 'result_keypoints.json')
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_file = osp.join(tmp_folder.name, 'result_keypoints.json')
|
||||
|
||||
kpts = []
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
str_image_path = image_paths[i]
|
||||
image_id = int(osp.basename(osp.splitext(str_image_path)[0]))
|
||||
|
||||
kpts.append({
|
||||
'keypoints': preds[i].tolist(),
|
||||
'center': boxes[i][0:2].tolist(),
|
||||
'scale': boxes[i][2:4].tolist(),
|
||||
'area': float(boxes[i][4]),
|
||||
'score': float(boxes[i][5]),
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
self._write_keypoint_results(kpts, res_file)
|
||||
info_str = self._report_metric(res_file)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
|
||||
@staticmethod
|
||||
def _write_keypoint_results(keypoints, res_file):
|
||||
"""Write results into a json file."""
|
||||
|
||||
with open(res_file, 'w') as f:
|
||||
json.dump(keypoints, f, sort_keys=True, indent=4)
|
||||
|
||||
def _report_metric(self, res_file):
|
||||
"""Keypoint evaluation.
|
||||
|
||||
Report Mean Acc of skeleton, contour and all joints.
|
||||
"""
|
||||
num_joints = self.ann_info['num_joints']
|
||||
hit = np.zeros(num_joints, dtype=np.float32)
|
||||
exist = np.zeros(num_joints, dtype=np.float32)
|
||||
|
||||
with open(res_file, 'r') as fin:
|
||||
preds = json.load(fin)
|
||||
|
||||
assert len(preds) == len(
|
||||
self.db), f'len(preds)={len(preds)}, len(self.db)={len(self.db)}'
|
||||
for pred, item in zip(preds, self.db):
|
||||
h, e = self._evaluate_kernel(pred['keypoints'], item['joints_3d'],
|
||||
item['joints_3d_visible'],
|
||||
item['headbox'])
|
||||
hit += h
|
||||
exist += e
|
||||
skeleton = np.sum(hit[:14]) / np.sum(exist[:14])
|
||||
contour = np.sum(hit[14:]) / np.sum(exist[14:])
|
||||
mean = np.sum(hit) / np.sum(exist)
|
||||
|
||||
info_str = []
|
||||
info_str.append(('Skeleton_acc', skeleton.item()))
|
||||
info_str.append(('Contour_acc', contour.item()))
|
||||
info_str.append(('PCKh', mean.item()))
|
||||
return info_str
|
||||
|
||||
def _sort_and_unique_bboxes(self, kpts, key='bbox_id'):
|
||||
"""sort kpts and remove the repeated ones."""
|
||||
kpts = sorted(kpts, key=lambda x: x[key])
|
||||
num = len(kpts)
|
||||
for i in range(num - 1, 0, -1):
|
||||
if kpts[i][key] == kpts[i - 1][key]:
|
||||
del kpts[i]
|
||||
|
||||
return kpts
|
||||
Vendored
+97
@@ -0,0 +1,97 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
from mmcv import Config
|
||||
|
||||
from ...builder import DATASETS
|
||||
from .topdown_coco_dataset import TopDownCocoDataset
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class TopDownOCHumanDataset(TopDownCocoDataset):
|
||||
"""OChuman dataset for top-down pose estimation.
|
||||
|
||||
"Pose2Seg: Detection Free Human Instance Segmentation", CVPR'2019.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1803.10683>`__ .
|
||||
|
||||
"Occluded Human (OCHuman)" dataset contains 8110 heavily occluded
|
||||
human instances within 4731 images. OCHuman dataset is designed for
|
||||
validation and testing. To evaluate on OCHuman, the model should be
|
||||
trained on COCO training set, and then test the robustness of the
|
||||
model to occlusion using OCHuman.
|
||||
|
||||
OCHuman keypoint indexes (same as COCO)::
|
||||
|
||||
0: 'nose',
|
||||
1: 'left_eye',
|
||||
2: 'right_eye',
|
||||
3: 'left_ear',
|
||||
4: 'right_ear',
|
||||
5: 'left_shoulder',
|
||||
6: 'right_shoulder',
|
||||
7: 'left_elbow',
|
||||
8: 'right_elbow',
|
||||
9: 'left_wrist',
|
||||
10: 'right_wrist',
|
||||
11: 'left_hip',
|
||||
12: 'right_hip',
|
||||
13: 'left_knee',
|
||||
14: 'right_knee',
|
||||
15: 'left_ankle',
|
||||
16: 'right_ankle'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/ochuman.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super(TopDownCocoDataset, self).__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.use_gt_bbox = data_cfg['use_gt_bbox']
|
||||
self.bbox_file = data_cfg['bbox_file']
|
||||
self.det_bbox_thr = data_cfg.get('det_bbox_thr', 0.0)
|
||||
self.use_nms = data_cfg.get('use_nms', True)
|
||||
self.soft_nms = data_cfg['soft_nms']
|
||||
self.nms_thr = data_cfg['nms_thr']
|
||||
self.oks_thr = data_cfg['oks_thr']
|
||||
self.vis_thr = data_cfg['vis_thr']
|
||||
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
assert self.use_gt_bbox
|
||||
gt_db = self._load_coco_keypoint_annotations()
|
||||
return gt_db
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
from mmcv import Config, deprecated_api_warning
|
||||
|
||||
from ....core.post_processing import oks_nms, soft_oks_nms
|
||||
from ...builder import DATASETS
|
||||
from .topdown_coco_dataset import TopDownCocoDataset
|
||||
|
||||
try:
|
||||
from poseval import eval_helpers
|
||||
from poseval.evaluateAP import evaluateAP
|
||||
has_poseval = True
|
||||
except (ImportError, ModuleNotFoundError):
|
||||
has_poseval = False
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class TopDownPoseTrack18Dataset(TopDownCocoDataset):
|
||||
"""PoseTrack18 dataset for top-down pose estimation.
|
||||
|
||||
"Posetrack: A benchmark for human pose estimation and tracking", CVPR'2018.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1710.10000>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
PoseTrack2018 keypoint indexes::
|
||||
|
||||
0: 'nose',
|
||||
1: 'head_bottom',
|
||||
2: 'head_top',
|
||||
3: 'left_ear',
|
||||
4: 'right_ear',
|
||||
5: 'left_shoulder',
|
||||
6: 'right_shoulder',
|
||||
7: 'left_elbow',
|
||||
8: 'right_elbow',
|
||||
9: 'left_wrist',
|
||||
10: 'right_wrist',
|
||||
11: 'left_hip',
|
||||
12: 'right_hip',
|
||||
13: 'left_knee',
|
||||
14: 'right_knee',
|
||||
15: 'left_ankle',
|
||||
16: 'right_ankle'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False):
|
||||
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset_info is missing. '
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 '
|
||||
'for details.', DeprecationWarning)
|
||||
cfg = Config.fromfile('configs/_base_/datasets/posetrack18.py')
|
||||
dataset_info = cfg._cfg_dict['dataset_info']
|
||||
|
||||
super(TopDownCocoDataset, self).__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.use_gt_bbox = data_cfg['use_gt_bbox']
|
||||
self.bbox_file = data_cfg['bbox_file']
|
||||
self.det_bbox_thr = data_cfg.get('det_bbox_thr', 0.0)
|
||||
self.use_nms = data_cfg.get('use_nms', True)
|
||||
self.soft_nms = data_cfg['soft_nms']
|
||||
self.nms_thr = data_cfg['nms_thr']
|
||||
self.oks_thr = data_cfg['oks_thr']
|
||||
self.vis_thr = data_cfg['vis_thr']
|
||||
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='mAP', **kwargs):
|
||||
"""Evaluate posetrack keypoint results. The pose prediction results
|
||||
will be saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['val/010016_mpii_test\
|
||||
/000024.jpg']
|
||||
- heatmap (np.ndarray[N, K, H, W]): model output heatmap.
|
||||
- bbox_id (list(int))
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed. Defaults: 'mAP'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['mAP']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_folder = tmp_folder.name
|
||||
|
||||
gt_folder = osp.join(
|
||||
osp.dirname(self.ann_file),
|
||||
osp.splitext(self.ann_file.split('_')[-1])[0])
|
||||
|
||||
kpts = defaultdict(list)
|
||||
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
image_id = self.name2id[image_paths[i][len(self.img_prefix):]]
|
||||
kpts[image_id].append({
|
||||
'keypoints': preds[i],
|
||||
'center': boxes[i][0:2],
|
||||
'scale': boxes[i][2:4],
|
||||
'area': boxes[i][4],
|
||||
'score': boxes[i][5],
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
# rescoring and oks nms
|
||||
num_joints = self.ann_info['num_joints']
|
||||
vis_thr = self.vis_thr
|
||||
oks_thr = self.oks_thr
|
||||
valid_kpts = defaultdict(list)
|
||||
for image_id in kpts.keys():
|
||||
img_kpts = kpts[image_id]
|
||||
for n_p in img_kpts:
|
||||
box_score = n_p['score']
|
||||
kpt_score = 0
|
||||
valid_num = 0
|
||||
for n_jt in range(0, num_joints):
|
||||
t_s = n_p['keypoints'][n_jt][2]
|
||||
if t_s > vis_thr:
|
||||
kpt_score = kpt_score + t_s
|
||||
valid_num = valid_num + 1
|
||||
if valid_num != 0:
|
||||
kpt_score = kpt_score / valid_num
|
||||
# rescoring
|
||||
n_p['score'] = kpt_score * box_score
|
||||
|
||||
if self.use_nms:
|
||||
nms = soft_oks_nms if self.soft_nms else oks_nms
|
||||
keep = nms(img_kpts, oks_thr, sigmas=self.sigmas)
|
||||
valid_kpts[image_id].append(
|
||||
[img_kpts[_keep] for _keep in keep])
|
||||
else:
|
||||
valid_kpts[image_id].append(img_kpts)
|
||||
|
||||
self._write_posetrack18_keypoint_results(valid_kpts, gt_folder,
|
||||
res_folder)
|
||||
|
||||
info_str = self._do_python_keypoint_eval(gt_folder, res_folder)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
|
||||
@staticmethod
|
||||
def _write_posetrack18_keypoint_results(keypoint_results, gt_folder,
|
||||
pred_folder):
|
||||
"""Write results into a json file.
|
||||
|
||||
Args:
|
||||
keypoint_results (dict): keypoint results organized by image_id.
|
||||
gt_folder (str): Path of directory for official gt files.
|
||||
pred_folder (str): Path of directory to save the results.
|
||||
"""
|
||||
categories = []
|
||||
|
||||
cat = {}
|
||||
cat['supercategory'] = 'person'
|
||||
cat['id'] = 1
|
||||
cat['name'] = 'person'
|
||||
cat['keypoints'] = [
|
||||
'nose', 'head_bottom', 'head_top', 'left_ear', 'right_ear',
|
||||
'left_shoulder', 'right_shoulder', 'left_elbow', 'right_elbow',
|
||||
'left_wrist', 'right_wrist', 'left_hip', 'right_hip', 'left_knee',
|
||||
'right_knee', 'left_ankle', 'right_ankle'
|
||||
]
|
||||
cat['skeleton'] = [[16, 14], [14, 12], [17, 15], [15, 13], [12, 13],
|
||||
[6, 12], [7, 13], [6, 7], [6, 8], [7, 9], [8, 10],
|
||||
[9, 11], [2, 3], [1, 2], [1, 3], [2, 4], [3, 5],
|
||||
[4, 6], [5, 7]]
|
||||
categories.append(cat)
|
||||
|
||||
json_files = [
|
||||
pos for pos in os.listdir(gt_folder) if pos.endswith('.json')
|
||||
]
|
||||
for json_file in json_files:
|
||||
|
||||
with open(osp.join(gt_folder, json_file), 'r') as f:
|
||||
gt = json.load(f)
|
||||
|
||||
annotations = []
|
||||
images = []
|
||||
|
||||
for image in gt['images']:
|
||||
im = {}
|
||||
im['id'] = image['id']
|
||||
im['file_name'] = image['file_name']
|
||||
images.append(im)
|
||||
|
||||
img_kpts = keypoint_results[im['id']]
|
||||
|
||||
if len(img_kpts) == 0:
|
||||
continue
|
||||
for track_id, img_kpt in enumerate(img_kpts[0]):
|
||||
ann = {}
|
||||
ann['image_id'] = img_kpt['image_id']
|
||||
ann['keypoints'] = np.array(
|
||||
img_kpt['keypoints']).reshape(-1).tolist()
|
||||
ann['scores'] = np.array(ann['keypoints']).reshape(
|
||||
[-1, 3])[:, 2].tolist()
|
||||
ann['score'] = float(img_kpt['score'])
|
||||
ann['track_id'] = track_id
|
||||
annotations.append(ann)
|
||||
|
||||
info = {}
|
||||
info['images'] = images
|
||||
info['categories'] = categories
|
||||
info['annotations'] = annotations
|
||||
|
||||
with open(osp.join(pred_folder, json_file), 'w') as f:
|
||||
json.dump(info, f, sort_keys=True, indent=4)
|
||||
|
||||
def _do_python_keypoint_eval(self, gt_folder, pred_folder):
|
||||
"""Keypoint evaluation using poseval."""
|
||||
|
||||
if not has_poseval:
|
||||
raise ImportError('Please install poseval package for evaluation'
|
||||
'on PoseTrack dataset '
|
||||
'(see requirements/optional.txt)')
|
||||
|
||||
argv = ['', gt_folder + '/', pred_folder + '/']
|
||||
|
||||
print('Loading data')
|
||||
gtFramesAll, prFramesAll = eval_helpers.load_data_dir(argv)
|
||||
|
||||
print('# gt frames :', len(gtFramesAll))
|
||||
print('# pred frames:', len(prFramesAll))
|
||||
|
||||
# evaluate per-frame multi-person pose estimation (AP)
|
||||
# compute AP
|
||||
print('Evaluation of per-frame multi-person pose estimation')
|
||||
apAll, _, _ = evaluateAP(gtFramesAll, prFramesAll, None, False, False)
|
||||
|
||||
# print AP
|
||||
print('Average Precision (AP) metric:')
|
||||
eval_helpers.printTable(apAll)
|
||||
|
||||
stats = eval_helpers.getCum(apAll)
|
||||
|
||||
stats_names = [
|
||||
'Head AP', 'Shou AP', 'Elb AP', 'Wri AP', 'Hip AP', 'Knee AP',
|
||||
'Ankl AP', 'Total AP'
|
||||
]
|
||||
|
||||
info_str = list(zip(stats_names, stats))
|
||||
|
||||
return info_str
|
||||
+549
@@ -0,0 +1,549 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os
|
||||
import os.path as osp
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
import json_tricks as json
|
||||
import numpy as np
|
||||
from mmcv import deprecated_api_warning
|
||||
|
||||
from ....core.post_processing import oks_nms, soft_oks_nms
|
||||
from ...builder import DATASETS
|
||||
from ..base import Kpt2dSviewRgbVidTopDownDataset
|
||||
|
||||
try:
|
||||
from poseval import eval_helpers
|
||||
from poseval.evaluateAP import evaluateAP
|
||||
has_poseval = True
|
||||
except (ImportError, ModuleNotFoundError):
|
||||
has_poseval = False
|
||||
|
||||
|
||||
@DATASETS.register_module()
|
||||
class TopDownPoseTrack18VideoDataset(Kpt2dSviewRgbVidTopDownDataset):
|
||||
"""PoseTrack18 dataset for top-down pose estimation.
|
||||
|
||||
"Posetrack: A benchmark for human pose estimation and tracking", CVPR'2018.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1710.10000>`__ .
|
||||
|
||||
The dataset loads raw features and apply specified transforms
|
||||
to return a dict containing the image tensors and other information.
|
||||
|
||||
PoseTrack2018 keypoint indexes::
|
||||
|
||||
0: 'nose',
|
||||
1: 'head_bottom',
|
||||
2: 'head_top',
|
||||
3: 'left_ear',
|
||||
4: 'right_ear',
|
||||
5: 'left_shoulder',
|
||||
6: 'right_shoulder',
|
||||
7: 'left_elbow',
|
||||
8: 'right_elbow',
|
||||
9: 'left_wrist',
|
||||
10: 'right_wrist',
|
||||
11: 'left_hip',
|
||||
12: 'right_hip',
|
||||
13: 'left_knee',
|
||||
14: 'right_knee',
|
||||
15: 'left_ankle',
|
||||
16: 'right_ankle'
|
||||
|
||||
Args:
|
||||
ann_file (str): Path to the annotation file.
|
||||
img_prefix (str): Path to a directory where videos/images are held.
|
||||
Default: None.
|
||||
data_cfg (dict): config
|
||||
pipeline (list[dict | callable]): A sequence of data transforms.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
test_mode (bool): Store True when building test or
|
||||
validation dataset. Default: False.
|
||||
ph_fill_len (int): The length of the placeholder to fill in the
|
||||
image filenames, default: 6 in PoseTrack18.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=None,
|
||||
test_mode=False,
|
||||
ph_fill_len=6):
|
||||
super().__init__(
|
||||
ann_file,
|
||||
img_prefix,
|
||||
data_cfg,
|
||||
pipeline,
|
||||
dataset_info=dataset_info,
|
||||
test_mode=test_mode)
|
||||
|
||||
self.use_gt_bbox = data_cfg['use_gt_bbox']
|
||||
self.bbox_file = data_cfg['bbox_file']
|
||||
self.det_bbox_thr = data_cfg.get('det_bbox_thr', 0.0)
|
||||
self.use_nms = data_cfg.get('use_nms', True)
|
||||
self.soft_nms = data_cfg['soft_nms']
|
||||
self.nms_thr = data_cfg['nms_thr']
|
||||
self.oks_thr = data_cfg['oks_thr']
|
||||
self.vis_thr = data_cfg['vis_thr']
|
||||
self.frame_weight_train = data_cfg['frame_weight_train']
|
||||
self.frame_weight_test = data_cfg['frame_weight_test']
|
||||
self.frame_weight = self.frame_weight_test \
|
||||
if self.test_mode else self.frame_weight_train
|
||||
|
||||
self.ph_fill_len = ph_fill_len
|
||||
|
||||
# select the frame indices
|
||||
self.frame_index_rand = data_cfg.get('frame_index_rand', True)
|
||||
self.frame_index_range = data_cfg.get('frame_index_range', [-2, 2])
|
||||
self.num_adj_frames = data_cfg.get('num_adj_frames', 1)
|
||||
self.frame_indices_train = data_cfg.get('frame_indices_train', None)
|
||||
self.frame_indices_test = data_cfg.get('frame_indices_test',
|
||||
[-2, -1, 0, 1, 2])
|
||||
|
||||
if self.frame_indices_train is not None:
|
||||
self.frame_indices_train.sort()
|
||||
self.frame_indices_test.sort()
|
||||
|
||||
self.db = self._get_db()
|
||||
|
||||
print(f'=> num_images: {self.num_images}')
|
||||
print(f'=> load {len(self.db)} samples')
|
||||
|
||||
def _get_db(self):
|
||||
"""Load dataset."""
|
||||
if (not self.test_mode) or self.use_gt_bbox:
|
||||
# use ground truth bbox
|
||||
gt_db = self._load_coco_keypoint_annotations()
|
||||
else:
|
||||
# use bbox from detection
|
||||
gt_db = self._load_posetrack_person_detection_results()
|
||||
return gt_db
|
||||
|
||||
def _load_coco_keypoint_annotations(self):
|
||||
"""Ground truth bbox and keypoints."""
|
||||
gt_db = []
|
||||
for img_id in self.img_ids:
|
||||
gt_db.extend(self._load_coco_keypoint_annotation_kernel(img_id))
|
||||
return gt_db
|
||||
|
||||
def _load_coco_keypoint_annotation_kernel(self, img_id):
|
||||
"""load annotation from COCOAPI.
|
||||
|
||||
Note:
|
||||
bbox:[x1, y1, w, h]
|
||||
Args:
|
||||
img_id: coco image id
|
||||
Returns:
|
||||
dict: db entry
|
||||
"""
|
||||
img_ann = self.coco.loadImgs(img_id)[0]
|
||||
width = img_ann['width']
|
||||
height = img_ann['height']
|
||||
num_joints = self.ann_info['num_joints']
|
||||
|
||||
file_name = img_ann['file_name']
|
||||
nframes = int(img_ann['nframes'])
|
||||
frame_id = int(img_ann['frame_id'])
|
||||
|
||||
ann_ids = self.coco.getAnnIds(imgIds=img_id, iscrowd=False)
|
||||
objs = self.coco.loadAnns(ann_ids)
|
||||
|
||||
# sanitize bboxes
|
||||
valid_objs = []
|
||||
for obj in objs:
|
||||
if 'bbox' not in obj:
|
||||
continue
|
||||
x, y, w, h = obj['bbox']
|
||||
x1 = max(0, x)
|
||||
y1 = max(0, y)
|
||||
x2 = min(width - 1, x1 + max(0, w - 1))
|
||||
y2 = min(height - 1, y1 + max(0, h - 1))
|
||||
if ('area' not in obj or obj['area'] > 0) and x2 > x1 and y2 > y1:
|
||||
obj['clean_bbox'] = [x1, y1, x2 - x1, y2 - y1]
|
||||
valid_objs.append(obj)
|
||||
objs = valid_objs
|
||||
|
||||
bbox_id = 0
|
||||
rec = []
|
||||
for obj in objs:
|
||||
if 'keypoints' not in obj:
|
||||
continue
|
||||
if max(obj['keypoints']) == 0:
|
||||
continue
|
||||
if 'num_keypoints' in obj and obj['num_keypoints'] == 0:
|
||||
continue
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
|
||||
keypoints = np.array(obj['keypoints']).reshape(-1, 3)
|
||||
joints_3d[:, :2] = keypoints[:, :2]
|
||||
joints_3d_visible[:, :2] = np.minimum(1, keypoints[:, 2:3])
|
||||
|
||||
center, scale = self._xywh2cs(*obj['clean_bbox'][:4])
|
||||
|
||||
image_files = []
|
||||
cur_image_file = osp.join(self.img_prefix, self.id2name[img_id])
|
||||
image_files.append(cur_image_file)
|
||||
|
||||
# "images/val/012834_mpii_test/000000.jpg" -->> "000000.jpg"
|
||||
cur_image_name = file_name.split('/')[-1]
|
||||
ref_idx = int(cur_image_name.replace('.jpg', ''))
|
||||
|
||||
# select the frame indices
|
||||
if not self.test_mode and self.frame_indices_train is not None:
|
||||
indices = self.frame_indices_train
|
||||
elif not self.test_mode and self.frame_index_rand:
|
||||
low, high = self.frame_index_range
|
||||
indices = np.random.randint(low, high + 1, self.num_adj_frames)
|
||||
else:
|
||||
indices = self.frame_indices_test
|
||||
|
||||
for index in indices:
|
||||
if self.test_mode and index == 0:
|
||||
continue
|
||||
# the supporting frame index
|
||||
support_idx = ref_idx + index
|
||||
support_idx = np.clip(support_idx, 0, nframes - 1)
|
||||
sup_image_file = cur_image_file.replace(
|
||||
cur_image_name,
|
||||
str(support_idx).zfill(self.ph_fill_len) + '.jpg')
|
||||
|
||||
if osp.exists(sup_image_file):
|
||||
image_files.append(sup_image_file)
|
||||
else:
|
||||
warnings.warn(
|
||||
f'{sup_image_file} does not exist, '
|
||||
f'use {cur_image_file} instead.', UserWarning)
|
||||
image_files.append(cur_image_file)
|
||||
rec.append({
|
||||
'image_file': image_files,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'bbox': obj['clean_bbox'][:4],
|
||||
'rotation': 0,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'dataset': self.dataset_name,
|
||||
'bbox_score': 1,
|
||||
'bbox_id': bbox_id,
|
||||
'nframes': nframes,
|
||||
'frame_id': frame_id,
|
||||
'frame_weight': self.frame_weight
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
|
||||
return rec
|
||||
|
||||
def _load_posetrack_person_detection_results(self):
|
||||
"""Load Posetrack person detection results.
|
||||
|
||||
Only in test mode.
|
||||
"""
|
||||
num_joints = self.ann_info['num_joints']
|
||||
all_boxes = None
|
||||
with open(self.bbox_file, 'r') as f:
|
||||
all_boxes = json.load(f)
|
||||
|
||||
if not all_boxes:
|
||||
raise ValueError('=> Load %s fail!' % self.bbox_file)
|
||||
|
||||
print(f'=> Total boxes: {len(all_boxes)}')
|
||||
|
||||
kpt_db = []
|
||||
bbox_id = 0
|
||||
for det_res in all_boxes:
|
||||
if det_res['category_id'] != 1:
|
||||
continue
|
||||
|
||||
score = det_res['score']
|
||||
if score < self.det_bbox_thr:
|
||||
continue
|
||||
|
||||
box = det_res['bbox']
|
||||
|
||||
# deal with different bbox file formats
|
||||
if 'nframes' in det_res and 'frame_id' in det_res:
|
||||
nframes = int(det_res['nframes'])
|
||||
frame_id = int(det_res['frame_id'])
|
||||
elif 'image_name' in det_res:
|
||||
img_id = self.name2id[det_res['image_name']]
|
||||
img_ann = self.coco.loadImgs(img_id)[0]
|
||||
nframes = int(img_ann['nframes'])
|
||||
frame_id = int(img_ann['frame_id'])
|
||||
else:
|
||||
img_id = det_res['image_id']
|
||||
img_ann = self.coco.loadImgs(img_id)[0]
|
||||
nframes = int(img_ann['nframes'])
|
||||
frame_id = int(img_ann['frame_id'])
|
||||
|
||||
image_files = []
|
||||
if 'image_name' in det_res:
|
||||
file_name = det_res['image_name']
|
||||
else:
|
||||
file_name = self.id2name[det_res['image_id']]
|
||||
|
||||
cur_image_file = osp.join(self.img_prefix, file_name)
|
||||
image_files.append(cur_image_file)
|
||||
|
||||
# "images/val/012834_mpii_test/000000.jpg" -->> "000000.jpg"
|
||||
cur_image_name = file_name.split('/')[-1]
|
||||
ref_idx = int(cur_image_name.replace('.jpg', ''))
|
||||
|
||||
indices = self.frame_indices_test
|
||||
for index in indices:
|
||||
if self.test_mode and index == 0:
|
||||
continue
|
||||
# the supporting frame index
|
||||
support_idx = ref_idx + index
|
||||
support_idx = np.clip(support_idx, 0, nframes - 1)
|
||||
sup_image_file = cur_image_file.replace(
|
||||
cur_image_name,
|
||||
str(support_idx).zfill(self.ph_fill_len) + '.jpg')
|
||||
|
||||
if osp.exists(sup_image_file):
|
||||
image_files.append(sup_image_file)
|
||||
else:
|
||||
warnings.warn(f'{sup_image_file} does not exist, '
|
||||
f'use {cur_image_file} instead.')
|
||||
image_files.append(cur_image_file)
|
||||
|
||||
center, scale = self._xywh2cs(*box[:4])
|
||||
joints_3d = np.zeros((num_joints, 3), dtype=np.float32)
|
||||
joints_3d_visible = np.ones((num_joints, 3), dtype=np.float32)
|
||||
kpt_db.append({
|
||||
'image_file': image_files,
|
||||
'center': center,
|
||||
'scale': scale,
|
||||
'rotation': 0,
|
||||
'bbox': box[:4],
|
||||
'bbox_score': score,
|
||||
'dataset': self.dataset_name,
|
||||
'joints_3d': joints_3d,
|
||||
'joints_3d_visible': joints_3d_visible,
|
||||
'bbox_id': bbox_id,
|
||||
'nframes': nframes,
|
||||
'frame_id': frame_id,
|
||||
'frame_weight': self.frame_weight
|
||||
})
|
||||
bbox_id = bbox_id + 1
|
||||
print(f'=> Total boxes after filter '
|
||||
f'low score@{self.det_bbox_thr}: {bbox_id}')
|
||||
return kpt_db
|
||||
|
||||
@deprecated_api_warning(name_dict=dict(outputs='results'))
|
||||
def evaluate(self, results, res_folder=None, metric='mAP', **kwargs):
|
||||
"""Evaluate posetrack keypoint results. The pose prediction results
|
||||
will be saved in ``${res_folder}/result_keypoints.json``.
|
||||
|
||||
Note:
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
results (list[dict]): Testing results containing the following
|
||||
items:
|
||||
|
||||
- preds (np.ndarray[N,K,3]): The first two dimensions are \
|
||||
coordinates, score is the third dimension of the array.
|
||||
- boxes (np.ndarray[N,6]): [center[0], center[1], scale[0], \
|
||||
scale[1],area, score]
|
||||
- image_paths (list[str]): For example, ['val/010016_mpii_test\
|
||||
/000024.jpg']
|
||||
- heatmap (np.ndarray[N, K, H, W]): model output heatmap.
|
||||
- bbox_id (list(int))
|
||||
res_folder (str, optional): The folder to save the testing
|
||||
results. If not specified, a temp folder will be created.
|
||||
Default: None.
|
||||
metric (str | list[str]): Metric to be performed. Defaults: 'mAP'.
|
||||
|
||||
Returns:
|
||||
dict: Evaluation results for evaluation metric.
|
||||
"""
|
||||
metrics = metric if isinstance(metric, list) else [metric]
|
||||
allowed_metrics = ['mAP']
|
||||
for metric in metrics:
|
||||
if metric not in allowed_metrics:
|
||||
raise KeyError(f'metric {metric} is not supported')
|
||||
|
||||
if res_folder is not None:
|
||||
tmp_folder = None
|
||||
else:
|
||||
tmp_folder = tempfile.TemporaryDirectory()
|
||||
res_folder = tmp_folder.name
|
||||
|
||||
gt_folder = osp.join(
|
||||
osp.dirname(self.ann_file),
|
||||
osp.splitext(self.ann_file.split('_')[-1])[0])
|
||||
|
||||
kpts = defaultdict(list)
|
||||
|
||||
for result in results:
|
||||
preds = result['preds']
|
||||
boxes = result['boxes']
|
||||
image_paths = result['image_paths']
|
||||
bbox_ids = result['bbox_ids']
|
||||
|
||||
batch_size = len(image_paths)
|
||||
for i in range(batch_size):
|
||||
if not isinstance(image_paths[i], list):
|
||||
image_id = self.name2id[image_paths[i]
|
||||
[len(self.img_prefix):]]
|
||||
else:
|
||||
image_id = self.name2id[image_paths[i][0]
|
||||
[len(self.img_prefix):]]
|
||||
|
||||
kpts[image_id].append({
|
||||
'keypoints': preds[i],
|
||||
'center': boxes[i][0:2],
|
||||
'scale': boxes[i][2:4],
|
||||
'area': boxes[i][4],
|
||||
'score': boxes[i][5],
|
||||
'image_id': image_id,
|
||||
'bbox_id': bbox_ids[i]
|
||||
})
|
||||
kpts = self._sort_and_unique_bboxes(kpts)
|
||||
|
||||
# rescoring and oks nms
|
||||
num_joints = self.ann_info['num_joints']
|
||||
vis_thr = self.vis_thr
|
||||
oks_thr = self.oks_thr
|
||||
valid_kpts = defaultdict(list)
|
||||
for image_id in kpts.keys():
|
||||
img_kpts = kpts[image_id]
|
||||
for n_p in img_kpts:
|
||||
box_score = n_p['score']
|
||||
kpt_score = 0
|
||||
valid_num = 0
|
||||
for n_jt in range(0, num_joints):
|
||||
t_s = n_p['keypoints'][n_jt][2]
|
||||
if t_s > vis_thr:
|
||||
kpt_score = kpt_score + t_s
|
||||
valid_num = valid_num + 1
|
||||
if valid_num != 0:
|
||||
kpt_score = kpt_score / valid_num
|
||||
# rescoring
|
||||
n_p['score'] = kpt_score * box_score
|
||||
|
||||
if self.use_nms:
|
||||
nms = soft_oks_nms if self.soft_nms else oks_nms
|
||||
keep = nms(img_kpts, oks_thr, sigmas=self.sigmas)
|
||||
valid_kpts[image_id].append(
|
||||
[img_kpts[_keep] for _keep in keep])
|
||||
else:
|
||||
valid_kpts[image_id].append(img_kpts)
|
||||
|
||||
self._write_keypoint_results(valid_kpts, gt_folder, res_folder)
|
||||
|
||||
info_str = self._do_keypoint_eval(gt_folder, res_folder)
|
||||
name_value = OrderedDict(info_str)
|
||||
|
||||
if tmp_folder is not None:
|
||||
tmp_folder.cleanup()
|
||||
|
||||
return name_value
|
||||
|
||||
@staticmethod
|
||||
def _write_keypoint_results(keypoint_results, gt_folder, pred_folder):
|
||||
"""Write results into a json file.
|
||||
|
||||
Args:
|
||||
keypoint_results (dict): keypoint results organized by image_id.
|
||||
gt_folder (str): Path of directory for official gt files.
|
||||
pred_folder (str): Path of directory to save the results.
|
||||
"""
|
||||
categories = []
|
||||
|
||||
cat = {}
|
||||
cat['supercategory'] = 'person'
|
||||
cat['id'] = 1
|
||||
cat['name'] = 'person'
|
||||
cat['keypoints'] = [
|
||||
'nose', 'head_bottom', 'head_top', 'left_ear', 'right_ear',
|
||||
'left_shoulder', 'right_shoulder', 'left_elbow', 'right_elbow',
|
||||
'left_wrist', 'right_wrist', 'left_hip', 'right_hip', 'left_knee',
|
||||
'right_knee', 'left_ankle', 'right_ankle'
|
||||
]
|
||||
cat['skeleton'] = [[16, 14], [14, 12], [17, 15], [15, 13], [12, 13],
|
||||
[6, 12], [7, 13], [6, 7], [6, 8], [7, 9], [8, 10],
|
||||
[9, 11], [2, 3], [1, 2], [1, 3], [2, 4], [3, 5],
|
||||
[4, 6], [5, 7]]
|
||||
categories.append(cat)
|
||||
|
||||
json_files = [
|
||||
pos for pos in os.listdir(gt_folder) if pos.endswith('.json')
|
||||
]
|
||||
for json_file in json_files:
|
||||
|
||||
with open(osp.join(gt_folder, json_file), 'r') as f:
|
||||
gt = json.load(f)
|
||||
|
||||
annotations = []
|
||||
images = []
|
||||
|
||||
for image in gt['images']:
|
||||
im = {}
|
||||
im['id'] = image['id']
|
||||
im['file_name'] = image['file_name']
|
||||
images.append(im)
|
||||
|
||||
img_kpts = keypoint_results[im['id']]
|
||||
|
||||
if len(img_kpts) == 0:
|
||||
continue
|
||||
for track_id, img_kpt in enumerate(img_kpts[0]):
|
||||
ann = {}
|
||||
ann['image_id'] = img_kpt['image_id']
|
||||
ann['keypoints'] = np.array(
|
||||
img_kpt['keypoints']).reshape(-1).tolist()
|
||||
ann['scores'] = np.array(ann['keypoints']).reshape(
|
||||
[-1, 3])[:, 2].tolist()
|
||||
ann['score'] = float(img_kpt['score'])
|
||||
ann['track_id'] = track_id
|
||||
annotations.append(ann)
|
||||
|
||||
info = {}
|
||||
info['images'] = images
|
||||
info['categories'] = categories
|
||||
info['annotations'] = annotations
|
||||
|
||||
with open(osp.join(pred_folder, json_file), 'w') as f:
|
||||
json.dump(info, f, sort_keys=True, indent=4)
|
||||
|
||||
def _do_keypoint_eval(self, gt_folder, pred_folder):
|
||||
"""Keypoint evaluation using poseval."""
|
||||
|
||||
if not has_poseval:
|
||||
raise ImportError('Please install poseval package for evaluation'
|
||||
'on PoseTrack dataset '
|
||||
'(see requirements/optional.txt)')
|
||||
|
||||
argv = ['', gt_folder + '/', pred_folder + '/']
|
||||
|
||||
print('Loading data')
|
||||
gtFramesAll, prFramesAll = eval_helpers.load_data_dir(argv)
|
||||
|
||||
print('# gt frames :', len(gtFramesAll))
|
||||
print('# pred frames:', len(prFramesAll))
|
||||
|
||||
# evaluate per-frame multi-person pose estimation (AP)
|
||||
# compute AP
|
||||
print('Evaluation of per-frame multi-person pose estimation')
|
||||
apAll, _, _ = evaluateAP(gtFramesAll, prFramesAll, None, False, False)
|
||||
|
||||
# print AP
|
||||
print('Average Precision (AP) metric:')
|
||||
eval_helpers.printTable(apAll)
|
||||
|
||||
stats = eval_helpers.getCum(apAll)
|
||||
|
||||
stats_names = [
|
||||
'Head AP', 'Shou AP', 'Elb AP', 'Wri AP', 'Hip AP', 'Knee AP',
|
||||
'Ankl AP', 'Total AP'
|
||||
]
|
||||
|
||||
info_str = list(zip(stats_names, stats))
|
||||
|
||||
return info_str
|
||||
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .bottom_up_transform import * # noqa
|
||||
from .hand_transform import * # noqa
|
||||
from .loading import LoadImageFromFile # noqa
|
||||
from .mesh_transform import * # noqa
|
||||
from .pose3d_transform import * # noqa
|
||||
from .shared_transform import * # noqa
|
||||
from .top_down_transform import * # noqa
|
||||
+816
@@ -0,0 +1,816 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from mmpose.core.post_processing import (get_affine_transform, get_warp_matrix,
|
||||
warp_affine_joints)
|
||||
from mmpose.datasets.builder import PIPELINES
|
||||
from .shared_transform import Compose
|
||||
|
||||
|
||||
def _ceil_to_multiples_of(x, base=64):
|
||||
"""Transform x to the integral multiple of the base."""
|
||||
return int(np.ceil(x / base)) * base
|
||||
|
||||
|
||||
def _get_multi_scale_size(image,
|
||||
input_size,
|
||||
current_scale,
|
||||
min_scale,
|
||||
use_udp=False):
|
||||
"""Get the size for multi-scale training.
|
||||
|
||||
Args:
|
||||
image: Input image.
|
||||
input_size (np.ndarray[2]): Size (w, h) of the image input.
|
||||
current_scale (float): Scale factor.
|
||||
min_scale (float): Minimal scale.
|
||||
use_udp (bool): To use unbiased data processing.
|
||||
Paper ref: Huang et al. The Devil is in the Details: Delving into
|
||||
Unbiased Data Processing for Human Pose Estimation (CVPR 2020).
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing multi-scale sizes.
|
||||
|
||||
- (w_resized, h_resized) (tuple(int)): resized width/height
|
||||
- center (np.ndarray)image center
|
||||
- scale (np.ndarray): scales wrt width/height
|
||||
"""
|
||||
assert len(input_size) == 2
|
||||
h, w, _ = image.shape
|
||||
|
||||
# calculate the size for min_scale
|
||||
min_input_w = _ceil_to_multiples_of(min_scale * input_size[0], 64)
|
||||
min_input_h = _ceil_to_multiples_of(min_scale * input_size[1], 64)
|
||||
if w < h:
|
||||
w_resized = int(min_input_w * current_scale / min_scale)
|
||||
h_resized = int(
|
||||
_ceil_to_multiples_of(min_input_w / w * h, 64) * current_scale /
|
||||
min_scale)
|
||||
if use_udp:
|
||||
scale_w = w - 1.0
|
||||
scale_h = (h_resized - 1.0) / (w_resized - 1.0) * (w - 1.0)
|
||||
else:
|
||||
scale_w = w / 200.0
|
||||
scale_h = h_resized / w_resized * w / 200.0
|
||||
else:
|
||||
h_resized = int(min_input_h * current_scale / min_scale)
|
||||
w_resized = int(
|
||||
_ceil_to_multiples_of(min_input_h / h * w, 64) * current_scale /
|
||||
min_scale)
|
||||
if use_udp:
|
||||
scale_h = h - 1.0
|
||||
scale_w = (w_resized - 1.0) / (h_resized - 1.0) * (h - 1.0)
|
||||
else:
|
||||
scale_h = h / 200.0
|
||||
scale_w = w_resized / h_resized * h / 200.0
|
||||
if use_udp:
|
||||
center = (scale_w / 2.0, scale_h / 2.0)
|
||||
else:
|
||||
center = np.array([round(w / 2.0), round(h / 2.0)])
|
||||
return (w_resized, h_resized), center, np.array([scale_w, scale_h])
|
||||
|
||||
|
||||
def _resize_align_multi_scale(image, input_size, current_scale, min_scale):
|
||||
"""Resize the images for multi-scale training.
|
||||
|
||||
Args:
|
||||
image: Input image
|
||||
input_size (np.ndarray[2]): Size (w, h) of the image input
|
||||
current_scale (float): Current scale
|
||||
min_scale (float): Minimal scale
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing image info.
|
||||
|
||||
- image_resized (np.ndarray): resized image
|
||||
- center (np.ndarray): center of image
|
||||
- scale (np.ndarray): scale
|
||||
"""
|
||||
assert len(input_size) == 2
|
||||
size_resized, center, scale = _get_multi_scale_size(
|
||||
image, input_size, current_scale, min_scale)
|
||||
|
||||
trans = get_affine_transform(center, scale, 0, size_resized)
|
||||
image_resized = cv2.warpAffine(image, trans, size_resized)
|
||||
|
||||
return image_resized, center, scale
|
||||
|
||||
|
||||
def _resize_align_multi_scale_udp(image, input_size, current_scale, min_scale):
|
||||
"""Resize the images for multi-scale training.
|
||||
|
||||
Args:
|
||||
image: Input image
|
||||
input_size (np.ndarray[2]): Size (w, h) of the image input
|
||||
current_scale (float): Current scale
|
||||
min_scale (float): Minimal scale
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing image info.
|
||||
|
||||
- image_resized (np.ndarray): resized image
|
||||
- center (np.ndarray): center of image
|
||||
- scale (np.ndarray): scale
|
||||
"""
|
||||
assert len(input_size) == 2
|
||||
size_resized, _, _ = _get_multi_scale_size(image, input_size,
|
||||
current_scale, min_scale, True)
|
||||
|
||||
_, center, scale = _get_multi_scale_size(image, input_size, min_scale,
|
||||
min_scale, True)
|
||||
|
||||
trans = get_warp_matrix(
|
||||
theta=0,
|
||||
size_input=np.array(scale, dtype=np.float32),
|
||||
size_dst=np.array(size_resized, dtype=np.float32) - 1.0,
|
||||
size_target=np.array(scale, dtype=np.float32))
|
||||
image_resized = cv2.warpAffine(
|
||||
image.copy(), trans, size_resized, flags=cv2.INTER_LINEAR)
|
||||
|
||||
return image_resized, center, scale
|
||||
|
||||
|
||||
class HeatmapGenerator:
|
||||
"""Generate heatmaps for bottom-up models.
|
||||
|
||||
Args:
|
||||
num_joints (int): Number of keypoints
|
||||
output_size (np.ndarray): Size (w, h) of feature map
|
||||
sigma (int): Sigma of the heatmaps.
|
||||
use_udp (bool): To use unbiased data processing.
|
||||
Paper ref: Huang et al. The Devil is in the Details: Delving into
|
||||
Unbiased Data Processing for Human Pose Estimation (CVPR 2020).
|
||||
"""
|
||||
|
||||
def __init__(self, output_size, num_joints, sigma=-1, use_udp=False):
|
||||
if not isinstance(output_size, np.ndarray):
|
||||
output_size = np.array(output_size)
|
||||
if output_size.size > 1:
|
||||
assert len(output_size) == 2
|
||||
self.output_size = output_size
|
||||
else:
|
||||
self.output_size = np.array([output_size, output_size],
|
||||
dtype=np.int)
|
||||
self.num_joints = num_joints
|
||||
if sigma < 0:
|
||||
sigma = self.output_size.prod()**0.5 / 64
|
||||
self.sigma = sigma
|
||||
size = 6 * sigma + 3
|
||||
self.use_udp = use_udp
|
||||
if use_udp:
|
||||
self.x = np.arange(0, size, 1, np.float32)
|
||||
self.y = self.x[:, None]
|
||||
else:
|
||||
x = np.arange(0, size, 1, np.float32)
|
||||
y = x[:, None]
|
||||
x0, y0 = 3 * sigma + 1, 3 * sigma + 1
|
||||
self.g = np.exp(-((x - x0)**2 + (y - y0)**2) / (2 * sigma**2))
|
||||
|
||||
def __call__(self, joints):
|
||||
"""Generate heatmaps."""
|
||||
hms = np.zeros(
|
||||
(self.num_joints, self.output_size[1], self.output_size[0]),
|
||||
dtype=np.float32)
|
||||
|
||||
sigma = self.sigma
|
||||
for p in joints:
|
||||
for idx, pt in enumerate(p):
|
||||
if pt[2] > 0:
|
||||
x, y = int(pt[0]), int(pt[1])
|
||||
if x < 0 or y < 0 or \
|
||||
x >= self.output_size[0] or y >= self.output_size[1]:
|
||||
continue
|
||||
|
||||
if self.use_udp:
|
||||
x0 = 3 * sigma + 1 + pt[0] - x
|
||||
y0 = 3 * sigma + 1 + pt[1] - y
|
||||
g = np.exp(-((self.x - x0)**2 + (self.y - y0)**2) /
|
||||
(2 * sigma**2))
|
||||
else:
|
||||
g = self.g
|
||||
|
||||
ul = int(np.round(x - 3 * sigma -
|
||||
1)), int(np.round(y - 3 * sigma - 1))
|
||||
br = int(np.round(x + 3 * sigma +
|
||||
2)), int(np.round(y + 3 * sigma + 2))
|
||||
|
||||
c, d = max(0,
|
||||
-ul[0]), min(br[0], self.output_size[0]) - ul[0]
|
||||
a, b = max(0,
|
||||
-ul[1]), min(br[1], self.output_size[1]) - ul[1]
|
||||
|
||||
cc, dd = max(0, ul[0]), min(br[0], self.output_size[0])
|
||||
aa, bb = max(0, ul[1]), min(br[1], self.output_size[1])
|
||||
hms[idx, aa:bb,
|
||||
cc:dd] = np.maximum(hms[idx, aa:bb, cc:dd], g[a:b,
|
||||
c:d])
|
||||
return hms
|
||||
|
||||
|
||||
class JointsEncoder:
|
||||
"""Encodes the visible joints into (coordinates, score); The coordinate of
|
||||
one joint and its score are of `int` type.
|
||||
|
||||
(idx * output_size**2 + y * output_size + x, 1) or (0, 0).
|
||||
|
||||
Args:
|
||||
max_num_people(int): Max number of people in an image
|
||||
num_joints(int): Number of keypoints
|
||||
output_size(np.ndarray): Size (w, h) of feature map
|
||||
tag_per_joint(bool): Option to use one tag map per joint.
|
||||
"""
|
||||
|
||||
def __init__(self, max_num_people, num_joints, output_size, tag_per_joint):
|
||||
self.max_num_people = max_num_people
|
||||
self.num_joints = num_joints
|
||||
if not isinstance(output_size, np.ndarray):
|
||||
output_size = np.array(output_size)
|
||||
if output_size.size > 1:
|
||||
assert len(output_size) == 2
|
||||
self.output_size = output_size
|
||||
else:
|
||||
self.output_size = np.array([output_size, output_size],
|
||||
dtype=np.int)
|
||||
self.tag_per_joint = tag_per_joint
|
||||
|
||||
def __call__(self, joints):
|
||||
"""
|
||||
Note:
|
||||
- number of people in image: N
|
||||
- number of keypoints: K
|
||||
- max number of people in an image: M
|
||||
|
||||
Args:
|
||||
joints (np.ndarray[N,K,3])
|
||||
|
||||
Returns:
|
||||
visible_kpts (np.ndarray[M,K,2]).
|
||||
"""
|
||||
visible_kpts = np.zeros((self.max_num_people, self.num_joints, 2),
|
||||
dtype=np.float32)
|
||||
for i in range(len(joints)):
|
||||
tot = 0
|
||||
for idx, pt in enumerate(joints[i]):
|
||||
x, y = int(pt[0]), int(pt[1])
|
||||
if (pt[2] > 0 and 0 <= y < self.output_size[1]
|
||||
and 0 <= x < self.output_size[0]):
|
||||
if self.tag_per_joint:
|
||||
visible_kpts[i][tot] = \
|
||||
(idx * self.output_size.prod()
|
||||
+ y * self.output_size[0] + x, 1)
|
||||
else:
|
||||
visible_kpts[i][tot] = (y * self.output_size[0] + x, 1)
|
||||
tot += 1
|
||||
return visible_kpts
|
||||
|
||||
|
||||
class PAFGenerator:
|
||||
"""Generate part affinity fields.
|
||||
|
||||
Args:
|
||||
output_size (np.ndarray): Size (w, h) of feature map.
|
||||
limb_width (int): Limb width of part affinity fields.
|
||||
skeleton (list[list]): connections of joints.
|
||||
"""
|
||||
|
||||
def __init__(self, output_size, limb_width, skeleton):
|
||||
if not isinstance(output_size, np.ndarray):
|
||||
output_size = np.array(output_size)
|
||||
if output_size.size > 1:
|
||||
assert len(output_size) == 2
|
||||
self.output_size = output_size
|
||||
else:
|
||||
self.output_size = np.array([output_size, output_size],
|
||||
dtype=np.int)
|
||||
self.limb_width = limb_width
|
||||
self.skeleton = skeleton
|
||||
|
||||
def _accumulate_paf_map_(self, pafs, src, dst, count):
|
||||
"""Accumulate part affinity fields between two given joints.
|
||||
|
||||
Args:
|
||||
pafs (np.ndarray[2,H,W]): paf maps (2 dimensions:x axis and
|
||||
y axis) for a certain limb connection. This argument will
|
||||
be modified inplace.
|
||||
src (np.ndarray[2,]): coordinates of the source joint.
|
||||
dst (np.ndarray[2,]): coordinates of the destination joint.
|
||||
count (np.ndarray[H,W]): count map that preserves the number
|
||||
of non-zero vectors at each point. This argument will be
|
||||
modified inplace.
|
||||
"""
|
||||
limb_vec = dst - src
|
||||
norm = np.linalg.norm(limb_vec)
|
||||
if norm == 0:
|
||||
unit_limb_vec = np.zeros(2)
|
||||
else:
|
||||
unit_limb_vec = limb_vec / norm
|
||||
|
||||
min_x = max(np.floor(min(src[0], dst[0]) - self.limb_width), 0)
|
||||
max_x = min(
|
||||
np.ceil(max(src[0], dst[0]) + self.limb_width),
|
||||
self.output_size[0] - 1)
|
||||
min_y = max(np.floor(min(src[1], dst[1]) - self.limb_width), 0)
|
||||
max_y = min(
|
||||
np.ceil(max(src[1], dst[1]) + self.limb_width),
|
||||
self.output_size[1] - 1)
|
||||
|
||||
range_x = list(range(int(min_x), int(max_x + 1), 1))
|
||||
range_y = list(range(int(min_y), int(max_y + 1), 1))
|
||||
|
||||
mask = np.zeros_like(count, dtype=bool)
|
||||
if len(range_x) > 0 and len(range_y) > 0:
|
||||
xx, yy = np.meshgrid(range_x, range_y)
|
||||
delta_x = xx - src[0]
|
||||
delta_y = yy - src[1]
|
||||
dist = np.abs(delta_x * unit_limb_vec[1] -
|
||||
delta_y * unit_limb_vec[0])
|
||||
mask_local = (dist < self.limb_width)
|
||||
mask[yy, xx] = mask_local
|
||||
|
||||
pafs[0, mask] += unit_limb_vec[0]
|
||||
pafs[1, mask] += unit_limb_vec[1]
|
||||
count += mask
|
||||
|
||||
return pafs, count
|
||||
|
||||
def __call__(self, joints):
|
||||
"""Generate the target part affinity fields."""
|
||||
pafs = np.zeros(
|
||||
(len(self.skeleton) * 2, self.output_size[1], self.output_size[0]),
|
||||
dtype=np.float32)
|
||||
|
||||
for idx, sk in enumerate(self.skeleton):
|
||||
count = np.zeros((self.output_size[1], self.output_size[0]),
|
||||
dtype=np.float32)
|
||||
|
||||
for p in joints:
|
||||
src = p[sk[0]]
|
||||
dst = p[sk[1]]
|
||||
if src[2] > 0 and dst[2] > 0:
|
||||
self._accumulate_paf_map_(pafs[2 * idx:2 * idx + 2],
|
||||
src[:2], dst[:2], count)
|
||||
|
||||
pafs[2 * idx:2 * idx + 2] /= np.maximum(count, 1)
|
||||
|
||||
return pafs
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class BottomUpRandomFlip:
|
||||
"""Data augmentation with random image flip for bottom-up.
|
||||
|
||||
Args:
|
||||
flip_prob (float): Probability of flip.
|
||||
"""
|
||||
|
||||
def __init__(self, flip_prob=0.5):
|
||||
self.flip_prob = flip_prob
|
||||
|
||||
def __call__(self, results):
|
||||
"""Perform data augmentation with random image flip."""
|
||||
image, mask, joints = results['img'], results['mask'], results[
|
||||
'joints']
|
||||
self.flip_index = results['ann_info']['flip_index']
|
||||
self.output_size = results['ann_info']['heatmap_size']
|
||||
|
||||
assert isinstance(mask, list)
|
||||
assert isinstance(joints, list)
|
||||
assert len(mask) == len(joints)
|
||||
assert len(mask) == len(self.output_size)
|
||||
|
||||
if np.random.random() < self.flip_prob:
|
||||
image = image[:, ::-1].copy() - np.zeros_like(image)
|
||||
for i, _output_size in enumerate(self.output_size):
|
||||
if not isinstance(_output_size, np.ndarray):
|
||||
_output_size = np.array(_output_size)
|
||||
if _output_size.size > 1:
|
||||
assert len(_output_size) == 2
|
||||
else:
|
||||
_output_size = np.array([_output_size, _output_size],
|
||||
dtype=np.int)
|
||||
mask[i] = mask[i][:, ::-1].copy()
|
||||
joints[i] = joints[i][:, self.flip_index]
|
||||
joints[i][:, :, 0] = _output_size[0] - joints[i][:, :, 0] - 1
|
||||
results['img'], results['mask'], results[
|
||||
'joints'] = image, mask, joints
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class BottomUpRandomAffine:
|
||||
"""Data augmentation with random scaling & rotating.
|
||||
|
||||
Args:
|
||||
rot_factor (int): Rotating to [-rotation_factor, rotation_factor]
|
||||
scale_factor (float): Scaling to [1-scale_factor, 1+scale_factor]
|
||||
scale_type: wrt ``long`` or ``short`` length of the image.
|
||||
trans_factor: Translation factor.
|
||||
use_udp (bool): To use unbiased data processing.
|
||||
Paper ref: Huang et al. The Devil is in the Details: Delving into
|
||||
Unbiased Data Processing for Human Pose Estimation (CVPR 2020).
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
rot_factor,
|
||||
scale_factor,
|
||||
scale_type,
|
||||
trans_factor,
|
||||
use_udp=False):
|
||||
self.max_rotation = rot_factor
|
||||
self.min_scale = scale_factor[0]
|
||||
self.max_scale = scale_factor[1]
|
||||
self.scale_type = scale_type
|
||||
self.trans_factor = trans_factor
|
||||
self.use_udp = use_udp
|
||||
|
||||
def _get_scale(self, image_size, resized_size):
|
||||
w, h = image_size
|
||||
w_resized, h_resized = resized_size
|
||||
if w / w_resized < h / h_resized:
|
||||
if self.scale_type == 'long':
|
||||
w_pad = h / h_resized * w_resized
|
||||
h_pad = h
|
||||
elif self.scale_type == 'short':
|
||||
w_pad = w
|
||||
h_pad = w / w_resized * h_resized
|
||||
else:
|
||||
raise ValueError(f'Unknown scale type: {self.scale_type}')
|
||||
else:
|
||||
if self.scale_type == 'long':
|
||||
w_pad = w
|
||||
h_pad = w / w_resized * h_resized
|
||||
elif self.scale_type == 'short':
|
||||
w_pad = h / h_resized * w_resized
|
||||
h_pad = h
|
||||
else:
|
||||
raise ValueError(f'Unknown scale type: {self.scale_type}')
|
||||
|
||||
scale = np.array([w_pad, h_pad], dtype=np.float32)
|
||||
|
||||
return scale
|
||||
|
||||
def __call__(self, results):
|
||||
"""Perform data augmentation with random scaling & rotating."""
|
||||
image, mask, joints = results['img'], results['mask'], results[
|
||||
'joints']
|
||||
|
||||
self.input_size = results['ann_info']['image_size']
|
||||
if not isinstance(self.input_size, np.ndarray):
|
||||
self.input_size = np.array(self.input_size)
|
||||
if self.input_size.size > 1:
|
||||
assert len(self.input_size) == 2
|
||||
else:
|
||||
self.input_size = [self.input_size, self.input_size]
|
||||
self.output_size = results['ann_info']['heatmap_size']
|
||||
|
||||
assert isinstance(mask, list)
|
||||
assert isinstance(joints, list)
|
||||
assert len(mask) == len(joints)
|
||||
assert len(mask) == len(self.output_size), (len(mask),
|
||||
len(self.output_size),
|
||||
self.output_size)
|
||||
|
||||
height, width = image.shape[:2]
|
||||
if self.use_udp:
|
||||
center = np.array(((width - 1.0) / 2, (height - 1.0) / 2))
|
||||
else:
|
||||
center = np.array((width / 2, height / 2))
|
||||
|
||||
img_scale = np.array([width, height], dtype=np.float32)
|
||||
aug_scale = np.random.random() * (self.max_scale - self.min_scale) \
|
||||
+ self.min_scale
|
||||
img_scale *= aug_scale
|
||||
aug_rot = (np.random.random() * 2 - 1) * self.max_rotation
|
||||
|
||||
if self.trans_factor > 0:
|
||||
dx = np.random.randint(-self.trans_factor * img_scale[0] / 200.0,
|
||||
self.trans_factor * img_scale[0] / 200.0)
|
||||
dy = np.random.randint(-self.trans_factor * img_scale[1] / 200.0,
|
||||
self.trans_factor * img_scale[1] / 200.0)
|
||||
|
||||
center[0] += dx
|
||||
center[1] += dy
|
||||
if self.use_udp:
|
||||
for i, _output_size in enumerate(self.output_size):
|
||||
if not isinstance(_output_size, np.ndarray):
|
||||
_output_size = np.array(_output_size)
|
||||
if _output_size.size > 1:
|
||||
assert len(_output_size) == 2
|
||||
else:
|
||||
_output_size = [_output_size, _output_size]
|
||||
|
||||
scale = self._get_scale(img_scale, _output_size)
|
||||
|
||||
trans = get_warp_matrix(
|
||||
theta=aug_rot,
|
||||
size_input=center * 2.0,
|
||||
size_dst=np.array(
|
||||
(_output_size[0], _output_size[1]), dtype=np.float32) -
|
||||
1.0,
|
||||
size_target=scale)
|
||||
mask[i] = cv2.warpAffine(
|
||||
(mask[i] * 255).astype(np.uint8),
|
||||
trans, (int(_output_size[0]), int(_output_size[1])),
|
||||
flags=cv2.INTER_LINEAR) / 255
|
||||
mask[i] = (mask[i] > 0.5).astype(np.float32)
|
||||
joints[i][:, :, 0:2] = \
|
||||
warp_affine_joints(joints[i][:, :, 0:2].copy(), trans)
|
||||
if results['ann_info']['scale_aware_sigma']:
|
||||
joints[i][:, :, 3] = joints[i][:, :, 3] / aug_scale
|
||||
scale = self._get_scale(img_scale, self.input_size)
|
||||
mat_input = get_warp_matrix(
|
||||
theta=aug_rot,
|
||||
size_input=center * 2.0,
|
||||
size_dst=np.array((self.input_size[0], self.input_size[1]),
|
||||
dtype=np.float32) - 1.0,
|
||||
size_target=scale)
|
||||
image = cv2.warpAffine(
|
||||
image,
|
||||
mat_input, (int(self.input_size[0]), int(self.input_size[1])),
|
||||
flags=cv2.INTER_LINEAR)
|
||||
else:
|
||||
for i, _output_size in enumerate(self.output_size):
|
||||
if not isinstance(_output_size, np.ndarray):
|
||||
_output_size = np.array(_output_size)
|
||||
if _output_size.size > 1:
|
||||
assert len(_output_size) == 2
|
||||
else:
|
||||
_output_size = [_output_size, _output_size]
|
||||
scale = self._get_scale(img_scale, _output_size)
|
||||
mat_output = get_affine_transform(
|
||||
center=center,
|
||||
scale=scale / 200.0,
|
||||
rot=aug_rot,
|
||||
output_size=_output_size)
|
||||
mask[i] = cv2.warpAffine(
|
||||
(mask[i] * 255).astype(np.uint8), mat_output,
|
||||
(int(_output_size[0]), int(_output_size[1]))) / 255
|
||||
mask[i] = (mask[i] > 0.5).astype(np.float32)
|
||||
|
||||
joints[i][:, :, 0:2] = \
|
||||
warp_affine_joints(joints[i][:, :, 0:2], mat_output)
|
||||
if results['ann_info']['scale_aware_sigma']:
|
||||
joints[i][:, :, 3] = joints[i][:, :, 3] / aug_scale
|
||||
|
||||
scale = self._get_scale(img_scale, self.input_size)
|
||||
mat_input = get_affine_transform(
|
||||
center=center,
|
||||
scale=scale / 200.0,
|
||||
rot=aug_rot,
|
||||
output_size=self.input_size)
|
||||
image = cv2.warpAffine(image, mat_input, (int(
|
||||
self.input_size[0]), int(self.input_size[1])))
|
||||
|
||||
results['img'], results['mask'], results[
|
||||
'joints'] = image, mask, joints
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class BottomUpGenerateHeatmapTarget:
|
||||
"""Generate multi-scale heatmap target for bottom-up.
|
||||
|
||||
Args:
|
||||
sigma (int): Sigma of heatmap Gaussian
|
||||
max_num_people (int): Maximum number of people in an image
|
||||
use_udp (bool): To use unbiased data processing.
|
||||
Paper ref: Huang et al. The Devil is in the Details: Delving into
|
||||
Unbiased Data Processing for Human Pose Estimation (CVPR 2020).
|
||||
"""
|
||||
|
||||
def __init__(self, sigma, use_udp=False):
|
||||
self.sigma = sigma
|
||||
self.use_udp = use_udp
|
||||
|
||||
def _generate(self, num_joints, heatmap_size):
|
||||
"""Get heatmap generator."""
|
||||
heatmap_generator = [
|
||||
HeatmapGenerator(output_size, num_joints, self.sigma, self.use_udp)
|
||||
for output_size in heatmap_size
|
||||
]
|
||||
return heatmap_generator
|
||||
|
||||
def __call__(self, results):
|
||||
"""Generate multi-scale heatmap target for bottom-up."""
|
||||
heatmap_generator = \
|
||||
self._generate(results['ann_info']['num_joints'],
|
||||
results['ann_info']['heatmap_size'])
|
||||
target_list = list()
|
||||
joints_list = results['joints']
|
||||
|
||||
for scale_id in range(results['ann_info']['num_scales']):
|
||||
heatmaps = heatmap_generator[scale_id](joints_list[scale_id])
|
||||
target_list.append(heatmaps.astype(np.float32))
|
||||
results['target'] = target_list
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class BottomUpGenerateTarget:
|
||||
"""Generate multi-scale heatmap target for associate embedding.
|
||||
|
||||
Args:
|
||||
sigma (int): Sigma of heatmap Gaussian
|
||||
max_num_people (int): Maximum number of people in an image
|
||||
use_udp (bool): To use unbiased data processing.
|
||||
Paper ref: Huang et al. The Devil is in the Details: Delving into
|
||||
Unbiased Data Processing for Human Pose Estimation (CVPR 2020).
|
||||
"""
|
||||
|
||||
def __init__(self, sigma, max_num_people, use_udp=False):
|
||||
self.sigma = sigma
|
||||
self.max_num_people = max_num_people
|
||||
self.use_udp = use_udp
|
||||
|
||||
def _generate(self, num_joints, heatmap_size):
|
||||
"""Get heatmap generator and joint encoder."""
|
||||
heatmap_generator = [
|
||||
HeatmapGenerator(output_size, num_joints, self.sigma, self.use_udp)
|
||||
for output_size in heatmap_size
|
||||
]
|
||||
joints_encoder = [
|
||||
JointsEncoder(self.max_num_people, num_joints, output_size, True)
|
||||
for output_size in heatmap_size
|
||||
]
|
||||
return heatmap_generator, joints_encoder
|
||||
|
||||
def __call__(self, results):
|
||||
"""Generate multi-scale heatmap target for bottom-up."""
|
||||
heatmap_generator, joints_encoder = \
|
||||
self._generate(results['ann_info']['num_joints'],
|
||||
results['ann_info']['heatmap_size'])
|
||||
target_list = list()
|
||||
mask_list, joints_list = results['mask'], results['joints']
|
||||
|
||||
for scale_id in range(results['ann_info']['num_scales']):
|
||||
target_t = heatmap_generator[scale_id](joints_list[scale_id])
|
||||
joints_t = joints_encoder[scale_id](joints_list[scale_id])
|
||||
|
||||
target_list.append(target_t.astype(np.float32))
|
||||
mask_list[scale_id] = mask_list[scale_id].astype(np.float32)
|
||||
joints_list[scale_id] = joints_t.astype(np.int32)
|
||||
|
||||
results['masks'], results['joints'] = mask_list, joints_list
|
||||
results['targets'] = target_list
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class BottomUpGeneratePAFTarget:
|
||||
"""Generate multi-scale heatmaps and part affinity fields (PAF) target for
|
||||
bottom-up. Paper ref: Cao et al. Realtime Multi-Person 2D Human Pose
|
||||
Estimation using Part Affinity Fields (CVPR 2017).
|
||||
|
||||
Args:
|
||||
limb_width (int): Limb width of part affinity fields
|
||||
"""
|
||||
|
||||
def __init__(self, limb_width, skeleton=None):
|
||||
self.limb_width = limb_width
|
||||
self.skeleton = skeleton
|
||||
|
||||
def _generate(self, heatmap_size, skeleton):
|
||||
"""Get PAF generator."""
|
||||
paf_generator = [
|
||||
PAFGenerator(output_size, self.limb_width, skeleton)
|
||||
for output_size in heatmap_size
|
||||
]
|
||||
return paf_generator
|
||||
|
||||
def __call__(self, results):
|
||||
"""Generate multi-scale part affinity fields for bottom-up."""
|
||||
if self.skeleton is None:
|
||||
assert results['ann_info']['skeleton'] is not None
|
||||
self.skeleton = results['ann_info']['skeleton']
|
||||
|
||||
paf_generator = \
|
||||
self._generate(results['ann_info']['heatmap_size'],
|
||||
self.skeleton)
|
||||
target_list = list()
|
||||
joints_list = results['joints']
|
||||
|
||||
for scale_id in range(results['ann_info']['num_scales']):
|
||||
pafs = paf_generator[scale_id](joints_list[scale_id])
|
||||
target_list.append(pafs.astype(np.float32))
|
||||
|
||||
results['target'] = target_list
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class BottomUpGetImgSize:
|
||||
"""Get multi-scale image sizes for bottom-up, including base_size and
|
||||
test_scale_factor. Keep the ratio and the image is resized to
|
||||
`results['ann_info']['image_size']×current_scale`.
|
||||
|
||||
Args:
|
||||
test_scale_factor (List[float]): Multi scale
|
||||
current_scale (int): default 1
|
||||
use_udp (bool): To use unbiased data processing.
|
||||
Paper ref: Huang et al. The Devil is in the Details: Delving into
|
||||
Unbiased Data Processing for Human Pose Estimation (CVPR 2020).
|
||||
"""
|
||||
|
||||
def __init__(self, test_scale_factor, current_scale=1, use_udp=False):
|
||||
self.test_scale_factor = test_scale_factor
|
||||
self.min_scale = min(test_scale_factor)
|
||||
self.current_scale = current_scale
|
||||
self.use_udp = use_udp
|
||||
|
||||
def __call__(self, results):
|
||||
"""Get multi-scale image sizes for bottom-up."""
|
||||
input_size = results['ann_info']['image_size']
|
||||
if not isinstance(input_size, np.ndarray):
|
||||
input_size = np.array(input_size)
|
||||
if input_size.size > 1:
|
||||
assert len(input_size) == 2
|
||||
else:
|
||||
input_size = np.array([input_size, input_size], dtype=np.int)
|
||||
img = results['img']
|
||||
|
||||
h, w, _ = img.shape
|
||||
|
||||
# calculate the size for min_scale
|
||||
min_input_w = _ceil_to_multiples_of(self.min_scale * input_size[0], 64)
|
||||
min_input_h = _ceil_to_multiples_of(self.min_scale * input_size[1], 64)
|
||||
if w < h:
|
||||
w_resized = int(min_input_w * self.current_scale / self.min_scale)
|
||||
h_resized = int(
|
||||
_ceil_to_multiples_of(min_input_w / w * h, 64) *
|
||||
self.current_scale / self.min_scale)
|
||||
if self.use_udp:
|
||||
scale_w = w - 1.0
|
||||
scale_h = (h_resized - 1.0) / (w_resized - 1.0) * (w - 1.0)
|
||||
else:
|
||||
scale_w = w / 200.0
|
||||
scale_h = h_resized / w_resized * w / 200.0
|
||||
else:
|
||||
h_resized = int(min_input_h * self.current_scale / self.min_scale)
|
||||
w_resized = int(
|
||||
_ceil_to_multiples_of(min_input_h / h * w, 64) *
|
||||
self.current_scale / self.min_scale)
|
||||
if self.use_udp:
|
||||
scale_h = h - 1.0
|
||||
scale_w = (w_resized - 1.0) / (h_resized - 1.0) * (h - 1.0)
|
||||
else:
|
||||
scale_h = h / 200.0
|
||||
scale_w = w_resized / h_resized * h / 200.0
|
||||
if self.use_udp:
|
||||
center = (scale_w / 2.0, scale_h / 2.0)
|
||||
else:
|
||||
center = np.array([round(w / 2.0), round(h / 2.0)])
|
||||
results['ann_info']['test_scale_factor'] = self.test_scale_factor
|
||||
results['ann_info']['base_size'] = (w_resized, h_resized)
|
||||
results['ann_info']['center'] = center
|
||||
results['ann_info']['scale'] = np.array([scale_w, scale_h])
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class BottomUpResizeAlign:
|
||||
"""Resize multi-scale size and align transform for bottom-up.
|
||||
|
||||
Args:
|
||||
transforms (List): ToTensor & Normalize
|
||||
use_udp (bool): To use unbiased data processing.
|
||||
Paper ref: Huang et al. The Devil is in the Details: Delving into
|
||||
Unbiased Data Processing for Human Pose Estimation (CVPR 2020).
|
||||
"""
|
||||
|
||||
def __init__(self, transforms, use_udp=False):
|
||||
self.transforms = Compose(transforms)
|
||||
if use_udp:
|
||||
self._resize_align_multi_scale = _resize_align_multi_scale_udp
|
||||
else:
|
||||
self._resize_align_multi_scale = _resize_align_multi_scale
|
||||
|
||||
def __call__(self, results):
|
||||
"""Resize multi-scale size and align transform for bottom-up."""
|
||||
input_size = results['ann_info']['image_size']
|
||||
if not isinstance(input_size, np.ndarray):
|
||||
input_size = np.array(input_size)
|
||||
if input_size.size > 1:
|
||||
assert len(input_size) == 2
|
||||
else:
|
||||
input_size = np.array([input_size, input_size], dtype=np.int)
|
||||
test_scale_factor = results['ann_info']['test_scale_factor']
|
||||
aug_data = []
|
||||
|
||||
for _, s in enumerate(sorted(test_scale_factor, reverse=True)):
|
||||
_results = results.copy()
|
||||
image_resized, _, _ = self._resize_align_multi_scale(
|
||||
_results['img'], input_size, s, min(test_scale_factor))
|
||||
_results['img'] = image_resized
|
||||
_results = self.transforms(_results)
|
||||
transformed_img = _results['img'].unsqueeze(0)
|
||||
aug_data.append(transformed_img)
|
||||
|
||||
results['ann_info']['aug_data'] = aug_data
|
||||
|
||||
return results
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import numpy as np
|
||||
|
||||
from mmpose.datasets.builder import PIPELINES
|
||||
from .top_down_transform import TopDownRandomFlip
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class HandRandomFlip(TopDownRandomFlip):
|
||||
"""Data augmentation with random image flip. A child class of
|
||||
TopDownRandomFlip.
|
||||
|
||||
Required keys: 'img', 'joints_3d', 'joints_3d_visible', 'center',
|
||||
'hand_type', 'rel_root_depth' and 'ann_info'.
|
||||
|
||||
Modifies key: 'img', 'joints_3d', 'joints_3d_visible', 'center',
|
||||
'hand_type', 'rel_root_depth'.
|
||||
|
||||
Args:
|
||||
flip_prob (float): Probability of flip.
|
||||
"""
|
||||
|
||||
def __call__(self, results):
|
||||
"""Perform data augmentation with random image flip."""
|
||||
# base flip augmentation
|
||||
super().__call__(results)
|
||||
|
||||
# flip hand type and root depth
|
||||
hand_type = results['hand_type']
|
||||
rel_root_depth = results['rel_root_depth']
|
||||
flipped = results['flipped']
|
||||
if flipped:
|
||||
hand_type[0], hand_type[1] = hand_type[1], hand_type[0]
|
||||
rel_root_depth = -rel_root_depth
|
||||
results['hand_type'] = hand_type
|
||||
results['rel_root_depth'] = rel_root_depth
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class HandGenerateRelDepthTarget:
|
||||
"""Generate the target relative root depth.
|
||||
|
||||
Required keys: 'rel_root_depth', 'rel_root_valid', 'ann_info'.
|
||||
|
||||
Modified keys: 'target', 'target_weight'.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __call__(self, results):
|
||||
"""Generate the target heatmap."""
|
||||
rel_root_depth = results['rel_root_depth']
|
||||
rel_root_valid = results['rel_root_valid']
|
||||
cfg = results['ann_info']
|
||||
D = cfg['heatmap_size_root']
|
||||
root_depth_bound = cfg['root_depth_bound']
|
||||
target = (rel_root_depth / root_depth_bound + 0.5) * D
|
||||
target_weight = rel_root_valid * (target >= 0) * (target <= D)
|
||||
results['target'] = target * np.ones(1, dtype=np.float32)
|
||||
results['target_weight'] = target_weight * np.ones(1, dtype=np.float32)
|
||||
return results
|
||||
@@ -0,0 +1,91 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import mmcv
|
||||
import numpy as np
|
||||
|
||||
from ..builder import PIPELINES
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class LoadImageFromFile:
|
||||
"""Loading image(s) from file.
|
||||
|
||||
Required key: "image_file".
|
||||
|
||||
Added key: "img".
|
||||
|
||||
Args:
|
||||
to_float32 (bool): Whether to convert the loaded image to a float32
|
||||
numpy array. If set to False, the loaded image is an uint8 array.
|
||||
Defaults to False.
|
||||
color_type (str): Flags specifying the color type of a loaded image,
|
||||
candidates are 'color', 'grayscale' and 'unchanged'.
|
||||
channel_order (str): Order of channel, candidates are 'bgr' and 'rgb'.
|
||||
file_client_args (dict): Arguments to instantiate a FileClient.
|
||||
See :class:`mmcv.fileio.FileClient` for details.
|
||||
Defaults to ``dict(backend='disk')``.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
to_float32=False,
|
||||
color_type='color',
|
||||
channel_order='rgb',
|
||||
file_client_args=dict(backend='disk')):
|
||||
self.to_float32 = to_float32
|
||||
self.color_type = color_type
|
||||
self.channel_order = channel_order
|
||||
self.file_client_args = file_client_args.copy()
|
||||
self.file_client = None
|
||||
|
||||
def _read_image(self, path):
|
||||
img_bytes = self.file_client.get(path)
|
||||
img = mmcv.imfrombytes(
|
||||
img_bytes, flag=self.color_type, channel_order=self.channel_order)
|
||||
if img is None:
|
||||
raise ValueError(f'Fail to read {path}')
|
||||
if self.to_float32:
|
||||
img = img.astype(np.float32)
|
||||
return img
|
||||
|
||||
def __call__(self, results):
|
||||
"""Loading image(s) from file."""
|
||||
if self.file_client is None:
|
||||
self.file_client = mmcv.FileClient(**self.file_client_args)
|
||||
|
||||
image_file = results.get('image_file', None)
|
||||
|
||||
if isinstance(image_file, (list, tuple)):
|
||||
# Load images from a list of paths
|
||||
results['img'] = [self._read_image(path) for path in image_file]
|
||||
elif image_file is not None:
|
||||
# Load single image from path
|
||||
results['img'] = self._read_image(image_file)
|
||||
else:
|
||||
if 'img' not in results:
|
||||
# If `image_file`` is not in results, check the `img` exists
|
||||
# and format the image. This for compatibility when the image
|
||||
# is manually set outside the pipeline.
|
||||
raise KeyError('Either `image_file` or `img` should exist in '
|
||||
'results.')
|
||||
assert isinstance(results['img'], np.ndarray)
|
||||
if self.color_type == 'color' and self.channel_order == 'rgb':
|
||||
# The original results['img'] is assumed to be image(s) in BGR
|
||||
# order, so we convert the color according to the arguments.
|
||||
if results['img'].ndim == 3:
|
||||
results['img'] = mmcv.bgr2rgb(results['img'])
|
||||
elif results['img'].ndim == 4:
|
||||
results['img'] = np.concatenate(
|
||||
[mmcv.bgr2rgb(img) for img in results['img']], axis=0)
|
||||
else:
|
||||
raise ValueError('results["img"] has invalid shape '
|
||||
f'{results["img"].shape}')
|
||||
|
||||
results['image_file'] = None
|
||||
|
||||
return results
|
||||
|
||||
def __repr__(self):
|
||||
repr_str = (f'{self.__class__.__name__}('
|
||||
f'to_float32={self.to_float32}, '
|
||||
f"color_type='{self.color_type}', "
|
||||
f'file_client_args={self.file_client_args})')
|
||||
return repr_str
|
||||
+399
@@ -0,0 +1,399 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import cv2
|
||||
import mmcv
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from mmpose.core.post_processing import (affine_transform, fliplr_joints,
|
||||
get_affine_transform)
|
||||
from mmpose.datasets.builder import PIPELINES
|
||||
|
||||
|
||||
def _flip_smpl_pose(pose):
|
||||
"""Flip SMPL pose parameters horizontally.
|
||||
|
||||
Args:
|
||||
pose (np.ndarray([72])): SMPL pose parameters
|
||||
|
||||
Returns:
|
||||
pose_flipped
|
||||
"""
|
||||
|
||||
flippedParts = [
|
||||
0, 1, 2, 6, 7, 8, 3, 4, 5, 9, 10, 11, 15, 16, 17, 12, 13, 14, 18, 19,
|
||||
20, 24, 25, 26, 21, 22, 23, 27, 28, 29, 33, 34, 35, 30, 31, 32, 36, 37,
|
||||
38, 42, 43, 44, 39, 40, 41, 45, 46, 47, 51, 52, 53, 48, 49, 50, 57, 58,
|
||||
59, 54, 55, 56, 63, 64, 65, 60, 61, 62, 69, 70, 71, 66, 67, 68
|
||||
]
|
||||
pose_flipped = pose[flippedParts]
|
||||
# Negate the second and the third dimension of the axis-angle
|
||||
pose_flipped[1::3] = -pose_flipped[1::3]
|
||||
pose_flipped[2::3] = -pose_flipped[2::3]
|
||||
return pose_flipped
|
||||
|
||||
|
||||
def _flip_iuv(iuv, uv_type='BF'):
|
||||
"""Flip IUV image horizontally.
|
||||
|
||||
Note:
|
||||
IUV image height: H
|
||||
IUV image width: W
|
||||
|
||||
Args:
|
||||
iuv np.ndarray([H, W, 3]): IUV image
|
||||
uv_type (str): The type of the UV map.
|
||||
Candidate values:
|
||||
'DP': The UV map used in DensePose project.
|
||||
'SMPL': The default UV map of SMPL model.
|
||||
'BF': The UV map used in DecoMR project.
|
||||
Default: 'BF'
|
||||
|
||||
Returns:
|
||||
iuv_flipped np.ndarray([H, W, 3]): Flipped IUV image
|
||||
"""
|
||||
assert uv_type in ['DP', 'SMPL', 'BF']
|
||||
if uv_type == 'BF':
|
||||
iuv_flipped = iuv[:, ::-1, :]
|
||||
iuv_flipped[:, :, 1] = 255 - iuv_flipped[:, :, 1]
|
||||
else:
|
||||
# The flip of other UV map is complex, not finished yet.
|
||||
raise NotImplementedError(
|
||||
f'The flip of {uv_type} UV map is not implemented yet.')
|
||||
|
||||
return iuv_flipped
|
||||
|
||||
|
||||
def _construct_rotation_matrix(rot, size=3):
|
||||
"""Construct the in-plane rotation matrix.
|
||||
|
||||
Args:
|
||||
rot (float): Rotation angle (degree).
|
||||
size (int): The size of the rotation matrix.
|
||||
Candidate Values: 2, 3. Defaults to 3.
|
||||
|
||||
Returns:
|
||||
rot_mat (np.ndarray([size, size]): Rotation matrix.
|
||||
"""
|
||||
rot_mat = np.eye(size, dtype=np.float32)
|
||||
if rot != 0:
|
||||
rot_rad = np.deg2rad(rot)
|
||||
sn, cs = np.sin(rot_rad), np.cos(rot_rad)
|
||||
rot_mat[0, :2] = [cs, -sn]
|
||||
rot_mat[1, :2] = [sn, cs]
|
||||
|
||||
return rot_mat
|
||||
|
||||
|
||||
def _rotate_joints_3d(joints_3d, rot):
|
||||
"""Rotate the 3D joints in the local coordinates.
|
||||
|
||||
Note:
|
||||
Joints number: K
|
||||
|
||||
Args:
|
||||
joints_3d (np.ndarray([K, 3])): Coordinates of keypoints.
|
||||
rot (float): Rotation angle (degree).
|
||||
|
||||
Returns:
|
||||
joints_3d_rotated
|
||||
"""
|
||||
# in-plane rotation
|
||||
# 3D joints are rotated counterclockwise,
|
||||
# so the rot angle is inversed.
|
||||
rot_mat = _construct_rotation_matrix(-rot, 3)
|
||||
|
||||
joints_3d_rotated = np.einsum('ij,kj->ki', rot_mat, joints_3d)
|
||||
joints_3d_rotated = joints_3d_rotated.astype('float32')
|
||||
return joints_3d_rotated
|
||||
|
||||
|
||||
def _rotate_smpl_pose(pose, rot):
|
||||
"""Rotate SMPL pose parameters. SMPL (https://smpl.is.tue.mpg.de/) is a 3D
|
||||
human model.
|
||||
|
||||
Args:
|
||||
pose (np.ndarray([72])): SMPL pose parameters
|
||||
rot (float): Rotation angle (degree).
|
||||
|
||||
Returns:
|
||||
pose_rotated
|
||||
"""
|
||||
pose_rotated = pose.copy()
|
||||
if rot != 0:
|
||||
rot_mat = _construct_rotation_matrix(-rot)
|
||||
orient = pose[:3]
|
||||
# find the rotation of the body in camera frame
|
||||
per_rdg, _ = cv2.Rodrigues(orient)
|
||||
# apply the global rotation to the global orientation
|
||||
res_rot, _ = cv2.Rodrigues(np.dot(rot_mat, per_rdg))
|
||||
pose_rotated[:3] = (res_rot.T)[0]
|
||||
|
||||
return pose_rotated
|
||||
|
||||
|
||||
def _flip_joints_3d(joints_3d, joints_3d_visible, flip_pairs):
|
||||
"""Flip human joints in 3D space horizontally.
|
||||
|
||||
Note:
|
||||
num_keypoints: K
|
||||
|
||||
Args:
|
||||
joints_3d (np.ndarray([K, 3])): Coordinates of keypoints.
|
||||
joints_3d_visible (np.ndarray([K, 1])): Visibility of keypoints.
|
||||
flip_pairs (list[tuple()]): Pairs of keypoints which are mirrored
|
||||
(for example, left ear -- right ear).
|
||||
|
||||
Returns:
|
||||
joints_3d_flipped, joints_3d_visible_flipped
|
||||
"""
|
||||
|
||||
assert len(joints_3d) == len(joints_3d_visible)
|
||||
|
||||
joints_3d_flipped = joints_3d.copy()
|
||||
joints_3d_visible_flipped = joints_3d_visible.copy()
|
||||
|
||||
# Swap left-right parts
|
||||
for left, right in flip_pairs:
|
||||
joints_3d_flipped[left, :] = joints_3d[right, :]
|
||||
joints_3d_flipped[right, :] = joints_3d[left, :]
|
||||
|
||||
joints_3d_visible_flipped[left, :] = joints_3d_visible[right, :]
|
||||
joints_3d_visible_flipped[right, :] = joints_3d_visible[left, :]
|
||||
|
||||
# Flip horizontally
|
||||
joints_3d_flipped[:, 0] = -joints_3d_flipped[:, 0]
|
||||
joints_3d_flipped = joints_3d_flipped * joints_3d_visible_flipped
|
||||
|
||||
return joints_3d_flipped, joints_3d_visible_flipped
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class LoadIUVFromFile:
|
||||
"""Loading IUV image from file."""
|
||||
|
||||
def __init__(self, to_float32=False):
|
||||
self.to_float32 = to_float32
|
||||
self.color_type = 'color'
|
||||
# channel relations: iuv->bgr
|
||||
self.channel_order = 'bgr'
|
||||
|
||||
def __call__(self, results):
|
||||
"""Loading image from file."""
|
||||
has_iuv = results['has_iuv']
|
||||
use_iuv = results['ann_info']['use_IUV']
|
||||
if has_iuv and use_iuv:
|
||||
iuv_file = results['iuv_file']
|
||||
iuv = mmcv.imread(iuv_file, self.color_type, self.channel_order)
|
||||
if iuv is None:
|
||||
raise ValueError(f'Fail to read {iuv_file}')
|
||||
else:
|
||||
has_iuv = 0
|
||||
iuv = None
|
||||
|
||||
results['has_iuv'] = has_iuv
|
||||
results['iuv'] = iuv
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class IUVToTensor:
|
||||
"""Transform IUV image to part index mask and uv coordinates image. The 3
|
||||
channels of IUV image means: part index, u coordinates, v coordinates.
|
||||
|
||||
Required key: 'iuv', 'ann_info'.
|
||||
Modifies key: 'part_index', 'uv_coordinates'.
|
||||
|
||||
Args:
|
||||
results (dict): contain all information about training.
|
||||
"""
|
||||
|
||||
def __call__(self, results):
|
||||
iuv = results['iuv']
|
||||
if iuv is None:
|
||||
H, W = results['ann_info']['iuv_size']
|
||||
part_index = torch.zeros([1, H, W], dtype=torch.long)
|
||||
uv_coordinates = torch.zeros([2, H, W], dtype=torch.float32)
|
||||
else:
|
||||
part_index = torch.LongTensor(iuv[:, :, 0])[None, :, :]
|
||||
uv_coordinates = torch.FloatTensor(iuv[:, :, 1:]) / 255
|
||||
uv_coordinates = uv_coordinates.permute(2, 0, 1)
|
||||
results['part_index'] = part_index
|
||||
results['uv_coordinates'] = uv_coordinates
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class MeshRandomChannelNoise:
|
||||
"""Data augmentation with random channel noise.
|
||||
|
||||
Required keys: 'img'
|
||||
Modifies key: 'img'
|
||||
|
||||
Args:
|
||||
noise_factor (float): Multiply each channel with
|
||||
a factor between``[1-scale_factor, 1+scale_factor]``
|
||||
"""
|
||||
|
||||
def __init__(self, noise_factor=0.4):
|
||||
self.noise_factor = noise_factor
|
||||
|
||||
def __call__(self, results):
|
||||
"""Perform data augmentation with random channel noise."""
|
||||
img = results['img']
|
||||
|
||||
# Each channel is multiplied with a number
|
||||
# in the area [1-self.noise_factor, 1+self.noise_factor]
|
||||
pn = np.random.uniform(1 - self.noise_factor, 1 + self.noise_factor,
|
||||
(1, 3))
|
||||
img = cv2.multiply(img, pn)
|
||||
|
||||
results['img'] = img
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class MeshRandomFlip:
|
||||
"""Data augmentation with random image flip.
|
||||
|
||||
Required keys: 'img', 'joints_2d','joints_2d_visible', 'joints_3d',
|
||||
'joints_3d_visible', 'center', 'pose', 'iuv' and 'ann_info'.
|
||||
Modifies key: 'img', 'joints_2d','joints_2d_visible', 'joints_3d',
|
||||
'joints_3d_visible', 'center', 'pose', 'iuv'.
|
||||
|
||||
Args:
|
||||
flip_prob (float): Probability of flip.
|
||||
"""
|
||||
|
||||
def __init__(self, flip_prob=0.5):
|
||||
self.flip_prob = flip_prob
|
||||
|
||||
def __call__(self, results):
|
||||
"""Perform data augmentation with random image flip."""
|
||||
if np.random.rand() > self.flip_prob:
|
||||
return results
|
||||
|
||||
img = results['img']
|
||||
joints_2d = results['joints_2d']
|
||||
joints_2d_visible = results['joints_2d_visible']
|
||||
joints_3d = results['joints_3d']
|
||||
joints_3d_visible = results['joints_3d_visible']
|
||||
pose = results['pose']
|
||||
center = results['center']
|
||||
|
||||
img = img[:, ::-1, :]
|
||||
pose = _flip_smpl_pose(pose)
|
||||
|
||||
joints_2d, joints_2d_visible = fliplr_joints(
|
||||
joints_2d, joints_2d_visible, img.shape[1],
|
||||
results['ann_info']['flip_pairs'])
|
||||
|
||||
joints_3d, joints_3d_visible = _flip_joints_3d(
|
||||
joints_3d, joints_3d_visible, results['ann_info']['flip_pairs'])
|
||||
center[0] = img.shape[1] - center[0] - 1
|
||||
|
||||
if 'iuv' in results.keys():
|
||||
iuv = results['iuv']
|
||||
if iuv is not None:
|
||||
iuv = _flip_iuv(iuv, results['ann_info']['uv_type'])
|
||||
results['iuv'] = iuv
|
||||
|
||||
results['img'] = img
|
||||
results['joints_2d'] = joints_2d
|
||||
results['joints_2d_visible'] = joints_2d_visible
|
||||
results['joints_3d'] = joints_3d
|
||||
results['joints_3d_visible'] = joints_3d_visible
|
||||
results['pose'] = pose
|
||||
results['center'] = center
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class MeshGetRandomScaleRotation:
|
||||
"""Data augmentation with random scaling & rotating.
|
||||
|
||||
Required key: 'scale'. Modifies key: 'scale' and 'rotation'.
|
||||
|
||||
Args:
|
||||
rot_factor (int): Rotating to ``[-2*rot_factor, 2*rot_factor]``.
|
||||
scale_factor (float): Scaling to ``[1-scale_factor, 1+scale_factor]``.
|
||||
rot_prob (float): Probability of random rotation.
|
||||
"""
|
||||
|
||||
def __init__(self, rot_factor=30, scale_factor=0.25, rot_prob=0.6):
|
||||
self.rot_factor = rot_factor
|
||||
self.scale_factor = scale_factor
|
||||
self.rot_prob = rot_prob
|
||||
|
||||
def __call__(self, results):
|
||||
"""Perform data augmentation with random scaling & rotating."""
|
||||
s = results['scale']
|
||||
|
||||
sf = self.scale_factor
|
||||
rf = self.rot_factor
|
||||
|
||||
s_factor = np.clip(np.random.randn() * sf + 1, 1 - sf, 1 + sf)
|
||||
s = s * s_factor
|
||||
|
||||
r_factor = np.clip(np.random.randn() * rf, -rf * 2, rf * 2)
|
||||
r = r_factor if np.random.rand() <= self.rot_prob else 0
|
||||
|
||||
results['scale'] = s
|
||||
results['rotation'] = r
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class MeshAffine:
|
||||
"""Affine transform the image to get input image. Affine transform the 2D
|
||||
keypoints, 3D kepoints and IUV image too.
|
||||
|
||||
Required keys: 'img', 'joints_2d','joints_2d_visible', 'joints_3d',
|
||||
'joints_3d_visible', 'pose', 'iuv', 'ann_info','scale', 'rotation' and
|
||||
'center'. Modifies key: 'img', 'joints_2d','joints_2d_visible',
|
||||
'joints_3d', 'pose', 'iuv'.
|
||||
"""
|
||||
|
||||
def __call__(self, results):
|
||||
image_size = results['ann_info']['image_size']
|
||||
|
||||
img = results['img']
|
||||
joints_2d = results['joints_2d']
|
||||
joints_2d_visible = results['joints_2d_visible']
|
||||
joints_3d = results['joints_3d']
|
||||
pose = results['pose']
|
||||
|
||||
c = results['center']
|
||||
s = results['scale']
|
||||
r = results['rotation']
|
||||
trans = get_affine_transform(c, s, r, image_size)
|
||||
|
||||
img = cv2.warpAffine(
|
||||
img,
|
||||
trans, (int(image_size[0]), int(image_size[1])),
|
||||
flags=cv2.INTER_LINEAR)
|
||||
|
||||
for i in range(results['ann_info']['num_joints']):
|
||||
if joints_2d_visible[i, 0] > 0.0:
|
||||
joints_2d[i] = affine_transform(joints_2d[i], trans)
|
||||
|
||||
joints_3d = _rotate_joints_3d(joints_3d, r)
|
||||
pose = _rotate_smpl_pose(pose, r)
|
||||
|
||||
results['img'] = img
|
||||
results['joints_2d'] = joints_2d
|
||||
results['joints_2d_visible'] = joints_2d_visible
|
||||
results['joints_3d'] = joints_3d
|
||||
results['pose'] = pose
|
||||
|
||||
if 'iuv' in results.keys():
|
||||
iuv = results['iuv']
|
||||
if iuv is not None:
|
||||
iuv_size = results['ann_info']['iuv_size']
|
||||
iuv = cv2.warpAffine(
|
||||
iuv,
|
||||
trans, (int(iuv_size[0]), int(iuv_size[1])),
|
||||
flags=cv2.INTER_NEAREST)
|
||||
results['iuv'] = iuv
|
||||
|
||||
return results
|
||||
+643
@@ -0,0 +1,643 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
|
||||
import mmcv
|
||||
import numpy as np
|
||||
import torch
|
||||
from mmcv.utils import build_from_cfg
|
||||
|
||||
from mmpose.core.camera import CAMERAS
|
||||
from mmpose.core.post_processing import fliplr_regression
|
||||
from mmpose.datasets.builder import PIPELINES
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class GetRootCenteredPose:
|
||||
"""Zero-center the pose around a given root joint. Optionally, the root
|
||||
joint can be removed from the original pose and stored as a separate item.
|
||||
|
||||
Note that the root-centered joints may no longer align with some annotation
|
||||
information (e.g. flip_pairs, num_joints, inference_channel, etc.) due to
|
||||
the removal of the root joint.
|
||||
|
||||
Args:
|
||||
item (str): The name of the pose to apply root-centering.
|
||||
root_index (int): Root joint index in the pose.
|
||||
visible_item (str): The name of the visibility item.
|
||||
remove_root (bool): If true, remove the root joint from the pose
|
||||
root_name (str): Optional. If not none, it will be used as the key to
|
||||
store the root position separated from the original pose.
|
||||
|
||||
Required keys:
|
||||
item
|
||||
|
||||
Modified keys:
|
||||
item, visible_item, root_name
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
item,
|
||||
root_index,
|
||||
visible_item=None,
|
||||
remove_root=False,
|
||||
root_name=None):
|
||||
self.item = item
|
||||
self.root_index = root_index
|
||||
self.remove_root = remove_root
|
||||
self.root_name = root_name
|
||||
self.visible_item = visible_item
|
||||
|
||||
def __call__(self, results):
|
||||
assert self.item in results
|
||||
joints = results[self.item]
|
||||
root_idx = self.root_index
|
||||
|
||||
assert joints.ndim >= 2 and joints.shape[-2] > root_idx,\
|
||||
f'Got invalid joint shape {joints.shape}'
|
||||
|
||||
root = joints[..., root_idx:root_idx + 1, :]
|
||||
joints = joints - root
|
||||
|
||||
results[self.item] = joints
|
||||
if self.root_name is not None:
|
||||
results[self.root_name] = root
|
||||
|
||||
if self.remove_root:
|
||||
results[self.item] = np.delete(
|
||||
results[self.item], root_idx, axis=-2)
|
||||
if self.visible_item is not None:
|
||||
assert self.visible_item in results
|
||||
results[self.visible_item] = np.delete(
|
||||
results[self.visible_item], root_idx, axis=-2)
|
||||
# Add a flag to avoid latter transforms that rely on the root
|
||||
# joint or the original joint index
|
||||
results[f'{self.item}_root_removed'] = True
|
||||
|
||||
# Save the root index which is necessary to restore the global pose
|
||||
if self.root_name is not None:
|
||||
results[f'{self.root_name}_index'] = self.root_index
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class NormalizeJointCoordinate:
|
||||
"""Normalize the joint coordinate with given mean and std.
|
||||
|
||||
Args:
|
||||
item (str): The name of the pose to normalize.
|
||||
mean (array): Mean values of joint coordinates in shape [K, C].
|
||||
std (array): Std values of joint coordinates in shape [K, C].
|
||||
norm_param_file (str): Optionally load a dict containing `mean` and
|
||||
`std` from a file using `mmcv.load`.
|
||||
|
||||
Required keys:
|
||||
item
|
||||
|
||||
Modified keys:
|
||||
item
|
||||
"""
|
||||
|
||||
def __init__(self, item, mean=None, std=None, norm_param_file=None):
|
||||
self.item = item
|
||||
self.norm_param_file = norm_param_file
|
||||
if norm_param_file is not None:
|
||||
norm_param = mmcv.load(norm_param_file)
|
||||
assert 'mean' in norm_param and 'std' in norm_param
|
||||
mean = norm_param['mean']
|
||||
std = norm_param['std']
|
||||
else:
|
||||
assert mean is not None
|
||||
assert std is not None
|
||||
|
||||
self.mean = np.array(mean, dtype=np.float32)
|
||||
self.std = np.array(std, dtype=np.float32)
|
||||
|
||||
def __call__(self, results):
|
||||
assert self.item in results
|
||||
results[self.item] = (results[self.item] - self.mean) / self.std
|
||||
results[f'{self.item}_mean'] = self.mean.copy()
|
||||
results[f'{self.item}_std'] = self.std.copy()
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class ImageCoordinateNormalization:
|
||||
"""Normalize the 2D joint coordinate with image width and height. Range [0,
|
||||
w] is mapped to [-1, 1], while preserving the aspect ratio.
|
||||
|
||||
Args:
|
||||
item (str|list[str]): The name of the pose to normalize.
|
||||
norm_camera (bool): Whether to normalize camera intrinsics.
|
||||
Default: False.
|
||||
camera_param (dict|None): The camera parameter dict. See the camera
|
||||
class definition for more details. If None is given, the camera
|
||||
parameter will be obtained during processing of each data sample
|
||||
with the key "camera_param".
|
||||
|
||||
Required keys:
|
||||
item
|
||||
|
||||
Modified keys:
|
||||
item (, camera_param)
|
||||
"""
|
||||
|
||||
def __init__(self, item, norm_camera=False, camera_param=None):
|
||||
self.item = item
|
||||
if isinstance(self.item, str):
|
||||
self.item = [self.item]
|
||||
|
||||
self.norm_camera = norm_camera
|
||||
|
||||
if camera_param is None:
|
||||
self.static_camera = False
|
||||
else:
|
||||
self.static_camera = True
|
||||
self.camera_param = camera_param
|
||||
|
||||
def __call__(self, results):
|
||||
center = np.array(
|
||||
[0.5 * results['image_width'], 0.5 * results['image_height']],
|
||||
dtype=np.float32)
|
||||
scale = np.array(0.5 * results['image_width'], dtype=np.float32)
|
||||
|
||||
for item in self.item:
|
||||
results[item] = (results[item] - center) / scale
|
||||
|
||||
if self.norm_camera:
|
||||
if self.static_camera:
|
||||
camera_param = copy.deepcopy(self.camera_param)
|
||||
else:
|
||||
assert 'camera_param' in results, \
|
||||
'Camera parameters are missing.'
|
||||
camera_param = results['camera_param']
|
||||
assert 'f' in camera_param and 'c' in camera_param
|
||||
camera_param['f'] = camera_param['f'] / scale
|
||||
camera_param['c'] = (camera_param['c'] - center[:, None]) / scale
|
||||
if 'camera_param' not in results:
|
||||
results['camera_param'] = dict()
|
||||
results['camera_param'].update(camera_param)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class CollectCameraIntrinsics:
|
||||
"""Store camera intrinsics in a 1-dim array, including f, c, k, p.
|
||||
|
||||
Args:
|
||||
camera_param (dict|None): The camera parameter dict. See the camera
|
||||
class definition for more details. If None is given, the camera
|
||||
parameter will be obtained during processing of each data sample
|
||||
with the key "camera_param".
|
||||
need_distortion (bool): Whether need distortion parameters k and p.
|
||||
Default: True.
|
||||
|
||||
Required keys:
|
||||
camera_param (if camera parameters are not given in initialization)
|
||||
|
||||
Modified keys:
|
||||
intrinsics
|
||||
"""
|
||||
|
||||
def __init__(self, camera_param=None, need_distortion=True):
|
||||
if camera_param is None:
|
||||
self.static_camera = False
|
||||
else:
|
||||
self.static_camera = True
|
||||
self.camera_param = camera_param
|
||||
self.need_distortion = need_distortion
|
||||
|
||||
def __call__(self, results):
|
||||
if self.static_camera:
|
||||
camera_param = copy.deepcopy(self.camera_param)
|
||||
else:
|
||||
assert 'camera_param' in results, 'Camera parameters are missing.'
|
||||
camera_param = results['camera_param']
|
||||
assert 'f' in camera_param and 'c' in camera_param
|
||||
intrinsics = np.concatenate(
|
||||
[camera_param['f'].reshape(2), camera_param['c'].reshape(2)])
|
||||
if self.need_distortion:
|
||||
assert 'k' in camera_param and 'p' in camera_param
|
||||
intrinsics = np.concatenate([
|
||||
intrinsics, camera_param['k'].reshape(3),
|
||||
camera_param['p'].reshape(2)
|
||||
])
|
||||
results['intrinsics'] = intrinsics
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class CameraProjection:
|
||||
"""Apply camera projection to joint coordinates.
|
||||
|
||||
Args:
|
||||
item (str): The name of the pose to apply camera projection.
|
||||
mode (str): The type of camera projection, supported options are
|
||||
|
||||
- world_to_camera
|
||||
- world_to_pixel
|
||||
- camera_to_world
|
||||
- camera_to_pixel
|
||||
output_name (str|None): The name of the projected pose. If None
|
||||
(default) is given, the projected pose will be stored in place.
|
||||
camera_type (str): The camera class name (should be registered in
|
||||
CAMERA).
|
||||
camera_param (dict|None): The camera parameter dict. See the camera
|
||||
class definition for more details. If None is given, the camera
|
||||
parameter will be obtained during processing of each data sample
|
||||
with the key "camera_param".
|
||||
|
||||
Required keys:
|
||||
|
||||
- item
|
||||
- camera_param (if camera parameters are not given in initialization)
|
||||
|
||||
Modified keys:
|
||||
output_name
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
item,
|
||||
mode,
|
||||
output_name=None,
|
||||
camera_type='SimpleCamera',
|
||||
camera_param=None):
|
||||
self.item = item
|
||||
self.mode = mode
|
||||
self.output_name = output_name
|
||||
self.camera_type = camera_type
|
||||
allowed_mode = {
|
||||
'world_to_camera',
|
||||
'world_to_pixel',
|
||||
'camera_to_world',
|
||||
'camera_to_pixel',
|
||||
}
|
||||
if mode not in allowed_mode:
|
||||
raise ValueError(
|
||||
f'Got invalid mode: {mode}, allowed modes are {allowed_mode}')
|
||||
|
||||
if camera_param is None:
|
||||
self.static_camera = False
|
||||
else:
|
||||
self.static_camera = True
|
||||
self.camera = self._build_camera(camera_param)
|
||||
|
||||
def _build_camera(self, param):
|
||||
cfgs = dict(type=self.camera_type, param=param)
|
||||
return build_from_cfg(cfgs, CAMERAS)
|
||||
|
||||
def __call__(self, results):
|
||||
assert self.item in results
|
||||
joints = results[self.item]
|
||||
|
||||
if self.static_camera:
|
||||
camera = self.camera
|
||||
else:
|
||||
assert 'camera_param' in results, 'Camera parameters are missing.'
|
||||
camera = self._build_camera(results['camera_param'])
|
||||
|
||||
if self.mode == 'world_to_camera':
|
||||
output = camera.world_to_camera(joints)
|
||||
elif self.mode == 'world_to_pixel':
|
||||
output = camera.world_to_pixel(joints)
|
||||
elif self.mode == 'camera_to_world':
|
||||
output = camera.camera_to_world(joints)
|
||||
elif self.mode == 'camera_to_pixel':
|
||||
output = camera.camera_to_pixel(joints)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
output_name = self.output_name
|
||||
if output_name is None:
|
||||
output_name = self.item
|
||||
|
||||
results[output_name] = output
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class RelativeJointRandomFlip:
|
||||
"""Data augmentation with random horizontal joint flip around a root joint.
|
||||
|
||||
Args:
|
||||
item (str|list[str]): The name of the pose to flip.
|
||||
flip_cfg (dict|list[dict]): Configurations of the fliplr_regression
|
||||
function. It should contain the following arguments:
|
||||
|
||||
- ``center_mode``: The mode to set the center location on the \
|
||||
x-axis to flip around.
|
||||
- ``center_x`` or ``center_index``: Set the x-axis location or \
|
||||
the root joint's index to define the flip center.
|
||||
|
||||
Please refer to the docstring of the fliplr_regression function for
|
||||
more details.
|
||||
visible_item (str|list[str]): The name of the visibility item which
|
||||
will be flipped accordingly along with the pose.
|
||||
flip_prob (float): Probability of flip.
|
||||
flip_camera (bool): Whether to flip horizontal distortion coefficients.
|
||||
camera_param (dict|None): The camera parameter dict. See the camera
|
||||
class definition for more details. If None is given, the camera
|
||||
parameter will be obtained during processing of each data sample
|
||||
with the key "camera_param".
|
||||
|
||||
Required keys:
|
||||
item
|
||||
|
||||
Modified keys:
|
||||
item (, camera_param)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
item,
|
||||
flip_cfg,
|
||||
visible_item=None,
|
||||
flip_prob=0.5,
|
||||
flip_camera=False,
|
||||
camera_param=None):
|
||||
self.item = item
|
||||
self.flip_cfg = flip_cfg
|
||||
self.vis_item = visible_item
|
||||
self.flip_prob = flip_prob
|
||||
self.flip_camera = flip_camera
|
||||
if camera_param is None:
|
||||
self.static_camera = False
|
||||
else:
|
||||
self.static_camera = True
|
||||
self.camera_param = camera_param
|
||||
|
||||
if isinstance(self.item, str):
|
||||
self.item = [self.item]
|
||||
if isinstance(self.flip_cfg, dict):
|
||||
self.flip_cfg = [self.flip_cfg] * len(self.item)
|
||||
assert len(self.item) == len(self.flip_cfg)
|
||||
if isinstance(self.vis_item, str):
|
||||
self.vis_item = [self.vis_item]
|
||||
|
||||
def __call__(self, results):
|
||||
|
||||
if results.get(f'{self.item}_root_removed', False):
|
||||
raise RuntimeError('The transform RelativeJointRandomFlip should '
|
||||
f'not be applied to {self.item} whose root '
|
||||
'joint has been removed and joint indices have '
|
||||
'been changed')
|
||||
|
||||
if np.random.rand() <= self.flip_prob:
|
||||
|
||||
flip_pairs = results['ann_info']['flip_pairs']
|
||||
|
||||
# flip joint coordinates
|
||||
for i, item in enumerate(self.item):
|
||||
assert item in results
|
||||
joints = results[item]
|
||||
|
||||
joints_flipped = fliplr_regression(joints, flip_pairs,
|
||||
**self.flip_cfg[i])
|
||||
|
||||
results[item] = joints_flipped
|
||||
|
||||
# flip joint visibility
|
||||
for vis_item in self.vis_item:
|
||||
assert vis_item in results
|
||||
visible = results[vis_item]
|
||||
visible_flipped = visible.copy()
|
||||
for left, right in flip_pairs:
|
||||
visible_flipped[..., left, :] = visible[..., right, :]
|
||||
visible_flipped[..., right, :] = visible[..., left, :]
|
||||
results[vis_item] = visible_flipped
|
||||
|
||||
# flip horizontal distortion coefficients
|
||||
if self.flip_camera:
|
||||
if self.static_camera:
|
||||
camera_param = copy.deepcopy(self.camera_param)
|
||||
else:
|
||||
assert 'camera_param' in results, \
|
||||
'Camera parameters are missing.'
|
||||
camera_param = results['camera_param']
|
||||
assert 'c' in camera_param
|
||||
camera_param['c'][0] *= -1
|
||||
|
||||
if 'p' in camera_param:
|
||||
camera_param['p'][0] *= -1
|
||||
|
||||
if 'camera_param' not in results:
|
||||
results['camera_param'] = dict()
|
||||
results['camera_param'].update(camera_param)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class PoseSequenceToTensor:
|
||||
"""Convert pose sequence from numpy array to Tensor.
|
||||
|
||||
The original pose sequence should have a shape of [T,K,C] or [K,C], where
|
||||
T is the sequence length, K and C are keypoint number and dimension. The
|
||||
converted pose sequence will have a shape of [KxC, T].
|
||||
|
||||
Args:
|
||||
item (str): The name of the pose sequence
|
||||
|
||||
Required keys:
|
||||
item
|
||||
|
||||
Modified keys:
|
||||
item
|
||||
"""
|
||||
|
||||
def __init__(self, item):
|
||||
self.item = item
|
||||
|
||||
def __call__(self, results):
|
||||
assert self.item in results
|
||||
seq = results[self.item]
|
||||
|
||||
assert isinstance(seq, np.ndarray)
|
||||
assert seq.ndim in {2, 3}
|
||||
|
||||
if seq.ndim == 2:
|
||||
seq = seq[None, ...]
|
||||
|
||||
T = seq.shape[0]
|
||||
seq = seq.transpose(1, 2, 0).reshape(-1, T)
|
||||
results[self.item] = torch.from_numpy(seq)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class Generate3DHeatmapTarget:
|
||||
"""Generate the target 3d heatmap.
|
||||
|
||||
Required keys: 'joints_3d', 'joints_3d_visible', 'ann_info'.
|
||||
Modified keys: 'target', and 'target_weight'.
|
||||
|
||||
Args:
|
||||
sigma: Sigma of heatmap gaussian.
|
||||
joint_indices (list): Indices of joints used for heatmap generation.
|
||||
If None (default) is given, all joints will be used.
|
||||
max_bound (float): The maximal value of heatmap.
|
||||
"""
|
||||
|
||||
def __init__(self, sigma=2, joint_indices=None, max_bound=1.0):
|
||||
self.sigma = sigma
|
||||
self.joint_indices = joint_indices
|
||||
self.max_bound = max_bound
|
||||
|
||||
def __call__(self, results):
|
||||
"""Generate the target heatmap."""
|
||||
joints_3d = results['joints_3d']
|
||||
joints_3d_visible = results['joints_3d_visible']
|
||||
cfg = results['ann_info']
|
||||
image_size = cfg['image_size']
|
||||
W, H, D = cfg['heatmap_size']
|
||||
heatmap3d_depth_bound = cfg['heatmap3d_depth_bound']
|
||||
joint_weights = cfg['joint_weights']
|
||||
use_different_joint_weights = cfg['use_different_joint_weights']
|
||||
|
||||
# select the joints used for target generation
|
||||
if self.joint_indices is not None:
|
||||
joints_3d = joints_3d[self.joint_indices, ...]
|
||||
joints_3d_visible = joints_3d_visible[self.joint_indices, ...]
|
||||
joint_weights = joint_weights[self.joint_indices, ...]
|
||||
num_joints = joints_3d.shape[0]
|
||||
|
||||
# get the joint location in heatmap coordinates
|
||||
mu_x = joints_3d[:, 0] * W / image_size[0]
|
||||
mu_y = joints_3d[:, 1] * H / image_size[1]
|
||||
mu_z = (joints_3d[:, 2] / heatmap3d_depth_bound + 0.5) * D
|
||||
|
||||
target = np.zeros([num_joints, D, H, W], dtype=np.float32)
|
||||
|
||||
target_weight = joints_3d_visible[:, 0].astype(np.float32)
|
||||
target_weight = target_weight * (mu_z >= 0) * (mu_z < D)
|
||||
if use_different_joint_weights:
|
||||
target_weight = target_weight * joint_weights
|
||||
target_weight = target_weight[:, None]
|
||||
|
||||
# only compute the voxel value near the joints location
|
||||
tmp_size = 3 * self.sigma
|
||||
|
||||
# get neighboring voxels coordinates
|
||||
x = y = z = np.arange(2 * tmp_size + 1, dtype=np.float32) - tmp_size
|
||||
zz, yy, xx = np.meshgrid(z, y, x)
|
||||
xx = xx[None, ...].astype(np.float32)
|
||||
yy = yy[None, ...].astype(np.float32)
|
||||
zz = zz[None, ...].astype(np.float32)
|
||||
mu_x = mu_x[..., None, None, None]
|
||||
mu_y = mu_y[..., None, None, None]
|
||||
mu_z = mu_z[..., None, None, None]
|
||||
xx, yy, zz = xx + mu_x, yy + mu_y, zz + mu_z
|
||||
|
||||
# round the coordinates
|
||||
xx = xx.round().clip(0, W - 1)
|
||||
yy = yy.round().clip(0, H - 1)
|
||||
zz = zz.round().clip(0, D - 1)
|
||||
|
||||
# compute the target value near joints
|
||||
local_target = \
|
||||
np.exp(-((xx - mu_x)**2 + (yy - mu_y)**2 + (zz - mu_z)**2) /
|
||||
(2 * self.sigma**2))
|
||||
|
||||
# put the local target value to the full target heatmap
|
||||
local_size = xx.shape[1]
|
||||
idx_joints = np.tile(
|
||||
np.arange(num_joints)[:, None, None, None],
|
||||
[1, local_size, local_size, local_size])
|
||||
idx = np.stack([idx_joints, zz, yy, xx],
|
||||
axis=-1).astype(int).reshape(-1, 4)
|
||||
target[idx[:, 0], idx[:, 1], idx[:, 2],
|
||||
idx[:, 3]] = local_target.reshape(-1)
|
||||
target = target * self.max_bound
|
||||
results['target'] = target
|
||||
results['target_weight'] = target_weight
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class GenerateVoxel3DHeatmapTarget:
|
||||
"""Generate the target 3d heatmap.
|
||||
|
||||
Required keys: 'joints_3d', 'joints_3d_visible', 'ann_info_3d'.
|
||||
Modified keys: 'target', and 'target_weight'.
|
||||
|
||||
Args:
|
||||
sigma: Sigma of heatmap gaussian (mm).
|
||||
joint_indices (list): Indices of joints used for heatmap generation.
|
||||
If None (default) is given, all joints will be used.
|
||||
"""
|
||||
|
||||
def __init__(self, sigma=200.0, joint_indices=None):
|
||||
self.sigma = sigma # mm
|
||||
self.joint_indices = joint_indices
|
||||
|
||||
def __call__(self, results):
|
||||
"""Generate the target heatmap."""
|
||||
joints_3d = results['joints_3d']
|
||||
joints_3d_visible = results['joints_3d_visible']
|
||||
cfg = results['ann_info']
|
||||
|
||||
num_people = len(joints_3d)
|
||||
num_joints = joints_3d[0].shape[0]
|
||||
|
||||
if self.joint_indices is not None:
|
||||
num_joints = len(self.joint_indices)
|
||||
joint_indices = self.joint_indices
|
||||
else:
|
||||
joint_indices = list(range(num_joints))
|
||||
|
||||
space_size = cfg['space_size']
|
||||
space_center = cfg['space_center']
|
||||
cube_size = cfg['cube_size']
|
||||
grids_x = np.linspace(-space_size[0] / 2, space_size[0] / 2,
|
||||
cube_size[0]) + space_center[0]
|
||||
grids_y = np.linspace(-space_size[1] / 2, space_size[1] / 2,
|
||||
cube_size[1]) + space_center[1]
|
||||
grids_z = np.linspace(-space_size[2] / 2, space_size[2] / 2,
|
||||
cube_size[2]) + space_center[2]
|
||||
|
||||
target = np.zeros(
|
||||
(num_joints, cube_size[0], cube_size[1], cube_size[2]),
|
||||
dtype=np.float32)
|
||||
|
||||
for n in range(num_people):
|
||||
for idx, joint_id in enumerate(joint_indices):
|
||||
mu_x = joints_3d[n][joint_id][0]
|
||||
mu_y = joints_3d[n][joint_id][1]
|
||||
mu_z = joints_3d[n][joint_id][2]
|
||||
vis = joints_3d_visible[n][joint_id][0]
|
||||
if vis < 1:
|
||||
continue
|
||||
i_x = [
|
||||
np.searchsorted(grids_x, mu_x - 3 * self.sigma),
|
||||
np.searchsorted(grids_x, mu_x + 3 * self.sigma, 'right')
|
||||
]
|
||||
i_y = [
|
||||
np.searchsorted(grids_y, mu_y - 3 * self.sigma),
|
||||
np.searchsorted(grids_y, mu_y + 3 * self.sigma, 'right')
|
||||
]
|
||||
i_z = [
|
||||
np.searchsorted(grids_z, mu_z - 3 * self.sigma),
|
||||
np.searchsorted(grids_z, mu_z + 3 * self.sigma, 'right')
|
||||
]
|
||||
if i_x[0] >= i_x[1] or i_y[0] >= i_y[1] or i_z[0] >= i_z[1]:
|
||||
continue
|
||||
kernel_xs, kernel_ys, kernel_zs = np.meshgrid(
|
||||
grids_x[i_x[0]:i_x[1]],
|
||||
grids_y[i_y[0]:i_y[1]],
|
||||
grids_z[i_z[0]:i_z[1]],
|
||||
indexing='ij')
|
||||
g = np.exp(-((kernel_xs - mu_x)**2 + (kernel_ys - mu_y)**2 +
|
||||
(kernel_zs - mu_z)**2) / (2 * self.sigma**2))
|
||||
target[idx, i_x[0]:i_x[1], i_y[0]:i_y[1], i_z[0]:i_z[1]] \
|
||||
= np.maximum(target[idx, i_x[0]:i_x[1],
|
||||
i_y[0]:i_y[1], i_z[0]:i_z[1]], g)
|
||||
|
||||
target = np.clip(target, 0, 1)
|
||||
if target.shape[0] == 1:
|
||||
target = target[0]
|
||||
|
||||
results['targets_3d'] = target
|
||||
|
||||
return results
|
||||
+527
@@ -0,0 +1,527 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
from collections.abc import Sequence
|
||||
|
||||
import mmcv
|
||||
import numpy as np
|
||||
from mmcv.parallel import DataContainer as DC
|
||||
from mmcv.utils import build_from_cfg
|
||||
from numpy import random
|
||||
from torchvision.transforms import functional as F
|
||||
|
||||
from ..builder import PIPELINES
|
||||
|
||||
try:
|
||||
import albumentations
|
||||
except ImportError:
|
||||
albumentations = None
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class ToTensor:
|
||||
"""Transform image to Tensor.
|
||||
|
||||
Required key: 'img'. Modifies key: 'img'.
|
||||
|
||||
Args:
|
||||
results (dict): contain all information about training.
|
||||
"""
|
||||
|
||||
def __call__(self, results):
|
||||
if isinstance(results['img'], (list, tuple)):
|
||||
results['img'] = [F.to_tensor(img) for img in results['img']]
|
||||
else:
|
||||
results['img'] = F.to_tensor(results['img'])
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class NormalizeTensor:
|
||||
"""Normalize the Tensor image (CxHxW), with mean and std.
|
||||
|
||||
Required key: 'img'. Modifies key: 'img'.
|
||||
|
||||
Args:
|
||||
mean (list[float]): Mean values of 3 channels.
|
||||
std (list[float]): Std values of 3 channels.
|
||||
"""
|
||||
|
||||
def __init__(self, mean, std):
|
||||
self.mean = mean
|
||||
self.std = std
|
||||
|
||||
def __call__(self, results):
|
||||
if isinstance(results['img'], (list, tuple)):
|
||||
results['img'] = [
|
||||
F.normalize(img, mean=self.mean, std=self.std)
|
||||
for img in results['img']
|
||||
]
|
||||
else:
|
||||
results['img'] = F.normalize(
|
||||
results['img'], mean=self.mean, std=self.std)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class Compose:
|
||||
"""Compose a data pipeline with a sequence of transforms.
|
||||
|
||||
Args:
|
||||
transforms (list[dict | callable]): Either config
|
||||
dicts of transforms or transform objects.
|
||||
"""
|
||||
|
||||
def __init__(self, transforms):
|
||||
assert isinstance(transforms, Sequence)
|
||||
self.transforms = []
|
||||
for transform in transforms:
|
||||
if isinstance(transform, dict):
|
||||
transform = build_from_cfg(transform, PIPELINES)
|
||||
self.transforms.append(transform)
|
||||
elif callable(transform):
|
||||
self.transforms.append(transform)
|
||||
else:
|
||||
raise TypeError('transform must be callable or a dict, but got'
|
||||
f' {type(transform)}')
|
||||
|
||||
def __call__(self, data):
|
||||
"""Call function to apply transforms sequentially.
|
||||
|
||||
Args:
|
||||
data (dict): A result dict contains the data to transform.
|
||||
|
||||
Returns:
|
||||
dict: Transformed data.
|
||||
"""
|
||||
for t in self.transforms:
|
||||
data = t(data)
|
||||
if data is None:
|
||||
return None
|
||||
return data
|
||||
|
||||
def __repr__(self):
|
||||
"""Compute the string representation."""
|
||||
format_string = self.__class__.__name__ + '('
|
||||
for t in self.transforms:
|
||||
format_string += f'\n {t}'
|
||||
format_string += '\n)'
|
||||
return format_string
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class Collect:
|
||||
"""Collect data from the loader relevant to the specific task.
|
||||
|
||||
This keeps the items in `keys` as it is, and collect items in `meta_keys`
|
||||
into a meta item called `meta_name`.This is usually the last stage of the
|
||||
data loader pipeline.
|
||||
For example, when keys='imgs', meta_keys=('filename', 'label',
|
||||
'original_shape'), meta_name='img_metas', the results will be a dict with
|
||||
keys 'imgs' and 'img_metas', where 'img_metas' is a DataContainer of
|
||||
another dict with keys 'filename', 'label', 'original_shape'.
|
||||
|
||||
Args:
|
||||
keys (Sequence[str|tuple]): Required keys to be collected. If a tuple
|
||||
(key, key_new) is given as an element, the item retrieved by key will
|
||||
be renamed as key_new in collected data.
|
||||
meta_name (str): The name of the key that contains meta information.
|
||||
This key is always populated. Default: "img_metas".
|
||||
meta_keys (Sequence[str|tuple]): Keys that are collected under
|
||||
meta_name. The contents of the `meta_name` dictionary depends
|
||||
on `meta_keys`.
|
||||
"""
|
||||
|
||||
def __init__(self, keys, meta_keys, meta_name='img_metas'):
|
||||
self.keys = keys
|
||||
self.meta_keys = meta_keys
|
||||
self.meta_name = meta_name
|
||||
|
||||
def __call__(self, results):
|
||||
"""Performs the Collect formatting.
|
||||
|
||||
Args:
|
||||
results (dict): The resulting dict to be modified and passed
|
||||
to the next transform in pipeline.
|
||||
"""
|
||||
if 'ann_info' in results:
|
||||
results.update(results['ann_info'])
|
||||
|
||||
data = {}
|
||||
for key in self.keys:
|
||||
if isinstance(key, tuple):
|
||||
assert len(key) == 2
|
||||
key_src, key_tgt = key[:2]
|
||||
else:
|
||||
key_src = key_tgt = key
|
||||
data[key_tgt] = results[key_src]
|
||||
|
||||
meta = {}
|
||||
if len(self.meta_keys) != 0:
|
||||
for key in self.meta_keys:
|
||||
if isinstance(key, tuple):
|
||||
assert len(key) == 2
|
||||
key_src, key_tgt = key[:2]
|
||||
else:
|
||||
key_src = key_tgt = key
|
||||
meta[key_tgt] = results[key_src]
|
||||
if 'bbox_id' in results:
|
||||
meta['bbox_id'] = results['bbox_id']
|
||||
data[self.meta_name] = DC(meta, cpu_only=True)
|
||||
|
||||
return data
|
||||
|
||||
def __repr__(self):
|
||||
"""Compute the string representation."""
|
||||
return (f'{self.__class__.__name__}('
|
||||
f'keys={self.keys}, meta_keys={self.meta_keys})')
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class Albumentation:
|
||||
"""Albumentation augmentation (pixel-level transforms only). Adds custom
|
||||
pixel-level transformations from Albumentations library. Please visit
|
||||
`https://albumentations.readthedocs.io` to get more information.
|
||||
|
||||
Note: we only support pixel-level transforms.
|
||||
Please visit `https://github.com/albumentations-team/`
|
||||
`albumentations#pixel-level-transforms`
|
||||
to get more information about pixel-level transforms.
|
||||
|
||||
An example of ``transforms`` is as followed:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[
|
||||
dict(
|
||||
type='RandomBrightnessContrast',
|
||||
brightness_limit=[0.1, 0.3],
|
||||
contrast_limit=[0.1, 0.3],
|
||||
p=0.2),
|
||||
dict(type='ChannelShuffle', p=0.1),
|
||||
dict(
|
||||
type='OneOf',
|
||||
transforms=[
|
||||
dict(type='Blur', blur_limit=3, p=1.0),
|
||||
dict(type='MedianBlur', blur_limit=3, p=1.0)
|
||||
],
|
||||
p=0.1),
|
||||
]
|
||||
|
||||
Args:
|
||||
transforms (list[dict]): A list of Albumentation transformations
|
||||
keymap (dict): Contains {'input key':'albumentation-style key'},
|
||||
e.g., {'img': 'image'}.
|
||||
"""
|
||||
|
||||
def __init__(self, transforms, keymap=None):
|
||||
if albumentations is None:
|
||||
raise RuntimeError('albumentations is not installed')
|
||||
|
||||
self.transforms = transforms
|
||||
self.filter_lost_elements = False
|
||||
|
||||
self.aug = albumentations.Compose(
|
||||
[self.albu_builder(t) for t in self.transforms])
|
||||
|
||||
if not keymap:
|
||||
self.keymap_to_albu = {
|
||||
'img': 'image',
|
||||
}
|
||||
else:
|
||||
self.keymap_to_albu = keymap
|
||||
self.keymap_back = {v: k for k, v in self.keymap_to_albu.items()}
|
||||
|
||||
def albu_builder(self, cfg):
|
||||
"""Import a module from albumentations.
|
||||
|
||||
It resembles some of :func:`build_from_cfg` logic.
|
||||
|
||||
Args:
|
||||
cfg (dict): Config dict. It should at least contain the key "type".
|
||||
|
||||
Returns:
|
||||
obj: The constructed object.
|
||||
"""
|
||||
|
||||
assert isinstance(cfg, dict) and 'type' in cfg
|
||||
args = cfg.copy()
|
||||
|
||||
obj_type = args.pop('type')
|
||||
if mmcv.is_str(obj_type):
|
||||
if albumentations is None:
|
||||
raise RuntimeError('albumentations is not installed')
|
||||
if not hasattr(albumentations.augmentations.transforms, obj_type):
|
||||
warnings.warn('{obj_type} is not pixel-level transformations. '
|
||||
'Please use with caution.')
|
||||
obj_cls = getattr(albumentations, obj_type)
|
||||
else:
|
||||
raise TypeError(f'type must be a str, but got {type(obj_type)}')
|
||||
|
||||
if 'transforms' in args:
|
||||
args['transforms'] = [
|
||||
self.albu_builder(transform)
|
||||
for transform in args['transforms']
|
||||
]
|
||||
|
||||
return obj_cls(**args)
|
||||
|
||||
@staticmethod
|
||||
def mapper(d, keymap):
|
||||
"""Dictionary mapper.
|
||||
|
||||
Renames keys according to keymap provided.
|
||||
|
||||
Args:
|
||||
d (dict): old dict
|
||||
keymap (dict): {'old_key':'new_key'}
|
||||
|
||||
Returns:
|
||||
dict: new dict.
|
||||
"""
|
||||
|
||||
updated_dict = {keymap.get(k, k): v for k, v in d.items()}
|
||||
return updated_dict
|
||||
|
||||
def __call__(self, results):
|
||||
# dict to albumentations format
|
||||
results = self.mapper(results, self.keymap_to_albu)
|
||||
|
||||
results = self.aug(**results)
|
||||
# back to the original format
|
||||
results = self.mapper(results, self.keymap_back)
|
||||
|
||||
return results
|
||||
|
||||
def __repr__(self):
|
||||
repr_str = self.__class__.__name__ + f'(transforms={self.transforms})'
|
||||
return repr_str
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class PhotometricDistortion:
|
||||
"""Apply photometric distortion to image sequentially, every transformation
|
||||
is applied with a probability of 0.5. The position of random contrast is in
|
||||
second or second to last.
|
||||
|
||||
1. random brightness
|
||||
2. random contrast (mode 0)
|
||||
3. convert color from BGR to HSV
|
||||
4. random saturation
|
||||
5. random hue
|
||||
6. convert color from HSV to BGR
|
||||
7. random contrast (mode 1)
|
||||
8. randomly swap channels
|
||||
|
||||
Args:
|
||||
brightness_delta (int): delta of brightness.
|
||||
contrast_range (tuple): range of contrast.
|
||||
saturation_range (tuple): range of saturation.
|
||||
hue_delta (int): delta of hue.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
brightness_delta=32,
|
||||
contrast_range=(0.5, 1.5),
|
||||
saturation_range=(0.5, 1.5),
|
||||
hue_delta=18):
|
||||
self.brightness_delta = brightness_delta
|
||||
self.contrast_lower, self.contrast_upper = contrast_range
|
||||
self.saturation_lower, self.saturation_upper = saturation_range
|
||||
self.hue_delta = hue_delta
|
||||
|
||||
def convert(self, img, alpha=1, beta=0):
|
||||
"""Multiple with alpha and add beta with clip."""
|
||||
img = img.astype(np.float32) * alpha + beta
|
||||
img = np.clip(img, 0, 255)
|
||||
return img.astype(np.uint8)
|
||||
|
||||
def brightness(self, img):
|
||||
"""Brightness distortion."""
|
||||
if random.randint(2):
|
||||
return self.convert(
|
||||
img,
|
||||
beta=random.uniform(-self.brightness_delta,
|
||||
self.brightness_delta))
|
||||
return img
|
||||
|
||||
def contrast(self, img):
|
||||
"""Contrast distortion."""
|
||||
if random.randint(2):
|
||||
return self.convert(
|
||||
img,
|
||||
alpha=random.uniform(self.contrast_lower, self.contrast_upper))
|
||||
return img
|
||||
|
||||
def saturation(self, img):
|
||||
# Apply saturation distortion to hsv-formatted img
|
||||
img[:, :, 1] = self.convert(
|
||||
img[:, :, 1],
|
||||
alpha=random.uniform(self.saturation_lower, self.saturation_upper))
|
||||
return img
|
||||
|
||||
def hue(self, img):
|
||||
# Apply hue distortion to hsv-formatted img
|
||||
img[:, :, 0] = (img[:, :, 0].astype(int) +
|
||||
random.randint(-self.hue_delta, self.hue_delta)) % 180
|
||||
return img
|
||||
|
||||
def swap_channels(self, img):
|
||||
# Apply channel swap
|
||||
if random.randint(2):
|
||||
img = img[..., random.permutation(3)]
|
||||
return img
|
||||
|
||||
def __call__(self, results):
|
||||
"""Call function to perform photometric distortion on images.
|
||||
|
||||
Args:
|
||||
results (dict): Result dict from loading pipeline.
|
||||
|
||||
Returns:
|
||||
dict: Result dict with images distorted.
|
||||
"""
|
||||
|
||||
img = results['img']
|
||||
# random brightness
|
||||
img = self.brightness(img)
|
||||
|
||||
# mode == 0 --> do random contrast first
|
||||
# mode == 1 --> do random contrast last
|
||||
mode = random.randint(2)
|
||||
if mode == 1:
|
||||
img = self.contrast(img)
|
||||
|
||||
hsv_mode = random.randint(4)
|
||||
if hsv_mode:
|
||||
# random saturation/hue distortion
|
||||
img = mmcv.bgr2hsv(img)
|
||||
if hsv_mode == 1 or hsv_mode == 3:
|
||||
img = self.saturation(img)
|
||||
if hsv_mode == 2 or hsv_mode == 3:
|
||||
img = self.hue(img)
|
||||
img = mmcv.hsv2bgr(img)
|
||||
|
||||
# random contrast
|
||||
if mode == 0:
|
||||
img = self.contrast(img)
|
||||
|
||||
# randomly swap channels
|
||||
self.swap_channels(img)
|
||||
|
||||
results['img'] = img
|
||||
return results
|
||||
|
||||
def __repr__(self):
|
||||
repr_str = self.__class__.__name__
|
||||
repr_str += (f'(brightness_delta={self.brightness_delta}, '
|
||||
f'contrast_range=({self.contrast_lower}, '
|
||||
f'{self.contrast_upper}), '
|
||||
f'saturation_range=({self.saturation_lower}, '
|
||||
f'{self.saturation_upper}), '
|
||||
f'hue_delta={self.hue_delta})')
|
||||
return repr_str
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class MultiItemProcess:
|
||||
"""Process each item and merge multi-item results to lists.
|
||||
|
||||
Args:
|
||||
pipeline (dict): Dictionary to construct pipeline for a single item.
|
||||
"""
|
||||
|
||||
def __init__(self, pipeline):
|
||||
self.pipeline = Compose(pipeline)
|
||||
|
||||
def __call__(self, results):
|
||||
results_ = {}
|
||||
for idx, result in results.items():
|
||||
single_result = self.pipeline(result)
|
||||
for k, v in single_result.items():
|
||||
if k in results_:
|
||||
results_[k].append(v)
|
||||
else:
|
||||
results_[k] = [v]
|
||||
|
||||
return results_
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class DiscardDuplicatedItems:
|
||||
|
||||
def __init__(self, keys_list):
|
||||
"""Discard duplicated single-item results.
|
||||
|
||||
Args:
|
||||
keys_list (list): List of keys that need to be deduplicate.
|
||||
"""
|
||||
self.keys_list = keys_list
|
||||
|
||||
def __call__(self, results):
|
||||
for k, v in results.items():
|
||||
if k in self.keys_list:
|
||||
assert isinstance(v, Sequence)
|
||||
results[k] = v[0]
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class MultitaskGatherTarget:
|
||||
"""Gather the targets for multitask heads.
|
||||
|
||||
Args:
|
||||
pipeline_list (list[list]): List of pipelines for all heads.
|
||||
pipeline_indices (list[int]): Pipeline index of each head.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
pipeline_list,
|
||||
pipeline_indices=None,
|
||||
keys=('target', 'target_weight')):
|
||||
self.keys = keys
|
||||
self.pipelines = []
|
||||
for pipeline in pipeline_list:
|
||||
self.pipelines.append(Compose(pipeline))
|
||||
if pipeline_indices is None:
|
||||
self.pipeline_indices = list(range(len(pipeline_list)))
|
||||
else:
|
||||
self.pipeline_indices = pipeline_indices
|
||||
|
||||
def __call__(self, results):
|
||||
# generate target and target weights using all pipelines
|
||||
pipeline_outputs = []
|
||||
for pipeline in self.pipelines:
|
||||
pipeline_output = pipeline(results)
|
||||
pipeline_outputs.append(pipeline_output.copy())
|
||||
|
||||
for key in self.keys:
|
||||
result_key = []
|
||||
for ind in self.pipeline_indices:
|
||||
result_key.append(pipeline_outputs[ind].get(key, None))
|
||||
results[key] = result_key
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class RenameKeys:
|
||||
"""Rename the keys.
|
||||
|
||||
Args:
|
||||
key_pairs (Sequence[tuple]): Required keys to be renamed.
|
||||
If a tuple (key_src, key_tgt) is given as an element,
|
||||
the item retrieved by key_src will be renamed as key_tgt.
|
||||
"""
|
||||
|
||||
def __init__(self, key_pairs):
|
||||
self.key_pairs = key_pairs
|
||||
|
||||
def __call__(self, results):
|
||||
"""Rename keys."""
|
||||
for key_pair in self.key_pairs:
|
||||
assert len(key_pair) == 2
|
||||
key_src, key_tgt = key_pair
|
||||
results[key_tgt] = results.pop(key_src)
|
||||
return results
|
||||
+736
@@ -0,0 +1,736 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from mmpose.core.post_processing import (affine_transform, fliplr_joints,
|
||||
get_affine_transform, get_warp_matrix,
|
||||
warp_affine_joints)
|
||||
from mmpose.datasets.builder import PIPELINES
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class TopDownRandomFlip:
|
||||
"""Data augmentation with random image flip.
|
||||
|
||||
Required keys: 'img', 'joints_3d', 'joints_3d_visible', 'center' and
|
||||
'ann_info'.
|
||||
|
||||
Modifies key: 'img', 'joints_3d', 'joints_3d_visible', 'center' and
|
||||
'flipped'.
|
||||
|
||||
Args:
|
||||
flip (bool): Option to perform random flip.
|
||||
flip_prob (float): Probability of flip.
|
||||
"""
|
||||
|
||||
def __init__(self, flip_prob=0.5):
|
||||
self.flip_prob = flip_prob
|
||||
|
||||
def __call__(self, results):
|
||||
"""Perform data augmentation with random image flip."""
|
||||
img = results['img']
|
||||
joints_3d = results['joints_3d']
|
||||
joints_3d_visible = results['joints_3d_visible']
|
||||
center = results['center']
|
||||
|
||||
# A flag indicating whether the image is flipped,
|
||||
# which can be used by child class.
|
||||
flipped = False
|
||||
if np.random.rand() <= self.flip_prob:
|
||||
flipped = True
|
||||
if not isinstance(img, list):
|
||||
img = img[:, ::-1, :]
|
||||
else:
|
||||
img = [i[:, ::-1, :] for i in img]
|
||||
if not isinstance(img, list):
|
||||
joints_3d, joints_3d_visible = fliplr_joints(
|
||||
joints_3d, joints_3d_visible, img.shape[1],
|
||||
results['ann_info']['flip_pairs'])
|
||||
center[0] = img.shape[1] - center[0] - 1
|
||||
else:
|
||||
joints_3d, joints_3d_visible = fliplr_joints(
|
||||
joints_3d, joints_3d_visible, img[0].shape[1],
|
||||
results['ann_info']['flip_pairs'])
|
||||
center[0] = img[0].shape[1] - center[0] - 1
|
||||
|
||||
results['img'] = img
|
||||
results['joints_3d'] = joints_3d
|
||||
results['joints_3d_visible'] = joints_3d_visible
|
||||
results['center'] = center
|
||||
results['flipped'] = flipped
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class TopDownHalfBodyTransform:
|
||||
"""Data augmentation with half-body transform. Keep only the upper body or
|
||||
the lower body at random.
|
||||
|
||||
Required keys: 'joints_3d', 'joints_3d_visible', and 'ann_info'.
|
||||
|
||||
Modifies key: 'scale' and 'center'.
|
||||
|
||||
Args:
|
||||
num_joints_half_body (int): Threshold of performing
|
||||
half-body transform. If the body has fewer number
|
||||
of joints (< num_joints_half_body), ignore this step.
|
||||
prob_half_body (float): Probability of half-body transform.
|
||||
"""
|
||||
|
||||
def __init__(self, num_joints_half_body=8, prob_half_body=0.3):
|
||||
self.num_joints_half_body = num_joints_half_body
|
||||
self.prob_half_body = prob_half_body
|
||||
|
||||
@staticmethod
|
||||
def half_body_transform(cfg, joints_3d, joints_3d_visible):
|
||||
"""Get center&scale for half-body transform."""
|
||||
upper_joints = []
|
||||
lower_joints = []
|
||||
for joint_id in range(cfg['num_joints']):
|
||||
if joints_3d_visible[joint_id][0] > 0:
|
||||
if joint_id in cfg['upper_body_ids']:
|
||||
upper_joints.append(joints_3d[joint_id])
|
||||
else:
|
||||
lower_joints.append(joints_3d[joint_id])
|
||||
|
||||
if np.random.randn() < 0.5 and len(upper_joints) > 2:
|
||||
selected_joints = upper_joints
|
||||
elif len(lower_joints) > 2:
|
||||
selected_joints = lower_joints
|
||||
else:
|
||||
selected_joints = upper_joints
|
||||
|
||||
if len(selected_joints) < 2:
|
||||
return None, None
|
||||
|
||||
selected_joints = np.array(selected_joints, dtype=np.float32)
|
||||
center = selected_joints.mean(axis=0)[:2]
|
||||
|
||||
left_top = np.amin(selected_joints, axis=0)
|
||||
|
||||
right_bottom = np.amax(selected_joints, axis=0)
|
||||
|
||||
w = right_bottom[0] - left_top[0]
|
||||
h = right_bottom[1] - left_top[1]
|
||||
|
||||
aspect_ratio = cfg['image_size'][0] / cfg['image_size'][1]
|
||||
|
||||
if w > aspect_ratio * h:
|
||||
h = w * 1.0 / aspect_ratio
|
||||
elif w < aspect_ratio * h:
|
||||
w = h * aspect_ratio
|
||||
|
||||
scale = np.array([w / 200.0, h / 200.0], dtype=np.float32)
|
||||
scale = scale * 1.5
|
||||
return center, scale
|
||||
|
||||
def __call__(self, results):
|
||||
"""Perform data augmentation with half-body transform."""
|
||||
joints_3d = results['joints_3d']
|
||||
joints_3d_visible = results['joints_3d_visible']
|
||||
|
||||
if (np.sum(joints_3d_visible[:, 0]) > self.num_joints_half_body
|
||||
and np.random.rand() < self.prob_half_body):
|
||||
|
||||
c_half_body, s_half_body = self.half_body_transform(
|
||||
results['ann_info'], joints_3d, joints_3d_visible)
|
||||
|
||||
if c_half_body is not None and s_half_body is not None:
|
||||
results['center'] = c_half_body
|
||||
results['scale'] = s_half_body
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class TopDownGetRandomScaleRotation:
|
||||
"""Data augmentation with random scaling & rotating.
|
||||
|
||||
Required key: 'scale'.
|
||||
|
||||
Modifies key: 'scale' and 'rotation'.
|
||||
|
||||
Args:
|
||||
rot_factor (int): Rotating to ``[-2*rot_factor, 2*rot_factor]``.
|
||||
scale_factor (float): Scaling to ``[1-scale_factor, 1+scale_factor]``.
|
||||
rot_prob (float): Probability of random rotation.
|
||||
"""
|
||||
|
||||
def __init__(self, rot_factor=40, scale_factor=0.5, rot_prob=0.6):
|
||||
self.rot_factor = rot_factor
|
||||
self.scale_factor = scale_factor
|
||||
self.rot_prob = rot_prob
|
||||
|
||||
def __call__(self, results):
|
||||
"""Perform data augmentation with random scaling & rotating."""
|
||||
s = results['scale']
|
||||
|
||||
sf = self.scale_factor
|
||||
rf = self.rot_factor
|
||||
|
||||
s_factor = np.clip(np.random.randn() * sf + 1, 1 - sf, 1 + sf)
|
||||
s = s * s_factor
|
||||
|
||||
r_factor = np.clip(np.random.randn() * rf, -rf * 2, rf * 2)
|
||||
r = r_factor if np.random.rand() <= self.rot_prob else 0
|
||||
|
||||
results['scale'] = s
|
||||
results['rotation'] = r
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class TopDownAffine:
|
||||
"""Affine transform the image to make input.
|
||||
|
||||
Required keys:'img', 'joints_3d', 'joints_3d_visible', 'ann_info','scale',
|
||||
'rotation' and 'center'.
|
||||
|
||||
Modified keys:'img', 'joints_3d', and 'joints_3d_visible'.
|
||||
|
||||
Args:
|
||||
use_udp (bool): To use unbiased data processing.
|
||||
Paper ref: Huang et al. The Devil is in the Details: Delving into
|
||||
Unbiased Data Processing for Human Pose Estimation (CVPR 2020).
|
||||
"""
|
||||
|
||||
def __init__(self, use_udp=False):
|
||||
self.use_udp = use_udp
|
||||
|
||||
def __call__(self, results):
|
||||
image_size = results['ann_info']['image_size']
|
||||
|
||||
img = results['img']
|
||||
joints_3d = results['joints_3d']
|
||||
joints_3d_visible = results['joints_3d_visible']
|
||||
c = results['center']
|
||||
s = results['scale']
|
||||
r = results['rotation']
|
||||
|
||||
if self.use_udp:
|
||||
trans = get_warp_matrix(r, c * 2.0, image_size - 1.0, s * 200.0)
|
||||
if not isinstance(img, list):
|
||||
img = cv2.warpAffine(
|
||||
img,
|
||||
trans, (int(image_size[0]), int(image_size[1])),
|
||||
flags=cv2.INTER_LINEAR)
|
||||
else:
|
||||
img = [
|
||||
cv2.warpAffine(
|
||||
i,
|
||||
trans, (int(image_size[0]), int(image_size[1])),
|
||||
flags=cv2.INTER_LINEAR) for i in img
|
||||
]
|
||||
|
||||
joints_3d[:, 0:2] = \
|
||||
warp_affine_joints(joints_3d[:, 0:2].copy(), trans)
|
||||
|
||||
else:
|
||||
trans = get_affine_transform(c, s, r, image_size)
|
||||
if not isinstance(img, list):
|
||||
img = cv2.warpAffine(
|
||||
img,
|
||||
trans, (int(image_size[0]), int(image_size[1])),
|
||||
flags=cv2.INTER_LINEAR)
|
||||
else:
|
||||
img = [
|
||||
cv2.warpAffine(
|
||||
i,
|
||||
trans, (int(image_size[0]), int(image_size[1])),
|
||||
flags=cv2.INTER_LINEAR) for i in img
|
||||
]
|
||||
for i in range(results['ann_info']['num_joints']):
|
||||
if joints_3d_visible[i, 0] > 0.0:
|
||||
joints_3d[i,
|
||||
0:2] = affine_transform(joints_3d[i, 0:2], trans)
|
||||
|
||||
results['img'] = img
|
||||
results['joints_3d'] = joints_3d
|
||||
results['joints_3d_visible'] = joints_3d_visible
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class TopDownGenerateTarget:
|
||||
"""Generate the target heatmap.
|
||||
|
||||
Required keys: 'joints_3d', 'joints_3d_visible', 'ann_info'.
|
||||
|
||||
Modified keys: 'target', and 'target_weight'.
|
||||
|
||||
Args:
|
||||
sigma: Sigma of heatmap gaussian for 'MSRA' approach.
|
||||
kernel: Kernel of heatmap gaussian for 'Megvii' approach.
|
||||
encoding (str): Approach to generate target heatmaps.
|
||||
Currently supported approaches: 'MSRA', 'Megvii', 'UDP'.
|
||||
Default:'MSRA'
|
||||
unbiased_encoding (bool): Option to use unbiased
|
||||
encoding methods.
|
||||
Paper ref: Zhang et al. Distribution-Aware Coordinate
|
||||
Representation for Human Pose Estimation (CVPR 2020).
|
||||
keypoint_pose_distance: Keypoint pose distance for UDP.
|
||||
Paper ref: Huang et al. The Devil is in the Details: Delving into
|
||||
Unbiased Data Processing for Human Pose Estimation (CVPR 2020).
|
||||
target_type (str): supported targets: 'GaussianHeatmap',
|
||||
'CombinedTarget'. Default:'GaussianHeatmap'
|
||||
CombinedTarget: The combination of classification target
|
||||
(response map) and regression target (offset map).
|
||||
Paper ref: Huang et al. The Devil is in the Details: Delving into
|
||||
Unbiased Data Processing for Human Pose Estimation (CVPR 2020).
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
sigma=2,
|
||||
kernel=(11, 11),
|
||||
valid_radius_factor=0.0546875,
|
||||
target_type='GaussianHeatmap',
|
||||
encoding='MSRA',
|
||||
unbiased_encoding=False):
|
||||
self.sigma = sigma
|
||||
self.unbiased_encoding = unbiased_encoding
|
||||
self.kernel = kernel
|
||||
self.valid_radius_factor = valid_radius_factor
|
||||
self.target_type = target_type
|
||||
self.encoding = encoding
|
||||
|
||||
def _msra_generate_target(self, cfg, joints_3d, joints_3d_visible, sigma):
|
||||
"""Generate the target heatmap via "MSRA" approach.
|
||||
|
||||
Args:
|
||||
cfg (dict): data config
|
||||
joints_3d: np.ndarray ([num_joints, 3])
|
||||
joints_3d_visible: np.ndarray ([num_joints, 3])
|
||||
sigma: Sigma of heatmap gaussian
|
||||
Returns:
|
||||
tuple: A tuple containing targets.
|
||||
|
||||
- target: Target heatmaps.
|
||||
- target_weight: (1: visible, 0: invisible)
|
||||
"""
|
||||
num_joints = cfg['num_joints']
|
||||
image_size = cfg['image_size']
|
||||
W, H = cfg['heatmap_size']
|
||||
joint_weights = cfg['joint_weights']
|
||||
use_different_joint_weights = cfg['use_different_joint_weights']
|
||||
|
||||
target_weight = np.zeros((num_joints, 1), dtype=np.float32)
|
||||
target = np.zeros((num_joints, H, W), dtype=np.float32)
|
||||
|
||||
# 3-sigma rule
|
||||
tmp_size = sigma * 3
|
||||
|
||||
if self.unbiased_encoding:
|
||||
for joint_id in range(num_joints):
|
||||
target_weight[joint_id] = joints_3d_visible[joint_id, 0]
|
||||
|
||||
feat_stride = image_size / [W, H]
|
||||
mu_x = joints_3d[joint_id][0] / feat_stride[0]
|
||||
mu_y = joints_3d[joint_id][1] / feat_stride[1]
|
||||
# Check that any part of the gaussian is in-bounds
|
||||
ul = [mu_x - tmp_size, mu_y - tmp_size]
|
||||
br = [mu_x + tmp_size + 1, mu_y + tmp_size + 1]
|
||||
if ul[0] >= W or ul[1] >= H or br[0] < 0 or br[1] < 0:
|
||||
target_weight[joint_id] = 0
|
||||
|
||||
if target_weight[joint_id] == 0:
|
||||
continue
|
||||
|
||||
x = np.arange(0, W, 1, np.float32)
|
||||
y = np.arange(0, H, 1, np.float32)
|
||||
y = y[:, None]
|
||||
|
||||
if target_weight[joint_id] > 0.5:
|
||||
target[joint_id] = np.exp(-((x - mu_x)**2 +
|
||||
(y - mu_y)**2) /
|
||||
(2 * sigma**2))
|
||||
else:
|
||||
for joint_id in range(num_joints):
|
||||
target_weight[joint_id] = joints_3d_visible[joint_id, 0]
|
||||
|
||||
feat_stride = image_size / [W, H]
|
||||
mu_x = int(joints_3d[joint_id][0] / feat_stride[0] + 0.5)
|
||||
mu_y = int(joints_3d[joint_id][1] / feat_stride[1] + 0.5)
|
||||
# Check that any part of the gaussian is in-bounds
|
||||
ul = [int(mu_x - tmp_size), int(mu_y - tmp_size)]
|
||||
br = [int(mu_x + tmp_size + 1), int(mu_y + tmp_size + 1)]
|
||||
if ul[0] >= W or ul[1] >= H or br[0] < 0 or br[1] < 0:
|
||||
target_weight[joint_id] = 0
|
||||
|
||||
if target_weight[joint_id] > 0.5:
|
||||
size = 2 * tmp_size + 1
|
||||
x = np.arange(0, size, 1, np.float32)
|
||||
y = x[:, None]
|
||||
x0 = y0 = size // 2
|
||||
# The gaussian is not normalized,
|
||||
# we want the center value to equal 1
|
||||
g = np.exp(-((x - x0)**2 + (y - y0)**2) / (2 * sigma**2))
|
||||
|
||||
# Usable gaussian range
|
||||
g_x = max(0, -ul[0]), min(br[0], W) - ul[0]
|
||||
g_y = max(0, -ul[1]), min(br[1], H) - ul[1]
|
||||
# Image range
|
||||
img_x = max(0, ul[0]), min(br[0], W)
|
||||
img_y = max(0, ul[1]), min(br[1], H)
|
||||
|
||||
target[joint_id][img_y[0]:img_y[1], img_x[0]:img_x[1]] = \
|
||||
g[g_y[0]:g_y[1], g_x[0]:g_x[1]]
|
||||
|
||||
if use_different_joint_weights:
|
||||
target_weight = np.multiply(target_weight, joint_weights)
|
||||
|
||||
return target, target_weight
|
||||
|
||||
def _megvii_generate_target(self, cfg, joints_3d, joints_3d_visible,
|
||||
kernel):
|
||||
"""Generate the target heatmap via "Megvii" approach.
|
||||
|
||||
Args:
|
||||
cfg (dict): data config
|
||||
joints_3d: np.ndarray ([num_joints, 3])
|
||||
joints_3d_visible: np.ndarray ([num_joints, 3])
|
||||
kernel: Kernel of heatmap gaussian
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing targets.
|
||||
|
||||
- target: Target heatmaps.
|
||||
- target_weight: (1: visible, 0: invisible)
|
||||
"""
|
||||
|
||||
num_joints = cfg['num_joints']
|
||||
image_size = cfg['image_size']
|
||||
W, H = cfg['heatmap_size']
|
||||
heatmaps = np.zeros((num_joints, H, W), dtype='float32')
|
||||
target_weight = np.zeros((num_joints, 1), dtype=np.float32)
|
||||
|
||||
for i in range(num_joints):
|
||||
target_weight[i] = joints_3d_visible[i, 0]
|
||||
|
||||
if target_weight[i] < 1:
|
||||
continue
|
||||
|
||||
target_y = int(joints_3d[i, 1] * H / image_size[1])
|
||||
target_x = int(joints_3d[i, 0] * W / image_size[0])
|
||||
|
||||
if (target_x >= W or target_x < 0) \
|
||||
or (target_y >= H or target_y < 0):
|
||||
target_weight[i] = 0
|
||||
continue
|
||||
|
||||
heatmaps[i, target_y, target_x] = 1
|
||||
heatmaps[i] = cv2.GaussianBlur(heatmaps[i], kernel, 0)
|
||||
maxi = heatmaps[i, target_y, target_x]
|
||||
|
||||
heatmaps[i] /= maxi / 255
|
||||
|
||||
return heatmaps, target_weight
|
||||
|
||||
def _udp_generate_target(self, cfg, joints_3d, joints_3d_visible, factor,
|
||||
target_type):
|
||||
"""Generate the target heatmap via 'UDP' approach. Paper ref: Huang et
|
||||
al. The Devil is in the Details: Delving into Unbiased Data Processing
|
||||
for Human Pose Estimation (CVPR 2020).
|
||||
|
||||
Note:
|
||||
- num keypoints: K
|
||||
- heatmap height: H
|
||||
- heatmap width: W
|
||||
- num target channels: C
|
||||
- C = K if target_type=='GaussianHeatmap'
|
||||
- C = 3*K if target_type=='CombinedTarget'
|
||||
|
||||
Args:
|
||||
cfg (dict): data config
|
||||
joints_3d (np.ndarray[K, 3]): Annotated keypoints.
|
||||
joints_3d_visible (np.ndarray[K, 3]): Visibility of keypoints.
|
||||
factor (float): kernel factor for GaussianHeatmap target or
|
||||
valid radius factor for CombinedTarget.
|
||||
target_type (str): 'GaussianHeatmap' or 'CombinedTarget'.
|
||||
GaussianHeatmap: Heatmap target with gaussian distribution.
|
||||
CombinedTarget: The combination of classification target
|
||||
(response map) and regression target (offset map).
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing targets.
|
||||
|
||||
- target (np.ndarray[C, H, W]): Target heatmaps.
|
||||
- target_weight (np.ndarray[K, 1]): (1: visible, 0: invisible)
|
||||
"""
|
||||
num_joints = cfg['num_joints']
|
||||
image_size = cfg['image_size']
|
||||
heatmap_size = cfg['heatmap_size']
|
||||
joint_weights = cfg['joint_weights']
|
||||
use_different_joint_weights = cfg['use_different_joint_weights']
|
||||
|
||||
target_weight = np.ones((num_joints, 1), dtype=np.float32)
|
||||
target_weight[:, 0] = joints_3d_visible[:, 0]
|
||||
|
||||
if target_type.lower() == 'GaussianHeatmap'.lower():
|
||||
target = np.zeros((num_joints, heatmap_size[1], heatmap_size[0]),
|
||||
dtype=np.float32)
|
||||
|
||||
tmp_size = factor * 3
|
||||
|
||||
# prepare for gaussian
|
||||
size = 2 * tmp_size + 1
|
||||
x = np.arange(0, size, 1, np.float32)
|
||||
y = x[:, None]
|
||||
|
||||
for joint_id in range(num_joints):
|
||||
feat_stride = (image_size - 1.0) / (heatmap_size - 1.0)
|
||||
mu_x = int(joints_3d[joint_id][0] / feat_stride[0] + 0.5)
|
||||
mu_y = int(joints_3d[joint_id][1] / feat_stride[1] + 0.5)
|
||||
# Check that any part of the gaussian is in-bounds
|
||||
ul = [int(mu_x - tmp_size), int(mu_y - tmp_size)]
|
||||
br = [int(mu_x + tmp_size + 1), int(mu_y + tmp_size + 1)]
|
||||
if ul[0] >= heatmap_size[0] or ul[1] >= heatmap_size[1] \
|
||||
or br[0] < 0 or br[1] < 0:
|
||||
# If not, just return the image as is
|
||||
target_weight[joint_id] = 0
|
||||
continue
|
||||
|
||||
# # Generate gaussian
|
||||
mu_x_ac = joints_3d[joint_id][0] / feat_stride[0]
|
||||
mu_y_ac = joints_3d[joint_id][1] / feat_stride[1]
|
||||
x0 = y0 = size // 2
|
||||
x0 += mu_x_ac - mu_x
|
||||
y0 += mu_y_ac - mu_y
|
||||
g = np.exp(-((x - x0)**2 + (y - y0)**2) / (2 * factor**2))
|
||||
|
||||
# Usable gaussian range
|
||||
g_x = max(0, -ul[0]), min(br[0], heatmap_size[0]) - ul[0]
|
||||
g_y = max(0, -ul[1]), min(br[1], heatmap_size[1]) - ul[1]
|
||||
# Image range
|
||||
img_x = max(0, ul[0]), min(br[0], heatmap_size[0])
|
||||
img_y = max(0, ul[1]), min(br[1], heatmap_size[1])
|
||||
|
||||
v = target_weight[joint_id]
|
||||
if v > 0.5:
|
||||
target[joint_id][img_y[0]:img_y[1], img_x[0]:img_x[1]] = \
|
||||
g[g_y[0]:g_y[1], g_x[0]:g_x[1]]
|
||||
|
||||
elif target_type.lower() == 'CombinedTarget'.lower():
|
||||
target = np.zeros(
|
||||
(num_joints, 3, heatmap_size[1] * heatmap_size[0]),
|
||||
dtype=np.float32)
|
||||
feat_width = heatmap_size[0]
|
||||
feat_height = heatmap_size[1]
|
||||
feat_x_int = np.arange(0, feat_width)
|
||||
feat_y_int = np.arange(0, feat_height)
|
||||
feat_x_int, feat_y_int = np.meshgrid(feat_x_int, feat_y_int)
|
||||
feat_x_int = feat_x_int.flatten()
|
||||
feat_y_int = feat_y_int.flatten()
|
||||
# Calculate the radius of the positive area in classification
|
||||
# heatmap.
|
||||
valid_radius = factor * heatmap_size[1]
|
||||
feat_stride = (image_size - 1.0) / (heatmap_size - 1.0)
|
||||
for joint_id in range(num_joints):
|
||||
mu_x = joints_3d[joint_id][0] / feat_stride[0]
|
||||
mu_y = joints_3d[joint_id][1] / feat_stride[1]
|
||||
x_offset = (mu_x - feat_x_int) / valid_radius
|
||||
y_offset = (mu_y - feat_y_int) / valid_radius
|
||||
dis = x_offset**2 + y_offset**2
|
||||
keep_pos = np.where(dis <= 1)[0]
|
||||
v = target_weight[joint_id]
|
||||
if v > 0.5:
|
||||
target[joint_id, 0, keep_pos] = 1
|
||||
target[joint_id, 1, keep_pos] = x_offset[keep_pos]
|
||||
target[joint_id, 2, keep_pos] = y_offset[keep_pos]
|
||||
target = target.reshape(num_joints * 3, heatmap_size[1],
|
||||
heatmap_size[0])
|
||||
else:
|
||||
raise ValueError('target_type should be either '
|
||||
"'GaussianHeatmap' or 'CombinedTarget'")
|
||||
|
||||
if use_different_joint_weights:
|
||||
target_weight = np.multiply(target_weight, joint_weights)
|
||||
|
||||
return target, target_weight
|
||||
|
||||
def __call__(self, results):
|
||||
"""Generate the target heatmap."""
|
||||
joints_3d = results['joints_3d']
|
||||
joints_3d_visible = results['joints_3d_visible']
|
||||
|
||||
assert self.encoding in ['MSRA', 'Megvii', 'UDP']
|
||||
|
||||
if self.encoding == 'MSRA':
|
||||
if isinstance(self.sigma, list):
|
||||
num_sigmas = len(self.sigma)
|
||||
cfg = results['ann_info']
|
||||
num_joints = cfg['num_joints']
|
||||
heatmap_size = cfg['heatmap_size']
|
||||
|
||||
target = np.empty(
|
||||
(0, num_joints, heatmap_size[1], heatmap_size[0]),
|
||||
dtype=np.float32)
|
||||
target_weight = np.empty((0, num_joints, 1), dtype=np.float32)
|
||||
for i in range(num_sigmas):
|
||||
target_i, target_weight_i = self._msra_generate_target(
|
||||
cfg, joints_3d, joints_3d_visible, self.sigma[i])
|
||||
target = np.concatenate([target, target_i[None]], axis=0)
|
||||
target_weight = np.concatenate(
|
||||
[target_weight, target_weight_i[None]], axis=0)
|
||||
else:
|
||||
target, target_weight = self._msra_generate_target(
|
||||
results['ann_info'], joints_3d, joints_3d_visible,
|
||||
self.sigma)
|
||||
|
||||
elif self.encoding == 'Megvii':
|
||||
if isinstance(self.kernel, list):
|
||||
num_kernels = len(self.kernel)
|
||||
cfg = results['ann_info']
|
||||
num_joints = cfg['num_joints']
|
||||
W, H = cfg['heatmap_size']
|
||||
|
||||
target = np.empty((0, num_joints, H, W), dtype=np.float32)
|
||||
target_weight = np.empty((0, num_joints, 1), dtype=np.float32)
|
||||
for i in range(num_kernels):
|
||||
target_i, target_weight_i = self._megvii_generate_target(
|
||||
cfg, joints_3d, joints_3d_visible, self.kernel[i])
|
||||
target = np.concatenate([target, target_i[None]], axis=0)
|
||||
target_weight = np.concatenate(
|
||||
[target_weight, target_weight_i[None]], axis=0)
|
||||
else:
|
||||
target, target_weight = self._megvii_generate_target(
|
||||
results['ann_info'], joints_3d, joints_3d_visible,
|
||||
self.kernel)
|
||||
|
||||
elif self.encoding == 'UDP':
|
||||
if self.target_type.lower() == 'CombinedTarget'.lower():
|
||||
factors = self.valid_radius_factor
|
||||
channel_factor = 3
|
||||
elif self.target_type.lower() == 'GaussianHeatmap'.lower():
|
||||
factors = self.sigma
|
||||
channel_factor = 1
|
||||
else:
|
||||
raise ValueError('target_type should be either '
|
||||
"'GaussianHeatmap' or 'CombinedTarget'")
|
||||
if isinstance(factors, list):
|
||||
num_factors = len(factors)
|
||||
cfg = results['ann_info']
|
||||
num_joints = cfg['num_joints']
|
||||
W, H = cfg['heatmap_size']
|
||||
|
||||
target = np.empty((0, channel_factor * num_joints, H, W),
|
||||
dtype=np.float32)
|
||||
target_weight = np.empty((0, num_joints, 1), dtype=np.float32)
|
||||
for i in range(num_factors):
|
||||
target_i, target_weight_i = self._udp_generate_target(
|
||||
cfg, joints_3d, joints_3d_visible, factors[i],
|
||||
self.target_type)
|
||||
target = np.concatenate([target, target_i[None]], axis=0)
|
||||
target_weight = np.concatenate(
|
||||
[target_weight, target_weight_i[None]], axis=0)
|
||||
else:
|
||||
target, target_weight = self._udp_generate_target(
|
||||
results['ann_info'], joints_3d, joints_3d_visible, factors,
|
||||
self.target_type)
|
||||
else:
|
||||
raise ValueError(
|
||||
f'Encoding approach {self.encoding} is not supported!')
|
||||
|
||||
if results['ann_info'].get('max_num_joints', None) is not None:
|
||||
W, H = results['ann_info']['heatmap_size']
|
||||
padded_length = int(results['ann_info'].get('max_num_joints') - results['ann_info'].get('num_joints'))
|
||||
target_weight = np.concatenate([target_weight, np.zeros((padded_length, 1), dtype=np.float32)], 0)
|
||||
target = np.concatenate([target, np.zeros((padded_length, H, W), dtype=np.float32)], 0)
|
||||
|
||||
results['target'] = target
|
||||
results['target_weight'] = target_weight
|
||||
|
||||
results['dataset_idx'] = results['ann_info'].get('dataset_idx', 0)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class TopDownGenerateTargetRegression:
|
||||
"""Generate the target regression vector (coordinates).
|
||||
|
||||
Required keys: 'joints_3d', 'joints_3d_visible', 'ann_info'. Modified keys:
|
||||
'target', and 'target_weight'.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _generate_target(self, cfg, joints_3d, joints_3d_visible):
|
||||
"""Generate the target regression vector.
|
||||
|
||||
Args:
|
||||
cfg (dict): data config
|
||||
joints_3d: np.ndarray([num_joints, 3])
|
||||
joints_3d_visible: np.ndarray([num_joints, 3])
|
||||
|
||||
Returns:
|
||||
target, target_weight(1: visible, 0: invisible)
|
||||
"""
|
||||
image_size = cfg['image_size']
|
||||
joint_weights = cfg['joint_weights']
|
||||
use_different_joint_weights = cfg['use_different_joint_weights']
|
||||
|
||||
mask = (joints_3d[:, 0] >= 0) * (
|
||||
joints_3d[:, 0] <= image_size[0] - 1) * (joints_3d[:, 1] >= 0) * (
|
||||
joints_3d[:, 1] <= image_size[1] - 1)
|
||||
|
||||
target = joints_3d[:, :2] / image_size
|
||||
|
||||
target = target.astype(np.float32)
|
||||
target_weight = joints_3d_visible[:, :2] * mask[:, None]
|
||||
|
||||
if use_different_joint_weights:
|
||||
target_weight = np.multiply(target_weight, joint_weights)
|
||||
|
||||
return target, target_weight
|
||||
|
||||
def __call__(self, results):
|
||||
"""Generate the target heatmap."""
|
||||
joints_3d = results['joints_3d']
|
||||
joints_3d_visible = results['joints_3d_visible']
|
||||
|
||||
target, target_weight = self._generate_target(results['ann_info'],
|
||||
joints_3d,
|
||||
joints_3d_visible)
|
||||
|
||||
results['target'] = target
|
||||
results['target_weight'] = target_weight
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@PIPELINES.register_module()
|
||||
class TopDownRandomTranslation:
|
||||
"""Data augmentation with random translation.
|
||||
|
||||
Required key: 'scale' and 'center'.
|
||||
|
||||
Modifies key: 'center'.
|
||||
|
||||
Note:
|
||||
- bbox height: H
|
||||
- bbox width: W
|
||||
|
||||
Args:
|
||||
trans_factor (float): Translating center to
|
||||
``[-trans_factor, trans_factor] * [W, H] + center``.
|
||||
trans_prob (float): Probability of random translation.
|
||||
"""
|
||||
|
||||
def __init__(self, trans_factor=0.15, trans_prob=1.0):
|
||||
self.trans_factor = trans_factor
|
||||
self.trans_prob = trans_prob
|
||||
|
||||
def __call__(self, results):
|
||||
"""Perform data augmentation with random translation."""
|
||||
center = results['center']
|
||||
scale = results['scale']
|
||||
if np.random.rand() <= self.trans_prob:
|
||||
# reference bbox size is [200, 200] pixels
|
||||
center += self.trans_factor * np.random.uniform(
|
||||
-1, 1, size=2) * scale * 200
|
||||
results['center'] = center
|
||||
return results
|
||||
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
from .builder import DATASETS, PIPELINES
|
||||
|
||||
__all__ = ['DATASETS', 'PIPELINES']
|
||||
|
||||
warnings.simplefilter('once', DeprecationWarning)
|
||||
warnings.warn(
|
||||
'Registries (DATASETS, PIPELINES) have been moved to '
|
||||
'mmpose.datasets.builder. Importing from '
|
||||
'mmpose.models.registry will be deprecated in the future.',
|
||||
DeprecationWarning)
|
||||
@@ -0,0 +1,4 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .distributed_sampler import DistributedSampler
|
||||
|
||||
__all__ = ['DistributedSampler']
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
from torch.utils.data import DistributedSampler as _DistributedSampler
|
||||
|
||||
|
||||
class DistributedSampler(_DistributedSampler):
|
||||
"""DistributedSampler inheriting from
|
||||
`torch.utils.data.DistributedSampler`.
|
||||
|
||||
In pytorch of lower versions, there is no `shuffle` argument. This child
|
||||
class will port one to DistributedSampler.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
dataset,
|
||||
num_replicas=None,
|
||||
rank=None,
|
||||
shuffle=True,
|
||||
seed=0):
|
||||
super().__init__(
|
||||
dataset, num_replicas=num_replicas, rank=rank, shuffle=shuffle)
|
||||
# for the compatibility from PyTorch 1.3+
|
||||
self.seed = seed if seed is not None else 0
|
||||
|
||||
def __iter__(self):
|
||||
"""Deterministically shuffle based on epoch."""
|
||||
if self.shuffle:
|
||||
g = torch.Generator()
|
||||
g.manual_seed(self.epoch + self.seed)
|
||||
indices = torch.randperm(len(self.dataset), generator=g).tolist()
|
||||
else:
|
||||
indices = torch.arange(len(self.dataset)).tolist()
|
||||
|
||||
# add extra samples to make it evenly divisible
|
||||
indices += indices[:(self.total_size - len(indices))]
|
||||
assert len(indices) == self.total_size
|
||||
|
||||
# subsample
|
||||
indices = indices[self.rank:self.total_size:self.num_replicas]
|
||||
assert len(indices) == self.num_samples
|
||||
return iter(indices)
|
||||
Reference in New Issue
Block a user