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,24 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .ae_higher_resolution_head import AEHigherResolutionHead
|
||||
from .ae_multi_stage_head import AEMultiStageHead
|
||||
from .ae_simple_head import AESimpleHead
|
||||
from .deconv_head import DeconvHead
|
||||
from .deeppose_regression_head import DeepposeRegressionHead
|
||||
from .hmr_head import HMRMeshHead
|
||||
from .interhand_3d_head import Interhand3DHead
|
||||
from .temporal_regression_head import TemporalRegressionHead
|
||||
from .topdown_heatmap_base_head import TopdownHeatmapBaseHead
|
||||
from .topdown_heatmap_multi_stage_head import (TopdownHeatmapMSMUHead,
|
||||
TopdownHeatmapMultiStageHead)
|
||||
from .topdown_heatmap_simple_head import TopdownHeatmapSimpleHead
|
||||
from .vipnas_heatmap_simple_head import ViPNASHeatmapSimpleHead
|
||||
from .voxelpose_head import CuboidCenterHead, CuboidPoseHead
|
||||
|
||||
__all__ = [
|
||||
'TopdownHeatmapSimpleHead', 'TopdownHeatmapMultiStageHead',
|
||||
'TopdownHeatmapMSMUHead', 'TopdownHeatmapBaseHead',
|
||||
'AEHigherResolutionHead', 'AESimpleHead', 'AEMultiStageHead',
|
||||
'DeepposeRegressionHead', 'TemporalRegressionHead', 'Interhand3DHead',
|
||||
'HMRMeshHead', 'DeconvHead', 'ViPNASHeatmapSimpleHead', 'CuboidCenterHead',
|
||||
'CuboidPoseHead'
|
||||
]
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import (build_conv_layer, build_upsample_layer, constant_init,
|
||||
normal_init)
|
||||
|
||||
from mmpose.models.builder import build_loss
|
||||
from ..backbones.resnet import BasicBlock
|
||||
from ..builder import HEADS
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class AEHigherResolutionHead(nn.Module):
|
||||
"""Associative embedding with higher resolution head. paper ref: Bowen
|
||||
Cheng et al. "HigherHRNet: Scale-Aware Representation Learning for Bottom-
|
||||
Up Human Pose Estimation".
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels.
|
||||
num_joints (int): Number of joints
|
||||
tag_per_joint (bool): If tag_per_joint is True,
|
||||
the dimension of tags equals to num_joints,
|
||||
else the dimension of tags is 1. Default: True
|
||||
extra (dict): Configs for extra conv layers. Default: None
|
||||
num_deconv_layers (int): Number of deconv layers.
|
||||
num_deconv_layers should >= 0. Note that 0 means
|
||||
no deconv layers.
|
||||
num_deconv_filters (list|tuple): Number of filters.
|
||||
If num_deconv_layers > 0, the length of
|
||||
num_deconv_kernels (list|tuple): Kernel sizes.
|
||||
cat_output (list[bool]): Option to concat outputs.
|
||||
with_ae_loss (list[bool]): Option to use ae loss.
|
||||
loss_keypoint (dict): Config for loss. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
num_joints,
|
||||
tag_per_joint=True,
|
||||
extra=None,
|
||||
num_deconv_layers=1,
|
||||
num_deconv_filters=(32, ),
|
||||
num_deconv_kernels=(4, ),
|
||||
num_basic_blocks=4,
|
||||
cat_output=None,
|
||||
with_ae_loss=None,
|
||||
loss_keypoint=None):
|
||||
super().__init__()
|
||||
|
||||
self.loss = build_loss(loss_keypoint)
|
||||
dim_tag = num_joints if tag_per_joint else 1
|
||||
|
||||
self.num_deconvs = num_deconv_layers
|
||||
self.cat_output = cat_output
|
||||
|
||||
final_layer_output_channels = []
|
||||
|
||||
if with_ae_loss[0]:
|
||||
out_channels = num_joints + dim_tag
|
||||
else:
|
||||
out_channels = num_joints
|
||||
|
||||
final_layer_output_channels.append(out_channels)
|
||||
for i in range(num_deconv_layers):
|
||||
if with_ae_loss[i + 1]:
|
||||
out_channels = num_joints + dim_tag
|
||||
else:
|
||||
out_channels = num_joints
|
||||
final_layer_output_channels.append(out_channels)
|
||||
|
||||
deconv_layer_output_channels = []
|
||||
for i in range(num_deconv_layers):
|
||||
if with_ae_loss[i]:
|
||||
out_channels = num_joints + dim_tag
|
||||
else:
|
||||
out_channels = num_joints
|
||||
deconv_layer_output_channels.append(out_channels)
|
||||
|
||||
self.final_layers = self._make_final_layers(
|
||||
in_channels, final_layer_output_channels, extra, num_deconv_layers,
|
||||
num_deconv_filters)
|
||||
self.deconv_layers = self._make_deconv_layers(
|
||||
in_channels, deconv_layer_output_channels, num_deconv_layers,
|
||||
num_deconv_filters, num_deconv_kernels, num_basic_blocks,
|
||||
cat_output)
|
||||
|
||||
@staticmethod
|
||||
def _make_final_layers(in_channels, final_layer_output_channels, extra,
|
||||
num_deconv_layers, num_deconv_filters):
|
||||
"""Make final layers."""
|
||||
if extra is not None and 'final_conv_kernel' in extra:
|
||||
assert extra['final_conv_kernel'] in [1, 3]
|
||||
if extra['final_conv_kernel'] == 3:
|
||||
padding = 1
|
||||
else:
|
||||
padding = 0
|
||||
kernel_size = extra['final_conv_kernel']
|
||||
else:
|
||||
kernel_size = 1
|
||||
padding = 0
|
||||
|
||||
final_layers = []
|
||||
final_layers.append(
|
||||
build_conv_layer(
|
||||
cfg=dict(type='Conv2d'),
|
||||
in_channels=in_channels,
|
||||
out_channels=final_layer_output_channels[0],
|
||||
kernel_size=kernel_size,
|
||||
stride=1,
|
||||
padding=padding))
|
||||
|
||||
for i in range(num_deconv_layers):
|
||||
in_channels = num_deconv_filters[i]
|
||||
final_layers.append(
|
||||
build_conv_layer(
|
||||
cfg=dict(type='Conv2d'),
|
||||
in_channels=in_channels,
|
||||
out_channels=final_layer_output_channels[i + 1],
|
||||
kernel_size=kernel_size,
|
||||
stride=1,
|
||||
padding=padding))
|
||||
|
||||
return nn.ModuleList(final_layers)
|
||||
|
||||
def _make_deconv_layers(self, in_channels, deconv_layer_output_channels,
|
||||
num_deconv_layers, num_deconv_filters,
|
||||
num_deconv_kernels, num_basic_blocks, cat_output):
|
||||
"""Make deconv layers."""
|
||||
deconv_layers = []
|
||||
for i in range(num_deconv_layers):
|
||||
if cat_output[i]:
|
||||
in_channels += deconv_layer_output_channels[i]
|
||||
|
||||
planes = num_deconv_filters[i]
|
||||
deconv_kernel, padding, output_padding = \
|
||||
self._get_deconv_cfg(num_deconv_kernels[i])
|
||||
|
||||
layers = []
|
||||
layers.append(
|
||||
nn.Sequential(
|
||||
build_upsample_layer(
|
||||
dict(type='deconv'),
|
||||
in_channels=in_channels,
|
||||
out_channels=planes,
|
||||
kernel_size=deconv_kernel,
|
||||
stride=2,
|
||||
padding=padding,
|
||||
output_padding=output_padding,
|
||||
bias=False), nn.BatchNorm2d(planes, momentum=0.1),
|
||||
nn.ReLU(inplace=True)))
|
||||
for _ in range(num_basic_blocks):
|
||||
layers.append(nn.Sequential(BasicBlock(planes, planes), ))
|
||||
deconv_layers.append(nn.Sequential(*layers))
|
||||
in_channels = planes
|
||||
|
||||
return nn.ModuleList(deconv_layers)
|
||||
|
||||
@staticmethod
|
||||
def _get_deconv_cfg(deconv_kernel):
|
||||
"""Get configurations for deconv layers."""
|
||||
if deconv_kernel == 4:
|
||||
padding = 1
|
||||
output_padding = 0
|
||||
elif deconv_kernel == 3:
|
||||
padding = 1
|
||||
output_padding = 1
|
||||
elif deconv_kernel == 2:
|
||||
padding = 0
|
||||
output_padding = 0
|
||||
else:
|
||||
raise ValueError(f'Not supported num_kernels ({deconv_kernel}).')
|
||||
|
||||
return deconv_kernel, padding, output_padding
|
||||
|
||||
def get_loss(self, outputs, targets, masks, joints):
|
||||
"""Calculate bottom-up keypoint loss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- num_outputs: O
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
outputs (list(torch.Tensor[N,K,H,W])): Multi-scale output heatmaps.
|
||||
targets (List(torch.Tensor[N,K,H,W])): Multi-scale target heatmaps.
|
||||
masks (List(torch.Tensor[N,H,W])): Masks of multi-scale target
|
||||
heatmaps
|
||||
joints (List(torch.Tensor[N,M,K,2])): Joints of multi-scale target
|
||||
heatmaps for ae loss
|
||||
"""
|
||||
|
||||
losses = dict()
|
||||
|
||||
heatmaps_losses, push_losses, pull_losses = self.loss(
|
||||
outputs, targets, masks, joints)
|
||||
|
||||
for idx in range(len(targets)):
|
||||
if heatmaps_losses[idx] is not None:
|
||||
heatmaps_loss = heatmaps_losses[idx].mean(dim=0)
|
||||
if 'heatmap_loss' not in losses:
|
||||
losses['heatmap_loss'] = heatmaps_loss
|
||||
else:
|
||||
losses['heatmap_loss'] += heatmaps_loss
|
||||
if push_losses[idx] is not None:
|
||||
push_loss = push_losses[idx].mean(dim=0)
|
||||
if 'push_loss' not in losses:
|
||||
losses['push_loss'] = push_loss
|
||||
else:
|
||||
losses['push_loss'] += push_loss
|
||||
if pull_losses[idx] is not None:
|
||||
pull_loss = pull_losses[idx].mean(dim=0)
|
||||
if 'pull_loss' not in losses:
|
||||
losses['pull_loss'] = pull_loss
|
||||
else:
|
||||
losses['pull_loss'] += pull_loss
|
||||
|
||||
return losses
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
if isinstance(x, list):
|
||||
x = x[0]
|
||||
|
||||
final_outputs = []
|
||||
y = self.final_layers[0](x)
|
||||
final_outputs.append(y)
|
||||
|
||||
for i in range(self.num_deconvs):
|
||||
if self.cat_output[i]:
|
||||
x = torch.cat((x, y), 1)
|
||||
|
||||
x = self.deconv_layers[i](x)
|
||||
y = self.final_layers[i + 1](x)
|
||||
final_outputs.append(y)
|
||||
|
||||
return final_outputs
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize model weights."""
|
||||
for _, m in self.deconv_layers.named_modules():
|
||||
if isinstance(m, nn.ConvTranspose2d):
|
||||
normal_init(m, std=0.001)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
constant_init(m, 1)
|
||||
for _, m in self.final_layers.named_modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
normal_init(m, std=0.001, bias=0)
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import (build_conv_layer, build_upsample_layer, constant_init,
|
||||
normal_init)
|
||||
|
||||
from mmpose.models.builder import build_loss
|
||||
from ..builder import HEADS
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class AEMultiStageHead(nn.Module):
|
||||
"""Associative embedding multi-stage head.
|
||||
paper ref: Alejandro Newell et al. "Associative
|
||||
Embedding: End-to-end Learning for Joint Detection
|
||||
and Grouping"
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels.
|
||||
out_channels (int): Number of output channels.
|
||||
num_deconv_layers (int): Number of deconv layers.
|
||||
num_deconv_layers should >= 0. Note that 0 means
|
||||
no deconv layers.
|
||||
num_deconv_filters (list|tuple): Number of filters.
|
||||
If num_deconv_layers > 0, the length of
|
||||
num_deconv_kernels (list|tuple): Kernel sizes.
|
||||
loss_keypoint (dict): Config for loss. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
num_stages=1,
|
||||
num_deconv_layers=3,
|
||||
num_deconv_filters=(256, 256, 256),
|
||||
num_deconv_kernels=(4, 4, 4),
|
||||
extra=None,
|
||||
loss_keypoint=None):
|
||||
super().__init__()
|
||||
|
||||
self.loss = build_loss(loss_keypoint)
|
||||
|
||||
self.in_channels = in_channels
|
||||
self.num_stages = num_stages
|
||||
|
||||
if extra is not None and not isinstance(extra, dict):
|
||||
raise TypeError('extra should be dict or None.')
|
||||
|
||||
# build multi-stage deconv layers
|
||||
self.multi_deconv_layers = nn.ModuleList([])
|
||||
for _ in range(self.num_stages):
|
||||
if num_deconv_layers > 0:
|
||||
deconv_layers = self._make_deconv_layer(
|
||||
num_deconv_layers,
|
||||
num_deconv_filters,
|
||||
num_deconv_kernels,
|
||||
)
|
||||
elif num_deconv_layers == 0:
|
||||
deconv_layers = nn.Identity()
|
||||
else:
|
||||
raise ValueError(
|
||||
f'num_deconv_layers ({num_deconv_layers}) should >= 0.')
|
||||
self.multi_deconv_layers.append(deconv_layers)
|
||||
|
||||
identity_final_layer = False
|
||||
if extra is not None and 'final_conv_kernel' in extra:
|
||||
assert extra['final_conv_kernel'] in [0, 1, 3]
|
||||
if extra['final_conv_kernel'] == 3:
|
||||
padding = 1
|
||||
elif extra['final_conv_kernel'] == 1:
|
||||
padding = 0
|
||||
else:
|
||||
# 0 for Identity mapping.
|
||||
identity_final_layer = True
|
||||
kernel_size = extra['final_conv_kernel']
|
||||
else:
|
||||
kernel_size = 1
|
||||
padding = 0
|
||||
|
||||
# build multi-stage final layers
|
||||
self.multi_final_layers = nn.ModuleList([])
|
||||
for i in range(self.num_stages):
|
||||
if identity_final_layer:
|
||||
final_layer = nn.Identity()
|
||||
else:
|
||||
final_layer = build_conv_layer(
|
||||
cfg=dict(type='Conv2d'),
|
||||
in_channels=num_deconv_filters[-1]
|
||||
if num_deconv_layers > 0 else in_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=1,
|
||||
padding=padding)
|
||||
self.multi_final_layers.append(final_layer)
|
||||
|
||||
def get_loss(self, output, targets, masks, joints):
|
||||
"""Calculate bottom-up keypoint loss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
output (List(torch.Tensor[NxKxHxW])): Output heatmaps.
|
||||
targets(List(List(torch.Tensor[NxKxHxW]))):
|
||||
Multi-stage and multi-scale target heatmaps.
|
||||
masks(List(List(torch.Tensor[NxHxW]))):
|
||||
Masks of multi-stage and multi-scale target heatmaps
|
||||
joints(List(List(torch.Tensor[NxMxKx2]))):
|
||||
Joints of multi-stage multi-scale target heatmaps for ae loss
|
||||
"""
|
||||
|
||||
losses = dict()
|
||||
|
||||
# Flatten list:
|
||||
# [stage_1_scale_1, stage_1_scale_2, ... , stage_1_scale_m,
|
||||
# ...
|
||||
# stage_n_scale_1, stage_n_scale_2, ... , stage_n_scale_m]
|
||||
targets = [target for _targets in targets for target in _targets]
|
||||
masks = [mask for _masks in masks for mask in _masks]
|
||||
joints = [joint for _joints in joints for joint in _joints]
|
||||
|
||||
heatmaps_losses, push_losses, pull_losses = self.loss(
|
||||
output, targets, masks, joints)
|
||||
|
||||
for idx in range(len(targets)):
|
||||
if heatmaps_losses[idx] is not None:
|
||||
heatmaps_loss = heatmaps_losses[idx].mean(dim=0)
|
||||
if 'heatmap_loss' not in losses:
|
||||
losses['heatmap_loss'] = heatmaps_loss
|
||||
else:
|
||||
losses['heatmap_loss'] += heatmaps_loss
|
||||
if push_losses[idx] is not None:
|
||||
push_loss = push_losses[idx].mean(dim=0)
|
||||
if 'push_loss' not in losses:
|
||||
losses['push_loss'] = push_loss
|
||||
else:
|
||||
losses['push_loss'] += push_loss
|
||||
if pull_losses[idx] is not None:
|
||||
pull_loss = pull_losses[idx].mean(dim=0)
|
||||
if 'pull_loss' not in losses:
|
||||
losses['pull_loss'] = pull_loss
|
||||
else:
|
||||
losses['pull_loss'] += pull_loss
|
||||
|
||||
return losses
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function.
|
||||
|
||||
Returns:
|
||||
out (list[Tensor]): a list of heatmaps from multiple stages.
|
||||
"""
|
||||
out = []
|
||||
assert isinstance(x, list)
|
||||
for i in range(self.num_stages):
|
||||
y = self.multi_deconv_layers[i](x[i])
|
||||
y = self.multi_final_layers[i](y)
|
||||
out.append(y)
|
||||
return out
|
||||
|
||||
def _make_deconv_layer(self, num_layers, num_filters, num_kernels):
|
||||
"""Make deconv layers."""
|
||||
if num_layers != len(num_filters):
|
||||
error_msg = f'num_layers({num_layers}) ' \
|
||||
f'!= length of num_filters({len(num_filters)})'
|
||||
raise ValueError(error_msg)
|
||||
if num_layers != len(num_kernels):
|
||||
error_msg = f'num_layers({num_layers}) ' \
|
||||
f'!= length of num_kernels({len(num_kernels)})'
|
||||
raise ValueError(error_msg)
|
||||
|
||||
layers = []
|
||||
for i in range(num_layers):
|
||||
kernel, padding, output_padding = \
|
||||
self._get_deconv_cfg(num_kernels[i])
|
||||
|
||||
planes = num_filters[i]
|
||||
layers.append(
|
||||
build_upsample_layer(
|
||||
dict(type='deconv'),
|
||||
in_channels=self.in_channels,
|
||||
out_channels=planes,
|
||||
kernel_size=kernel,
|
||||
stride=2,
|
||||
padding=padding,
|
||||
output_padding=output_padding,
|
||||
bias=False))
|
||||
layers.append(nn.BatchNorm2d(planes))
|
||||
layers.append(nn.ReLU(inplace=True))
|
||||
self.in_channels = planes
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
@staticmethod
|
||||
def _get_deconv_cfg(deconv_kernel):
|
||||
"""Get configurations for deconv layers."""
|
||||
if deconv_kernel == 4:
|
||||
padding = 1
|
||||
output_padding = 0
|
||||
elif deconv_kernel == 3:
|
||||
padding = 1
|
||||
output_padding = 1
|
||||
elif deconv_kernel == 2:
|
||||
padding = 0
|
||||
output_padding = 0
|
||||
else:
|
||||
raise ValueError(f'Not supported num_kernels ({deconv_kernel}).')
|
||||
|
||||
return deconv_kernel, padding, output_padding
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize model weights."""
|
||||
for _, m in self.multi_deconv_layers.named_modules():
|
||||
if isinstance(m, nn.ConvTranspose2d):
|
||||
normal_init(m, std=0.001)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
constant_init(m, 1)
|
||||
for m in self.multi_final_layers.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
normal_init(m, std=0.001, bias=0)
|
||||
@@ -0,0 +1,99 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from ..builder import HEADS
|
||||
from .deconv_head import DeconvHead
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class AESimpleHead(DeconvHead):
|
||||
"""Associative embedding simple head.
|
||||
paper ref: Alejandro Newell et al. "Associative
|
||||
Embedding: End-to-end Learning for Joint Detection
|
||||
and Grouping"
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels.
|
||||
num_joints (int): Number of joints.
|
||||
num_deconv_layers (int): Number of deconv layers.
|
||||
num_deconv_layers should >= 0. Note that 0 means
|
||||
no deconv layers.
|
||||
num_deconv_filters (list|tuple): Number of filters.
|
||||
If num_deconv_layers > 0, the length of
|
||||
num_deconv_kernels (list|tuple): Kernel sizes.
|
||||
tag_per_joint (bool): If tag_per_joint is True,
|
||||
the dimension of tags equals to num_joints,
|
||||
else the dimension of tags is 1. Default: True
|
||||
with_ae_loss (list[bool]): Option to use ae loss or not.
|
||||
loss_keypoint (dict): Config for loss. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
num_joints,
|
||||
num_deconv_layers=3,
|
||||
num_deconv_filters=(256, 256, 256),
|
||||
num_deconv_kernels=(4, 4, 4),
|
||||
tag_per_joint=True,
|
||||
with_ae_loss=None,
|
||||
extra=None,
|
||||
loss_keypoint=None):
|
||||
|
||||
dim_tag = num_joints if tag_per_joint else 1
|
||||
if with_ae_loss[0]:
|
||||
out_channels = num_joints + dim_tag
|
||||
else:
|
||||
out_channels = num_joints
|
||||
|
||||
super().__init__(
|
||||
in_channels,
|
||||
out_channels,
|
||||
num_deconv_layers=num_deconv_layers,
|
||||
num_deconv_filters=num_deconv_filters,
|
||||
num_deconv_kernels=num_deconv_kernels,
|
||||
extra=extra,
|
||||
loss_keypoint=loss_keypoint)
|
||||
|
||||
def get_loss(self, outputs, targets, masks, joints):
|
||||
"""Calculate bottom-up keypoint loss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- num_outputs: O
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
outputs (list(torch.Tensor[N,K,H,W])): Multi-scale output heatmaps.
|
||||
targets (List(torch.Tensor[N,K,H,W])): Multi-scale target heatmaps.
|
||||
masks (List(torch.Tensor[N,H,W])): Masks of multi-scale target
|
||||
heatmaps
|
||||
joints(List(torch.Tensor[N,M,K,2])): Joints of multi-scale target
|
||||
heatmaps for ae loss
|
||||
"""
|
||||
|
||||
losses = dict()
|
||||
|
||||
heatmaps_losses, push_losses, pull_losses = self.loss(
|
||||
outputs, targets, masks, joints)
|
||||
|
||||
for idx in range(len(targets)):
|
||||
if heatmaps_losses[idx] is not None:
|
||||
heatmaps_loss = heatmaps_losses[idx].mean(dim=0)
|
||||
if 'heatmap_loss' not in losses:
|
||||
losses['heatmap_loss'] = heatmaps_loss
|
||||
else:
|
||||
losses['heatmap_loss'] += heatmaps_loss
|
||||
if push_losses[idx] is not None:
|
||||
push_loss = push_losses[idx].mean(dim=0)
|
||||
if 'push_loss' not in losses:
|
||||
losses['push_loss'] = push_loss
|
||||
else:
|
||||
losses['push_loss'] += push_loss
|
||||
if pull_losses[idx] is not None:
|
||||
pull_loss = pull_losses[idx].mean(dim=0)
|
||||
if 'pull_loss' not in losses:
|
||||
losses['pull_loss'] = pull_loss
|
||||
else:
|
||||
losses['pull_loss'] += pull_loss
|
||||
|
||||
return losses
|
||||
@@ -0,0 +1,295 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import (build_conv_layer, build_norm_layer, build_upsample_layer,
|
||||
constant_init, normal_init)
|
||||
|
||||
from mmpose.models.builder import HEADS, build_loss
|
||||
from mmpose.models.utils.ops import resize
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class DeconvHead(nn.Module):
|
||||
"""Simple deconv head.
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels.
|
||||
out_channels (int): Number of output channels.
|
||||
num_deconv_layers (int): Number of deconv layers.
|
||||
num_deconv_layers should >= 0. Note that 0 means
|
||||
no deconv layers.
|
||||
num_deconv_filters (list|tuple): Number of filters.
|
||||
If num_deconv_layers > 0, the length of
|
||||
num_deconv_kernels (list|tuple): Kernel sizes.
|
||||
in_index (int|Sequence[int]): Input feature index. Default: 0
|
||||
input_transform (str|None): Transformation type of input features.
|
||||
Options: 'resize_concat', 'multiple_select', None.
|
||||
Default: None.
|
||||
|
||||
- 'resize_concat': Multiple feature maps will be resized to the
|
||||
same size as the first one and then concat together.
|
||||
Usually used in FCN head of HRNet.
|
||||
- 'multiple_select': Multiple feature maps will be bundle into
|
||||
a list and passed into decode head.
|
||||
- None: Only one select feature map is allowed.
|
||||
align_corners (bool): align_corners argument of F.interpolate.
|
||||
Default: False.
|
||||
loss_keypoint (dict): Config for loss. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels=3,
|
||||
out_channels=17,
|
||||
num_deconv_layers=3,
|
||||
num_deconv_filters=(256, 256, 256),
|
||||
num_deconv_kernels=(4, 4, 4),
|
||||
extra=None,
|
||||
in_index=0,
|
||||
input_transform=None,
|
||||
align_corners=False,
|
||||
loss_keypoint=None):
|
||||
super().__init__()
|
||||
|
||||
self.in_channels = in_channels
|
||||
self.loss = build_loss(loss_keypoint)
|
||||
|
||||
self._init_inputs(in_channels, in_index, input_transform)
|
||||
self.in_index = in_index
|
||||
self.align_corners = align_corners
|
||||
|
||||
if extra is not None and not isinstance(extra, dict):
|
||||
raise TypeError('extra should be dict or None.')
|
||||
|
||||
if num_deconv_layers > 0:
|
||||
self.deconv_layers = self._make_deconv_layer(
|
||||
num_deconv_layers,
|
||||
num_deconv_filters,
|
||||
num_deconv_kernels,
|
||||
)
|
||||
elif num_deconv_layers == 0:
|
||||
self.deconv_layers = nn.Identity()
|
||||
else:
|
||||
raise ValueError(
|
||||
f'num_deconv_layers ({num_deconv_layers}) should >= 0.')
|
||||
|
||||
identity_final_layer = False
|
||||
if extra is not None and 'final_conv_kernel' in extra:
|
||||
assert extra['final_conv_kernel'] in [0, 1, 3]
|
||||
if extra['final_conv_kernel'] == 3:
|
||||
padding = 1
|
||||
elif extra['final_conv_kernel'] == 1:
|
||||
padding = 0
|
||||
else:
|
||||
# 0 for Identity mapping.
|
||||
identity_final_layer = True
|
||||
kernel_size = extra['final_conv_kernel']
|
||||
else:
|
||||
kernel_size = 1
|
||||
padding = 0
|
||||
|
||||
if identity_final_layer:
|
||||
self.final_layer = nn.Identity()
|
||||
else:
|
||||
conv_channels = num_deconv_filters[
|
||||
-1] if num_deconv_layers > 0 else self.in_channels
|
||||
|
||||
layers = []
|
||||
if extra is not None:
|
||||
num_conv_layers = extra.get('num_conv_layers', 0)
|
||||
num_conv_kernels = extra.get('num_conv_kernels',
|
||||
[1] * num_conv_layers)
|
||||
|
||||
for i in range(num_conv_layers):
|
||||
layers.append(
|
||||
build_conv_layer(
|
||||
dict(type='Conv2d'),
|
||||
in_channels=conv_channels,
|
||||
out_channels=conv_channels,
|
||||
kernel_size=num_conv_kernels[i],
|
||||
stride=1,
|
||||
padding=(num_conv_kernels[i] - 1) // 2))
|
||||
layers.append(
|
||||
build_norm_layer(dict(type='BN'), conv_channels)[1])
|
||||
layers.append(nn.ReLU(inplace=True))
|
||||
|
||||
layers.append(
|
||||
build_conv_layer(
|
||||
cfg=dict(type='Conv2d'),
|
||||
in_channels=conv_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=1,
|
||||
padding=padding))
|
||||
|
||||
if len(layers) > 1:
|
||||
self.final_layer = nn.Sequential(*layers)
|
||||
else:
|
||||
self.final_layer = layers[0]
|
||||
|
||||
def _init_inputs(self, in_channels, in_index, input_transform):
|
||||
"""Check and initialize input transforms.
|
||||
|
||||
The in_channels, in_index and input_transform must match.
|
||||
Specifically, when input_transform is None, only single feature map
|
||||
will be selected. So in_channels and in_index must be of type int.
|
||||
When input_transform is not None, in_channels and in_index must be
|
||||
list or tuple, with the same length.
|
||||
|
||||
Args:
|
||||
in_channels (int|Sequence[int]): Input channels.
|
||||
in_index (int|Sequence[int]): Input feature index.
|
||||
input_transform (str|None): Transformation type of input features.
|
||||
Options: 'resize_concat', 'multiple_select', None.
|
||||
|
||||
- 'resize_concat': Multiple feature maps will be resize to the
|
||||
same size as first one and than concat together.
|
||||
Usually used in FCN head of HRNet.
|
||||
- 'multiple_select': Multiple feature maps will be bundle into
|
||||
a list and passed into decode head.
|
||||
- None: Only one select feature map is allowed.
|
||||
"""
|
||||
|
||||
if input_transform is not None:
|
||||
assert input_transform in ['resize_concat', 'multiple_select']
|
||||
self.input_transform = input_transform
|
||||
self.in_index = in_index
|
||||
if input_transform is not None:
|
||||
assert isinstance(in_channels, (list, tuple))
|
||||
assert isinstance(in_index, (list, tuple))
|
||||
assert len(in_channels) == len(in_index)
|
||||
if input_transform == 'resize_concat':
|
||||
self.in_channels = sum(in_channels)
|
||||
else:
|
||||
self.in_channels = in_channels
|
||||
else:
|
||||
assert isinstance(in_channels, int)
|
||||
assert isinstance(in_index, int)
|
||||
self.in_channels = in_channels
|
||||
|
||||
def _transform_inputs(self, inputs):
|
||||
"""Transform inputs for decoder.
|
||||
|
||||
Args:
|
||||
inputs (list[Tensor] | Tensor): multi-level img features.
|
||||
|
||||
Returns:
|
||||
Tensor: The transformed inputs
|
||||
"""
|
||||
if not isinstance(inputs, list):
|
||||
return inputs
|
||||
|
||||
if self.input_transform == 'resize_concat':
|
||||
inputs = [inputs[i] for i in self.in_index]
|
||||
upsampled_inputs = [
|
||||
resize(
|
||||
input=x,
|
||||
size=inputs[0].shape[2:],
|
||||
mode='bilinear',
|
||||
align_corners=self.align_corners) for x in inputs
|
||||
]
|
||||
inputs = torch.cat(upsampled_inputs, dim=1)
|
||||
elif self.input_transform == 'multiple_select':
|
||||
inputs = [inputs[i] for i in self.in_index]
|
||||
else:
|
||||
inputs = inputs[self.in_index]
|
||||
|
||||
return inputs
|
||||
|
||||
def _make_deconv_layer(self, num_layers, num_filters, num_kernels):
|
||||
"""Make deconv layers."""
|
||||
if num_layers != len(num_filters):
|
||||
error_msg = f'num_layers({num_layers}) ' \
|
||||
f'!= length of num_filters({len(num_filters)})'
|
||||
raise ValueError(error_msg)
|
||||
if num_layers != len(num_kernels):
|
||||
error_msg = f'num_layers({num_layers}) ' \
|
||||
f'!= length of num_kernels({len(num_kernels)})'
|
||||
raise ValueError(error_msg)
|
||||
|
||||
layers = []
|
||||
for i in range(num_layers):
|
||||
kernel, padding, output_padding = \
|
||||
self._get_deconv_cfg(num_kernels[i])
|
||||
|
||||
planes = num_filters[i]
|
||||
layers.append(
|
||||
build_upsample_layer(
|
||||
dict(type='deconv'),
|
||||
in_channels=self.in_channels,
|
||||
out_channels=planes,
|
||||
kernel_size=kernel,
|
||||
stride=2,
|
||||
padding=padding,
|
||||
output_padding=output_padding,
|
||||
bias=False))
|
||||
layers.append(nn.BatchNorm2d(planes))
|
||||
layers.append(nn.ReLU(inplace=True))
|
||||
self.in_channels = planes
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
@staticmethod
|
||||
def _get_deconv_cfg(deconv_kernel):
|
||||
"""Get configurations for deconv layers."""
|
||||
if deconv_kernel == 4:
|
||||
padding = 1
|
||||
output_padding = 0
|
||||
elif deconv_kernel == 3:
|
||||
padding = 1
|
||||
output_padding = 1
|
||||
elif deconv_kernel == 2:
|
||||
padding = 0
|
||||
output_padding = 0
|
||||
else:
|
||||
raise ValueError(f'Not supported num_kernels ({deconv_kernel}).')
|
||||
|
||||
return deconv_kernel, padding, output_padding
|
||||
|
||||
def get_loss(self, outputs, targets, masks):
|
||||
"""Calculate bottom-up masked mse loss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_channels: C
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
outputs (List(torch.Tensor[N,C,H,W])): Multi-scale outputs.
|
||||
targets (List(torch.Tensor[N,C,H,W])): Multi-scale targets.
|
||||
masks (List(torch.Tensor[N,H,W])): Masks of multi-scale targets.
|
||||
"""
|
||||
|
||||
losses = dict()
|
||||
|
||||
for idx in range(len(targets)):
|
||||
if 'loss' not in losses:
|
||||
losses['loss'] = self.loss(outputs[idx], targets[idx],
|
||||
masks[idx])
|
||||
else:
|
||||
losses['loss'] += self.loss(outputs[idx], targets[idx],
|
||||
masks[idx])
|
||||
|
||||
return losses
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
x = self._transform_inputs(x)
|
||||
final_outputs = []
|
||||
x = self.deconv_layers(x)
|
||||
y = self.final_layer(x)
|
||||
final_outputs.append(y)
|
||||
return final_outputs
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize model weights."""
|
||||
for _, m in self.deconv_layers.named_modules():
|
||||
if isinstance(m, nn.ConvTranspose2d):
|
||||
normal_init(m, std=0.001)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
constant_init(m, 1)
|
||||
for m in self.final_layer.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
normal_init(m, std=0.001, bias=0)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
constant_init(m, 1)
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import numpy as np
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import normal_init
|
||||
|
||||
from mmpose.core.evaluation import (keypoint_pck_accuracy,
|
||||
keypoints_from_regression)
|
||||
from mmpose.core.post_processing import fliplr_regression
|
||||
from mmpose.models.builder import HEADS, build_loss
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class DeepposeRegressionHead(nn.Module):
|
||||
"""Deeppose regression head with fully connected layers.
|
||||
|
||||
"DeepPose: Human Pose Estimation via Deep Neural Networks".
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels
|
||||
num_joints (int): Number of joints
|
||||
loss_keypoint (dict): Config for keypoint loss. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
num_joints,
|
||||
loss_keypoint=None,
|
||||
train_cfg=None,
|
||||
test_cfg=None):
|
||||
super().__init__()
|
||||
|
||||
self.in_channels = in_channels
|
||||
self.num_joints = num_joints
|
||||
|
||||
self.loss = build_loss(loss_keypoint)
|
||||
|
||||
self.train_cfg = {} if train_cfg is None else train_cfg
|
||||
self.test_cfg = {} if test_cfg is None else test_cfg
|
||||
|
||||
self.fc = nn.Linear(self.in_channels, self.num_joints * 2)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
output = self.fc(x)
|
||||
N, C = output.shape
|
||||
return output.reshape([N, C // 2, 2])
|
||||
|
||||
def get_loss(self, output, target, target_weight):
|
||||
"""Calculate top-down keypoint loss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, 2]): Output keypoints.
|
||||
target (torch.Tensor[N, K, 2]): Target keypoints.
|
||||
target_weight (torch.Tensor[N, K, 2]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
|
||||
losses = dict()
|
||||
assert not isinstance(self.loss, nn.Sequential)
|
||||
assert target.dim() == 3 and target_weight.dim() == 3
|
||||
losses['reg_loss'] = self.loss(output, target, target_weight)
|
||||
|
||||
return losses
|
||||
|
||||
def get_accuracy(self, output, target, target_weight):
|
||||
"""Calculate accuracy for top-down keypoint loss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, 2]): Output keypoints.
|
||||
target (torch.Tensor[N, K, 2]): Target keypoints.
|
||||
target_weight (torch.Tensor[N, K, 2]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
|
||||
accuracy = dict()
|
||||
|
||||
N = output.shape[0]
|
||||
|
||||
_, avg_acc, cnt = keypoint_pck_accuracy(
|
||||
output.detach().cpu().numpy(),
|
||||
target.detach().cpu().numpy(),
|
||||
target_weight[:, :, 0].detach().cpu().numpy() > 0,
|
||||
thr=0.05,
|
||||
normalize=np.ones((N, 2), dtype=np.float32))
|
||||
accuracy['acc_pose'] = avg_acc
|
||||
|
||||
return accuracy
|
||||
|
||||
def inference_model(self, x, flip_pairs=None):
|
||||
"""Inference function.
|
||||
|
||||
Returns:
|
||||
output_regression (np.ndarray): Output regression.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor[N, K, 2]): Input features.
|
||||
flip_pairs (None | list[tuple()):
|
||||
Pairs of keypoints which are mirrored.
|
||||
"""
|
||||
output = self.forward(x)
|
||||
|
||||
if flip_pairs is not None:
|
||||
output_regression = fliplr_regression(
|
||||
output.detach().cpu().numpy(), flip_pairs)
|
||||
else:
|
||||
output_regression = output.detach().cpu().numpy()
|
||||
return output_regression
|
||||
|
||||
def decode(self, img_metas, output, **kwargs):
|
||||
"""Decode the keypoints from output regression.
|
||||
|
||||
Args:
|
||||
img_metas (list(dict)): Information about data augmentation
|
||||
By default this includes:
|
||||
|
||||
- "image_file: path to the image file
|
||||
- "center": center of the bbox
|
||||
- "scale": scale of the bbox
|
||||
- "rotation": rotation of the bbox
|
||||
- "bbox_score": score of bbox
|
||||
output (np.ndarray[N, K, 2]): predicted regression vector.
|
||||
kwargs: dict contains 'img_size'.
|
||||
img_size (tuple(img_width, img_height)): input image size.
|
||||
"""
|
||||
batch_size = len(img_metas)
|
||||
|
||||
if 'bbox_id' in img_metas[0]:
|
||||
bbox_ids = []
|
||||
else:
|
||||
bbox_ids = None
|
||||
|
||||
c = np.zeros((batch_size, 2), dtype=np.float32)
|
||||
s = np.zeros((batch_size, 2), dtype=np.float32)
|
||||
image_paths = []
|
||||
score = np.ones(batch_size)
|
||||
for i in range(batch_size):
|
||||
c[i, :] = img_metas[i]['center']
|
||||
s[i, :] = img_metas[i]['scale']
|
||||
image_paths.append(img_metas[i]['image_file'])
|
||||
|
||||
if 'bbox_score' in img_metas[i]:
|
||||
score[i] = np.array(img_metas[i]['bbox_score']).reshape(-1)
|
||||
if bbox_ids is not None:
|
||||
bbox_ids.append(img_metas[i]['bbox_id'])
|
||||
|
||||
preds, maxvals = keypoints_from_regression(output, c, s,
|
||||
kwargs['img_size'])
|
||||
|
||||
all_preds = np.zeros((batch_size, preds.shape[1], 3), dtype=np.float32)
|
||||
all_boxes = np.zeros((batch_size, 6), dtype=np.float32)
|
||||
all_preds[:, :, 0:2] = preds[:, :, 0:2]
|
||||
all_preds[:, :, 2:3] = maxvals
|
||||
all_boxes[:, 0:2] = c[:, 0:2]
|
||||
all_boxes[:, 2:4] = s[:, 0:2]
|
||||
all_boxes[:, 4] = np.prod(s * 200.0, axis=1)
|
||||
all_boxes[:, 5] = score
|
||||
|
||||
result = {}
|
||||
|
||||
result['preds'] = all_preds
|
||||
result['boxes'] = all_boxes
|
||||
result['image_paths'] = image_paths
|
||||
result['bbox_ids'] = bbox_ids
|
||||
|
||||
return result
|
||||
|
||||
def init_weights(self):
|
||||
normal_init(self.fc, mean=0, std=0.01, bias=0)
|
||||
@@ -0,0 +1,94 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import xavier_init
|
||||
|
||||
from ..builder import HEADS
|
||||
from ..utils.geometry import rot6d_to_rotmat
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class HMRMeshHead(nn.Module):
|
||||
"""SMPL parameters regressor head of simple baseline. "End-to-end Recovery
|
||||
of Human Shape and Pose", CVPR'2018.
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels
|
||||
smpl_mean_params (str): The file name of the mean SMPL parameters
|
||||
n_iter (int): The iterations of estimating delta parameters
|
||||
"""
|
||||
|
||||
def __init__(self, in_channels, smpl_mean_params=None, n_iter=3):
|
||||
super().__init__()
|
||||
|
||||
self.in_channels = in_channels
|
||||
self.n_iter = n_iter
|
||||
|
||||
npose = 24 * 6
|
||||
nbeta = 10
|
||||
ncam = 3
|
||||
hidden_dim = 1024
|
||||
|
||||
self.fc1 = nn.Linear(in_channels + npose + nbeta + ncam, hidden_dim)
|
||||
self.drop1 = nn.Dropout()
|
||||
self.fc2 = nn.Linear(hidden_dim, hidden_dim)
|
||||
self.drop2 = nn.Dropout()
|
||||
self.decpose = nn.Linear(hidden_dim, npose)
|
||||
self.decshape = nn.Linear(hidden_dim, nbeta)
|
||||
self.deccam = nn.Linear(hidden_dim, ncam)
|
||||
|
||||
# Load mean SMPL parameters
|
||||
if smpl_mean_params is None:
|
||||
init_pose = torch.zeros([1, npose])
|
||||
init_shape = torch.zeros([1, nbeta])
|
||||
init_cam = torch.FloatTensor([[1, 0, 0]])
|
||||
else:
|
||||
mean_params = np.load(smpl_mean_params)
|
||||
init_pose = torch.from_numpy(
|
||||
mean_params['pose'][:]).unsqueeze(0).float()
|
||||
init_shape = torch.from_numpy(
|
||||
mean_params['shape'][:]).unsqueeze(0).float()
|
||||
init_cam = torch.from_numpy(
|
||||
mean_params['cam']).unsqueeze(0).float()
|
||||
self.register_buffer('init_pose', init_pose)
|
||||
self.register_buffer('init_shape', init_shape)
|
||||
self.register_buffer('init_cam', init_cam)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function.
|
||||
|
||||
x is the image feature map and is expected to be in shape (batch size x
|
||||
channel number x height x width)
|
||||
"""
|
||||
batch_size = x.shape[0]
|
||||
# extract the global feature vector by average along
|
||||
# spatial dimension.
|
||||
x = x.mean(dim=-1).mean(dim=-1)
|
||||
|
||||
init_pose = self.init_pose.expand(batch_size, -1)
|
||||
init_shape = self.init_shape.expand(batch_size, -1)
|
||||
init_cam = self.init_cam.expand(batch_size, -1)
|
||||
|
||||
pred_pose = init_pose
|
||||
pred_shape = init_shape
|
||||
pred_cam = init_cam
|
||||
for _ in range(self.n_iter):
|
||||
xc = torch.cat([x, pred_pose, pred_shape, pred_cam], 1)
|
||||
xc = self.fc1(xc)
|
||||
xc = self.drop1(xc)
|
||||
xc = self.fc2(xc)
|
||||
xc = self.drop2(xc)
|
||||
pred_pose = self.decpose(xc) + pred_pose
|
||||
pred_shape = self.decshape(xc) + pred_shape
|
||||
pred_cam = self.deccam(xc) + pred_cam
|
||||
|
||||
pred_rotmat = rot6d_to_rotmat(pred_pose).view(batch_size, 24, 3, 3)
|
||||
out = (pred_rotmat, pred_shape, pred_cam)
|
||||
return out
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize model weights."""
|
||||
xavier_init(self.decpose, gain=0.01)
|
||||
xavier_init(self.decshape, gain=0.01)
|
||||
xavier_init(self.deccam, gain=0.01)
|
||||
+521
@@ -0,0 +1,521 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmcv.cnn import (build_conv_layer, build_norm_layer, build_upsample_layer,
|
||||
constant_init, normal_init)
|
||||
|
||||
from mmpose.core.evaluation.top_down_eval import (
|
||||
keypoints_from_heatmaps3d, multilabel_classification_accuracy)
|
||||
from mmpose.core.post_processing import flip_back
|
||||
from mmpose.models.builder import build_loss
|
||||
from mmpose.models.necks import GlobalAveragePooling
|
||||
from ..builder import HEADS
|
||||
|
||||
|
||||
class Heatmap3DHead(nn.Module):
|
||||
"""Heatmap3DHead is a sub-module of Interhand3DHead, and outputs 3D
|
||||
heatmaps. Heatmap3DHead is composed of (>=0) number of deconv layers and a
|
||||
simple conv2d layer.
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels
|
||||
out_channels (int): Number of output channels
|
||||
depth_size (int): Number of depth discretization size
|
||||
num_deconv_layers (int): Number of deconv layers.
|
||||
num_deconv_layers should >= 0. Note that 0 means no deconv layers.
|
||||
num_deconv_filters (list|tuple): Number of filters.
|
||||
num_deconv_kernels (list|tuple): Kernel sizes.
|
||||
extra (dict): Configs for extra conv layers. Default: None
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
depth_size=64,
|
||||
num_deconv_layers=3,
|
||||
num_deconv_filters=(256, 256, 256),
|
||||
num_deconv_kernels=(4, 4, 4),
|
||||
extra=None):
|
||||
|
||||
super().__init__()
|
||||
|
||||
assert out_channels % depth_size == 0
|
||||
self.depth_size = depth_size
|
||||
self.in_channels = in_channels
|
||||
|
||||
if extra is not None and not isinstance(extra, dict):
|
||||
raise TypeError('extra should be dict or None.')
|
||||
|
||||
if num_deconv_layers > 0:
|
||||
self.deconv_layers = self._make_deconv_layer(
|
||||
num_deconv_layers,
|
||||
num_deconv_filters,
|
||||
num_deconv_kernels,
|
||||
)
|
||||
elif num_deconv_layers == 0:
|
||||
self.deconv_layers = nn.Identity()
|
||||
else:
|
||||
raise ValueError(
|
||||
f'num_deconv_layers ({num_deconv_layers}) should >= 0.')
|
||||
|
||||
identity_final_layer = False
|
||||
if extra is not None and 'final_conv_kernel' in extra:
|
||||
assert extra['final_conv_kernel'] in [0, 1, 3]
|
||||
if extra['final_conv_kernel'] == 3:
|
||||
padding = 1
|
||||
elif extra['final_conv_kernel'] == 1:
|
||||
padding = 0
|
||||
else:
|
||||
# 0 for Identity mapping.
|
||||
identity_final_layer = True
|
||||
kernel_size = extra['final_conv_kernel']
|
||||
else:
|
||||
kernel_size = 1
|
||||
padding = 0
|
||||
|
||||
if identity_final_layer:
|
||||
self.final_layer = nn.Identity()
|
||||
else:
|
||||
conv_channels = num_deconv_filters[
|
||||
-1] if num_deconv_layers > 0 else self.in_channels
|
||||
|
||||
layers = []
|
||||
if extra is not None:
|
||||
num_conv_layers = extra.get('num_conv_layers', 0)
|
||||
num_conv_kernels = extra.get('num_conv_kernels',
|
||||
[1] * num_conv_layers)
|
||||
|
||||
for i in range(num_conv_layers):
|
||||
layers.append(
|
||||
build_conv_layer(
|
||||
dict(type='Conv2d'),
|
||||
in_channels=conv_channels,
|
||||
out_channels=conv_channels,
|
||||
kernel_size=num_conv_kernels[i],
|
||||
stride=1,
|
||||
padding=(num_conv_kernels[i] - 1) // 2))
|
||||
layers.append(
|
||||
build_norm_layer(dict(type='BN'), conv_channels)[1])
|
||||
layers.append(nn.ReLU(inplace=True))
|
||||
|
||||
layers.append(
|
||||
build_conv_layer(
|
||||
cfg=dict(type='Conv2d'),
|
||||
in_channels=conv_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=1,
|
||||
padding=padding))
|
||||
|
||||
if len(layers) > 1:
|
||||
self.final_layer = nn.Sequential(*layers)
|
||||
else:
|
||||
self.final_layer = layers[0]
|
||||
|
||||
def _make_deconv_layer(self, num_layers, num_filters, num_kernels):
|
||||
"""Make deconv layers."""
|
||||
if num_layers != len(num_filters):
|
||||
error_msg = f'num_layers({num_layers}) ' \
|
||||
f'!= length of num_filters({len(num_filters)})'
|
||||
raise ValueError(error_msg)
|
||||
if num_layers != len(num_kernels):
|
||||
error_msg = f'num_layers({num_layers}) ' \
|
||||
f'!= length of num_kernels({len(num_kernels)})'
|
||||
raise ValueError(error_msg)
|
||||
|
||||
layers = []
|
||||
for i in range(num_layers):
|
||||
kernel, padding, output_padding = \
|
||||
self._get_deconv_cfg(num_kernels[i])
|
||||
|
||||
planes = num_filters[i]
|
||||
layers.append(
|
||||
build_upsample_layer(
|
||||
dict(type='deconv'),
|
||||
in_channels=self.in_channels,
|
||||
out_channels=planes,
|
||||
kernel_size=kernel,
|
||||
stride=2,
|
||||
padding=padding,
|
||||
output_padding=output_padding,
|
||||
bias=False))
|
||||
layers.append(nn.BatchNorm2d(planes))
|
||||
layers.append(nn.ReLU(inplace=True))
|
||||
self.in_channels = planes
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
@staticmethod
|
||||
def _get_deconv_cfg(deconv_kernel):
|
||||
"""Get configurations for deconv layers."""
|
||||
if deconv_kernel == 4:
|
||||
padding = 1
|
||||
output_padding = 0
|
||||
elif deconv_kernel == 3:
|
||||
padding = 1
|
||||
output_padding = 1
|
||||
elif deconv_kernel == 2:
|
||||
padding = 0
|
||||
output_padding = 0
|
||||
else:
|
||||
raise ValueError(f'Not supported num_kernels ({deconv_kernel}).')
|
||||
|
||||
return deconv_kernel, padding, output_padding
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
x = self.deconv_layers(x)
|
||||
x = self.final_layer(x)
|
||||
N, C, H, W = x.shape
|
||||
# reshape the 2D heatmap to 3D heatmap
|
||||
x = x.reshape(N, C // self.depth_size, self.depth_size, H, W)
|
||||
return x
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize model weights."""
|
||||
for _, m in self.deconv_layers.named_modules():
|
||||
if isinstance(m, nn.ConvTranspose2d):
|
||||
normal_init(m, std=0.001)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
constant_init(m, 1)
|
||||
for m in self.final_layer.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
normal_init(m, std=0.001, bias=0)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
constant_init(m, 1)
|
||||
|
||||
|
||||
class Heatmap1DHead(nn.Module):
|
||||
"""Heatmap1DHead is a sub-module of Interhand3DHead, and outputs 1D
|
||||
heatmaps.
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels
|
||||
heatmap_size (int): Heatmap size
|
||||
hidden_dims (list|tuple): Number of feature dimension of FC layers.
|
||||
"""
|
||||
|
||||
def __init__(self, in_channels=2048, heatmap_size=64, hidden_dims=(512, )):
|
||||
super().__init__()
|
||||
|
||||
self.in_channels = in_channels
|
||||
self.heatmap_size = heatmap_size
|
||||
|
||||
feature_dims = [in_channels, *hidden_dims, heatmap_size]
|
||||
self.fc = self._make_linear_layers(feature_dims, relu_final=False)
|
||||
|
||||
def soft_argmax_1d(self, heatmap1d):
|
||||
heatmap1d = F.softmax(heatmap1d, 1)
|
||||
accu = heatmap1d * torch.arange(
|
||||
self.heatmap_size, dtype=heatmap1d.dtype,
|
||||
device=heatmap1d.device)[None, :]
|
||||
coord = accu.sum(dim=1)
|
||||
return coord
|
||||
|
||||
def _make_linear_layers(self, feat_dims, relu_final=False):
|
||||
"""Make linear layers."""
|
||||
layers = []
|
||||
for i in range(len(feat_dims) - 1):
|
||||
layers.append(nn.Linear(feat_dims[i], feat_dims[i + 1]))
|
||||
if i < len(feat_dims) - 2 or \
|
||||
(i == len(feat_dims) - 2 and relu_final):
|
||||
layers.append(nn.ReLU(inplace=True))
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
heatmap1d = self.fc(x)
|
||||
value = self.soft_argmax_1d(heatmap1d).view(-1, 1)
|
||||
return value
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize model weights."""
|
||||
for m in self.fc.modules():
|
||||
if isinstance(m, nn.Linear):
|
||||
normal_init(m, mean=0, std=0.01, bias=0)
|
||||
|
||||
|
||||
class MultilabelClassificationHead(nn.Module):
|
||||
"""MultilabelClassificationHead is a sub-module of Interhand3DHead, and
|
||||
outputs hand type classification.
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels
|
||||
num_labels (int): Number of labels
|
||||
hidden_dims (list|tuple): Number of hidden dimension of FC layers.
|
||||
"""
|
||||
|
||||
def __init__(self, in_channels=2048, num_labels=2, hidden_dims=(512, )):
|
||||
super().__init__()
|
||||
|
||||
self.in_channels = in_channels
|
||||
self.num_labesl = num_labels
|
||||
|
||||
feature_dims = [in_channels, *hidden_dims, num_labels]
|
||||
self.fc = self._make_linear_layers(feature_dims, relu_final=False)
|
||||
|
||||
def _make_linear_layers(self, feat_dims, relu_final=False):
|
||||
"""Make linear layers."""
|
||||
layers = []
|
||||
for i in range(len(feat_dims) - 1):
|
||||
layers.append(nn.Linear(feat_dims[i], feat_dims[i + 1]))
|
||||
if i < len(feat_dims) - 2 or \
|
||||
(i == len(feat_dims) - 2 and relu_final):
|
||||
layers.append(nn.ReLU(inplace=True))
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
labels = torch.sigmoid(self.fc(x))
|
||||
return labels
|
||||
|
||||
def init_weights(self):
|
||||
for m in self.fc.modules():
|
||||
if isinstance(m, nn.Linear):
|
||||
normal_init(m, mean=0, std=0.01, bias=0)
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class Interhand3DHead(nn.Module):
|
||||
"""Interhand 3D head of paper ref: Gyeongsik Moon. "InterHand2.6M: A
|
||||
Dataset and Baseline for 3D Interacting Hand Pose Estimation from a Single
|
||||
RGB Image".
|
||||
|
||||
Args:
|
||||
keypoint_head_cfg (dict): Configs of Heatmap3DHead for hand
|
||||
keypoint estimation.
|
||||
root_head_cfg (dict): Configs of Heatmap1DHead for relative
|
||||
hand root depth estimation.
|
||||
hand_type_head_cfg (dict): Configs of MultilabelClassificationHead
|
||||
for hand type classification.
|
||||
loss_keypoint (dict): Config for keypoint loss. Default: None.
|
||||
loss_root_depth (dict): Config for relative root depth loss.
|
||||
Default: None.
|
||||
loss_hand_type (dict): Config for hand type classification
|
||||
loss. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
keypoint_head_cfg,
|
||||
root_head_cfg,
|
||||
hand_type_head_cfg,
|
||||
loss_keypoint=None,
|
||||
loss_root_depth=None,
|
||||
loss_hand_type=None,
|
||||
train_cfg=None,
|
||||
test_cfg=None):
|
||||
super().__init__()
|
||||
|
||||
# build sub-module heads
|
||||
self.right_hand_head = Heatmap3DHead(**keypoint_head_cfg)
|
||||
self.left_hand_head = Heatmap3DHead(**keypoint_head_cfg)
|
||||
self.root_head = Heatmap1DHead(**root_head_cfg)
|
||||
self.hand_type_head = MultilabelClassificationHead(
|
||||
**hand_type_head_cfg)
|
||||
self.neck = GlobalAveragePooling()
|
||||
|
||||
# build losses
|
||||
self.keypoint_loss = build_loss(loss_keypoint)
|
||||
self.root_depth_loss = build_loss(loss_root_depth)
|
||||
self.hand_type_loss = build_loss(loss_hand_type)
|
||||
self.train_cfg = {} if train_cfg is None else train_cfg
|
||||
self.test_cfg = {} if test_cfg is None else test_cfg
|
||||
self.target_type = self.test_cfg.get('target_type', 'GaussianHeatmap')
|
||||
|
||||
def init_weights(self):
|
||||
self.left_hand_head.init_weights()
|
||||
self.right_hand_head.init_weights()
|
||||
self.root_head.init_weights()
|
||||
self.hand_type_head.init_weights()
|
||||
|
||||
def get_loss(self, output, target, target_weight):
|
||||
"""Calculate loss for hand keypoint heatmaps, relative root depth and
|
||||
hand type.
|
||||
|
||||
Args:
|
||||
output (list[Tensor]): a list of outputs from multiple heads.
|
||||
target (list[Tensor]): a list of targets for multiple heads.
|
||||
target_weight (list[Tensor]): a list of targets weight for
|
||||
multiple heads.
|
||||
"""
|
||||
losses = dict()
|
||||
|
||||
# hand keypoint loss
|
||||
assert not isinstance(self.keypoint_loss, nn.Sequential)
|
||||
out, tar, tar_weight = output[0], target[0], target_weight[0]
|
||||
assert tar.dim() == 5 and tar_weight.dim() == 3
|
||||
losses['hand_loss'] = self.keypoint_loss(out, tar, tar_weight)
|
||||
|
||||
# relative root depth loss
|
||||
assert not isinstance(self.root_depth_loss, nn.Sequential)
|
||||
out, tar, tar_weight = output[1], target[1], target_weight[1]
|
||||
assert tar.dim() == 2 and tar_weight.dim() == 2
|
||||
losses['rel_root_loss'] = self.root_depth_loss(out, tar, tar_weight)
|
||||
|
||||
# hand type loss
|
||||
assert not isinstance(self.hand_type_loss, nn.Sequential)
|
||||
out, tar, tar_weight = output[2], target[2], target_weight[2]
|
||||
assert tar.dim() == 2 and tar_weight.dim() in [1, 2]
|
||||
losses['hand_type_loss'] = self.hand_type_loss(out, tar, tar_weight)
|
||||
|
||||
return losses
|
||||
|
||||
def get_accuracy(self, output, target, target_weight):
|
||||
"""Calculate accuracy for hand type.
|
||||
|
||||
Args:
|
||||
output (list[Tensor]): a list of outputs from multiple heads.
|
||||
target (list[Tensor]): a list of targets for multiple heads.
|
||||
target_weight (list[Tensor]): a list of targets weight for
|
||||
multiple heads.
|
||||
"""
|
||||
accuracy = dict()
|
||||
avg_acc = multilabel_classification_accuracy(
|
||||
output[2].detach().cpu().numpy(),
|
||||
target[2].detach().cpu().numpy(),
|
||||
target_weight[2].detach().cpu().numpy(),
|
||||
)
|
||||
accuracy['acc_classification'] = float(avg_acc)
|
||||
return accuracy
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
outputs = []
|
||||
outputs.append(
|
||||
torch.cat([self.right_hand_head(x),
|
||||
self.left_hand_head(x)], dim=1))
|
||||
x = self.neck(x)
|
||||
outputs.append(self.root_head(x))
|
||||
outputs.append(self.hand_type_head(x))
|
||||
return outputs
|
||||
|
||||
def inference_model(self, x, flip_pairs=None):
|
||||
"""Inference function.
|
||||
|
||||
Returns:
|
||||
output (list[np.ndarray]): list of output hand keypoint
|
||||
heatmaps, relative root depth and hand type.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor[N,K,H,W]): Input features.
|
||||
flip_pairs (None | list[tuple()):
|
||||
Pairs of keypoints which are mirrored.
|
||||
"""
|
||||
|
||||
output = self.forward(x)
|
||||
|
||||
if flip_pairs is not None:
|
||||
# flip 3D heatmap
|
||||
heatmap_3d = output[0]
|
||||
N, K, D, H, W = heatmap_3d.shape
|
||||
# reshape 3D heatmap to 2D heatmap
|
||||
heatmap_3d = heatmap_3d.reshape(N, K * D, H, W)
|
||||
# 2D heatmap flip
|
||||
heatmap_3d_flipped_back = flip_back(
|
||||
heatmap_3d.detach().cpu().numpy(),
|
||||
flip_pairs,
|
||||
target_type=self.target_type)
|
||||
# reshape back to 3D heatmap
|
||||
heatmap_3d_flipped_back = heatmap_3d_flipped_back.reshape(
|
||||
N, K, D, H, W)
|
||||
# feature is not aligned, shift flipped heatmap for higher accuracy
|
||||
if self.test_cfg.get('shift_heatmap', False):
|
||||
heatmap_3d_flipped_back[...,
|
||||
1:] = heatmap_3d_flipped_back[..., :-1]
|
||||
output[0] = heatmap_3d_flipped_back
|
||||
|
||||
# flip relative hand root depth
|
||||
output[1] = -output[1].detach().cpu().numpy()
|
||||
|
||||
# flip hand type
|
||||
hand_type = output[2].detach().cpu().numpy()
|
||||
hand_type_flipped_back = hand_type.copy()
|
||||
hand_type_flipped_back[:, 0] = hand_type[:, 1]
|
||||
hand_type_flipped_back[:, 1] = hand_type[:, 0]
|
||||
output[2] = hand_type_flipped_back
|
||||
else:
|
||||
output = [out.detach().cpu().numpy() for out in output]
|
||||
|
||||
return output
|
||||
|
||||
def decode(self, img_metas, output, **kwargs):
|
||||
"""Decode hand keypoint, relative root depth and hand type.
|
||||
|
||||
Args:
|
||||
img_metas (list(dict)): Information about data augmentation
|
||||
By default this includes:
|
||||
|
||||
- "image_file: path to the image file
|
||||
- "center": center of the bbox
|
||||
- "scale": scale of the bbox
|
||||
- "rotation": rotation of the bbox
|
||||
- "bbox_score": score of bbox
|
||||
- "heatmap3d_depth_bound": depth bound of hand keypoint
|
||||
3D heatmap
|
||||
- "root_depth_bound": depth bound of relative root depth
|
||||
1D heatmap
|
||||
output (list[np.ndarray]): model predicted 3D heatmaps, relative
|
||||
root depth and hand type.
|
||||
"""
|
||||
|
||||
batch_size = len(img_metas)
|
||||
result = {}
|
||||
|
||||
heatmap3d_depth_bound = np.ones(batch_size, dtype=np.float32)
|
||||
root_depth_bound = np.ones(batch_size, dtype=np.float32)
|
||||
center = np.zeros((batch_size, 2), dtype=np.float32)
|
||||
scale = np.zeros((batch_size, 2), dtype=np.float32)
|
||||
image_paths = []
|
||||
score = np.ones(batch_size, dtype=np.float32)
|
||||
if 'bbox_id' in img_metas[0]:
|
||||
bbox_ids = []
|
||||
else:
|
||||
bbox_ids = None
|
||||
|
||||
for i in range(batch_size):
|
||||
heatmap3d_depth_bound[i] = img_metas[i]['heatmap3d_depth_bound']
|
||||
root_depth_bound[i] = img_metas[i]['root_depth_bound']
|
||||
center[i, :] = img_metas[i]['center']
|
||||
scale[i, :] = img_metas[i]['scale']
|
||||
image_paths.append(img_metas[i]['image_file'])
|
||||
|
||||
if 'bbox_score' in img_metas[i]:
|
||||
score[i] = np.array(img_metas[i]['bbox_score']).reshape(-1)
|
||||
if bbox_ids is not None:
|
||||
bbox_ids.append(img_metas[i]['bbox_id'])
|
||||
|
||||
all_boxes = np.zeros((batch_size, 6), dtype=np.float32)
|
||||
all_boxes[:, 0:2] = center[:, 0:2]
|
||||
all_boxes[:, 2:4] = scale[:, 0:2]
|
||||
# scale is defined as: bbox_size / 200.0, so we
|
||||
# need multiply 200.0 to get bbox size
|
||||
all_boxes[:, 4] = np.prod(scale * 200.0, axis=1)
|
||||
all_boxes[:, 5] = score
|
||||
result['boxes'] = all_boxes
|
||||
result['image_paths'] = image_paths
|
||||
result['bbox_ids'] = bbox_ids
|
||||
|
||||
# decode 3D heatmaps of hand keypoints
|
||||
heatmap3d = output[0]
|
||||
preds, maxvals = keypoints_from_heatmaps3d(heatmap3d, center, scale)
|
||||
keypoints_3d = np.zeros((batch_size, preds.shape[1], 4),
|
||||
dtype=np.float32)
|
||||
keypoints_3d[:, :, 0:3] = preds[:, :, 0:3]
|
||||
keypoints_3d[:, :, 3:4] = maxvals
|
||||
# transform keypoint depth to camera space
|
||||
keypoints_3d[:, :, 2] = \
|
||||
(keypoints_3d[:, :, 2] / self.right_hand_head.depth_size - 0.5) \
|
||||
* heatmap3d_depth_bound[:, np.newaxis]
|
||||
|
||||
result['preds'] = keypoints_3d
|
||||
|
||||
# decode relative hand root depth
|
||||
# transform relative root depth to camera space
|
||||
result['rel_root_depth'] = (output[1] / self.root_head.heatmap_size -
|
||||
0.5) * root_depth_bound
|
||||
|
||||
# decode hand type
|
||||
result['hand_type'] = output[2] > 0.5
|
||||
return result
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import numpy as np
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import build_conv_layer, constant_init, kaiming_init
|
||||
from mmcv.utils.parrots_wrapper import _BatchNorm
|
||||
|
||||
from mmpose.core import (WeightNormClipHook, compute_similarity_transform,
|
||||
fliplr_regression)
|
||||
from mmpose.models.builder import HEADS, build_loss
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class TemporalRegressionHead(nn.Module):
|
||||
"""Regression head of VideoPose3D.
|
||||
|
||||
"3D human pose estimation in video with temporal convolutions and
|
||||
semi-supervised training", CVPR'2019.
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels
|
||||
num_joints (int): Number of joints
|
||||
loss_keypoint (dict): Config for keypoint loss. Default: None.
|
||||
max_norm (float|None): if not None, the weight of convolution layers
|
||||
will be clipped to have a maximum norm of max_norm.
|
||||
is_trajectory (bool): If the model only predicts root joint
|
||||
position, then this arg should be set to True. In this case,
|
||||
traj_loss will be calculated. Otherwise, it should be set to
|
||||
False. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
num_joints,
|
||||
max_norm=None,
|
||||
loss_keypoint=None,
|
||||
is_trajectory=False,
|
||||
train_cfg=None,
|
||||
test_cfg=None):
|
||||
super().__init__()
|
||||
|
||||
self.in_channels = in_channels
|
||||
self.num_joints = num_joints
|
||||
self.max_norm = max_norm
|
||||
self.loss = build_loss(loss_keypoint)
|
||||
self.is_trajectory = is_trajectory
|
||||
if self.is_trajectory:
|
||||
assert self.num_joints == 1
|
||||
|
||||
self.train_cfg = {} if train_cfg is None else train_cfg
|
||||
self.test_cfg = {} if test_cfg is None else test_cfg
|
||||
|
||||
self.conv = build_conv_layer(
|
||||
dict(type='Conv1d'), in_channels, num_joints * 3, 1)
|
||||
|
||||
if self.max_norm is not None:
|
||||
# Apply weight norm clip to conv layers
|
||||
weight_clip = WeightNormClipHook(self.max_norm)
|
||||
for module in self.modules():
|
||||
if isinstance(module, nn.modules.conv._ConvNd):
|
||||
weight_clip.register(module)
|
||||
|
||||
@staticmethod
|
||||
def _transform_inputs(x):
|
||||
"""Transform inputs for decoder.
|
||||
|
||||
Args:
|
||||
inputs (tuple or list of Tensor | Tensor): multi-level features.
|
||||
|
||||
Returns:
|
||||
Tensor: The transformed inputs
|
||||
"""
|
||||
if not isinstance(x, (list, tuple)):
|
||||
return x
|
||||
|
||||
assert len(x) > 0
|
||||
|
||||
# return the top-level feature of the 1D feature pyramid
|
||||
return x[-1]
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
x = self._transform_inputs(x)
|
||||
|
||||
assert x.ndim == 3 and x.shape[2] == 1, f'Invalid shape {x.shape}'
|
||||
output = self.conv(x)
|
||||
N = output.shape[0]
|
||||
return output.reshape(N, self.num_joints, 3)
|
||||
|
||||
def get_loss(self, output, target, target_weight):
|
||||
"""Calculate keypoint loss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, 3]): Output keypoints.
|
||||
target (torch.Tensor[N, K, 3]): Target keypoints.
|
||||
target_weight (torch.Tensor[N, K, 3]):
|
||||
Weights across different joint types.
|
||||
If self.is_trajectory is True and target_weight is None,
|
||||
target_weight will be set inversely proportional to joint
|
||||
depth.
|
||||
"""
|
||||
losses = dict()
|
||||
assert not isinstance(self.loss, nn.Sequential)
|
||||
|
||||
# trajectory model
|
||||
if self.is_trajectory:
|
||||
if target.dim() == 2:
|
||||
target.unsqueeze_(1)
|
||||
|
||||
if target_weight is None:
|
||||
target_weight = (1 / target[:, :, 2:]).expand(target.shape)
|
||||
assert target.dim() == 3 and target_weight.dim() == 3
|
||||
|
||||
losses['traj_loss'] = self.loss(output, target, target_weight)
|
||||
|
||||
# pose model
|
||||
else:
|
||||
if target_weight is None:
|
||||
target_weight = target.new_ones(target.shape)
|
||||
assert target.dim() == 3 and target_weight.dim() == 3
|
||||
losses['reg_loss'] = self.loss(output, target, target_weight)
|
||||
|
||||
return losses
|
||||
|
||||
def get_accuracy(self, output, target, target_weight, metas):
|
||||
"""Calculate accuracy for keypoint loss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, 3]): Output keypoints.
|
||||
target (torch.Tensor[N, K, 3]): Target keypoints.
|
||||
target_weight (torch.Tensor[N, K, 3]):
|
||||
Weights across different joint types.
|
||||
metas (list(dict)): Information about data augmentation including:
|
||||
|
||||
- target_image_path (str): Optional, path to the image file
|
||||
- target_mean (float): Optional, normalization parameter of
|
||||
the target pose.
|
||||
- target_std (float): Optional, normalization parameter of the
|
||||
target pose.
|
||||
- root_position (np.ndarray[3,1]): Optional, global
|
||||
position of the root joint.
|
||||
- root_index (torch.ndarray[1,]): Optional, original index of
|
||||
the root joint before root-centering.
|
||||
"""
|
||||
|
||||
accuracy = dict()
|
||||
|
||||
N = output.shape[0]
|
||||
output_ = output.detach().cpu().numpy()
|
||||
target_ = target.detach().cpu().numpy()
|
||||
# Denormalize the predicted pose
|
||||
if 'target_mean' in metas[0] and 'target_std' in metas[0]:
|
||||
target_mean = np.stack([m['target_mean'] for m in metas])
|
||||
target_std = np.stack([m['target_std'] for m in metas])
|
||||
output_ = self._denormalize_joints(output_, target_mean,
|
||||
target_std)
|
||||
target_ = self._denormalize_joints(target_, target_mean,
|
||||
target_std)
|
||||
|
||||
# Restore global position
|
||||
if self.test_cfg.get('restore_global_position', False):
|
||||
root_pos = np.stack([m['root_position'] for m in metas])
|
||||
root_idx = metas[0].get('root_position_index', None)
|
||||
output_ = self._restore_global_position(output_, root_pos,
|
||||
root_idx)
|
||||
target_ = self._restore_global_position(target_, root_pos,
|
||||
root_idx)
|
||||
# Get target weight
|
||||
if target_weight is None:
|
||||
target_weight_ = np.ones_like(target_)
|
||||
else:
|
||||
target_weight_ = target_weight.detach().cpu().numpy()
|
||||
if self.test_cfg.get('restore_global_position', False):
|
||||
root_idx = metas[0].get('root_position_index', None)
|
||||
root_weight = metas[0].get('root_joint_weight', 1.0)
|
||||
target_weight_ = self._restore_root_target_weight(
|
||||
target_weight_, root_weight, root_idx)
|
||||
|
||||
mpjpe = np.mean(
|
||||
np.linalg.norm((output_ - target_) * target_weight_, axis=-1))
|
||||
|
||||
transformed_output = np.zeros_like(output_)
|
||||
for i in range(N):
|
||||
transformed_output[i, :, :] = compute_similarity_transform(
|
||||
output_[i, :, :], target_[i, :, :])
|
||||
p_mpjpe = np.mean(
|
||||
np.linalg.norm(
|
||||
(transformed_output - target_) * target_weight_, axis=-1))
|
||||
|
||||
accuracy['mpjpe'] = output.new_tensor(mpjpe)
|
||||
accuracy['p_mpjpe'] = output.new_tensor(p_mpjpe)
|
||||
|
||||
return accuracy
|
||||
|
||||
def inference_model(self, x, flip_pairs=None):
|
||||
"""Inference function.
|
||||
|
||||
Returns:
|
||||
output_regression (np.ndarray): Output regression.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor[N, K, 2]): Input features.
|
||||
flip_pairs (None | list[tuple()):
|
||||
Pairs of keypoints which are mirrored.
|
||||
"""
|
||||
output = self.forward(x)
|
||||
|
||||
if flip_pairs is not None:
|
||||
output_regression = fliplr_regression(
|
||||
output.detach().cpu().numpy(),
|
||||
flip_pairs,
|
||||
center_mode='static',
|
||||
center_x=0)
|
||||
else:
|
||||
output_regression = output.detach().cpu().numpy()
|
||||
return output_regression
|
||||
|
||||
def decode(self, metas, output):
|
||||
"""Decode the keypoints from output regression.
|
||||
|
||||
Args:
|
||||
metas (list(dict)): Information about data augmentation.
|
||||
By default this includes:
|
||||
|
||||
- "target_image_path": path to the image file
|
||||
output (np.ndarray[N, K, 3]): predicted regression vector.
|
||||
metas (list(dict)): Information about data augmentation including:
|
||||
|
||||
- target_image_path (str): Optional, path to the image file
|
||||
- target_mean (float): Optional, normalization parameter of
|
||||
the target pose.
|
||||
- target_std (float): Optional, normalization parameter of the
|
||||
target pose.
|
||||
- root_position (np.ndarray[3,1]): Optional, global
|
||||
position of the root joint.
|
||||
- root_index (torch.ndarray[1,]): Optional, original index of
|
||||
the root joint before root-centering.
|
||||
"""
|
||||
|
||||
# Denormalize the predicted pose
|
||||
if 'target_mean' in metas[0] and 'target_std' in metas[0]:
|
||||
target_mean = np.stack([m['target_mean'] for m in metas])
|
||||
target_std = np.stack([m['target_std'] for m in metas])
|
||||
output = self._denormalize_joints(output, target_mean, target_std)
|
||||
|
||||
# Restore global position
|
||||
if self.test_cfg.get('restore_global_position', False):
|
||||
root_pos = np.stack([m['root_position'] for m in metas])
|
||||
root_idx = metas[0].get('root_position_index', None)
|
||||
output = self._restore_global_position(output, root_pos, root_idx)
|
||||
|
||||
target_image_paths = [m.get('target_image_path', None) for m in metas]
|
||||
result = {'preds': output, 'target_image_paths': target_image_paths}
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _denormalize_joints(x, mean, std):
|
||||
"""Denormalize joint coordinates with given statistics mean and std.
|
||||
|
||||
Args:
|
||||
x (np.ndarray[N, K, 3]): Normalized joint coordinates.
|
||||
mean (np.ndarray[K, 3]): Mean value.
|
||||
std (np.ndarray[K, 3]): Std value.
|
||||
"""
|
||||
assert x.ndim == 3
|
||||
assert x.shape == mean.shape == std.shape
|
||||
|
||||
return x * std + mean
|
||||
|
||||
@staticmethod
|
||||
def _restore_global_position(x, root_pos, root_idx=None):
|
||||
"""Restore global position of the root-centered joints.
|
||||
|
||||
Args:
|
||||
x (np.ndarray[N, K, 3]): root-centered joint coordinates
|
||||
root_pos (np.ndarray[N,1,3]): The global position of the
|
||||
root joint.
|
||||
root_idx (int|None): If not none, the root joint will be inserted
|
||||
back to the pose at the given index.
|
||||
"""
|
||||
x = x + root_pos
|
||||
if root_idx is not None:
|
||||
x = np.insert(x, root_idx, root_pos.squeeze(1), axis=1)
|
||||
return x
|
||||
|
||||
@staticmethod
|
||||
def _restore_root_target_weight(target_weight, root_weight, root_idx=None):
|
||||
"""Restore the target weight of the root joint after the restoration of
|
||||
the global position.
|
||||
|
||||
Args:
|
||||
target_weight (np.ndarray[N, K, 1]): Target weight of relativized
|
||||
joints.
|
||||
root_weight (float): The target weight value of the root joint.
|
||||
root_idx (int|None): If not none, the root joint weight will be
|
||||
inserted back to the target weight at the given index.
|
||||
"""
|
||||
if root_idx is not None:
|
||||
root_weight = np.full(
|
||||
target_weight.shape[0], root_weight, dtype=target_weight.dtype)
|
||||
target_weight = np.insert(
|
||||
target_weight, root_idx, root_weight[:, None], axis=1)
|
||||
return target_weight
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize the weights."""
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.modules.conv._ConvNd):
|
||||
kaiming_init(m, mode='fan_in', nonlinearity='relu')
|
||||
elif isinstance(m, _BatchNorm):
|
||||
constant_init(m, 1)
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import numpy as np
|
||||
import torch.nn as nn
|
||||
|
||||
from mmpose.core.evaluation.top_down_eval import keypoints_from_heatmaps
|
||||
|
||||
|
||||
class TopdownHeatmapBaseHead(nn.Module):
|
||||
"""Base class for top-down heatmap heads.
|
||||
|
||||
All top-down heatmap heads should subclass it.
|
||||
All subclass should overwrite:
|
||||
|
||||
Methods:`get_loss`, supporting to calculate loss.
|
||||
Methods:`get_accuracy`, supporting to calculate accuracy.
|
||||
Methods:`forward`, supporting to forward model.
|
||||
Methods:`inference_model`, supporting to inference model.
|
||||
"""
|
||||
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def get_loss(self, **kwargs):
|
||||
"""Gets the loss."""
|
||||
|
||||
@abstractmethod
|
||||
def get_accuracy(self, **kwargs):
|
||||
"""Gets the accuracy."""
|
||||
|
||||
@abstractmethod
|
||||
def forward(self, **kwargs):
|
||||
"""Forward function."""
|
||||
|
||||
@abstractmethod
|
||||
def inference_model(self, **kwargs):
|
||||
"""Inference function."""
|
||||
|
||||
def decode(self, img_metas, output, **kwargs):
|
||||
"""Decode keypoints from heatmaps.
|
||||
|
||||
Args:
|
||||
img_metas (list(dict)): Information about data augmentation
|
||||
By default this includes:
|
||||
|
||||
- "image_file: path to the image file
|
||||
- "center": center of the bbox
|
||||
- "scale": scale of the bbox
|
||||
- "rotation": rotation of the bbox
|
||||
- "bbox_score": score of bbox
|
||||
output (np.ndarray[N, K, H, W]): model predicted heatmaps.
|
||||
"""
|
||||
batch_size = len(img_metas)
|
||||
|
||||
if 'bbox_id' in img_metas[0]:
|
||||
bbox_ids = []
|
||||
else:
|
||||
bbox_ids = None
|
||||
|
||||
c = np.zeros((batch_size, 2), dtype=np.float32)
|
||||
s = np.zeros((batch_size, 2), dtype=np.float32)
|
||||
image_paths = []
|
||||
score = np.ones(batch_size)
|
||||
for i in range(batch_size):
|
||||
c[i, :] = img_metas[i]['center']
|
||||
s[i, :] = img_metas[i]['scale']
|
||||
image_paths.append(img_metas[i]['image_file'])
|
||||
|
||||
if 'bbox_score' in img_metas[i]:
|
||||
score[i] = np.array(img_metas[i]['bbox_score']).reshape(-1)
|
||||
if bbox_ids is not None:
|
||||
bbox_ids.append(img_metas[i]['bbox_id'])
|
||||
|
||||
preds, maxvals = keypoints_from_heatmaps(
|
||||
output,
|
||||
c,
|
||||
s,
|
||||
unbiased=self.test_cfg.get('unbiased_decoding', False),
|
||||
post_process=self.test_cfg.get('post_process', 'default'),
|
||||
kernel=self.test_cfg.get('modulate_kernel', 11),
|
||||
valid_radius_factor=self.test_cfg.get('valid_radius_factor',
|
||||
0.0546875),
|
||||
use_udp=self.test_cfg.get('use_udp', False),
|
||||
target_type=self.test_cfg.get('target_type', 'GaussianHeatmap'))
|
||||
|
||||
all_preds = np.zeros((batch_size, preds.shape[1], 3), dtype=np.float32)
|
||||
all_boxes = np.zeros((batch_size, 6), dtype=np.float32)
|
||||
all_preds[:, :, 0:2] = preds[:, :, 0:2]
|
||||
all_preds[:, :, 2:3] = maxvals
|
||||
all_boxes[:, 0:2] = c[:, 0:2]
|
||||
all_boxes[:, 2:4] = s[:, 0:2]
|
||||
all_boxes[:, 4] = np.prod(s * 200.0, axis=1)
|
||||
all_boxes[:, 5] = score
|
||||
|
||||
result = {}
|
||||
|
||||
result['preds'] = all_preds
|
||||
result['boxes'] = all_boxes
|
||||
result['image_paths'] = image_paths
|
||||
result['bbox_ids'] = bbox_ids
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _get_deconv_cfg(deconv_kernel):
|
||||
"""Get configurations for deconv layers."""
|
||||
if deconv_kernel == 4:
|
||||
padding = 1
|
||||
output_padding = 0
|
||||
elif deconv_kernel == 3:
|
||||
padding = 1
|
||||
output_padding = 1
|
||||
elif deconv_kernel == 2:
|
||||
padding = 0
|
||||
output_padding = 0
|
||||
else:
|
||||
raise ValueError(f'Not supported num_kernels ({deconv_kernel}).')
|
||||
|
||||
return deconv_kernel, padding, output_padding
|
||||
Vendored
+572
@@ -0,0 +1,572 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy as cp
|
||||
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import (ConvModule, DepthwiseSeparableConvModule, Linear,
|
||||
build_activation_layer, build_conv_layer,
|
||||
build_norm_layer, build_upsample_layer, constant_init,
|
||||
kaiming_init, normal_init)
|
||||
|
||||
from mmpose.core.evaluation import pose_pck_accuracy
|
||||
from mmpose.core.post_processing import flip_back
|
||||
from mmpose.models.builder import build_loss
|
||||
from ..builder import HEADS
|
||||
from .topdown_heatmap_base_head import TopdownHeatmapBaseHead
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class TopdownHeatmapMultiStageHead(TopdownHeatmapBaseHead):
|
||||
"""Top-down heatmap multi-stage head.
|
||||
|
||||
TopdownHeatmapMultiStageHead is consisted of multiple branches,
|
||||
each of which has num_deconv_layers(>=0) number of deconv layers
|
||||
and a simple conv2d layer.
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels.
|
||||
out_channels (int): Number of output channels.
|
||||
num_stages (int): Number of stages.
|
||||
num_deconv_layers (int): Number of deconv layers.
|
||||
num_deconv_layers should >= 0. Note that 0 means
|
||||
no deconv layers.
|
||||
num_deconv_filters (list|tuple): Number of filters.
|
||||
If num_deconv_layers > 0, the length of
|
||||
num_deconv_kernels (list|tuple): Kernel sizes.
|
||||
loss_keypoint (dict): Config for keypoint loss. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels=512,
|
||||
out_channels=17,
|
||||
num_stages=1,
|
||||
num_deconv_layers=3,
|
||||
num_deconv_filters=(256, 256, 256),
|
||||
num_deconv_kernels=(4, 4, 4),
|
||||
extra=None,
|
||||
loss_keypoint=None,
|
||||
train_cfg=None,
|
||||
test_cfg=None):
|
||||
super().__init__()
|
||||
|
||||
self.in_channels = in_channels
|
||||
self.num_stages = num_stages
|
||||
self.loss = build_loss(loss_keypoint)
|
||||
|
||||
self.train_cfg = {} if train_cfg is None else train_cfg
|
||||
self.test_cfg = {} if test_cfg is None else test_cfg
|
||||
self.target_type = self.test_cfg.get('target_type', 'GaussianHeatmap')
|
||||
|
||||
if extra is not None and not isinstance(extra, dict):
|
||||
raise TypeError('extra should be dict or None.')
|
||||
|
||||
# build multi-stage deconv layers
|
||||
self.multi_deconv_layers = nn.ModuleList([])
|
||||
for _ in range(self.num_stages):
|
||||
if num_deconv_layers > 0:
|
||||
deconv_layers = self._make_deconv_layer(
|
||||
num_deconv_layers,
|
||||
num_deconv_filters,
|
||||
num_deconv_kernels,
|
||||
)
|
||||
elif num_deconv_layers == 0:
|
||||
deconv_layers = nn.Identity()
|
||||
else:
|
||||
raise ValueError(
|
||||
f'num_deconv_layers ({num_deconv_layers}) should >= 0.')
|
||||
self.multi_deconv_layers.append(deconv_layers)
|
||||
|
||||
identity_final_layer = False
|
||||
if extra is not None and 'final_conv_kernel' in extra:
|
||||
assert extra['final_conv_kernel'] in [0, 1, 3]
|
||||
if extra['final_conv_kernel'] == 3:
|
||||
padding = 1
|
||||
elif extra['final_conv_kernel'] == 1:
|
||||
padding = 0
|
||||
else:
|
||||
# 0 for Identity mapping.
|
||||
identity_final_layer = True
|
||||
kernel_size = extra['final_conv_kernel']
|
||||
else:
|
||||
kernel_size = 1
|
||||
padding = 0
|
||||
|
||||
# build multi-stage final layers
|
||||
self.multi_final_layers = nn.ModuleList([])
|
||||
for i in range(self.num_stages):
|
||||
if identity_final_layer:
|
||||
final_layer = nn.Identity()
|
||||
else:
|
||||
final_layer = build_conv_layer(
|
||||
cfg=dict(type='Conv2d'),
|
||||
in_channels=num_deconv_filters[-1]
|
||||
if num_deconv_layers > 0 else in_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=1,
|
||||
padding=padding)
|
||||
self.multi_final_layers.append(final_layer)
|
||||
|
||||
def get_loss(self, output, target, target_weight):
|
||||
"""Calculate top-down keypoint loss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- num_outputs: O
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N,K,H,W]):
|
||||
Output heatmaps.
|
||||
target (torch.Tensor[N,K,H,W]):
|
||||
Target heatmaps.
|
||||
target_weight (torch.Tensor[N,K,1]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
|
||||
losses = dict()
|
||||
|
||||
assert isinstance(output, list)
|
||||
assert target.dim() == 4 and target_weight.dim() == 3
|
||||
|
||||
if isinstance(self.loss, nn.Sequential):
|
||||
assert len(self.loss) == len(output)
|
||||
for i in range(len(output)):
|
||||
target_i = target
|
||||
target_weight_i = target_weight
|
||||
if isinstance(self.loss, nn.Sequential):
|
||||
loss_func = self.loss[i]
|
||||
else:
|
||||
loss_func = self.loss
|
||||
loss_i = loss_func(output[i], target_i, target_weight_i)
|
||||
if 'heatmap_loss' not in losses:
|
||||
losses['heatmap_loss'] = loss_i
|
||||
else:
|
||||
losses['heatmap_loss'] += loss_i
|
||||
|
||||
return losses
|
||||
|
||||
def get_accuracy(self, output, target, target_weight):
|
||||
"""Calculate accuracy for top-down keypoint loss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N,K,H,W]): Output heatmaps.
|
||||
target (torch.Tensor[N,K,H,W]): Target heatmaps.
|
||||
target_weight (torch.Tensor[N,K,1]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
|
||||
accuracy = dict()
|
||||
|
||||
if self.target_type == 'GaussianHeatmap':
|
||||
_, avg_acc, _ = pose_pck_accuracy(
|
||||
output[-1].detach().cpu().numpy(),
|
||||
target.detach().cpu().numpy(),
|
||||
target_weight.detach().cpu().numpy().squeeze(-1) > 0)
|
||||
accuracy['acc_pose'] = float(avg_acc)
|
||||
|
||||
return accuracy
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function.
|
||||
|
||||
Returns:
|
||||
out (list[Tensor]): a list of heatmaps from multiple stages.
|
||||
"""
|
||||
out = []
|
||||
assert isinstance(x, list)
|
||||
for i in range(self.num_stages):
|
||||
y = self.multi_deconv_layers[i](x[i])
|
||||
y = self.multi_final_layers[i](y)
|
||||
out.append(y)
|
||||
return out
|
||||
|
||||
def inference_model(self, x, flip_pairs=None):
|
||||
"""Inference function.
|
||||
|
||||
Returns:
|
||||
output_heatmap (np.ndarray): Output heatmaps.
|
||||
|
||||
Args:
|
||||
x (List[torch.Tensor[NxKxHxW]]): Input features.
|
||||
flip_pairs (None | list[tuple()):
|
||||
Pairs of keypoints which are mirrored.
|
||||
"""
|
||||
output = self.forward(x)
|
||||
assert isinstance(output, list)
|
||||
output = output[-1]
|
||||
|
||||
if flip_pairs is not None:
|
||||
# perform flip
|
||||
output_heatmap = flip_back(
|
||||
output.detach().cpu().numpy(),
|
||||
flip_pairs,
|
||||
target_type=self.target_type)
|
||||
# feature is not aligned, shift flipped heatmap for higher accuracy
|
||||
if self.test_cfg.get('shift_heatmap', False):
|
||||
output_heatmap[:, :, :, 1:] = output_heatmap[:, :, :, :-1]
|
||||
else:
|
||||
output_heatmap = output.detach().cpu().numpy()
|
||||
|
||||
return output_heatmap
|
||||
|
||||
def _make_deconv_layer(self, num_layers, num_filters, num_kernels):
|
||||
"""Make deconv layers."""
|
||||
if num_layers != len(num_filters):
|
||||
error_msg = f'num_layers({num_layers}) ' \
|
||||
f'!= length of num_filters({len(num_filters)})'
|
||||
raise ValueError(error_msg)
|
||||
if num_layers != len(num_kernels):
|
||||
error_msg = f'num_layers({num_layers}) ' \
|
||||
f'!= length of num_kernels({len(num_kernels)})'
|
||||
raise ValueError(error_msg)
|
||||
|
||||
layers = []
|
||||
for i in range(num_layers):
|
||||
kernel, padding, output_padding = \
|
||||
self._get_deconv_cfg(num_kernels[i])
|
||||
|
||||
planes = num_filters[i]
|
||||
layers.append(
|
||||
build_upsample_layer(
|
||||
dict(type='deconv'),
|
||||
in_channels=self.in_channels,
|
||||
out_channels=planes,
|
||||
kernel_size=kernel,
|
||||
stride=2,
|
||||
padding=padding,
|
||||
output_padding=output_padding,
|
||||
bias=False))
|
||||
layers.append(nn.BatchNorm2d(planes))
|
||||
layers.append(nn.ReLU(inplace=True))
|
||||
self.in_channels = planes
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize model weights."""
|
||||
for _, m in self.multi_deconv_layers.named_modules():
|
||||
if isinstance(m, nn.ConvTranspose2d):
|
||||
normal_init(m, std=0.001)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
constant_init(m, 1)
|
||||
for m in self.multi_final_layers.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
normal_init(m, std=0.001, bias=0)
|
||||
|
||||
|
||||
class PredictHeatmap(nn.Module):
|
||||
"""Predict the heat map for an input feature.
|
||||
|
||||
Args:
|
||||
unit_channels (int): Number of input channels.
|
||||
out_channels (int): Number of output channels.
|
||||
out_shape (tuple): Shape of the output heatmap.
|
||||
use_prm (bool): Whether to use pose refine machine. Default: False.
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
unit_channels,
|
||||
out_channels,
|
||||
out_shape,
|
||||
use_prm=False,
|
||||
norm_cfg=dict(type='BN')):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = cp.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
self.unit_channels = unit_channels
|
||||
self.out_channels = out_channels
|
||||
self.out_shape = out_shape
|
||||
self.use_prm = use_prm
|
||||
if use_prm:
|
||||
self.prm = PRM(out_channels, norm_cfg=norm_cfg)
|
||||
self.conv_layers = nn.Sequential(
|
||||
ConvModule(
|
||||
unit_channels,
|
||||
unit_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
norm_cfg=norm_cfg,
|
||||
inplace=False),
|
||||
ConvModule(
|
||||
unit_channels,
|
||||
out_channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None,
|
||||
inplace=False))
|
||||
|
||||
def forward(self, feature):
|
||||
feature = self.conv_layers(feature)
|
||||
output = nn.functional.interpolate(
|
||||
feature, size=self.out_shape, mode='bilinear', align_corners=True)
|
||||
if self.use_prm:
|
||||
output = self.prm(output)
|
||||
return output
|
||||
|
||||
|
||||
class PRM(nn.Module):
|
||||
"""Pose Refine Machine.
|
||||
|
||||
Please refer to "Learning Delicate Local Representations
|
||||
for Multi-Person Pose Estimation" (ECCV 2020).
|
||||
|
||||
Args:
|
||||
out_channels (int): Channel number of the output. Equals to
|
||||
the number of key points.
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
"""
|
||||
|
||||
def __init__(self, out_channels, norm_cfg=dict(type='BN')):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = cp.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
self.out_channels = out_channels
|
||||
self.global_pooling = nn.AdaptiveAvgPool2d((1, 1))
|
||||
self.middle_path = nn.Sequential(
|
||||
Linear(self.out_channels, self.out_channels),
|
||||
build_norm_layer(dict(type='BN1d'), out_channels)[1],
|
||||
build_activation_layer(dict(type='ReLU')),
|
||||
Linear(self.out_channels, self.out_channels),
|
||||
build_norm_layer(dict(type='BN1d'), out_channels)[1],
|
||||
build_activation_layer(dict(type='ReLU')),
|
||||
build_activation_layer(dict(type='Sigmoid')))
|
||||
|
||||
self.bottom_path = nn.Sequential(
|
||||
ConvModule(
|
||||
self.out_channels,
|
||||
self.out_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
norm_cfg=norm_cfg,
|
||||
inplace=False),
|
||||
DepthwiseSeparableConvModule(
|
||||
self.out_channels,
|
||||
1,
|
||||
kernel_size=9,
|
||||
stride=1,
|
||||
padding=4,
|
||||
norm_cfg=norm_cfg,
|
||||
inplace=False), build_activation_layer(dict(type='Sigmoid')))
|
||||
self.conv_bn_relu_prm_1 = ConvModule(
|
||||
self.out_channels,
|
||||
self.out_channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
norm_cfg=norm_cfg,
|
||||
inplace=False)
|
||||
|
||||
def forward(self, x):
|
||||
out = self.conv_bn_relu_prm_1(x)
|
||||
out_1 = out
|
||||
|
||||
out_2 = self.global_pooling(out_1)
|
||||
out_2 = out_2.view(out_2.size(0), -1)
|
||||
out_2 = self.middle_path(out_2)
|
||||
out_2 = out_2.unsqueeze(2)
|
||||
out_2 = out_2.unsqueeze(3)
|
||||
|
||||
out_3 = self.bottom_path(out_1)
|
||||
out = out_1 * (1 + out_2 * out_3)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class TopdownHeatmapMSMUHead(TopdownHeatmapBaseHead):
|
||||
"""Heads for multi-stage multi-unit heads used in Multi-Stage Pose
|
||||
estimation Network (MSPN), and Residual Steps Networks (RSN).
|
||||
|
||||
Args:
|
||||
unit_channels (int): Number of input channels.
|
||||
out_channels (int): Number of output channels.
|
||||
out_shape (tuple): Shape of the output heatmap.
|
||||
num_stages (int): Number of stages.
|
||||
num_units (int): Number of units in each stage.
|
||||
use_prm (bool): Whether to use pose refine machine (PRM).
|
||||
Default: False.
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
loss_keypoint (dict): Config for keypoint loss. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
out_shape,
|
||||
unit_channels=256,
|
||||
out_channels=17,
|
||||
num_stages=4,
|
||||
num_units=4,
|
||||
use_prm=False,
|
||||
norm_cfg=dict(type='BN'),
|
||||
loss_keypoint=None,
|
||||
train_cfg=None,
|
||||
test_cfg=None):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = cp.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
|
||||
self.train_cfg = {} if train_cfg is None else train_cfg
|
||||
self.test_cfg = {} if test_cfg is None else test_cfg
|
||||
self.target_type = self.test_cfg.get('target_type', 'GaussianHeatmap')
|
||||
|
||||
self.out_shape = out_shape
|
||||
self.unit_channels = unit_channels
|
||||
self.out_channels = out_channels
|
||||
self.num_stages = num_stages
|
||||
self.num_units = num_units
|
||||
|
||||
self.loss = build_loss(loss_keypoint)
|
||||
|
||||
self.predict_layers = nn.ModuleList([])
|
||||
for i in range(self.num_stages):
|
||||
for j in range(self.num_units):
|
||||
self.predict_layers.append(
|
||||
PredictHeatmap(
|
||||
unit_channels,
|
||||
out_channels,
|
||||
out_shape,
|
||||
use_prm,
|
||||
norm_cfg=norm_cfg))
|
||||
|
||||
def get_loss(self, output, target, target_weight):
|
||||
"""Calculate top-down keypoint loss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- num_outputs: O
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N,O,K,H,W]): Output heatmaps.
|
||||
target (torch.Tensor[N,O,K,H,W]): Target heatmaps.
|
||||
target_weight (torch.Tensor[N,O,K,1]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
|
||||
losses = dict()
|
||||
|
||||
assert isinstance(output, list)
|
||||
assert target.dim() == 5 and target_weight.dim() == 4
|
||||
assert target.size(1) == len(output)
|
||||
|
||||
if isinstance(self.loss, nn.Sequential):
|
||||
assert len(self.loss) == len(output)
|
||||
for i in range(len(output)):
|
||||
target_i = target[:, i, :, :, :]
|
||||
target_weight_i = target_weight[:, i, :, :]
|
||||
|
||||
if isinstance(self.loss, nn.Sequential):
|
||||
loss_func = self.loss[i]
|
||||
else:
|
||||
loss_func = self.loss
|
||||
|
||||
loss_i = loss_func(output[i], target_i, target_weight_i)
|
||||
if 'heatmap_loss' not in losses:
|
||||
losses['heatmap_loss'] = loss_i
|
||||
else:
|
||||
losses['heatmap_loss'] += loss_i
|
||||
|
||||
return losses
|
||||
|
||||
def get_accuracy(self, output, target, target_weight):
|
||||
"""Calculate accuracy for top-down keypoint loss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N,K,H,W]): Output heatmaps.
|
||||
target (torch.Tensor[N,K,H,W]): Target heatmaps.
|
||||
target_weight (torch.Tensor[N,K,1]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
|
||||
accuracy = dict()
|
||||
|
||||
if self.target_type == 'GaussianHeatmap':
|
||||
assert isinstance(output, list)
|
||||
assert target.dim() == 5 and target_weight.dim() == 4
|
||||
_, avg_acc, _ = pose_pck_accuracy(
|
||||
output[-1].detach().cpu().numpy(),
|
||||
target[:, -1, ...].detach().cpu().numpy(),
|
||||
target_weight[:, -1,
|
||||
...].detach().cpu().numpy().squeeze(-1) > 0)
|
||||
accuracy['acc_pose'] = float(avg_acc)
|
||||
|
||||
return accuracy
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function.
|
||||
|
||||
Returns:
|
||||
out (list[Tensor]): a list of heatmaps from multiple stages
|
||||
and units.
|
||||
"""
|
||||
out = []
|
||||
assert isinstance(x, list)
|
||||
assert len(x) == self.num_stages
|
||||
assert isinstance(x[0], list)
|
||||
assert len(x[0]) == self.num_units
|
||||
assert x[0][0].shape[1] == self.unit_channels
|
||||
for i in range(self.num_stages):
|
||||
for j in range(self.num_units):
|
||||
y = self.predict_layers[i * self.num_units + j](x[i][j])
|
||||
out.append(y)
|
||||
|
||||
return out
|
||||
|
||||
def inference_model(self, x, flip_pairs=None):
|
||||
"""Inference function.
|
||||
|
||||
Returns:
|
||||
output_heatmap (np.ndarray): Output heatmaps.
|
||||
|
||||
Args:
|
||||
x (list[torch.Tensor[N,K,H,W]]): Input features.
|
||||
flip_pairs (None | list[tuple]):
|
||||
Pairs of keypoints which are mirrored.
|
||||
"""
|
||||
output = self.forward(x)
|
||||
assert isinstance(output, list)
|
||||
output = output[-1]
|
||||
if flip_pairs is not None:
|
||||
output_heatmap = flip_back(
|
||||
output.detach().cpu().numpy(),
|
||||
flip_pairs,
|
||||
target_type=self.target_type)
|
||||
# feature is not aligned, shift flipped heatmap for higher accuracy
|
||||
if self.test_cfg.get('shift_heatmap', False):
|
||||
output_heatmap[:, :, :, 1:] = output_heatmap[:, :, :, :-1]
|
||||
else:
|
||||
output_heatmap = output.detach().cpu().numpy()
|
||||
return output_heatmap
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize model weights."""
|
||||
for m in self.predict_layers.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
kaiming_init(m)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
constant_init(m, 1)
|
||||
elif isinstance(m, nn.Linear):
|
||||
normal_init(m, std=0.01)
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import (build_conv_layer, build_norm_layer, build_upsample_layer,
|
||||
constant_init, normal_init)
|
||||
|
||||
from mmpose.core.evaluation import pose_pck_accuracy
|
||||
from mmpose.core.post_processing import flip_back
|
||||
from mmpose.models.builder import build_loss
|
||||
from mmpose.models.utils.ops import resize
|
||||
from ..builder import HEADS
|
||||
import torch.nn.functional as F
|
||||
from .topdown_heatmap_base_head import TopdownHeatmapBaseHead
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class TopdownHeatmapSimpleHead(TopdownHeatmapBaseHead):
|
||||
"""Top-down heatmap simple head. paper ref: Bin Xiao et al. ``Simple
|
||||
Baselines for Human Pose Estimation and Tracking``.
|
||||
|
||||
TopdownHeatmapSimpleHead is consisted of (>=0) number of deconv layers
|
||||
and a simple conv2d layer.
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels
|
||||
out_channels (int): Number of output channels
|
||||
num_deconv_layers (int): Number of deconv layers.
|
||||
num_deconv_layers should >= 0. Note that 0 means
|
||||
no deconv layers.
|
||||
num_deconv_filters (list|tuple): Number of filters.
|
||||
If num_deconv_layers > 0, the length of
|
||||
num_deconv_kernels (list|tuple): Kernel sizes.
|
||||
in_index (int|Sequence[int]): Input feature index. Default: 0
|
||||
input_transform (str|None): Transformation type of input features.
|
||||
Options: 'resize_concat', 'multiple_select', None.
|
||||
Default: None.
|
||||
|
||||
- 'resize_concat': Multiple feature maps will be resized to the
|
||||
same size as the first one and then concat together.
|
||||
Usually used in FCN head of HRNet.
|
||||
- 'multiple_select': Multiple feature maps will be bundle into
|
||||
a list and passed into decode head.
|
||||
- None: Only one select feature map is allowed.
|
||||
align_corners (bool): align_corners argument of F.interpolate.
|
||||
Default: False.
|
||||
loss_keypoint (dict): Config for keypoint loss. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
num_deconv_layers=3,
|
||||
num_deconv_filters=(256, 256, 256),
|
||||
num_deconv_kernels=(4, 4, 4),
|
||||
extra=None,
|
||||
in_index=0,
|
||||
input_transform=None,
|
||||
align_corners=False,
|
||||
loss_keypoint=None,
|
||||
train_cfg=None,
|
||||
test_cfg=None,
|
||||
upsample=0,):
|
||||
super().__init__()
|
||||
|
||||
self.in_channels = in_channels
|
||||
self.loss = build_loss(loss_keypoint)
|
||||
self.upsample = upsample
|
||||
|
||||
self.train_cfg = {} if train_cfg is None else train_cfg
|
||||
self.test_cfg = {} if test_cfg is None else test_cfg
|
||||
self.target_type = self.test_cfg.get('target_type', 'GaussianHeatmap')
|
||||
|
||||
self._init_inputs(in_channels, in_index, input_transform)
|
||||
self.in_index = in_index
|
||||
self.align_corners = align_corners
|
||||
|
||||
if extra is not None and not isinstance(extra, dict):
|
||||
raise TypeError('extra should be dict or None.')
|
||||
|
||||
if num_deconv_layers > 0:
|
||||
self.deconv_layers = self._make_deconv_layer(
|
||||
num_deconv_layers,
|
||||
num_deconv_filters,
|
||||
num_deconv_kernels,
|
||||
)
|
||||
elif num_deconv_layers == 0:
|
||||
self.deconv_layers = nn.Identity()
|
||||
else:
|
||||
raise ValueError(
|
||||
f'num_deconv_layers ({num_deconv_layers}) should >= 0.')
|
||||
|
||||
identity_final_layer = False
|
||||
if extra is not None and 'final_conv_kernel' in extra:
|
||||
assert extra['final_conv_kernel'] in [0, 1, 3]
|
||||
if extra['final_conv_kernel'] == 3:
|
||||
padding = 1
|
||||
elif extra['final_conv_kernel'] == 1:
|
||||
padding = 0
|
||||
else:
|
||||
# 0 for Identity mapping.
|
||||
identity_final_layer = True
|
||||
kernel_size = extra['final_conv_kernel']
|
||||
else:
|
||||
kernel_size = 1
|
||||
padding = 0
|
||||
|
||||
if identity_final_layer:
|
||||
self.final_layer = nn.Identity()
|
||||
else:
|
||||
conv_channels = num_deconv_filters[
|
||||
-1] if num_deconv_layers > 0 else self.in_channels
|
||||
|
||||
layers = []
|
||||
if extra is not None:
|
||||
num_conv_layers = extra.get('num_conv_layers', 0)
|
||||
num_conv_kernels = extra.get('num_conv_kernels',
|
||||
[1] * num_conv_layers)
|
||||
|
||||
for i in range(num_conv_layers):
|
||||
layers.append(
|
||||
build_conv_layer(
|
||||
dict(type='Conv2d'),
|
||||
in_channels=conv_channels,
|
||||
out_channels=conv_channels,
|
||||
kernel_size=num_conv_kernels[i],
|
||||
stride=1,
|
||||
padding=(num_conv_kernels[i] - 1) // 2))
|
||||
layers.append(
|
||||
build_norm_layer(dict(type='BN'), conv_channels)[1])
|
||||
layers.append(nn.ReLU(inplace=True))
|
||||
|
||||
layers.append(
|
||||
build_conv_layer(
|
||||
cfg=dict(type='Conv2d'),
|
||||
in_channels=conv_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=1,
|
||||
padding=padding))
|
||||
|
||||
if len(layers) > 1:
|
||||
self.final_layer = nn.Sequential(*layers)
|
||||
else:
|
||||
self.final_layer = layers[0]
|
||||
|
||||
def get_loss(self, output, target, target_weight):
|
||||
"""Calculate top-down keypoint loss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N,K,H,W]): Output heatmaps.
|
||||
target (torch.Tensor[N,K,H,W]): Target heatmaps.
|
||||
target_weight (torch.Tensor[N,K,1]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
|
||||
losses = dict()
|
||||
|
||||
assert not isinstance(self.loss, nn.Sequential)
|
||||
assert target.dim() == 4 and target_weight.dim() == 3
|
||||
losses['heatmap_loss'] = self.loss(output, target, target_weight)
|
||||
|
||||
return losses
|
||||
|
||||
def get_accuracy(self, output, target, target_weight):
|
||||
"""Calculate accuracy for top-down keypoint loss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N,K,H,W]): Output heatmaps.
|
||||
target (torch.Tensor[N,K,H,W]): Target heatmaps.
|
||||
target_weight (torch.Tensor[N,K,1]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
|
||||
accuracy = dict()
|
||||
|
||||
if self.target_type == 'GaussianHeatmap':
|
||||
_, avg_acc, _ = pose_pck_accuracy(
|
||||
output.detach().cpu().numpy(),
|
||||
target.detach().cpu().numpy(),
|
||||
target_weight.detach().cpu().numpy().squeeze(-1) > 0)
|
||||
accuracy['acc_pose'] = float(avg_acc)
|
||||
|
||||
return accuracy
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
x = self._transform_inputs(x)
|
||||
x = self.deconv_layers(x)
|
||||
x = self.final_layer(x)
|
||||
return x
|
||||
|
||||
def inference_model(self, x, flip_pairs=None):
|
||||
"""Inference function.
|
||||
|
||||
Returns:
|
||||
output_heatmap (np.ndarray): Output heatmaps.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor[N,K,H,W]): Input features.
|
||||
flip_pairs (None | list[tuple]):
|
||||
Pairs of keypoints which are mirrored.
|
||||
"""
|
||||
output = self.forward(x)
|
||||
|
||||
if flip_pairs is not None:
|
||||
output_heatmap = flip_back(
|
||||
output.detach().cpu().numpy(),
|
||||
flip_pairs,
|
||||
target_type=self.target_type)
|
||||
# feature is not aligned, shift flipped heatmap for higher accuracy
|
||||
if self.test_cfg.get('shift_heatmap', False):
|
||||
output_heatmap[:, :, :, 1:] = output_heatmap[:, :, :, :-1]
|
||||
else:
|
||||
output_heatmap = output.detach().cpu().numpy()
|
||||
return output_heatmap
|
||||
|
||||
def _init_inputs(self, in_channels, in_index, input_transform):
|
||||
"""Check and initialize input transforms.
|
||||
|
||||
The in_channels, in_index and input_transform must match.
|
||||
Specifically, when input_transform is None, only single feature map
|
||||
will be selected. So in_channels and in_index must be of type int.
|
||||
When input_transform is not None, in_channels and in_index must be
|
||||
list or tuple, with the same length.
|
||||
|
||||
Args:
|
||||
in_channels (int|Sequence[int]): Input channels.
|
||||
in_index (int|Sequence[int]): Input feature index.
|
||||
input_transform (str|None): Transformation type of input features.
|
||||
Options: 'resize_concat', 'multiple_select', None.
|
||||
|
||||
- 'resize_concat': Multiple feature maps will be resize to the
|
||||
same size as first one and than concat together.
|
||||
Usually used in FCN head of HRNet.
|
||||
- 'multiple_select': Multiple feature maps will be bundle into
|
||||
a list and passed into decode head.
|
||||
- None: Only one select feature map is allowed.
|
||||
"""
|
||||
|
||||
if input_transform is not None:
|
||||
assert input_transform in ['resize_concat', 'multiple_select']
|
||||
self.input_transform = input_transform
|
||||
self.in_index = in_index
|
||||
if input_transform is not None:
|
||||
assert isinstance(in_channels, (list, tuple))
|
||||
assert isinstance(in_index, (list, tuple))
|
||||
assert len(in_channels) == len(in_index)
|
||||
if input_transform == 'resize_concat':
|
||||
self.in_channels = sum(in_channels)
|
||||
else:
|
||||
self.in_channels = in_channels
|
||||
else:
|
||||
assert isinstance(in_channels, int)
|
||||
assert isinstance(in_index, int)
|
||||
self.in_channels = in_channels
|
||||
|
||||
def _transform_inputs(self, inputs):
|
||||
"""Transform inputs for decoder.
|
||||
|
||||
Args:
|
||||
inputs (list[Tensor] | Tensor): multi-level img features.
|
||||
|
||||
Returns:
|
||||
Tensor: The transformed inputs
|
||||
"""
|
||||
if not isinstance(inputs, list):
|
||||
if not isinstance(inputs, list):
|
||||
if self.upsample > 0:
|
||||
inputs = resize(
|
||||
input=F.relu(inputs),
|
||||
scale_factor=self.upsample,
|
||||
mode='bilinear',
|
||||
align_corners=self.align_corners
|
||||
)
|
||||
return inputs
|
||||
|
||||
if self.input_transform == 'resize_concat':
|
||||
inputs = [inputs[i] for i in self.in_index]
|
||||
upsampled_inputs = [
|
||||
resize(
|
||||
input=x,
|
||||
size=inputs[0].shape[2:],
|
||||
mode='bilinear',
|
||||
align_corners=self.align_corners) for x in inputs
|
||||
]
|
||||
inputs = torch.cat(upsampled_inputs, dim=1)
|
||||
elif self.input_transform == 'multiple_select':
|
||||
inputs = [inputs[i] for i in self.in_index]
|
||||
else:
|
||||
inputs = inputs[self.in_index]
|
||||
|
||||
return inputs
|
||||
|
||||
def _make_deconv_layer(self, num_layers, num_filters, num_kernels):
|
||||
"""Make deconv layers."""
|
||||
if num_layers != len(num_filters):
|
||||
error_msg = f'num_layers({num_layers}) ' \
|
||||
f'!= length of num_filters({len(num_filters)})'
|
||||
raise ValueError(error_msg)
|
||||
if num_layers != len(num_kernels):
|
||||
error_msg = f'num_layers({num_layers}) ' \
|
||||
f'!= length of num_kernels({len(num_kernels)})'
|
||||
raise ValueError(error_msg)
|
||||
|
||||
layers = []
|
||||
for i in range(num_layers):
|
||||
kernel, padding, output_padding = \
|
||||
self._get_deconv_cfg(num_kernels[i])
|
||||
|
||||
planes = num_filters[i]
|
||||
layers.append(
|
||||
build_upsample_layer(
|
||||
dict(type='deconv'),
|
||||
in_channels=self.in_channels,
|
||||
out_channels=planes,
|
||||
kernel_size=kernel,
|
||||
stride=2,
|
||||
padding=padding,
|
||||
output_padding=output_padding,
|
||||
bias=False))
|
||||
layers.append(nn.BatchNorm2d(planes))
|
||||
layers.append(nn.ReLU(inplace=True))
|
||||
self.in_channels = planes
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize model weights."""
|
||||
for _, m in self.deconv_layers.named_modules():
|
||||
if isinstance(m, nn.ConvTranspose2d):
|
||||
normal_init(m, std=0.001)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
constant_init(m, 1)
|
||||
for m in self.final_layer.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
normal_init(m, std=0.001, bias=0)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
constant_init(m, 1)
|
||||
+349
@@ -0,0 +1,349 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import (build_conv_layer, build_norm_layer, build_upsample_layer,
|
||||
constant_init, normal_init)
|
||||
|
||||
from mmpose.core.evaluation import pose_pck_accuracy
|
||||
from mmpose.core.post_processing import flip_back
|
||||
from mmpose.models.builder import build_loss
|
||||
from mmpose.models.utils.ops import resize
|
||||
from ..builder import HEADS
|
||||
from .topdown_heatmap_base_head import TopdownHeatmapBaseHead
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class ViPNASHeatmapSimpleHead(TopdownHeatmapBaseHead):
|
||||
"""ViPNAS heatmap simple head.
|
||||
|
||||
ViPNAS: Efficient Video Pose Estimation via Neural Architecture Search.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/2105.10154>`__ .
|
||||
|
||||
TopdownHeatmapSimpleHead is consisted of (>=0) number of deconv layers
|
||||
and a simple conv2d layer.
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels
|
||||
out_channels (int): Number of output channels
|
||||
num_deconv_layers (int): Number of deconv layers.
|
||||
num_deconv_layers should >= 0. Note that 0 means
|
||||
no deconv layers.
|
||||
num_deconv_filters (list|tuple): Number of filters.
|
||||
If num_deconv_layers > 0, the length of
|
||||
num_deconv_kernels (list|tuple): Kernel sizes.
|
||||
num_deconv_groups (list|tuple): Group number.
|
||||
in_index (int|Sequence[int]): Input feature index. Default: -1
|
||||
input_transform (str|None): Transformation type of input features.
|
||||
Options: 'resize_concat', 'multiple_select', None.
|
||||
Default: None.
|
||||
|
||||
- 'resize_concat': Multiple feature maps will be resize to the
|
||||
same size as first one and than concat together.
|
||||
Usually used in FCN head of HRNet.
|
||||
- 'multiple_select': Multiple feature maps will be bundle into
|
||||
a list and passed into decode head.
|
||||
- None: Only one select feature map is allowed.
|
||||
align_corners (bool): align_corners argument of F.interpolate.
|
||||
Default: False.
|
||||
loss_keypoint (dict): Config for keypoint loss. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
num_deconv_layers=3,
|
||||
num_deconv_filters=(144, 144, 144),
|
||||
num_deconv_kernels=(4, 4, 4),
|
||||
num_deconv_groups=(16, 16, 16),
|
||||
extra=None,
|
||||
in_index=0,
|
||||
input_transform=None,
|
||||
align_corners=False,
|
||||
loss_keypoint=None,
|
||||
train_cfg=None,
|
||||
test_cfg=None):
|
||||
super().__init__()
|
||||
|
||||
self.in_channels = in_channels
|
||||
self.loss = build_loss(loss_keypoint)
|
||||
|
||||
self.train_cfg = {} if train_cfg is None else train_cfg
|
||||
self.test_cfg = {} if test_cfg is None else test_cfg
|
||||
self.target_type = self.test_cfg.get('target_type', 'GaussianHeatmap')
|
||||
|
||||
self._init_inputs(in_channels, in_index, input_transform)
|
||||
self.in_index = in_index
|
||||
self.align_corners = align_corners
|
||||
|
||||
if extra is not None and not isinstance(extra, dict):
|
||||
raise TypeError('extra should be dict or None.')
|
||||
|
||||
if num_deconv_layers > 0:
|
||||
self.deconv_layers = self._make_deconv_layer(
|
||||
num_deconv_layers, num_deconv_filters, num_deconv_kernels,
|
||||
num_deconv_groups)
|
||||
elif num_deconv_layers == 0:
|
||||
self.deconv_layers = nn.Identity()
|
||||
else:
|
||||
raise ValueError(
|
||||
f'num_deconv_layers ({num_deconv_layers}) should >= 0.')
|
||||
|
||||
identity_final_layer = False
|
||||
if extra is not None and 'final_conv_kernel' in extra:
|
||||
assert extra['final_conv_kernel'] in [0, 1, 3]
|
||||
if extra['final_conv_kernel'] == 3:
|
||||
padding = 1
|
||||
elif extra['final_conv_kernel'] == 1:
|
||||
padding = 0
|
||||
else:
|
||||
# 0 for Identity mapping.
|
||||
identity_final_layer = True
|
||||
kernel_size = extra['final_conv_kernel']
|
||||
else:
|
||||
kernel_size = 1
|
||||
padding = 0
|
||||
|
||||
if identity_final_layer:
|
||||
self.final_layer = nn.Identity()
|
||||
else:
|
||||
conv_channels = num_deconv_filters[
|
||||
-1] if num_deconv_layers > 0 else self.in_channels
|
||||
|
||||
layers = []
|
||||
if extra is not None:
|
||||
num_conv_layers = extra.get('num_conv_layers', 0)
|
||||
num_conv_kernels = extra.get('num_conv_kernels',
|
||||
[1] * num_conv_layers)
|
||||
|
||||
for i in range(num_conv_layers):
|
||||
layers.append(
|
||||
build_conv_layer(
|
||||
dict(type='Conv2d'),
|
||||
in_channels=conv_channels,
|
||||
out_channels=conv_channels,
|
||||
kernel_size=num_conv_kernels[i],
|
||||
stride=1,
|
||||
padding=(num_conv_kernels[i] - 1) // 2))
|
||||
layers.append(
|
||||
build_norm_layer(dict(type='BN'), conv_channels)[1])
|
||||
layers.append(nn.ReLU(inplace=True))
|
||||
|
||||
layers.append(
|
||||
build_conv_layer(
|
||||
cfg=dict(type='Conv2d'),
|
||||
in_channels=conv_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=1,
|
||||
padding=padding))
|
||||
|
||||
if len(layers) > 1:
|
||||
self.final_layer = nn.Sequential(*layers)
|
||||
else:
|
||||
self.final_layer = layers[0]
|
||||
|
||||
def get_loss(self, output, target, target_weight):
|
||||
"""Calculate top-down keypoint loss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N,K,H,W]): Output heatmaps.
|
||||
target (torch.Tensor[N,K,H,W]): Target heatmaps.
|
||||
target_weight (torch.Tensor[N,K,1]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
|
||||
losses = dict()
|
||||
|
||||
assert not isinstance(self.loss, nn.Sequential)
|
||||
assert target.dim() == 4 and target_weight.dim() == 3
|
||||
losses['heatmap_loss'] = self.loss(output, target, target_weight)
|
||||
|
||||
return losses
|
||||
|
||||
def get_accuracy(self, output, target, target_weight):
|
||||
"""Calculate accuracy for top-down keypoint loss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N,K,H,W]): Output heatmaps.
|
||||
target (torch.Tensor[N,K,H,W]): Target heatmaps.
|
||||
target_weight (torch.Tensor[N,K,1]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
|
||||
accuracy = dict()
|
||||
|
||||
if self.target_type.lower() == 'GaussianHeatmap'.lower():
|
||||
_, avg_acc, _ = pose_pck_accuracy(
|
||||
output.detach().cpu().numpy(),
|
||||
target.detach().cpu().numpy(),
|
||||
target_weight.detach().cpu().numpy().squeeze(-1) > 0)
|
||||
accuracy['acc_pose'] = float(avg_acc)
|
||||
|
||||
return accuracy
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
x = self._transform_inputs(x)
|
||||
x = self.deconv_layers(x)
|
||||
x = self.final_layer(x)
|
||||
return x
|
||||
|
||||
def inference_model(self, x, flip_pairs=None):
|
||||
"""Inference function.
|
||||
|
||||
Returns:
|
||||
output_heatmap (np.ndarray): Output heatmaps.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor[N,K,H,W]): Input features.
|
||||
flip_pairs (None | list[tuple]):
|
||||
Pairs of keypoints which are mirrored.
|
||||
"""
|
||||
output = self.forward(x)
|
||||
|
||||
if flip_pairs is not None:
|
||||
output_heatmap = flip_back(
|
||||
output.detach().cpu().numpy(),
|
||||
flip_pairs,
|
||||
target_type=self.target_type)
|
||||
# feature is not aligned, shift flipped heatmap for higher accuracy
|
||||
if self.test_cfg.get('shift_heatmap', False):
|
||||
output_heatmap[:, :, :, 1:] = output_heatmap[:, :, :, :-1]
|
||||
else:
|
||||
output_heatmap = output.detach().cpu().numpy()
|
||||
return output_heatmap
|
||||
|
||||
def _init_inputs(self, in_channels, in_index, input_transform):
|
||||
"""Check and initialize input transforms.
|
||||
|
||||
The in_channels, in_index and input_transform must match.
|
||||
Specifically, when input_transform is None, only single feature map
|
||||
will be selected. So in_channels and in_index must be of type int.
|
||||
When input_transform is not None, in_channels and in_index must be
|
||||
list or tuple, with the same length.
|
||||
|
||||
Args:
|
||||
in_channels (int|Sequence[int]): Input channels.
|
||||
in_index (int|Sequence[int]): Input feature index.
|
||||
input_transform (str|None): Transformation type of input features.
|
||||
Options: 'resize_concat', 'multiple_select', None.
|
||||
|
||||
- 'resize_concat': Multiple feature maps will be resize to the
|
||||
same size as first one and than concat together.
|
||||
Usually used in FCN head of HRNet.
|
||||
- 'multiple_select': Multiple feature maps will be bundle into
|
||||
a list and passed into decode head.
|
||||
- None: Only one select feature map is allowed.
|
||||
"""
|
||||
|
||||
if input_transform is not None:
|
||||
assert input_transform in ['resize_concat', 'multiple_select']
|
||||
self.input_transform = input_transform
|
||||
self.in_index = in_index
|
||||
if input_transform is not None:
|
||||
assert isinstance(in_channels, (list, tuple))
|
||||
assert isinstance(in_index, (list, tuple))
|
||||
assert len(in_channels) == len(in_index)
|
||||
if input_transform == 'resize_concat':
|
||||
self.in_channels = sum(in_channels)
|
||||
else:
|
||||
self.in_channels = in_channels
|
||||
else:
|
||||
assert isinstance(in_channels, int)
|
||||
assert isinstance(in_index, int)
|
||||
self.in_channels = in_channels
|
||||
|
||||
def _transform_inputs(self, inputs):
|
||||
"""Transform inputs for decoder.
|
||||
|
||||
Args:
|
||||
inputs (list[Tensor] | Tensor): multi-level img features.
|
||||
|
||||
Returns:
|
||||
Tensor: The transformed inputs
|
||||
"""
|
||||
if not isinstance(inputs, list):
|
||||
return inputs
|
||||
|
||||
if self.input_transform == 'resize_concat':
|
||||
inputs = [inputs[i] for i in self.in_index]
|
||||
upsampled_inputs = [
|
||||
resize(
|
||||
input=x,
|
||||
size=inputs[0].shape[2:],
|
||||
mode='bilinear',
|
||||
align_corners=self.align_corners) for x in inputs
|
||||
]
|
||||
inputs = torch.cat(upsampled_inputs, dim=1)
|
||||
elif self.input_transform == 'multiple_select':
|
||||
inputs = [inputs[i] for i in self.in_index]
|
||||
else:
|
||||
inputs = inputs[self.in_index]
|
||||
|
||||
return inputs
|
||||
|
||||
def _make_deconv_layer(self, num_layers, num_filters, num_kernels,
|
||||
num_groups):
|
||||
"""Make deconv layers."""
|
||||
if num_layers != len(num_filters):
|
||||
error_msg = f'num_layers({num_layers}) ' \
|
||||
f'!= length of num_filters({len(num_filters)})'
|
||||
raise ValueError(error_msg)
|
||||
if num_layers != len(num_kernels):
|
||||
error_msg = f'num_layers({num_layers}) ' \
|
||||
f'!= length of num_kernels({len(num_kernels)})'
|
||||
raise ValueError(error_msg)
|
||||
if num_layers != len(num_groups):
|
||||
error_msg = f'num_layers({num_layers}) ' \
|
||||
f'!= length of num_groups({len(num_groups)})'
|
||||
raise ValueError(error_msg)
|
||||
|
||||
layers = []
|
||||
for i in range(num_layers):
|
||||
kernel, padding, output_padding = \
|
||||
self._get_deconv_cfg(num_kernels[i])
|
||||
|
||||
planes = num_filters[i]
|
||||
groups = num_groups[i]
|
||||
layers.append(
|
||||
build_upsample_layer(
|
||||
dict(type='deconv'),
|
||||
in_channels=self.in_channels,
|
||||
out_channels=planes,
|
||||
kernel_size=kernel,
|
||||
groups=groups,
|
||||
stride=2,
|
||||
padding=padding,
|
||||
output_padding=output_padding,
|
||||
bias=False))
|
||||
layers.append(nn.BatchNorm2d(planes))
|
||||
layers.append(nn.ReLU(inplace=True))
|
||||
self.in_channels = planes
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize model weights."""
|
||||
for _, m in self.deconv_layers.named_modules():
|
||||
if isinstance(m, nn.ConvTranspose2d):
|
||||
normal_init(m, std=0.001)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
constant_init(m, 1)
|
||||
for m in self.final_layer.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
normal_init(m, std=0.001, bias=0)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
constant_init(m, 1)
|
||||
@@ -0,0 +1,167 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright and License Information
|
||||
# https://github.com/microsoft/voxelpose-pytorch/blob/main/lib/models
|
||||
# Original Licence: MIT License
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from ..builder import HEADS
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class CuboidCenterHead(nn.Module):
|
||||
"""Get results from the 3D human center heatmap. In this module, human 3D
|
||||
centers are local maximums obtained from the 3D heatmap via NMS (max-
|
||||
pooling).
|
||||
|
||||
Args:
|
||||
space_size (list[3]): The size of the 3D space.
|
||||
cube_size (list[3]): The size of the heatmap volume.
|
||||
space_center (list[3]): The coordinate of space center.
|
||||
max_num (int): Maximum of human center detections.
|
||||
max_pool_kernel (int): Kernel size of the max-pool kernel in nms.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
space_size,
|
||||
space_center,
|
||||
cube_size,
|
||||
max_num=10,
|
||||
max_pool_kernel=3):
|
||||
super(CuboidCenterHead, self).__init__()
|
||||
# use register_buffer
|
||||
self.register_buffer('grid_size', torch.tensor(space_size))
|
||||
self.register_buffer('cube_size', torch.tensor(cube_size))
|
||||
self.register_buffer('grid_center', torch.tensor(space_center))
|
||||
|
||||
self.num_candidates = max_num
|
||||
self.max_pool_kernel = max_pool_kernel
|
||||
self.loss = nn.MSELoss()
|
||||
|
||||
def _get_real_locations(self, indices):
|
||||
"""
|
||||
Args:
|
||||
indices (torch.Tensor(NXP)): Indices of points in the 3D tensor
|
||||
|
||||
Returns:
|
||||
real_locations (torch.Tensor(NXPx3)): Locations of points
|
||||
in the world coordinate system
|
||||
"""
|
||||
real_locations = indices.float() / (
|
||||
self.cube_size - 1) * self.grid_size + \
|
||||
self.grid_center - self.grid_size / 2.0
|
||||
return real_locations
|
||||
|
||||
def _nms_by_max_pool(self, heatmap_volumes):
|
||||
max_num = self.num_candidates
|
||||
batch_size = heatmap_volumes.shape[0]
|
||||
root_cubes_nms = self._max_pool(heatmap_volumes)
|
||||
root_cubes_nms_reshape = root_cubes_nms.reshape(batch_size, -1)
|
||||
topk_values, topk_index = root_cubes_nms_reshape.topk(max_num)
|
||||
topk_unravel_index = self._get_3d_indices(topk_index,
|
||||
heatmap_volumes[0].shape)
|
||||
|
||||
return topk_values, topk_unravel_index
|
||||
|
||||
def _max_pool(self, inputs):
|
||||
kernel = self.max_pool_kernel
|
||||
padding = (kernel - 1) // 2
|
||||
max = F.max_pool3d(
|
||||
inputs, kernel_size=kernel, stride=1, padding=padding)
|
||||
keep = (inputs == max).float()
|
||||
return keep * inputs
|
||||
|
||||
@staticmethod
|
||||
def _get_3d_indices(indices, shape):
|
||||
"""Get indices in the 3-D tensor.
|
||||
|
||||
Args:
|
||||
indices (torch.Tensor(NXp)): Indices of points in the 1D tensor
|
||||
shape (torch.Size(3)): The shape of the original 3D tensor
|
||||
|
||||
Returns:
|
||||
indices: Indices of points in the original 3D tensor
|
||||
"""
|
||||
batch_size = indices.shape[0]
|
||||
num_people = indices.shape[1]
|
||||
indices_x = (indices //
|
||||
(shape[1] * shape[2])).reshape(batch_size, num_people, -1)
|
||||
indices_y = ((indices % (shape[1] * shape[2])) //
|
||||
shape[2]).reshape(batch_size, num_people, -1)
|
||||
indices_z = (indices % shape[2]).reshape(batch_size, num_people, -1)
|
||||
indices = torch.cat([indices_x, indices_y, indices_z], dim=2)
|
||||
return indices
|
||||
|
||||
def forward(self, heatmap_volumes):
|
||||
"""
|
||||
|
||||
Args:
|
||||
heatmap_volumes (torch.Tensor(NXLXWXH)):
|
||||
3D human center heatmaps predicted by the network.
|
||||
Returns:
|
||||
human_centers (torch.Tensor(NXPX5)):
|
||||
Coordinates of human centers.
|
||||
"""
|
||||
batch_size = heatmap_volumes.shape[0]
|
||||
|
||||
topk_values, topk_unravel_index = self._nms_by_max_pool(
|
||||
heatmap_volumes.detach())
|
||||
|
||||
topk_unravel_index = self._get_real_locations(topk_unravel_index)
|
||||
|
||||
human_centers = torch.zeros(
|
||||
batch_size, self.num_candidates, 5, device=heatmap_volumes.device)
|
||||
human_centers[:, :, 0:3] = topk_unravel_index
|
||||
human_centers[:, :, 4] = topk_values
|
||||
|
||||
return human_centers
|
||||
|
||||
def get_loss(self, pred_cubes, gt):
|
||||
|
||||
return dict(loss_center=self.loss(pred_cubes, gt))
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class CuboidPoseHead(nn.Module):
|
||||
|
||||
def __init__(self, beta):
|
||||
"""Get results from the 3D human pose heatmap. Instead of obtaining
|
||||
maximums on the heatmap, this module regresses the coordinates of
|
||||
keypoints via integral pose regression. Refer to `paper.
|
||||
|
||||
<https://arxiv.org/abs/2004.06239>` for more details.
|
||||
|
||||
Args:
|
||||
beta: Constant to adjust the magnification of soft-maxed heatmap.
|
||||
"""
|
||||
super(CuboidPoseHead, self).__init__()
|
||||
self.beta = beta
|
||||
self.loss = nn.L1Loss()
|
||||
|
||||
def forward(self, heatmap_volumes, grid_coordinates):
|
||||
"""
|
||||
|
||||
Args:
|
||||
heatmap_volumes (torch.Tensor(NxKxLxWxH)):
|
||||
3D human pose heatmaps predicted by the network.
|
||||
grid_coordinates (torch.Tensor(Nx(LxWxH)x3)):
|
||||
Coordinates of the grids in the heatmap volumes.
|
||||
Returns:
|
||||
human_poses (torch.Tensor(NxKx3)): Coordinates of human poses.
|
||||
"""
|
||||
batch_size = heatmap_volumes.size(0)
|
||||
channel = heatmap_volumes.size(1)
|
||||
x = heatmap_volumes.reshape(batch_size, channel, -1, 1)
|
||||
x = F.softmax(self.beta * x, dim=2)
|
||||
grid_coordinates = grid_coordinates.unsqueeze(1)
|
||||
x = torch.mul(x, grid_coordinates)
|
||||
human_poses = torch.sum(x, dim=2)
|
||||
|
||||
return human_poses
|
||||
|
||||
def get_loss(self, preds, targets, weights):
|
||||
|
||||
return dict(loss_pose=self.loss(preds * weights, targets * weights))
|
||||
Reference in New Issue
Block a user