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,20 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .inference import (inference_bottom_up_pose_model,
|
||||
inference_top_down_pose_model, init_pose_model,
|
||||
process_mmdet_results, vis_pose_result)
|
||||
from .inference_3d import (extract_pose_sequence, inference_interhand_3d_model,
|
||||
inference_mesh_model, inference_pose_lifter_model,
|
||||
vis_3d_mesh_result, vis_3d_pose_result)
|
||||
from .inference_tracking import get_track_id, vis_pose_tracking_result
|
||||
from .test import multi_gpu_test, single_gpu_test
|
||||
from .train import init_random_seed, train_model
|
||||
|
||||
__all__ = [
|
||||
'train_model', 'init_pose_model', 'inference_top_down_pose_model',
|
||||
'inference_bottom_up_pose_model', 'multi_gpu_test', 'single_gpu_test',
|
||||
'vis_pose_result', 'get_track_id', 'vis_pose_tracking_result',
|
||||
'inference_pose_lifter_model', 'vis_3d_pose_result',
|
||||
'inference_interhand_3d_model', 'extract_pose_sequence',
|
||||
'inference_mesh_model', 'vis_3d_mesh_result', 'process_mmdet_results',
|
||||
'init_random_seed'
|
||||
]
|
||||
@@ -0,0 +1,833 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os
|
||||
import warnings
|
||||
|
||||
import mmcv
|
||||
import numpy as np
|
||||
import torch
|
||||
from mmcv.parallel import collate, scatter
|
||||
from mmcv.runner import load_checkpoint
|
||||
from PIL import Image
|
||||
|
||||
from mmpose.core.post_processing import oks_nms
|
||||
from mmpose.datasets.dataset_info import DatasetInfo
|
||||
from mmpose.datasets.pipelines import Compose
|
||||
from mmpose.models import build_posenet
|
||||
from mmpose.utils.hooks import OutputHook
|
||||
|
||||
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
|
||||
|
||||
|
||||
def init_pose_model(config, checkpoint=None, device='cuda:0'):
|
||||
"""Initialize a pose model from config file.
|
||||
|
||||
Args:
|
||||
config (str or :obj:`mmcv.Config`): Config file path or the config
|
||||
object.
|
||||
checkpoint (str, optional): Checkpoint path. If left as None, the model
|
||||
will not load any weights.
|
||||
|
||||
Returns:
|
||||
nn.Module: The constructed detector.
|
||||
"""
|
||||
if isinstance(config, str):
|
||||
config = mmcv.Config.fromfile(config)
|
||||
elif not isinstance(config, mmcv.Config):
|
||||
raise TypeError('config must be a filename or Config object, '
|
||||
f'but got {type(config)}')
|
||||
config.model.pretrained = None
|
||||
model = build_posenet(config.model)
|
||||
if checkpoint is not None:
|
||||
# load model checkpoint
|
||||
load_checkpoint(model, checkpoint, map_location='cpu')
|
||||
# save the config in the model for convenience
|
||||
model.cfg = config
|
||||
model.to(device)
|
||||
model.eval()
|
||||
return model
|
||||
|
||||
|
||||
def _xyxy2xywh(bbox_xyxy):
|
||||
"""Transform the bbox format from x1y1x2y2 to xywh.
|
||||
|
||||
Args:
|
||||
bbox_xyxy (np.ndarray): Bounding boxes (with scores), shaped (n, 4) or
|
||||
(n, 5). (left, top, right, bottom, [score])
|
||||
|
||||
Returns:
|
||||
np.ndarray: Bounding boxes (with scores),
|
||||
shaped (n, 4) or (n, 5). (left, top, width, height, [score])
|
||||
"""
|
||||
bbox_xywh = bbox_xyxy.copy()
|
||||
bbox_xywh[:, 2] = bbox_xywh[:, 2] - bbox_xywh[:, 0] + 1
|
||||
bbox_xywh[:, 3] = bbox_xywh[:, 3] - bbox_xywh[:, 1] + 1
|
||||
|
||||
return bbox_xywh
|
||||
|
||||
|
||||
def _xywh2xyxy(bbox_xywh):
|
||||
"""Transform the bbox format from xywh to x1y1x2y2.
|
||||
|
||||
Args:
|
||||
bbox_xywh (ndarray): Bounding boxes (with scores),
|
||||
shaped (n, 4) or (n, 5). (left, top, width, height, [score])
|
||||
Returns:
|
||||
np.ndarray: Bounding boxes (with scores), shaped (n, 4) or
|
||||
(n, 5). (left, top, right, bottom, [score])
|
||||
"""
|
||||
bbox_xyxy = bbox_xywh.copy()
|
||||
bbox_xyxy[:, 2] = bbox_xyxy[:, 2] + bbox_xyxy[:, 0] - 1
|
||||
bbox_xyxy[:, 3] = bbox_xyxy[:, 3] + bbox_xyxy[:, 1] - 1
|
||||
|
||||
return bbox_xyxy
|
||||
|
||||
|
||||
def _box2cs(cfg, box):
|
||||
"""This encodes bbox(x,y,w,h) into (center, scale)
|
||||
|
||||
Args:
|
||||
x, y, w, h
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing center and scale.
|
||||
|
||||
- np.ndarray[float32](2,): Center of the bbox (x, y).
|
||||
- np.ndarray[float32](2,): Scale of the bbox w & h.
|
||||
"""
|
||||
|
||||
x, y, w, h = box[:4]
|
||||
input_size = cfg.data_cfg['image_size']
|
||||
aspect_ratio = input_size[0] / input_size[1]
|
||||
center = np.array([x + w * 0.5, y + h * 0.5], dtype=np.float32)
|
||||
|
||||
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)
|
||||
scale = scale * 1.25
|
||||
|
||||
return center, scale
|
||||
|
||||
|
||||
def _inference_single_pose_model(model,
|
||||
img_or_path,
|
||||
bboxes,
|
||||
dataset='TopDownCocoDataset',
|
||||
dataset_info=None,
|
||||
return_heatmap=False):
|
||||
"""Inference human bounding boxes.
|
||||
|
||||
Note:
|
||||
- num_bboxes: N
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
model (nn.Module): The loaded pose model.
|
||||
img_or_path (str | np.ndarray): Image filename or loaded image.
|
||||
bboxes (list | np.ndarray): All bounding boxes (with scores),
|
||||
shaped (N, 4) or (N, 5). (left, top, width, height, [score])
|
||||
where N is number of bounding boxes.
|
||||
dataset (str): Dataset name. Deprecated.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
outputs (list[str] | tuple[str]): Names of layers whose output is
|
||||
to be returned, default: None
|
||||
|
||||
Returns:
|
||||
ndarray[NxKx3]: Predicted pose x, y, score.
|
||||
heatmap[N, K, H, W]: Model output heatmap.
|
||||
"""
|
||||
|
||||
cfg = model.cfg
|
||||
device = next(model.parameters()).device
|
||||
if device.type == 'cpu':
|
||||
device = -1
|
||||
|
||||
# build the data pipeline
|
||||
test_pipeline = Compose(cfg.test_pipeline)
|
||||
|
||||
assert len(bboxes[0]) in [4, 5]
|
||||
|
||||
if dataset_info is not None:
|
||||
dataset_name = dataset_info.dataset_name
|
||||
flip_pairs = dataset_info.flip_pairs
|
||||
else:
|
||||
warnings.warn(
|
||||
'dataset is deprecated.'
|
||||
'Please set `dataset_info` in the config.'
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 for details.',
|
||||
DeprecationWarning)
|
||||
# TODO: These will be removed in the later versions.
|
||||
if dataset in ('TopDownCocoDataset', 'TopDownOCHumanDataset',
|
||||
'AnimalMacaqueDataset'):
|
||||
flip_pairs = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12],
|
||||
[13, 14], [15, 16]]
|
||||
elif dataset == 'TopDownCocoWholeBodyDataset':
|
||||
body = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12],
|
||||
[13, 14], [15, 16]]
|
||||
foot = [[17, 20], [18, 21], [19, 22]]
|
||||
|
||||
face = [[23, 39], [24, 38], [25, 37], [26, 36], [27, 35], [28, 34],
|
||||
[29, 33], [30, 32], [40, 49], [41, 48], [42, 47], [43, 46],
|
||||
[44, 45], [54, 58], [55, 57], [59, 68], [60, 67], [61, 66],
|
||||
[62, 65], [63, 70], [64, 69], [71, 77], [72, 76], [73, 75],
|
||||
[78, 82], [79, 81], [83, 87], [84, 86], [88, 90]]
|
||||
|
||||
hand = [[91, 112], [92, 113], [93, 114], [94, 115], [95, 116],
|
||||
[96, 117], [97, 118], [98, 119], [99, 120], [100, 121],
|
||||
[101, 122], [102, 123], [103, 124], [104, 125], [105, 126],
|
||||
[106, 127], [107, 128], [108, 129], [109, 130], [110, 131],
|
||||
[111, 132]]
|
||||
flip_pairs = body + foot + face + hand
|
||||
elif dataset == 'TopDownAicDataset':
|
||||
flip_pairs = [[0, 3], [1, 4], [2, 5], [6, 9], [7, 10], [8, 11]]
|
||||
elif dataset == 'TopDownMpiiDataset':
|
||||
flip_pairs = [[0, 5], [1, 4], [2, 3], [10, 15], [11, 14], [12, 13]]
|
||||
elif dataset == 'TopDownMpiiTrbDataset':
|
||||
flip_pairs = [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [10, 11],
|
||||
[14, 15], [16, 22], [28, 34], [17, 23], [29, 35],
|
||||
[18, 24], [30, 36], [19, 25], [31, 37], [20, 26],
|
||||
[32, 38], [21, 27], [33, 39]]
|
||||
elif dataset in ('OneHand10KDataset', 'FreiHandDataset',
|
||||
'PanopticDataset', 'InterHand2DDataset'):
|
||||
flip_pairs = []
|
||||
elif dataset in 'Face300WDataset':
|
||||
flip_pairs = [[0, 16], [1, 15], [2, 14], [3, 13], [4, 12], [5, 11],
|
||||
[6, 10], [7, 9], [17, 26], [18, 25], [19, 24],
|
||||
[20, 23], [21, 22], [31, 35], [32, 34], [36, 45],
|
||||
[37, 44], [38, 43], [39, 42], [40, 47], [41, 46],
|
||||
[48, 54], [49, 53], [50, 52], [61, 63], [60, 64],
|
||||
[67, 65], [58, 56], [59, 55]]
|
||||
|
||||
elif dataset in 'FaceAFLWDataset':
|
||||
flip_pairs = [[0, 5], [1, 4], [2, 3], [6, 11], [7, 10], [8, 9],
|
||||
[12, 14], [15, 17]]
|
||||
|
||||
elif dataset in 'FaceCOFWDataset':
|
||||
flip_pairs = [[0, 1], [4, 6], [2, 3], [5, 7], [8, 9], [10, 11],
|
||||
[12, 14], [16, 17], [13, 15], [18, 19], [22, 23]]
|
||||
|
||||
elif dataset in 'FaceWFLWDataset':
|
||||
flip_pairs = [[0, 32], [1, 31], [2, 30], [3, 29], [4, 28], [5, 27],
|
||||
[6, 26], [7, 25], [8, 24], [9, 23], [10, 22],
|
||||
[11, 21], [12, 20], [13, 19], [14, 18], [15, 17],
|
||||
[33, 46], [34, 45], [35, 44], [36, 43], [37, 42],
|
||||
[38, 50], [39, 49], [40, 48], [41, 47], [60, 72],
|
||||
[61, 71], [62, 70], [63, 69], [64, 68], [65, 75],
|
||||
[66, 74], [67, 73], [55, 59], [56, 58], [76, 82],
|
||||
[77, 81], [78, 80], [87, 83], [86, 84], [88, 92],
|
||||
[89, 91], [95, 93], [96, 97]]
|
||||
|
||||
elif dataset in 'AnimalFlyDataset':
|
||||
flip_pairs = [[1, 2], [6, 18], [7, 19], [8, 20], [9, 21], [10, 22],
|
||||
[11, 23], [12, 24], [13, 25], [14, 26], [15, 27],
|
||||
[16, 28], [17, 29], [30, 31]]
|
||||
elif dataset in 'AnimalHorse10Dataset':
|
||||
flip_pairs = []
|
||||
|
||||
elif dataset in 'AnimalLocustDataset':
|
||||
flip_pairs = [[5, 20], [6, 21], [7, 22], [8, 23], [9, 24],
|
||||
[10, 25], [11, 26], [12, 27], [13, 28], [14, 29],
|
||||
[15, 30], [16, 31], [17, 32], [18, 33], [19, 34]]
|
||||
|
||||
elif dataset in 'AnimalZebraDataset':
|
||||
flip_pairs = [[3, 4], [5, 6]]
|
||||
|
||||
elif dataset in 'AnimalPoseDataset':
|
||||
flip_pairs = [[0, 1], [2, 3], [8, 9], [10, 11], [12, 13], [14, 15],
|
||||
[16, 17], [18, 19]]
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
dataset_name = dataset
|
||||
|
||||
batch_data = []
|
||||
for bbox in bboxes:
|
||||
center, scale = _box2cs(cfg, bbox)
|
||||
|
||||
# prepare data
|
||||
data = {
|
||||
'center':
|
||||
center,
|
||||
'scale':
|
||||
scale,
|
||||
'bbox_score':
|
||||
bbox[4] if len(bbox) == 5 else 1,
|
||||
'bbox_id':
|
||||
0, # need to be assigned if batch_size > 1
|
||||
'dataset':
|
||||
dataset_name,
|
||||
'joints_3d':
|
||||
np.zeros((cfg.data_cfg.num_joints, 3), dtype=np.float32),
|
||||
'joints_3d_visible':
|
||||
np.zeros((cfg.data_cfg.num_joints, 3), dtype=np.float32),
|
||||
'rotation':
|
||||
0,
|
||||
'ann_info': {
|
||||
'image_size': np.array(cfg.data_cfg['image_size']),
|
||||
'num_joints': cfg.data_cfg['num_joints'],
|
||||
'flip_pairs': flip_pairs
|
||||
}
|
||||
}
|
||||
if isinstance(img_or_path, np.ndarray):
|
||||
data['img'] = img_or_path
|
||||
else:
|
||||
data['image_file'] = img_or_path
|
||||
|
||||
data = test_pipeline(data)
|
||||
batch_data.append(data)
|
||||
|
||||
batch_data = collate(batch_data, samples_per_gpu=len(batch_data))
|
||||
batch_data = scatter(batch_data, [device])[0]
|
||||
|
||||
# forward the model
|
||||
with torch.no_grad():
|
||||
result = model(
|
||||
img=batch_data['img'],
|
||||
img_metas=batch_data['img_metas'],
|
||||
return_loss=False,
|
||||
return_heatmap=return_heatmap)
|
||||
|
||||
return result['preds'], result['output_heatmap']
|
||||
|
||||
|
||||
def inference_top_down_pose_model(model,
|
||||
img_or_path,
|
||||
person_results=None,
|
||||
bbox_thr=None,
|
||||
format='xywh',
|
||||
dataset='TopDownCocoDataset',
|
||||
dataset_info=None,
|
||||
return_heatmap=False,
|
||||
outputs=None):
|
||||
"""Inference a single image with a list of person bounding boxes.
|
||||
|
||||
Note:
|
||||
- num_people: P
|
||||
- num_keypoints: K
|
||||
- bbox height: H
|
||||
- bbox width: W
|
||||
|
||||
Args:
|
||||
model (nn.Module): The loaded pose model.
|
||||
img_or_path (str| np.ndarray): Image filename or loaded image.
|
||||
person_results (list(dict), optional): a list of detected persons that
|
||||
contains ``bbox`` and/or ``track_id``:
|
||||
|
||||
- ``bbox`` (4, ) or (5, ): The person bounding box, which contains
|
||||
4 box coordinates (and score).
|
||||
- ``track_id`` (int): The unique id for each human instance. If
|
||||
not provided, a dummy person result with a bbox covering
|
||||
the entire image will be used. Default: None.
|
||||
bbox_thr (float | None): Threshold for bounding boxes. Only bboxes
|
||||
with higher scores will be fed into the pose detector.
|
||||
If bbox_thr is None, all boxes will be used.
|
||||
format (str): bbox format ('xyxy' | 'xywh'). Default: 'xywh'.
|
||||
|
||||
- `xyxy` means (left, top, right, bottom),
|
||||
- `xywh` means (left, top, width, height).
|
||||
dataset (str): Dataset name, e.g. 'TopDownCocoDataset'.
|
||||
It is deprecated. Please use dataset_info instead.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
return_heatmap (bool) : Flag to return heatmap, default: False
|
||||
outputs (list(str) | tuple(str)) : Names of layers whose outputs
|
||||
need to be returned. Default: None.
|
||||
|
||||
Returns:
|
||||
tuple:
|
||||
- pose_results (list[dict]): The bbox & pose info. \
|
||||
Each item in the list is a dictionary, \
|
||||
containing the bbox: (left, top, right, bottom, [score]) \
|
||||
and the pose (ndarray[Kx3]): x, y, score.
|
||||
- returned_outputs (list[dict[np.ndarray[N, K, H, W] | \
|
||||
torch.Tensor[N, K, H, W]]]): \
|
||||
Output feature maps from layers specified in `outputs`. \
|
||||
Includes 'heatmap' if `return_heatmap` is True.
|
||||
"""
|
||||
# get dataset info
|
||||
if (dataset_info is None and hasattr(model, 'cfg')
|
||||
and 'dataset_info' in model.cfg):
|
||||
dataset_info = DatasetInfo(model.cfg.dataset_info)
|
||||
if dataset_info is None:
|
||||
warnings.warn(
|
||||
'dataset is deprecated.'
|
||||
'Please set `dataset_info` in the config.'
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663'
|
||||
' for details.', DeprecationWarning)
|
||||
|
||||
# only two kinds of bbox format is supported.
|
||||
assert format in ['xyxy', 'xywh']
|
||||
|
||||
pose_results = []
|
||||
returned_outputs = []
|
||||
|
||||
if person_results is None:
|
||||
# create dummy person results
|
||||
if isinstance(img_or_path, str):
|
||||
width, height = Image.open(img_or_path).size
|
||||
else:
|
||||
height, width = img_or_path.shape[:2]
|
||||
person_results = [{'bbox': np.array([0, 0, width, height])}]
|
||||
|
||||
if len(person_results) == 0:
|
||||
return pose_results, returned_outputs
|
||||
|
||||
# Change for-loop preprocess each bbox to preprocess all bboxes at once.
|
||||
bboxes = np.array([box['bbox'] for box in person_results])
|
||||
|
||||
# Select bboxes by score threshold
|
||||
if bbox_thr is not None:
|
||||
assert bboxes.shape[1] == 5
|
||||
valid_idx = np.where(bboxes[:, 4] > bbox_thr)[0]
|
||||
bboxes = bboxes[valid_idx]
|
||||
person_results = [person_results[i] for i in valid_idx]
|
||||
|
||||
if format == 'xyxy':
|
||||
bboxes_xyxy = bboxes
|
||||
bboxes_xywh = _xyxy2xywh(bboxes)
|
||||
else:
|
||||
# format is already 'xywh'
|
||||
bboxes_xywh = bboxes
|
||||
bboxes_xyxy = _xywh2xyxy(bboxes)
|
||||
|
||||
# if bbox_thr remove all bounding box
|
||||
if len(bboxes_xywh) == 0:
|
||||
return [], []
|
||||
|
||||
with OutputHook(model, outputs=outputs, as_tensor=False) as h:
|
||||
# poses is results['pred'] # N x 17x 3
|
||||
poses, heatmap = _inference_single_pose_model(
|
||||
model,
|
||||
img_or_path,
|
||||
bboxes_xywh,
|
||||
dataset=dataset,
|
||||
dataset_info=dataset_info,
|
||||
return_heatmap=return_heatmap)
|
||||
|
||||
if return_heatmap:
|
||||
h.layer_outputs['heatmap'] = heatmap
|
||||
|
||||
returned_outputs.append(h.layer_outputs)
|
||||
|
||||
assert len(poses) == len(person_results), print(
|
||||
len(poses), len(person_results), len(bboxes_xyxy))
|
||||
for pose, person_result, bbox_xyxy in zip(poses, person_results,
|
||||
bboxes_xyxy):
|
||||
pose_result = person_result.copy()
|
||||
pose_result['keypoints'] = pose
|
||||
pose_result['bbox'] = bbox_xyxy
|
||||
pose_results.append(pose_result)
|
||||
|
||||
return pose_results, returned_outputs
|
||||
|
||||
|
||||
def inference_bottom_up_pose_model(model,
|
||||
img_or_path,
|
||||
dataset='BottomUpCocoDataset',
|
||||
dataset_info=None,
|
||||
pose_nms_thr=0.9,
|
||||
return_heatmap=False,
|
||||
outputs=None):
|
||||
"""Inference a single image with a bottom-up pose model.
|
||||
|
||||
Note:
|
||||
- num_people: P
|
||||
- num_keypoints: K
|
||||
- bbox height: H
|
||||
- bbox width: W
|
||||
|
||||
Args:
|
||||
model (nn.Module): The loaded pose model.
|
||||
img_or_path (str| np.ndarray): Image filename or loaded image.
|
||||
dataset (str): Dataset name, e.g. 'BottomUpCocoDataset'.
|
||||
It is deprecated. Please use dataset_info instead.
|
||||
dataset_info (DatasetInfo): A class containing all dataset info.
|
||||
pose_nms_thr (float): retain oks overlap < pose_nms_thr, default: 0.9.
|
||||
return_heatmap (bool) : Flag to return heatmap, default: False.
|
||||
outputs (list(str) | tuple(str)) : Names of layers whose outputs
|
||||
need to be returned, default: None.
|
||||
|
||||
Returns:
|
||||
tuple:
|
||||
- pose_results (list[np.ndarray]): The predicted pose info. \
|
||||
The length of the list is the number of people (P). \
|
||||
Each item in the list is a ndarray, containing each \
|
||||
person's pose (np.ndarray[Kx3]): x, y, score.
|
||||
- returned_outputs (list[dict[np.ndarray[N, K, H, W] | \
|
||||
torch.Tensor[N, K, H, W]]]): \
|
||||
Output feature maps from layers specified in `outputs`. \
|
||||
Includes 'heatmap' if `return_heatmap` is True.
|
||||
"""
|
||||
# get dataset info
|
||||
if (dataset_info is None and hasattr(model, 'cfg')
|
||||
and 'dataset_info' in model.cfg):
|
||||
dataset_info = DatasetInfo(model.cfg.dataset_info)
|
||||
|
||||
if dataset_info is not None:
|
||||
dataset_name = dataset_info.dataset_name
|
||||
flip_index = dataset_info.flip_index
|
||||
sigmas = getattr(dataset_info, 'sigmas', None)
|
||||
else:
|
||||
warnings.warn(
|
||||
'dataset is deprecated.'
|
||||
'Please set `dataset_info` in the config.'
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 for details.',
|
||||
DeprecationWarning)
|
||||
assert (dataset == 'BottomUpCocoDataset')
|
||||
dataset_name = dataset
|
||||
flip_index = [0, 2, 1, 4, 3, 6, 5, 8, 7, 10, 9, 12, 11, 14, 13, 16, 15]
|
||||
sigmas = None
|
||||
|
||||
pose_results = []
|
||||
returned_outputs = []
|
||||
|
||||
cfg = model.cfg
|
||||
device = next(model.parameters()).device
|
||||
if device.type == 'cpu':
|
||||
device = -1
|
||||
|
||||
# build the data pipeline
|
||||
test_pipeline = Compose(cfg.test_pipeline)
|
||||
|
||||
# prepare data
|
||||
data = {
|
||||
'dataset': dataset_name,
|
||||
'ann_info': {
|
||||
'image_size': np.array(cfg.data_cfg['image_size']),
|
||||
'num_joints': cfg.data_cfg['num_joints'],
|
||||
'flip_index': flip_index,
|
||||
}
|
||||
}
|
||||
if isinstance(img_or_path, np.ndarray):
|
||||
data['img'] = img_or_path
|
||||
else:
|
||||
data['image_file'] = img_or_path
|
||||
|
||||
data = test_pipeline(data)
|
||||
data = collate([data], samples_per_gpu=1)
|
||||
data = scatter(data, [device])[0]
|
||||
|
||||
with OutputHook(model, outputs=outputs, as_tensor=False) as h:
|
||||
# forward the model
|
||||
with torch.no_grad():
|
||||
result = model(
|
||||
img=data['img'],
|
||||
img_metas=data['img_metas'],
|
||||
return_loss=False,
|
||||
return_heatmap=return_heatmap)
|
||||
|
||||
if return_heatmap:
|
||||
h.layer_outputs['heatmap'] = result['output_heatmap']
|
||||
|
||||
returned_outputs.append(h.layer_outputs)
|
||||
|
||||
for idx, pred in enumerate(result['preds']):
|
||||
area = (np.max(pred[:, 0]) - np.min(pred[:, 0])) * (
|
||||
np.max(pred[:, 1]) - np.min(pred[:, 1]))
|
||||
pose_results.append({
|
||||
'keypoints': pred[:, :3],
|
||||
'score': result['scores'][idx],
|
||||
'area': area,
|
||||
})
|
||||
|
||||
# pose nms
|
||||
score_per_joint = cfg.model.test_cfg.get('score_per_joint', False)
|
||||
keep = oks_nms(
|
||||
pose_results,
|
||||
pose_nms_thr,
|
||||
sigmas,
|
||||
score_per_joint=score_per_joint)
|
||||
pose_results = [pose_results[_keep] for _keep in keep]
|
||||
|
||||
return pose_results, returned_outputs
|
||||
|
||||
|
||||
def vis_pose_result(model,
|
||||
img,
|
||||
result,
|
||||
radius=4,
|
||||
thickness=1,
|
||||
kpt_score_thr=0.3,
|
||||
bbox_color='green',
|
||||
dataset='TopDownCocoDataset',
|
||||
dataset_info=None,
|
||||
show=False,
|
||||
out_file=None):
|
||||
"""Visualize the detection results on the image.
|
||||
|
||||
Args:
|
||||
model (nn.Module): The loaded detector.
|
||||
img (str | np.ndarray): Image filename or loaded image.
|
||||
result (list[dict]): The results to draw over `img`
|
||||
(bbox_result, pose_result).
|
||||
radius (int): Radius of circles.
|
||||
thickness (int): Thickness of lines.
|
||||
kpt_score_thr (float): The threshold to visualize the keypoints.
|
||||
skeleton (list[tuple()]): Default None.
|
||||
show (bool): Whether to show the image. Default True.
|
||||
out_file (str|None): The filename of the output visualization image.
|
||||
"""
|
||||
|
||||
# get dataset info
|
||||
if (dataset_info is None and hasattr(model, 'cfg')
|
||||
and 'dataset_info' in model.cfg):
|
||||
dataset_info = DatasetInfo(model.cfg.dataset_info)
|
||||
|
||||
if dataset_info is not None:
|
||||
skeleton = dataset_info.skeleton
|
||||
pose_kpt_color = dataset_info.pose_kpt_color
|
||||
pose_link_color = dataset_info.pose_link_color
|
||||
else:
|
||||
warnings.warn(
|
||||
'dataset is deprecated.'
|
||||
'Please set `dataset_info` in the config.'
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 for details.',
|
||||
DeprecationWarning)
|
||||
# TODO: These will be removed in the later versions.
|
||||
palette = np.array([[255, 128, 0], [255, 153, 51], [255, 178, 102],
|
||||
[230, 230, 0], [255, 153, 255], [153, 204, 255],
|
||||
[255, 102, 255], [255, 51, 255], [102, 178, 255],
|
||||
[51, 153, 255], [255, 153, 153], [255, 102, 102],
|
||||
[255, 51, 51], [153, 255, 153], [102, 255, 102],
|
||||
[51, 255, 51], [0, 255, 0], [0, 0, 255],
|
||||
[255, 0, 0], [255, 255, 255]])
|
||||
|
||||
if dataset in ('TopDownCocoDataset', 'BottomUpCocoDataset',
|
||||
'TopDownOCHumanDataset', 'AnimalMacaqueDataset'):
|
||||
# show the results
|
||||
skeleton = [[15, 13], [13, 11], [16, 14], [14, 12], [11, 12],
|
||||
[5, 11], [6, 12], [5, 6], [5, 7], [6, 8], [7, 9],
|
||||
[8, 10], [1, 2], [0, 1], [0, 2], [1, 3], [2, 4],
|
||||
[3, 5], [4, 6]]
|
||||
|
||||
pose_link_color = palette[[
|
||||
0, 0, 0, 0, 7, 7, 7, 9, 9, 9, 9, 9, 16, 16, 16, 16, 16, 16, 16
|
||||
]]
|
||||
pose_kpt_color = palette[[
|
||||
16, 16, 16, 16, 16, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0
|
||||
]]
|
||||
|
||||
elif dataset == 'TopDownCocoWholeBodyDataset':
|
||||
# show the results
|
||||
skeleton = [[15, 13], [13, 11], [16, 14], [14, 12], [11, 12],
|
||||
[5, 11], [6, 12], [5, 6], [5, 7], [6, 8], [7, 9],
|
||||
[8, 10], [1, 2], [0, 1], [0, 2],
|
||||
[1, 3], [2, 4], [3, 5], [4, 6], [15, 17], [15, 18],
|
||||
[15, 19], [16, 20], [16, 21], [16, 22], [91, 92],
|
||||
[92, 93], [93, 94], [94, 95], [91, 96], [96, 97],
|
||||
[97, 98], [98, 99], [91, 100], [100, 101], [101, 102],
|
||||
[102, 103], [91, 104], [104, 105], [105, 106],
|
||||
[106, 107], [91, 108], [108, 109], [109, 110],
|
||||
[110, 111], [112, 113], [113, 114], [114, 115],
|
||||
[115, 116], [112, 117], [117, 118], [118, 119],
|
||||
[119, 120], [112, 121], [121, 122], [122, 123],
|
||||
[123, 124], [112, 125], [125, 126], [126, 127],
|
||||
[127, 128], [112, 129], [129, 130], [130, 131],
|
||||
[131, 132]]
|
||||
|
||||
pose_link_color = palette[[
|
||||
0, 0, 0, 0, 7, 7, 7, 9, 9, 9, 9, 9, 16, 16, 16, 16, 16, 16, 16
|
||||
] + [16, 16, 16, 16, 16, 16] + [
|
||||
0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12, 16, 16, 16,
|
||||
16
|
||||
] + [
|
||||
0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12, 16, 16, 16,
|
||||
16
|
||||
]]
|
||||
pose_kpt_color = palette[
|
||||
[16, 16, 16, 16, 16, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0] +
|
||||
[0, 0, 0, 0, 0, 0] + [19] * (68 + 42)]
|
||||
|
||||
elif dataset == 'TopDownAicDataset':
|
||||
skeleton = [[2, 1], [1, 0], [0, 13], [13, 3], [3, 4], [4, 5],
|
||||
[8, 7], [7, 6], [6, 9], [9, 10], [10, 11], [12, 13],
|
||||
[0, 6], [3, 9]]
|
||||
|
||||
pose_link_color = palette[[
|
||||
9, 9, 9, 9, 9, 9, 16, 16, 16, 16, 16, 0, 7, 7
|
||||
]]
|
||||
pose_kpt_color = palette[[
|
||||
9, 9, 9, 9, 9, 9, 16, 16, 16, 16, 16, 16, 0, 0
|
||||
]]
|
||||
|
||||
elif dataset == 'TopDownMpiiDataset':
|
||||
skeleton = [[0, 1], [1, 2], [2, 6], [6, 3], [3, 4], [4, 5], [6, 7],
|
||||
[7, 8], [8, 9], [8, 12], [12, 11], [11, 10], [8, 13],
|
||||
[13, 14], [14, 15]]
|
||||
|
||||
pose_link_color = palette[[
|
||||
16, 16, 16, 16, 16, 16, 7, 7, 0, 9, 9, 9, 9, 9, 9
|
||||
]]
|
||||
pose_kpt_color = palette[[
|
||||
16, 16, 16, 16, 16, 16, 7, 7, 0, 0, 9, 9, 9, 9, 9, 9
|
||||
]]
|
||||
|
||||
elif dataset == 'TopDownMpiiTrbDataset':
|
||||
skeleton = [[12, 13], [13, 0], [13, 1], [0, 2], [1, 3], [2, 4],
|
||||
[3, 5], [0, 6], [1, 7], [6, 7], [6, 8], [7,
|
||||
9], [8, 10],
|
||||
[9, 11], [14, 15], [16, 17], [18, 19], [20, 21],
|
||||
[22, 23], [24, 25], [26, 27], [28, 29], [30, 31],
|
||||
[32, 33], [34, 35], [36, 37], [38, 39]]
|
||||
|
||||
pose_link_color = palette[[16] * 14 + [19] * 13]
|
||||
pose_kpt_color = palette[[16] * 14 + [0] * 26]
|
||||
|
||||
elif dataset in ('OneHand10KDataset', 'FreiHandDataset',
|
||||
'PanopticDataset'):
|
||||
skeleton = [[0, 1], [1, 2], [2, 3], [3, 4], [0, 5], [5, 6], [6, 7],
|
||||
[7, 8], [0, 9], [9, 10], [10, 11], [11, 12], [0, 13],
|
||||
[13, 14], [14, 15], [15, 16], [0, 17], [17, 18],
|
||||
[18, 19], [19, 20]]
|
||||
|
||||
pose_link_color = palette[[
|
||||
0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12, 16, 16, 16,
|
||||
16
|
||||
]]
|
||||
pose_kpt_color = palette[[
|
||||
0, 0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12, 16, 16,
|
||||
16, 16
|
||||
]]
|
||||
|
||||
elif dataset == 'InterHand2DDataset':
|
||||
skeleton = [[0, 1], [1, 2], [2, 3], [4, 5], [5, 6], [6, 7], [8, 9],
|
||||
[9, 10], [10, 11], [12, 13], [13, 14], [14, 15],
|
||||
[16, 17], [17, 18], [18, 19], [3, 20], [7, 20],
|
||||
[11, 20], [15, 20], [19, 20]]
|
||||
|
||||
pose_link_color = palette[[
|
||||
0, 0, 0, 4, 4, 4, 8, 8, 8, 12, 12, 12, 16, 16, 16, 0, 4, 8, 12,
|
||||
16
|
||||
]]
|
||||
pose_kpt_color = palette[[
|
||||
0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12, 16, 16, 16,
|
||||
16, 0
|
||||
]]
|
||||
|
||||
elif dataset == 'Face300WDataset':
|
||||
# show the results
|
||||
skeleton = []
|
||||
|
||||
pose_link_color = palette[[]]
|
||||
pose_kpt_color = palette[[19] * 68]
|
||||
kpt_score_thr = 0
|
||||
|
||||
elif dataset == 'FaceAFLWDataset':
|
||||
# show the results
|
||||
skeleton = []
|
||||
|
||||
pose_link_color = palette[[]]
|
||||
pose_kpt_color = palette[[19] * 19]
|
||||
kpt_score_thr = 0
|
||||
|
||||
elif dataset == 'FaceCOFWDataset':
|
||||
# show the results
|
||||
skeleton = []
|
||||
|
||||
pose_link_color = palette[[]]
|
||||
pose_kpt_color = palette[[19] * 29]
|
||||
kpt_score_thr = 0
|
||||
|
||||
elif dataset == 'FaceWFLWDataset':
|
||||
# show the results
|
||||
skeleton = []
|
||||
|
||||
pose_link_color = palette[[]]
|
||||
pose_kpt_color = palette[[19] * 98]
|
||||
kpt_score_thr = 0
|
||||
|
||||
elif dataset == 'AnimalHorse10Dataset':
|
||||
skeleton = [[0, 1], [1, 12], [12, 16], [16, 21], [21, 17],
|
||||
[17, 11], [11, 10], [10, 8], [8, 9], [9, 12], [2, 3],
|
||||
[3, 4], [5, 6], [6, 7], [13, 14], [14, 15], [18, 19],
|
||||
[19, 20]]
|
||||
|
||||
pose_link_color = palette[[4] * 10 + [6] * 2 + [6] * 2 + [7] * 2 +
|
||||
[7] * 2]
|
||||
pose_kpt_color = palette[[
|
||||
4, 4, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 4, 7, 7, 7, 4, 4, 7, 7, 7,
|
||||
4
|
||||
]]
|
||||
|
||||
elif dataset == 'AnimalFlyDataset':
|
||||
skeleton = [[1, 0], [2, 0], [3, 0], [4, 3], [5, 4], [7, 6], [8, 7],
|
||||
[9, 8], [11, 10], [12, 11], [13, 12], [15, 14],
|
||||
[16, 15], [17, 16], [19, 18], [20, 19], [21, 20],
|
||||
[23, 22], [24, 23], [25, 24], [27, 26], [28, 27],
|
||||
[29, 28], [30, 3], [31, 3]]
|
||||
|
||||
pose_link_color = palette[[0] * 25]
|
||||
pose_kpt_color = palette[[0] * 32]
|
||||
|
||||
elif dataset == 'AnimalLocustDataset':
|
||||
skeleton = [[1, 0], [2, 1], [3, 2], [4, 3], [6, 5], [7, 6], [9, 8],
|
||||
[10, 9], [11, 10], [13, 12], [14, 13], [15, 14],
|
||||
[17, 16], [18, 17], [19, 18], [21, 20], [22, 21],
|
||||
[24, 23], [25, 24], [26, 25], [28, 27], [29, 28],
|
||||
[30, 29], [32, 31], [33, 32], [34, 33]]
|
||||
|
||||
pose_link_color = palette[[0] * 26]
|
||||
pose_kpt_color = palette[[0] * 35]
|
||||
|
||||
elif dataset == 'AnimalZebraDataset':
|
||||
skeleton = [[1, 0], [2, 1], [3, 2], [4, 2], [5, 7], [6, 7], [7, 2],
|
||||
[8, 7]]
|
||||
|
||||
pose_link_color = palette[[0] * 8]
|
||||
pose_kpt_color = palette[[0] * 9]
|
||||
|
||||
elif dataset in 'AnimalPoseDataset':
|
||||
skeleton = [[0, 1], [0, 2], [1, 3], [0, 4], [1, 4], [4, 5], [5, 7],
|
||||
[6, 7], [5, 8], [8, 12], [12, 16], [5, 9], [9, 13],
|
||||
[13, 17], [6, 10], [10, 14], [14, 18], [6, 11],
|
||||
[11, 15], [15, 19]]
|
||||
|
||||
pose_link_color = palette[[0] * 20]
|
||||
pose_kpt_color = palette[[0] * 20]
|
||||
else:
|
||||
NotImplementedError()
|
||||
|
||||
if hasattr(model, 'module'):
|
||||
model = model.module
|
||||
|
||||
img = model.show_result(
|
||||
img,
|
||||
result,
|
||||
skeleton,
|
||||
radius=radius,
|
||||
thickness=thickness,
|
||||
pose_kpt_color=pose_kpt_color,
|
||||
pose_link_color=pose_link_color,
|
||||
kpt_score_thr=kpt_score_thr,
|
||||
bbox_color=bbox_color,
|
||||
show=show,
|
||||
out_file=out_file)
|
||||
|
||||
return img
|
||||
|
||||
|
||||
def process_mmdet_results(mmdet_results, cat_id=1):
|
||||
"""Process mmdet results, and return a list of bboxes.
|
||||
|
||||
Args:
|
||||
mmdet_results (list|tuple): mmdet results.
|
||||
cat_id (int): category id (default: 1 for human)
|
||||
|
||||
Returns:
|
||||
person_results (list): a list of detected bounding boxes
|
||||
"""
|
||||
if isinstance(mmdet_results, tuple):
|
||||
det_results = mmdet_results[0]
|
||||
else:
|
||||
det_results = mmdet_results
|
||||
|
||||
bboxes = det_results[cat_id - 1]
|
||||
|
||||
person_results = []
|
||||
for bbox in bboxes:
|
||||
person = {}
|
||||
person['bbox'] = bbox
|
||||
person_results.append(person)
|
||||
|
||||
return person_results
|
||||
@@ -0,0 +1,791 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from mmcv.parallel import collate, scatter
|
||||
|
||||
from mmpose.datasets.pipelines import Compose
|
||||
from .inference import _box2cs, _xywh2xyxy, _xyxy2xywh
|
||||
|
||||
|
||||
def extract_pose_sequence(pose_results, frame_idx, causal, seq_len, step=1):
|
||||
"""Extract the target frame from 2D pose results, and pad the sequence to a
|
||||
fixed length.
|
||||
|
||||
Args:
|
||||
pose_results (list[list[dict]]): Multi-frame pose detection results
|
||||
stored in a nested list. Each element of the outer list is the
|
||||
pose detection results of a single frame, and each element of the
|
||||
inner list is the pose information of one person, which contains:
|
||||
|
||||
- keypoints (ndarray[K, 2 or 3]): x, y, [score]
|
||||
- track_id (int): unique id of each person, required \
|
||||
when ``with_track_id==True``.
|
||||
- bbox ((4, ) or (5, )): left, right, top, bottom, [score]
|
||||
|
||||
frame_idx (int): The index of the frame in the original video.
|
||||
causal (bool): If True, the target frame is the last frame in
|
||||
a sequence. Otherwise, the target frame is in the middle of
|
||||
a sequence.
|
||||
seq_len (int): The number of frames in the input sequence.
|
||||
step (int): Step size to extract frames from the video.
|
||||
|
||||
Returns:
|
||||
list[list[dict]]: Multi-frame pose detection results stored \
|
||||
in a nested list with a length of seq_len.
|
||||
"""
|
||||
|
||||
if causal:
|
||||
frames_left = seq_len - 1
|
||||
frames_right = 0
|
||||
else:
|
||||
frames_left = (seq_len - 1) // 2
|
||||
frames_right = frames_left
|
||||
num_frames = len(pose_results)
|
||||
|
||||
# get the padded sequence
|
||||
pad_left = max(0, frames_left - frame_idx // step)
|
||||
pad_right = max(0, frames_right - (num_frames - 1 - frame_idx) // step)
|
||||
start = max(frame_idx % step, frame_idx - frames_left * step)
|
||||
end = min(num_frames - (num_frames - 1 - frame_idx) % step,
|
||||
frame_idx + frames_right * step + 1)
|
||||
pose_results_seq = [pose_results[0]] * pad_left + \
|
||||
pose_results[start:end:step] + [pose_results[-1]] * pad_right
|
||||
return pose_results_seq
|
||||
|
||||
|
||||
def _gather_pose_lifter_inputs(pose_results,
|
||||
bbox_center,
|
||||
bbox_scale,
|
||||
norm_pose_2d=False):
|
||||
"""Gather input data (keypoints and track_id) for pose lifter model.
|
||||
|
||||
Note:
|
||||
- The temporal length of the pose detection results: T
|
||||
- The number of the person instances: N
|
||||
- The number of the keypoints: K
|
||||
- The channel number of each keypoint: C
|
||||
|
||||
Args:
|
||||
pose_results (List[List[Dict]]): Multi-frame pose detection results
|
||||
stored in a nested list. Each element of the outer list is the
|
||||
pose detection results of a single frame, and each element of the
|
||||
inner list is the pose information of one person, which contains:
|
||||
|
||||
- keypoints (ndarray[K, 2 or 3]): x, y, [score]
|
||||
- track_id (int): unique id of each person, required when
|
||||
``with_track_id==True```
|
||||
- bbox ((4, ) or (5, )): left, right, top, bottom, [score]
|
||||
|
||||
bbox_center (ndarray[1, 2]): x, y. The average center coordinate of the
|
||||
bboxes in the dataset.
|
||||
bbox_scale (int|float): The average scale of the bboxes in the dataset.
|
||||
norm_pose_2d (bool): If True, scale the bbox (along with the 2D
|
||||
pose) to bbox_scale, and move the bbox (along with the 2D pose) to
|
||||
bbox_center. Default: False.
|
||||
|
||||
Returns:
|
||||
list[list[dict]]: Multi-frame pose detection results
|
||||
stored in a nested list. Each element of the outer list is the
|
||||
pose detection results of a single frame, and each element of the
|
||||
inner list is the pose information of one person, which contains:
|
||||
|
||||
- keypoints (ndarray[K, 2 or 3]): x, y, [score]
|
||||
- track_id (int): unique id of each person, required when
|
||||
``with_track_id==True``
|
||||
"""
|
||||
sequence_inputs = []
|
||||
for frame in pose_results:
|
||||
frame_inputs = []
|
||||
for res in frame:
|
||||
inputs = dict()
|
||||
|
||||
if norm_pose_2d:
|
||||
bbox = res['bbox']
|
||||
center = np.array([[(bbox[0] + bbox[2]) / 2,
|
||||
(bbox[1] + bbox[3]) / 2]])
|
||||
scale = max(bbox[2] - bbox[0], bbox[3] - bbox[1])
|
||||
inputs['keypoints'] = (res['keypoints'][:, :2] - center) \
|
||||
/ scale * bbox_scale + bbox_center
|
||||
else:
|
||||
inputs['keypoints'] = res['keypoints'][:, :2]
|
||||
|
||||
if res['keypoints'].shape[1] == 3:
|
||||
inputs['keypoints'] = np.concatenate(
|
||||
[inputs['keypoints'], res['keypoints'][:, 2:]], axis=1)
|
||||
|
||||
if 'track_id' in res:
|
||||
inputs['track_id'] = res['track_id']
|
||||
frame_inputs.append(inputs)
|
||||
sequence_inputs.append(frame_inputs)
|
||||
return sequence_inputs
|
||||
|
||||
|
||||
def _collate_pose_sequence(pose_results, with_track_id=True, target_frame=-1):
|
||||
"""Reorganize multi-frame pose detection results into individual pose
|
||||
sequences.
|
||||
|
||||
Note:
|
||||
- The temporal length of the pose detection results: T
|
||||
- The number of the person instances: N
|
||||
- The number of the keypoints: K
|
||||
- The channel number of each keypoint: C
|
||||
|
||||
Args:
|
||||
pose_results (List[List[Dict]]): Multi-frame pose detection results
|
||||
stored in a nested list. Each element of the outer list is the
|
||||
pose detection results of a single frame, and each element of the
|
||||
inner list is the pose information of one person, which contains:
|
||||
|
||||
- keypoints (ndarray[K, 2 or 3]): x, y, [score]
|
||||
- track_id (int): unique id of each person, required when
|
||||
``with_track_id==True```
|
||||
|
||||
with_track_id (bool): If True, the element in pose_results is expected
|
||||
to contain "track_id", which will be used to gather the pose
|
||||
sequence of a person from multiple frames. Otherwise, the pose
|
||||
results in each frame are expected to have a consistent number and
|
||||
order of identities. Default is True.
|
||||
target_frame (int): The index of the target frame. Default: -1.
|
||||
"""
|
||||
T = len(pose_results)
|
||||
assert T > 0
|
||||
|
||||
target_frame = (T + target_frame) % T # convert negative index to positive
|
||||
|
||||
N = len(pose_results[target_frame]) # use identities in the target frame
|
||||
if N == 0:
|
||||
return []
|
||||
|
||||
K, C = pose_results[target_frame][0]['keypoints'].shape
|
||||
|
||||
track_ids = None
|
||||
if with_track_id:
|
||||
track_ids = [res['track_id'] for res in pose_results[target_frame]]
|
||||
|
||||
pose_sequences = []
|
||||
for idx in range(N):
|
||||
pose_seq = dict()
|
||||
# gather static information
|
||||
for k, v in pose_results[target_frame][idx].items():
|
||||
if k != 'keypoints':
|
||||
pose_seq[k] = v
|
||||
# gather keypoints
|
||||
if not with_track_id:
|
||||
pose_seq['keypoints'] = np.stack(
|
||||
[frame[idx]['keypoints'] for frame in pose_results])
|
||||
else:
|
||||
keypoints = np.zeros((T, K, C), dtype=np.float32)
|
||||
keypoints[target_frame] = pose_results[target_frame][idx][
|
||||
'keypoints']
|
||||
# find the left most frame containing track_ids[idx]
|
||||
for frame_idx in range(target_frame - 1, -1, -1):
|
||||
contains_idx = False
|
||||
for res in pose_results[frame_idx]:
|
||||
if res['track_id'] == track_ids[idx]:
|
||||
keypoints[frame_idx] = res['keypoints']
|
||||
contains_idx = True
|
||||
break
|
||||
if not contains_idx:
|
||||
# replicate the left most frame
|
||||
keypoints[:frame_idx + 1] = keypoints[frame_idx + 1]
|
||||
break
|
||||
# find the right most frame containing track_idx[idx]
|
||||
for frame_idx in range(target_frame + 1, T):
|
||||
contains_idx = False
|
||||
for res in pose_results[frame_idx]:
|
||||
if res['track_id'] == track_ids[idx]:
|
||||
keypoints[frame_idx] = res['keypoints']
|
||||
contains_idx = True
|
||||
break
|
||||
if not contains_idx:
|
||||
# replicate the right most frame
|
||||
keypoints[frame_idx + 1:] = keypoints[frame_idx]
|
||||
break
|
||||
pose_seq['keypoints'] = keypoints
|
||||
pose_sequences.append(pose_seq)
|
||||
|
||||
return pose_sequences
|
||||
|
||||
|
||||
def inference_pose_lifter_model(model,
|
||||
pose_results_2d,
|
||||
dataset=None,
|
||||
dataset_info=None,
|
||||
with_track_id=True,
|
||||
image_size=None,
|
||||
norm_pose_2d=False):
|
||||
"""Inference 3D pose from 2D pose sequences using a pose lifter model.
|
||||
|
||||
Args:
|
||||
model (nn.Module): The loaded pose lifter model
|
||||
pose_results_2d (list[list[dict]]): The 2D pose sequences stored in a
|
||||
nested list. Each element of the outer list is the 2D pose results
|
||||
of a single frame, and each element of the inner list is the 2D
|
||||
pose of one person, which contains:
|
||||
|
||||
- "keypoints" (ndarray[K, 2 or 3]): x, y, [score]
|
||||
- "track_id" (int)
|
||||
dataset (str): Dataset name, e.g. 'Body3DH36MDataset'
|
||||
with_track_id: If True, the element in pose_results_2d is expected to
|
||||
contain "track_id", which will be used to gather the pose sequence
|
||||
of a person from multiple frames. Otherwise, the pose results in
|
||||
each frame are expected to have a consistent number and order of
|
||||
identities. Default is True.
|
||||
image_size (tuple|list): image width, image height. If None, image size
|
||||
will not be contained in dict ``data``.
|
||||
norm_pose_2d (bool): If True, scale the bbox (along with the 2D
|
||||
pose) to the average bbox scale of the dataset, and move the bbox
|
||||
(along with the 2D pose) to the average bbox center of the dataset.
|
||||
|
||||
Returns:
|
||||
list[dict]: 3D pose inference results. Each element is the result of \
|
||||
an instance, which contains:
|
||||
|
||||
- "keypoints_3d" (ndarray[K, 3]): predicted 3D keypoints
|
||||
- "keypoints" (ndarray[K, 2 or 3]): from the last frame in \
|
||||
``pose_results_2d``.
|
||||
- "track_id" (int): from the last frame in ``pose_results_2d``. \
|
||||
If there is no valid instance, an empty list will be \
|
||||
returned.
|
||||
"""
|
||||
cfg = model.cfg
|
||||
test_pipeline = Compose(cfg.test_pipeline)
|
||||
|
||||
device = next(model.parameters()).device
|
||||
if device.type == 'cpu':
|
||||
device = -1
|
||||
|
||||
if dataset_info is not None:
|
||||
flip_pairs = dataset_info.flip_pairs
|
||||
assert 'stats_info' in dataset_info._dataset_info
|
||||
bbox_center = dataset_info._dataset_info['stats_info']['bbox_center']
|
||||
bbox_scale = dataset_info._dataset_info['stats_info']['bbox_scale']
|
||||
else:
|
||||
warnings.warn(
|
||||
'dataset is deprecated.'
|
||||
'Please set `dataset_info` in the config.'
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 for details.',
|
||||
DeprecationWarning)
|
||||
# TODO: These will be removed in the later versions.
|
||||
if dataset == 'Body3DH36MDataset':
|
||||
flip_pairs = [[1, 4], [2, 5], [3, 6], [11, 14], [12, 15], [13, 16]]
|
||||
bbox_center = np.array([[528, 427]], dtype=np.float32)
|
||||
bbox_scale = 400
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
target_idx = -1 if model.causal else len(pose_results_2d) // 2
|
||||
pose_lifter_inputs = _gather_pose_lifter_inputs(pose_results_2d,
|
||||
bbox_center, bbox_scale,
|
||||
norm_pose_2d)
|
||||
pose_sequences_2d = _collate_pose_sequence(pose_lifter_inputs,
|
||||
with_track_id, target_idx)
|
||||
|
||||
if not pose_sequences_2d:
|
||||
return []
|
||||
|
||||
batch_data = []
|
||||
for seq in pose_sequences_2d:
|
||||
pose_2d = seq['keypoints'].astype(np.float32)
|
||||
T, K, C = pose_2d.shape
|
||||
|
||||
input_2d = pose_2d[..., :2]
|
||||
input_2d_visible = pose_2d[..., 2:3]
|
||||
if C > 2:
|
||||
input_2d_visible = pose_2d[..., 2:3]
|
||||
else:
|
||||
input_2d_visible = np.ones((T, K, 1), dtype=np.float32)
|
||||
|
||||
# TODO: Will be removed in the later versions
|
||||
# Dummy 3D input
|
||||
# This is for compatibility with configs in mmpose<=v0.14.0, where a
|
||||
# 3D input is required to generate denormalization parameters. This
|
||||
# part will be removed in the future.
|
||||
target = np.zeros((K, 3), dtype=np.float32)
|
||||
target_visible = np.ones((K, 1), dtype=np.float32)
|
||||
|
||||
# Dummy image path
|
||||
# This is for compatibility with configs in mmpose<=v0.14.0, where
|
||||
# target_image_path is required. This part will be removed in the
|
||||
# future.
|
||||
target_image_path = None
|
||||
|
||||
data = {
|
||||
'input_2d': input_2d,
|
||||
'input_2d_visible': input_2d_visible,
|
||||
'target': target,
|
||||
'target_visible': target_visible,
|
||||
'target_image_path': target_image_path,
|
||||
'ann_info': {
|
||||
'num_joints': K,
|
||||
'flip_pairs': flip_pairs
|
||||
}
|
||||
}
|
||||
|
||||
if image_size is not None:
|
||||
assert len(image_size) == 2
|
||||
data['image_width'] = image_size[0]
|
||||
data['image_height'] = image_size[1]
|
||||
|
||||
data = test_pipeline(data)
|
||||
batch_data.append(data)
|
||||
|
||||
batch_data = collate(batch_data, samples_per_gpu=len(batch_data))
|
||||
batch_data = scatter(batch_data, target_gpus=[device])[0]
|
||||
|
||||
with torch.no_grad():
|
||||
result = model(
|
||||
input=batch_data['input'],
|
||||
metas=batch_data['metas'],
|
||||
return_loss=False)
|
||||
|
||||
poses_3d = result['preds']
|
||||
if poses_3d.shape[-1] != 4:
|
||||
assert poses_3d.shape[-1] == 3
|
||||
dummy_score = np.ones(
|
||||
poses_3d.shape[:-1] + (1, ), dtype=poses_3d.dtype)
|
||||
poses_3d = np.concatenate((poses_3d, dummy_score), axis=-1)
|
||||
pose_results = []
|
||||
for pose_2d, pose_3d in zip(pose_sequences_2d, poses_3d):
|
||||
pose_result = pose_2d.copy()
|
||||
pose_result['keypoints_3d'] = pose_3d
|
||||
pose_results.append(pose_result)
|
||||
|
||||
return pose_results
|
||||
|
||||
|
||||
def vis_3d_pose_result(model,
|
||||
result,
|
||||
img=None,
|
||||
dataset='Body3DH36MDataset',
|
||||
dataset_info=None,
|
||||
kpt_score_thr=0.3,
|
||||
radius=8,
|
||||
thickness=2,
|
||||
num_instances=-1,
|
||||
show=False,
|
||||
out_file=None):
|
||||
"""Visualize the 3D pose estimation results.
|
||||
|
||||
Args:
|
||||
model (nn.Module): The loaded model.
|
||||
result (list[dict])
|
||||
"""
|
||||
|
||||
if dataset_info is not None:
|
||||
skeleton = dataset_info.skeleton
|
||||
pose_kpt_color = dataset_info.pose_kpt_color
|
||||
pose_link_color = dataset_info.pose_link_color
|
||||
else:
|
||||
warnings.warn(
|
||||
'dataset is deprecated.'
|
||||
'Please set `dataset_info` in the config.'
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 for details.',
|
||||
DeprecationWarning)
|
||||
# TODO: These will be removed in the later versions.
|
||||
palette = np.array([[255, 128, 0], [255, 153, 51], [255, 178, 102],
|
||||
[230, 230, 0], [255, 153, 255], [153, 204, 255],
|
||||
[255, 102, 255], [255, 51, 255], [102, 178, 255],
|
||||
[51, 153, 255], [255, 153, 153], [255, 102, 102],
|
||||
[255, 51, 51], [153, 255, 153], [102, 255, 102],
|
||||
[51, 255, 51], [0, 255, 0], [0, 0, 255],
|
||||
[255, 0, 0], [255, 255, 255]])
|
||||
|
||||
if dataset == 'Body3DH36MDataset':
|
||||
skeleton = [[0, 1], [1, 2], [2, 3], [0, 4], [4, 5], [5, 6], [0, 7],
|
||||
[7, 8], [8, 9], [9, 10], [8, 11], [11, 12], [12, 13],
|
||||
[8, 14], [14, 15], [15, 16]]
|
||||
|
||||
pose_kpt_color = palette[[
|
||||
9, 0, 0, 0, 16, 16, 16, 9, 9, 9, 9, 16, 16, 16, 0, 0, 0
|
||||
]]
|
||||
pose_link_color = palette[[
|
||||
0, 0, 0, 16, 16, 16, 9, 9, 9, 9, 16, 16, 16, 0, 0, 0
|
||||
]]
|
||||
|
||||
elif dataset == 'InterHand3DDataset':
|
||||
skeleton = [[0, 1], [1, 2], [2, 3], [3, 20], [4, 5], [5, 6],
|
||||
[6, 7], [7, 20], [8, 9], [9, 10], [10, 11], [11, 20],
|
||||
[12, 13], [13, 14], [14, 15], [15, 20], [16, 17],
|
||||
[17, 18], [18, 19], [19, 20], [21, 22], [22, 23],
|
||||
[23, 24], [24, 41], [25, 26], [26, 27], [27, 28],
|
||||
[28, 41], [29, 30], [30, 31], [31, 32], [32, 41],
|
||||
[33, 34], [34, 35], [35, 36], [36, 41], [37, 38],
|
||||
[38, 39], [39, 40], [40, 41]]
|
||||
|
||||
pose_kpt_color = [[14, 128, 250], [14, 128, 250], [14, 128, 250],
|
||||
[14, 128, 250], [80, 127, 255], [80, 127, 255],
|
||||
[80, 127, 255], [80, 127, 255], [71, 99, 255],
|
||||
[71, 99, 255], [71, 99, 255], [71, 99, 255],
|
||||
[0, 36, 255], [0, 36, 255], [0, 36, 255],
|
||||
[0, 36, 255], [0, 0, 230], [0, 0, 230],
|
||||
[0, 0, 230], [0, 0, 230], [0, 0, 139],
|
||||
[237, 149, 100], [237, 149, 100],
|
||||
[237, 149, 100], [237, 149, 100], [230, 128, 77],
|
||||
[230, 128, 77], [230, 128, 77], [230, 128, 77],
|
||||
[255, 144, 30], [255, 144, 30], [255, 144, 30],
|
||||
[255, 144, 30], [153, 51, 0], [153, 51, 0],
|
||||
[153, 51, 0], [153, 51, 0], [255, 51, 13],
|
||||
[255, 51, 13], [255, 51, 13], [255, 51, 13],
|
||||
[103, 37, 8]]
|
||||
|
||||
pose_link_color = [[14, 128, 250], [14, 128, 250], [14, 128, 250],
|
||||
[14, 128, 250], [80, 127, 255], [80, 127, 255],
|
||||
[80, 127, 255], [80, 127, 255], [71, 99, 255],
|
||||
[71, 99, 255], [71, 99, 255], [71, 99, 255],
|
||||
[0, 36, 255], [0, 36, 255], [0, 36, 255],
|
||||
[0, 36, 255], [0, 0, 230], [0, 0, 230],
|
||||
[0, 0, 230], [0, 0, 230], [237, 149, 100],
|
||||
[237, 149, 100], [237, 149, 100],
|
||||
[237, 149, 100], [230, 128, 77], [230, 128, 77],
|
||||
[230, 128, 77], [230, 128, 77], [255, 144, 30],
|
||||
[255, 144, 30], [255, 144, 30], [255, 144, 30],
|
||||
[153, 51, 0], [153, 51, 0], [153, 51, 0],
|
||||
[153, 51, 0], [255, 51, 13], [255, 51, 13],
|
||||
[255, 51, 13], [255, 51, 13]]
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if hasattr(model, 'module'):
|
||||
model = model.module
|
||||
|
||||
img = model.show_result(
|
||||
result,
|
||||
img,
|
||||
skeleton,
|
||||
radius=radius,
|
||||
thickness=thickness,
|
||||
pose_kpt_color=pose_kpt_color,
|
||||
pose_link_color=pose_link_color,
|
||||
num_instances=num_instances,
|
||||
show=show,
|
||||
out_file=out_file)
|
||||
|
||||
return img
|
||||
|
||||
|
||||
def inference_interhand_3d_model(model,
|
||||
img_or_path,
|
||||
det_results,
|
||||
bbox_thr=None,
|
||||
format='xywh',
|
||||
dataset='InterHand3DDataset'):
|
||||
"""Inference a single image with a list of hand bounding boxes.
|
||||
|
||||
Note:
|
||||
- num_bboxes: N
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
model (nn.Module): The loaded pose model.
|
||||
img_or_path (str | np.ndarray): Image filename or loaded image.
|
||||
det_results (list[dict]): The 2D bbox sequences stored in a list.
|
||||
Each each element of the list is the bbox of one person, whose
|
||||
shape is (ndarray[4 or 5]), containing 4 box coordinates
|
||||
(and score).
|
||||
dataset (str): Dataset name.
|
||||
format: bbox format ('xyxy' | 'xywh'). Default: 'xywh'.
|
||||
'xyxy' means (left, top, right, bottom),
|
||||
'xywh' means (left, top, width, height).
|
||||
|
||||
Returns:
|
||||
list[dict]: 3D pose inference results. Each element is the result \
|
||||
of an instance, which contains the predicted 3D keypoints with \
|
||||
shape (ndarray[K,3]). If there is no valid instance, an \
|
||||
empty list will be returned.
|
||||
"""
|
||||
|
||||
assert format in ['xyxy', 'xywh']
|
||||
|
||||
pose_results = []
|
||||
|
||||
if len(det_results) == 0:
|
||||
return pose_results
|
||||
|
||||
# Change for-loop preprocess each bbox to preprocess all bboxes at once.
|
||||
bboxes = np.array([box['bbox'] for box in det_results])
|
||||
|
||||
# Select bboxes by score threshold
|
||||
if bbox_thr is not None:
|
||||
assert bboxes.shape[1] == 5
|
||||
valid_idx = np.where(bboxes[:, 4] > bbox_thr)[0]
|
||||
bboxes = bboxes[valid_idx]
|
||||
det_results = [det_results[i] for i in valid_idx]
|
||||
|
||||
if format == 'xyxy':
|
||||
bboxes_xyxy = bboxes
|
||||
bboxes_xywh = _xyxy2xywh(bboxes)
|
||||
else:
|
||||
# format is already 'xywh'
|
||||
bboxes_xywh = bboxes
|
||||
bboxes_xyxy = _xywh2xyxy(bboxes)
|
||||
|
||||
# if bbox_thr remove all bounding box
|
||||
if len(bboxes_xywh) == 0:
|
||||
return []
|
||||
|
||||
cfg = model.cfg
|
||||
device = next(model.parameters()).device
|
||||
if device.type == 'cpu':
|
||||
device = -1
|
||||
|
||||
# build the data pipeline
|
||||
test_pipeline = Compose(cfg.test_pipeline)
|
||||
|
||||
assert len(bboxes[0]) in [4, 5]
|
||||
|
||||
if dataset == 'InterHand3DDataset':
|
||||
flip_pairs = [[i, 21 + i] for i in range(21)]
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
batch_data = []
|
||||
for bbox in bboxes:
|
||||
center, scale = _box2cs(cfg, bbox)
|
||||
|
||||
# prepare data
|
||||
data = {
|
||||
'center':
|
||||
center,
|
||||
'scale':
|
||||
scale,
|
||||
'bbox_score':
|
||||
bbox[4] if len(bbox) == 5 else 1,
|
||||
'bbox_id':
|
||||
0, # need to be assigned if batch_size > 1
|
||||
'dataset':
|
||||
dataset,
|
||||
'joints_3d':
|
||||
np.zeros((cfg.data_cfg.num_joints, 3), dtype=np.float32),
|
||||
'joints_3d_visible':
|
||||
np.zeros((cfg.data_cfg.num_joints, 3), dtype=np.float32),
|
||||
'rotation':
|
||||
0,
|
||||
'ann_info': {
|
||||
'image_size': np.array(cfg.data_cfg['image_size']),
|
||||
'num_joints': cfg.data_cfg['num_joints'],
|
||||
'flip_pairs': flip_pairs,
|
||||
'heatmap3d_depth_bound': cfg.data_cfg['heatmap3d_depth_bound'],
|
||||
'heatmap_size_root': cfg.data_cfg['heatmap_size_root'],
|
||||
'root_depth_bound': cfg.data_cfg['root_depth_bound']
|
||||
}
|
||||
}
|
||||
|
||||
if isinstance(img_or_path, np.ndarray):
|
||||
data['img'] = img_or_path
|
||||
else:
|
||||
data['image_file'] = img_or_path
|
||||
|
||||
data = test_pipeline(data)
|
||||
batch_data.append(data)
|
||||
|
||||
batch_data = collate(batch_data, samples_per_gpu=len(batch_data))
|
||||
batch_data = scatter(batch_data, [device])[0]
|
||||
|
||||
# forward the model
|
||||
with torch.no_grad():
|
||||
result = model(
|
||||
img=batch_data['img'],
|
||||
img_metas=batch_data['img_metas'],
|
||||
return_loss=False)
|
||||
|
||||
poses_3d = result['preds']
|
||||
rel_root_depth = result['rel_root_depth']
|
||||
hand_type = result['hand_type']
|
||||
if poses_3d.shape[-1] != 4:
|
||||
assert poses_3d.shape[-1] == 3
|
||||
dummy_score = np.ones(
|
||||
poses_3d.shape[:-1] + (1, ), dtype=poses_3d.dtype)
|
||||
poses_3d = np.concatenate((poses_3d, dummy_score), axis=-1)
|
||||
|
||||
# add relative root depth to left hand joints
|
||||
poses_3d[:, 21:, 2] += rel_root_depth
|
||||
|
||||
# set joint scores according to hand type
|
||||
poses_3d[:, :21, 3] *= hand_type[:, [0]]
|
||||
poses_3d[:, 21:, 3] *= hand_type[:, [1]]
|
||||
|
||||
pose_results = []
|
||||
for pose_3d, person_res, bbox_xyxy in zip(poses_3d, det_results,
|
||||
bboxes_xyxy):
|
||||
pose_res = person_res.copy()
|
||||
pose_res['keypoints_3d'] = pose_3d
|
||||
pose_res['bbox'] = bbox_xyxy
|
||||
pose_results.append(pose_res)
|
||||
|
||||
return pose_results
|
||||
|
||||
|
||||
def inference_mesh_model(model,
|
||||
img_or_path,
|
||||
det_results,
|
||||
bbox_thr=None,
|
||||
format='xywh',
|
||||
dataset='MeshH36MDataset'):
|
||||
"""Inference a single image with a list of bounding boxes.
|
||||
|
||||
Note:
|
||||
- num_bboxes: N
|
||||
- num_keypoints: K
|
||||
- num_vertices: V
|
||||
- num_faces: F
|
||||
|
||||
Args:
|
||||
model (nn.Module): The loaded pose model.
|
||||
img_or_path (str | np.ndarray): Image filename or loaded image.
|
||||
det_results (list[dict]): The 2D bbox sequences stored in a list.
|
||||
Each element of the list is the bbox of one person.
|
||||
"bbox" (ndarray[4 or 5]): The person bounding box,
|
||||
which contains 4 box coordinates (and score).
|
||||
bbox_thr (float | None): Threshold for bounding boxes.
|
||||
Only bboxes with higher scores will be fed into the pose
|
||||
detector. If bbox_thr is None, all boxes will be used.
|
||||
format (str): bbox format ('xyxy' | 'xywh'). Default: 'xywh'.
|
||||
|
||||
- 'xyxy' means (left, top, right, bottom),
|
||||
- 'xywh' means (left, top, width, height).
|
||||
dataset (str): Dataset name.
|
||||
|
||||
Returns:
|
||||
list[dict]: 3D pose inference results. Each element \
|
||||
is the result of an instance, which contains:
|
||||
|
||||
- 'bbox' (ndarray[4]): instance bounding bbox
|
||||
- 'center' (ndarray[2]): bbox center
|
||||
- 'scale' (ndarray[2]): bbox scale
|
||||
- 'keypoints_3d' (ndarray[K,3]): predicted 3D keypoints
|
||||
- 'camera' (ndarray[3]): camera parameters
|
||||
- 'vertices' (ndarray[V, 3]): predicted 3D vertices
|
||||
- 'faces' (ndarray[F, 3]): mesh faces
|
||||
|
||||
If there is no valid instance, an empty list
|
||||
will be returned.
|
||||
"""
|
||||
|
||||
assert format in ['xyxy', 'xywh']
|
||||
|
||||
pose_results = []
|
||||
|
||||
if len(det_results) == 0:
|
||||
return pose_results
|
||||
|
||||
# Change for-loop preprocess each bbox to preprocess all bboxes at once.
|
||||
bboxes = np.array([box['bbox'] for box in det_results])
|
||||
|
||||
# Select bboxes by score threshold
|
||||
if bbox_thr is not None:
|
||||
assert bboxes.shape[1] == 5
|
||||
valid_idx = np.where(bboxes[:, 4] > bbox_thr)[0]
|
||||
bboxes = bboxes[valid_idx]
|
||||
det_results = [det_results[i] for i in valid_idx]
|
||||
|
||||
if format == 'xyxy':
|
||||
bboxes_xyxy = bboxes
|
||||
bboxes_xywh = _xyxy2xywh(bboxes)
|
||||
else:
|
||||
# format is already 'xywh'
|
||||
bboxes_xywh = bboxes
|
||||
bboxes_xyxy = _xywh2xyxy(bboxes)
|
||||
|
||||
# if bbox_thr remove all bounding box
|
||||
if len(bboxes_xywh) == 0:
|
||||
return []
|
||||
|
||||
cfg = model.cfg
|
||||
device = next(model.parameters()).device
|
||||
if device.type == 'cpu':
|
||||
device = -1
|
||||
|
||||
# build the data pipeline
|
||||
test_pipeline = Compose(cfg.test_pipeline)
|
||||
|
||||
assert len(bboxes[0]) in [4, 5]
|
||||
|
||||
if dataset == 'MeshH36MDataset':
|
||||
flip_pairs = [[0, 5], [1, 4], [2, 3], [6, 11], [7, 10], [8, 9],
|
||||
[20, 21], [22, 23]]
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
batch_data = []
|
||||
for bbox in bboxes:
|
||||
center, scale = _box2cs(cfg, bbox)
|
||||
|
||||
# prepare data
|
||||
data = {
|
||||
'image_file':
|
||||
img_or_path,
|
||||
'center':
|
||||
center,
|
||||
'scale':
|
||||
scale,
|
||||
'rotation':
|
||||
0,
|
||||
'bbox_score':
|
||||
bbox[4] if len(bbox) == 5 else 1,
|
||||
'dataset':
|
||||
dataset,
|
||||
'joints_2d':
|
||||
np.zeros((cfg.data_cfg.num_joints, 2), dtype=np.float32),
|
||||
'joints_2d_visible':
|
||||
np.zeros((cfg.data_cfg.num_joints, 1), dtype=np.float32),
|
||||
'joints_3d':
|
||||
np.zeros((cfg.data_cfg.num_joints, 3), dtype=np.float32),
|
||||
'joints_3d_visible':
|
||||
np.zeros((cfg.data_cfg.num_joints, 3), dtype=np.float32),
|
||||
'pose':
|
||||
np.zeros(72, dtype=np.float32),
|
||||
'beta':
|
||||
np.zeros(10, dtype=np.float32),
|
||||
'has_smpl':
|
||||
0,
|
||||
'ann_info': {
|
||||
'image_size': np.array(cfg.data_cfg['image_size']),
|
||||
'num_joints': cfg.data_cfg['num_joints'],
|
||||
'flip_pairs': flip_pairs,
|
||||
}
|
||||
}
|
||||
|
||||
data = test_pipeline(data)
|
||||
batch_data.append(data)
|
||||
|
||||
batch_data = collate(batch_data, samples_per_gpu=len(batch_data))
|
||||
batch_data = scatter(batch_data, target_gpus=[device])[0]
|
||||
|
||||
# forward the model
|
||||
with torch.no_grad():
|
||||
preds = model(
|
||||
img=batch_data['img'],
|
||||
img_metas=batch_data['img_metas'],
|
||||
return_loss=False,
|
||||
return_vertices=True,
|
||||
return_faces=True)
|
||||
|
||||
for idx in range(len(det_results)):
|
||||
pose_res = det_results[idx].copy()
|
||||
pose_res['bbox'] = bboxes_xyxy[idx]
|
||||
pose_res['center'] = batch_data['img_metas'][idx]['center']
|
||||
pose_res['scale'] = batch_data['img_metas'][idx]['scale']
|
||||
pose_res['keypoints_3d'] = preds['keypoints_3d'][idx]
|
||||
pose_res['camera'] = preds['camera'][idx]
|
||||
pose_res['vertices'] = preds['vertices'][idx]
|
||||
pose_res['faces'] = preds['faces']
|
||||
pose_results.append(pose_res)
|
||||
return pose_results
|
||||
|
||||
|
||||
def vis_3d_mesh_result(model, result, img=None, show=False, out_file=None):
|
||||
"""Visualize the 3D mesh estimation results.
|
||||
|
||||
Args:
|
||||
model (nn.Module): The loaded model.
|
||||
result (list[dict]): 3D mesh estimation results.
|
||||
"""
|
||||
if hasattr(model, 'module'):
|
||||
model = model.module
|
||||
|
||||
img = model.show_result(result, img, show=show, out_file=out_file)
|
||||
|
||||
return img
|
||||
@@ -0,0 +1,347 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
|
||||
from mmpose.core import OneEuroFilter, oks_iou
|
||||
|
||||
|
||||
def _compute_iou(bboxA, bboxB):
|
||||
"""Compute the Intersection over Union (IoU) between two boxes .
|
||||
|
||||
Args:
|
||||
bboxA (list): The first bbox info (left, top, right, bottom, score).
|
||||
bboxB (list): The second bbox info (left, top, right, bottom, score).
|
||||
|
||||
Returns:
|
||||
float: The IoU value.
|
||||
"""
|
||||
|
||||
x1 = max(bboxA[0], bboxB[0])
|
||||
y1 = max(bboxA[1], bboxB[1])
|
||||
x2 = min(bboxA[2], bboxB[2])
|
||||
y2 = min(bboxA[3], bboxB[3])
|
||||
|
||||
inter_area = max(0, x2 - x1) * max(0, y2 - y1)
|
||||
|
||||
bboxA_area = (bboxA[2] - bboxA[0]) * (bboxA[3] - bboxA[1])
|
||||
bboxB_area = (bboxB[2] - bboxB[0]) * (bboxB[3] - bboxB[1])
|
||||
union_area = float(bboxA_area + bboxB_area - inter_area)
|
||||
if union_area == 0:
|
||||
union_area = 1e-5
|
||||
warnings.warn('union_area=0 is unexpected')
|
||||
|
||||
iou = inter_area / union_area
|
||||
|
||||
return iou
|
||||
|
||||
|
||||
def _track_by_iou(res, results_last, thr):
|
||||
"""Get track id using IoU tracking greedily.
|
||||
|
||||
Args:
|
||||
res (dict): The bbox & pose results of the person instance.
|
||||
results_last (list[dict]): The bbox & pose & track_id info of the
|
||||
last frame (bbox_result, pose_result, track_id).
|
||||
thr (float): The threshold for iou tracking.
|
||||
|
||||
Returns:
|
||||
int: The track id for the new person instance.
|
||||
list[dict]: The bbox & pose & track_id info of the persons
|
||||
that have not been matched on the last frame.
|
||||
dict: The matched person instance on the last frame.
|
||||
"""
|
||||
|
||||
bbox = list(res['bbox'])
|
||||
|
||||
max_iou_score = -1
|
||||
max_index = -1
|
||||
match_result = {}
|
||||
for index, res_last in enumerate(results_last):
|
||||
bbox_last = list(res_last['bbox'])
|
||||
|
||||
iou_score = _compute_iou(bbox, bbox_last)
|
||||
if iou_score > max_iou_score:
|
||||
max_iou_score = iou_score
|
||||
max_index = index
|
||||
|
||||
if max_iou_score > thr:
|
||||
track_id = results_last[max_index]['track_id']
|
||||
match_result = results_last[max_index]
|
||||
del results_last[max_index]
|
||||
else:
|
||||
track_id = -1
|
||||
|
||||
return track_id, results_last, match_result
|
||||
|
||||
|
||||
def _track_by_oks(res, results_last, thr):
|
||||
"""Get track id using OKS tracking greedily.
|
||||
|
||||
Args:
|
||||
res (dict): The pose results of the person instance.
|
||||
results_last (list[dict]): The pose & track_id info of the
|
||||
last frame (pose_result, track_id).
|
||||
thr (float): The threshold for oks tracking.
|
||||
|
||||
Returns:
|
||||
int: The track id for the new person instance.
|
||||
list[dict]: The pose & track_id info of the persons
|
||||
that have not been matched on the last frame.
|
||||
dict: The matched person instance on the last frame.
|
||||
"""
|
||||
pose = res['keypoints'].reshape((-1))
|
||||
area = res['area']
|
||||
max_index = -1
|
||||
match_result = {}
|
||||
|
||||
if len(results_last) == 0:
|
||||
return -1, results_last, match_result
|
||||
|
||||
pose_last = np.array(
|
||||
[res_last['keypoints'].reshape((-1)) for res_last in results_last])
|
||||
area_last = np.array([res_last['area'] for res_last in results_last])
|
||||
|
||||
oks_score = oks_iou(pose, pose_last, area, area_last)
|
||||
|
||||
max_index = np.argmax(oks_score)
|
||||
|
||||
if oks_score[max_index] > thr:
|
||||
track_id = results_last[max_index]['track_id']
|
||||
match_result = results_last[max_index]
|
||||
del results_last[max_index]
|
||||
else:
|
||||
track_id = -1
|
||||
|
||||
return track_id, results_last, match_result
|
||||
|
||||
|
||||
def _get_area(results):
|
||||
"""Get bbox for each person instance on the current frame.
|
||||
|
||||
Args:
|
||||
results (list[dict]): The pose results of the current frame
|
||||
(pose_result).
|
||||
Returns:
|
||||
list[dict]: The bbox & pose info of the current frame
|
||||
(bbox_result, pose_result, area).
|
||||
"""
|
||||
for result in results:
|
||||
if 'bbox' in result:
|
||||
result['area'] = ((result['bbox'][2] - result['bbox'][0]) *
|
||||
(result['bbox'][3] - result['bbox'][1]))
|
||||
else:
|
||||
xmin = np.min(
|
||||
result['keypoints'][:, 0][result['keypoints'][:, 0] > 0],
|
||||
initial=1e10)
|
||||
xmax = np.max(result['keypoints'][:, 0])
|
||||
ymin = np.min(
|
||||
result['keypoints'][:, 1][result['keypoints'][:, 1] > 0],
|
||||
initial=1e10)
|
||||
ymax = np.max(result['keypoints'][:, 1])
|
||||
result['area'] = (xmax - xmin) * (ymax - ymin)
|
||||
result['bbox'] = np.array([xmin, ymin, xmax, ymax])
|
||||
return results
|
||||
|
||||
|
||||
def _temporal_refine(result, match_result, fps=None):
|
||||
"""Refine koypoints using tracked person instance on last frame.
|
||||
|
||||
Args:
|
||||
results (dict): The pose results of the current frame
|
||||
(pose_result).
|
||||
match_result (dict): The pose results of the last frame
|
||||
(match_result)
|
||||
Returns:
|
||||
(array): The person keypoints after refine.
|
||||
"""
|
||||
if 'one_euro' in match_result:
|
||||
result['keypoints'][:, :2] = match_result['one_euro'](
|
||||
result['keypoints'][:, :2])
|
||||
result['one_euro'] = match_result['one_euro']
|
||||
else:
|
||||
result['one_euro'] = OneEuroFilter(result['keypoints'][:, :2], fps=fps)
|
||||
return result['keypoints']
|
||||
|
||||
|
||||
def get_track_id(results,
|
||||
results_last,
|
||||
next_id,
|
||||
min_keypoints=3,
|
||||
use_oks=False,
|
||||
tracking_thr=0.3,
|
||||
use_one_euro=False,
|
||||
fps=None):
|
||||
"""Get track id for each person instance on the current frame.
|
||||
|
||||
Args:
|
||||
results (list[dict]): The bbox & pose results of the current frame
|
||||
(bbox_result, pose_result).
|
||||
results_last (list[dict]): The bbox & pose & track_id info of the
|
||||
last frame (bbox_result, pose_result, track_id).
|
||||
next_id (int): The track id for the new person instance.
|
||||
min_keypoints (int): Minimum number of keypoints recognized as person.
|
||||
default: 3.
|
||||
use_oks (bool): Flag to using oks tracking. default: False.
|
||||
tracking_thr (float): The threshold for tracking.
|
||||
use_one_euro (bool): Option to use one-euro-filter. default: False.
|
||||
fps (optional): Parameters that d_cutoff
|
||||
when one-euro-filter is used as a video input
|
||||
|
||||
Returns:
|
||||
tuple:
|
||||
- results (list[dict]): The bbox & pose & track_id info of the \
|
||||
current frame (bbox_result, pose_result, track_id).
|
||||
- next_id (int): The track id for the new person instance.
|
||||
"""
|
||||
results = _get_area(results)
|
||||
|
||||
if use_oks:
|
||||
_track = _track_by_oks
|
||||
else:
|
||||
_track = _track_by_iou
|
||||
|
||||
for result in results:
|
||||
track_id, results_last, match_result = _track(result, results_last,
|
||||
tracking_thr)
|
||||
if track_id == -1:
|
||||
if np.count_nonzero(result['keypoints'][:, 1]) > min_keypoints:
|
||||
result['track_id'] = next_id
|
||||
next_id += 1
|
||||
else:
|
||||
# If the number of keypoints detected is small,
|
||||
# delete that person instance.
|
||||
result['keypoints'][:, 1] = -10
|
||||
result['bbox'] *= 0
|
||||
result['track_id'] = -1
|
||||
else:
|
||||
result['track_id'] = track_id
|
||||
if use_one_euro:
|
||||
result['keypoints'] = _temporal_refine(
|
||||
result, match_result, fps=fps)
|
||||
del match_result
|
||||
|
||||
return results, next_id
|
||||
|
||||
|
||||
def vis_pose_tracking_result(model,
|
||||
img,
|
||||
result,
|
||||
radius=4,
|
||||
thickness=1,
|
||||
kpt_score_thr=0.3,
|
||||
dataset='TopDownCocoDataset',
|
||||
dataset_info=None,
|
||||
show=False,
|
||||
out_file=None):
|
||||
"""Visualize the pose tracking results on the image.
|
||||
|
||||
Args:
|
||||
model (nn.Module): The loaded detector.
|
||||
img (str | np.ndarray): Image filename or loaded image.
|
||||
result (list[dict]): The results to draw over `img`
|
||||
(bbox_result, pose_result).
|
||||
radius (int): Radius of circles.
|
||||
thickness (int): Thickness of lines.
|
||||
kpt_score_thr (float): The threshold to visualize the keypoints.
|
||||
skeleton (list[tuple]): Default None.
|
||||
show (bool): Whether to show the image. Default True.
|
||||
out_file (str|None): The filename of the output visualization image.
|
||||
"""
|
||||
if hasattr(model, 'module'):
|
||||
model = model.module
|
||||
|
||||
palette = np.array([[255, 128, 0], [255, 153, 51], [255, 178, 102],
|
||||
[230, 230, 0], [255, 153, 255], [153, 204, 255],
|
||||
[255, 102, 255], [255, 51, 255], [102, 178, 255],
|
||||
[51, 153, 255], [255, 153, 153], [255, 102, 102],
|
||||
[255, 51, 51], [153, 255, 153], [102, 255, 102],
|
||||
[51, 255, 51], [0, 255, 0], [0, 0, 255], [255, 0, 0],
|
||||
[255, 255, 255]])
|
||||
|
||||
if dataset_info is None and dataset is not None:
|
||||
warnings.warn(
|
||||
'dataset is deprecated.'
|
||||
'Please set `dataset_info` in the config.'
|
||||
'Check https://github.com/open-mmlab/mmpose/pull/663 for details.',
|
||||
DeprecationWarning)
|
||||
# TODO: These will be removed in the later versions.
|
||||
if dataset in ('TopDownCocoDataset', 'BottomUpCocoDataset',
|
||||
'TopDownOCHumanDataset'):
|
||||
kpt_num = 17
|
||||
skeleton = [[15, 13], [13, 11], [16, 14], [14, 12], [11, 12],
|
||||
[5, 11], [6, 12], [5, 6], [5, 7], [6, 8], [7, 9],
|
||||
[8, 10], [1, 2], [0, 1], [0, 2], [1, 3], [2, 4],
|
||||
[3, 5], [4, 6]]
|
||||
|
||||
elif dataset == 'TopDownCocoWholeBodyDataset':
|
||||
kpt_num = 133
|
||||
skeleton = [[15, 13], [13, 11], [16, 14], [14, 12], [11, 12],
|
||||
[5, 11], [6, 12], [5, 6], [5, 7], [6, 8], [7, 9],
|
||||
[8, 10], [1, 2], [0, 1], [0, 2],
|
||||
[1, 3], [2, 4], [3, 5], [4, 6], [15, 17], [15, 18],
|
||||
[15, 19], [16, 20], [16, 21], [16, 22], [91, 92],
|
||||
[92, 93], [93, 94], [94, 95], [91, 96], [96, 97],
|
||||
[97, 98], [98, 99], [91, 100], [100, 101], [101, 102],
|
||||
[102, 103], [91, 104], [104, 105], [105, 106],
|
||||
[106, 107], [91, 108], [108, 109], [109, 110],
|
||||
[110, 111], [112, 113], [113, 114], [114, 115],
|
||||
[115, 116], [112, 117], [117, 118], [118, 119],
|
||||
[119, 120], [112, 121], [121, 122], [122, 123],
|
||||
[123, 124], [112, 125], [125, 126], [126, 127],
|
||||
[127, 128], [112, 129], [129, 130], [130, 131],
|
||||
[131, 132]]
|
||||
radius = 1
|
||||
|
||||
elif dataset == 'TopDownAicDataset':
|
||||
kpt_num = 14
|
||||
skeleton = [[2, 1], [1, 0], [0, 13], [13, 3], [3, 4], [4, 5],
|
||||
[8, 7], [7, 6], [6, 9], [9, 10], [10, 11], [12, 13],
|
||||
[0, 6], [3, 9]]
|
||||
|
||||
elif dataset == 'TopDownMpiiDataset':
|
||||
kpt_num = 16
|
||||
skeleton = [[0, 1], [1, 2], [2, 6], [6, 3], [3, 4], [4, 5], [6, 7],
|
||||
[7, 8], [8, 9], [8, 12], [12, 11], [11, 10], [8, 13],
|
||||
[13, 14], [14, 15]]
|
||||
|
||||
elif dataset in ('OneHand10KDataset', 'FreiHandDataset',
|
||||
'PanopticDataset'):
|
||||
kpt_num = 21
|
||||
skeleton = [[0, 1], [1, 2], [2, 3], [3, 4], [0, 5], [5, 6], [6, 7],
|
||||
[7, 8], [0, 9], [9, 10], [10, 11], [11, 12], [0, 13],
|
||||
[13, 14], [14, 15], [15, 16], [0, 17], [17, 18],
|
||||
[18, 19], [19, 20]]
|
||||
|
||||
elif dataset == 'InterHand2DDataset':
|
||||
kpt_num = 21
|
||||
skeleton = [[0, 1], [1, 2], [2, 3], [4, 5], [5, 6], [6, 7], [8, 9],
|
||||
[9, 10], [10, 11], [12, 13], [13, 14], [14, 15],
|
||||
[16, 17], [17, 18], [18, 19], [3, 20], [7, 20],
|
||||
[11, 20], [15, 20], [19, 20]]
|
||||
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
elif dataset_info is not None:
|
||||
kpt_num = dataset_info.keypoint_num
|
||||
skeleton = dataset_info.skeleton
|
||||
|
||||
for res in result:
|
||||
track_id = res['track_id']
|
||||
bbox_color = palette[track_id % len(palette)]
|
||||
pose_kpt_color = palette[[track_id % len(palette)] * kpt_num]
|
||||
pose_link_color = palette[[track_id % len(palette)] * len(skeleton)]
|
||||
img = model.show_result(
|
||||
img, [res],
|
||||
skeleton,
|
||||
radius=radius,
|
||||
thickness=thickness,
|
||||
pose_kpt_color=pose_kpt_color,
|
||||
pose_link_color=pose_link_color,
|
||||
bbox_color=tuple(bbox_color.tolist()),
|
||||
kpt_score_thr=kpt_score_thr,
|
||||
show=show,
|
||||
out_file=out_file)
|
||||
|
||||
return img
|
||||
@@ -0,0 +1,191 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import os.path as osp
|
||||
import pickle
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
import mmcv
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
from mmcv.runner import get_dist_info
|
||||
|
||||
|
||||
def single_gpu_test(model, data_loader):
|
||||
"""Test model with a single gpu.
|
||||
|
||||
This method tests model with a single gpu and displays test progress bar.
|
||||
|
||||
Args:
|
||||
model (nn.Module): Model to be tested.
|
||||
data_loader (nn.Dataloader): Pytorch data loader.
|
||||
|
||||
|
||||
Returns:
|
||||
list: The prediction results.
|
||||
"""
|
||||
|
||||
model.eval()
|
||||
results = []
|
||||
dataset = data_loader.dataset
|
||||
prog_bar = mmcv.ProgressBar(len(dataset))
|
||||
for data in data_loader:
|
||||
with torch.no_grad():
|
||||
result = model(return_loss=False, **data)
|
||||
results.append(result)
|
||||
|
||||
# use the first key as main key to calculate the batch size
|
||||
batch_size = len(next(iter(data.values())))
|
||||
for _ in range(batch_size):
|
||||
prog_bar.update()
|
||||
return results
|
||||
|
||||
|
||||
def multi_gpu_test(model, data_loader, tmpdir=None, gpu_collect=False):
|
||||
"""Test model with multiple gpus.
|
||||
|
||||
This method tests model with multiple gpus and collects the results
|
||||
under two different modes: gpu and cpu modes. By setting 'gpu_collect=True'
|
||||
it encodes results to gpu tensors and use gpu communication for results
|
||||
collection. On cpu mode it saves the results on different gpus to 'tmpdir'
|
||||
and collects them by the rank 0 worker.
|
||||
|
||||
Args:
|
||||
model (nn.Module): Model to be tested.
|
||||
data_loader (nn.Dataloader): Pytorch data loader.
|
||||
tmpdir (str): Path of directory to save the temporary results from
|
||||
different gpus under cpu mode.
|
||||
gpu_collect (bool): Option to use either gpu or cpu to collect results.
|
||||
|
||||
Returns:
|
||||
list: The prediction results.
|
||||
"""
|
||||
model.eval()
|
||||
results = []
|
||||
dataset = data_loader.dataset
|
||||
rank, world_size = get_dist_info()
|
||||
if rank == 0:
|
||||
prog_bar = mmcv.ProgressBar(len(dataset))
|
||||
for data in data_loader:
|
||||
with torch.no_grad():
|
||||
result = model(return_loss=False, **data)
|
||||
results.append(result)
|
||||
|
||||
if rank == 0:
|
||||
# use the first key as main key to calculate the batch size
|
||||
batch_size = len(next(iter(data.values())))
|
||||
for _ in range(batch_size * world_size):
|
||||
prog_bar.update()
|
||||
|
||||
# collect results from all ranks
|
||||
if gpu_collect:
|
||||
results = collect_results_gpu(results, len(dataset))
|
||||
else:
|
||||
results = collect_results_cpu(results, len(dataset), tmpdir)
|
||||
return results
|
||||
|
||||
|
||||
def collect_results_cpu(result_part, size, tmpdir=None):
|
||||
"""Collect results in cpu mode.
|
||||
|
||||
It saves the results on different gpus to 'tmpdir' and collects
|
||||
them by the rank 0 worker.
|
||||
|
||||
Args:
|
||||
result_part (list): Results to be collected
|
||||
size (int): Result size.
|
||||
tmpdir (str): Path of directory to save the temporary results from
|
||||
different gpus under cpu mode. Default: None
|
||||
|
||||
Returns:
|
||||
list: Ordered results.
|
||||
"""
|
||||
rank, world_size = get_dist_info()
|
||||
# create a tmp dir if it is not specified
|
||||
if tmpdir is None:
|
||||
MAX_LEN = 512
|
||||
# 32 is whitespace
|
||||
dir_tensor = torch.full((MAX_LEN, ),
|
||||
32,
|
||||
dtype=torch.uint8,
|
||||
device='cuda')
|
||||
if rank == 0:
|
||||
mmcv.mkdir_or_exist('.dist_test')
|
||||
tmpdir = tempfile.mkdtemp(dir='.dist_test')
|
||||
tmpdir = torch.tensor(
|
||||
bytearray(tmpdir.encode()), dtype=torch.uint8, device='cuda')
|
||||
dir_tensor[:len(tmpdir)] = tmpdir
|
||||
dist.broadcast(dir_tensor, 0)
|
||||
tmpdir = dir_tensor.cpu().numpy().tobytes().decode().rstrip()
|
||||
else:
|
||||
mmcv.mkdir_or_exist(tmpdir)
|
||||
# synchronizes all processes to make sure tmpdir exist
|
||||
dist.barrier()
|
||||
# dump the part result to the dir
|
||||
mmcv.dump(result_part, osp.join(tmpdir, f'part_{rank}.pkl'))
|
||||
# synchronizes all processes for loading pickle file
|
||||
dist.barrier()
|
||||
# collect all parts
|
||||
if rank != 0:
|
||||
return None
|
||||
|
||||
# load results of all parts from tmp dir
|
||||
part_list = []
|
||||
for i in range(world_size):
|
||||
part_file = osp.join(tmpdir, f'part_{i}.pkl')
|
||||
part_list.append(mmcv.load(part_file))
|
||||
# sort the results
|
||||
ordered_results = []
|
||||
for res in zip(*part_list):
|
||||
ordered_results.extend(list(res))
|
||||
# the dataloader may pad some samples
|
||||
ordered_results = ordered_results[:size]
|
||||
# remove tmp dir
|
||||
shutil.rmtree(tmpdir)
|
||||
return ordered_results
|
||||
|
||||
|
||||
def collect_results_gpu(result_part, size):
|
||||
"""Collect results in gpu mode.
|
||||
|
||||
It encodes results to gpu tensors and use gpu communication for results
|
||||
collection.
|
||||
|
||||
Args:
|
||||
result_part (list): Results to be collected
|
||||
size (int): Result size.
|
||||
|
||||
Returns:
|
||||
list: Ordered results.
|
||||
"""
|
||||
|
||||
rank, world_size = get_dist_info()
|
||||
# dump result part to tensor with pickle
|
||||
part_tensor = torch.tensor(
|
||||
bytearray(pickle.dumps(result_part)), dtype=torch.uint8, device='cuda')
|
||||
# gather all result part tensor shape
|
||||
shape_tensor = torch.tensor(part_tensor.shape, device='cuda')
|
||||
shape_list = [shape_tensor.clone() for _ in range(world_size)]
|
||||
dist.all_gather(shape_list, shape_tensor)
|
||||
# padding result part tensor to max length
|
||||
shape_max = torch.tensor(shape_list).max()
|
||||
part_send = torch.zeros(shape_max, dtype=torch.uint8, device='cuda')
|
||||
part_send[:shape_tensor[0]] = part_tensor
|
||||
part_recv_list = [
|
||||
part_tensor.new_zeros(shape_max) for _ in range(world_size)
|
||||
]
|
||||
# gather all result part
|
||||
dist.all_gather(part_recv_list, part_send)
|
||||
|
||||
if rank == 0:
|
||||
part_list = []
|
||||
for recv, shape in zip(part_recv_list, shape_list):
|
||||
part_list.append(
|
||||
pickle.loads(recv[:shape[0]].cpu().numpy().tobytes()))
|
||||
# sort the results
|
||||
ordered_results = []
|
||||
for res in zip(*part_list):
|
||||
ordered_results.extend(list(res))
|
||||
# the dataloader may pad some samples
|
||||
ordered_results = ordered_results[:size]
|
||||
return ordered_results
|
||||
return None
|
||||
@@ -0,0 +1,200 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
import mmcv
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
from mmcv.parallel import MMDataParallel, MMDistributedDataParallel
|
||||
from mmcv.runner import (DistSamplerSeedHook, EpochBasedRunner, OptimizerHook,
|
||||
get_dist_info)
|
||||
from mmcv.utils import digit_version
|
||||
|
||||
from mmpose.core import DistEvalHook, EvalHook, build_optimizers
|
||||
from mmpose.core.distributed_wrapper import DistributedDataParallelWrapper
|
||||
from mmpose.datasets import build_dataloader, build_dataset
|
||||
from mmpose.utils import get_root_logger
|
||||
|
||||
try:
|
||||
from mmcv.runner import Fp16OptimizerHook
|
||||
except ImportError:
|
||||
warnings.warn(
|
||||
'Fp16OptimizerHook from mmpose will be deprecated from '
|
||||
'v0.15.0. Please install mmcv>=1.1.4', DeprecationWarning)
|
||||
from mmpose.core import Fp16OptimizerHook
|
||||
|
||||
|
||||
def init_random_seed(seed=None, device='cuda'):
|
||||
"""Initialize random seed.
|
||||
|
||||
If the seed is not set, the seed will be automatically randomized,
|
||||
and then broadcast to all processes to prevent some potential bugs.
|
||||
|
||||
Args:
|
||||
seed (int, Optional): The seed. Default to None.
|
||||
device (str): The device where the seed will be put on.
|
||||
Default to 'cuda'.
|
||||
|
||||
Returns:
|
||||
int: Seed to be used.
|
||||
"""
|
||||
if seed is not None:
|
||||
return seed
|
||||
|
||||
# Make sure all ranks share the same random seed to prevent
|
||||
# some potential bugs. Please refer to
|
||||
# https://github.com/open-mmlab/mmdetection/issues/6339
|
||||
rank, world_size = get_dist_info()
|
||||
seed = np.random.randint(2**31)
|
||||
if world_size == 1:
|
||||
return seed
|
||||
|
||||
if rank == 0:
|
||||
random_num = torch.tensor(seed, dtype=torch.int32, device=device)
|
||||
else:
|
||||
random_num = torch.tensor(0, dtype=torch.int32, device=device)
|
||||
dist.broadcast(random_num, src=0)
|
||||
return random_num.item()
|
||||
|
||||
|
||||
def train_model(model,
|
||||
dataset,
|
||||
cfg,
|
||||
distributed=False,
|
||||
validate=False,
|
||||
timestamp=None,
|
||||
meta=None):
|
||||
"""Train model entry function.
|
||||
|
||||
Args:
|
||||
model (nn.Module): The model to be trained.
|
||||
dataset (Dataset): Train dataset.
|
||||
cfg (dict): The config dict for training.
|
||||
distributed (bool): Whether to use distributed training.
|
||||
Default: False.
|
||||
validate (bool): Whether to do evaluation. Default: False.
|
||||
timestamp (str | None): Local time for runner. Default: None.
|
||||
meta (dict | None): Meta dict to record some important information.
|
||||
Default: None
|
||||
"""
|
||||
logger = get_root_logger(cfg.log_level)
|
||||
|
||||
# prepare data loaders
|
||||
dataset = dataset if isinstance(dataset, (list, tuple)) else [dataset]
|
||||
# step 1: give default values and override (if exist) from cfg.data
|
||||
loader_cfg = {
|
||||
**dict(
|
||||
seed=cfg.get('seed'),
|
||||
drop_last=False,
|
||||
dist=distributed,
|
||||
num_gpus=len(cfg.gpu_ids)),
|
||||
**({} if torch.__version__ != 'parrots' else dict(
|
||||
prefetch_num=2,
|
||||
pin_memory=False,
|
||||
)),
|
||||
**dict((k, cfg.data[k]) for k in [
|
||||
'samples_per_gpu',
|
||||
'workers_per_gpu',
|
||||
'shuffle',
|
||||
'seed',
|
||||
'drop_last',
|
||||
'prefetch_num',
|
||||
'pin_memory',
|
||||
'persistent_workers',
|
||||
] if k in cfg.data)
|
||||
}
|
||||
|
||||
# step 2: cfg.data.train_dataloader has highest priority
|
||||
train_loader_cfg = dict(loader_cfg, **cfg.data.get('train_dataloader', {}))
|
||||
|
||||
data_loaders = [build_dataloader(ds, **train_loader_cfg) for ds in dataset]
|
||||
|
||||
# determine whether use adversarial training precess or not
|
||||
use_adverserial_train = cfg.get('use_adversarial_train', False)
|
||||
|
||||
# put model on gpus
|
||||
if distributed:
|
||||
find_unused_parameters = cfg.get('find_unused_parameters', False)
|
||||
# Sets the `find_unused_parameters` parameter in
|
||||
# torch.nn.parallel.DistributedDataParallel
|
||||
|
||||
if use_adverserial_train:
|
||||
# Use DistributedDataParallelWrapper for adversarial training
|
||||
model = DistributedDataParallelWrapper(
|
||||
model,
|
||||
device_ids=[torch.cuda.current_device()],
|
||||
broadcast_buffers=False,
|
||||
find_unused_parameters=find_unused_parameters)
|
||||
else:
|
||||
model = MMDistributedDataParallel(
|
||||
model.cuda(),
|
||||
device_ids=[torch.cuda.current_device()],
|
||||
broadcast_buffers=False,
|
||||
find_unused_parameters=find_unused_parameters)
|
||||
else:
|
||||
if digit_version(mmcv.__version__) >= digit_version(
|
||||
'1.4.4') or torch.cuda.is_available():
|
||||
model = MMDataParallel(model, device_ids=cfg.gpu_ids)
|
||||
else:
|
||||
warnings.warn(
|
||||
'We recommend to use MMCV >= 1.4.4 for CPU training. '
|
||||
'See https://github.com/open-mmlab/mmpose/pull/1157 for '
|
||||
'details.')
|
||||
|
||||
# build runner
|
||||
optimizer = build_optimizers(model, cfg.optimizer)
|
||||
|
||||
runner = EpochBasedRunner(
|
||||
model,
|
||||
optimizer=optimizer,
|
||||
work_dir=cfg.work_dir,
|
||||
logger=logger,
|
||||
meta=meta)
|
||||
# an ugly workaround to make .log and .log.json filenames the same
|
||||
runner.timestamp = timestamp
|
||||
|
||||
if use_adverserial_train:
|
||||
# The optimizer step process is included in the train_step function
|
||||
# of the model, so the runner should NOT include optimizer hook.
|
||||
optimizer_config = None
|
||||
else:
|
||||
# fp16 setting
|
||||
fp16_cfg = cfg.get('fp16', None)
|
||||
if fp16_cfg is not None:
|
||||
optimizer_config = Fp16OptimizerHook(
|
||||
**cfg.optimizer_config, **fp16_cfg, distributed=distributed)
|
||||
elif distributed and 'type' not in cfg.optimizer_config:
|
||||
optimizer_config = OptimizerHook(**cfg.optimizer_config)
|
||||
else:
|
||||
optimizer_config = cfg.optimizer_config
|
||||
|
||||
# register hooks
|
||||
runner.register_training_hooks(cfg.lr_config, optimizer_config,
|
||||
cfg.checkpoint_config, cfg.log_config,
|
||||
cfg.get('momentum_config', None))
|
||||
if distributed:
|
||||
runner.register_hook(DistSamplerSeedHook())
|
||||
|
||||
# register eval hooks
|
||||
if validate:
|
||||
eval_cfg = cfg.get('evaluation', {})
|
||||
val_dataset = build_dataset(cfg.data.val, dict(test_mode=True))
|
||||
dataloader_setting = dict(
|
||||
samples_per_gpu=1,
|
||||
workers_per_gpu=cfg.data.get('workers_per_gpu', 1),
|
||||
# cfg.gpus will be ignored if distributed
|
||||
num_gpus=len(cfg.gpu_ids),
|
||||
dist=distributed,
|
||||
drop_last=False,
|
||||
shuffle=False)
|
||||
dataloader_setting = dict(dataloader_setting,
|
||||
**cfg.data.get('val_dataloader', {}))
|
||||
val_dataloader = build_dataloader(val_dataset, **dataloader_setting)
|
||||
eval_hook = DistEvalHook if distributed else EvalHook
|
||||
runner.register_hook(eval_hook(val_dataloader, **eval_cfg))
|
||||
|
||||
if cfg.resume_from:
|
||||
runner.resume(cfg.resume_from)
|
||||
elif cfg.load_from:
|
||||
runner.load_checkpoint(cfg.load_from)
|
||||
runner.run(data_loaders, cfg.workflow, cfg.total_epochs)
|
||||
Reference in New Issue
Block a user