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,16 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .backbones import * # noqa
|
||||
from .builder import (BACKBONES, HEADS, LOSSES, MESH_MODELS, NECKS, POSENETS,
|
||||
build_backbone, build_head, build_loss, build_mesh_model,
|
||||
build_neck, build_posenet)
|
||||
from .detectors import * # noqa
|
||||
from .heads import * # noqa
|
||||
from .losses import * # noqa
|
||||
from .necks import * # noqa
|
||||
from .utils import * # noqa
|
||||
|
||||
__all__ = [
|
||||
'BACKBONES', 'HEADS', 'NECKS', 'LOSSES', 'POSENETS', 'MESH_MODELS',
|
||||
'build_backbone', 'build_head', 'build_loss', 'build_posenet',
|
||||
'build_neck', 'build_mesh_model'
|
||||
]
|
||||
@@ -0,0 +1,36 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .alexnet import AlexNet
|
||||
from .cpm import CPM
|
||||
from .hourglass import HourglassNet
|
||||
from .hourglass_ae import HourglassAENet
|
||||
from .hrformer import HRFormer
|
||||
from .hrnet import HRNet
|
||||
from .litehrnet import LiteHRNet
|
||||
from .mobilenet_v2 import MobileNetV2
|
||||
from .mobilenet_v3 import MobileNetV3
|
||||
from .mspn import MSPN
|
||||
from .regnet import RegNet
|
||||
from .resnest import ResNeSt
|
||||
from .resnet import ResNet, ResNetV1d
|
||||
from .resnext import ResNeXt
|
||||
from .rsn import RSN
|
||||
from .scnet import SCNet
|
||||
from .seresnet import SEResNet
|
||||
from .seresnext import SEResNeXt
|
||||
from .shufflenet_v1 import ShuffleNetV1
|
||||
from .shufflenet_v2 import ShuffleNetV2
|
||||
from .tcn import TCN
|
||||
from .v2v_net import V2VNet
|
||||
from .vgg import VGG
|
||||
from .vipnas_mbv3 import ViPNAS_MobileNetV3
|
||||
from .vipnas_resnet import ViPNAS_ResNet
|
||||
from .vit import ViT
|
||||
from .vit_moe import ViTMoE
|
||||
|
||||
__all__ = [
|
||||
'AlexNet', 'HourglassNet', 'HourglassAENet', 'HRNet', 'MobileNetV2',
|
||||
'MobileNetV3', 'RegNet', 'ResNet', 'ResNetV1d', 'ResNeXt', 'SCNet',
|
||||
'SEResNet', 'SEResNeXt', 'ShuffleNetV1', 'ShuffleNetV2', 'CPM', 'RSN',
|
||||
'MSPN', 'ResNeSt', 'VGG', 'TCN', 'ViPNAS_ResNet', 'ViPNAS_MobileNetV3',
|
||||
'LiteHRNet', 'V2VNet', 'HRFormer', 'ViT', 'ViTMoE'
|
||||
]
|
||||
@@ -0,0 +1,56 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch.nn as nn
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class AlexNet(BaseBackbone):
|
||||
"""`AlexNet <https://en.wikipedia.org/wiki/AlexNet>`__ backbone.
|
||||
|
||||
The input for AlexNet is a 224x224 RGB image.
|
||||
|
||||
Args:
|
||||
num_classes (int): number of classes for classification.
|
||||
The default value is -1, which uses the backbone as
|
||||
a feature extractor without the top classifier.
|
||||
"""
|
||||
|
||||
def __init__(self, num_classes=-1):
|
||||
super().__init__()
|
||||
self.num_classes = num_classes
|
||||
self.features = nn.Sequential(
|
||||
nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.MaxPool2d(kernel_size=3, stride=2),
|
||||
nn.Conv2d(64, 192, kernel_size=5, padding=2),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.MaxPool2d(kernel_size=3, stride=2),
|
||||
nn.Conv2d(192, 384, kernel_size=3, padding=1),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(384, 256, kernel_size=3, padding=1),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(256, 256, kernel_size=3, padding=1),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.MaxPool2d(kernel_size=3, stride=2),
|
||||
)
|
||||
if self.num_classes > 0:
|
||||
self.classifier = nn.Sequential(
|
||||
nn.Dropout(),
|
||||
nn.Linear(256 * 6 * 6, 4096),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Dropout(),
|
||||
nn.Linear(4096, 4096),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Linear(4096, num_classes),
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
x = self.features(x)
|
||||
if self.num_classes > 0:
|
||||
x = x.view(x.size(0), 256 * 6 * 6)
|
||||
x = self.classifier(x)
|
||||
|
||||
return x
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import logging
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import torch.nn as nn
|
||||
|
||||
# from .utils import load_checkpoint
|
||||
from mmcv_custom.checkpoint import load_checkpoint
|
||||
|
||||
class BaseBackbone(nn.Module, metaclass=ABCMeta):
|
||||
"""Base backbone.
|
||||
|
||||
This class defines the basic functions of a backbone. Any backbone that
|
||||
inherits this class should at least define its own `forward` function.
|
||||
"""
|
||||
|
||||
def init_weights(self, pretrained=None, patch_padding='pad', part_features=None):
|
||||
"""Init backbone weights.
|
||||
|
||||
Args:
|
||||
pretrained (str | None): If pretrained is a string, then it
|
||||
initializes backbone weights by loading the pretrained
|
||||
checkpoint. If pretrained is None, then it follows default
|
||||
initializer or customized initializer in subclasses.
|
||||
"""
|
||||
if isinstance(pretrained, str):
|
||||
logger = logging.getLogger()
|
||||
load_checkpoint(self, pretrained, strict=False, logger=logger, patch_padding=patch_padding, part_features=part_features)
|
||||
elif pretrained is None:
|
||||
# use default initializer or customized initializer in subclasses
|
||||
pass
|
||||
else:
|
||||
raise TypeError('pretrained must be a str or None.'
|
||||
f' But received {type(pretrained)}.')
|
||||
|
||||
@abstractmethod
|
||||
def forward(self, x):
|
||||
"""Forward function.
|
||||
|
||||
Args:
|
||||
x (Tensor | tuple[Tensor]): x could be a torch.Tensor or a tuple of
|
||||
torch.Tensor, containing input data for forward computation.
|
||||
"""
|
||||
@@ -0,0 +1,186 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import ConvModule, constant_init, normal_init
|
||||
from torch.nn.modules.batchnorm import _BatchNorm
|
||||
|
||||
from mmpose.utils import get_root_logger
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
from .utils import load_checkpoint
|
||||
|
||||
|
||||
class CpmBlock(nn.Module):
|
||||
"""CpmBlock for Convolutional Pose Machine.
|
||||
|
||||
Args:
|
||||
in_channels (int): Input channels of this block.
|
||||
channels (list): Output channels of each conv module.
|
||||
kernels (list): Kernel sizes of each conv module.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
channels=(128, 128, 128),
|
||||
kernels=(11, 11, 11),
|
||||
norm_cfg=None):
|
||||
super().__init__()
|
||||
|
||||
assert len(channels) == len(kernels)
|
||||
layers = []
|
||||
for i in range(len(channels)):
|
||||
if i == 0:
|
||||
input_channels = in_channels
|
||||
else:
|
||||
input_channels = channels[i - 1]
|
||||
layers.append(
|
||||
ConvModule(
|
||||
input_channels,
|
||||
channels[i],
|
||||
kernels[i],
|
||||
padding=(kernels[i] - 1) // 2,
|
||||
norm_cfg=norm_cfg))
|
||||
self.model = nn.Sequential(*layers)
|
||||
|
||||
def forward(self, x):
|
||||
"""Model forward function."""
|
||||
out = self.model(x)
|
||||
return out
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class CPM(BaseBackbone):
|
||||
"""CPM backbone.
|
||||
|
||||
Convolutional Pose Machines.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1602.00134>`__ .
|
||||
|
||||
Args:
|
||||
in_channels (int): The input channels of the CPM.
|
||||
out_channels (int): The output channels of the CPM.
|
||||
feat_channels (int): Feature channel of each CPM stage.
|
||||
middle_channels (int): Feature channel of conv after the middle stage.
|
||||
num_stages (int): Number of stages.
|
||||
norm_cfg (dict): Dictionary to construct and config norm layer.
|
||||
|
||||
Example:
|
||||
>>> from mmpose.models import CPM
|
||||
>>> import torch
|
||||
>>> self = CPM(3, 17)
|
||||
>>> self.eval()
|
||||
>>> inputs = torch.rand(1, 3, 368, 368)
|
||||
>>> level_outputs = self.forward(inputs)
|
||||
>>> for level_output in level_outputs:
|
||||
... print(tuple(level_output.shape))
|
||||
(1, 17, 46, 46)
|
||||
(1, 17, 46, 46)
|
||||
(1, 17, 46, 46)
|
||||
(1, 17, 46, 46)
|
||||
(1, 17, 46, 46)
|
||||
(1, 17, 46, 46)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
feat_channels=128,
|
||||
middle_channels=32,
|
||||
num_stages=6,
|
||||
norm_cfg=dict(type='BN', requires_grad=True)):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
|
||||
assert in_channels == 3
|
||||
|
||||
self.num_stages = num_stages
|
||||
assert self.num_stages >= 1
|
||||
|
||||
self.stem = nn.Sequential(
|
||||
ConvModule(in_channels, 128, 9, padding=4, norm_cfg=norm_cfg),
|
||||
nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
|
||||
ConvModule(128, 128, 9, padding=4, norm_cfg=norm_cfg),
|
||||
nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
|
||||
ConvModule(128, 128, 9, padding=4, norm_cfg=norm_cfg),
|
||||
nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
|
||||
ConvModule(128, 32, 5, padding=2, norm_cfg=norm_cfg),
|
||||
ConvModule(32, 512, 9, padding=4, norm_cfg=norm_cfg),
|
||||
ConvModule(512, 512, 1, padding=0, norm_cfg=norm_cfg),
|
||||
ConvModule(512, out_channels, 1, padding=0, act_cfg=None))
|
||||
|
||||
self.middle = nn.Sequential(
|
||||
ConvModule(in_channels, 128, 9, padding=4, norm_cfg=norm_cfg),
|
||||
nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
|
||||
ConvModule(128, 128, 9, padding=4, norm_cfg=norm_cfg),
|
||||
nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
|
||||
ConvModule(128, 128, 9, padding=4, norm_cfg=norm_cfg),
|
||||
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
|
||||
|
||||
self.cpm_stages = nn.ModuleList([
|
||||
CpmBlock(
|
||||
middle_channels + out_channels,
|
||||
channels=[feat_channels, feat_channels, feat_channels],
|
||||
kernels=[11, 11, 11],
|
||||
norm_cfg=norm_cfg) for _ in range(num_stages - 1)
|
||||
])
|
||||
|
||||
self.middle_conv = nn.ModuleList([
|
||||
nn.Sequential(
|
||||
ConvModule(
|
||||
128, middle_channels, 5, padding=2, norm_cfg=norm_cfg))
|
||||
for _ in range(num_stages - 1)
|
||||
])
|
||||
|
||||
self.out_convs = nn.ModuleList([
|
||||
nn.Sequential(
|
||||
ConvModule(
|
||||
feat_channels,
|
||||
feat_channels,
|
||||
1,
|
||||
padding=0,
|
||||
norm_cfg=norm_cfg),
|
||||
ConvModule(feat_channels, out_channels, 1, act_cfg=None))
|
||||
for _ in range(num_stages - 1)
|
||||
])
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Initialize the weights in backbone.
|
||||
|
||||
Args:
|
||||
pretrained (str, optional): Path to pre-trained weights.
|
||||
Defaults to None.
|
||||
"""
|
||||
if isinstance(pretrained, str):
|
||||
logger = get_root_logger()
|
||||
load_checkpoint(self, pretrained, strict=False, logger=logger)
|
||||
elif pretrained is None:
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
normal_init(m, std=0.001)
|
||||
elif isinstance(m, (_BatchNorm, nn.GroupNorm)):
|
||||
constant_init(m, 1)
|
||||
else:
|
||||
raise TypeError('pretrained must be a str or None')
|
||||
|
||||
def forward(self, x):
|
||||
"""Model forward function."""
|
||||
stage1_out = self.stem(x)
|
||||
middle_out = self.middle(x)
|
||||
out_feats = []
|
||||
|
||||
out_feats.append(stage1_out)
|
||||
|
||||
for ind in range(self.num_stages - 1):
|
||||
single_stage = self.cpm_stages[ind]
|
||||
out_conv = self.out_convs[ind]
|
||||
|
||||
inp_feat = torch.cat(
|
||||
[out_feats[-1], self.middle_conv[ind](middle_out)], 1)
|
||||
cpm_feat = single_stage(inp_feat)
|
||||
out_feat = out_conv(cpm_feat)
|
||||
out_feats.append(out_feat)
|
||||
|
||||
return out_feats
|
||||
@@ -0,0 +1,212 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import ConvModule, constant_init, normal_init
|
||||
from torch.nn.modules.batchnorm import _BatchNorm
|
||||
|
||||
from mmpose.utils import get_root_logger
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
from .resnet import BasicBlock, ResLayer
|
||||
from .utils import load_checkpoint
|
||||
|
||||
|
||||
class HourglassModule(nn.Module):
|
||||
"""Hourglass Module for HourglassNet backbone.
|
||||
|
||||
Generate module recursively and use BasicBlock as the base unit.
|
||||
|
||||
Args:
|
||||
depth (int): Depth of current HourglassModule.
|
||||
stage_channels (list[int]): Feature channels of sub-modules in current
|
||||
and follow-up HourglassModule.
|
||||
stage_blocks (list[int]): Number of sub-modules stacked in current and
|
||||
follow-up HourglassModule.
|
||||
norm_cfg (dict): Dictionary to construct and config norm layer.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
depth,
|
||||
stage_channels,
|
||||
stage_blocks,
|
||||
norm_cfg=dict(type='BN', requires_grad=True)):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
|
||||
self.depth = depth
|
||||
|
||||
cur_block = stage_blocks[0]
|
||||
next_block = stage_blocks[1]
|
||||
|
||||
cur_channel = stage_channels[0]
|
||||
next_channel = stage_channels[1]
|
||||
|
||||
self.up1 = ResLayer(
|
||||
BasicBlock, cur_block, cur_channel, cur_channel, norm_cfg=norm_cfg)
|
||||
|
||||
self.low1 = ResLayer(
|
||||
BasicBlock,
|
||||
cur_block,
|
||||
cur_channel,
|
||||
next_channel,
|
||||
stride=2,
|
||||
norm_cfg=norm_cfg)
|
||||
|
||||
if self.depth > 1:
|
||||
self.low2 = HourglassModule(depth - 1, stage_channels[1:],
|
||||
stage_blocks[1:])
|
||||
else:
|
||||
self.low2 = ResLayer(
|
||||
BasicBlock,
|
||||
next_block,
|
||||
next_channel,
|
||||
next_channel,
|
||||
norm_cfg=norm_cfg)
|
||||
|
||||
self.low3 = ResLayer(
|
||||
BasicBlock,
|
||||
cur_block,
|
||||
next_channel,
|
||||
cur_channel,
|
||||
norm_cfg=norm_cfg,
|
||||
downsample_first=False)
|
||||
|
||||
self.up2 = nn.Upsample(scale_factor=2)
|
||||
|
||||
def forward(self, x):
|
||||
"""Model forward function."""
|
||||
up1 = self.up1(x)
|
||||
low1 = self.low1(x)
|
||||
low2 = self.low2(low1)
|
||||
low3 = self.low3(low2)
|
||||
up2 = self.up2(low3)
|
||||
return up1 + up2
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class HourglassNet(BaseBackbone):
|
||||
"""HourglassNet backbone.
|
||||
|
||||
Stacked Hourglass Networks for Human Pose Estimation.
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1603.06937>`__ .
|
||||
|
||||
Args:
|
||||
downsample_times (int): Downsample times in a HourglassModule.
|
||||
num_stacks (int): Number of HourglassModule modules stacked,
|
||||
1 for Hourglass-52, 2 for Hourglass-104.
|
||||
stage_channels (list[int]): Feature channel of each sub-module in a
|
||||
HourglassModule.
|
||||
stage_blocks (list[int]): Number of sub-modules stacked in a
|
||||
HourglassModule.
|
||||
feat_channel (int): Feature channel of conv after a HourglassModule.
|
||||
norm_cfg (dict): Dictionary to construct and config norm layer.
|
||||
|
||||
Example:
|
||||
>>> from mmpose.models import HourglassNet
|
||||
>>> import torch
|
||||
>>> self = HourglassNet()
|
||||
>>> self.eval()
|
||||
>>> inputs = torch.rand(1, 3, 511, 511)
|
||||
>>> level_outputs = self.forward(inputs)
|
||||
>>> for level_output in level_outputs:
|
||||
... print(tuple(level_output.shape))
|
||||
(1, 256, 128, 128)
|
||||
(1, 256, 128, 128)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
downsample_times=5,
|
||||
num_stacks=2,
|
||||
stage_channels=(256, 256, 384, 384, 384, 512),
|
||||
stage_blocks=(2, 2, 2, 2, 2, 4),
|
||||
feat_channel=256,
|
||||
norm_cfg=dict(type='BN', requires_grad=True)):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
|
||||
self.num_stacks = num_stacks
|
||||
assert self.num_stacks >= 1
|
||||
assert len(stage_channels) == len(stage_blocks)
|
||||
assert len(stage_channels) > downsample_times
|
||||
|
||||
cur_channel = stage_channels[0]
|
||||
|
||||
self.stem = nn.Sequential(
|
||||
ConvModule(3, 128, 7, padding=3, stride=2, norm_cfg=norm_cfg),
|
||||
ResLayer(BasicBlock, 1, 128, 256, stride=2, norm_cfg=norm_cfg))
|
||||
|
||||
self.hourglass_modules = nn.ModuleList([
|
||||
HourglassModule(downsample_times, stage_channels, stage_blocks)
|
||||
for _ in range(num_stacks)
|
||||
])
|
||||
|
||||
self.inters = ResLayer(
|
||||
BasicBlock,
|
||||
num_stacks - 1,
|
||||
cur_channel,
|
||||
cur_channel,
|
||||
norm_cfg=norm_cfg)
|
||||
|
||||
self.conv1x1s = nn.ModuleList([
|
||||
ConvModule(
|
||||
cur_channel, cur_channel, 1, norm_cfg=norm_cfg, act_cfg=None)
|
||||
for _ in range(num_stacks - 1)
|
||||
])
|
||||
|
||||
self.out_convs = nn.ModuleList([
|
||||
ConvModule(
|
||||
cur_channel, feat_channel, 3, padding=1, norm_cfg=norm_cfg)
|
||||
for _ in range(num_stacks)
|
||||
])
|
||||
|
||||
self.remap_convs = nn.ModuleList([
|
||||
ConvModule(
|
||||
feat_channel, cur_channel, 1, norm_cfg=norm_cfg, act_cfg=None)
|
||||
for _ in range(num_stacks - 1)
|
||||
])
|
||||
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Initialize the weights in backbone.
|
||||
|
||||
Args:
|
||||
pretrained (str, optional): Path to pre-trained weights.
|
||||
Defaults to None.
|
||||
"""
|
||||
if isinstance(pretrained, str):
|
||||
logger = get_root_logger()
|
||||
load_checkpoint(self, pretrained, strict=False, logger=logger)
|
||||
elif pretrained is None:
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
normal_init(m, std=0.001)
|
||||
elif isinstance(m, (_BatchNorm, nn.GroupNorm)):
|
||||
constant_init(m, 1)
|
||||
else:
|
||||
raise TypeError('pretrained must be a str or None')
|
||||
|
||||
def forward(self, x):
|
||||
"""Model forward function."""
|
||||
inter_feat = self.stem(x)
|
||||
out_feats = []
|
||||
|
||||
for ind in range(self.num_stacks):
|
||||
single_hourglass = self.hourglass_modules[ind]
|
||||
out_conv = self.out_convs[ind]
|
||||
|
||||
hourglass_feat = single_hourglass(inter_feat)
|
||||
out_feat = out_conv(hourglass_feat)
|
||||
out_feats.append(out_feat)
|
||||
|
||||
if ind < self.num_stacks - 1:
|
||||
inter_feat = self.conv1x1s[ind](
|
||||
inter_feat) + self.remap_convs[ind](
|
||||
out_feat)
|
||||
inter_feat = self.inters[ind](self.relu(inter_feat))
|
||||
|
||||
return out_feats
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import ConvModule, MaxPool2d, constant_init, normal_init
|
||||
from torch.nn.modules.batchnorm import _BatchNorm
|
||||
|
||||
from mmpose.utils import get_root_logger
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
from .utils import load_checkpoint
|
||||
|
||||
|
||||
class HourglassAEModule(nn.Module):
|
||||
"""Modified Hourglass Module for HourglassNet_AE backbone.
|
||||
|
||||
Generate module recursively and use BasicBlock as the base unit.
|
||||
|
||||
Args:
|
||||
depth (int): Depth of current HourglassModule.
|
||||
stage_channels (list[int]): Feature channels of sub-modules in current
|
||||
and follow-up HourglassModule.
|
||||
norm_cfg (dict): Dictionary to construct and config norm layer.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
depth,
|
||||
stage_channels,
|
||||
norm_cfg=dict(type='BN', requires_grad=True)):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
|
||||
self.depth = depth
|
||||
|
||||
cur_channel = stage_channels[0]
|
||||
next_channel = stage_channels[1]
|
||||
|
||||
self.up1 = ConvModule(
|
||||
cur_channel, cur_channel, 3, padding=1, norm_cfg=norm_cfg)
|
||||
|
||||
self.pool1 = MaxPool2d(2, 2)
|
||||
|
||||
self.low1 = ConvModule(
|
||||
cur_channel, next_channel, 3, padding=1, norm_cfg=norm_cfg)
|
||||
|
||||
if self.depth > 1:
|
||||
self.low2 = HourglassAEModule(depth - 1, stage_channels[1:])
|
||||
else:
|
||||
self.low2 = ConvModule(
|
||||
next_channel, next_channel, 3, padding=1, norm_cfg=norm_cfg)
|
||||
|
||||
self.low3 = ConvModule(
|
||||
next_channel, cur_channel, 3, padding=1, norm_cfg=norm_cfg)
|
||||
|
||||
self.up2 = nn.UpsamplingNearest2d(scale_factor=2)
|
||||
|
||||
def forward(self, x):
|
||||
"""Model forward function."""
|
||||
up1 = self.up1(x)
|
||||
pool1 = self.pool1(x)
|
||||
low1 = self.low1(pool1)
|
||||
low2 = self.low2(low1)
|
||||
low3 = self.low3(low2)
|
||||
up2 = self.up2(low3)
|
||||
return up1 + up2
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class HourglassAENet(BaseBackbone):
|
||||
"""Hourglass-AE Network proposed by Newell et al.
|
||||
|
||||
Associative Embedding: End-to-End Learning for Joint
|
||||
Detection and Grouping.
|
||||
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/1611.05424>`__ .
|
||||
|
||||
Args:
|
||||
downsample_times (int): Downsample times in a HourglassModule.
|
||||
num_stacks (int): Number of HourglassModule modules stacked,
|
||||
1 for Hourglass-52, 2 for Hourglass-104.
|
||||
stage_channels (list[int]): Feature channel of each sub-module in a
|
||||
HourglassModule.
|
||||
stage_blocks (list[int]): Number of sub-modules stacked in a
|
||||
HourglassModule.
|
||||
feat_channels (int): Feature channel of conv after a HourglassModule.
|
||||
norm_cfg (dict): Dictionary to construct and config norm layer.
|
||||
|
||||
Example:
|
||||
>>> from mmpose.models import HourglassAENet
|
||||
>>> import torch
|
||||
>>> self = HourglassAENet()
|
||||
>>> self.eval()
|
||||
>>> inputs = torch.rand(1, 3, 512, 512)
|
||||
>>> level_outputs = self.forward(inputs)
|
||||
>>> for level_output in level_outputs:
|
||||
... print(tuple(level_output.shape))
|
||||
(1, 34, 128, 128)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
downsample_times=4,
|
||||
num_stacks=1,
|
||||
out_channels=34,
|
||||
stage_channels=(256, 384, 512, 640, 768),
|
||||
feat_channels=256,
|
||||
norm_cfg=dict(type='BN', requires_grad=True)):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
|
||||
self.num_stacks = num_stacks
|
||||
assert self.num_stacks >= 1
|
||||
assert len(stage_channels) > downsample_times
|
||||
|
||||
cur_channels = stage_channels[0]
|
||||
|
||||
self.stem = nn.Sequential(
|
||||
ConvModule(3, 64, 7, padding=3, stride=2, norm_cfg=norm_cfg),
|
||||
ConvModule(64, 128, 3, padding=1, norm_cfg=norm_cfg),
|
||||
MaxPool2d(2, 2),
|
||||
ConvModule(128, 128, 3, padding=1, norm_cfg=norm_cfg),
|
||||
ConvModule(128, feat_channels, 3, padding=1, norm_cfg=norm_cfg),
|
||||
)
|
||||
|
||||
self.hourglass_modules = nn.ModuleList([
|
||||
nn.Sequential(
|
||||
HourglassAEModule(
|
||||
downsample_times, stage_channels, norm_cfg=norm_cfg),
|
||||
ConvModule(
|
||||
feat_channels,
|
||||
feat_channels,
|
||||
3,
|
||||
padding=1,
|
||||
norm_cfg=norm_cfg),
|
||||
ConvModule(
|
||||
feat_channels,
|
||||
feat_channels,
|
||||
3,
|
||||
padding=1,
|
||||
norm_cfg=norm_cfg)) for _ in range(num_stacks)
|
||||
])
|
||||
|
||||
self.out_convs = nn.ModuleList([
|
||||
ConvModule(
|
||||
cur_channels,
|
||||
out_channels,
|
||||
1,
|
||||
padding=0,
|
||||
norm_cfg=None,
|
||||
act_cfg=None) for _ in range(num_stacks)
|
||||
])
|
||||
|
||||
self.remap_out_convs = nn.ModuleList([
|
||||
ConvModule(
|
||||
out_channels,
|
||||
feat_channels,
|
||||
1,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None) for _ in range(num_stacks - 1)
|
||||
])
|
||||
|
||||
self.remap_feature_convs = nn.ModuleList([
|
||||
ConvModule(
|
||||
feat_channels,
|
||||
feat_channels,
|
||||
1,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None) for _ in range(num_stacks - 1)
|
||||
])
|
||||
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Initialize the weights in backbone.
|
||||
|
||||
Args:
|
||||
pretrained (str, optional): Path to pre-trained weights.
|
||||
Defaults to None.
|
||||
"""
|
||||
if isinstance(pretrained, str):
|
||||
logger = get_root_logger()
|
||||
load_checkpoint(self, pretrained, strict=False, logger=logger)
|
||||
elif pretrained is None:
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
normal_init(m, std=0.001)
|
||||
elif isinstance(m, (_BatchNorm, nn.GroupNorm)):
|
||||
constant_init(m, 1)
|
||||
else:
|
||||
raise TypeError('pretrained must be a str or None')
|
||||
|
||||
def forward(self, x):
|
||||
"""Model forward function."""
|
||||
inter_feat = self.stem(x)
|
||||
out_feats = []
|
||||
|
||||
for ind in range(self.num_stacks):
|
||||
single_hourglass = self.hourglass_modules[ind]
|
||||
out_conv = self.out_convs[ind]
|
||||
|
||||
hourglass_feat = single_hourglass(inter_feat)
|
||||
out_feat = out_conv(hourglass_feat)
|
||||
out_feats.append(out_feat)
|
||||
|
||||
if ind < self.num_stacks - 1:
|
||||
inter_feat = inter_feat + self.remap_out_convs[ind](
|
||||
out_feat) + self.remap_feature_convs[ind](
|
||||
hourglass_feat)
|
||||
|
||||
return out_feats
|
||||
@@ -0,0 +1,746 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
# from timm.models.layers import to_2tuple, trunc_normal_
|
||||
from mmcv.cnn import (build_activation_layer, build_conv_layer,
|
||||
build_norm_layer, trunc_normal_init)
|
||||
from mmcv.cnn.bricks.transformer import build_dropout
|
||||
from mmcv.runner import BaseModule
|
||||
from torch.nn.functional import pad
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .hrnet import Bottleneck, HRModule, HRNet
|
||||
|
||||
|
||||
def nlc_to_nchw(x, hw_shape):
|
||||
"""Convert [N, L, C] shape tensor to [N, C, H, W] shape tensor.
|
||||
|
||||
Args:
|
||||
x (Tensor): The input tensor of shape [N, L, C] before conversion.
|
||||
hw_shape (Sequence[int]): The height and width of output feature map.
|
||||
|
||||
Returns:
|
||||
Tensor: The output tensor of shape [N, C, H, W] after conversion.
|
||||
"""
|
||||
H, W = hw_shape
|
||||
assert len(x.shape) == 3
|
||||
B, L, C = x.shape
|
||||
assert L == H * W, 'The seq_len doesn\'t match H, W'
|
||||
return x.transpose(1, 2).reshape(B, C, H, W)
|
||||
|
||||
|
||||
def nchw_to_nlc(x):
|
||||
"""Flatten [N, C, H, W] shape tensor to [N, L, C] shape tensor.
|
||||
|
||||
Args:
|
||||
x (Tensor): The input tensor of shape [N, C, H, W] before conversion.
|
||||
|
||||
Returns:
|
||||
Tensor: The output tensor of shape [N, L, C] after conversion.
|
||||
"""
|
||||
assert len(x.shape) == 4
|
||||
return x.flatten(2).transpose(1, 2).contiguous()
|
||||
|
||||
|
||||
def build_drop_path(drop_path_rate):
|
||||
"""Build drop path layer."""
|
||||
return build_dropout(dict(type='DropPath', drop_prob=drop_path_rate))
|
||||
|
||||
|
||||
class WindowMSA(BaseModule):
|
||||
"""Window based multi-head self-attention (W-MSA) module with relative
|
||||
position bias.
|
||||
|
||||
Args:
|
||||
embed_dims (int): Number of input channels.
|
||||
num_heads (int): Number of attention heads.
|
||||
window_size (tuple[int]): The height and width of the window.
|
||||
qkv_bias (bool, optional): If True, add a learnable bias to q, k, v.
|
||||
Default: True.
|
||||
qk_scale (float | None, optional): Override default qk scale of
|
||||
head_dim ** -0.5 if set. Default: None.
|
||||
attn_drop_rate (float, optional): Dropout ratio of attention weight.
|
||||
Default: 0.0
|
||||
proj_drop_rate (float, optional): Dropout ratio of output. Default: 0.
|
||||
with_rpe (bool, optional): If True, use relative position bias.
|
||||
Default: True.
|
||||
init_cfg (dict | None, optional): The Config for initialization.
|
||||
Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
embed_dims,
|
||||
num_heads,
|
||||
window_size,
|
||||
qkv_bias=True,
|
||||
qk_scale=None,
|
||||
attn_drop_rate=0.,
|
||||
proj_drop_rate=0.,
|
||||
with_rpe=True,
|
||||
init_cfg=None):
|
||||
|
||||
super().__init__(init_cfg=init_cfg)
|
||||
self.embed_dims = embed_dims
|
||||
self.window_size = window_size # Wh, Ww
|
||||
self.num_heads = num_heads
|
||||
head_embed_dims = embed_dims // num_heads
|
||||
self.scale = qk_scale or head_embed_dims**-0.5
|
||||
|
||||
self.with_rpe = with_rpe
|
||||
if self.with_rpe:
|
||||
# define a parameter table of relative position bias
|
||||
self.relative_position_bias_table = nn.Parameter(
|
||||
torch.zeros(
|
||||
(2 * window_size[0] - 1) * (2 * window_size[1] - 1),
|
||||
num_heads)) # 2*Wh-1 * 2*Ww-1, nH
|
||||
|
||||
Wh, Ww = self.window_size
|
||||
rel_index_coords = self.double_step_seq(2 * Ww - 1, Wh, 1, Ww)
|
||||
rel_position_index = rel_index_coords + rel_index_coords.T
|
||||
rel_position_index = rel_position_index.flip(1).contiguous()
|
||||
self.register_buffer('relative_position_index', rel_position_index)
|
||||
|
||||
self.qkv = nn.Linear(embed_dims, embed_dims * 3, bias=qkv_bias)
|
||||
self.attn_drop = nn.Dropout(attn_drop_rate)
|
||||
self.proj = nn.Linear(embed_dims, embed_dims)
|
||||
self.proj_drop = nn.Dropout(proj_drop_rate)
|
||||
|
||||
self.softmax = nn.Softmax(dim=-1)
|
||||
|
||||
def init_weights(self):
|
||||
trunc_normal_init(self.relative_position_bias_table, std=0.02)
|
||||
|
||||
def forward(self, x, mask=None):
|
||||
"""
|
||||
Args:
|
||||
|
||||
x (tensor): input features with shape of (B*num_windows, N, C)
|
||||
mask (tensor | None, Optional): mask with shape of (num_windows,
|
||||
Wh*Ww, Wh*Ww), value should be between (-inf, 0].
|
||||
"""
|
||||
B, N, C = x.shape
|
||||
qkv = self.qkv(x).reshape(B, N, 3, self.num_heads,
|
||||
C // self.num_heads).permute(2, 0, 3, 1, 4)
|
||||
q, k, v = qkv[0], qkv[1], qkv[2]
|
||||
|
||||
q = q * self.scale
|
||||
attn = (q @ k.transpose(-2, -1))
|
||||
|
||||
if self.with_rpe:
|
||||
relative_position_bias = self.relative_position_bias_table[
|
||||
self.relative_position_index.view(-1)].view(
|
||||
self.window_size[0] * self.window_size[1],
|
||||
self.window_size[0] * self.window_size[1],
|
||||
-1) # Wh*Ww,Wh*Ww,nH
|
||||
relative_position_bias = relative_position_bias.permute(
|
||||
2, 0, 1).contiguous() # nH, Wh*Ww, Wh*Ww
|
||||
attn = attn + relative_position_bias.unsqueeze(0)
|
||||
|
||||
if mask is not None:
|
||||
nW = mask.shape[0]
|
||||
attn = attn.view(B // nW, nW, self.num_heads, N,
|
||||
N) + mask.unsqueeze(1).unsqueeze(0)
|
||||
attn = attn.view(-1, self.num_heads, N, N)
|
||||
attn = self.softmax(attn)
|
||||
|
||||
attn = self.attn_drop(attn)
|
||||
|
||||
x = (attn @ v).transpose(1, 2).reshape(B, N, C)
|
||||
x = self.proj(x)
|
||||
x = self.proj_drop(x)
|
||||
return x
|
||||
|
||||
@staticmethod
|
||||
def double_step_seq(step1, len1, step2, len2):
|
||||
seq1 = torch.arange(0, step1 * len1, step1)
|
||||
seq2 = torch.arange(0, step2 * len2, step2)
|
||||
return (seq1[:, None] + seq2[None, :]).reshape(1, -1)
|
||||
|
||||
|
||||
class LocalWindowSelfAttention(BaseModule):
|
||||
r""" Local-window Self Attention (LSA) module with relative position bias.
|
||||
|
||||
This module is the short-range self-attention module in the
|
||||
Interlaced Sparse Self-Attention <https://arxiv.org/abs/1907.12273>`_.
|
||||
|
||||
Args:
|
||||
embed_dims (int): Number of input channels.
|
||||
num_heads (int): Number of attention heads.
|
||||
window_size (tuple[int] | int): The height and width of the window.
|
||||
qkv_bias (bool, optional): If True, add a learnable bias to q, k, v.
|
||||
Default: True.
|
||||
qk_scale (float | None, optional): Override default qk scale of
|
||||
head_dim ** -0.5 if set. Default: None.
|
||||
attn_drop_rate (float, optional): Dropout ratio of attention weight.
|
||||
Default: 0.0
|
||||
proj_drop_rate (float, optional): Dropout ratio of output. Default: 0.
|
||||
with_rpe (bool, optional): If True, use relative position bias.
|
||||
Default: True.
|
||||
with_pad_mask (bool, optional): If True, mask out the padded tokens in
|
||||
the attention process. Default: False.
|
||||
init_cfg (dict | None, optional): The Config for initialization.
|
||||
Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
embed_dims,
|
||||
num_heads,
|
||||
window_size,
|
||||
qkv_bias=True,
|
||||
qk_scale=None,
|
||||
attn_drop_rate=0.,
|
||||
proj_drop_rate=0.,
|
||||
with_rpe=True,
|
||||
with_pad_mask=False,
|
||||
init_cfg=None):
|
||||
super().__init__(init_cfg=init_cfg)
|
||||
if isinstance(window_size, int):
|
||||
window_size = (window_size, window_size)
|
||||
self.window_size = window_size
|
||||
self.with_pad_mask = with_pad_mask
|
||||
self.attn = WindowMSA(
|
||||
embed_dims=embed_dims,
|
||||
num_heads=num_heads,
|
||||
window_size=window_size,
|
||||
qkv_bias=qkv_bias,
|
||||
qk_scale=qk_scale,
|
||||
attn_drop_rate=attn_drop_rate,
|
||||
proj_drop_rate=proj_drop_rate,
|
||||
with_rpe=with_rpe,
|
||||
init_cfg=init_cfg)
|
||||
|
||||
def forward(self, x, H, W, **kwargs):
|
||||
"""Forward function."""
|
||||
B, N, C = x.shape
|
||||
x = x.view(B, H, W, C)
|
||||
Wh, Ww = self.window_size
|
||||
|
||||
# center-pad the feature on H and W axes
|
||||
pad_h = math.ceil(H / Wh) * Wh - H
|
||||
pad_w = math.ceil(W / Ww) * Ww - W
|
||||
x = pad(x, (0, 0, pad_w // 2, pad_w - pad_w // 2, pad_h // 2,
|
||||
pad_h - pad_h // 2))
|
||||
|
||||
# permute
|
||||
x = x.view(B, math.ceil(H / Wh), Wh, math.ceil(W / Ww), Ww, C)
|
||||
x = x.permute(0, 1, 3, 2, 4, 5)
|
||||
x = x.reshape(-1, Wh * Ww, C) # (B*num_window, Wh*Ww, C)
|
||||
|
||||
# attention
|
||||
if self.with_pad_mask and pad_h > 0 and pad_w > 0:
|
||||
pad_mask = x.new_zeros(1, H, W, 1)
|
||||
pad_mask = pad(
|
||||
pad_mask, [
|
||||
0, 0, pad_w // 2, pad_w - pad_w // 2, pad_h // 2,
|
||||
pad_h - pad_h // 2
|
||||
],
|
||||
value=-float('inf'))
|
||||
pad_mask = pad_mask.view(1, math.ceil(H / Wh), Wh,
|
||||
math.ceil(W / Ww), Ww, 1)
|
||||
pad_mask = pad_mask.permute(1, 3, 0, 2, 4, 5)
|
||||
pad_mask = pad_mask.reshape(-1, Wh * Ww)
|
||||
pad_mask = pad_mask[:, None, :].expand([-1, Wh * Ww, -1])
|
||||
out = self.attn(x, pad_mask, **kwargs)
|
||||
else:
|
||||
out = self.attn(x, **kwargs)
|
||||
|
||||
# reverse permutation
|
||||
out = out.reshape(B, math.ceil(H / Wh), math.ceil(W / Ww), Wh, Ww, C)
|
||||
out = out.permute(0, 1, 3, 2, 4, 5)
|
||||
out = out.reshape(B, H + pad_h, W + pad_w, C)
|
||||
|
||||
# de-pad
|
||||
out = out[:, pad_h // 2:H + pad_h // 2, pad_w // 2:W + pad_w // 2]
|
||||
return out.reshape(B, N, C)
|
||||
|
||||
|
||||
class CrossFFN(BaseModule):
|
||||
r"""FFN with Depthwise Conv of HRFormer.
|
||||
|
||||
Args:
|
||||
in_features (int): The feature dimension.
|
||||
hidden_features (int, optional): The hidden dimension of FFNs.
|
||||
Defaults: The same as in_features.
|
||||
act_cfg (dict, optional): Config of activation layer.
|
||||
Default: dict(type='GELU').
|
||||
dw_act_cfg (dict, optional): Config of activation layer appended
|
||||
right after DW Conv. Default: dict(type='GELU').
|
||||
norm_cfg (dict, optional): Config of norm layer.
|
||||
Default: dict(type='SyncBN').
|
||||
init_cfg (dict | list | None, optional): The init config.
|
||||
Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_features,
|
||||
hidden_features=None,
|
||||
out_features=None,
|
||||
act_cfg=dict(type='GELU'),
|
||||
dw_act_cfg=dict(type='GELU'),
|
||||
norm_cfg=dict(type='SyncBN'),
|
||||
init_cfg=None):
|
||||
super().__init__(init_cfg=init_cfg)
|
||||
out_features = out_features or in_features
|
||||
hidden_features = hidden_features or in_features
|
||||
self.fc1 = nn.Conv2d(in_features, hidden_features, kernel_size=1)
|
||||
self.act1 = build_activation_layer(act_cfg)
|
||||
self.norm1 = build_norm_layer(norm_cfg, hidden_features)[1]
|
||||
self.dw3x3 = nn.Conv2d(
|
||||
hidden_features,
|
||||
hidden_features,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
groups=hidden_features,
|
||||
padding=1)
|
||||
self.act2 = build_activation_layer(dw_act_cfg)
|
||||
self.norm2 = build_norm_layer(norm_cfg, hidden_features)[1]
|
||||
self.fc2 = nn.Conv2d(hidden_features, out_features, kernel_size=1)
|
||||
self.act3 = build_activation_layer(act_cfg)
|
||||
self.norm3 = build_norm_layer(norm_cfg, out_features)[1]
|
||||
|
||||
# put the modules togather
|
||||
self.layers = [
|
||||
self.fc1, self.norm1, self.act1, self.dw3x3, self.norm2, self.act2,
|
||||
self.fc2, self.norm3, self.act3
|
||||
]
|
||||
|
||||
def forward(self, x, H, W):
|
||||
"""Forward function."""
|
||||
x = nlc_to_nchw(x, (H, W))
|
||||
for layer in self.layers:
|
||||
x = layer(x)
|
||||
x = nchw_to_nlc(x)
|
||||
return x
|
||||
|
||||
|
||||
class HRFormerBlock(BaseModule):
|
||||
"""High-Resolution Block for HRFormer.
|
||||
|
||||
Args:
|
||||
in_features (int): The input dimension.
|
||||
out_features (int): The output dimension.
|
||||
num_heads (int): The number of head within each LSA.
|
||||
window_size (int, optional): The window size for the LSA.
|
||||
Default: 7
|
||||
mlp_ratio (int, optional): The expansion ration of FFN.
|
||||
Default: 4
|
||||
act_cfg (dict, optional): Config of activation layer.
|
||||
Default: dict(type='GELU').
|
||||
norm_cfg (dict, optional): Config of norm layer.
|
||||
Default: dict(type='SyncBN').
|
||||
transformer_norm_cfg (dict, optional): Config of transformer norm
|
||||
layer. Default: dict(type='LN', eps=1e-6).
|
||||
init_cfg (dict | list | None, optional): The init config.
|
||||
Default: None.
|
||||
"""
|
||||
|
||||
expansion = 1
|
||||
|
||||
def __init__(self,
|
||||
in_features,
|
||||
out_features,
|
||||
num_heads,
|
||||
window_size=7,
|
||||
mlp_ratio=4.0,
|
||||
drop_path=0.0,
|
||||
act_cfg=dict(type='GELU'),
|
||||
norm_cfg=dict(type='SyncBN'),
|
||||
transformer_norm_cfg=dict(type='LN', eps=1e-6),
|
||||
init_cfg=None,
|
||||
**kwargs):
|
||||
super(HRFormerBlock, self).__init__(init_cfg=init_cfg)
|
||||
self.num_heads = num_heads
|
||||
self.window_size = window_size
|
||||
self.mlp_ratio = mlp_ratio
|
||||
|
||||
self.norm1 = build_norm_layer(transformer_norm_cfg, in_features)[1]
|
||||
self.attn = LocalWindowSelfAttention(
|
||||
in_features,
|
||||
num_heads=num_heads,
|
||||
window_size=window_size,
|
||||
init_cfg=None,
|
||||
**kwargs)
|
||||
|
||||
self.norm2 = build_norm_layer(transformer_norm_cfg, out_features)[1]
|
||||
self.ffn = CrossFFN(
|
||||
in_features=in_features,
|
||||
hidden_features=int(in_features * mlp_ratio),
|
||||
out_features=out_features,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg,
|
||||
dw_act_cfg=act_cfg,
|
||||
init_cfg=None)
|
||||
|
||||
self.drop_path = build_drop_path(
|
||||
drop_path) if drop_path > 0.0 else nn.Identity()
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
B, C, H, W = x.size()
|
||||
# Attention
|
||||
x = x.view(B, C, -1).permute(0, 2, 1)
|
||||
x = x + self.drop_path(self.attn(self.norm1(x), H, W))
|
||||
# FFN
|
||||
x = x + self.drop_path(self.ffn(self.norm2(x), H, W))
|
||||
x = x.permute(0, 2, 1).view(B, C, H, W)
|
||||
return x
|
||||
|
||||
def extra_repr(self):
|
||||
"""(Optional) Set the extra information about this module."""
|
||||
return 'num_heads={}, window_size={}, mlp_ratio={}'.format(
|
||||
self.num_heads, self.window_size, self.mlp_ratio)
|
||||
|
||||
|
||||
class HRFomerModule(HRModule):
|
||||
"""High-Resolution Module for HRFormer.
|
||||
|
||||
Args:
|
||||
num_branches (int): The number of branches in the HRFormerModule.
|
||||
block (nn.Module): The building block of HRFormer.
|
||||
The block should be the HRFormerBlock.
|
||||
num_blocks (tuple): The number of blocks in each branch.
|
||||
The length must be equal to num_branches.
|
||||
num_inchannels (tuple): The number of input channels in each branch.
|
||||
The length must be equal to num_branches.
|
||||
num_channels (tuple): The number of channels in each branch.
|
||||
The length must be equal to num_branches.
|
||||
num_heads (tuple): The number of heads within the LSAs.
|
||||
num_window_sizes (tuple): The window size for the LSAs.
|
||||
num_mlp_ratios (tuple): The expansion ratio for the FFNs.
|
||||
drop_path (int, optional): The drop path rate of HRFomer.
|
||||
Default: 0.0
|
||||
multiscale_output (bool, optional): Whether to output multi-level
|
||||
features produced by multiple branches. If False, only the first
|
||||
level feature will be output. Default: True.
|
||||
conv_cfg (dict, optional): Config of the conv layers.
|
||||
Default: None.
|
||||
norm_cfg (dict, optional): Config of the norm layers appended
|
||||
right after conv. Default: dict(type='SyncBN', requires_grad=True)
|
||||
transformer_norm_cfg (dict, optional): Config of the norm layers.
|
||||
Default: dict(type='LN', eps=1e-6)
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False
|
||||
upsample_cfg(dict, optional): The config of upsample layers in fuse
|
||||
layers. Default: dict(mode='bilinear', align_corners=False)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
num_branches,
|
||||
block,
|
||||
num_blocks,
|
||||
num_inchannels,
|
||||
num_channels,
|
||||
num_heads,
|
||||
num_window_sizes,
|
||||
num_mlp_ratios,
|
||||
multiscale_output=True,
|
||||
drop_paths=0.0,
|
||||
with_rpe=True,
|
||||
with_pad_mask=False,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='SyncBN', requires_grad=True),
|
||||
transformer_norm_cfg=dict(type='LN', eps=1e-6),
|
||||
with_cp=False,
|
||||
upsample_cfg=dict(mode='bilinear', align_corners=False)):
|
||||
|
||||
self.transformer_norm_cfg = transformer_norm_cfg
|
||||
self.drop_paths = drop_paths
|
||||
self.num_heads = num_heads
|
||||
self.num_window_sizes = num_window_sizes
|
||||
self.num_mlp_ratios = num_mlp_ratios
|
||||
self.with_rpe = with_rpe
|
||||
self.with_pad_mask = with_pad_mask
|
||||
|
||||
super().__init__(num_branches, block, num_blocks, num_inchannels,
|
||||
num_channels, multiscale_output, with_cp, conv_cfg,
|
||||
norm_cfg, upsample_cfg)
|
||||
|
||||
def _make_one_branch(self,
|
||||
branch_index,
|
||||
block,
|
||||
num_blocks,
|
||||
num_channels,
|
||||
stride=1):
|
||||
"""Build one branch."""
|
||||
# HRFormerBlock does not support down sample layer yet.
|
||||
assert stride == 1 and self.in_channels[branch_index] == num_channels[
|
||||
branch_index]
|
||||
layers = []
|
||||
layers.append(
|
||||
block(
|
||||
self.in_channels[branch_index],
|
||||
num_channels[branch_index],
|
||||
num_heads=self.num_heads[branch_index],
|
||||
window_size=self.num_window_sizes[branch_index],
|
||||
mlp_ratio=self.num_mlp_ratios[branch_index],
|
||||
drop_path=self.drop_paths[0],
|
||||
norm_cfg=self.norm_cfg,
|
||||
transformer_norm_cfg=self.transformer_norm_cfg,
|
||||
init_cfg=None,
|
||||
with_rpe=self.with_rpe,
|
||||
with_pad_mask=self.with_pad_mask))
|
||||
|
||||
self.in_channels[
|
||||
branch_index] = self.in_channels[branch_index] * block.expansion
|
||||
for i in range(1, num_blocks[branch_index]):
|
||||
layers.append(
|
||||
block(
|
||||
self.in_channels[branch_index],
|
||||
num_channels[branch_index],
|
||||
num_heads=self.num_heads[branch_index],
|
||||
window_size=self.num_window_sizes[branch_index],
|
||||
mlp_ratio=self.num_mlp_ratios[branch_index],
|
||||
drop_path=self.drop_paths[i],
|
||||
norm_cfg=self.norm_cfg,
|
||||
transformer_norm_cfg=self.transformer_norm_cfg,
|
||||
init_cfg=None,
|
||||
with_rpe=self.with_rpe,
|
||||
with_pad_mask=self.with_pad_mask))
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def _make_fuse_layers(self):
|
||||
"""Build fuse layers."""
|
||||
if self.num_branches == 1:
|
||||
return None
|
||||
num_branches = self.num_branches
|
||||
num_inchannels = self.in_channels
|
||||
fuse_layers = []
|
||||
for i in range(num_branches if self.multiscale_output else 1):
|
||||
fuse_layer = []
|
||||
for j in range(num_branches):
|
||||
if j > i:
|
||||
fuse_layer.append(
|
||||
nn.Sequential(
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
num_inchannels[j],
|
||||
num_inchannels[i],
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg,
|
||||
num_inchannels[i])[1],
|
||||
nn.Upsample(
|
||||
scale_factor=2**(j - i),
|
||||
mode=self.upsample_cfg['mode'],
|
||||
align_corners=self.
|
||||
upsample_cfg['align_corners'])))
|
||||
elif j == i:
|
||||
fuse_layer.append(None)
|
||||
else:
|
||||
conv3x3s = []
|
||||
for k in range(i - j):
|
||||
if k == i - j - 1:
|
||||
num_outchannels_conv3x3 = num_inchannels[i]
|
||||
with_out_act = False
|
||||
else:
|
||||
num_outchannels_conv3x3 = num_inchannels[j]
|
||||
with_out_act = True
|
||||
sub_modules = [
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
num_inchannels[j],
|
||||
num_inchannels[j],
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
groups=num_inchannels[j],
|
||||
bias=False,
|
||||
),
|
||||
build_norm_layer(self.norm_cfg,
|
||||
num_inchannels[j])[1],
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
num_inchannels[j],
|
||||
num_outchannels_conv3x3,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
bias=False,
|
||||
),
|
||||
build_norm_layer(self.norm_cfg,
|
||||
num_outchannels_conv3x3)[1]
|
||||
]
|
||||
if with_out_act:
|
||||
sub_modules.append(nn.ReLU(False))
|
||||
conv3x3s.append(nn.Sequential(*sub_modules))
|
||||
fuse_layer.append(nn.Sequential(*conv3x3s))
|
||||
fuse_layers.append(nn.ModuleList(fuse_layer))
|
||||
|
||||
return nn.ModuleList(fuse_layers)
|
||||
|
||||
def get_num_inchannels(self):
|
||||
"""Return the number of input channels."""
|
||||
return self.in_channels
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class HRFormer(HRNet):
|
||||
"""HRFormer backbone.
|
||||
|
||||
This backbone is the implementation of `HRFormer: High-Resolution
|
||||
Transformer for Dense Prediction <https://arxiv.org/abs/2110.09408>`_.
|
||||
|
||||
Args:
|
||||
extra (dict): Detailed configuration for each stage of HRNet.
|
||||
There must be 4 stages, the configuration for each stage must have
|
||||
5 keys:
|
||||
|
||||
- num_modules (int): The number of HRModule in this stage.
|
||||
- num_branches (int): The number of branches in the HRModule.
|
||||
- block (str): The type of block.
|
||||
- num_blocks (tuple): The number of blocks in each branch.
|
||||
The length must be equal to num_branches.
|
||||
- num_channels (tuple): The number of channels in each branch.
|
||||
The length must be equal to num_branches.
|
||||
in_channels (int): Number of input image channels. Normally 3.
|
||||
conv_cfg (dict): Dictionary to construct and config conv layer.
|
||||
Default: None.
|
||||
norm_cfg (dict): Config of norm layer.
|
||||
Use `SyncBN` by default.
|
||||
transformer_norm_cfg (dict): Config of transformer norm layer.
|
||||
Use `LN` by default.
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False.
|
||||
zero_init_residual (bool): Whether to use zero init for last norm layer
|
||||
in resblocks to let them behave as identity. Default: False.
|
||||
frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
|
||||
-1 means not freezing any parameters. Default: -1.
|
||||
Example:
|
||||
>>> from mmpose.models import HRFormer
|
||||
>>> import torch
|
||||
>>> extra = dict(
|
||||
>>> stage1=dict(
|
||||
>>> num_modules=1,
|
||||
>>> num_branches=1,
|
||||
>>> block='BOTTLENECK',
|
||||
>>> num_blocks=(2, ),
|
||||
>>> num_channels=(64, )),
|
||||
>>> stage2=dict(
|
||||
>>> num_modules=1,
|
||||
>>> num_branches=2,
|
||||
>>> block='HRFORMER',
|
||||
>>> window_sizes=(7, 7),
|
||||
>>> num_heads=(1, 2),
|
||||
>>> mlp_ratios=(4, 4),
|
||||
>>> num_blocks=(2, 2),
|
||||
>>> num_channels=(32, 64)),
|
||||
>>> stage3=dict(
|
||||
>>> num_modules=4,
|
||||
>>> num_branches=3,
|
||||
>>> block='HRFORMER',
|
||||
>>> window_sizes=(7, 7, 7),
|
||||
>>> num_heads=(1, 2, 4),
|
||||
>>> mlp_ratios=(4, 4, 4),
|
||||
>>> num_blocks=(2, 2, 2),
|
||||
>>> num_channels=(32, 64, 128)),
|
||||
>>> stage4=dict(
|
||||
>>> num_modules=2,
|
||||
>>> num_branches=4,
|
||||
>>> block='HRFORMER',
|
||||
>>> window_sizes=(7, 7, 7, 7),
|
||||
>>> num_heads=(1, 2, 4, 8),
|
||||
>>> mlp_ratios=(4, 4, 4, 4),
|
||||
>>> num_blocks=(2, 2, 2, 2),
|
||||
>>> num_channels=(32, 64, 128, 256)))
|
||||
>>> self = HRFormer(extra, in_channels=1)
|
||||
>>> self.eval()
|
||||
>>> inputs = torch.rand(1, 1, 32, 32)
|
||||
>>> level_outputs = self.forward(inputs)
|
||||
>>> for level_out in level_outputs:
|
||||
... print(tuple(level_out.shape))
|
||||
(1, 32, 8, 8)
|
||||
(1, 64, 4, 4)
|
||||
(1, 128, 2, 2)
|
||||
(1, 256, 1, 1)
|
||||
"""
|
||||
|
||||
blocks_dict = {'BOTTLENECK': Bottleneck, 'HRFORMERBLOCK': HRFormerBlock}
|
||||
|
||||
def __init__(self,
|
||||
extra,
|
||||
in_channels=3,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN', requires_grad=True),
|
||||
transformer_norm_cfg=dict(type='LN', eps=1e-6),
|
||||
norm_eval=False,
|
||||
with_cp=False,
|
||||
zero_init_residual=False,
|
||||
frozen_stages=-1):
|
||||
|
||||
# stochastic depth
|
||||
depths = [
|
||||
extra[stage]['num_blocks'][0] * extra[stage]['num_modules']
|
||||
for stage in ['stage2', 'stage3', 'stage4']
|
||||
]
|
||||
depth_s2, depth_s3, _ = depths
|
||||
drop_path_rate = extra['drop_path_rate']
|
||||
dpr = [
|
||||
x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))
|
||||
]
|
||||
extra['stage2']['drop_path_rates'] = dpr[0:depth_s2]
|
||||
extra['stage3']['drop_path_rates'] = dpr[depth_s2:depth_s2 + depth_s3]
|
||||
extra['stage4']['drop_path_rates'] = dpr[depth_s2 + depth_s3:]
|
||||
|
||||
# HRFormer use bilinear upsample as default
|
||||
upsample_cfg = extra.get('upsample', {
|
||||
'mode': 'bilinear',
|
||||
'align_corners': False
|
||||
})
|
||||
extra['upsample'] = upsample_cfg
|
||||
self.transformer_norm_cfg = transformer_norm_cfg
|
||||
self.with_rpe = extra.get('with_rpe', True)
|
||||
self.with_pad_mask = extra.get('with_pad_mask', False)
|
||||
|
||||
super().__init__(extra, in_channels, conv_cfg, norm_cfg, norm_eval,
|
||||
with_cp, zero_init_residual, frozen_stages)
|
||||
|
||||
def _make_stage(self,
|
||||
layer_config,
|
||||
num_inchannels,
|
||||
multiscale_output=True):
|
||||
"""Make each stage."""
|
||||
num_modules = layer_config['num_modules']
|
||||
num_branches = layer_config['num_branches']
|
||||
num_blocks = layer_config['num_blocks']
|
||||
num_channels = layer_config['num_channels']
|
||||
block = self.blocks_dict[layer_config['block']]
|
||||
num_heads = layer_config['num_heads']
|
||||
num_window_sizes = layer_config['window_sizes']
|
||||
num_mlp_ratios = layer_config['mlp_ratios']
|
||||
drop_path_rates = layer_config['drop_path_rates']
|
||||
|
||||
modules = []
|
||||
for i in range(num_modules):
|
||||
# multiscale_output is only used at the last module
|
||||
if not multiscale_output and i == num_modules - 1:
|
||||
reset_multiscale_output = False
|
||||
else:
|
||||
reset_multiscale_output = True
|
||||
|
||||
modules.append(
|
||||
HRFomerModule(
|
||||
num_branches,
|
||||
block,
|
||||
num_blocks,
|
||||
num_inchannels,
|
||||
num_channels,
|
||||
num_heads,
|
||||
num_window_sizes,
|
||||
num_mlp_ratios,
|
||||
reset_multiscale_output,
|
||||
drop_paths=drop_path_rates[num_blocks[0] *
|
||||
i:num_blocks[0] * (i + 1)],
|
||||
with_rpe=self.with_rpe,
|
||||
with_pad_mask=self.with_pad_mask,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
transformer_norm_cfg=self.transformer_norm_cfg,
|
||||
with_cp=self.with_cp,
|
||||
upsample_cfg=self.upsample_cfg))
|
||||
num_inchannels = modules[-1].get_num_inchannels()
|
||||
|
||||
return nn.Sequential(*modules), num_inchannels
|
||||
@@ -0,0 +1,604 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import (build_conv_layer, build_norm_layer, constant_init,
|
||||
normal_init)
|
||||
from torch.nn.modules.batchnorm import _BatchNorm
|
||||
|
||||
from mmpose.utils import get_root_logger
|
||||
from ..builder import BACKBONES
|
||||
from .resnet import BasicBlock, Bottleneck, get_expansion
|
||||
from .utils import load_checkpoint
|
||||
|
||||
|
||||
class HRModule(nn.Module):
|
||||
"""High-Resolution Module for HRNet.
|
||||
|
||||
In this module, every branch has 4 BasicBlocks/Bottlenecks. Fusion/Exchange
|
||||
is in this module.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
num_branches,
|
||||
blocks,
|
||||
num_blocks,
|
||||
in_channels,
|
||||
num_channels,
|
||||
multiscale_output=False,
|
||||
with_cp=False,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
upsample_cfg=dict(mode='nearest', align_corners=None)):
|
||||
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
self._check_branches(num_branches, num_blocks, in_channels,
|
||||
num_channels)
|
||||
|
||||
self.in_channels = in_channels
|
||||
self.num_branches = num_branches
|
||||
|
||||
self.multiscale_output = multiscale_output
|
||||
self.norm_cfg = norm_cfg
|
||||
self.conv_cfg = conv_cfg
|
||||
self.upsample_cfg = upsample_cfg
|
||||
self.with_cp = with_cp
|
||||
self.branches = self._make_branches(num_branches, blocks, num_blocks,
|
||||
num_channels)
|
||||
self.fuse_layers = self._make_fuse_layers()
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
|
||||
@staticmethod
|
||||
def _check_branches(num_branches, num_blocks, in_channels, num_channels):
|
||||
"""Check input to avoid ValueError."""
|
||||
if num_branches != len(num_blocks):
|
||||
error_msg = f'NUM_BRANCHES({num_branches}) ' \
|
||||
f'!= NUM_BLOCKS({len(num_blocks)})'
|
||||
raise ValueError(error_msg)
|
||||
|
||||
if num_branches != len(num_channels):
|
||||
error_msg = f'NUM_BRANCHES({num_branches}) ' \
|
||||
f'!= NUM_CHANNELS({len(num_channels)})'
|
||||
raise ValueError(error_msg)
|
||||
|
||||
if num_branches != len(in_channels):
|
||||
error_msg = f'NUM_BRANCHES({num_branches}) ' \
|
||||
f'!= NUM_INCHANNELS({len(in_channels)})'
|
||||
raise ValueError(error_msg)
|
||||
|
||||
def _make_one_branch(self,
|
||||
branch_index,
|
||||
block,
|
||||
num_blocks,
|
||||
num_channels,
|
||||
stride=1):
|
||||
"""Make one branch."""
|
||||
downsample = None
|
||||
if stride != 1 or \
|
||||
self.in_channels[branch_index] != \
|
||||
num_channels[branch_index] * get_expansion(block):
|
||||
downsample = nn.Sequential(
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
self.in_channels[branch_index],
|
||||
num_channels[branch_index] * get_expansion(block),
|
||||
kernel_size=1,
|
||||
stride=stride,
|
||||
bias=False),
|
||||
build_norm_layer(
|
||||
self.norm_cfg,
|
||||
num_channels[branch_index] * get_expansion(block))[1])
|
||||
|
||||
layers = []
|
||||
layers.append(
|
||||
block(
|
||||
self.in_channels[branch_index],
|
||||
num_channels[branch_index] * get_expansion(block),
|
||||
stride=stride,
|
||||
downsample=downsample,
|
||||
with_cp=self.with_cp,
|
||||
norm_cfg=self.norm_cfg,
|
||||
conv_cfg=self.conv_cfg))
|
||||
self.in_channels[branch_index] = \
|
||||
num_channels[branch_index] * get_expansion(block)
|
||||
for _ in range(1, num_blocks[branch_index]):
|
||||
layers.append(
|
||||
block(
|
||||
self.in_channels[branch_index],
|
||||
num_channels[branch_index] * get_expansion(block),
|
||||
with_cp=self.with_cp,
|
||||
norm_cfg=self.norm_cfg,
|
||||
conv_cfg=self.conv_cfg))
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def _make_branches(self, num_branches, block, num_blocks, num_channels):
|
||||
"""Make branches."""
|
||||
branches = []
|
||||
|
||||
for i in range(num_branches):
|
||||
branches.append(
|
||||
self._make_one_branch(i, block, num_blocks, num_channels))
|
||||
|
||||
return nn.ModuleList(branches)
|
||||
|
||||
def _make_fuse_layers(self):
|
||||
"""Make fuse layer."""
|
||||
if self.num_branches == 1:
|
||||
return None
|
||||
|
||||
num_branches = self.num_branches
|
||||
in_channels = self.in_channels
|
||||
fuse_layers = []
|
||||
num_out_branches = num_branches if self.multiscale_output else 1
|
||||
|
||||
for i in range(num_out_branches):
|
||||
fuse_layer = []
|
||||
for j in range(num_branches):
|
||||
if j > i:
|
||||
fuse_layer.append(
|
||||
nn.Sequential(
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels[j],
|
||||
in_channels[i],
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg, in_channels[i])[1],
|
||||
nn.Upsample(
|
||||
scale_factor=2**(j - i),
|
||||
mode=self.upsample_cfg['mode'],
|
||||
align_corners=self.
|
||||
upsample_cfg['align_corners'])))
|
||||
elif j == i:
|
||||
fuse_layer.append(None)
|
||||
else:
|
||||
conv_downsamples = []
|
||||
for k in range(i - j):
|
||||
if k == i - j - 1:
|
||||
conv_downsamples.append(
|
||||
nn.Sequential(
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels[j],
|
||||
in_channels[i],
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg,
|
||||
in_channels[i])[1]))
|
||||
else:
|
||||
conv_downsamples.append(
|
||||
nn.Sequential(
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels[j],
|
||||
in_channels[j],
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg,
|
||||
in_channels[j])[1],
|
||||
nn.ReLU(inplace=True)))
|
||||
fuse_layer.append(nn.Sequential(*conv_downsamples))
|
||||
fuse_layers.append(nn.ModuleList(fuse_layer))
|
||||
|
||||
return nn.ModuleList(fuse_layers)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
if self.num_branches == 1:
|
||||
return [self.branches[0](x[0])]
|
||||
|
||||
for i in range(self.num_branches):
|
||||
x[i] = self.branches[i](x[i])
|
||||
|
||||
x_fuse = []
|
||||
for i in range(len(self.fuse_layers)):
|
||||
y = 0
|
||||
for j in range(self.num_branches):
|
||||
if i == j:
|
||||
y += x[j]
|
||||
else:
|
||||
y += self.fuse_layers[i][j](x[j])
|
||||
x_fuse.append(self.relu(y))
|
||||
return x_fuse
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class HRNet(nn.Module):
|
||||
"""HRNet backbone.
|
||||
|
||||
`High-Resolution Representations for Labeling Pixels and Regions
|
||||
<https://arxiv.org/abs/1904.04514>`__
|
||||
|
||||
Args:
|
||||
extra (dict): detailed configuration for each stage of HRNet.
|
||||
in_channels (int): Number of input image channels. Default: 3.
|
||||
conv_cfg (dict): dictionary to construct and config conv layer.
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed.
|
||||
zero_init_residual (bool): whether to use zero init for last norm layer
|
||||
in resblocks to let them behave as identity.
|
||||
frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
|
||||
-1 means not freezing any parameters. Default: -1.
|
||||
|
||||
Example:
|
||||
>>> from mmpose.models import HRNet
|
||||
>>> import torch
|
||||
>>> extra = dict(
|
||||
>>> stage1=dict(
|
||||
>>> num_modules=1,
|
||||
>>> num_branches=1,
|
||||
>>> block='BOTTLENECK',
|
||||
>>> num_blocks=(4, ),
|
||||
>>> num_channels=(64, )),
|
||||
>>> stage2=dict(
|
||||
>>> num_modules=1,
|
||||
>>> num_branches=2,
|
||||
>>> block='BASIC',
|
||||
>>> num_blocks=(4, 4),
|
||||
>>> num_channels=(32, 64)),
|
||||
>>> stage3=dict(
|
||||
>>> num_modules=4,
|
||||
>>> num_branches=3,
|
||||
>>> block='BASIC',
|
||||
>>> num_blocks=(4, 4, 4),
|
||||
>>> num_channels=(32, 64, 128)),
|
||||
>>> stage4=dict(
|
||||
>>> num_modules=3,
|
||||
>>> num_branches=4,
|
||||
>>> block='BASIC',
|
||||
>>> num_blocks=(4, 4, 4, 4),
|
||||
>>> num_channels=(32, 64, 128, 256)))
|
||||
>>> self = HRNet(extra, in_channels=1)
|
||||
>>> self.eval()
|
||||
>>> inputs = torch.rand(1, 1, 32, 32)
|
||||
>>> level_outputs = self.forward(inputs)
|
||||
>>> for level_out in level_outputs:
|
||||
... print(tuple(level_out.shape))
|
||||
(1, 32, 8, 8)
|
||||
"""
|
||||
|
||||
blocks_dict = {'BASIC': BasicBlock, 'BOTTLENECK': Bottleneck}
|
||||
|
||||
def __init__(self,
|
||||
extra,
|
||||
in_channels=3,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
norm_eval=False,
|
||||
with_cp=False,
|
||||
zero_init_residual=False,
|
||||
frozen_stages=-1):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
self.extra = extra
|
||||
self.conv_cfg = conv_cfg
|
||||
self.norm_cfg = norm_cfg
|
||||
self.norm_eval = norm_eval
|
||||
self.with_cp = with_cp
|
||||
self.zero_init_residual = zero_init_residual
|
||||
self.frozen_stages = frozen_stages
|
||||
|
||||
# stem net
|
||||
self.norm1_name, norm1 = build_norm_layer(self.norm_cfg, 64, postfix=1)
|
||||
self.norm2_name, norm2 = build_norm_layer(self.norm_cfg, 64, postfix=2)
|
||||
|
||||
self.conv1 = build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels,
|
||||
64,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
bias=False)
|
||||
|
||||
self.add_module(self.norm1_name, norm1)
|
||||
self.conv2 = build_conv_layer(
|
||||
self.conv_cfg,
|
||||
64,
|
||||
64,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
bias=False)
|
||||
|
||||
self.add_module(self.norm2_name, norm2)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
|
||||
self.upsample_cfg = self.extra.get('upsample', {
|
||||
'mode': 'nearest',
|
||||
'align_corners': None
|
||||
})
|
||||
|
||||
# stage 1
|
||||
self.stage1_cfg = self.extra['stage1']
|
||||
num_channels = self.stage1_cfg['num_channels'][0]
|
||||
block_type = self.stage1_cfg['block']
|
||||
num_blocks = self.stage1_cfg['num_blocks'][0]
|
||||
|
||||
block = self.blocks_dict[block_type]
|
||||
stage1_out_channels = num_channels * get_expansion(block)
|
||||
self.layer1 = self._make_layer(block, 64, stage1_out_channels,
|
||||
num_blocks)
|
||||
|
||||
# stage 2
|
||||
self.stage2_cfg = self.extra['stage2']
|
||||
num_channels = self.stage2_cfg['num_channels']
|
||||
block_type = self.stage2_cfg['block']
|
||||
|
||||
block = self.blocks_dict[block_type]
|
||||
num_channels = [
|
||||
channel * get_expansion(block) for channel in num_channels
|
||||
]
|
||||
self.transition1 = self._make_transition_layer([stage1_out_channels],
|
||||
num_channels)
|
||||
self.stage2, pre_stage_channels = self._make_stage(
|
||||
self.stage2_cfg, num_channels)
|
||||
|
||||
# stage 3
|
||||
self.stage3_cfg = self.extra['stage3']
|
||||
num_channels = self.stage3_cfg['num_channels']
|
||||
block_type = self.stage3_cfg['block']
|
||||
|
||||
block = self.blocks_dict[block_type]
|
||||
num_channels = [
|
||||
channel * get_expansion(block) for channel in num_channels
|
||||
]
|
||||
self.transition2 = self._make_transition_layer(pre_stage_channels,
|
||||
num_channels)
|
||||
self.stage3, pre_stage_channels = self._make_stage(
|
||||
self.stage3_cfg, num_channels)
|
||||
|
||||
# stage 4
|
||||
self.stage4_cfg = self.extra['stage4']
|
||||
num_channels = self.stage4_cfg['num_channels']
|
||||
block_type = self.stage4_cfg['block']
|
||||
|
||||
block = self.blocks_dict[block_type]
|
||||
num_channels = [
|
||||
channel * get_expansion(block) for channel in num_channels
|
||||
]
|
||||
self.transition3 = self._make_transition_layer(pre_stage_channels,
|
||||
num_channels)
|
||||
|
||||
self.stage4, pre_stage_channels = self._make_stage(
|
||||
self.stage4_cfg,
|
||||
num_channels,
|
||||
multiscale_output=self.stage4_cfg.get('multiscale_output', False))
|
||||
|
||||
self._freeze_stages()
|
||||
|
||||
@property
|
||||
def norm1(self):
|
||||
"""nn.Module: the normalization layer named "norm1" """
|
||||
return getattr(self, self.norm1_name)
|
||||
|
||||
@property
|
||||
def norm2(self):
|
||||
"""nn.Module: the normalization layer named "norm2" """
|
||||
return getattr(self, self.norm2_name)
|
||||
|
||||
def _make_transition_layer(self, num_channels_pre_layer,
|
||||
num_channels_cur_layer):
|
||||
"""Make transition layer."""
|
||||
num_branches_cur = len(num_channels_cur_layer)
|
||||
num_branches_pre = len(num_channels_pre_layer)
|
||||
|
||||
transition_layers = []
|
||||
for i in range(num_branches_cur):
|
||||
if i < num_branches_pre:
|
||||
if num_channels_cur_layer[i] != num_channels_pre_layer[i]:
|
||||
transition_layers.append(
|
||||
nn.Sequential(
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
num_channels_pre_layer[i],
|
||||
num_channels_cur_layer[i],
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg,
|
||||
num_channels_cur_layer[i])[1],
|
||||
nn.ReLU(inplace=True)))
|
||||
else:
|
||||
transition_layers.append(None)
|
||||
else:
|
||||
conv_downsamples = []
|
||||
for j in range(i + 1 - num_branches_pre):
|
||||
in_channels = num_channels_pre_layer[-1]
|
||||
out_channels = num_channels_cur_layer[i] \
|
||||
if j == i - num_branches_pre else in_channels
|
||||
conv_downsamples.append(
|
||||
nn.Sequential(
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg, out_channels)[1],
|
||||
nn.ReLU(inplace=True)))
|
||||
transition_layers.append(nn.Sequential(*conv_downsamples))
|
||||
|
||||
return nn.ModuleList(transition_layers)
|
||||
|
||||
def _make_layer(self, block, in_channels, out_channels, blocks, stride=1):
|
||||
"""Make layer."""
|
||||
downsample = None
|
||||
if stride != 1 or in_channels != out_channels:
|
||||
downsample = nn.Sequential(
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=1,
|
||||
stride=stride,
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg, out_channels)[1])
|
||||
|
||||
layers = []
|
||||
layers.append(
|
||||
block(
|
||||
in_channels,
|
||||
out_channels,
|
||||
stride=stride,
|
||||
downsample=downsample,
|
||||
with_cp=self.with_cp,
|
||||
norm_cfg=self.norm_cfg,
|
||||
conv_cfg=self.conv_cfg))
|
||||
for _ in range(1, blocks):
|
||||
layers.append(
|
||||
block(
|
||||
out_channels,
|
||||
out_channels,
|
||||
with_cp=self.with_cp,
|
||||
norm_cfg=self.norm_cfg,
|
||||
conv_cfg=self.conv_cfg))
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def _make_stage(self, layer_config, in_channels, multiscale_output=True):
|
||||
"""Make stage."""
|
||||
num_modules = layer_config['num_modules']
|
||||
num_branches = layer_config['num_branches']
|
||||
num_blocks = layer_config['num_blocks']
|
||||
num_channels = layer_config['num_channels']
|
||||
block = self.blocks_dict[layer_config['block']]
|
||||
|
||||
hr_modules = []
|
||||
for i in range(num_modules):
|
||||
# multi_scale_output is only used for the last module
|
||||
if not multiscale_output and i == num_modules - 1:
|
||||
reset_multiscale_output = False
|
||||
else:
|
||||
reset_multiscale_output = True
|
||||
|
||||
hr_modules.append(
|
||||
HRModule(
|
||||
num_branches,
|
||||
block,
|
||||
num_blocks,
|
||||
in_channels,
|
||||
num_channels,
|
||||
reset_multiscale_output,
|
||||
with_cp=self.with_cp,
|
||||
norm_cfg=self.norm_cfg,
|
||||
conv_cfg=self.conv_cfg,
|
||||
upsample_cfg=self.upsample_cfg))
|
||||
|
||||
in_channels = hr_modules[-1].in_channels
|
||||
|
||||
return nn.Sequential(*hr_modules), in_channels
|
||||
|
||||
def _freeze_stages(self):
|
||||
"""Freeze parameters."""
|
||||
if self.frozen_stages >= 0:
|
||||
self.norm1.eval()
|
||||
self.norm2.eval()
|
||||
|
||||
for m in [self.conv1, self.norm1, self.conv2, self.norm2]:
|
||||
for param in m.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
for i in range(1, self.frozen_stages + 1):
|
||||
if i == 1:
|
||||
m = getattr(self, 'layer1')
|
||||
else:
|
||||
m = getattr(self, f'stage{i}')
|
||||
|
||||
m.eval()
|
||||
for param in m.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
if i < 4:
|
||||
m = getattr(self, f'transition{i}')
|
||||
m.eval()
|
||||
for param in m.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Initialize the weights in backbone.
|
||||
|
||||
Args:
|
||||
pretrained (str, optional): Path to pre-trained weights.
|
||||
Defaults to None.
|
||||
"""
|
||||
if isinstance(pretrained, str):
|
||||
logger = get_root_logger()
|
||||
load_checkpoint(self, pretrained, strict=False, logger=logger)
|
||||
elif pretrained is None:
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
normal_init(m, std=0.001)
|
||||
elif isinstance(m, (_BatchNorm, nn.GroupNorm)):
|
||||
constant_init(m, 1)
|
||||
|
||||
if self.zero_init_residual:
|
||||
for m in self.modules():
|
||||
if isinstance(m, Bottleneck):
|
||||
constant_init(m.norm3, 0)
|
||||
elif isinstance(m, BasicBlock):
|
||||
constant_init(m.norm2, 0)
|
||||
else:
|
||||
raise TypeError('pretrained must be a str or None')
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
x = self.conv1(x)
|
||||
x = self.norm1(x)
|
||||
x = self.relu(x)
|
||||
x = self.conv2(x)
|
||||
x = self.norm2(x)
|
||||
x = self.relu(x)
|
||||
x = self.layer1(x)
|
||||
|
||||
x_list = []
|
||||
for i in range(self.stage2_cfg['num_branches']):
|
||||
if self.transition1[i] is not None:
|
||||
x_list.append(self.transition1[i](x))
|
||||
else:
|
||||
x_list.append(x)
|
||||
y_list = self.stage2(x_list)
|
||||
|
||||
x_list = []
|
||||
for i in range(self.stage3_cfg['num_branches']):
|
||||
if self.transition2[i] is not None:
|
||||
x_list.append(self.transition2[i](y_list[-1]))
|
||||
else:
|
||||
x_list.append(y_list[i])
|
||||
y_list = self.stage3(x_list)
|
||||
|
||||
x_list = []
|
||||
for i in range(self.stage4_cfg['num_branches']):
|
||||
if self.transition3[i] is not None:
|
||||
x_list.append(self.transition3[i](y_list[-1]))
|
||||
else:
|
||||
x_list.append(y_list[i])
|
||||
y_list = self.stage4(x_list)
|
||||
|
||||
return y_list
|
||||
|
||||
def train(self, mode=True):
|
||||
"""Convert the model into training mode."""
|
||||
super().train(mode)
|
||||
self._freeze_stages()
|
||||
if mode and self.norm_eval:
|
||||
for m in self.modules():
|
||||
if isinstance(m, _BatchNorm):
|
||||
m.eval()
|
||||
@@ -0,0 +1,984 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Adapted from https://github.com/HRNet/Lite-HRNet
|
||||
# Original licence: Apache License 2.0.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import mmcv
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch.utils.checkpoint as cp
|
||||
from mmcv.cnn import (ConvModule, DepthwiseSeparableConvModule,
|
||||
build_conv_layer, build_norm_layer, constant_init,
|
||||
normal_init)
|
||||
from torch.nn.modules.batchnorm import _BatchNorm
|
||||
|
||||
from mmpose.utils import get_root_logger
|
||||
from ..builder import BACKBONES
|
||||
from .utils import channel_shuffle, load_checkpoint
|
||||
|
||||
|
||||
class SpatialWeighting(nn.Module):
|
||||
"""Spatial weighting module.
|
||||
|
||||
Args:
|
||||
channels (int): The channels of the module.
|
||||
ratio (int): channel reduction ratio.
|
||||
conv_cfg (dict): Config dict for convolution layer.
|
||||
Default: None, which means using conv2d.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: None.
|
||||
act_cfg (dict): Config dict for activation layer.
|
||||
Default: (dict(type='ReLU'), dict(type='Sigmoid')).
|
||||
The last ConvModule uses Sigmoid by default.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
channels,
|
||||
ratio=16,
|
||||
conv_cfg=None,
|
||||
norm_cfg=None,
|
||||
act_cfg=(dict(type='ReLU'), dict(type='Sigmoid'))):
|
||||
super().__init__()
|
||||
if isinstance(act_cfg, dict):
|
||||
act_cfg = (act_cfg, act_cfg)
|
||||
assert len(act_cfg) == 2
|
||||
assert mmcv.is_tuple_of(act_cfg, dict)
|
||||
self.global_avgpool = nn.AdaptiveAvgPool2d(1)
|
||||
self.conv1 = ConvModule(
|
||||
in_channels=channels,
|
||||
out_channels=int(channels / ratio),
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg[0])
|
||||
self.conv2 = ConvModule(
|
||||
in_channels=int(channels / ratio),
|
||||
out_channels=channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg[1])
|
||||
|
||||
def forward(self, x):
|
||||
out = self.global_avgpool(x)
|
||||
out = self.conv1(out)
|
||||
out = self.conv2(out)
|
||||
return x * out
|
||||
|
||||
|
||||
class CrossResolutionWeighting(nn.Module):
|
||||
"""Cross-resolution channel weighting module.
|
||||
|
||||
Args:
|
||||
channels (int): The channels of the module.
|
||||
ratio (int): channel reduction ratio.
|
||||
conv_cfg (dict): Config dict for convolution layer.
|
||||
Default: None, which means using conv2d.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: None.
|
||||
act_cfg (dict): Config dict for activation layer.
|
||||
Default: (dict(type='ReLU'), dict(type='Sigmoid')).
|
||||
The last ConvModule uses Sigmoid by default.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
channels,
|
||||
ratio=16,
|
||||
conv_cfg=None,
|
||||
norm_cfg=None,
|
||||
act_cfg=(dict(type='ReLU'), dict(type='Sigmoid'))):
|
||||
super().__init__()
|
||||
if isinstance(act_cfg, dict):
|
||||
act_cfg = (act_cfg, act_cfg)
|
||||
assert len(act_cfg) == 2
|
||||
assert mmcv.is_tuple_of(act_cfg, dict)
|
||||
self.channels = channels
|
||||
total_channel = sum(channels)
|
||||
self.conv1 = ConvModule(
|
||||
in_channels=total_channel,
|
||||
out_channels=int(total_channel / ratio),
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg[0])
|
||||
self.conv2 = ConvModule(
|
||||
in_channels=int(total_channel / ratio),
|
||||
out_channels=total_channel,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg[1])
|
||||
|
||||
def forward(self, x):
|
||||
mini_size = x[-1].size()[-2:]
|
||||
out = [F.adaptive_avg_pool2d(s, mini_size) for s in x[:-1]] + [x[-1]]
|
||||
out = torch.cat(out, dim=1)
|
||||
out = self.conv1(out)
|
||||
out = self.conv2(out)
|
||||
out = torch.split(out, self.channels, dim=1)
|
||||
out = [
|
||||
s * F.interpolate(a, size=s.size()[-2:], mode='nearest')
|
||||
for s, a in zip(x, out)
|
||||
]
|
||||
return out
|
||||
|
||||
|
||||
class ConditionalChannelWeighting(nn.Module):
|
||||
"""Conditional channel weighting block.
|
||||
|
||||
Args:
|
||||
in_channels (int): The input channels of the block.
|
||||
stride (int): Stride of the 3x3 convolution layer.
|
||||
reduce_ratio (int): channel reduction ratio.
|
||||
conv_cfg (dict): Config dict for convolution layer.
|
||||
Default: None, which means using conv2d.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: dict(type='BN').
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
stride,
|
||||
reduce_ratio,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
with_cp=False):
|
||||
super().__init__()
|
||||
self.with_cp = with_cp
|
||||
self.stride = stride
|
||||
assert stride in [1, 2]
|
||||
|
||||
branch_channels = [channel // 2 for channel in in_channels]
|
||||
|
||||
self.cross_resolution_weighting = CrossResolutionWeighting(
|
||||
branch_channels,
|
||||
ratio=reduce_ratio,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg)
|
||||
|
||||
self.depthwise_convs = nn.ModuleList([
|
||||
ConvModule(
|
||||
channel,
|
||||
channel,
|
||||
kernel_size=3,
|
||||
stride=self.stride,
|
||||
padding=1,
|
||||
groups=channel,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None) for channel in branch_channels
|
||||
])
|
||||
|
||||
self.spatial_weighting = nn.ModuleList([
|
||||
SpatialWeighting(channels=channel, ratio=4)
|
||||
for channel in branch_channels
|
||||
])
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
def _inner_forward(x):
|
||||
x = [s.chunk(2, dim=1) for s in x]
|
||||
x1 = [s[0] for s in x]
|
||||
x2 = [s[1] for s in x]
|
||||
|
||||
x2 = self.cross_resolution_weighting(x2)
|
||||
x2 = [dw(s) for s, dw in zip(x2, self.depthwise_convs)]
|
||||
x2 = [sw(s) for s, sw in zip(x2, self.spatial_weighting)]
|
||||
|
||||
out = [torch.cat([s1, s2], dim=1) for s1, s2 in zip(x1, x2)]
|
||||
out = [channel_shuffle(s, 2) for s in out]
|
||||
|
||||
return out
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
out = cp.checkpoint(_inner_forward, x)
|
||||
else:
|
||||
out = _inner_forward(x)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class Stem(nn.Module):
|
||||
"""Stem network block.
|
||||
|
||||
Args:
|
||||
in_channels (int): The input channels of the block.
|
||||
stem_channels (int): Output channels of the stem layer.
|
||||
out_channels (int): The output channels of the block.
|
||||
expand_ratio (int): adjusts number of channels of the hidden layer
|
||||
in InvertedResidual by this amount.
|
||||
conv_cfg (dict): Config dict for convolution layer.
|
||||
Default: None, which means using conv2d.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: dict(type='BN').
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
stem_channels,
|
||||
out_channels,
|
||||
expand_ratio,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
with_cp=False):
|
||||
super().__init__()
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
self.conv_cfg = conv_cfg
|
||||
self.norm_cfg = norm_cfg
|
||||
self.with_cp = with_cp
|
||||
|
||||
self.conv1 = ConvModule(
|
||||
in_channels=in_channels,
|
||||
out_channels=stem_channels,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
act_cfg=dict(type='ReLU'))
|
||||
|
||||
mid_channels = int(round(stem_channels * expand_ratio))
|
||||
branch_channels = stem_channels // 2
|
||||
if stem_channels == self.out_channels:
|
||||
inc_channels = self.out_channels - branch_channels
|
||||
else:
|
||||
inc_channels = self.out_channels - stem_channels
|
||||
|
||||
self.branch1 = nn.Sequential(
|
||||
ConvModule(
|
||||
branch_channels,
|
||||
branch_channels,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
groups=branch_channels,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None),
|
||||
ConvModule(
|
||||
branch_channels,
|
||||
inc_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=dict(type='ReLU')),
|
||||
)
|
||||
|
||||
self.expand_conv = ConvModule(
|
||||
branch_channels,
|
||||
mid_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=dict(type='ReLU'))
|
||||
self.depthwise_conv = ConvModule(
|
||||
mid_channels,
|
||||
mid_channels,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
groups=mid_channels,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None)
|
||||
self.linear_conv = ConvModule(
|
||||
mid_channels,
|
||||
branch_channels
|
||||
if stem_channels == self.out_channels else stem_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=dict(type='ReLU'))
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
def _inner_forward(x):
|
||||
x = self.conv1(x)
|
||||
x1, x2 = x.chunk(2, dim=1)
|
||||
|
||||
x2 = self.expand_conv(x2)
|
||||
x2 = self.depthwise_conv(x2)
|
||||
x2 = self.linear_conv(x2)
|
||||
|
||||
out = torch.cat((self.branch1(x1), x2), dim=1)
|
||||
|
||||
out = channel_shuffle(out, 2)
|
||||
|
||||
return out
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
out = cp.checkpoint(_inner_forward, x)
|
||||
else:
|
||||
out = _inner_forward(x)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class IterativeHead(nn.Module):
|
||||
"""Extra iterative head for feature learning.
|
||||
|
||||
Args:
|
||||
in_channels (int): The input channels of the block.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: dict(type='BN').
|
||||
"""
|
||||
|
||||
def __init__(self, in_channels, norm_cfg=dict(type='BN')):
|
||||
super().__init__()
|
||||
projects = []
|
||||
num_branchs = len(in_channels)
|
||||
self.in_channels = in_channels[::-1]
|
||||
|
||||
for i in range(num_branchs):
|
||||
if i != num_branchs - 1:
|
||||
projects.append(
|
||||
DepthwiseSeparableConvModule(
|
||||
in_channels=self.in_channels[i],
|
||||
out_channels=self.in_channels[i + 1],
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=dict(type='ReLU'),
|
||||
dw_act_cfg=None,
|
||||
pw_act_cfg=dict(type='ReLU')))
|
||||
else:
|
||||
projects.append(
|
||||
DepthwiseSeparableConvModule(
|
||||
in_channels=self.in_channels[i],
|
||||
out_channels=self.in_channels[i],
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=dict(type='ReLU'),
|
||||
dw_act_cfg=None,
|
||||
pw_act_cfg=dict(type='ReLU')))
|
||||
self.projects = nn.ModuleList(projects)
|
||||
|
||||
def forward(self, x):
|
||||
x = x[::-1]
|
||||
|
||||
y = []
|
||||
last_x = None
|
||||
for i, s in enumerate(x):
|
||||
if last_x is not None:
|
||||
last_x = F.interpolate(
|
||||
last_x,
|
||||
size=s.size()[-2:],
|
||||
mode='bilinear',
|
||||
align_corners=True)
|
||||
s = s + last_x
|
||||
s = self.projects[i](s)
|
||||
y.append(s)
|
||||
last_x = s
|
||||
|
||||
return y[::-1]
|
||||
|
||||
|
||||
class ShuffleUnit(nn.Module):
|
||||
"""InvertedResidual block for ShuffleNetV2 backbone.
|
||||
|
||||
Args:
|
||||
in_channels (int): The input channels of the block.
|
||||
out_channels (int): The output channels of the block.
|
||||
stride (int): Stride of the 3x3 convolution layer. Default: 1
|
||||
conv_cfg (dict): Config dict for convolution layer.
|
||||
Default: None, which means using conv2d.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: dict(type='BN').
|
||||
act_cfg (dict): Config dict for activation layer.
|
||||
Default: dict(type='ReLU').
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
stride=1,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
act_cfg=dict(type='ReLU'),
|
||||
with_cp=False):
|
||||
super().__init__()
|
||||
self.stride = stride
|
||||
self.with_cp = with_cp
|
||||
|
||||
branch_features = out_channels // 2
|
||||
if self.stride == 1:
|
||||
assert in_channels == branch_features * 2, (
|
||||
f'in_channels ({in_channels}) should equal to '
|
||||
f'branch_features * 2 ({branch_features * 2}) '
|
||||
'when stride is 1')
|
||||
|
||||
if in_channels != branch_features * 2:
|
||||
assert self.stride != 1, (
|
||||
f'stride ({self.stride}) should not equal 1 when '
|
||||
f'in_channels != branch_features * 2')
|
||||
|
||||
if self.stride > 1:
|
||||
self.branch1 = nn.Sequential(
|
||||
ConvModule(
|
||||
in_channels,
|
||||
in_channels,
|
||||
kernel_size=3,
|
||||
stride=self.stride,
|
||||
padding=1,
|
||||
groups=in_channels,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None),
|
||||
ConvModule(
|
||||
in_channels,
|
||||
branch_features,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg),
|
||||
)
|
||||
|
||||
self.branch2 = nn.Sequential(
|
||||
ConvModule(
|
||||
in_channels if (self.stride > 1) else branch_features,
|
||||
branch_features,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg),
|
||||
ConvModule(
|
||||
branch_features,
|
||||
branch_features,
|
||||
kernel_size=3,
|
||||
stride=self.stride,
|
||||
padding=1,
|
||||
groups=branch_features,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None),
|
||||
ConvModule(
|
||||
branch_features,
|
||||
branch_features,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg))
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
def _inner_forward(x):
|
||||
if self.stride > 1:
|
||||
out = torch.cat((self.branch1(x), self.branch2(x)), dim=1)
|
||||
else:
|
||||
x1, x2 = x.chunk(2, dim=1)
|
||||
out = torch.cat((x1, self.branch2(x2)), dim=1)
|
||||
|
||||
out = channel_shuffle(out, 2)
|
||||
|
||||
return out
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
out = cp.checkpoint(_inner_forward, x)
|
||||
else:
|
||||
out = _inner_forward(x)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class LiteHRModule(nn.Module):
|
||||
"""High-Resolution Module for LiteHRNet.
|
||||
|
||||
It contains conditional channel weighting blocks and
|
||||
shuffle blocks.
|
||||
|
||||
|
||||
Args:
|
||||
num_branches (int): Number of branches in the module.
|
||||
num_blocks (int): Number of blocks in the module.
|
||||
in_channels (list(int)): Number of input image channels.
|
||||
reduce_ratio (int): Channel reduction ratio.
|
||||
module_type (str): 'LITE' or 'NAIVE'
|
||||
multiscale_output (bool): Whether to output multi-scale features.
|
||||
with_fuse (bool): Whether to use fuse layers.
|
||||
conv_cfg (dict): dictionary to construct and config conv layer.
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
num_branches,
|
||||
num_blocks,
|
||||
in_channels,
|
||||
reduce_ratio,
|
||||
module_type,
|
||||
multiscale_output=False,
|
||||
with_fuse=True,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
with_cp=False,
|
||||
):
|
||||
super().__init__()
|
||||
self._check_branches(num_branches, in_channels)
|
||||
|
||||
self.in_channels = in_channels
|
||||
self.num_branches = num_branches
|
||||
|
||||
self.module_type = module_type
|
||||
self.multiscale_output = multiscale_output
|
||||
self.with_fuse = with_fuse
|
||||
self.norm_cfg = norm_cfg
|
||||
self.conv_cfg = conv_cfg
|
||||
self.with_cp = with_cp
|
||||
|
||||
if self.module_type.upper() == 'LITE':
|
||||
self.layers = self._make_weighting_blocks(num_blocks, reduce_ratio)
|
||||
elif self.module_type.upper() == 'NAIVE':
|
||||
self.layers = self._make_naive_branches(num_branches, num_blocks)
|
||||
else:
|
||||
raise ValueError("module_type should be either 'LITE' or 'NAIVE'.")
|
||||
if self.with_fuse:
|
||||
self.fuse_layers = self._make_fuse_layers()
|
||||
self.relu = nn.ReLU()
|
||||
|
||||
def _check_branches(self, num_branches, in_channels):
|
||||
"""Check input to avoid ValueError."""
|
||||
if num_branches != len(in_channels):
|
||||
error_msg = f'NUM_BRANCHES({num_branches}) ' \
|
||||
f'!= NUM_INCHANNELS({len(in_channels)})'
|
||||
raise ValueError(error_msg)
|
||||
|
||||
def _make_weighting_blocks(self, num_blocks, reduce_ratio, stride=1):
|
||||
"""Make channel weighting blocks."""
|
||||
layers = []
|
||||
for i in range(num_blocks):
|
||||
layers.append(
|
||||
ConditionalChannelWeighting(
|
||||
self.in_channels,
|
||||
stride=stride,
|
||||
reduce_ratio=reduce_ratio,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
with_cp=self.with_cp))
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def _make_one_branch(self, branch_index, num_blocks, stride=1):
|
||||
"""Make one branch."""
|
||||
layers = []
|
||||
layers.append(
|
||||
ShuffleUnit(
|
||||
self.in_channels[branch_index],
|
||||
self.in_channels[branch_index],
|
||||
stride=stride,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
act_cfg=dict(type='ReLU'),
|
||||
with_cp=self.with_cp))
|
||||
for i in range(1, num_blocks):
|
||||
layers.append(
|
||||
ShuffleUnit(
|
||||
self.in_channels[branch_index],
|
||||
self.in_channels[branch_index],
|
||||
stride=1,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
act_cfg=dict(type='ReLU'),
|
||||
with_cp=self.with_cp))
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def _make_naive_branches(self, num_branches, num_blocks):
|
||||
"""Make branches."""
|
||||
branches = []
|
||||
|
||||
for i in range(num_branches):
|
||||
branches.append(self._make_one_branch(i, num_blocks))
|
||||
|
||||
return nn.ModuleList(branches)
|
||||
|
||||
def _make_fuse_layers(self):
|
||||
"""Make fuse layer."""
|
||||
if self.num_branches == 1:
|
||||
return None
|
||||
|
||||
num_branches = self.num_branches
|
||||
in_channels = self.in_channels
|
||||
fuse_layers = []
|
||||
num_out_branches = num_branches if self.multiscale_output else 1
|
||||
for i in range(num_out_branches):
|
||||
fuse_layer = []
|
||||
for j in range(num_branches):
|
||||
if j > i:
|
||||
fuse_layer.append(
|
||||
nn.Sequential(
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels[j],
|
||||
in_channels[i],
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg, in_channels[i])[1],
|
||||
nn.Upsample(
|
||||
scale_factor=2**(j - i), mode='nearest')))
|
||||
elif j == i:
|
||||
fuse_layer.append(None)
|
||||
else:
|
||||
conv_downsamples = []
|
||||
for k in range(i - j):
|
||||
if k == i - j - 1:
|
||||
conv_downsamples.append(
|
||||
nn.Sequential(
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels[j],
|
||||
in_channels[j],
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
groups=in_channels[j],
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg,
|
||||
in_channels[j])[1],
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels[j],
|
||||
in_channels[i],
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg,
|
||||
in_channels[i])[1]))
|
||||
else:
|
||||
conv_downsamples.append(
|
||||
nn.Sequential(
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels[j],
|
||||
in_channels[j],
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
groups=in_channels[j],
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg,
|
||||
in_channels[j])[1],
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels[j],
|
||||
in_channels[j],
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg,
|
||||
in_channels[j])[1],
|
||||
nn.ReLU(inplace=True)))
|
||||
fuse_layer.append(nn.Sequential(*conv_downsamples))
|
||||
fuse_layers.append(nn.ModuleList(fuse_layer))
|
||||
|
||||
return nn.ModuleList(fuse_layers)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
if self.num_branches == 1:
|
||||
return [self.layers[0](x[0])]
|
||||
|
||||
if self.module_type.upper() == 'LITE':
|
||||
out = self.layers(x)
|
||||
elif self.module_type.upper() == 'NAIVE':
|
||||
for i in range(self.num_branches):
|
||||
x[i] = self.layers[i](x[i])
|
||||
out = x
|
||||
|
||||
if self.with_fuse:
|
||||
out_fuse = []
|
||||
for i in range(len(self.fuse_layers)):
|
||||
# `y = 0` will lead to decreased accuracy (0.5~1 mAP)
|
||||
y = out[0] if i == 0 else self.fuse_layers[i][0](out[0])
|
||||
for j in range(self.num_branches):
|
||||
if i == j:
|
||||
y += out[j]
|
||||
else:
|
||||
y += self.fuse_layers[i][j](out[j])
|
||||
out_fuse.append(self.relu(y))
|
||||
out = out_fuse
|
||||
if not self.multiscale_output:
|
||||
out = [out[0]]
|
||||
return out
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class LiteHRNet(nn.Module):
|
||||
"""Lite-HRNet backbone.
|
||||
|
||||
`Lite-HRNet: A Lightweight High-Resolution Network
|
||||
<https://arxiv.org/abs/2104.06403>`_.
|
||||
|
||||
Code adapted from 'https://github.com/HRNet/Lite-HRNet'.
|
||||
|
||||
Args:
|
||||
extra (dict): detailed configuration for each stage of HRNet.
|
||||
in_channels (int): Number of input image channels. Default: 3.
|
||||
conv_cfg (dict): dictionary to construct and config conv layer.
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed.
|
||||
|
||||
Example:
|
||||
>>> from mmpose.models import LiteHRNet
|
||||
>>> import torch
|
||||
>>> extra=dict(
|
||||
>>> stem=dict(stem_channels=32, out_channels=32, expand_ratio=1),
|
||||
>>> num_stages=3,
|
||||
>>> stages_spec=dict(
|
||||
>>> num_modules=(2, 4, 2),
|
||||
>>> num_branches=(2, 3, 4),
|
||||
>>> num_blocks=(2, 2, 2),
|
||||
>>> module_type=('LITE', 'LITE', 'LITE'),
|
||||
>>> with_fuse=(True, True, True),
|
||||
>>> reduce_ratios=(8, 8, 8),
|
||||
>>> num_channels=(
|
||||
>>> (40, 80),
|
||||
>>> (40, 80, 160),
|
||||
>>> (40, 80, 160, 320),
|
||||
>>> )),
|
||||
>>> with_head=False)
|
||||
>>> self = LiteHRNet(extra, in_channels=1)
|
||||
>>> self.eval()
|
||||
>>> inputs = torch.rand(1, 1, 32, 32)
|
||||
>>> level_outputs = self.forward(inputs)
|
||||
>>> for level_out in level_outputs:
|
||||
... print(tuple(level_out.shape))
|
||||
(1, 40, 8, 8)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
extra,
|
||||
in_channels=3,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
norm_eval=False,
|
||||
with_cp=False):
|
||||
super().__init__()
|
||||
self.extra = extra
|
||||
self.conv_cfg = conv_cfg
|
||||
self.norm_cfg = norm_cfg
|
||||
self.norm_eval = norm_eval
|
||||
self.with_cp = with_cp
|
||||
|
||||
self.stem = Stem(
|
||||
in_channels,
|
||||
stem_channels=self.extra['stem']['stem_channels'],
|
||||
out_channels=self.extra['stem']['out_channels'],
|
||||
expand_ratio=self.extra['stem']['expand_ratio'],
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg)
|
||||
|
||||
self.num_stages = self.extra['num_stages']
|
||||
self.stages_spec = self.extra['stages_spec']
|
||||
|
||||
num_channels_last = [
|
||||
self.stem.out_channels,
|
||||
]
|
||||
for i in range(self.num_stages):
|
||||
num_channels = self.stages_spec['num_channels'][i]
|
||||
num_channels = [num_channels[i] for i in range(len(num_channels))]
|
||||
setattr(
|
||||
self, f'transition{i}',
|
||||
self._make_transition_layer(num_channels_last, num_channels))
|
||||
|
||||
stage, num_channels_last = self._make_stage(
|
||||
self.stages_spec, i, num_channels, multiscale_output=True)
|
||||
setattr(self, f'stage{i}', stage)
|
||||
|
||||
self.with_head = self.extra['with_head']
|
||||
if self.with_head:
|
||||
self.head_layer = IterativeHead(
|
||||
in_channels=num_channels_last,
|
||||
norm_cfg=self.norm_cfg,
|
||||
)
|
||||
|
||||
def _make_transition_layer(self, num_channels_pre_layer,
|
||||
num_channels_cur_layer):
|
||||
"""Make transition layer."""
|
||||
num_branches_cur = len(num_channels_cur_layer)
|
||||
num_branches_pre = len(num_channels_pre_layer)
|
||||
|
||||
transition_layers = []
|
||||
for i in range(num_branches_cur):
|
||||
if i < num_branches_pre:
|
||||
if num_channels_cur_layer[i] != num_channels_pre_layer[i]:
|
||||
transition_layers.append(
|
||||
nn.Sequential(
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
num_channels_pre_layer[i],
|
||||
num_channels_pre_layer[i],
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
groups=num_channels_pre_layer[i],
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg,
|
||||
num_channels_pre_layer[i])[1],
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
num_channels_pre_layer[i],
|
||||
num_channels_cur_layer[i],
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg,
|
||||
num_channels_cur_layer[i])[1],
|
||||
nn.ReLU()))
|
||||
else:
|
||||
transition_layers.append(None)
|
||||
else:
|
||||
conv_downsamples = []
|
||||
for j in range(i + 1 - num_branches_pre):
|
||||
in_channels = num_channels_pre_layer[-1]
|
||||
out_channels = num_channels_cur_layer[i] \
|
||||
if j == i - num_branches_pre else in_channels
|
||||
conv_downsamples.append(
|
||||
nn.Sequential(
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels,
|
||||
in_channels,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
groups=in_channels,
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg, in_channels)[1],
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg, out_channels)[1],
|
||||
nn.ReLU()))
|
||||
transition_layers.append(nn.Sequential(*conv_downsamples))
|
||||
|
||||
return nn.ModuleList(transition_layers)
|
||||
|
||||
def _make_stage(self,
|
||||
stages_spec,
|
||||
stage_index,
|
||||
in_channels,
|
||||
multiscale_output=True):
|
||||
num_modules = stages_spec['num_modules'][stage_index]
|
||||
num_branches = stages_spec['num_branches'][stage_index]
|
||||
num_blocks = stages_spec['num_blocks'][stage_index]
|
||||
reduce_ratio = stages_spec['reduce_ratios'][stage_index]
|
||||
with_fuse = stages_spec['with_fuse'][stage_index]
|
||||
module_type = stages_spec['module_type'][stage_index]
|
||||
|
||||
modules = []
|
||||
for i in range(num_modules):
|
||||
# multi_scale_output is only used last module
|
||||
if not multiscale_output and i == num_modules - 1:
|
||||
reset_multiscale_output = False
|
||||
else:
|
||||
reset_multiscale_output = True
|
||||
|
||||
modules.append(
|
||||
LiteHRModule(
|
||||
num_branches,
|
||||
num_blocks,
|
||||
in_channels,
|
||||
reduce_ratio,
|
||||
module_type,
|
||||
multiscale_output=reset_multiscale_output,
|
||||
with_fuse=with_fuse,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
with_cp=self.with_cp))
|
||||
in_channels = modules[-1].in_channels
|
||||
|
||||
return nn.Sequential(*modules), in_channels
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Initialize the weights in backbone.
|
||||
|
||||
Args:
|
||||
pretrained (str, optional): Path to pre-trained weights.
|
||||
Defaults to None.
|
||||
"""
|
||||
if isinstance(pretrained, str):
|
||||
logger = get_root_logger()
|
||||
load_checkpoint(self, pretrained, strict=False, logger=logger)
|
||||
elif pretrained is None:
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
normal_init(m, std=0.001)
|
||||
elif isinstance(m, (_BatchNorm, nn.GroupNorm)):
|
||||
constant_init(m, 1)
|
||||
else:
|
||||
raise TypeError('pretrained must be a str or None')
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
x = self.stem(x)
|
||||
|
||||
y_list = [x]
|
||||
for i in range(self.num_stages):
|
||||
x_list = []
|
||||
transition = getattr(self, f'transition{i}')
|
||||
for j in range(self.stages_spec['num_branches'][i]):
|
||||
if transition[j]:
|
||||
if j >= len(y_list):
|
||||
x_list.append(transition[j](y_list[-1]))
|
||||
else:
|
||||
x_list.append(transition[j](y_list[j]))
|
||||
else:
|
||||
x_list.append(y_list[j])
|
||||
y_list = getattr(self, f'stage{i}')(x_list)
|
||||
|
||||
x = y_list
|
||||
if self.with_head:
|
||||
x = self.head_layer(x)
|
||||
|
||||
return [x[0]]
|
||||
|
||||
def train(self, mode=True):
|
||||
"""Convert the model into training mode."""
|
||||
super().train(mode)
|
||||
if mode and self.norm_eval:
|
||||
for m in self.modules():
|
||||
if isinstance(m, _BatchNorm):
|
||||
m.eval()
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
import logging
|
||||
|
||||
import torch.nn as nn
|
||||
import torch.utils.checkpoint as cp
|
||||
from mmcv.cnn import ConvModule, constant_init, kaiming_init
|
||||
from torch.nn.modules.batchnorm import _BatchNorm
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
from .utils import load_checkpoint, make_divisible
|
||||
|
||||
|
||||
class InvertedResidual(nn.Module):
|
||||
"""InvertedResidual block for MobileNetV2.
|
||||
|
||||
Args:
|
||||
in_channels (int): The input channels of the InvertedResidual block.
|
||||
out_channels (int): The output channels of the InvertedResidual block.
|
||||
stride (int): Stride of the middle (first) 3x3 convolution.
|
||||
expand_ratio (int): adjusts number of channels of the hidden layer
|
||||
in InvertedResidual by this amount.
|
||||
conv_cfg (dict): Config dict for convolution layer.
|
||||
Default: None, which means using conv2d.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: dict(type='BN').
|
||||
act_cfg (dict): Config dict for activation layer.
|
||||
Default: dict(type='ReLU6').
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
stride,
|
||||
expand_ratio,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
act_cfg=dict(type='ReLU6'),
|
||||
with_cp=False):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
act_cfg = copy.deepcopy(act_cfg)
|
||||
super().__init__()
|
||||
self.stride = stride
|
||||
assert stride in [1, 2], f'stride must in [1, 2]. ' \
|
||||
f'But received {stride}.'
|
||||
self.with_cp = with_cp
|
||||
self.use_res_connect = self.stride == 1 and in_channels == out_channels
|
||||
hidden_dim = int(round(in_channels * expand_ratio))
|
||||
|
||||
layers = []
|
||||
if expand_ratio != 1:
|
||||
layers.append(
|
||||
ConvModule(
|
||||
in_channels=in_channels,
|
||||
out_channels=hidden_dim,
|
||||
kernel_size=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg))
|
||||
layers.extend([
|
||||
ConvModule(
|
||||
in_channels=hidden_dim,
|
||||
out_channels=hidden_dim,
|
||||
kernel_size=3,
|
||||
stride=stride,
|
||||
padding=1,
|
||||
groups=hidden_dim,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg),
|
||||
ConvModule(
|
||||
in_channels=hidden_dim,
|
||||
out_channels=out_channels,
|
||||
kernel_size=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None)
|
||||
])
|
||||
self.conv = nn.Sequential(*layers)
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
def _inner_forward(x):
|
||||
if self.use_res_connect:
|
||||
return x + self.conv(x)
|
||||
return self.conv(x)
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
out = cp.checkpoint(_inner_forward, x)
|
||||
else:
|
||||
out = _inner_forward(x)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class MobileNetV2(BaseBackbone):
|
||||
"""MobileNetV2 backbone.
|
||||
|
||||
Args:
|
||||
widen_factor (float): Width multiplier, multiply number of
|
||||
channels in each layer by this amount. Default: 1.0.
|
||||
out_indices (None or Sequence[int]): Output from which stages.
|
||||
Default: (7, ).
|
||||
frozen_stages (int): Stages to be frozen (all param fixed).
|
||||
Default: -1, which means not freezing any parameters.
|
||||
conv_cfg (dict): Config dict for convolution layer.
|
||||
Default: None, which means using conv2d.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: dict(type='BN').
|
||||
act_cfg (dict): Config dict for activation layer.
|
||||
Default: dict(type='ReLU6').
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False.
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False.
|
||||
"""
|
||||
|
||||
# Parameters to build layers. 4 parameters are needed to construct a
|
||||
# layer, from left to right: expand_ratio, channel, num_blocks, stride.
|
||||
arch_settings = [[1, 16, 1, 1], [6, 24, 2, 2], [6, 32, 3, 2],
|
||||
[6, 64, 4, 2], [6, 96, 3, 1], [6, 160, 3, 2],
|
||||
[6, 320, 1, 1]]
|
||||
|
||||
def __init__(self,
|
||||
widen_factor=1.,
|
||||
out_indices=(7, ),
|
||||
frozen_stages=-1,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
act_cfg=dict(type='ReLU6'),
|
||||
norm_eval=False,
|
||||
with_cp=False):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
act_cfg = copy.deepcopy(act_cfg)
|
||||
super().__init__()
|
||||
self.widen_factor = widen_factor
|
||||
self.out_indices = out_indices
|
||||
for index in out_indices:
|
||||
if index not in range(0, 8):
|
||||
raise ValueError('the item in out_indices must in '
|
||||
f'range(0, 8). But received {index}')
|
||||
|
||||
if frozen_stages not in range(-1, 8):
|
||||
raise ValueError('frozen_stages must be in range(-1, 8). '
|
||||
f'But received {frozen_stages}')
|
||||
self.out_indices = out_indices
|
||||
self.frozen_stages = frozen_stages
|
||||
self.conv_cfg = conv_cfg
|
||||
self.norm_cfg = norm_cfg
|
||||
self.act_cfg = act_cfg
|
||||
self.norm_eval = norm_eval
|
||||
self.with_cp = with_cp
|
||||
|
||||
self.in_channels = make_divisible(32 * widen_factor, 8)
|
||||
|
||||
self.conv1 = ConvModule(
|
||||
in_channels=3,
|
||||
out_channels=self.in_channels,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
act_cfg=self.act_cfg)
|
||||
|
||||
self.layers = []
|
||||
|
||||
for i, layer_cfg in enumerate(self.arch_settings):
|
||||
expand_ratio, channel, num_blocks, stride = layer_cfg
|
||||
out_channels = make_divisible(channel * widen_factor, 8)
|
||||
inverted_res_layer = self.make_layer(
|
||||
out_channels=out_channels,
|
||||
num_blocks=num_blocks,
|
||||
stride=stride,
|
||||
expand_ratio=expand_ratio)
|
||||
layer_name = f'layer{i + 1}'
|
||||
self.add_module(layer_name, inverted_res_layer)
|
||||
self.layers.append(layer_name)
|
||||
|
||||
if widen_factor > 1.0:
|
||||
self.out_channel = int(1280 * widen_factor)
|
||||
else:
|
||||
self.out_channel = 1280
|
||||
|
||||
layer = ConvModule(
|
||||
in_channels=self.in_channels,
|
||||
out_channels=self.out_channel,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
act_cfg=self.act_cfg)
|
||||
self.add_module('conv2', layer)
|
||||
self.layers.append('conv2')
|
||||
|
||||
def make_layer(self, out_channels, num_blocks, stride, expand_ratio):
|
||||
"""Stack InvertedResidual blocks to build a layer for MobileNetV2.
|
||||
|
||||
Args:
|
||||
out_channels (int): out_channels of block.
|
||||
num_blocks (int): number of blocks.
|
||||
stride (int): stride of the first block. Default: 1
|
||||
expand_ratio (int): Expand the number of channels of the
|
||||
hidden layer in InvertedResidual by this ratio. Default: 6.
|
||||
"""
|
||||
layers = []
|
||||
for i in range(num_blocks):
|
||||
if i >= 1:
|
||||
stride = 1
|
||||
layers.append(
|
||||
InvertedResidual(
|
||||
self.in_channels,
|
||||
out_channels,
|
||||
stride,
|
||||
expand_ratio=expand_ratio,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
act_cfg=self.act_cfg,
|
||||
with_cp=self.with_cp))
|
||||
self.in_channels = out_channels
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
if isinstance(pretrained, str):
|
||||
logger = logging.getLogger()
|
||||
load_checkpoint(self, pretrained, strict=False, logger=logger)
|
||||
elif pretrained is None:
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
kaiming_init(m)
|
||||
elif isinstance(m, (_BatchNorm, nn.GroupNorm)):
|
||||
constant_init(m, 1)
|
||||
else:
|
||||
raise TypeError('pretrained must be a str or None')
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv1(x)
|
||||
|
||||
outs = []
|
||||
for i, layer_name in enumerate(self.layers):
|
||||
layer = getattr(self, layer_name)
|
||||
x = layer(x)
|
||||
if i in self.out_indices:
|
||||
outs.append(x)
|
||||
|
||||
if len(outs) == 1:
|
||||
return outs[0]
|
||||
return tuple(outs)
|
||||
|
||||
def _freeze_stages(self):
|
||||
if self.frozen_stages >= 0:
|
||||
for param in self.conv1.parameters():
|
||||
param.requires_grad = False
|
||||
for i in range(1, self.frozen_stages + 1):
|
||||
layer = getattr(self, f'layer{i}')
|
||||
layer.eval()
|
||||
for param in layer.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def train(self, mode=True):
|
||||
super().train(mode)
|
||||
self._freeze_stages()
|
||||
if mode and self.norm_eval:
|
||||
for m in self.modules():
|
||||
if isinstance(m, _BatchNorm):
|
||||
m.eval()
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
import logging
|
||||
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import ConvModule, constant_init, kaiming_init
|
||||
from torch.nn.modules.batchnorm import _BatchNorm
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
from .utils import InvertedResidual, load_checkpoint
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class MobileNetV3(BaseBackbone):
|
||||
"""MobileNetV3 backbone.
|
||||
|
||||
Args:
|
||||
arch (str): Architecture of mobilnetv3, from {small, big}.
|
||||
Default: small.
|
||||
conv_cfg (dict): Config dict for convolution layer.
|
||||
Default: None, which means using conv2d.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: dict(type='BN').
|
||||
out_indices (None or Sequence[int]): Output from which stages.
|
||||
Default: (-1, ), which means output tensors from final stage.
|
||||
frozen_stages (int): Stages to be frozen (all param fixed).
|
||||
Default: -1, which means not freezing any parameters.
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False.
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save
|
||||
some memory while slowing down the training speed.
|
||||
Default: False.
|
||||
"""
|
||||
# Parameters to build each block:
|
||||
# [kernel size, mid channels, out channels, with_se, act type, stride]
|
||||
arch_settings = {
|
||||
'small': [[3, 16, 16, True, 'ReLU', 2],
|
||||
[3, 72, 24, False, 'ReLU', 2],
|
||||
[3, 88, 24, False, 'ReLU', 1],
|
||||
[5, 96, 40, True, 'HSwish', 2],
|
||||
[5, 240, 40, True, 'HSwish', 1],
|
||||
[5, 240, 40, True, 'HSwish', 1],
|
||||
[5, 120, 48, True, 'HSwish', 1],
|
||||
[5, 144, 48, True, 'HSwish', 1],
|
||||
[5, 288, 96, True, 'HSwish', 2],
|
||||
[5, 576, 96, True, 'HSwish', 1],
|
||||
[5, 576, 96, True, 'HSwish', 1]],
|
||||
'big': [[3, 16, 16, False, 'ReLU', 1],
|
||||
[3, 64, 24, False, 'ReLU', 2],
|
||||
[3, 72, 24, False, 'ReLU', 1],
|
||||
[5, 72, 40, True, 'ReLU', 2],
|
||||
[5, 120, 40, True, 'ReLU', 1],
|
||||
[5, 120, 40, True, 'ReLU', 1],
|
||||
[3, 240, 80, False, 'HSwish', 2],
|
||||
[3, 200, 80, False, 'HSwish', 1],
|
||||
[3, 184, 80, False, 'HSwish', 1],
|
||||
[3, 184, 80, False, 'HSwish', 1],
|
||||
[3, 480, 112, True, 'HSwish', 1],
|
||||
[3, 672, 112, True, 'HSwish', 1],
|
||||
[5, 672, 160, True, 'HSwish', 1],
|
||||
[5, 672, 160, True, 'HSwish', 2],
|
||||
[5, 960, 160, True, 'HSwish', 1]]
|
||||
} # yapf: disable
|
||||
|
||||
def __init__(self,
|
||||
arch='small',
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
out_indices=(-1, ),
|
||||
frozen_stages=-1,
|
||||
norm_eval=False,
|
||||
with_cp=False):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
assert arch in self.arch_settings
|
||||
for index in out_indices:
|
||||
if index not in range(-len(self.arch_settings[arch]),
|
||||
len(self.arch_settings[arch])):
|
||||
raise ValueError('the item in out_indices must in '
|
||||
f'range(0, {len(self.arch_settings[arch])}). '
|
||||
f'But received {index}')
|
||||
|
||||
if frozen_stages not in range(-1, len(self.arch_settings[arch])):
|
||||
raise ValueError('frozen_stages must be in range(-1, '
|
||||
f'{len(self.arch_settings[arch])}). '
|
||||
f'But received {frozen_stages}')
|
||||
self.arch = arch
|
||||
self.conv_cfg = conv_cfg
|
||||
self.norm_cfg = norm_cfg
|
||||
self.out_indices = out_indices
|
||||
self.frozen_stages = frozen_stages
|
||||
self.norm_eval = norm_eval
|
||||
self.with_cp = with_cp
|
||||
|
||||
self.in_channels = 16
|
||||
self.conv1 = ConvModule(
|
||||
in_channels=3,
|
||||
out_channels=self.in_channels,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=dict(type='HSwish'))
|
||||
|
||||
self.layers = self._make_layer()
|
||||
self.feat_dim = self.arch_settings[arch][-1][2]
|
||||
|
||||
def _make_layer(self):
|
||||
layers = []
|
||||
layer_setting = self.arch_settings[self.arch]
|
||||
for i, params in enumerate(layer_setting):
|
||||
(kernel_size, mid_channels, out_channels, with_se, act,
|
||||
stride) = params
|
||||
if with_se:
|
||||
se_cfg = dict(
|
||||
channels=mid_channels,
|
||||
ratio=4,
|
||||
act_cfg=(dict(type='ReLU'), dict(type='HSigmoid')))
|
||||
else:
|
||||
se_cfg = None
|
||||
|
||||
layer = InvertedResidual(
|
||||
in_channels=self.in_channels,
|
||||
out_channels=out_channels,
|
||||
mid_channels=mid_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
se_cfg=se_cfg,
|
||||
with_expand_conv=True,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
act_cfg=dict(type=act),
|
||||
with_cp=self.with_cp)
|
||||
self.in_channels = out_channels
|
||||
layer_name = f'layer{i + 1}'
|
||||
self.add_module(layer_name, layer)
|
||||
layers.append(layer_name)
|
||||
return layers
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
if isinstance(pretrained, str):
|
||||
logger = logging.getLogger()
|
||||
load_checkpoint(self, pretrained, strict=False, logger=logger)
|
||||
elif pretrained is None:
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
kaiming_init(m)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
constant_init(m, 1)
|
||||
else:
|
||||
raise TypeError('pretrained must be a str or None')
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv1(x)
|
||||
|
||||
outs = []
|
||||
for i, layer_name in enumerate(self.layers):
|
||||
layer = getattr(self, layer_name)
|
||||
x = layer(x)
|
||||
if i in self.out_indices or \
|
||||
i - len(self.layers) in self.out_indices:
|
||||
outs.append(x)
|
||||
|
||||
if len(outs) == 1:
|
||||
return outs[0]
|
||||
return tuple(outs)
|
||||
|
||||
def _freeze_stages(self):
|
||||
if self.frozen_stages >= 0:
|
||||
for param in self.conv1.parameters():
|
||||
param.requires_grad = False
|
||||
for i in range(1, self.frozen_stages + 1):
|
||||
layer = getattr(self, f'layer{i}')
|
||||
layer.eval()
|
||||
for param in layer.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def train(self, mode=True):
|
||||
super().train(mode)
|
||||
self._freeze_stages()
|
||||
if mode and self.norm_eval:
|
||||
for m in self.modules():
|
||||
if isinstance(m, _BatchNorm):
|
||||
m.eval()
|
||||
@@ -0,0 +1,513 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy as cp
|
||||
from collections import OrderedDict
|
||||
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmcv.cnn import (ConvModule, MaxPool2d, constant_init, kaiming_init,
|
||||
normal_init)
|
||||
from mmcv.runner.checkpoint import load_state_dict
|
||||
|
||||
from mmpose.utils import get_root_logger
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
from .resnet import Bottleneck as _Bottleneck
|
||||
from .utils.utils import get_state_dict
|
||||
|
||||
|
||||
class Bottleneck(_Bottleneck):
|
||||
expansion = 4
|
||||
"""Bottleneck block for MSPN.
|
||||
|
||||
Args:
|
||||
in_channels (int): Input channels of this block.
|
||||
out_channels (int): Output channels of this block.
|
||||
stride (int): stride of the block. Default: 1
|
||||
downsample (nn.Module): downsample operation on identity branch.
|
||||
Default: None
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
"""
|
||||
|
||||
def __init__(self, in_channels, out_channels, **kwargs):
|
||||
super().__init__(in_channels, out_channels * 4, **kwargs)
|
||||
|
||||
|
||||
class DownsampleModule(nn.Module):
|
||||
"""Downsample module for MSPN.
|
||||
|
||||
Args:
|
||||
block (nn.Module): Downsample block.
|
||||
num_blocks (list): Number of blocks in each downsample unit.
|
||||
num_units (int): Numbers of downsample units. Default: 4
|
||||
has_skip (bool): Have skip connections from prior upsample
|
||||
module or not. Default:False
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
in_channels (int): Number of channels of the input feature to
|
||||
downsample module. Default: 64
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
block,
|
||||
num_blocks,
|
||||
num_units=4,
|
||||
has_skip=False,
|
||||
norm_cfg=dict(type='BN'),
|
||||
in_channels=64):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = cp.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
self.has_skip = has_skip
|
||||
self.in_channels = in_channels
|
||||
assert len(num_blocks) == num_units
|
||||
self.num_blocks = num_blocks
|
||||
self.num_units = num_units
|
||||
self.norm_cfg = norm_cfg
|
||||
self.layer1 = self._make_layer(block, in_channels, num_blocks[0])
|
||||
for i in range(1, num_units):
|
||||
module_name = f'layer{i + 1}'
|
||||
self.add_module(
|
||||
module_name,
|
||||
self._make_layer(
|
||||
block, in_channels * pow(2, i), num_blocks[i], stride=2))
|
||||
|
||||
def _make_layer(self, block, out_channels, blocks, stride=1):
|
||||
downsample = None
|
||||
if stride != 1 or self.in_channels != out_channels * block.expansion:
|
||||
downsample = ConvModule(
|
||||
self.in_channels,
|
||||
out_channels * block.expansion,
|
||||
kernel_size=1,
|
||||
stride=stride,
|
||||
padding=0,
|
||||
norm_cfg=self.norm_cfg,
|
||||
act_cfg=None,
|
||||
inplace=True)
|
||||
|
||||
units = list()
|
||||
units.append(
|
||||
block(
|
||||
self.in_channels,
|
||||
out_channels,
|
||||
stride=stride,
|
||||
downsample=downsample,
|
||||
norm_cfg=self.norm_cfg))
|
||||
self.in_channels = out_channels * block.expansion
|
||||
for _ in range(1, blocks):
|
||||
units.append(block(self.in_channels, out_channels))
|
||||
|
||||
return nn.Sequential(*units)
|
||||
|
||||
def forward(self, x, skip1, skip2):
|
||||
out = list()
|
||||
for i in range(self.num_units):
|
||||
module_name = f'layer{i + 1}'
|
||||
module_i = getattr(self, module_name)
|
||||
x = module_i(x)
|
||||
if self.has_skip:
|
||||
x = x + skip1[i] + skip2[i]
|
||||
out.append(x)
|
||||
out.reverse()
|
||||
|
||||
return tuple(out)
|
||||
|
||||
|
||||
class UpsampleUnit(nn.Module):
|
||||
"""Upsample unit for upsample module.
|
||||
|
||||
Args:
|
||||
ind (int): Indicates whether to interpolate (>0) and whether to
|
||||
generate feature map for the next hourglass-like module.
|
||||
num_units (int): Number of units that form a upsample module. Along
|
||||
with ind and gen_cross_conv, nm_units is used to decide whether
|
||||
to generate feature map for the next hourglass-like module.
|
||||
in_channels (int): Channel number of the skip-in feature maps from
|
||||
the corresponding downsample unit.
|
||||
unit_channels (int): Channel number in this unit. Default:256.
|
||||
gen_skip: (bool): Whether or not to generate skips for the posterior
|
||||
downsample module. Default:False
|
||||
gen_cross_conv (bool): Whether to generate feature map for the next
|
||||
hourglass-like module. Default:False
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
out_channels (int): Number of channels of feature output by upsample
|
||||
module. Must equal to in_channels of downsample module. Default:64
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ind,
|
||||
num_units,
|
||||
in_channels,
|
||||
unit_channels=256,
|
||||
gen_skip=False,
|
||||
gen_cross_conv=False,
|
||||
norm_cfg=dict(type='BN'),
|
||||
out_channels=64):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = cp.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
self.num_units = num_units
|
||||
self.norm_cfg = norm_cfg
|
||||
self.in_skip = ConvModule(
|
||||
in_channels,
|
||||
unit_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
norm_cfg=self.norm_cfg,
|
||||
act_cfg=None,
|
||||
inplace=True)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
|
||||
self.ind = ind
|
||||
if self.ind > 0:
|
||||
self.up_conv = ConvModule(
|
||||
unit_channels,
|
||||
unit_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
norm_cfg=self.norm_cfg,
|
||||
act_cfg=None,
|
||||
inplace=True)
|
||||
|
||||
self.gen_skip = gen_skip
|
||||
if self.gen_skip:
|
||||
self.out_skip1 = ConvModule(
|
||||
in_channels,
|
||||
in_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
norm_cfg=self.norm_cfg,
|
||||
inplace=True)
|
||||
|
||||
self.out_skip2 = ConvModule(
|
||||
unit_channels,
|
||||
in_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
norm_cfg=self.norm_cfg,
|
||||
inplace=True)
|
||||
|
||||
self.gen_cross_conv = gen_cross_conv
|
||||
if self.ind == num_units - 1 and self.gen_cross_conv:
|
||||
self.cross_conv = ConvModule(
|
||||
unit_channels,
|
||||
out_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
norm_cfg=self.norm_cfg,
|
||||
inplace=True)
|
||||
|
||||
def forward(self, x, up_x):
|
||||
out = self.in_skip(x)
|
||||
|
||||
if self.ind > 0:
|
||||
up_x = F.interpolate(
|
||||
up_x,
|
||||
size=(x.size(2), x.size(3)),
|
||||
mode='bilinear',
|
||||
align_corners=True)
|
||||
up_x = self.up_conv(up_x)
|
||||
out = out + up_x
|
||||
out = self.relu(out)
|
||||
|
||||
skip1 = None
|
||||
skip2 = None
|
||||
if self.gen_skip:
|
||||
skip1 = self.out_skip1(x)
|
||||
skip2 = self.out_skip2(out)
|
||||
|
||||
cross_conv = None
|
||||
if self.ind == self.num_units - 1 and self.gen_cross_conv:
|
||||
cross_conv = self.cross_conv(out)
|
||||
|
||||
return out, skip1, skip2, cross_conv
|
||||
|
||||
|
||||
class UpsampleModule(nn.Module):
|
||||
"""Upsample module for MSPN.
|
||||
|
||||
Args:
|
||||
unit_channels (int): Channel number in the upsample units.
|
||||
Default:256.
|
||||
num_units (int): Numbers of upsample units. Default: 4
|
||||
gen_skip (bool): Whether to generate skip for posterior downsample
|
||||
module or not. Default:False
|
||||
gen_cross_conv (bool): Whether to generate feature map for the next
|
||||
hourglass-like module. Default:False
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
out_channels (int): Number of channels of feature output by upsample
|
||||
module. Must equal to in_channels of downsample module. Default:64
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
unit_channels=256,
|
||||
num_units=4,
|
||||
gen_skip=False,
|
||||
gen_cross_conv=False,
|
||||
norm_cfg=dict(type='BN'),
|
||||
out_channels=64):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = cp.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
self.in_channels = list()
|
||||
for i in range(num_units):
|
||||
self.in_channels.append(Bottleneck.expansion * out_channels *
|
||||
pow(2, i))
|
||||
self.in_channels.reverse()
|
||||
self.num_units = num_units
|
||||
self.gen_skip = gen_skip
|
||||
self.gen_cross_conv = gen_cross_conv
|
||||
self.norm_cfg = norm_cfg
|
||||
for i in range(num_units):
|
||||
module_name = f'up{i + 1}'
|
||||
self.add_module(
|
||||
module_name,
|
||||
UpsampleUnit(
|
||||
i,
|
||||
self.num_units,
|
||||
self.in_channels[i],
|
||||
unit_channels,
|
||||
self.gen_skip,
|
||||
self.gen_cross_conv,
|
||||
norm_cfg=self.norm_cfg,
|
||||
out_channels=64))
|
||||
|
||||
def forward(self, x):
|
||||
out = list()
|
||||
skip1 = list()
|
||||
skip2 = list()
|
||||
cross_conv = None
|
||||
for i in range(self.num_units):
|
||||
module_i = getattr(self, f'up{i + 1}')
|
||||
if i == 0:
|
||||
outi, skip1_i, skip2_i, _ = module_i(x[i], None)
|
||||
elif i == self.num_units - 1:
|
||||
outi, skip1_i, skip2_i, cross_conv = module_i(x[i], out[i - 1])
|
||||
else:
|
||||
outi, skip1_i, skip2_i, _ = module_i(x[i], out[i - 1])
|
||||
out.append(outi)
|
||||
skip1.append(skip1_i)
|
||||
skip2.append(skip2_i)
|
||||
skip1.reverse()
|
||||
skip2.reverse()
|
||||
|
||||
return out, skip1, skip2, cross_conv
|
||||
|
||||
|
||||
class SingleStageNetwork(nn.Module):
|
||||
"""Single_stage Network.
|
||||
|
||||
Args:
|
||||
unit_channels (int): Channel number in the upsample units. Default:256.
|
||||
num_units (int): Numbers of downsample/upsample units. Default: 4
|
||||
gen_skip (bool): Whether to generate skip for posterior downsample
|
||||
module or not. Default:False
|
||||
gen_cross_conv (bool): Whether to generate feature map for the next
|
||||
hourglass-like module. Default:False
|
||||
has_skip (bool): Have skip connections from prior upsample
|
||||
module or not. Default:False
|
||||
num_blocks (list): Number of blocks in each downsample unit.
|
||||
Default: [2, 2, 2, 2] Note: Make sure num_units==len(num_blocks)
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
in_channels (int): Number of channels of the feature from ResNetTop.
|
||||
Default: 64.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
has_skip=False,
|
||||
gen_skip=False,
|
||||
gen_cross_conv=False,
|
||||
unit_channels=256,
|
||||
num_units=4,
|
||||
num_blocks=[2, 2, 2, 2],
|
||||
norm_cfg=dict(type='BN'),
|
||||
in_channels=64):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = cp.deepcopy(norm_cfg)
|
||||
num_blocks = cp.deepcopy(num_blocks)
|
||||
super().__init__()
|
||||
assert len(num_blocks) == num_units
|
||||
self.has_skip = has_skip
|
||||
self.gen_skip = gen_skip
|
||||
self.gen_cross_conv = gen_cross_conv
|
||||
self.num_units = num_units
|
||||
self.unit_channels = unit_channels
|
||||
self.num_blocks = num_blocks
|
||||
self.norm_cfg = norm_cfg
|
||||
|
||||
self.downsample = DownsampleModule(Bottleneck, num_blocks, num_units,
|
||||
has_skip, norm_cfg, in_channels)
|
||||
self.upsample = UpsampleModule(unit_channels, num_units, gen_skip,
|
||||
gen_cross_conv, norm_cfg, in_channels)
|
||||
|
||||
def forward(self, x, skip1, skip2):
|
||||
mid = self.downsample(x, skip1, skip2)
|
||||
out, skip1, skip2, cross_conv = self.upsample(mid)
|
||||
|
||||
return out, skip1, skip2, cross_conv
|
||||
|
||||
|
||||
class ResNetTop(nn.Module):
|
||||
"""ResNet top for MSPN.
|
||||
|
||||
Args:
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
channels (int): Number of channels of the feature output by ResNetTop.
|
||||
"""
|
||||
|
||||
def __init__(self, norm_cfg=dict(type='BN'), channels=64):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = cp.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
self.top = nn.Sequential(
|
||||
ConvModule(
|
||||
3,
|
||||
channels,
|
||||
kernel_size=7,
|
||||
stride=2,
|
||||
padding=3,
|
||||
norm_cfg=norm_cfg,
|
||||
inplace=True), MaxPool2d(kernel_size=3, stride=2, padding=1))
|
||||
|
||||
def forward(self, img):
|
||||
return self.top(img)
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class MSPN(BaseBackbone):
|
||||
"""MSPN backbone. Paper ref: Li et al. "Rethinking on Multi-Stage Networks
|
||||
for Human Pose Estimation" (CVPR 2020).
|
||||
|
||||
Args:
|
||||
unit_channels (int): Number of Channels in an upsample unit.
|
||||
Default: 256
|
||||
num_stages (int): Number of stages in a multi-stage MSPN. Default: 4
|
||||
num_units (int): Number of downsample/upsample units in a single-stage
|
||||
network. Default: 4
|
||||
Note: Make sure num_units == len(self.num_blocks)
|
||||
num_blocks (list): Number of bottlenecks in each
|
||||
downsample unit. Default: [2, 2, 2, 2]
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
res_top_channels (int): Number of channels of feature from ResNetTop.
|
||||
Default: 64.
|
||||
|
||||
Example:
|
||||
>>> from mmpose.models import MSPN
|
||||
>>> import torch
|
||||
>>> self = MSPN(num_stages=2,num_units=2,num_blocks=[2,2])
|
||||
>>> self.eval()
|
||||
>>> inputs = torch.rand(1, 3, 511, 511)
|
||||
>>> level_outputs = self.forward(inputs)
|
||||
>>> for level_output in level_outputs:
|
||||
... for feature in level_output:
|
||||
... print(tuple(feature.shape))
|
||||
...
|
||||
(1, 256, 64, 64)
|
||||
(1, 256, 128, 128)
|
||||
(1, 256, 64, 64)
|
||||
(1, 256, 128, 128)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
unit_channels=256,
|
||||
num_stages=4,
|
||||
num_units=4,
|
||||
num_blocks=[2, 2, 2, 2],
|
||||
norm_cfg=dict(type='BN'),
|
||||
res_top_channels=64):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = cp.deepcopy(norm_cfg)
|
||||
num_blocks = cp.deepcopy(num_blocks)
|
||||
super().__init__()
|
||||
self.unit_channels = unit_channels
|
||||
self.num_stages = num_stages
|
||||
self.num_units = num_units
|
||||
self.num_blocks = num_blocks
|
||||
self.norm_cfg = norm_cfg
|
||||
|
||||
assert self.num_stages > 0
|
||||
assert self.num_units > 1
|
||||
assert self.num_units == len(self.num_blocks)
|
||||
self.top = ResNetTop(norm_cfg=norm_cfg)
|
||||
self.multi_stage_mspn = nn.ModuleList([])
|
||||
for i in range(self.num_stages):
|
||||
if i == 0:
|
||||
has_skip = False
|
||||
else:
|
||||
has_skip = True
|
||||
if i != self.num_stages - 1:
|
||||
gen_skip = True
|
||||
gen_cross_conv = True
|
||||
else:
|
||||
gen_skip = False
|
||||
gen_cross_conv = False
|
||||
self.multi_stage_mspn.append(
|
||||
SingleStageNetwork(has_skip, gen_skip, gen_cross_conv,
|
||||
unit_channels, num_units, num_blocks,
|
||||
norm_cfg, res_top_channels))
|
||||
|
||||
def forward(self, x):
|
||||
"""Model forward function."""
|
||||
out_feats = []
|
||||
skip1 = None
|
||||
skip2 = None
|
||||
x = self.top(x)
|
||||
for i in range(self.num_stages):
|
||||
out, skip1, skip2, x = self.multi_stage_mspn[i](x, skip1, skip2)
|
||||
out_feats.append(out)
|
||||
|
||||
return out_feats
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Initialize model weights."""
|
||||
if isinstance(pretrained, str):
|
||||
logger = get_root_logger()
|
||||
state_dict_tmp = get_state_dict(pretrained)
|
||||
state_dict = OrderedDict()
|
||||
state_dict['top'] = OrderedDict()
|
||||
state_dict['bottlenecks'] = OrderedDict()
|
||||
for k, v in state_dict_tmp.items():
|
||||
if k.startswith('layer'):
|
||||
if 'downsample.0' in k:
|
||||
state_dict['bottlenecks'][k.replace(
|
||||
'downsample.0', 'downsample.conv')] = v
|
||||
elif 'downsample.1' in k:
|
||||
state_dict['bottlenecks'][k.replace(
|
||||
'downsample.1', 'downsample.bn')] = v
|
||||
else:
|
||||
state_dict['bottlenecks'][k] = v
|
||||
elif k.startswith('conv1'):
|
||||
state_dict['top'][k.replace('conv1', 'top.0.conv')] = v
|
||||
elif k.startswith('bn1'):
|
||||
state_dict['top'][k.replace('bn1', 'top.0.bn')] = v
|
||||
|
||||
load_state_dict(
|
||||
self.top, state_dict['top'], strict=False, logger=logger)
|
||||
for i in range(self.num_stages):
|
||||
load_state_dict(
|
||||
self.multi_stage_mspn[i].downsample,
|
||||
state_dict['bottlenecks'],
|
||||
strict=False,
|
||||
logger=logger)
|
||||
else:
|
||||
for m in self.multi_stage_mspn.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)
|
||||
|
||||
for m in self.top.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
kaiming_init(m)
|
||||
@@ -0,0 +1,317 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
|
||||
import numpy as np
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import build_conv_layer, build_norm_layer
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .resnet import ResNet
|
||||
from .resnext import Bottleneck
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class RegNet(ResNet):
|
||||
"""RegNet backbone.
|
||||
|
||||
More details can be found in `paper <https://arxiv.org/abs/2003.13678>`__ .
|
||||
|
||||
Args:
|
||||
arch (dict): The parameter of RegNets.
|
||||
- w0 (int): initial width
|
||||
- wa (float): slope of width
|
||||
- wm (float): quantization parameter to quantize the width
|
||||
- depth (int): depth of the backbone
|
||||
- group_w (int): width of group
|
||||
- bot_mul (float): bottleneck ratio, i.e. expansion of bottleneck.
|
||||
strides (Sequence[int]): Strides of the first block of each stage.
|
||||
base_channels (int): Base channels after stem layer.
|
||||
in_channels (int): Number of input image channels. Default: 3.
|
||||
dilations (Sequence[int]): Dilation of each stage.
|
||||
out_indices (Sequence[int]): Output from which stages.
|
||||
style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two
|
||||
layer is the 3x3 conv layer, otherwise the stride-two layer is
|
||||
the first 1x1 conv layer. Default: "pytorch".
|
||||
frozen_stages (int): Stages to be frozen (all param fixed). -1 means
|
||||
not freezing any parameters. Default: -1.
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN', requires_grad=True).
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False.
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False.
|
||||
zero_init_residual (bool): whether to use zero init for last norm layer
|
||||
in resblocks to let them behave as identity. Default: True.
|
||||
|
||||
Example:
|
||||
>>> from mmpose.models import RegNet
|
||||
>>> import torch
|
||||
>>> self = RegNet(
|
||||
arch=dict(
|
||||
w0=88,
|
||||
wa=26.31,
|
||||
wm=2.25,
|
||||
group_w=48,
|
||||
depth=25,
|
||||
bot_mul=1.0),
|
||||
out_indices=(0, 1, 2, 3))
|
||||
>>> self.eval()
|
||||
>>> inputs = torch.rand(1, 3, 32, 32)
|
||||
>>> level_outputs = self.forward(inputs)
|
||||
>>> for level_out in level_outputs:
|
||||
... print(tuple(level_out.shape))
|
||||
(1, 96, 8, 8)
|
||||
(1, 192, 4, 4)
|
||||
(1, 432, 2, 2)
|
||||
(1, 1008, 1, 1)
|
||||
"""
|
||||
arch_settings = {
|
||||
'regnetx_400mf':
|
||||
dict(w0=24, wa=24.48, wm=2.54, group_w=16, depth=22, bot_mul=1.0),
|
||||
'regnetx_800mf':
|
||||
dict(w0=56, wa=35.73, wm=2.28, group_w=16, depth=16, bot_mul=1.0),
|
||||
'regnetx_1.6gf':
|
||||
dict(w0=80, wa=34.01, wm=2.25, group_w=24, depth=18, bot_mul=1.0),
|
||||
'regnetx_3.2gf':
|
||||
dict(w0=88, wa=26.31, wm=2.25, group_w=48, depth=25, bot_mul=1.0),
|
||||
'regnetx_4.0gf':
|
||||
dict(w0=96, wa=38.65, wm=2.43, group_w=40, depth=23, bot_mul=1.0),
|
||||
'regnetx_6.4gf':
|
||||
dict(w0=184, wa=60.83, wm=2.07, group_w=56, depth=17, bot_mul=1.0),
|
||||
'regnetx_8.0gf':
|
||||
dict(w0=80, wa=49.56, wm=2.88, group_w=120, depth=23, bot_mul=1.0),
|
||||
'regnetx_12gf':
|
||||
dict(w0=168, wa=73.36, wm=2.37, group_w=112, depth=19, bot_mul=1.0),
|
||||
}
|
||||
|
||||
def __init__(self,
|
||||
arch,
|
||||
in_channels=3,
|
||||
stem_channels=32,
|
||||
base_channels=32,
|
||||
strides=(2, 2, 2, 2),
|
||||
dilations=(1, 1, 1, 1),
|
||||
out_indices=(3, ),
|
||||
style='pytorch',
|
||||
deep_stem=False,
|
||||
avg_down=False,
|
||||
frozen_stages=-1,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN', requires_grad=True),
|
||||
norm_eval=False,
|
||||
with_cp=False,
|
||||
zero_init_residual=True):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super(ResNet, self).__init__()
|
||||
|
||||
# Generate RegNet parameters first
|
||||
if isinstance(arch, str):
|
||||
assert arch in self.arch_settings, \
|
||||
f'"arch": "{arch}" is not one of the' \
|
||||
' arch_settings'
|
||||
arch = self.arch_settings[arch]
|
||||
elif not isinstance(arch, dict):
|
||||
raise TypeError('Expect "arch" to be either a string '
|
||||
f'or a dict, got {type(arch)}')
|
||||
|
||||
widths, num_stages = self.generate_regnet(
|
||||
arch['w0'],
|
||||
arch['wa'],
|
||||
arch['wm'],
|
||||
arch['depth'],
|
||||
)
|
||||
# Convert to per stage format
|
||||
stage_widths, stage_blocks = self.get_stages_from_blocks(widths)
|
||||
# Generate group widths and bot muls
|
||||
group_widths = [arch['group_w'] for _ in range(num_stages)]
|
||||
self.bottleneck_ratio = [arch['bot_mul'] for _ in range(num_stages)]
|
||||
# Adjust the compatibility of stage_widths and group_widths
|
||||
stage_widths, group_widths = self.adjust_width_group(
|
||||
stage_widths, self.bottleneck_ratio, group_widths)
|
||||
|
||||
# Group params by stage
|
||||
self.stage_widths = stage_widths
|
||||
self.group_widths = group_widths
|
||||
self.depth = sum(stage_blocks)
|
||||
self.stem_channels = stem_channels
|
||||
self.base_channels = base_channels
|
||||
self.num_stages = num_stages
|
||||
assert 1 <= num_stages <= 4
|
||||
self.strides = strides
|
||||
self.dilations = dilations
|
||||
assert len(strides) == len(dilations) == num_stages
|
||||
self.out_indices = out_indices
|
||||
assert max(out_indices) < num_stages
|
||||
self.style = style
|
||||
self.deep_stem = deep_stem
|
||||
if self.deep_stem:
|
||||
raise NotImplementedError(
|
||||
'deep_stem has not been implemented for RegNet')
|
||||
self.avg_down = avg_down
|
||||
self.frozen_stages = frozen_stages
|
||||
self.conv_cfg = conv_cfg
|
||||
self.norm_cfg = norm_cfg
|
||||
self.with_cp = with_cp
|
||||
self.norm_eval = norm_eval
|
||||
self.zero_init_residual = zero_init_residual
|
||||
self.stage_blocks = stage_blocks[:num_stages]
|
||||
|
||||
self._make_stem_layer(in_channels, stem_channels)
|
||||
|
||||
_in_channels = stem_channels
|
||||
self.res_layers = []
|
||||
for i, num_blocks in enumerate(self.stage_blocks):
|
||||
stride = self.strides[i]
|
||||
dilation = self.dilations[i]
|
||||
group_width = self.group_widths[i]
|
||||
width = int(round(self.stage_widths[i] * self.bottleneck_ratio[i]))
|
||||
stage_groups = width // group_width
|
||||
|
||||
res_layer = self.make_res_layer(
|
||||
block=Bottleneck,
|
||||
num_blocks=num_blocks,
|
||||
in_channels=_in_channels,
|
||||
out_channels=self.stage_widths[i],
|
||||
expansion=1,
|
||||
stride=stride,
|
||||
dilation=dilation,
|
||||
style=self.style,
|
||||
avg_down=self.avg_down,
|
||||
with_cp=self.with_cp,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
base_channels=self.stage_widths[i],
|
||||
groups=stage_groups,
|
||||
width_per_group=group_width)
|
||||
_in_channels = self.stage_widths[i]
|
||||
layer_name = f'layer{i + 1}'
|
||||
self.add_module(layer_name, res_layer)
|
||||
self.res_layers.append(layer_name)
|
||||
|
||||
self._freeze_stages()
|
||||
|
||||
self.feat_dim = stage_widths[-1]
|
||||
|
||||
def _make_stem_layer(self, in_channels, base_channels):
|
||||
self.conv1 = build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels,
|
||||
base_channels,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
bias=False)
|
||||
self.norm1_name, norm1 = build_norm_layer(
|
||||
self.norm_cfg, base_channels, postfix=1)
|
||||
self.add_module(self.norm1_name, norm1)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
|
||||
@staticmethod
|
||||
def generate_regnet(initial_width,
|
||||
width_slope,
|
||||
width_parameter,
|
||||
depth,
|
||||
divisor=8):
|
||||
"""Generates per block width from RegNet parameters.
|
||||
|
||||
Args:
|
||||
initial_width ([int]): Initial width of the backbone
|
||||
width_slope ([float]): Slope of the quantized linear function
|
||||
width_parameter ([int]): Parameter used to quantize the width.
|
||||
depth ([int]): Depth of the backbone.
|
||||
divisor (int, optional): The divisor of channels. Defaults to 8.
|
||||
|
||||
Returns:
|
||||
list, int: return a list of widths of each stage and the number of
|
||||
stages
|
||||
"""
|
||||
assert width_slope >= 0
|
||||
assert initial_width > 0
|
||||
assert width_parameter > 1
|
||||
assert initial_width % divisor == 0
|
||||
widths_cont = np.arange(depth) * width_slope + initial_width
|
||||
ks = np.round(
|
||||
np.log(widths_cont / initial_width) / np.log(width_parameter))
|
||||
widths = initial_width * np.power(width_parameter, ks)
|
||||
widths = np.round(np.divide(widths, divisor)) * divisor
|
||||
num_stages = len(np.unique(widths))
|
||||
widths, widths_cont = widths.astype(int).tolist(), widths_cont.tolist()
|
||||
return widths, num_stages
|
||||
|
||||
@staticmethod
|
||||
def quantize_float(number, divisor):
|
||||
"""Converts a float to closest non-zero int divisible by divior.
|
||||
|
||||
Args:
|
||||
number (int): Original number to be quantized.
|
||||
divisor (int): Divisor used to quantize the number.
|
||||
|
||||
Returns:
|
||||
int: quantized number that is divisible by devisor.
|
||||
"""
|
||||
return int(round(number / divisor) * divisor)
|
||||
|
||||
def adjust_width_group(self, widths, bottleneck_ratio, groups):
|
||||
"""Adjusts the compatibility of widths and groups.
|
||||
|
||||
Args:
|
||||
widths (list[int]): Width of each stage.
|
||||
bottleneck_ratio (float): Bottleneck ratio.
|
||||
groups (int): number of groups in each stage
|
||||
|
||||
Returns:
|
||||
tuple(list): The adjusted widths and groups of each stage.
|
||||
"""
|
||||
bottleneck_width = [
|
||||
int(w * b) for w, b in zip(widths, bottleneck_ratio)
|
||||
]
|
||||
groups = [min(g, w_bot) for g, w_bot in zip(groups, bottleneck_width)]
|
||||
bottleneck_width = [
|
||||
self.quantize_float(w_bot, g)
|
||||
for w_bot, g in zip(bottleneck_width, groups)
|
||||
]
|
||||
widths = [
|
||||
int(w_bot / b)
|
||||
for w_bot, b in zip(bottleneck_width, bottleneck_ratio)
|
||||
]
|
||||
return widths, groups
|
||||
|
||||
def get_stages_from_blocks(self, widths):
|
||||
"""Gets widths/stage_blocks of network at each stage.
|
||||
|
||||
Args:
|
||||
widths (list[int]): Width in each stage.
|
||||
|
||||
Returns:
|
||||
tuple(list): width and depth of each stage
|
||||
"""
|
||||
width_diff = [
|
||||
width != width_prev
|
||||
for width, width_prev in zip(widths + [0], [0] + widths)
|
||||
]
|
||||
stage_widths = [
|
||||
width for width, diff in zip(widths, width_diff[:-1]) if diff
|
||||
]
|
||||
stage_blocks = np.diff([
|
||||
depth for depth, diff in zip(range(len(width_diff)), width_diff)
|
||||
if diff
|
||||
]).tolist()
|
||||
return stage_widths, stage_blocks
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv1(x)
|
||||
x = self.norm1(x)
|
||||
x = self.relu(x)
|
||||
|
||||
outs = []
|
||||
for i, layer_name in enumerate(self.res_layers):
|
||||
res_layer = getattr(self, layer_name)
|
||||
x = res_layer(x)
|
||||
if i in self.out_indices:
|
||||
outs.append(x)
|
||||
|
||||
if len(outs) == 1:
|
||||
return outs[0]
|
||||
return tuple(outs)
|
||||
@@ -0,0 +1,338 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch.utils.checkpoint as cp
|
||||
from mmcv.cnn import build_conv_layer, build_norm_layer
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .resnet import Bottleneck as _Bottleneck
|
||||
from .resnet import ResLayer, ResNetV1d
|
||||
|
||||
|
||||
class RSoftmax(nn.Module):
|
||||
"""Radix Softmax module in ``SplitAttentionConv2d``.
|
||||
|
||||
Args:
|
||||
radix (int): Radix of input.
|
||||
groups (int): Groups of input.
|
||||
"""
|
||||
|
||||
def __init__(self, radix, groups):
|
||||
super().__init__()
|
||||
self.radix = radix
|
||||
self.groups = groups
|
||||
|
||||
def forward(self, x):
|
||||
batch = x.size(0)
|
||||
if self.radix > 1:
|
||||
x = x.view(batch, self.groups, self.radix, -1).transpose(1, 2)
|
||||
x = F.softmax(x, dim=1)
|
||||
x = x.reshape(batch, -1)
|
||||
else:
|
||||
x = torch.sigmoid(x)
|
||||
return x
|
||||
|
||||
|
||||
class SplitAttentionConv2d(nn.Module):
|
||||
"""Split-Attention Conv2d.
|
||||
|
||||
Args:
|
||||
in_channels (int): Same as nn.Conv2d.
|
||||
out_channels (int): Same as nn.Conv2d.
|
||||
kernel_size (int | tuple[int]): Same as nn.Conv2d.
|
||||
stride (int | tuple[int]): Same as nn.Conv2d.
|
||||
padding (int | tuple[int]): Same as nn.Conv2d.
|
||||
dilation (int | tuple[int]): Same as nn.Conv2d.
|
||||
groups (int): Same as nn.Conv2d.
|
||||
radix (int): Radix of SpltAtConv2d. Default: 2
|
||||
reduction_factor (int): Reduction factor of SplitAttentionConv2d.
|
||||
Default: 4.
|
||||
conv_cfg (dict): Config dict for convolution layer. Default: None,
|
||||
which means using conv2d.
|
||||
norm_cfg (dict): Config dict for normalization layer. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
channels,
|
||||
kernel_size,
|
||||
stride=1,
|
||||
padding=0,
|
||||
dilation=1,
|
||||
groups=1,
|
||||
radix=2,
|
||||
reduction_factor=4,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN')):
|
||||
super().__init__()
|
||||
inter_channels = max(in_channels * radix // reduction_factor, 32)
|
||||
self.radix = radix
|
||||
self.groups = groups
|
||||
self.channels = channels
|
||||
self.conv = build_conv_layer(
|
||||
conv_cfg,
|
||||
in_channels,
|
||||
channels * radix,
|
||||
kernel_size,
|
||||
stride=stride,
|
||||
padding=padding,
|
||||
dilation=dilation,
|
||||
groups=groups * radix,
|
||||
bias=False)
|
||||
self.norm0_name, norm0 = build_norm_layer(
|
||||
norm_cfg, channels * radix, postfix=0)
|
||||
self.add_module(self.norm0_name, norm0)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.fc1 = build_conv_layer(
|
||||
None, channels, inter_channels, 1, groups=self.groups)
|
||||
self.norm1_name, norm1 = build_norm_layer(
|
||||
norm_cfg, inter_channels, postfix=1)
|
||||
self.add_module(self.norm1_name, norm1)
|
||||
self.fc2 = build_conv_layer(
|
||||
None, inter_channels, channels * radix, 1, groups=self.groups)
|
||||
self.rsoftmax = RSoftmax(radix, groups)
|
||||
|
||||
@property
|
||||
def norm0(self):
|
||||
return getattr(self, self.norm0_name)
|
||||
|
||||
@property
|
||||
def norm1(self):
|
||||
return getattr(self, self.norm1_name)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv(x)
|
||||
x = self.norm0(x)
|
||||
x = self.relu(x)
|
||||
|
||||
batch, rchannel = x.shape[:2]
|
||||
if self.radix > 1:
|
||||
splits = x.view(batch, self.radix, -1, *x.shape[2:])
|
||||
gap = splits.sum(dim=1)
|
||||
else:
|
||||
gap = x
|
||||
gap = F.adaptive_avg_pool2d(gap, 1)
|
||||
gap = self.fc1(gap)
|
||||
|
||||
gap = self.norm1(gap)
|
||||
gap = self.relu(gap)
|
||||
|
||||
atten = self.fc2(gap)
|
||||
atten = self.rsoftmax(atten).view(batch, -1, 1, 1)
|
||||
|
||||
if self.radix > 1:
|
||||
attens = atten.view(batch, self.radix, -1, *atten.shape[2:])
|
||||
out = torch.sum(attens * splits, dim=1)
|
||||
else:
|
||||
out = atten * x
|
||||
return out.contiguous()
|
||||
|
||||
|
||||
class Bottleneck(_Bottleneck):
|
||||
"""Bottleneck block for ResNeSt.
|
||||
|
||||
Args:
|
||||
in_channels (int): Input channels of this block.
|
||||
out_channels (int): Output channels of this block.
|
||||
groups (int): Groups of conv2.
|
||||
width_per_group (int): Width per group of conv2. 64x4d indicates
|
||||
``groups=64, width_per_group=4`` and 32x8d indicates
|
||||
``groups=32, width_per_group=8``.
|
||||
radix (int): Radix of SpltAtConv2d. Default: 2
|
||||
reduction_factor (int): Reduction factor of SplitAttentionConv2d.
|
||||
Default: 4.
|
||||
avg_down_stride (bool): Whether to use average pool for stride in
|
||||
Bottleneck. Default: True.
|
||||
stride (int): stride of the block. Default: 1
|
||||
dilation (int): dilation of convolution. Default: 1
|
||||
downsample (nn.Module): downsample operation on identity branch.
|
||||
Default: None
|
||||
style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two
|
||||
layer is the 3x3 conv layer, otherwise the stride-two layer is
|
||||
the first 1x1 conv layer.
|
||||
conv_cfg (dict): dictionary to construct and config conv layer.
|
||||
Default: None
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
groups=1,
|
||||
width_per_group=4,
|
||||
base_channels=64,
|
||||
radix=2,
|
||||
reduction_factor=4,
|
||||
avg_down_stride=True,
|
||||
**kwargs):
|
||||
super().__init__(in_channels, out_channels, **kwargs)
|
||||
|
||||
self.groups = groups
|
||||
self.width_per_group = width_per_group
|
||||
|
||||
# For ResNet bottleneck, middle channels are determined by expansion
|
||||
# and out_channels, but for ResNeXt bottleneck, it is determined by
|
||||
# groups and width_per_group and the stage it is located in.
|
||||
if groups != 1:
|
||||
assert self.mid_channels % base_channels == 0
|
||||
self.mid_channels = (
|
||||
groups * width_per_group * self.mid_channels // base_channels)
|
||||
|
||||
self.avg_down_stride = avg_down_stride and self.conv2_stride > 1
|
||||
|
||||
self.norm1_name, norm1 = build_norm_layer(
|
||||
self.norm_cfg, self.mid_channels, postfix=1)
|
||||
self.norm3_name, norm3 = build_norm_layer(
|
||||
self.norm_cfg, self.out_channels, postfix=3)
|
||||
|
||||
self.conv1 = build_conv_layer(
|
||||
self.conv_cfg,
|
||||
self.in_channels,
|
||||
self.mid_channels,
|
||||
kernel_size=1,
|
||||
stride=self.conv1_stride,
|
||||
bias=False)
|
||||
self.add_module(self.norm1_name, norm1)
|
||||
self.conv2 = SplitAttentionConv2d(
|
||||
self.mid_channels,
|
||||
self.mid_channels,
|
||||
kernel_size=3,
|
||||
stride=1 if self.avg_down_stride else self.conv2_stride,
|
||||
padding=self.dilation,
|
||||
dilation=self.dilation,
|
||||
groups=groups,
|
||||
radix=radix,
|
||||
reduction_factor=reduction_factor,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg)
|
||||
delattr(self, self.norm2_name)
|
||||
|
||||
if self.avg_down_stride:
|
||||
self.avd_layer = nn.AvgPool2d(3, self.conv2_stride, padding=1)
|
||||
|
||||
self.conv3 = build_conv_layer(
|
||||
self.conv_cfg,
|
||||
self.mid_channels,
|
||||
self.out_channels,
|
||||
kernel_size=1,
|
||||
bias=False)
|
||||
self.add_module(self.norm3_name, norm3)
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
def _inner_forward(x):
|
||||
identity = x
|
||||
|
||||
out = self.conv1(x)
|
||||
out = self.norm1(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv2(out)
|
||||
|
||||
if self.avg_down_stride:
|
||||
out = self.avd_layer(out)
|
||||
|
||||
out = self.conv3(out)
|
||||
out = self.norm3(out)
|
||||
|
||||
if self.downsample is not None:
|
||||
identity = self.downsample(x)
|
||||
|
||||
out += identity
|
||||
|
||||
return out
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
out = cp.checkpoint(_inner_forward, x)
|
||||
else:
|
||||
out = _inner_forward(x)
|
||||
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class ResNeSt(ResNetV1d):
|
||||
"""ResNeSt backbone.
|
||||
|
||||
Please refer to the `paper <https://arxiv.org/pdf/2004.08955.pdf>`__
|
||||
for details.
|
||||
|
||||
Args:
|
||||
depth (int): Network depth, from {50, 101, 152, 200}.
|
||||
groups (int): Groups of conv2 in Bottleneck. Default: 32.
|
||||
width_per_group (int): Width per group of conv2 in Bottleneck.
|
||||
Default: 4.
|
||||
radix (int): Radix of SpltAtConv2d. Default: 2
|
||||
reduction_factor (int): Reduction factor of SplitAttentionConv2d.
|
||||
Default: 4.
|
||||
avg_down_stride (bool): Whether to use average pool for stride in
|
||||
Bottleneck. Default: True.
|
||||
in_channels (int): Number of input image channels. Default: 3.
|
||||
stem_channels (int): Output channels of the stem layer. Default: 64.
|
||||
num_stages (int): Stages of the network. Default: 4.
|
||||
strides (Sequence[int]): Strides of the first block of each stage.
|
||||
Default: ``(1, 2, 2, 2)``.
|
||||
dilations (Sequence[int]): Dilation of each stage.
|
||||
Default: ``(1, 1, 1, 1)``.
|
||||
out_indices (Sequence[int]): Output from which stages. If only one
|
||||
stage is specified, a single tensor (feature map) is returned,
|
||||
otherwise multiple stages are specified, a tuple of tensors will
|
||||
be returned. Default: ``(3, )``.
|
||||
style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two
|
||||
layer is the 3x3 conv layer, otherwise the stride-two layer is
|
||||
the first 1x1 conv layer.
|
||||
deep_stem (bool): Replace 7x7 conv in input stem with 3 3x3 conv.
|
||||
Default: False.
|
||||
avg_down (bool): Use AvgPool instead of stride conv when
|
||||
downsampling in the bottleneck. Default: False.
|
||||
frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
|
||||
-1 means not freezing any parameters. Default: -1.
|
||||
conv_cfg (dict | None): The config dict for conv layers. Default: None.
|
||||
norm_cfg (dict): The config dict for norm layers.
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False.
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False.
|
||||
zero_init_residual (bool): Whether to use zero init for last norm layer
|
||||
in resblocks to let them behave as identity. Default: True.
|
||||
"""
|
||||
|
||||
arch_settings = {
|
||||
50: (Bottleneck, (3, 4, 6, 3)),
|
||||
101: (Bottleneck, (3, 4, 23, 3)),
|
||||
152: (Bottleneck, (3, 8, 36, 3)),
|
||||
200: (Bottleneck, (3, 24, 36, 3)),
|
||||
269: (Bottleneck, (3, 30, 48, 8))
|
||||
}
|
||||
|
||||
def __init__(self,
|
||||
depth,
|
||||
groups=1,
|
||||
width_per_group=4,
|
||||
radix=2,
|
||||
reduction_factor=4,
|
||||
avg_down_stride=True,
|
||||
**kwargs):
|
||||
self.groups = groups
|
||||
self.width_per_group = width_per_group
|
||||
self.radix = radix
|
||||
self.reduction_factor = reduction_factor
|
||||
self.avg_down_stride = avg_down_stride
|
||||
super().__init__(depth=depth, **kwargs)
|
||||
|
||||
def make_res_layer(self, **kwargs):
|
||||
return ResLayer(
|
||||
groups=self.groups,
|
||||
width_per_group=self.width_per_group,
|
||||
base_channels=self.base_channels,
|
||||
radix=self.radix,
|
||||
reduction_factor=self.reduction_factor,
|
||||
avg_down_stride=self.avg_down_stride,
|
||||
**kwargs)
|
||||
@@ -0,0 +1,701 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
|
||||
import torch.nn as nn
|
||||
import torch.utils.checkpoint as cp
|
||||
from mmcv.cnn import (ConvModule, build_conv_layer, build_norm_layer,
|
||||
constant_init, kaiming_init)
|
||||
from mmcv.utils.parrots_wrapper import _BatchNorm
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
|
||||
|
||||
class BasicBlock(nn.Module):
|
||||
"""BasicBlock for ResNet.
|
||||
|
||||
Args:
|
||||
in_channels (int): Input channels of this block.
|
||||
out_channels (int): Output channels of this block.
|
||||
expansion (int): The ratio of ``out_channels/mid_channels`` where
|
||||
``mid_channels`` is the output channels of conv1. This is a
|
||||
reserved argument in BasicBlock and should always be 1. Default: 1.
|
||||
stride (int): stride of the block. Default: 1
|
||||
dilation (int): dilation of convolution. Default: 1
|
||||
downsample (nn.Module): downsample operation on identity branch.
|
||||
Default: None.
|
||||
style (str): `pytorch` or `caffe`. It is unused and reserved for
|
||||
unified API with Bottleneck.
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed.
|
||||
conv_cfg (dict): dictionary to construct and config conv layer.
|
||||
Default: None
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
expansion=1,
|
||||
stride=1,
|
||||
dilation=1,
|
||||
downsample=None,
|
||||
style='pytorch',
|
||||
with_cp=False,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN')):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
self.expansion = expansion
|
||||
assert self.expansion == 1
|
||||
assert out_channels % expansion == 0
|
||||
self.mid_channels = out_channels // expansion
|
||||
self.stride = stride
|
||||
self.dilation = dilation
|
||||
self.style = style
|
||||
self.with_cp = with_cp
|
||||
self.conv_cfg = conv_cfg
|
||||
self.norm_cfg = norm_cfg
|
||||
|
||||
self.norm1_name, norm1 = build_norm_layer(
|
||||
norm_cfg, self.mid_channels, postfix=1)
|
||||
self.norm2_name, norm2 = build_norm_layer(
|
||||
norm_cfg, out_channels, postfix=2)
|
||||
|
||||
self.conv1 = build_conv_layer(
|
||||
conv_cfg,
|
||||
in_channels,
|
||||
self.mid_channels,
|
||||
3,
|
||||
stride=stride,
|
||||
padding=dilation,
|
||||
dilation=dilation,
|
||||
bias=False)
|
||||
self.add_module(self.norm1_name, norm1)
|
||||
self.conv2 = build_conv_layer(
|
||||
conv_cfg,
|
||||
self.mid_channels,
|
||||
out_channels,
|
||||
3,
|
||||
padding=1,
|
||||
bias=False)
|
||||
self.add_module(self.norm2_name, norm2)
|
||||
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.downsample = downsample
|
||||
|
||||
@property
|
||||
def norm1(self):
|
||||
"""nn.Module: the normalization layer named "norm1" """
|
||||
return getattr(self, self.norm1_name)
|
||||
|
||||
@property
|
||||
def norm2(self):
|
||||
"""nn.Module: the normalization layer named "norm2" """
|
||||
return getattr(self, self.norm2_name)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
|
||||
def _inner_forward(x):
|
||||
identity = x
|
||||
|
||||
out = self.conv1(x)
|
||||
out = self.norm1(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv2(out)
|
||||
out = self.norm2(out)
|
||||
|
||||
if self.downsample is not None:
|
||||
identity = self.downsample(x)
|
||||
|
||||
out += identity
|
||||
|
||||
return out
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
out = cp.checkpoint(_inner_forward, x)
|
||||
else:
|
||||
out = _inner_forward(x)
|
||||
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class Bottleneck(nn.Module):
|
||||
"""Bottleneck block for ResNet.
|
||||
|
||||
Args:
|
||||
in_channels (int): Input channels of this block.
|
||||
out_channels (int): Output channels of this block.
|
||||
expansion (int): The ratio of ``out_channels/mid_channels`` where
|
||||
``mid_channels`` is the input/output channels of conv2. Default: 4.
|
||||
stride (int): stride of the block. Default: 1
|
||||
dilation (int): dilation of convolution. Default: 1
|
||||
downsample (nn.Module): downsample operation on identity branch.
|
||||
Default: None.
|
||||
style (str): ``"pytorch"`` or ``"caffe"``. If set to "pytorch", the
|
||||
stride-two layer is the 3x3 conv layer, otherwise the stride-two
|
||||
layer is the first 1x1 conv layer. Default: "pytorch".
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed.
|
||||
conv_cfg (dict): dictionary to construct and config conv layer.
|
||||
Default: None
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
expansion=4,
|
||||
stride=1,
|
||||
dilation=1,
|
||||
downsample=None,
|
||||
style='pytorch',
|
||||
with_cp=False,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN')):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
assert style in ['pytorch', 'caffe']
|
||||
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
self.expansion = expansion
|
||||
assert out_channels % expansion == 0
|
||||
self.mid_channels = out_channels // expansion
|
||||
self.stride = stride
|
||||
self.dilation = dilation
|
||||
self.style = style
|
||||
self.with_cp = with_cp
|
||||
self.conv_cfg = conv_cfg
|
||||
self.norm_cfg = norm_cfg
|
||||
|
||||
if self.style == 'pytorch':
|
||||
self.conv1_stride = 1
|
||||
self.conv2_stride = stride
|
||||
else:
|
||||
self.conv1_stride = stride
|
||||
self.conv2_stride = 1
|
||||
|
||||
self.norm1_name, norm1 = build_norm_layer(
|
||||
norm_cfg, self.mid_channels, postfix=1)
|
||||
self.norm2_name, norm2 = build_norm_layer(
|
||||
norm_cfg, self.mid_channels, postfix=2)
|
||||
self.norm3_name, norm3 = build_norm_layer(
|
||||
norm_cfg, out_channels, postfix=3)
|
||||
|
||||
self.conv1 = build_conv_layer(
|
||||
conv_cfg,
|
||||
in_channels,
|
||||
self.mid_channels,
|
||||
kernel_size=1,
|
||||
stride=self.conv1_stride,
|
||||
bias=False)
|
||||
self.add_module(self.norm1_name, norm1)
|
||||
self.conv2 = build_conv_layer(
|
||||
conv_cfg,
|
||||
self.mid_channels,
|
||||
self.mid_channels,
|
||||
kernel_size=3,
|
||||
stride=self.conv2_stride,
|
||||
padding=dilation,
|
||||
dilation=dilation,
|
||||
bias=False)
|
||||
|
||||
self.add_module(self.norm2_name, norm2)
|
||||
self.conv3 = build_conv_layer(
|
||||
conv_cfg,
|
||||
self.mid_channels,
|
||||
out_channels,
|
||||
kernel_size=1,
|
||||
bias=False)
|
||||
self.add_module(self.norm3_name, norm3)
|
||||
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.downsample = downsample
|
||||
|
||||
@property
|
||||
def norm1(self):
|
||||
"""nn.Module: the normalization layer named "norm1" """
|
||||
return getattr(self, self.norm1_name)
|
||||
|
||||
@property
|
||||
def norm2(self):
|
||||
"""nn.Module: the normalization layer named "norm2" """
|
||||
return getattr(self, self.norm2_name)
|
||||
|
||||
@property
|
||||
def norm3(self):
|
||||
"""nn.Module: the normalization layer named "norm3" """
|
||||
return getattr(self, self.norm3_name)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
|
||||
def _inner_forward(x):
|
||||
identity = x
|
||||
|
||||
out = self.conv1(x)
|
||||
out = self.norm1(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv2(out)
|
||||
out = self.norm2(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv3(out)
|
||||
out = self.norm3(out)
|
||||
|
||||
if self.downsample is not None:
|
||||
identity = self.downsample(x)
|
||||
|
||||
out += identity
|
||||
|
||||
return out
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
out = cp.checkpoint(_inner_forward, x)
|
||||
else:
|
||||
out = _inner_forward(x)
|
||||
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def get_expansion(block, expansion=None):
|
||||
"""Get the expansion of a residual block.
|
||||
|
||||
The block expansion will be obtained by the following order:
|
||||
|
||||
1. If ``expansion`` is given, just return it.
|
||||
2. If ``block`` has the attribute ``expansion``, then return
|
||||
``block.expansion``.
|
||||
3. Return the default value according the the block type:
|
||||
1 for ``BasicBlock`` and 4 for ``Bottleneck``.
|
||||
|
||||
Args:
|
||||
block (class): The block class.
|
||||
expansion (int | None): The given expansion ratio.
|
||||
|
||||
Returns:
|
||||
int: The expansion of the block.
|
||||
"""
|
||||
if isinstance(expansion, int):
|
||||
assert expansion > 0
|
||||
elif expansion is None:
|
||||
if hasattr(block, 'expansion'):
|
||||
expansion = block.expansion
|
||||
elif issubclass(block, BasicBlock):
|
||||
expansion = 1
|
||||
elif issubclass(block, Bottleneck):
|
||||
expansion = 4
|
||||
else:
|
||||
raise TypeError(f'expansion is not specified for {block.__name__}')
|
||||
else:
|
||||
raise TypeError('expansion must be an integer or None')
|
||||
|
||||
return expansion
|
||||
|
||||
|
||||
class ResLayer(nn.Sequential):
|
||||
"""ResLayer to build ResNet style backbone.
|
||||
|
||||
Args:
|
||||
block (nn.Module): Residual block used to build ResLayer.
|
||||
num_blocks (int): Number of blocks.
|
||||
in_channels (int): Input channels of this block.
|
||||
out_channels (int): Output channels of this block.
|
||||
expansion (int, optional): The expansion for BasicBlock/Bottleneck.
|
||||
If not specified, it will firstly be obtained via
|
||||
``block.expansion``. If the block has no attribute "expansion",
|
||||
the following default values will be used: 1 for BasicBlock and
|
||||
4 for Bottleneck. Default: None.
|
||||
stride (int): stride of the first block. Default: 1.
|
||||
avg_down (bool): Use AvgPool instead of stride conv when
|
||||
downsampling in the bottleneck. Default: False
|
||||
conv_cfg (dict): dictionary to construct and config conv layer.
|
||||
Default: None
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
downsample_first (bool): Downsample at the first block or last block.
|
||||
False for Hourglass, True for ResNet. Default: True
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
block,
|
||||
num_blocks,
|
||||
in_channels,
|
||||
out_channels,
|
||||
expansion=None,
|
||||
stride=1,
|
||||
avg_down=False,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
downsample_first=True,
|
||||
**kwargs):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
self.block = block
|
||||
self.expansion = get_expansion(block, expansion)
|
||||
|
||||
downsample = None
|
||||
if stride != 1 or in_channels != out_channels:
|
||||
downsample = []
|
||||
conv_stride = stride
|
||||
if avg_down and stride != 1:
|
||||
conv_stride = 1
|
||||
downsample.append(
|
||||
nn.AvgPool2d(
|
||||
kernel_size=stride,
|
||||
stride=stride,
|
||||
ceil_mode=True,
|
||||
count_include_pad=False))
|
||||
downsample.extend([
|
||||
build_conv_layer(
|
||||
conv_cfg,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=1,
|
||||
stride=conv_stride,
|
||||
bias=False),
|
||||
build_norm_layer(norm_cfg, out_channels)[1]
|
||||
])
|
||||
downsample = nn.Sequential(*downsample)
|
||||
|
||||
layers = []
|
||||
if downsample_first:
|
||||
layers.append(
|
||||
block(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
expansion=self.expansion,
|
||||
stride=stride,
|
||||
downsample=downsample,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
**kwargs))
|
||||
in_channels = out_channels
|
||||
for _ in range(1, num_blocks):
|
||||
layers.append(
|
||||
block(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
expansion=self.expansion,
|
||||
stride=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
**kwargs))
|
||||
else: # downsample_first=False is for HourglassModule
|
||||
for i in range(0, num_blocks - 1):
|
||||
layers.append(
|
||||
block(
|
||||
in_channels=in_channels,
|
||||
out_channels=in_channels,
|
||||
expansion=self.expansion,
|
||||
stride=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
**kwargs))
|
||||
layers.append(
|
||||
block(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
expansion=self.expansion,
|
||||
stride=stride,
|
||||
downsample=downsample,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
**kwargs))
|
||||
|
||||
super().__init__(*layers)
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class ResNet(BaseBackbone):
|
||||
"""ResNet backbone.
|
||||
|
||||
Please refer to the `paper <https://arxiv.org/abs/1512.03385>`__ for
|
||||
details.
|
||||
|
||||
Args:
|
||||
depth (int): Network depth, from {18, 34, 50, 101, 152}.
|
||||
in_channels (int): Number of input image channels. Default: 3.
|
||||
stem_channels (int): Output channels of the stem layer. Default: 64.
|
||||
base_channels (int): Middle channels of the first stage. Default: 64.
|
||||
num_stages (int): Stages of the network. Default: 4.
|
||||
strides (Sequence[int]): Strides of the first block of each stage.
|
||||
Default: ``(1, 2, 2, 2)``.
|
||||
dilations (Sequence[int]): Dilation of each stage.
|
||||
Default: ``(1, 1, 1, 1)``.
|
||||
out_indices (Sequence[int]): Output from which stages. If only one
|
||||
stage is specified, a single tensor (feature map) is returned,
|
||||
otherwise multiple stages are specified, a tuple of tensors will
|
||||
be returned. Default: ``(3, )``.
|
||||
style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two
|
||||
layer is the 3x3 conv layer, otherwise the stride-two layer is
|
||||
the first 1x1 conv layer.
|
||||
deep_stem (bool): Replace 7x7 conv in input stem with 3 3x3 conv.
|
||||
Default: False.
|
||||
avg_down (bool): Use AvgPool instead of stride conv when
|
||||
downsampling in the bottleneck. Default: False.
|
||||
frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
|
||||
-1 means not freezing any parameters. Default: -1.
|
||||
conv_cfg (dict | None): The config dict for conv layers. Default: None.
|
||||
norm_cfg (dict): The config dict for norm layers.
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False.
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False.
|
||||
zero_init_residual (bool): Whether to use zero init for last norm layer
|
||||
in resblocks to let them behave as identity. Default: True.
|
||||
|
||||
Example:
|
||||
>>> from mmpose.models import ResNet
|
||||
>>> import torch
|
||||
>>> self = ResNet(depth=18, out_indices=(0, 1, 2, 3))
|
||||
>>> self.eval()
|
||||
>>> inputs = torch.rand(1, 3, 32, 32)
|
||||
>>> level_outputs = self.forward(inputs)
|
||||
>>> for level_out in level_outputs:
|
||||
... print(tuple(level_out.shape))
|
||||
(1, 64, 8, 8)
|
||||
(1, 128, 4, 4)
|
||||
(1, 256, 2, 2)
|
||||
(1, 512, 1, 1)
|
||||
"""
|
||||
|
||||
arch_settings = {
|
||||
18: (BasicBlock, (2, 2, 2, 2)),
|
||||
34: (BasicBlock, (3, 4, 6, 3)),
|
||||
50: (Bottleneck, (3, 4, 6, 3)),
|
||||
101: (Bottleneck, (3, 4, 23, 3)),
|
||||
152: (Bottleneck, (3, 8, 36, 3))
|
||||
}
|
||||
|
||||
def __init__(self,
|
||||
depth,
|
||||
in_channels=3,
|
||||
stem_channels=64,
|
||||
base_channels=64,
|
||||
expansion=None,
|
||||
num_stages=4,
|
||||
strides=(1, 2, 2, 2),
|
||||
dilations=(1, 1, 1, 1),
|
||||
out_indices=(3, ),
|
||||
style='pytorch',
|
||||
deep_stem=False,
|
||||
avg_down=False,
|
||||
frozen_stages=-1,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN', requires_grad=True),
|
||||
norm_eval=False,
|
||||
with_cp=False,
|
||||
zero_init_residual=True):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
if depth not in self.arch_settings:
|
||||
raise KeyError(f'invalid depth {depth} for resnet')
|
||||
self.depth = depth
|
||||
self.stem_channels = stem_channels
|
||||
self.base_channels = base_channels
|
||||
self.num_stages = num_stages
|
||||
assert 1 <= num_stages <= 4
|
||||
self.strides = strides
|
||||
self.dilations = dilations
|
||||
assert len(strides) == len(dilations) == num_stages
|
||||
self.out_indices = out_indices
|
||||
assert max(out_indices) < num_stages
|
||||
self.style = style
|
||||
self.deep_stem = deep_stem
|
||||
self.avg_down = avg_down
|
||||
self.frozen_stages = frozen_stages
|
||||
self.conv_cfg = conv_cfg
|
||||
self.norm_cfg = norm_cfg
|
||||
self.with_cp = with_cp
|
||||
self.norm_eval = norm_eval
|
||||
self.zero_init_residual = zero_init_residual
|
||||
self.block, stage_blocks = self.arch_settings[depth]
|
||||
self.stage_blocks = stage_blocks[:num_stages]
|
||||
self.expansion = get_expansion(self.block, expansion)
|
||||
|
||||
self._make_stem_layer(in_channels, stem_channels)
|
||||
|
||||
self.res_layers = []
|
||||
_in_channels = stem_channels
|
||||
_out_channels = base_channels * self.expansion
|
||||
for i, num_blocks in enumerate(self.stage_blocks):
|
||||
stride = strides[i]
|
||||
dilation = dilations[i]
|
||||
res_layer = self.make_res_layer(
|
||||
block=self.block,
|
||||
num_blocks=num_blocks,
|
||||
in_channels=_in_channels,
|
||||
out_channels=_out_channels,
|
||||
expansion=self.expansion,
|
||||
stride=stride,
|
||||
dilation=dilation,
|
||||
style=self.style,
|
||||
avg_down=self.avg_down,
|
||||
with_cp=with_cp,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg)
|
||||
_in_channels = _out_channels
|
||||
_out_channels *= 2
|
||||
layer_name = f'layer{i + 1}'
|
||||
self.add_module(layer_name, res_layer)
|
||||
self.res_layers.append(layer_name)
|
||||
|
||||
self._freeze_stages()
|
||||
|
||||
self.feat_dim = res_layer[-1].out_channels
|
||||
|
||||
def make_res_layer(self, **kwargs):
|
||||
"""Make a ResLayer."""
|
||||
return ResLayer(**kwargs)
|
||||
|
||||
@property
|
||||
def norm1(self):
|
||||
"""nn.Module: the normalization layer named "norm1" """
|
||||
return getattr(self, self.norm1_name)
|
||||
|
||||
def _make_stem_layer(self, in_channels, stem_channels):
|
||||
"""Make stem layer."""
|
||||
if self.deep_stem:
|
||||
self.stem = nn.Sequential(
|
||||
ConvModule(
|
||||
in_channels,
|
||||
stem_channels // 2,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
inplace=True),
|
||||
ConvModule(
|
||||
stem_channels // 2,
|
||||
stem_channels // 2,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
inplace=True),
|
||||
ConvModule(
|
||||
stem_channels // 2,
|
||||
stem_channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
inplace=True))
|
||||
else:
|
||||
self.conv1 = build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels,
|
||||
stem_channels,
|
||||
kernel_size=7,
|
||||
stride=2,
|
||||
padding=3,
|
||||
bias=False)
|
||||
self.norm1_name, norm1 = build_norm_layer(
|
||||
self.norm_cfg, stem_channels, postfix=1)
|
||||
self.add_module(self.norm1_name, norm1)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
||||
|
||||
def _freeze_stages(self):
|
||||
"""Freeze parameters."""
|
||||
if self.frozen_stages >= 0:
|
||||
if self.deep_stem:
|
||||
self.stem.eval()
|
||||
for param in self.stem.parameters():
|
||||
param.requires_grad = False
|
||||
else:
|
||||
self.norm1.eval()
|
||||
for m in [self.conv1, self.norm1]:
|
||||
for param in m.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
for i in range(1, self.frozen_stages + 1):
|
||||
m = getattr(self, f'layer{i}')
|
||||
m.eval()
|
||||
for param in m.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Initialize the weights in backbone.
|
||||
|
||||
Args:
|
||||
pretrained (str, optional): Path to pre-trained weights.
|
||||
Defaults to None.
|
||||
"""
|
||||
super().init_weights(pretrained)
|
||||
if pretrained is None:
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
kaiming_init(m)
|
||||
elif isinstance(m, (_BatchNorm, nn.GroupNorm)):
|
||||
constant_init(m, 1)
|
||||
|
||||
if self.zero_init_residual:
|
||||
for m in self.modules():
|
||||
if isinstance(m, Bottleneck):
|
||||
constant_init(m.norm3, 0)
|
||||
elif isinstance(m, BasicBlock):
|
||||
constant_init(m.norm2, 0)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
if self.deep_stem:
|
||||
x = self.stem(x)
|
||||
else:
|
||||
x = self.conv1(x)
|
||||
x = self.norm1(x)
|
||||
x = self.relu(x)
|
||||
x = self.maxpool(x)
|
||||
outs = []
|
||||
for i, layer_name in enumerate(self.res_layers):
|
||||
res_layer = getattr(self, layer_name)
|
||||
x = res_layer(x)
|
||||
if i in self.out_indices:
|
||||
outs.append(x)
|
||||
if len(outs) == 1:
|
||||
return outs[0]
|
||||
return tuple(outs)
|
||||
|
||||
def train(self, mode=True):
|
||||
"""Convert the model into training mode."""
|
||||
super().train(mode)
|
||||
self._freeze_stages()
|
||||
if mode and self.norm_eval:
|
||||
for m in self.modules():
|
||||
# trick: eval have effect on BatchNorm only
|
||||
if isinstance(m, _BatchNorm):
|
||||
m.eval()
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class ResNetV1d(ResNet):
|
||||
r"""ResNetV1d variant described in `Bag of Tricks
|
||||
<https://arxiv.org/pdf/1812.01187.pdf>`__.
|
||||
|
||||
Compared with default ResNet(ResNetV1b), ResNetV1d replaces the 7x7 conv in
|
||||
the input stem with three 3x3 convs. And in the downsampling block, a 2x2
|
||||
avg_pool with stride 2 is added before conv, whose stride is changed to 1.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(deep_stem=True, avg_down=True, **kwargs)
|
||||
@@ -0,0 +1,162 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from mmcv.cnn import build_conv_layer, build_norm_layer
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .resnet import Bottleneck as _Bottleneck
|
||||
from .resnet import ResLayer, ResNet
|
||||
|
||||
|
||||
class Bottleneck(_Bottleneck):
|
||||
"""Bottleneck block for ResNeXt.
|
||||
|
||||
Args:
|
||||
in_channels (int): Input channels of this block.
|
||||
out_channels (int): Output channels of this block.
|
||||
groups (int): Groups of conv2.
|
||||
width_per_group (int): Width per group of conv2. 64x4d indicates
|
||||
``groups=64, width_per_group=4`` and 32x8d indicates
|
||||
``groups=32, width_per_group=8``.
|
||||
stride (int): stride of the block. Default: 1
|
||||
dilation (int): dilation of convolution. Default: 1
|
||||
downsample (nn.Module): downsample operation on identity branch.
|
||||
Default: None
|
||||
style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two
|
||||
layer is the 3x3 conv layer, otherwise the stride-two layer is
|
||||
the first 1x1 conv layer.
|
||||
conv_cfg (dict): dictionary to construct and config conv layer.
|
||||
Default: None
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
base_channels=64,
|
||||
groups=32,
|
||||
width_per_group=4,
|
||||
**kwargs):
|
||||
super().__init__(in_channels, out_channels, **kwargs)
|
||||
self.groups = groups
|
||||
self.width_per_group = width_per_group
|
||||
|
||||
# For ResNet bottleneck, middle channels are determined by expansion
|
||||
# and out_channels, but for ResNeXt bottleneck, it is determined by
|
||||
# groups and width_per_group and the stage it is located in.
|
||||
if groups != 1:
|
||||
assert self.mid_channels % base_channels == 0
|
||||
self.mid_channels = (
|
||||
groups * width_per_group * self.mid_channels // base_channels)
|
||||
|
||||
self.norm1_name, norm1 = build_norm_layer(
|
||||
self.norm_cfg, self.mid_channels, postfix=1)
|
||||
self.norm2_name, norm2 = build_norm_layer(
|
||||
self.norm_cfg, self.mid_channels, postfix=2)
|
||||
self.norm3_name, norm3 = build_norm_layer(
|
||||
self.norm_cfg, self.out_channels, postfix=3)
|
||||
|
||||
self.conv1 = build_conv_layer(
|
||||
self.conv_cfg,
|
||||
self.in_channels,
|
||||
self.mid_channels,
|
||||
kernel_size=1,
|
||||
stride=self.conv1_stride,
|
||||
bias=False)
|
||||
self.add_module(self.norm1_name, norm1)
|
||||
self.conv2 = build_conv_layer(
|
||||
self.conv_cfg,
|
||||
self.mid_channels,
|
||||
self.mid_channels,
|
||||
kernel_size=3,
|
||||
stride=self.conv2_stride,
|
||||
padding=self.dilation,
|
||||
dilation=self.dilation,
|
||||
groups=groups,
|
||||
bias=False)
|
||||
|
||||
self.add_module(self.norm2_name, norm2)
|
||||
self.conv3 = build_conv_layer(
|
||||
self.conv_cfg,
|
||||
self.mid_channels,
|
||||
self.out_channels,
|
||||
kernel_size=1,
|
||||
bias=False)
|
||||
self.add_module(self.norm3_name, norm3)
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class ResNeXt(ResNet):
|
||||
"""ResNeXt backbone.
|
||||
|
||||
Please refer to the `paper <https://arxiv.org/abs/1611.05431>`__ for
|
||||
details.
|
||||
|
||||
Args:
|
||||
depth (int): Network depth, from {50, 101, 152}.
|
||||
groups (int): Groups of conv2 in Bottleneck. Default: 32.
|
||||
width_per_group (int): Width per group of conv2 in Bottleneck.
|
||||
Default: 4.
|
||||
in_channels (int): Number of input image channels. Default: 3.
|
||||
stem_channels (int): Output channels of the stem layer. Default: 64.
|
||||
num_stages (int): Stages of the network. Default: 4.
|
||||
strides (Sequence[int]): Strides of the first block of each stage.
|
||||
Default: ``(1, 2, 2, 2)``.
|
||||
dilations (Sequence[int]): Dilation of each stage.
|
||||
Default: ``(1, 1, 1, 1)``.
|
||||
out_indices (Sequence[int]): Output from which stages. If only one
|
||||
stage is specified, a single tensor (feature map) is returned,
|
||||
otherwise multiple stages are specified, a tuple of tensors will
|
||||
be returned. Default: ``(3, )``.
|
||||
style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two
|
||||
layer is the 3x3 conv layer, otherwise the stride-two layer is
|
||||
the first 1x1 conv layer.
|
||||
deep_stem (bool): Replace 7x7 conv in input stem with 3 3x3 conv.
|
||||
Default: False.
|
||||
avg_down (bool): Use AvgPool instead of stride conv when
|
||||
downsampling in the bottleneck. Default: False.
|
||||
frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
|
||||
-1 means not freezing any parameters. Default: -1.
|
||||
conv_cfg (dict | None): The config dict for conv layers. Default: None.
|
||||
norm_cfg (dict): The config dict for norm layers.
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False.
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False.
|
||||
zero_init_residual (bool): Whether to use zero init for last norm layer
|
||||
in resblocks to let them behave as identity. Default: True.
|
||||
|
||||
Example:
|
||||
>>> from mmpose.models import ResNeXt
|
||||
>>> import torch
|
||||
>>> self = ResNeXt(depth=50, out_indices=(0, 1, 2, 3))
|
||||
>>> self.eval()
|
||||
>>> inputs = torch.rand(1, 3, 32, 32)
|
||||
>>> level_outputs = self.forward(inputs)
|
||||
>>> for level_out in level_outputs:
|
||||
... print(tuple(level_out.shape))
|
||||
(1, 256, 8, 8)
|
||||
(1, 512, 4, 4)
|
||||
(1, 1024, 2, 2)
|
||||
(1, 2048, 1, 1)
|
||||
"""
|
||||
|
||||
arch_settings = {
|
||||
50: (Bottleneck, (3, 4, 6, 3)),
|
||||
101: (Bottleneck, (3, 4, 23, 3)),
|
||||
152: (Bottleneck, (3, 8, 36, 3))
|
||||
}
|
||||
|
||||
def __init__(self, depth, groups=32, width_per_group=4, **kwargs):
|
||||
self.groups = groups
|
||||
self.width_per_group = width_per_group
|
||||
super().__init__(depth, **kwargs)
|
||||
|
||||
def make_res_layer(self, **kwargs):
|
||||
return ResLayer(
|
||||
groups=self.groups,
|
||||
width_per_group=self.width_per_group,
|
||||
base_channels=self.base_channels,
|
||||
**kwargs)
|
||||
@@ -0,0 +1,616 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy as cp
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmcv.cnn import (ConvModule, MaxPool2d, constant_init, kaiming_init,
|
||||
normal_init)
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
|
||||
|
||||
class RSB(nn.Module):
|
||||
"""Residual Steps block for RSN. Paper ref: Cai et al. "Learning Delicate
|
||||
Local Representations for Multi-Person Pose Estimation" (ECCV 2020).
|
||||
|
||||
Args:
|
||||
in_channels (int): Input channels of this block.
|
||||
out_channels (int): Output channels of this block.
|
||||
num_steps (int): Numbers of steps in RSB
|
||||
stride (int): stride of the block. Default: 1
|
||||
downsample (nn.Module): downsample operation on identity branch.
|
||||
Default: None.
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
expand_times (int): Times by which the in_channels are expanded.
|
||||
Default:26.
|
||||
res_top_channels (int): Number of channels of feature output by
|
||||
ResNet_top. Default:64.
|
||||
"""
|
||||
|
||||
expansion = 1
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
num_steps=4,
|
||||
stride=1,
|
||||
downsample=None,
|
||||
with_cp=False,
|
||||
norm_cfg=dict(type='BN'),
|
||||
expand_times=26,
|
||||
res_top_channels=64):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = cp.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
assert num_steps > 1
|
||||
self.in_channels = in_channels
|
||||
self.branch_channels = self.in_channels * expand_times
|
||||
self.branch_channels //= res_top_channels
|
||||
self.out_channels = out_channels
|
||||
self.stride = stride
|
||||
self.downsample = downsample
|
||||
self.with_cp = with_cp
|
||||
self.norm_cfg = norm_cfg
|
||||
self.num_steps = num_steps
|
||||
self.conv_bn_relu1 = ConvModule(
|
||||
self.in_channels,
|
||||
self.num_steps * self.branch_channels,
|
||||
kernel_size=1,
|
||||
stride=self.stride,
|
||||
padding=0,
|
||||
norm_cfg=self.norm_cfg,
|
||||
inplace=False)
|
||||
for i in range(self.num_steps):
|
||||
for j in range(i + 1):
|
||||
module_name = f'conv_bn_relu2_{i + 1}_{j + 1}'
|
||||
self.add_module(
|
||||
module_name,
|
||||
ConvModule(
|
||||
self.branch_channels,
|
||||
self.branch_channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
norm_cfg=self.norm_cfg,
|
||||
inplace=False))
|
||||
self.conv_bn3 = ConvModule(
|
||||
self.num_steps * self.branch_channels,
|
||||
self.out_channels * self.expansion,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
act_cfg=None,
|
||||
norm_cfg=self.norm_cfg,
|
||||
inplace=False)
|
||||
self.relu = nn.ReLU(inplace=False)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
|
||||
identity = x
|
||||
x = self.conv_bn_relu1(x)
|
||||
spx = torch.split(x, self.branch_channels, 1)
|
||||
outputs = list()
|
||||
outs = list()
|
||||
for i in range(self.num_steps):
|
||||
outputs_i = list()
|
||||
outputs.append(outputs_i)
|
||||
for j in range(i + 1):
|
||||
if j == 0:
|
||||
inputs = spx[i]
|
||||
else:
|
||||
inputs = outputs[i][j - 1]
|
||||
if i > j:
|
||||
inputs = inputs + outputs[i - 1][j]
|
||||
module_name = f'conv_bn_relu2_{i + 1}_{j + 1}'
|
||||
module_i_j = getattr(self, module_name)
|
||||
outputs[i].append(module_i_j(inputs))
|
||||
|
||||
outs.append(outputs[i][i])
|
||||
out = torch.cat(tuple(outs), 1)
|
||||
out = self.conv_bn3(out)
|
||||
|
||||
if self.downsample is not None:
|
||||
identity = self.downsample(identity)
|
||||
out = out + identity
|
||||
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class Downsample_module(nn.Module):
|
||||
"""Downsample module for RSN.
|
||||
|
||||
Args:
|
||||
block (nn.Module): Downsample block.
|
||||
num_blocks (list): Number of blocks in each downsample unit.
|
||||
num_units (int): Numbers of downsample units. Default: 4
|
||||
has_skip (bool): Have skip connections from prior upsample
|
||||
module or not. Default:False
|
||||
num_steps (int): Number of steps in a block. Default:4
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
in_channels (int): Number of channels of the input feature to
|
||||
downsample module. Default: 64
|
||||
expand_times (int): Times by which the in_channels are expanded.
|
||||
Default:26.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
block,
|
||||
num_blocks,
|
||||
num_steps=4,
|
||||
num_units=4,
|
||||
has_skip=False,
|
||||
norm_cfg=dict(type='BN'),
|
||||
in_channels=64,
|
||||
expand_times=26):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = cp.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
self.has_skip = has_skip
|
||||
self.in_channels = in_channels
|
||||
assert len(num_blocks) == num_units
|
||||
self.num_blocks = num_blocks
|
||||
self.num_units = num_units
|
||||
self.num_steps = num_steps
|
||||
self.norm_cfg = norm_cfg
|
||||
self.layer1 = self._make_layer(
|
||||
block,
|
||||
in_channels,
|
||||
num_blocks[0],
|
||||
expand_times=expand_times,
|
||||
res_top_channels=in_channels)
|
||||
for i in range(1, num_units):
|
||||
module_name = f'layer{i + 1}'
|
||||
self.add_module(
|
||||
module_name,
|
||||
self._make_layer(
|
||||
block,
|
||||
in_channels * pow(2, i),
|
||||
num_blocks[i],
|
||||
stride=2,
|
||||
expand_times=expand_times,
|
||||
res_top_channels=in_channels))
|
||||
|
||||
def _make_layer(self,
|
||||
block,
|
||||
out_channels,
|
||||
blocks,
|
||||
stride=1,
|
||||
expand_times=26,
|
||||
res_top_channels=64):
|
||||
downsample = None
|
||||
if stride != 1 or self.in_channels != out_channels * block.expansion:
|
||||
downsample = ConvModule(
|
||||
self.in_channels,
|
||||
out_channels * block.expansion,
|
||||
kernel_size=1,
|
||||
stride=stride,
|
||||
padding=0,
|
||||
norm_cfg=self.norm_cfg,
|
||||
act_cfg=None,
|
||||
inplace=True)
|
||||
|
||||
units = list()
|
||||
units.append(
|
||||
block(
|
||||
self.in_channels,
|
||||
out_channels,
|
||||
num_steps=self.num_steps,
|
||||
stride=stride,
|
||||
downsample=downsample,
|
||||
norm_cfg=self.norm_cfg,
|
||||
expand_times=expand_times,
|
||||
res_top_channels=res_top_channels))
|
||||
self.in_channels = out_channels * block.expansion
|
||||
for _ in range(1, blocks):
|
||||
units.append(
|
||||
block(
|
||||
self.in_channels,
|
||||
out_channels,
|
||||
num_steps=self.num_steps,
|
||||
expand_times=expand_times,
|
||||
res_top_channels=res_top_channels))
|
||||
|
||||
return nn.Sequential(*units)
|
||||
|
||||
def forward(self, x, skip1, skip2):
|
||||
out = list()
|
||||
for i in range(self.num_units):
|
||||
module_name = f'layer{i + 1}'
|
||||
module_i = getattr(self, module_name)
|
||||
x = module_i(x)
|
||||
if self.has_skip:
|
||||
x = x + skip1[i] + skip2[i]
|
||||
out.append(x)
|
||||
out.reverse()
|
||||
|
||||
return tuple(out)
|
||||
|
||||
|
||||
class Upsample_unit(nn.Module):
|
||||
"""Upsample unit for upsample module.
|
||||
|
||||
Args:
|
||||
ind (int): Indicates whether to interpolate (>0) and whether to
|
||||
generate feature map for the next hourglass-like module.
|
||||
num_units (int): Number of units that form a upsample module. Along
|
||||
with ind and gen_cross_conv, nm_units is used to decide whether
|
||||
to generate feature map for the next hourglass-like module.
|
||||
in_channels (int): Channel number of the skip-in feature maps from
|
||||
the corresponding downsample unit.
|
||||
unit_channels (int): Channel number in this unit. Default:256.
|
||||
gen_skip: (bool): Whether or not to generate skips for the posterior
|
||||
downsample module. Default:False
|
||||
gen_cross_conv (bool): Whether to generate feature map for the next
|
||||
hourglass-like module. Default:False
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
out_channels (in): Number of channels of feature output by upsample
|
||||
module. Must equal to in_channels of downsample module. Default:64
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ind,
|
||||
num_units,
|
||||
in_channels,
|
||||
unit_channels=256,
|
||||
gen_skip=False,
|
||||
gen_cross_conv=False,
|
||||
norm_cfg=dict(type='BN'),
|
||||
out_channels=64):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = cp.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
self.num_units = num_units
|
||||
self.norm_cfg = norm_cfg
|
||||
self.in_skip = ConvModule(
|
||||
in_channels,
|
||||
unit_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
norm_cfg=self.norm_cfg,
|
||||
act_cfg=None,
|
||||
inplace=True)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
|
||||
self.ind = ind
|
||||
if self.ind > 0:
|
||||
self.up_conv = ConvModule(
|
||||
unit_channels,
|
||||
unit_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
norm_cfg=self.norm_cfg,
|
||||
act_cfg=None,
|
||||
inplace=True)
|
||||
|
||||
self.gen_skip = gen_skip
|
||||
if self.gen_skip:
|
||||
self.out_skip1 = ConvModule(
|
||||
in_channels,
|
||||
in_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
norm_cfg=self.norm_cfg,
|
||||
inplace=True)
|
||||
|
||||
self.out_skip2 = ConvModule(
|
||||
unit_channels,
|
||||
in_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
norm_cfg=self.norm_cfg,
|
||||
inplace=True)
|
||||
|
||||
self.gen_cross_conv = gen_cross_conv
|
||||
if self.ind == num_units - 1 and self.gen_cross_conv:
|
||||
self.cross_conv = ConvModule(
|
||||
unit_channels,
|
||||
out_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
norm_cfg=self.norm_cfg,
|
||||
inplace=True)
|
||||
|
||||
def forward(self, x, up_x):
|
||||
out = self.in_skip(x)
|
||||
|
||||
if self.ind > 0:
|
||||
up_x = F.interpolate(
|
||||
up_x,
|
||||
size=(x.size(2), x.size(3)),
|
||||
mode='bilinear',
|
||||
align_corners=True)
|
||||
up_x = self.up_conv(up_x)
|
||||
out = out + up_x
|
||||
out = self.relu(out)
|
||||
|
||||
skip1 = None
|
||||
skip2 = None
|
||||
if self.gen_skip:
|
||||
skip1 = self.out_skip1(x)
|
||||
skip2 = self.out_skip2(out)
|
||||
|
||||
cross_conv = None
|
||||
if self.ind == self.num_units - 1 and self.gen_cross_conv:
|
||||
cross_conv = self.cross_conv(out)
|
||||
|
||||
return out, skip1, skip2, cross_conv
|
||||
|
||||
|
||||
class Upsample_module(nn.Module):
|
||||
"""Upsample module for RSN.
|
||||
|
||||
Args:
|
||||
unit_channels (int): Channel number in the upsample units.
|
||||
Default:256.
|
||||
num_units (int): Numbers of upsample units. Default: 4
|
||||
gen_skip (bool): Whether to generate skip for posterior downsample
|
||||
module or not. Default:False
|
||||
gen_cross_conv (bool): Whether to generate feature map for the next
|
||||
hourglass-like module. Default:False
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
out_channels (int): Number of channels of feature output by upsample
|
||||
module. Must equal to in_channels of downsample module. Default:64
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
unit_channels=256,
|
||||
num_units=4,
|
||||
gen_skip=False,
|
||||
gen_cross_conv=False,
|
||||
norm_cfg=dict(type='BN'),
|
||||
out_channels=64):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = cp.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
self.in_channels = list()
|
||||
for i in range(num_units):
|
||||
self.in_channels.append(RSB.expansion * out_channels * pow(2, i))
|
||||
self.in_channels.reverse()
|
||||
self.num_units = num_units
|
||||
self.gen_skip = gen_skip
|
||||
self.gen_cross_conv = gen_cross_conv
|
||||
self.norm_cfg = norm_cfg
|
||||
for i in range(num_units):
|
||||
module_name = f'up{i + 1}'
|
||||
self.add_module(
|
||||
module_name,
|
||||
Upsample_unit(
|
||||
i,
|
||||
self.num_units,
|
||||
self.in_channels[i],
|
||||
unit_channels,
|
||||
self.gen_skip,
|
||||
self.gen_cross_conv,
|
||||
norm_cfg=self.norm_cfg,
|
||||
out_channels=64))
|
||||
|
||||
def forward(self, x):
|
||||
out = list()
|
||||
skip1 = list()
|
||||
skip2 = list()
|
||||
cross_conv = None
|
||||
for i in range(self.num_units):
|
||||
module_i = getattr(self, f'up{i + 1}')
|
||||
if i == 0:
|
||||
outi, skip1_i, skip2_i, _ = module_i(x[i], None)
|
||||
elif i == self.num_units - 1:
|
||||
outi, skip1_i, skip2_i, cross_conv = module_i(x[i], out[i - 1])
|
||||
else:
|
||||
outi, skip1_i, skip2_i, _ = module_i(x[i], out[i - 1])
|
||||
out.append(outi)
|
||||
skip1.append(skip1_i)
|
||||
skip2.append(skip2_i)
|
||||
skip1.reverse()
|
||||
skip2.reverse()
|
||||
|
||||
return out, skip1, skip2, cross_conv
|
||||
|
||||
|
||||
class Single_stage_RSN(nn.Module):
|
||||
"""Single_stage Residual Steps Network.
|
||||
|
||||
Args:
|
||||
unit_channels (int): Channel number in the upsample units. Default:256.
|
||||
num_units (int): Numbers of downsample/upsample units. Default: 4
|
||||
gen_skip (bool): Whether to generate skip for posterior downsample
|
||||
module or not. Default:False
|
||||
gen_cross_conv (bool): Whether to generate feature map for the next
|
||||
hourglass-like module. Default:False
|
||||
has_skip (bool): Have skip connections from prior upsample
|
||||
module or not. Default:False
|
||||
num_steps (int): Number of steps in RSB. Default: 4
|
||||
num_blocks (list): Number of blocks in each downsample unit.
|
||||
Default: [2, 2, 2, 2] Note: Make sure num_units==len(num_blocks)
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
in_channels (int): Number of channels of the feature from ResNet_Top.
|
||||
Default: 64.
|
||||
expand_times (int): Times by which the in_channels are expanded in RSB.
|
||||
Default:26.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
has_skip=False,
|
||||
gen_skip=False,
|
||||
gen_cross_conv=False,
|
||||
unit_channels=256,
|
||||
num_units=4,
|
||||
num_steps=4,
|
||||
num_blocks=[2, 2, 2, 2],
|
||||
norm_cfg=dict(type='BN'),
|
||||
in_channels=64,
|
||||
expand_times=26):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = cp.deepcopy(norm_cfg)
|
||||
num_blocks = cp.deepcopy(num_blocks)
|
||||
super().__init__()
|
||||
assert len(num_blocks) == num_units
|
||||
self.has_skip = has_skip
|
||||
self.gen_skip = gen_skip
|
||||
self.gen_cross_conv = gen_cross_conv
|
||||
self.num_units = num_units
|
||||
self.num_steps = num_steps
|
||||
self.unit_channels = unit_channels
|
||||
self.num_blocks = num_blocks
|
||||
self.norm_cfg = norm_cfg
|
||||
|
||||
self.downsample = Downsample_module(RSB, num_blocks, num_steps,
|
||||
num_units, has_skip, norm_cfg,
|
||||
in_channels, expand_times)
|
||||
self.upsample = Upsample_module(unit_channels, num_units, gen_skip,
|
||||
gen_cross_conv, norm_cfg, in_channels)
|
||||
|
||||
def forward(self, x, skip1, skip2):
|
||||
mid = self.downsample(x, skip1, skip2)
|
||||
out, skip1, skip2, cross_conv = self.upsample(mid)
|
||||
|
||||
return out, skip1, skip2, cross_conv
|
||||
|
||||
|
||||
class ResNet_top(nn.Module):
|
||||
"""ResNet top for RSN.
|
||||
|
||||
Args:
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
channels (int): Number of channels of the feature output by ResNet_top.
|
||||
"""
|
||||
|
||||
def __init__(self, norm_cfg=dict(type='BN'), channels=64):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = cp.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
self.top = nn.Sequential(
|
||||
ConvModule(
|
||||
3,
|
||||
channels,
|
||||
kernel_size=7,
|
||||
stride=2,
|
||||
padding=3,
|
||||
norm_cfg=norm_cfg,
|
||||
inplace=True), MaxPool2d(kernel_size=3, stride=2, padding=1))
|
||||
|
||||
def forward(self, img):
|
||||
return self.top(img)
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class RSN(BaseBackbone):
|
||||
"""Residual Steps Network backbone. Paper ref: Cai et al. "Learning
|
||||
Delicate Local Representations for Multi-Person Pose Estimation" (ECCV
|
||||
2020).
|
||||
|
||||
Args:
|
||||
unit_channels (int): Number of Channels in an upsample unit.
|
||||
Default: 256
|
||||
num_stages (int): Number of stages in a multi-stage RSN. Default: 4
|
||||
num_units (int): NUmber of downsample/upsample units in a single-stage
|
||||
RSN. Default: 4 Note: Make sure num_units == len(self.num_blocks)
|
||||
num_blocks (list): Number of RSBs (Residual Steps Block) in each
|
||||
downsample unit. Default: [2, 2, 2, 2]
|
||||
num_steps (int): Number of steps in a RSB. Default:4
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
res_top_channels (int): Number of channels of feature from ResNet_top.
|
||||
Default: 64.
|
||||
expand_times (int): Times by which the in_channels are expanded in RSB.
|
||||
Default:26.
|
||||
Example:
|
||||
>>> from mmpose.models import RSN
|
||||
>>> import torch
|
||||
>>> self = RSN(num_stages=2,num_units=2,num_blocks=[2,2])
|
||||
>>> self.eval()
|
||||
>>> inputs = torch.rand(1, 3, 511, 511)
|
||||
>>> level_outputs = self.forward(inputs)
|
||||
>>> for level_output in level_outputs:
|
||||
... for feature in level_output:
|
||||
... print(tuple(feature.shape))
|
||||
...
|
||||
(1, 256, 64, 64)
|
||||
(1, 256, 128, 128)
|
||||
(1, 256, 64, 64)
|
||||
(1, 256, 128, 128)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
unit_channels=256,
|
||||
num_stages=4,
|
||||
num_units=4,
|
||||
num_blocks=[2, 2, 2, 2],
|
||||
num_steps=4,
|
||||
norm_cfg=dict(type='BN'),
|
||||
res_top_channels=64,
|
||||
expand_times=26):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = cp.deepcopy(norm_cfg)
|
||||
num_blocks = cp.deepcopy(num_blocks)
|
||||
super().__init__()
|
||||
self.unit_channels = unit_channels
|
||||
self.num_stages = num_stages
|
||||
self.num_units = num_units
|
||||
self.num_blocks = num_blocks
|
||||
self.num_steps = num_steps
|
||||
self.norm_cfg = norm_cfg
|
||||
|
||||
assert self.num_stages > 0
|
||||
assert self.num_steps > 1
|
||||
assert self.num_units > 1
|
||||
assert self.num_units == len(self.num_blocks)
|
||||
self.top = ResNet_top(norm_cfg=norm_cfg)
|
||||
self.multi_stage_rsn = nn.ModuleList([])
|
||||
for i in range(self.num_stages):
|
||||
if i == 0:
|
||||
has_skip = False
|
||||
else:
|
||||
has_skip = True
|
||||
if i != self.num_stages - 1:
|
||||
gen_skip = True
|
||||
gen_cross_conv = True
|
||||
else:
|
||||
gen_skip = False
|
||||
gen_cross_conv = False
|
||||
self.multi_stage_rsn.append(
|
||||
Single_stage_RSN(has_skip, gen_skip, gen_cross_conv,
|
||||
unit_channels, num_units, num_steps,
|
||||
num_blocks, norm_cfg, res_top_channels,
|
||||
expand_times))
|
||||
|
||||
def forward(self, x):
|
||||
"""Model forward function."""
|
||||
out_feats = []
|
||||
skip1 = None
|
||||
skip2 = None
|
||||
x = self.top(x)
|
||||
for i in range(self.num_stages):
|
||||
out, skip1, skip2, x = self.multi_stage_rsn[i](x, skip1, skip2)
|
||||
out_feats.append(out)
|
||||
|
||||
return out_feats
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Initialize model weights."""
|
||||
for m in self.multi_stage_rsn.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)
|
||||
|
||||
for m in self.top.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
kaiming_init(m)
|
||||
@@ -0,0 +1,248 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch.utils.checkpoint as cp
|
||||
from mmcv.cnn import build_conv_layer, build_norm_layer
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .resnet import Bottleneck, ResNet
|
||||
|
||||
|
||||
class SCConv(nn.Module):
|
||||
"""SCConv (Self-calibrated Convolution)
|
||||
|
||||
Args:
|
||||
in_channels (int): The input channels of the SCConv.
|
||||
out_channels (int): The output channel of the SCConv.
|
||||
stride (int): stride of SCConv.
|
||||
pooling_r (int): size of pooling for scconv.
|
||||
conv_cfg (dict): dictionary to construct and config conv layer.
|
||||
Default: None
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
stride,
|
||||
pooling_r,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN', momentum=0.1)):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
|
||||
assert in_channels == out_channels
|
||||
|
||||
self.k2 = nn.Sequential(
|
||||
nn.AvgPool2d(kernel_size=pooling_r, stride=pooling_r),
|
||||
build_conv_layer(
|
||||
conv_cfg,
|
||||
in_channels,
|
||||
in_channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
bias=False),
|
||||
build_norm_layer(norm_cfg, in_channels)[1],
|
||||
)
|
||||
self.k3 = nn.Sequential(
|
||||
build_conv_layer(
|
||||
conv_cfg,
|
||||
in_channels,
|
||||
in_channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
bias=False),
|
||||
build_norm_layer(norm_cfg, in_channels)[1],
|
||||
)
|
||||
self.k4 = nn.Sequential(
|
||||
build_conv_layer(
|
||||
conv_cfg,
|
||||
in_channels,
|
||||
in_channels,
|
||||
kernel_size=3,
|
||||
stride=stride,
|
||||
padding=1,
|
||||
bias=False),
|
||||
build_norm_layer(norm_cfg, out_channels)[1],
|
||||
nn.ReLU(inplace=True),
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
identity = x
|
||||
|
||||
out = torch.sigmoid(
|
||||
torch.add(identity, F.interpolate(self.k2(x),
|
||||
identity.size()[2:])))
|
||||
out = torch.mul(self.k3(x), out)
|
||||
out = self.k4(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class SCBottleneck(Bottleneck):
|
||||
"""SC(Self-calibrated) Bottleneck.
|
||||
|
||||
Args:
|
||||
in_channels (int): The input channels of the SCBottleneck block.
|
||||
out_channels (int): The output channel of the SCBottleneck block.
|
||||
"""
|
||||
|
||||
pooling_r = 4
|
||||
|
||||
def __init__(self, in_channels, out_channels, **kwargs):
|
||||
super().__init__(in_channels, out_channels, **kwargs)
|
||||
self.mid_channels = out_channels // self.expansion // 2
|
||||
|
||||
self.norm1_name, norm1 = build_norm_layer(
|
||||
self.norm_cfg, self.mid_channels, postfix=1)
|
||||
self.norm2_name, norm2 = build_norm_layer(
|
||||
self.norm_cfg, self.mid_channels, postfix=2)
|
||||
self.norm3_name, norm3 = build_norm_layer(
|
||||
self.norm_cfg, out_channels, postfix=3)
|
||||
|
||||
self.conv1 = build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels,
|
||||
self.mid_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
bias=False)
|
||||
self.add_module(self.norm1_name, norm1)
|
||||
|
||||
self.k1 = nn.Sequential(
|
||||
build_conv_layer(
|
||||
self.conv_cfg,
|
||||
self.mid_channels,
|
||||
self.mid_channels,
|
||||
kernel_size=3,
|
||||
stride=self.stride,
|
||||
padding=1,
|
||||
bias=False),
|
||||
build_norm_layer(self.norm_cfg, self.mid_channels)[1],
|
||||
nn.ReLU(inplace=True))
|
||||
|
||||
self.conv2 = build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels,
|
||||
self.mid_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
bias=False)
|
||||
self.add_module(self.norm2_name, norm2)
|
||||
|
||||
self.scconv = SCConv(self.mid_channels, self.mid_channels, self.stride,
|
||||
self.pooling_r, self.conv_cfg, self.norm_cfg)
|
||||
|
||||
self.conv3 = build_conv_layer(
|
||||
self.conv_cfg,
|
||||
self.mid_channels * 2,
|
||||
out_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
bias=False)
|
||||
self.add_module(self.norm3_name, norm3)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
|
||||
def _inner_forward(x):
|
||||
identity = x
|
||||
|
||||
out_a = self.conv1(x)
|
||||
out_a = self.norm1(out_a)
|
||||
out_a = self.relu(out_a)
|
||||
|
||||
out_a = self.k1(out_a)
|
||||
|
||||
out_b = self.conv2(x)
|
||||
out_b = self.norm2(out_b)
|
||||
out_b = self.relu(out_b)
|
||||
|
||||
out_b = self.scconv(out_b)
|
||||
|
||||
out = self.conv3(torch.cat([out_a, out_b], dim=1))
|
||||
out = self.norm3(out)
|
||||
|
||||
if self.downsample is not None:
|
||||
identity = self.downsample(x)
|
||||
|
||||
out += identity
|
||||
|
||||
return out
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
out = cp.checkpoint(_inner_forward, x)
|
||||
else:
|
||||
out = _inner_forward(x)
|
||||
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class SCNet(ResNet):
|
||||
"""SCNet backbone.
|
||||
|
||||
Improving Convolutional Networks with Self-Calibrated Convolutions,
|
||||
Jiang-Jiang Liu, Qibin Hou, Ming-Ming Cheng, Changhu Wang, Jiashi Feng,
|
||||
IEEE CVPR, 2020.
|
||||
http://mftp.mmcheng.net/Papers/20cvprSCNet.pdf
|
||||
|
||||
Args:
|
||||
depth (int): Depth of scnet, from {50, 101}.
|
||||
in_channels (int): Number of input image channels. Normally 3.
|
||||
base_channels (int): Number of base channels of hidden layer.
|
||||
num_stages (int): SCNet stages, normally 4.
|
||||
strides (Sequence[int]): Strides of the first block of each stage.
|
||||
dilations (Sequence[int]): Dilation of each stage.
|
||||
out_indices (Sequence[int]): Output from which stages.
|
||||
style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two
|
||||
layer is the 3x3 conv layer, otherwise the stride-two layer is
|
||||
the first 1x1 conv layer.
|
||||
deep_stem (bool): Replace 7x7 conv in input stem with 3 3x3 conv
|
||||
avg_down (bool): Use AvgPool instead of stride conv when
|
||||
downsampling in the bottleneck.
|
||||
frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
|
||||
-1 means not freezing any parameters.
|
||||
norm_cfg (dict): Dictionary to construct and config norm layer.
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only.
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed.
|
||||
zero_init_residual (bool): Whether to use zero init for last norm layer
|
||||
in resblocks to let them behave as identity.
|
||||
|
||||
Example:
|
||||
>>> from mmpose.models import SCNet
|
||||
>>> import torch
|
||||
>>> self = SCNet(depth=50, out_indices=(0, 1, 2, 3))
|
||||
>>> self.eval()
|
||||
>>> inputs = torch.rand(1, 3, 224, 224)
|
||||
>>> level_outputs = self.forward(inputs)
|
||||
>>> for level_out in level_outputs:
|
||||
... print(tuple(level_out.shape))
|
||||
(1, 256, 56, 56)
|
||||
(1, 512, 28, 28)
|
||||
(1, 1024, 14, 14)
|
||||
(1, 2048, 7, 7)
|
||||
"""
|
||||
|
||||
arch_settings = {
|
||||
50: (SCBottleneck, [3, 4, 6, 3]),
|
||||
101: (SCBottleneck, [3, 4, 23, 3])
|
||||
}
|
||||
|
||||
def __init__(self, depth, **kwargs):
|
||||
if depth not in self.arch_settings:
|
||||
raise KeyError(f'invalid depth {depth} for SCNet')
|
||||
super().__init__(depth, **kwargs)
|
||||
@@ -0,0 +1,125 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch.utils.checkpoint as cp
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .resnet import Bottleneck, ResLayer, ResNet
|
||||
from .utils.se_layer import SELayer
|
||||
|
||||
|
||||
class SEBottleneck(Bottleneck):
|
||||
"""SEBottleneck block for SEResNet.
|
||||
|
||||
Args:
|
||||
in_channels (int): The input channels of the SEBottleneck block.
|
||||
out_channels (int): The output channel of the SEBottleneck block.
|
||||
se_ratio (int): Squeeze ratio in SELayer. Default: 16
|
||||
"""
|
||||
|
||||
def __init__(self, in_channels, out_channels, se_ratio=16, **kwargs):
|
||||
super().__init__(in_channels, out_channels, **kwargs)
|
||||
self.se_layer = SELayer(out_channels, ratio=se_ratio)
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
def _inner_forward(x):
|
||||
identity = x
|
||||
|
||||
out = self.conv1(x)
|
||||
out = self.norm1(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv2(out)
|
||||
out = self.norm2(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv3(out)
|
||||
out = self.norm3(out)
|
||||
|
||||
out = self.se_layer(out)
|
||||
|
||||
if self.downsample is not None:
|
||||
identity = self.downsample(x)
|
||||
|
||||
out += identity
|
||||
|
||||
return out
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
out = cp.checkpoint(_inner_forward, x)
|
||||
else:
|
||||
out = _inner_forward(x)
|
||||
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class SEResNet(ResNet):
|
||||
"""SEResNet backbone.
|
||||
|
||||
Please refer to the `paper <https://arxiv.org/abs/1709.01507>`__ for
|
||||
details.
|
||||
|
||||
Args:
|
||||
depth (int): Network depth, from {50, 101, 152}.
|
||||
se_ratio (int): Squeeze ratio in SELayer. Default: 16.
|
||||
in_channels (int): Number of input image channels. Default: 3.
|
||||
stem_channels (int): Output channels of the stem layer. Default: 64.
|
||||
num_stages (int): Stages of the network. Default: 4.
|
||||
strides (Sequence[int]): Strides of the first block of each stage.
|
||||
Default: ``(1, 2, 2, 2)``.
|
||||
dilations (Sequence[int]): Dilation of each stage.
|
||||
Default: ``(1, 1, 1, 1)``.
|
||||
out_indices (Sequence[int]): Output from which stages. If only one
|
||||
stage is specified, a single tensor (feature map) is returned,
|
||||
otherwise multiple stages are specified, a tuple of tensors will
|
||||
be returned. Default: ``(3, )``.
|
||||
style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two
|
||||
layer is the 3x3 conv layer, otherwise the stride-two layer is
|
||||
the first 1x1 conv layer.
|
||||
deep_stem (bool): Replace 7x7 conv in input stem with 3 3x3 conv.
|
||||
Default: False.
|
||||
avg_down (bool): Use AvgPool instead of stride conv when
|
||||
downsampling in the bottleneck. Default: False.
|
||||
frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
|
||||
-1 means not freezing any parameters. Default: -1.
|
||||
conv_cfg (dict | None): The config dict for conv layers. Default: None.
|
||||
norm_cfg (dict): The config dict for norm layers.
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False.
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False.
|
||||
zero_init_residual (bool): Whether to use zero init for last norm layer
|
||||
in resblocks to let them behave as identity. Default: True.
|
||||
|
||||
Example:
|
||||
>>> from mmpose.models import SEResNet
|
||||
>>> import torch
|
||||
>>> self = SEResNet(depth=50, out_indices=(0, 1, 2, 3))
|
||||
>>> self.eval()
|
||||
>>> inputs = torch.rand(1, 3, 224, 224)
|
||||
>>> level_outputs = self.forward(inputs)
|
||||
>>> for level_out in level_outputs:
|
||||
... print(tuple(level_out.shape))
|
||||
(1, 256, 56, 56)
|
||||
(1, 512, 28, 28)
|
||||
(1, 1024, 14, 14)
|
||||
(1, 2048, 7, 7)
|
||||
"""
|
||||
|
||||
arch_settings = {
|
||||
50: (SEBottleneck, (3, 4, 6, 3)),
|
||||
101: (SEBottleneck, (3, 4, 23, 3)),
|
||||
152: (SEBottleneck, (3, 8, 36, 3))
|
||||
}
|
||||
|
||||
def __init__(self, depth, se_ratio=16, **kwargs):
|
||||
if depth not in self.arch_settings:
|
||||
raise KeyError(f'invalid depth {depth} for SEResNet')
|
||||
self.se_ratio = se_ratio
|
||||
super().__init__(depth, **kwargs)
|
||||
|
||||
def make_res_layer(self, **kwargs):
|
||||
return ResLayer(se_ratio=self.se_ratio, **kwargs)
|
||||
@@ -0,0 +1,168 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from mmcv.cnn import build_conv_layer, build_norm_layer
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .resnet import ResLayer
|
||||
from .seresnet import SEBottleneck as _SEBottleneck
|
||||
from .seresnet import SEResNet
|
||||
|
||||
|
||||
class SEBottleneck(_SEBottleneck):
|
||||
"""SEBottleneck block for SEResNeXt.
|
||||
|
||||
Args:
|
||||
in_channels (int): Input channels of this block.
|
||||
out_channels (int): Output channels of this block.
|
||||
base_channels (int): Middle channels of the first stage. Default: 64.
|
||||
groups (int): Groups of conv2.
|
||||
width_per_group (int): Width per group of conv2. 64x4d indicates
|
||||
``groups=64, width_per_group=4`` and 32x8d indicates
|
||||
``groups=32, width_per_group=8``.
|
||||
stride (int): stride of the block. Default: 1
|
||||
dilation (int): dilation of convolution. Default: 1
|
||||
downsample (nn.Module): downsample operation on identity branch.
|
||||
Default: None
|
||||
se_ratio (int): Squeeze ratio in SELayer. Default: 16
|
||||
style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two
|
||||
layer is the 3x3 conv layer, otherwise the stride-two layer is
|
||||
the first 1x1 conv layer.
|
||||
conv_cfg (dict): dictionary to construct and config conv layer.
|
||||
Default: None
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
base_channels=64,
|
||||
groups=32,
|
||||
width_per_group=4,
|
||||
se_ratio=16,
|
||||
**kwargs):
|
||||
super().__init__(in_channels, out_channels, se_ratio, **kwargs)
|
||||
self.groups = groups
|
||||
self.width_per_group = width_per_group
|
||||
|
||||
# We follow the same rational of ResNext to compute mid_channels.
|
||||
# For SEResNet bottleneck, middle channels are determined by expansion
|
||||
# and out_channels, but for SEResNeXt bottleneck, it is determined by
|
||||
# groups and width_per_group and the stage it is located in.
|
||||
if groups != 1:
|
||||
assert self.mid_channels % base_channels == 0
|
||||
self.mid_channels = (
|
||||
groups * width_per_group * self.mid_channels // base_channels)
|
||||
|
||||
self.norm1_name, norm1 = build_norm_layer(
|
||||
self.norm_cfg, self.mid_channels, postfix=1)
|
||||
self.norm2_name, norm2 = build_norm_layer(
|
||||
self.norm_cfg, self.mid_channels, postfix=2)
|
||||
self.norm3_name, norm3 = build_norm_layer(
|
||||
self.norm_cfg, self.out_channels, postfix=3)
|
||||
|
||||
self.conv1 = build_conv_layer(
|
||||
self.conv_cfg,
|
||||
self.in_channels,
|
||||
self.mid_channels,
|
||||
kernel_size=1,
|
||||
stride=self.conv1_stride,
|
||||
bias=False)
|
||||
self.add_module(self.norm1_name, norm1)
|
||||
self.conv2 = build_conv_layer(
|
||||
self.conv_cfg,
|
||||
self.mid_channels,
|
||||
self.mid_channels,
|
||||
kernel_size=3,
|
||||
stride=self.conv2_stride,
|
||||
padding=self.dilation,
|
||||
dilation=self.dilation,
|
||||
groups=groups,
|
||||
bias=False)
|
||||
|
||||
self.add_module(self.norm2_name, norm2)
|
||||
self.conv3 = build_conv_layer(
|
||||
self.conv_cfg,
|
||||
self.mid_channels,
|
||||
self.out_channels,
|
||||
kernel_size=1,
|
||||
bias=False)
|
||||
self.add_module(self.norm3_name, norm3)
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class SEResNeXt(SEResNet):
|
||||
"""SEResNeXt backbone.
|
||||
|
||||
Please refer to the `paper <https://arxiv.org/abs/1709.01507>`__ for
|
||||
details.
|
||||
|
||||
Args:
|
||||
depth (int): Network depth, from {50, 101, 152}.
|
||||
groups (int): Groups of conv2 in Bottleneck. Default: 32.
|
||||
width_per_group (int): Width per group of conv2 in Bottleneck.
|
||||
Default: 4.
|
||||
se_ratio (int): Squeeze ratio in SELayer. Default: 16.
|
||||
in_channels (int): Number of input image channels. Default: 3.
|
||||
stem_channels (int): Output channels of the stem layer. Default: 64.
|
||||
num_stages (int): Stages of the network. Default: 4.
|
||||
strides (Sequence[int]): Strides of the first block of each stage.
|
||||
Default: ``(1, 2, 2, 2)``.
|
||||
dilations (Sequence[int]): Dilation of each stage.
|
||||
Default: ``(1, 1, 1, 1)``.
|
||||
out_indices (Sequence[int]): Output from which stages. If only one
|
||||
stage is specified, a single tensor (feature map) is returned,
|
||||
otherwise multiple stages are specified, a tuple of tensors will
|
||||
be returned. Default: ``(3, )``.
|
||||
style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two
|
||||
layer is the 3x3 conv layer, otherwise the stride-two layer is
|
||||
the first 1x1 conv layer.
|
||||
deep_stem (bool): Replace 7x7 conv in input stem with 3 3x3 conv.
|
||||
Default: False.
|
||||
avg_down (bool): Use AvgPool instead of stride conv when
|
||||
downsampling in the bottleneck. Default: False.
|
||||
frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
|
||||
-1 means not freezing any parameters. Default: -1.
|
||||
conv_cfg (dict | None): The config dict for conv layers. Default: None.
|
||||
norm_cfg (dict): The config dict for norm layers.
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False.
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False.
|
||||
zero_init_residual (bool): Whether to use zero init for last norm layer
|
||||
in resblocks to let them behave as identity. Default: True.
|
||||
|
||||
Example:
|
||||
>>> from mmpose.models import SEResNeXt
|
||||
>>> import torch
|
||||
>>> self = SEResNet(depth=50, out_indices=(0, 1, 2, 3))
|
||||
>>> self.eval()
|
||||
>>> inputs = torch.rand(1, 3, 224, 224)
|
||||
>>> level_outputs = self.forward(inputs)
|
||||
>>> for level_out in level_outputs:
|
||||
... print(tuple(level_out.shape))
|
||||
(1, 256, 56, 56)
|
||||
(1, 512, 28, 28)
|
||||
(1, 1024, 14, 14)
|
||||
(1, 2048, 7, 7)
|
||||
"""
|
||||
|
||||
arch_settings = {
|
||||
50: (SEBottleneck, (3, 4, 6, 3)),
|
||||
101: (SEBottleneck, (3, 4, 23, 3)),
|
||||
152: (SEBottleneck, (3, 8, 36, 3))
|
||||
}
|
||||
|
||||
def __init__(self, depth, groups=32, width_per_group=4, **kwargs):
|
||||
self.groups = groups
|
||||
self.width_per_group = width_per_group
|
||||
super().__init__(depth, **kwargs)
|
||||
|
||||
def make_res_layer(self, **kwargs):
|
||||
return ResLayer(
|
||||
groups=self.groups,
|
||||
width_per_group=self.width_per_group,
|
||||
base_channels=self.base_channels,
|
||||
**kwargs)
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
import logging
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.utils.checkpoint as cp
|
||||
from mmcv.cnn import (ConvModule, build_activation_layer, constant_init,
|
||||
normal_init)
|
||||
from torch.nn.modules.batchnorm import _BatchNorm
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
from .utils import channel_shuffle, load_checkpoint, make_divisible
|
||||
|
||||
|
||||
class ShuffleUnit(nn.Module):
|
||||
"""ShuffleUnit block.
|
||||
|
||||
ShuffleNet unit with pointwise group convolution (GConv) and channel
|
||||
shuffle.
|
||||
|
||||
Args:
|
||||
in_channels (int): The input channels of the ShuffleUnit.
|
||||
out_channels (int): The output channels of the ShuffleUnit.
|
||||
groups (int, optional): The number of groups to be used in grouped 1x1
|
||||
convolutions in each ShuffleUnit. Default: 3
|
||||
first_block (bool, optional): Whether it is the first ShuffleUnit of a
|
||||
sequential ShuffleUnits. Default: True, which means not using the
|
||||
grouped 1x1 convolution.
|
||||
combine (str, optional): The ways to combine the input and output
|
||||
branches. Default: 'add'.
|
||||
conv_cfg (dict): Config dict for convolution layer. Default: None,
|
||||
which means using conv2d.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: dict(type='BN').
|
||||
act_cfg (dict): Config dict for activation layer.
|
||||
Default: dict(type='ReLU').
|
||||
with_cp (bool, optional): Use checkpoint or not. Using checkpoint
|
||||
will save some memory while slowing down the training speed.
|
||||
Default: False.
|
||||
|
||||
Returns:
|
||||
Tensor: The output tensor.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
groups=3,
|
||||
first_block=True,
|
||||
combine='add',
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
act_cfg=dict(type='ReLU'),
|
||||
with_cp=False):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
act_cfg = copy.deepcopy(act_cfg)
|
||||
super().__init__()
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
self.first_block = first_block
|
||||
self.combine = combine
|
||||
self.groups = groups
|
||||
self.bottleneck_channels = self.out_channels // 4
|
||||
self.with_cp = with_cp
|
||||
|
||||
if self.combine == 'add':
|
||||
self.depthwise_stride = 1
|
||||
self._combine_func = self._add
|
||||
assert in_channels == out_channels, (
|
||||
'in_channels must be equal to out_channels when combine '
|
||||
'is add')
|
||||
elif self.combine == 'concat':
|
||||
self.depthwise_stride = 2
|
||||
self._combine_func = self._concat
|
||||
self.out_channels -= self.in_channels
|
||||
self.avgpool = nn.AvgPool2d(kernel_size=3, stride=2, padding=1)
|
||||
else:
|
||||
raise ValueError(f'Cannot combine tensors with {self.combine}. '
|
||||
'Only "add" and "concat" are supported')
|
||||
|
||||
self.first_1x1_groups = 1 if first_block else self.groups
|
||||
self.g_conv_1x1_compress = ConvModule(
|
||||
in_channels=self.in_channels,
|
||||
out_channels=self.bottleneck_channels,
|
||||
kernel_size=1,
|
||||
groups=self.first_1x1_groups,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg)
|
||||
|
||||
self.depthwise_conv3x3_bn = ConvModule(
|
||||
in_channels=self.bottleneck_channels,
|
||||
out_channels=self.bottleneck_channels,
|
||||
kernel_size=3,
|
||||
stride=self.depthwise_stride,
|
||||
padding=1,
|
||||
groups=self.bottleneck_channels,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None)
|
||||
|
||||
self.g_conv_1x1_expand = ConvModule(
|
||||
in_channels=self.bottleneck_channels,
|
||||
out_channels=self.out_channels,
|
||||
kernel_size=1,
|
||||
groups=self.groups,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None)
|
||||
|
||||
self.act = build_activation_layer(act_cfg)
|
||||
|
||||
@staticmethod
|
||||
def _add(x, out):
|
||||
# residual connection
|
||||
return x + out
|
||||
|
||||
@staticmethod
|
||||
def _concat(x, out):
|
||||
# concatenate along channel axis
|
||||
return torch.cat((x, out), 1)
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
def _inner_forward(x):
|
||||
residual = x
|
||||
|
||||
out = self.g_conv_1x1_compress(x)
|
||||
out = self.depthwise_conv3x3_bn(out)
|
||||
|
||||
if self.groups > 1:
|
||||
out = channel_shuffle(out, self.groups)
|
||||
|
||||
out = self.g_conv_1x1_expand(out)
|
||||
|
||||
if self.combine == 'concat':
|
||||
residual = self.avgpool(residual)
|
||||
out = self.act(out)
|
||||
out = self._combine_func(residual, out)
|
||||
else:
|
||||
out = self._combine_func(residual, out)
|
||||
out = self.act(out)
|
||||
return out
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
out = cp.checkpoint(_inner_forward, x)
|
||||
else:
|
||||
out = _inner_forward(x)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class ShuffleNetV1(BaseBackbone):
|
||||
"""ShuffleNetV1 backbone.
|
||||
|
||||
Args:
|
||||
groups (int, optional): The number of groups to be used in grouped 1x1
|
||||
convolutions in each ShuffleUnit. Default: 3.
|
||||
widen_factor (float, optional): Width multiplier - adjusts the number
|
||||
of channels in each layer by this amount. Default: 1.0.
|
||||
out_indices (Sequence[int]): Output from which stages.
|
||||
Default: (2, )
|
||||
frozen_stages (int): Stages to be frozen (all param fixed).
|
||||
Default: -1, which means not freezing any parameters.
|
||||
conv_cfg (dict): Config dict for convolution layer. Default: None,
|
||||
which means using conv2d.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: dict(type='BN').
|
||||
act_cfg (dict): Config dict for activation layer.
|
||||
Default: dict(type='ReLU').
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False.
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
groups=3,
|
||||
widen_factor=1.0,
|
||||
out_indices=(2, ),
|
||||
frozen_stages=-1,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
act_cfg=dict(type='ReLU'),
|
||||
norm_eval=False,
|
||||
with_cp=False):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
act_cfg = copy.deepcopy(act_cfg)
|
||||
super().__init__()
|
||||
self.stage_blocks = [4, 8, 4]
|
||||
self.groups = groups
|
||||
|
||||
for index in out_indices:
|
||||
if index not in range(0, 3):
|
||||
raise ValueError('the item in out_indices must in '
|
||||
f'range(0, 3). But received {index}')
|
||||
|
||||
if frozen_stages not in range(-1, 3):
|
||||
raise ValueError('frozen_stages must be in range(-1, 3). '
|
||||
f'But received {frozen_stages}')
|
||||
self.out_indices = out_indices
|
||||
self.frozen_stages = frozen_stages
|
||||
self.conv_cfg = conv_cfg
|
||||
self.norm_cfg = norm_cfg
|
||||
self.act_cfg = act_cfg
|
||||
self.norm_eval = norm_eval
|
||||
self.with_cp = with_cp
|
||||
|
||||
if groups == 1:
|
||||
channels = (144, 288, 576)
|
||||
elif groups == 2:
|
||||
channels = (200, 400, 800)
|
||||
elif groups == 3:
|
||||
channels = (240, 480, 960)
|
||||
elif groups == 4:
|
||||
channels = (272, 544, 1088)
|
||||
elif groups == 8:
|
||||
channels = (384, 768, 1536)
|
||||
else:
|
||||
raise ValueError(f'{groups} groups is not supported for 1x1 '
|
||||
'Grouped Convolutions')
|
||||
|
||||
channels = [make_divisible(ch * widen_factor, 8) for ch in channels]
|
||||
|
||||
self.in_channels = int(24 * widen_factor)
|
||||
|
||||
self.conv1 = ConvModule(
|
||||
in_channels=3,
|
||||
out_channels=self.in_channels,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg)
|
||||
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
||||
|
||||
self.layers = nn.ModuleList()
|
||||
for i, num_blocks in enumerate(self.stage_blocks):
|
||||
first_block = (i == 0)
|
||||
layer = self.make_layer(channels[i], num_blocks, first_block)
|
||||
self.layers.append(layer)
|
||||
|
||||
def _freeze_stages(self):
|
||||
if self.frozen_stages >= 0:
|
||||
for param in self.conv1.parameters():
|
||||
param.requires_grad = False
|
||||
for i in range(self.frozen_stages):
|
||||
layer = self.layers[i]
|
||||
layer.eval()
|
||||
for param in layer.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
if isinstance(pretrained, str):
|
||||
logger = logging.getLogger()
|
||||
load_checkpoint(self, pretrained, strict=False, logger=logger)
|
||||
elif pretrained is None:
|
||||
for name, m in self.named_modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
if 'conv1' in name:
|
||||
normal_init(m, mean=0, std=0.01)
|
||||
else:
|
||||
normal_init(m, mean=0, std=1.0 / m.weight.shape[1])
|
||||
elif isinstance(m, (_BatchNorm, nn.GroupNorm)):
|
||||
constant_init(m, val=1, bias=0.0001)
|
||||
if isinstance(m, _BatchNorm):
|
||||
if m.running_mean is not None:
|
||||
nn.init.constant_(m.running_mean, 0)
|
||||
else:
|
||||
raise TypeError('pretrained must be a str or None. But received '
|
||||
f'{type(pretrained)}')
|
||||
|
||||
def make_layer(self, out_channels, num_blocks, first_block=False):
|
||||
"""Stack ShuffleUnit blocks to make a layer.
|
||||
|
||||
Args:
|
||||
out_channels (int): out_channels of the block.
|
||||
num_blocks (int): Number of blocks.
|
||||
first_block (bool, optional): Whether is the first ShuffleUnit of a
|
||||
sequential ShuffleUnits. Default: False, which means using
|
||||
the grouped 1x1 convolution.
|
||||
"""
|
||||
layers = []
|
||||
for i in range(num_blocks):
|
||||
first_block = first_block if i == 0 else False
|
||||
combine_mode = 'concat' if i == 0 else 'add'
|
||||
layers.append(
|
||||
ShuffleUnit(
|
||||
self.in_channels,
|
||||
out_channels,
|
||||
groups=self.groups,
|
||||
first_block=first_block,
|
||||
combine=combine_mode,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
act_cfg=self.act_cfg,
|
||||
with_cp=self.with_cp))
|
||||
self.in_channels = out_channels
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv1(x)
|
||||
x = self.maxpool(x)
|
||||
|
||||
outs = []
|
||||
for i, layer in enumerate(self.layers):
|
||||
x = layer(x)
|
||||
if i in self.out_indices:
|
||||
outs.append(x)
|
||||
|
||||
if len(outs) == 1:
|
||||
return outs[0]
|
||||
return tuple(outs)
|
||||
|
||||
def train(self, mode=True):
|
||||
super().train(mode)
|
||||
self._freeze_stages()
|
||||
if mode and self.norm_eval:
|
||||
for m in self.modules():
|
||||
if isinstance(m, _BatchNorm):
|
||||
m.eval()
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
import logging
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.utils.checkpoint as cp
|
||||
from mmcv.cnn import ConvModule, constant_init, normal_init
|
||||
from torch.nn.modules.batchnorm import _BatchNorm
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
from .utils import channel_shuffle, load_checkpoint
|
||||
|
||||
|
||||
class InvertedResidual(nn.Module):
|
||||
"""InvertedResidual block for ShuffleNetV2 backbone.
|
||||
|
||||
Args:
|
||||
in_channels (int): The input channels of the block.
|
||||
out_channels (int): The output channels of the block.
|
||||
stride (int): Stride of the 3x3 convolution layer. Default: 1
|
||||
conv_cfg (dict): Config dict for convolution layer.
|
||||
Default: None, which means using conv2d.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: dict(type='BN').
|
||||
act_cfg (dict): Config dict for activation layer.
|
||||
Default: dict(type='ReLU').
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
stride=1,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
act_cfg=dict(type='ReLU'),
|
||||
with_cp=False):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
act_cfg = copy.deepcopy(act_cfg)
|
||||
super().__init__()
|
||||
self.stride = stride
|
||||
self.with_cp = with_cp
|
||||
|
||||
branch_features = out_channels // 2
|
||||
if self.stride == 1:
|
||||
assert in_channels == branch_features * 2, (
|
||||
f'in_channels ({in_channels}) should equal to '
|
||||
f'branch_features * 2 ({branch_features * 2}) '
|
||||
'when stride is 1')
|
||||
|
||||
if in_channels != branch_features * 2:
|
||||
assert self.stride != 1, (
|
||||
f'stride ({self.stride}) should not equal 1 when '
|
||||
f'in_channels != branch_features * 2')
|
||||
|
||||
if self.stride > 1:
|
||||
self.branch1 = nn.Sequential(
|
||||
ConvModule(
|
||||
in_channels,
|
||||
in_channels,
|
||||
kernel_size=3,
|
||||
stride=self.stride,
|
||||
padding=1,
|
||||
groups=in_channels,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None),
|
||||
ConvModule(
|
||||
in_channels,
|
||||
branch_features,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg),
|
||||
)
|
||||
|
||||
self.branch2 = nn.Sequential(
|
||||
ConvModule(
|
||||
in_channels if (self.stride > 1) else branch_features,
|
||||
branch_features,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg),
|
||||
ConvModule(
|
||||
branch_features,
|
||||
branch_features,
|
||||
kernel_size=3,
|
||||
stride=self.stride,
|
||||
padding=1,
|
||||
groups=branch_features,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None),
|
||||
ConvModule(
|
||||
branch_features,
|
||||
branch_features,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg))
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
def _inner_forward(x):
|
||||
if self.stride > 1:
|
||||
out = torch.cat((self.branch1(x), self.branch2(x)), dim=1)
|
||||
else:
|
||||
x1, x2 = x.chunk(2, dim=1)
|
||||
out = torch.cat((x1, self.branch2(x2)), dim=1)
|
||||
|
||||
out = channel_shuffle(out, 2)
|
||||
|
||||
return out
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
out = cp.checkpoint(_inner_forward, x)
|
||||
else:
|
||||
out = _inner_forward(x)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class ShuffleNetV2(BaseBackbone):
|
||||
"""ShuffleNetV2 backbone.
|
||||
|
||||
Args:
|
||||
widen_factor (float): Width multiplier - adjusts the number of
|
||||
channels in each layer by this amount. Default: 1.0.
|
||||
out_indices (Sequence[int]): Output from which stages.
|
||||
Default: (0, 1, 2, 3).
|
||||
frozen_stages (int): Stages to be frozen (all param fixed).
|
||||
Default: -1, which means not freezing any parameters.
|
||||
conv_cfg (dict): Config dict for convolution layer.
|
||||
Default: None, which means using conv2d.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: dict(type='BN').
|
||||
act_cfg (dict): Config dict for activation layer.
|
||||
Default: dict(type='ReLU').
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False.
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
widen_factor=1.0,
|
||||
out_indices=(3, ),
|
||||
frozen_stages=-1,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
act_cfg=dict(type='ReLU'),
|
||||
norm_eval=False,
|
||||
with_cp=False):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
act_cfg = copy.deepcopy(act_cfg)
|
||||
super().__init__()
|
||||
self.stage_blocks = [4, 8, 4]
|
||||
for index in out_indices:
|
||||
if index not in range(0, 4):
|
||||
raise ValueError('the item in out_indices must in '
|
||||
f'range(0, 4). But received {index}')
|
||||
|
||||
if frozen_stages not in range(-1, 4):
|
||||
raise ValueError('frozen_stages must be in range(-1, 4). '
|
||||
f'But received {frozen_stages}')
|
||||
self.out_indices = out_indices
|
||||
self.frozen_stages = frozen_stages
|
||||
self.conv_cfg = conv_cfg
|
||||
self.norm_cfg = norm_cfg
|
||||
self.act_cfg = act_cfg
|
||||
self.norm_eval = norm_eval
|
||||
self.with_cp = with_cp
|
||||
|
||||
if widen_factor == 0.5:
|
||||
channels = [48, 96, 192, 1024]
|
||||
elif widen_factor == 1.0:
|
||||
channels = [116, 232, 464, 1024]
|
||||
elif widen_factor == 1.5:
|
||||
channels = [176, 352, 704, 1024]
|
||||
elif widen_factor == 2.0:
|
||||
channels = [244, 488, 976, 2048]
|
||||
else:
|
||||
raise ValueError('widen_factor must be in [0.5, 1.0, 1.5, 2.0]. '
|
||||
f'But received {widen_factor}')
|
||||
|
||||
self.in_channels = 24
|
||||
self.conv1 = ConvModule(
|
||||
in_channels=3,
|
||||
out_channels=self.in_channels,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg)
|
||||
|
||||
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
||||
|
||||
self.layers = nn.ModuleList()
|
||||
for i, num_blocks in enumerate(self.stage_blocks):
|
||||
layer = self._make_layer(channels[i], num_blocks)
|
||||
self.layers.append(layer)
|
||||
|
||||
output_channels = channels[-1]
|
||||
self.layers.append(
|
||||
ConvModule(
|
||||
in_channels=self.in_channels,
|
||||
out_channels=output_channels,
|
||||
kernel_size=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg))
|
||||
|
||||
def _make_layer(self, out_channels, num_blocks):
|
||||
"""Stack blocks to make a layer.
|
||||
|
||||
Args:
|
||||
out_channels (int): out_channels of the block.
|
||||
num_blocks (int): number of blocks.
|
||||
"""
|
||||
layers = []
|
||||
for i in range(num_blocks):
|
||||
stride = 2 if i == 0 else 1
|
||||
layers.append(
|
||||
InvertedResidual(
|
||||
in_channels=self.in_channels,
|
||||
out_channels=out_channels,
|
||||
stride=stride,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
act_cfg=self.act_cfg,
|
||||
with_cp=self.with_cp))
|
||||
self.in_channels = out_channels
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def _freeze_stages(self):
|
||||
if self.frozen_stages >= 0:
|
||||
for param in self.conv1.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
for i in range(self.frozen_stages):
|
||||
m = self.layers[i]
|
||||
m.eval()
|
||||
for param in m.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
if isinstance(pretrained, str):
|
||||
logger = logging.getLogger()
|
||||
load_checkpoint(self, pretrained, strict=False, logger=logger)
|
||||
elif pretrained is None:
|
||||
for name, m in self.named_modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
if 'conv1' in name:
|
||||
normal_init(m, mean=0, std=0.01)
|
||||
else:
|
||||
normal_init(m, mean=0, std=1.0 / m.weight.shape[1])
|
||||
elif isinstance(m, (_BatchNorm, nn.GroupNorm)):
|
||||
constant_init(m.weight, val=1, bias=0.0001)
|
||||
if isinstance(m, _BatchNorm):
|
||||
if m.running_mean is not None:
|
||||
nn.init.constant_(m.running_mean, 0)
|
||||
else:
|
||||
raise TypeError('pretrained must be a str or None. But received '
|
||||
f'{type(pretrained)}')
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv1(x)
|
||||
x = self.maxpool(x)
|
||||
|
||||
outs = []
|
||||
for i, layer in enumerate(self.layers):
|
||||
x = layer(x)
|
||||
if i in self.out_indices:
|
||||
outs.append(x)
|
||||
|
||||
if len(outs) == 1:
|
||||
return outs[0]
|
||||
return tuple(outs)
|
||||
|
||||
def train(self, mode=True):
|
||||
super().train(mode)
|
||||
self._freeze_stages()
|
||||
if mode and self.norm_eval:
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.BatchNorm2d):
|
||||
m.eval()
|
||||
@@ -0,0 +1,267 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import ConvModule, build_conv_layer, constant_init, kaiming_init
|
||||
from mmcv.utils.parrots_wrapper import _BatchNorm
|
||||
|
||||
from mmpose.core import WeightNormClipHook
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
|
||||
|
||||
class BasicTemporalBlock(nn.Module):
|
||||
"""Basic block for VideoPose3D.
|
||||
|
||||
Args:
|
||||
in_channels (int): Input channels of this block.
|
||||
out_channels (int): Output channels of this block.
|
||||
mid_channels (int): The output channels of conv1. Default: 1024.
|
||||
kernel_size (int): Size of the convolving kernel. Default: 3.
|
||||
dilation (int): Spacing between kernel elements. Default: 3.
|
||||
dropout (float): Dropout rate. Default: 0.25.
|
||||
causal (bool): Use causal convolutions instead of symmetric
|
||||
convolutions (for real-time applications). Default: False.
|
||||
residual (bool): Use residual connection. Default: True.
|
||||
use_stride_conv (bool): Use optimized TCN that designed
|
||||
specifically for single-frame batching, i.e. where batches have
|
||||
input length = receptive field, and output length = 1. This
|
||||
implementation replaces dilated convolutions with strided
|
||||
convolutions to avoid generating unused intermediate results.
|
||||
Default: False.
|
||||
conv_cfg (dict): dictionary to construct and config conv layer.
|
||||
Default: dict(type='Conv1d').
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN1d').
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
mid_channels=1024,
|
||||
kernel_size=3,
|
||||
dilation=3,
|
||||
dropout=0.25,
|
||||
causal=False,
|
||||
residual=True,
|
||||
use_stride_conv=False,
|
||||
conv_cfg=dict(type='Conv1d'),
|
||||
norm_cfg=dict(type='BN1d')):
|
||||
# Protect mutable default arguments
|
||||
conv_cfg = copy.deepcopy(conv_cfg)
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
self.mid_channels = mid_channels
|
||||
self.kernel_size = kernel_size
|
||||
self.dilation = dilation
|
||||
self.dropout = dropout
|
||||
self.causal = causal
|
||||
self.residual = residual
|
||||
self.use_stride_conv = use_stride_conv
|
||||
|
||||
self.pad = (kernel_size - 1) * dilation // 2
|
||||
if use_stride_conv:
|
||||
self.stride = kernel_size
|
||||
self.causal_shift = kernel_size // 2 if causal else 0
|
||||
self.dilation = 1
|
||||
else:
|
||||
self.stride = 1
|
||||
self.causal_shift = kernel_size // 2 * dilation if causal else 0
|
||||
|
||||
self.conv1 = nn.Sequential(
|
||||
ConvModule(
|
||||
in_channels,
|
||||
mid_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=self.stride,
|
||||
dilation=self.dilation,
|
||||
bias='auto',
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg))
|
||||
self.conv2 = nn.Sequential(
|
||||
ConvModule(
|
||||
mid_channels,
|
||||
out_channels,
|
||||
kernel_size=1,
|
||||
bias='auto',
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg))
|
||||
|
||||
if residual and in_channels != out_channels:
|
||||
self.short_cut = build_conv_layer(conv_cfg, in_channels,
|
||||
out_channels, 1)
|
||||
else:
|
||||
self.short_cut = None
|
||||
|
||||
self.dropout = nn.Dropout(dropout) if dropout > 0 else None
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
if self.use_stride_conv:
|
||||
assert self.causal_shift + self.kernel_size // 2 < x.shape[2]
|
||||
else:
|
||||
assert 0 <= self.pad + self.causal_shift < x.shape[2] - \
|
||||
self.pad + self.causal_shift <= x.shape[2]
|
||||
|
||||
out = self.conv1(x)
|
||||
if self.dropout is not None:
|
||||
out = self.dropout(out)
|
||||
|
||||
out = self.conv2(out)
|
||||
if self.dropout is not None:
|
||||
out = self.dropout(out)
|
||||
|
||||
if self.residual:
|
||||
if self.use_stride_conv:
|
||||
res = x[:, :, self.causal_shift +
|
||||
self.kernel_size // 2::self.kernel_size]
|
||||
else:
|
||||
res = x[:, :,
|
||||
(self.pad + self.causal_shift):(x.shape[2] - self.pad +
|
||||
self.causal_shift)]
|
||||
|
||||
if self.short_cut is not None:
|
||||
res = self.short_cut(res)
|
||||
out = out + res
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class TCN(BaseBackbone):
|
||||
"""TCN backbone.
|
||||
|
||||
Temporal Convolutional Networks.
|
||||
More details can be found in the
|
||||
`paper <https://arxiv.org/abs/1811.11742>`__ .
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels, which equals to
|
||||
num_keypoints * num_features.
|
||||
stem_channels (int): Number of feature channels. Default: 1024.
|
||||
num_blocks (int): NUmber of basic temporal convolutional blocks.
|
||||
Default: 2.
|
||||
kernel_sizes (Sequence[int]): Sizes of the convolving kernel of
|
||||
each basic block. Default: ``(3, 3, 3)``.
|
||||
dropout (float): Dropout rate. Default: 0.25.
|
||||
causal (bool): Use causal convolutions instead of symmetric
|
||||
convolutions (for real-time applications).
|
||||
Default: False.
|
||||
residual (bool): Use residual connection. Default: True.
|
||||
use_stride_conv (bool): Use TCN backbone optimized for
|
||||
single-frame batching, i.e. where batches have input length =
|
||||
receptive field, and output length = 1. This implementation
|
||||
replaces dilated convolutions with strided convolutions to avoid
|
||||
generating unused intermediate results. The weights are
|
||||
interchangeable with the reference implementation. Default: False
|
||||
conv_cfg (dict): dictionary to construct and config conv layer.
|
||||
Default: dict(type='Conv1d').
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN1d').
|
||||
max_norm (float|None): if not None, the weight of convolution layers
|
||||
will be clipped to have a maximum norm of max_norm.
|
||||
|
||||
Example:
|
||||
>>> from mmpose.models import TCN
|
||||
>>> import torch
|
||||
>>> self = TCN(in_channels=34)
|
||||
>>> self.eval()
|
||||
>>> inputs = torch.rand(1, 34, 243)
|
||||
>>> level_outputs = self.forward(inputs)
|
||||
>>> for level_out in level_outputs:
|
||||
... print(tuple(level_out.shape))
|
||||
(1, 1024, 235)
|
||||
(1, 1024, 217)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
stem_channels=1024,
|
||||
num_blocks=2,
|
||||
kernel_sizes=(3, 3, 3),
|
||||
dropout=0.25,
|
||||
causal=False,
|
||||
residual=True,
|
||||
use_stride_conv=False,
|
||||
conv_cfg=dict(type='Conv1d'),
|
||||
norm_cfg=dict(type='BN1d'),
|
||||
max_norm=None):
|
||||
# Protect mutable default arguments
|
||||
conv_cfg = copy.deepcopy(conv_cfg)
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
self.in_channels = in_channels
|
||||
self.stem_channels = stem_channels
|
||||
self.num_blocks = num_blocks
|
||||
self.kernel_sizes = kernel_sizes
|
||||
self.dropout = dropout
|
||||
self.causal = causal
|
||||
self.residual = residual
|
||||
self.use_stride_conv = use_stride_conv
|
||||
self.max_norm = max_norm
|
||||
|
||||
assert num_blocks == len(kernel_sizes) - 1
|
||||
for ks in kernel_sizes:
|
||||
assert ks % 2 == 1, 'Only odd filter widths are supported.'
|
||||
|
||||
self.expand_conv = ConvModule(
|
||||
in_channels,
|
||||
stem_channels,
|
||||
kernel_size=kernel_sizes[0],
|
||||
stride=kernel_sizes[0] if use_stride_conv else 1,
|
||||
bias='auto',
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg)
|
||||
|
||||
dilation = kernel_sizes[0]
|
||||
self.tcn_blocks = nn.ModuleList()
|
||||
for i in range(1, num_blocks + 1):
|
||||
self.tcn_blocks.append(
|
||||
BasicTemporalBlock(
|
||||
in_channels=stem_channels,
|
||||
out_channels=stem_channels,
|
||||
mid_channels=stem_channels,
|
||||
kernel_size=kernel_sizes[i],
|
||||
dilation=dilation,
|
||||
dropout=dropout,
|
||||
causal=causal,
|
||||
residual=residual,
|
||||
use_stride_conv=use_stride_conv,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg))
|
||||
dilation *= kernel_sizes[i]
|
||||
|
||||
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)
|
||||
|
||||
self.dropout = nn.Dropout(dropout) if dropout > 0 else None
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
x = self.expand_conv(x)
|
||||
|
||||
if self.dropout is not None:
|
||||
x = self.dropout(x)
|
||||
|
||||
outs = []
|
||||
for i in range(self.num_blocks):
|
||||
x = self.tcn_blocks[i](x)
|
||||
outs.append(x)
|
||||
|
||||
return tuple(outs)
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Initialize the weights."""
|
||||
super().init_weights(pretrained)
|
||||
if pretrained is None:
|
||||
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)
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .channel_shuffle import channel_shuffle
|
||||
from .inverted_residual import InvertedResidual
|
||||
from .make_divisible import make_divisible
|
||||
from .se_layer import SELayer
|
||||
from .utils import load_checkpoint
|
||||
|
||||
__all__ = [
|
||||
'channel_shuffle', 'make_divisible', 'InvertedResidual', 'SELayer',
|
||||
'load_checkpoint'
|
||||
]
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
|
||||
|
||||
def channel_shuffle(x, groups):
|
||||
"""Channel Shuffle operation.
|
||||
|
||||
This function enables cross-group information flow for multiple groups
|
||||
convolution layers.
|
||||
|
||||
Args:
|
||||
x (Tensor): The input tensor.
|
||||
groups (int): The number of groups to divide the input tensor
|
||||
in the channel dimension.
|
||||
|
||||
Returns:
|
||||
Tensor: The output tensor after channel shuffle operation.
|
||||
"""
|
||||
|
||||
batch_size, num_channels, height, width = x.size()
|
||||
assert (num_channels % groups == 0), ('num_channels should be '
|
||||
'divisible by groups')
|
||||
channels_per_group = num_channels // groups
|
||||
|
||||
x = x.view(batch_size, groups, channels_per_group, height, width)
|
||||
x = torch.transpose(x, 1, 2).contiguous()
|
||||
x = x.view(batch_size, -1, height, width)
|
||||
|
||||
return x
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
|
||||
import torch.nn as nn
|
||||
import torch.utils.checkpoint as cp
|
||||
from mmcv.cnn import ConvModule
|
||||
|
||||
from .se_layer import SELayer
|
||||
|
||||
|
||||
class InvertedResidual(nn.Module):
|
||||
"""Inverted Residual Block.
|
||||
|
||||
Args:
|
||||
in_channels (int): The input channels of this Module.
|
||||
out_channels (int): The output channels of this Module.
|
||||
mid_channels (int): The input channels of the depthwise convolution.
|
||||
kernel_size (int): The kernel size of the depthwise convolution.
|
||||
Default: 3.
|
||||
groups (None or int): The group number of the depthwise convolution.
|
||||
Default: None, which means group number = mid_channels.
|
||||
stride (int): The stride of the depthwise convolution. Default: 1.
|
||||
se_cfg (dict): Config dict for se layer. Default: None, which means no
|
||||
se layer.
|
||||
with_expand_conv (bool): Use expand conv or not. If set False,
|
||||
mid_channels must be the same with in_channels.
|
||||
Default: True.
|
||||
conv_cfg (dict): Config dict for convolution layer. Default: None,
|
||||
which means using conv2d.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: dict(type='BN').
|
||||
act_cfg (dict): Config dict for activation layer.
|
||||
Default: dict(type='ReLU').
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False.
|
||||
|
||||
Returns:
|
||||
Tensor: The output tensor.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
mid_channels,
|
||||
kernel_size=3,
|
||||
groups=None,
|
||||
stride=1,
|
||||
se_cfg=None,
|
||||
with_expand_conv=True,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
act_cfg=dict(type='ReLU'),
|
||||
with_cp=False):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
act_cfg = copy.deepcopy(act_cfg)
|
||||
super().__init__()
|
||||
self.with_res_shortcut = (stride == 1 and in_channels == out_channels)
|
||||
assert stride in [1, 2]
|
||||
self.with_cp = with_cp
|
||||
self.with_se = se_cfg is not None
|
||||
self.with_expand_conv = with_expand_conv
|
||||
|
||||
if groups is None:
|
||||
groups = mid_channels
|
||||
|
||||
if self.with_se:
|
||||
assert isinstance(se_cfg, dict)
|
||||
if not self.with_expand_conv:
|
||||
assert mid_channels == in_channels
|
||||
|
||||
if self.with_expand_conv:
|
||||
self.expand_conv = ConvModule(
|
||||
in_channels=in_channels,
|
||||
out_channels=mid_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg)
|
||||
self.depthwise_conv = ConvModule(
|
||||
in_channels=mid_channels,
|
||||
out_channels=mid_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
padding=kernel_size // 2,
|
||||
groups=groups,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg)
|
||||
if self.with_se:
|
||||
self.se = SELayer(**se_cfg)
|
||||
self.linear_conv = ConvModule(
|
||||
in_channels=mid_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None)
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
def _inner_forward(x):
|
||||
out = x
|
||||
|
||||
if self.with_expand_conv:
|
||||
out = self.expand_conv(out)
|
||||
|
||||
out = self.depthwise_conv(out)
|
||||
|
||||
if self.with_se:
|
||||
out = self.se(out)
|
||||
|
||||
out = self.linear_conv(out)
|
||||
|
||||
if self.with_res_shortcut:
|
||||
return x + out
|
||||
return out
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
out = cp.checkpoint(_inner_forward, x)
|
||||
else:
|
||||
out = _inner_forward(x)
|
||||
|
||||
return out
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
def make_divisible(value, divisor, min_value=None, min_ratio=0.9):
|
||||
"""Make divisible function.
|
||||
|
||||
This function rounds the channel number down to the nearest value that can
|
||||
be divisible by the divisor.
|
||||
|
||||
Args:
|
||||
value (int): The original channel number.
|
||||
divisor (int): The divisor to fully divide the channel number.
|
||||
min_value (int, optional): The minimum value of the output channel.
|
||||
Default: None, means that the minimum value equal to the divisor.
|
||||
min_ratio (float, optional): The minimum ratio of the rounded channel
|
||||
number to the original channel number. Default: 0.9.
|
||||
Returns:
|
||||
int: The modified output channel number
|
||||
"""
|
||||
|
||||
if min_value is None:
|
||||
min_value = divisor
|
||||
new_value = max(min_value, int(value + divisor / 2) // divisor * divisor)
|
||||
# Make sure that round down does not go down by more than (1-min_ratio).
|
||||
if new_value < min_ratio * value:
|
||||
new_value += divisor
|
||||
return new_value
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import mmcv
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import ConvModule
|
||||
|
||||
|
||||
class SELayer(nn.Module):
|
||||
"""Squeeze-and-Excitation Module.
|
||||
|
||||
Args:
|
||||
channels (int): The input (and output) channels of the SE layer.
|
||||
ratio (int): Squeeze ratio in SELayer, the intermediate channel will be
|
||||
``int(channels/ratio)``. Default: 16.
|
||||
conv_cfg (None or dict): Config dict for convolution layer.
|
||||
Default: None, which means using conv2d.
|
||||
act_cfg (dict or Sequence[dict]): Config dict for activation layer.
|
||||
If act_cfg is a dict, two activation layers will be configurated
|
||||
by this dict. If act_cfg is a sequence of dicts, the first
|
||||
activation layer will be configurated by the first dict and the
|
||||
second activation layer will be configurated by the second dict.
|
||||
Default: (dict(type='ReLU'), dict(type='Sigmoid'))
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
channels,
|
||||
ratio=16,
|
||||
conv_cfg=None,
|
||||
act_cfg=(dict(type='ReLU'), dict(type='Sigmoid'))):
|
||||
super().__init__()
|
||||
if isinstance(act_cfg, dict):
|
||||
act_cfg = (act_cfg, act_cfg)
|
||||
assert len(act_cfg) == 2
|
||||
assert mmcv.is_tuple_of(act_cfg, dict)
|
||||
self.global_avgpool = nn.AdaptiveAvgPool2d(1)
|
||||
self.conv1 = ConvModule(
|
||||
in_channels=channels,
|
||||
out_channels=int(channels / ratio),
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
conv_cfg=conv_cfg,
|
||||
act_cfg=act_cfg[0])
|
||||
self.conv2 = ConvModule(
|
||||
in_channels=int(channels / ratio),
|
||||
out_channels=channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
conv_cfg=conv_cfg,
|
||||
act_cfg=act_cfg[1])
|
||||
|
||||
def forward(self, x):
|
||||
out = self.global_avgpool(x)
|
||||
out = self.conv1(out)
|
||||
out = self.conv2(out)
|
||||
return x * out
|
||||
@@ -0,0 +1,87 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from collections import OrderedDict
|
||||
|
||||
from mmcv.runner.checkpoint import _load_checkpoint, load_state_dict
|
||||
|
||||
|
||||
def load_checkpoint(model,
|
||||
filename,
|
||||
map_location='cpu',
|
||||
strict=False,
|
||||
logger=None):
|
||||
"""Load checkpoint from a file or URI.
|
||||
|
||||
Args:
|
||||
model (Module): Module to load checkpoint.
|
||||
filename (str): Accept local filepath, URL, ``torchvision://xxx``,
|
||||
``open-mmlab://xxx``.
|
||||
map_location (str): Same as :func:`torch.load`.
|
||||
strict (bool): Whether to allow different params for the model and
|
||||
checkpoint.
|
||||
logger (:mod:`logging.Logger` or None): The logger for error message.
|
||||
|
||||
Returns:
|
||||
dict or OrderedDict: The loaded checkpoint.
|
||||
"""
|
||||
checkpoint = _load_checkpoint(filename, map_location)
|
||||
# OrderedDict is a subclass of dict
|
||||
if not isinstance(checkpoint, dict):
|
||||
raise RuntimeError(
|
||||
f'No state_dict found in checkpoint file {filename}')
|
||||
# get state_dict from checkpoint
|
||||
if 'state_dict' in checkpoint:
|
||||
state_dict_tmp = checkpoint['state_dict']
|
||||
else:
|
||||
state_dict_tmp = checkpoint
|
||||
|
||||
state_dict = OrderedDict()
|
||||
# strip prefix of state_dict
|
||||
for k, v in state_dict_tmp.items():
|
||||
if k.startswith('module.backbone.'):
|
||||
state_dict[k[16:]] = v
|
||||
elif k.startswith('module.'):
|
||||
state_dict[k[7:]] = v
|
||||
elif k.startswith('backbone.'):
|
||||
state_dict[k[9:]] = v
|
||||
else:
|
||||
state_dict[k] = v
|
||||
# load state_dict
|
||||
load_state_dict(model, state_dict, strict, logger)
|
||||
return checkpoint
|
||||
|
||||
|
||||
def get_state_dict(filename, map_location='cpu'):
|
||||
"""Get state_dict from a file or URI.
|
||||
|
||||
Args:
|
||||
filename (str): Accept local filepath, URL, ``torchvision://xxx``,
|
||||
``open-mmlab://xxx``.
|
||||
map_location (str): Same as :func:`torch.load`.
|
||||
|
||||
Returns:
|
||||
OrderedDict: The state_dict.
|
||||
"""
|
||||
checkpoint = _load_checkpoint(filename, map_location)
|
||||
# OrderedDict is a subclass of dict
|
||||
if not isinstance(checkpoint, dict):
|
||||
raise RuntimeError(
|
||||
f'No state_dict found in checkpoint file {filename}')
|
||||
# get state_dict from checkpoint
|
||||
if 'state_dict' in checkpoint:
|
||||
state_dict_tmp = checkpoint['state_dict']
|
||||
else:
|
||||
state_dict_tmp = checkpoint
|
||||
|
||||
state_dict = OrderedDict()
|
||||
# strip prefix of state_dict
|
||||
for k, v in state_dict_tmp.items():
|
||||
if k.startswith('module.backbone.'):
|
||||
state_dict[k[16:]] = v
|
||||
elif k.startswith('module.'):
|
||||
state_dict[k[7:]] = v
|
||||
elif k.startswith('backbone.'):
|
||||
state_dict[k[9:]] = v
|
||||
else:
|
||||
state_dict[k] = v
|
||||
|
||||
return state_dict
|
||||
@@ -0,0 +1,257 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright and License Information
|
||||
# Adapted from
|
||||
# https://github.com/microsoft/voxelpose-pytorch/blob/main/lib/models/v2v_net.py
|
||||
# Original Licence: MIT License
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmcv.cnn import ConvModule
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
|
||||
|
||||
class Basic3DBlock(nn.Module):
|
||||
"""A basic 3D convolutional block.
|
||||
|
||||
Args:
|
||||
in_channels (int): Input channels of this block.
|
||||
out_channels (int): Output channels of this block.
|
||||
kernel_size (int): Kernel size of the convolution operation
|
||||
conv_cfg (dict): Dictionary to construct and config conv layer.
|
||||
Default: dict(type='Conv3d')
|
||||
norm_cfg (dict): Dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN3d')
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size,
|
||||
conv_cfg=dict(type='Conv3d'),
|
||||
norm_cfg=dict(type='BN3d')):
|
||||
super(Basic3DBlock, self).__init__()
|
||||
self.block = ConvModule(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size,
|
||||
stride=1,
|
||||
padding=((kernel_size - 1) // 2),
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
bias=True)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
return self.block(x)
|
||||
|
||||
|
||||
class Res3DBlock(nn.Module):
|
||||
"""A residual 3D convolutional block.
|
||||
|
||||
Args:
|
||||
in_channels (int): Input channels of this block.
|
||||
out_channels (int): Output channels of this block.
|
||||
kernel_size (int): Kernel size of the convolution operation
|
||||
Default: 3
|
||||
conv_cfg (dict): Dictionary to construct and config conv layer.
|
||||
Default: dict(type='Conv3d')
|
||||
norm_cfg (dict): Dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN3d')
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=3,
|
||||
conv_cfg=dict(type='Conv3d'),
|
||||
norm_cfg=dict(type='BN3d')):
|
||||
super(Res3DBlock, self).__init__()
|
||||
self.res_branch = nn.Sequential(
|
||||
ConvModule(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size,
|
||||
stride=1,
|
||||
padding=((kernel_size - 1) // 2),
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
bias=True),
|
||||
ConvModule(
|
||||
out_channels,
|
||||
out_channels,
|
||||
kernel_size,
|
||||
stride=1,
|
||||
padding=((kernel_size - 1) // 2),
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None,
|
||||
bias=True))
|
||||
|
||||
if in_channels == out_channels:
|
||||
self.skip_con = nn.Sequential()
|
||||
else:
|
||||
self.skip_con = ConvModule(
|
||||
in_channels,
|
||||
out_channels,
|
||||
1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None,
|
||||
bias=True)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
res = self.res_branch(x)
|
||||
skip = self.skip_con(x)
|
||||
return F.relu(res + skip, True)
|
||||
|
||||
|
||||
class Pool3DBlock(nn.Module):
|
||||
"""A 3D max-pool block.
|
||||
|
||||
Args:
|
||||
pool_size (int): Pool size of the 3D max-pool layer
|
||||
"""
|
||||
|
||||
def __init__(self, pool_size):
|
||||
super(Pool3DBlock, self).__init__()
|
||||
self.pool_size = pool_size
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
return F.max_pool3d(
|
||||
x, kernel_size=self.pool_size, stride=self.pool_size)
|
||||
|
||||
|
||||
class Upsample3DBlock(nn.Module):
|
||||
"""A 3D upsample block.
|
||||
|
||||
Args:
|
||||
in_channels (int): Input channels of this block.
|
||||
out_channels (int): Output channels of this block.
|
||||
kernel_size (int): Kernel size of the transposed convolution operation.
|
||||
Default: 2
|
||||
stride (int): Kernel size of the transposed convolution operation.
|
||||
Default: 2
|
||||
"""
|
||||
|
||||
def __init__(self, in_channels, out_channels, kernel_size=2, stride=2):
|
||||
super(Upsample3DBlock, self).__init__()
|
||||
assert kernel_size == 2
|
||||
assert stride == 2
|
||||
self.block = nn.Sequential(
|
||||
nn.ConvTranspose3d(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
padding=0,
|
||||
output_padding=0), nn.BatchNorm3d(out_channels), nn.ReLU(True))
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
return self.block(x)
|
||||
|
||||
|
||||
class EncoderDecorder(nn.Module):
|
||||
"""An encoder-decoder block.
|
||||
|
||||
Args:
|
||||
in_channels (int): Input channels of this block
|
||||
"""
|
||||
|
||||
def __init__(self, in_channels=32):
|
||||
super(EncoderDecorder, self).__init__()
|
||||
|
||||
self.encoder_pool1 = Pool3DBlock(2)
|
||||
self.encoder_res1 = Res3DBlock(in_channels, in_channels * 2)
|
||||
self.encoder_pool2 = Pool3DBlock(2)
|
||||
self.encoder_res2 = Res3DBlock(in_channels * 2, in_channels * 4)
|
||||
|
||||
self.mid_res = Res3DBlock(in_channels * 4, in_channels * 4)
|
||||
|
||||
self.decoder_res2 = Res3DBlock(in_channels * 4, in_channels * 4)
|
||||
self.decoder_upsample2 = Upsample3DBlock(in_channels * 4,
|
||||
in_channels * 2, 2, 2)
|
||||
self.decoder_res1 = Res3DBlock(in_channels * 2, in_channels * 2)
|
||||
self.decoder_upsample1 = Upsample3DBlock(in_channels * 2, in_channels,
|
||||
2, 2)
|
||||
|
||||
self.skip_res1 = Res3DBlock(in_channels, in_channels)
|
||||
self.skip_res2 = Res3DBlock(in_channels * 2, in_channels * 2)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
skip_x1 = self.skip_res1(x)
|
||||
x = self.encoder_pool1(x)
|
||||
x = self.encoder_res1(x)
|
||||
|
||||
skip_x2 = self.skip_res2(x)
|
||||
x = self.encoder_pool2(x)
|
||||
x = self.encoder_res2(x)
|
||||
|
||||
x = self.mid_res(x)
|
||||
|
||||
x = self.decoder_res2(x)
|
||||
x = self.decoder_upsample2(x)
|
||||
x = x + skip_x2
|
||||
|
||||
x = self.decoder_res1(x)
|
||||
x = self.decoder_upsample1(x)
|
||||
x = x + skip_x1
|
||||
|
||||
return x
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class V2VNet(BaseBackbone):
|
||||
"""V2VNet.
|
||||
|
||||
Please refer to the `paper <https://arxiv.org/abs/1711.07399>`
|
||||
for details.
|
||||
|
||||
Args:
|
||||
input_channels (int):
|
||||
Number of channels of the input feature volume.
|
||||
output_channels (int):
|
||||
Number of channels of the output volume.
|
||||
mid_channels (int):
|
||||
Input and output channels of the encoder-decoder block.
|
||||
"""
|
||||
|
||||
def __init__(self, input_channels, output_channels, mid_channels=32):
|
||||
super(V2VNet, self).__init__()
|
||||
|
||||
self.front_layers = nn.Sequential(
|
||||
Basic3DBlock(input_channels, mid_channels // 2, 7),
|
||||
Res3DBlock(mid_channels // 2, mid_channels),
|
||||
)
|
||||
|
||||
self.encoder_decoder = EncoderDecorder(in_channels=mid_channels)
|
||||
|
||||
self.output_layer = nn.Conv3d(
|
||||
mid_channels, output_channels, kernel_size=1, stride=1, padding=0)
|
||||
|
||||
self._initialize_weights()
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
x = self.front_layers(x)
|
||||
x = self.encoder_decoder(x)
|
||||
x = self.output_layer(x)
|
||||
|
||||
return x
|
||||
|
||||
def _initialize_weights(self):
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv3d):
|
||||
nn.init.normal_(m.weight, 0, 0.001)
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.ConvTranspose3d):
|
||||
nn.init.normal_(m.weight, 0, 0.001)
|
||||
nn.init.constant_(m.bias, 0)
|
||||
@@ -0,0 +1,193 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import ConvModule, constant_init, kaiming_init, normal_init
|
||||
from mmcv.utils.parrots_wrapper import _BatchNorm
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
|
||||
|
||||
def make_vgg_layer(in_channels,
|
||||
out_channels,
|
||||
num_blocks,
|
||||
conv_cfg=None,
|
||||
norm_cfg=None,
|
||||
act_cfg=dict(type='ReLU'),
|
||||
dilation=1,
|
||||
with_norm=False,
|
||||
ceil_mode=False):
|
||||
layers = []
|
||||
for _ in range(num_blocks):
|
||||
layer = ConvModule(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=3,
|
||||
dilation=dilation,
|
||||
padding=dilation,
|
||||
bias=True,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg)
|
||||
layers.append(layer)
|
||||
in_channels = out_channels
|
||||
layers.append(nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=ceil_mode))
|
||||
|
||||
return layers
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class VGG(BaseBackbone):
|
||||
"""VGG backbone.
|
||||
|
||||
Args:
|
||||
depth (int): Depth of vgg, from {11, 13, 16, 19}.
|
||||
with_norm (bool): Use BatchNorm or not.
|
||||
num_classes (int): number of classes for classification.
|
||||
num_stages (int): VGG stages, normally 5.
|
||||
dilations (Sequence[int]): Dilation of each stage.
|
||||
out_indices (Sequence[int]): Output from which stages. If only one
|
||||
stage is specified, a single tensor (feature map) is returned,
|
||||
otherwise multiple stages are specified, a tuple of tensors will
|
||||
be returned. When it is None, the default behavior depends on
|
||||
whether num_classes is specified. If num_classes <= 0, the default
|
||||
value is (4, ), outputting the last feature map before classifier.
|
||||
If num_classes > 0, the default value is (5, ), outputting the
|
||||
classification score. Default: None.
|
||||
frozen_stages (int): Stages to be frozen (all param fixed). -1 means
|
||||
not freezing any parameters.
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False.
|
||||
ceil_mode (bool): Whether to use ceil_mode of MaxPool. Default: False.
|
||||
with_last_pool (bool): Whether to keep the last pooling before
|
||||
classifier. Default: True.
|
||||
"""
|
||||
|
||||
# Parameters to build layers. Each element specifies the number of conv in
|
||||
# each stage. For example, VGG11 contains 11 layers with learnable
|
||||
# parameters. 11 is computed as 11 = (1 + 1 + 2 + 2 + 2) + 3,
|
||||
# where 3 indicates the last three fully-connected layers.
|
||||
arch_settings = {
|
||||
11: (1, 1, 2, 2, 2),
|
||||
13: (2, 2, 2, 2, 2),
|
||||
16: (2, 2, 3, 3, 3),
|
||||
19: (2, 2, 4, 4, 4)
|
||||
}
|
||||
|
||||
def __init__(self,
|
||||
depth,
|
||||
num_classes=-1,
|
||||
num_stages=5,
|
||||
dilations=(1, 1, 1, 1, 1),
|
||||
out_indices=None,
|
||||
frozen_stages=-1,
|
||||
conv_cfg=None,
|
||||
norm_cfg=None,
|
||||
act_cfg=dict(type='ReLU'),
|
||||
norm_eval=False,
|
||||
ceil_mode=False,
|
||||
with_last_pool=True):
|
||||
super().__init__()
|
||||
if depth not in self.arch_settings:
|
||||
raise KeyError(f'invalid depth {depth} for vgg')
|
||||
assert num_stages >= 1 and num_stages <= 5
|
||||
stage_blocks = self.arch_settings[depth]
|
||||
self.stage_blocks = stage_blocks[:num_stages]
|
||||
assert len(dilations) == num_stages
|
||||
|
||||
self.num_classes = num_classes
|
||||
self.frozen_stages = frozen_stages
|
||||
self.norm_eval = norm_eval
|
||||
with_norm = norm_cfg is not None
|
||||
|
||||
if out_indices is None:
|
||||
out_indices = (5, ) if num_classes > 0 else (4, )
|
||||
assert max(out_indices) <= num_stages
|
||||
self.out_indices = out_indices
|
||||
|
||||
self.in_channels = 3
|
||||
start_idx = 0
|
||||
vgg_layers = []
|
||||
self.range_sub_modules = []
|
||||
for i, num_blocks in enumerate(self.stage_blocks):
|
||||
num_modules = num_blocks + 1
|
||||
end_idx = start_idx + num_modules
|
||||
dilation = dilations[i]
|
||||
out_channels = 64 * 2**i if i < 4 else 512
|
||||
vgg_layer = make_vgg_layer(
|
||||
self.in_channels,
|
||||
out_channels,
|
||||
num_blocks,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg,
|
||||
dilation=dilation,
|
||||
with_norm=with_norm,
|
||||
ceil_mode=ceil_mode)
|
||||
vgg_layers.extend(vgg_layer)
|
||||
self.in_channels = out_channels
|
||||
self.range_sub_modules.append([start_idx, end_idx])
|
||||
start_idx = end_idx
|
||||
if not with_last_pool:
|
||||
vgg_layers.pop(-1)
|
||||
self.range_sub_modules[-1][1] -= 1
|
||||
self.module_name = 'features'
|
||||
self.add_module(self.module_name, nn.Sequential(*vgg_layers))
|
||||
|
||||
if self.num_classes > 0:
|
||||
self.classifier = nn.Sequential(
|
||||
nn.Linear(512 * 7 * 7, 4096),
|
||||
nn.ReLU(True),
|
||||
nn.Dropout(),
|
||||
nn.Linear(4096, 4096),
|
||||
nn.ReLU(True),
|
||||
nn.Dropout(),
|
||||
nn.Linear(4096, num_classes),
|
||||
)
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
super().init_weights(pretrained)
|
||||
if pretrained is None:
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
kaiming_init(m)
|
||||
elif isinstance(m, _BatchNorm):
|
||||
constant_init(m, 1)
|
||||
elif isinstance(m, nn.Linear):
|
||||
normal_init(m, std=0.01)
|
||||
|
||||
def forward(self, x):
|
||||
outs = []
|
||||
vgg_layers = getattr(self, self.module_name)
|
||||
for i in range(len(self.stage_blocks)):
|
||||
for j in range(*self.range_sub_modules[i]):
|
||||
vgg_layer = vgg_layers[j]
|
||||
x = vgg_layer(x)
|
||||
if i in self.out_indices:
|
||||
outs.append(x)
|
||||
if self.num_classes > 0:
|
||||
x = x.view(x.size(0), -1)
|
||||
x = self.classifier(x)
|
||||
outs.append(x)
|
||||
if len(outs) == 1:
|
||||
return outs[0]
|
||||
else:
|
||||
return tuple(outs)
|
||||
|
||||
def _freeze_stages(self):
|
||||
vgg_layers = getattr(self, self.module_name)
|
||||
for i in range(self.frozen_stages):
|
||||
for j in range(*self.range_sub_modules[i]):
|
||||
m = vgg_layers[j]
|
||||
m.eval()
|
||||
for param in m.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def train(self, mode=True):
|
||||
super().train(mode)
|
||||
self._freeze_stages()
|
||||
if mode and self.norm_eval:
|
||||
for m in self.modules():
|
||||
# trick: eval have effect on BatchNorm only
|
||||
if isinstance(m, _BatchNorm):
|
||||
m.eval()
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
import logging
|
||||
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import ConvModule
|
||||
from torch.nn.modules.batchnorm import _BatchNorm
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
from .utils import InvertedResidual, load_checkpoint
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class ViPNAS_MobileNetV3(BaseBackbone):
|
||||
"""ViPNAS_MobileNetV3 backbone.
|
||||
|
||||
"ViPNAS: Efficient Video Pose Estimation via Neural Architecture Search"
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/2105.10154>`__ .
|
||||
|
||||
Args:
|
||||
wid (list(int)): Searched width config for each stage.
|
||||
expan (list(int)): Searched expansion ratio config for each stage.
|
||||
dep (list(int)): Searched depth config for each stage.
|
||||
ks (list(int)): Searched kernel size config for each stage.
|
||||
group (list(int)): Searched group number config for each stage.
|
||||
att (list(bool)): Searched attention config for each stage.
|
||||
stride (list(int)): Stride config for each stage.
|
||||
act (list(dict)): Activation config for each stage.
|
||||
conv_cfg (dict): Config dict for convolution layer.
|
||||
Default: None, which means using conv2d.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: dict(type='BN').
|
||||
frozen_stages (int): Stages to be frozen (all param fixed).
|
||||
Default: -1, which means not freezing any parameters.
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False.
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save
|
||||
some memory while slowing down the training speed.
|
||||
Default: False.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
wid=[16, 16, 24, 40, 80, 112, 160],
|
||||
expan=[None, 1, 5, 4, 5, 5, 6],
|
||||
dep=[None, 1, 4, 4, 4, 4, 4],
|
||||
ks=[3, 3, 7, 7, 5, 7, 5],
|
||||
group=[None, 8, 120, 20, 100, 280, 240],
|
||||
att=[None, True, True, False, True, True, True],
|
||||
stride=[2, 1, 2, 2, 2, 1, 2],
|
||||
act=[
|
||||
'HSwish', 'ReLU', 'ReLU', 'ReLU', 'HSwish', 'HSwish',
|
||||
'HSwish'
|
||||
],
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
frozen_stages=-1,
|
||||
norm_eval=False,
|
||||
with_cp=False):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
self.wid = wid
|
||||
self.expan = expan
|
||||
self.dep = dep
|
||||
self.ks = ks
|
||||
self.group = group
|
||||
self.att = att
|
||||
self.stride = stride
|
||||
self.act = act
|
||||
self.conv_cfg = conv_cfg
|
||||
self.norm_cfg = norm_cfg
|
||||
self.frozen_stages = frozen_stages
|
||||
self.norm_eval = norm_eval
|
||||
self.with_cp = with_cp
|
||||
|
||||
self.conv1 = ConvModule(
|
||||
in_channels=3,
|
||||
out_channels=self.wid[0],
|
||||
kernel_size=self.ks[0],
|
||||
stride=self.stride[0],
|
||||
padding=self.ks[0] // 2,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=dict(type=self.act[0]))
|
||||
|
||||
self.layers = self._make_layer()
|
||||
|
||||
def _make_layer(self):
|
||||
layers = []
|
||||
layer_index = 0
|
||||
for i, dep in enumerate(self.dep[1:]):
|
||||
mid_channels = self.wid[i + 1] * self.expan[i + 1]
|
||||
|
||||
if self.att[i + 1]:
|
||||
se_cfg = dict(
|
||||
channels=mid_channels,
|
||||
ratio=4,
|
||||
act_cfg=(dict(type='ReLU'), dict(type='HSigmoid')))
|
||||
else:
|
||||
se_cfg = None
|
||||
|
||||
if self.expan[i + 1] == 1:
|
||||
with_expand_conv = False
|
||||
else:
|
||||
with_expand_conv = True
|
||||
|
||||
for j in range(dep):
|
||||
if j == 0:
|
||||
stride = self.stride[i + 1]
|
||||
in_channels = self.wid[i]
|
||||
else:
|
||||
stride = 1
|
||||
in_channels = self.wid[i + 1]
|
||||
|
||||
layer = InvertedResidual(
|
||||
in_channels=in_channels,
|
||||
out_channels=self.wid[i + 1],
|
||||
mid_channels=mid_channels,
|
||||
kernel_size=self.ks[i + 1],
|
||||
groups=self.group[i + 1],
|
||||
stride=stride,
|
||||
se_cfg=se_cfg,
|
||||
with_expand_conv=with_expand_conv,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
act_cfg=dict(type=self.act[i + 1]),
|
||||
with_cp=self.with_cp)
|
||||
layer_index += 1
|
||||
layer_name = f'layer{layer_index}'
|
||||
self.add_module(layer_name, layer)
|
||||
layers.append(layer_name)
|
||||
return layers
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
if isinstance(pretrained, str):
|
||||
logger = logging.getLogger()
|
||||
load_checkpoint(self, pretrained, strict=False, logger=logger)
|
||||
elif pretrained is None:
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
nn.init.normal_(m.weight, std=0.001)
|
||||
for name, _ in m.named_parameters():
|
||||
if name in ['bias']:
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
nn.init.constant_(m.weight, 1)
|
||||
nn.init.constant_(m.bias, 0)
|
||||
else:
|
||||
raise TypeError('pretrained must be a str or None')
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv1(x)
|
||||
|
||||
for i, layer_name in enumerate(self.layers):
|
||||
layer = getattr(self, layer_name)
|
||||
x = layer(x)
|
||||
|
||||
return x
|
||||
|
||||
def _freeze_stages(self):
|
||||
if self.frozen_stages >= 0:
|
||||
for param in self.conv1.parameters():
|
||||
param.requires_grad = False
|
||||
for i in range(1, self.frozen_stages + 1):
|
||||
layer = getattr(self, f'layer{i}')
|
||||
layer.eval()
|
||||
for param in layer.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def train(self, mode=True):
|
||||
super().train(mode)
|
||||
self._freeze_stages()
|
||||
if mode and self.norm_eval:
|
||||
for m in self.modules():
|
||||
if isinstance(m, _BatchNorm):
|
||||
m.eval()
|
||||
+589
@@ -0,0 +1,589 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
|
||||
import torch.nn as nn
|
||||
import torch.utils.checkpoint as cp
|
||||
from mmcv.cnn import ConvModule, build_conv_layer, build_norm_layer
|
||||
from mmcv.cnn.bricks import ContextBlock
|
||||
from mmcv.utils.parrots_wrapper import _BatchNorm
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
|
||||
|
||||
class ViPNAS_Bottleneck(nn.Module):
|
||||
"""Bottleneck block for ViPNAS_ResNet.
|
||||
|
||||
Args:
|
||||
in_channels (int): Input channels of this block.
|
||||
out_channels (int): Output channels of this block.
|
||||
expansion (int): The ratio of ``out_channels/mid_channels`` where
|
||||
``mid_channels`` is the input/output channels of conv2. Default: 4.
|
||||
stride (int): stride of the block. Default: 1
|
||||
dilation (int): dilation of convolution. Default: 1
|
||||
downsample (nn.Module): downsample operation on identity branch.
|
||||
Default: None.
|
||||
style (str): ``"pytorch"`` or ``"caffe"``. If set to "pytorch", the
|
||||
stride-two layer is the 3x3 conv layer, otherwise the stride-two
|
||||
layer is the first 1x1 conv layer. Default: "pytorch".
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed.
|
||||
conv_cfg (dict): dictionary to construct and config conv layer.
|
||||
Default: None
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
kernel_size (int): kernel size of conv2 searched in ViPANS.
|
||||
groups (int): group number of conv2 searched in ViPNAS.
|
||||
attention (bool): whether to use attention module in the end of
|
||||
the block.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
expansion=4,
|
||||
stride=1,
|
||||
dilation=1,
|
||||
downsample=None,
|
||||
style='pytorch',
|
||||
with_cp=False,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
kernel_size=3,
|
||||
groups=1,
|
||||
attention=False):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
assert style in ['pytorch', 'caffe']
|
||||
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
self.expansion = expansion
|
||||
assert out_channels % expansion == 0
|
||||
self.mid_channels = out_channels // expansion
|
||||
self.stride = stride
|
||||
self.dilation = dilation
|
||||
self.style = style
|
||||
self.with_cp = with_cp
|
||||
self.conv_cfg = conv_cfg
|
||||
self.norm_cfg = norm_cfg
|
||||
|
||||
if self.style == 'pytorch':
|
||||
self.conv1_stride = 1
|
||||
self.conv2_stride = stride
|
||||
else:
|
||||
self.conv1_stride = stride
|
||||
self.conv2_stride = 1
|
||||
|
||||
self.norm1_name, norm1 = build_norm_layer(
|
||||
norm_cfg, self.mid_channels, postfix=1)
|
||||
self.norm2_name, norm2 = build_norm_layer(
|
||||
norm_cfg, self.mid_channels, postfix=2)
|
||||
self.norm3_name, norm3 = build_norm_layer(
|
||||
norm_cfg, out_channels, postfix=3)
|
||||
|
||||
self.conv1 = build_conv_layer(
|
||||
conv_cfg,
|
||||
in_channels,
|
||||
self.mid_channels,
|
||||
kernel_size=1,
|
||||
stride=self.conv1_stride,
|
||||
bias=False)
|
||||
self.add_module(self.norm1_name, norm1)
|
||||
self.conv2 = build_conv_layer(
|
||||
conv_cfg,
|
||||
self.mid_channels,
|
||||
self.mid_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=self.conv2_stride,
|
||||
padding=kernel_size // 2,
|
||||
groups=groups,
|
||||
dilation=dilation,
|
||||
bias=False)
|
||||
|
||||
self.add_module(self.norm2_name, norm2)
|
||||
self.conv3 = build_conv_layer(
|
||||
conv_cfg,
|
||||
self.mid_channels,
|
||||
out_channels,
|
||||
kernel_size=1,
|
||||
bias=False)
|
||||
self.add_module(self.norm3_name, norm3)
|
||||
|
||||
if attention:
|
||||
self.attention = ContextBlock(out_channels,
|
||||
max(1.0 / 16, 16.0 / out_channels))
|
||||
else:
|
||||
self.attention = None
|
||||
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.downsample = downsample
|
||||
|
||||
@property
|
||||
def norm1(self):
|
||||
"""nn.Module: the normalization layer named "norm1" """
|
||||
return getattr(self, self.norm1_name)
|
||||
|
||||
@property
|
||||
def norm2(self):
|
||||
"""nn.Module: the normalization layer named "norm2" """
|
||||
return getattr(self, self.norm2_name)
|
||||
|
||||
@property
|
||||
def norm3(self):
|
||||
"""nn.Module: the normalization layer named "norm3" """
|
||||
return getattr(self, self.norm3_name)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
|
||||
def _inner_forward(x):
|
||||
identity = x
|
||||
|
||||
out = self.conv1(x)
|
||||
out = self.norm1(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv2(out)
|
||||
out = self.norm2(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv3(out)
|
||||
out = self.norm3(out)
|
||||
|
||||
if self.attention is not None:
|
||||
out = self.attention(out)
|
||||
|
||||
if self.downsample is not None:
|
||||
identity = self.downsample(x)
|
||||
|
||||
out += identity
|
||||
|
||||
return out
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
out = cp.checkpoint(_inner_forward, x)
|
||||
else:
|
||||
out = _inner_forward(x)
|
||||
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def get_expansion(block, expansion=None):
|
||||
"""Get the expansion of a residual block.
|
||||
|
||||
The block expansion will be obtained by the following order:
|
||||
|
||||
1. If ``expansion`` is given, just return it.
|
||||
2. If ``block`` has the attribute ``expansion``, then return
|
||||
``block.expansion``.
|
||||
3. Return the default value according the the block type:
|
||||
4 for ``ViPNAS_Bottleneck``.
|
||||
|
||||
Args:
|
||||
block (class): The block class.
|
||||
expansion (int | None): The given expansion ratio.
|
||||
|
||||
Returns:
|
||||
int: The expansion of the block.
|
||||
"""
|
||||
if isinstance(expansion, int):
|
||||
assert expansion > 0
|
||||
elif expansion is None:
|
||||
if hasattr(block, 'expansion'):
|
||||
expansion = block.expansion
|
||||
elif issubclass(block, ViPNAS_Bottleneck):
|
||||
expansion = 1
|
||||
else:
|
||||
raise TypeError(f'expansion is not specified for {block.__name__}')
|
||||
else:
|
||||
raise TypeError('expansion must be an integer or None')
|
||||
|
||||
return expansion
|
||||
|
||||
|
||||
class ViPNAS_ResLayer(nn.Sequential):
|
||||
"""ViPNAS_ResLayer to build ResNet style backbone.
|
||||
|
||||
Args:
|
||||
block (nn.Module): Residual block used to build ViPNAS ResLayer.
|
||||
num_blocks (int): Number of blocks.
|
||||
in_channels (int): Input channels of this block.
|
||||
out_channels (int): Output channels of this block.
|
||||
expansion (int, optional): The expansion for BasicBlock/Bottleneck.
|
||||
If not specified, it will firstly be obtained via
|
||||
``block.expansion``. If the block has no attribute "expansion",
|
||||
the following default values will be used: 1 for BasicBlock and
|
||||
4 for Bottleneck. Default: None.
|
||||
stride (int): stride of the first block. Default: 1.
|
||||
avg_down (bool): Use AvgPool instead of stride conv when
|
||||
downsampling in the bottleneck. Default: False
|
||||
conv_cfg (dict): dictionary to construct and config conv layer.
|
||||
Default: None
|
||||
norm_cfg (dict): dictionary to construct and config norm layer.
|
||||
Default: dict(type='BN')
|
||||
downsample_first (bool): Downsample at the first block or last block.
|
||||
False for Hourglass, True for ResNet. Default: True
|
||||
kernel_size (int): Kernel Size of the corresponding convolution layer
|
||||
searched in the block.
|
||||
groups (int): Group number of the corresponding convolution layer
|
||||
searched in the block.
|
||||
attention (bool): Whether to use attention module in the end of the
|
||||
block.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
block,
|
||||
num_blocks,
|
||||
in_channels,
|
||||
out_channels,
|
||||
expansion=None,
|
||||
stride=1,
|
||||
avg_down=False,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN'),
|
||||
downsample_first=True,
|
||||
kernel_size=3,
|
||||
groups=1,
|
||||
attention=False,
|
||||
**kwargs):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
self.block = block
|
||||
self.expansion = get_expansion(block, expansion)
|
||||
|
||||
downsample = None
|
||||
if stride != 1 or in_channels != out_channels:
|
||||
downsample = []
|
||||
conv_stride = stride
|
||||
if avg_down and stride != 1:
|
||||
conv_stride = 1
|
||||
downsample.append(
|
||||
nn.AvgPool2d(
|
||||
kernel_size=stride,
|
||||
stride=stride,
|
||||
ceil_mode=True,
|
||||
count_include_pad=False))
|
||||
downsample.extend([
|
||||
build_conv_layer(
|
||||
conv_cfg,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=1,
|
||||
stride=conv_stride,
|
||||
bias=False),
|
||||
build_norm_layer(norm_cfg, out_channels)[1]
|
||||
])
|
||||
downsample = nn.Sequential(*downsample)
|
||||
|
||||
layers = []
|
||||
if downsample_first:
|
||||
layers.append(
|
||||
block(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
expansion=self.expansion,
|
||||
stride=stride,
|
||||
downsample=downsample,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
kernel_size=kernel_size,
|
||||
groups=groups,
|
||||
attention=attention,
|
||||
**kwargs))
|
||||
in_channels = out_channels
|
||||
for _ in range(1, num_blocks):
|
||||
layers.append(
|
||||
block(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
expansion=self.expansion,
|
||||
stride=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
kernel_size=kernel_size,
|
||||
groups=groups,
|
||||
attention=attention,
|
||||
**kwargs))
|
||||
else: # downsample_first=False is for HourglassModule
|
||||
for i in range(0, num_blocks - 1):
|
||||
layers.append(
|
||||
block(
|
||||
in_channels=in_channels,
|
||||
out_channels=in_channels,
|
||||
expansion=self.expansion,
|
||||
stride=1,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
kernel_size=kernel_size,
|
||||
groups=groups,
|
||||
attention=attention,
|
||||
**kwargs))
|
||||
layers.append(
|
||||
block(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
expansion=self.expansion,
|
||||
stride=stride,
|
||||
downsample=downsample,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
kernel_size=kernel_size,
|
||||
groups=groups,
|
||||
attention=attention,
|
||||
**kwargs))
|
||||
|
||||
super().__init__(*layers)
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class ViPNAS_ResNet(BaseBackbone):
|
||||
"""ViPNAS_ResNet backbone.
|
||||
|
||||
"ViPNAS: Efficient Video Pose Estimation via Neural Architecture Search"
|
||||
More details can be found in the `paper
|
||||
<https://arxiv.org/abs/2105.10154>`__ .
|
||||
|
||||
Args:
|
||||
depth (int): Network depth, from {18, 34, 50, 101, 152}.
|
||||
in_channels (int): Number of input image channels. Default: 3.
|
||||
num_stages (int): Stages of the network. Default: 4.
|
||||
strides (Sequence[int]): Strides of the first block of each stage.
|
||||
Default: ``(1, 2, 2, 2)``.
|
||||
dilations (Sequence[int]): Dilation of each stage.
|
||||
Default: ``(1, 1, 1, 1)``.
|
||||
out_indices (Sequence[int]): Output from which stages. If only one
|
||||
stage is specified, a single tensor (feature map) is returned,
|
||||
otherwise multiple stages are specified, a tuple of tensors will
|
||||
be returned. Default: ``(3, )``.
|
||||
style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two
|
||||
layer is the 3x3 conv layer, otherwise the stride-two layer is
|
||||
the first 1x1 conv layer.
|
||||
deep_stem (bool): Replace 7x7 conv in input stem with 3 3x3 conv.
|
||||
Default: False.
|
||||
avg_down (bool): Use AvgPool instead of stride conv when
|
||||
downsampling in the bottleneck. Default: False.
|
||||
frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
|
||||
-1 means not freezing any parameters. Default: -1.
|
||||
conv_cfg (dict | None): The config dict for conv layers. Default: None.
|
||||
norm_cfg (dict): The config dict for norm layers.
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False.
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
memory while slowing down the training speed. Default: False.
|
||||
zero_init_residual (bool): Whether to use zero init for last norm layer
|
||||
in resblocks to let them behave as identity. Default: True.
|
||||
wid (list(int)): Searched width config for each stage.
|
||||
expan (list(int)): Searched expansion ratio config for each stage.
|
||||
dep (list(int)): Searched depth config for each stage.
|
||||
ks (list(int)): Searched kernel size config for each stage.
|
||||
group (list(int)): Searched group number config for each stage.
|
||||
att (list(bool)): Searched attention config for each stage.
|
||||
"""
|
||||
|
||||
arch_settings = {
|
||||
50: ViPNAS_Bottleneck,
|
||||
}
|
||||
|
||||
def __init__(self,
|
||||
depth,
|
||||
in_channels=3,
|
||||
num_stages=4,
|
||||
strides=(1, 2, 2, 2),
|
||||
dilations=(1, 1, 1, 1),
|
||||
out_indices=(3, ),
|
||||
style='pytorch',
|
||||
deep_stem=False,
|
||||
avg_down=False,
|
||||
frozen_stages=-1,
|
||||
conv_cfg=None,
|
||||
norm_cfg=dict(type='BN', requires_grad=True),
|
||||
norm_eval=False,
|
||||
with_cp=False,
|
||||
zero_init_residual=True,
|
||||
wid=[48, 80, 160, 304, 608],
|
||||
expan=[None, 1, 1, 1, 1],
|
||||
dep=[None, 4, 6, 7, 3],
|
||||
ks=[7, 3, 5, 5, 5],
|
||||
group=[None, 16, 16, 16, 16],
|
||||
att=[None, True, False, True, True]):
|
||||
# Protect mutable default arguments
|
||||
norm_cfg = copy.deepcopy(norm_cfg)
|
||||
super().__init__()
|
||||
if depth not in self.arch_settings:
|
||||
raise KeyError(f'invalid depth {depth} for resnet')
|
||||
self.depth = depth
|
||||
self.stem_channels = dep[0]
|
||||
self.num_stages = num_stages
|
||||
assert 1 <= num_stages <= 4
|
||||
self.strides = strides
|
||||
self.dilations = dilations
|
||||
assert len(strides) == len(dilations) == num_stages
|
||||
self.out_indices = out_indices
|
||||
assert max(out_indices) < num_stages
|
||||
self.style = style
|
||||
self.deep_stem = deep_stem
|
||||
self.avg_down = avg_down
|
||||
self.frozen_stages = frozen_stages
|
||||
self.conv_cfg = conv_cfg
|
||||
self.norm_cfg = norm_cfg
|
||||
self.with_cp = with_cp
|
||||
self.norm_eval = norm_eval
|
||||
self.zero_init_residual = zero_init_residual
|
||||
self.block = self.arch_settings[depth]
|
||||
self.stage_blocks = dep[1:1 + num_stages]
|
||||
|
||||
self._make_stem_layer(in_channels, wid[0], ks[0])
|
||||
|
||||
self.res_layers = []
|
||||
_in_channels = wid[0]
|
||||
for i, num_blocks in enumerate(self.stage_blocks):
|
||||
expansion = get_expansion(self.block, expan[i + 1])
|
||||
_out_channels = wid[i + 1] * expansion
|
||||
stride = strides[i]
|
||||
dilation = dilations[i]
|
||||
res_layer = self.make_res_layer(
|
||||
block=self.block,
|
||||
num_blocks=num_blocks,
|
||||
in_channels=_in_channels,
|
||||
out_channels=_out_channels,
|
||||
expansion=expansion,
|
||||
stride=stride,
|
||||
dilation=dilation,
|
||||
style=self.style,
|
||||
avg_down=self.avg_down,
|
||||
with_cp=with_cp,
|
||||
conv_cfg=conv_cfg,
|
||||
norm_cfg=norm_cfg,
|
||||
kernel_size=ks[i + 1],
|
||||
groups=group[i + 1],
|
||||
attention=att[i + 1])
|
||||
_in_channels = _out_channels
|
||||
layer_name = f'layer{i + 1}'
|
||||
self.add_module(layer_name, res_layer)
|
||||
self.res_layers.append(layer_name)
|
||||
|
||||
self._freeze_stages()
|
||||
|
||||
self.feat_dim = res_layer[-1].out_channels
|
||||
|
||||
def make_res_layer(self, **kwargs):
|
||||
"""Make a ViPNAS ResLayer."""
|
||||
return ViPNAS_ResLayer(**kwargs)
|
||||
|
||||
@property
|
||||
def norm1(self):
|
||||
"""nn.Module: the normalization layer named "norm1" """
|
||||
return getattr(self, self.norm1_name)
|
||||
|
||||
def _make_stem_layer(self, in_channels, stem_channels, kernel_size):
|
||||
"""Make stem layer."""
|
||||
if self.deep_stem:
|
||||
self.stem = nn.Sequential(
|
||||
ConvModule(
|
||||
in_channels,
|
||||
stem_channels // 2,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
inplace=True),
|
||||
ConvModule(
|
||||
stem_channels // 2,
|
||||
stem_channels // 2,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
inplace=True),
|
||||
ConvModule(
|
||||
stem_channels // 2,
|
||||
stem_channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
conv_cfg=self.conv_cfg,
|
||||
norm_cfg=self.norm_cfg,
|
||||
inplace=True))
|
||||
else:
|
||||
self.conv1 = build_conv_layer(
|
||||
self.conv_cfg,
|
||||
in_channels,
|
||||
stem_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=2,
|
||||
padding=kernel_size // 2,
|
||||
bias=False)
|
||||
self.norm1_name, norm1 = build_norm_layer(
|
||||
self.norm_cfg, stem_channels, postfix=1)
|
||||
self.add_module(self.norm1_name, norm1)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
||||
|
||||
def _freeze_stages(self):
|
||||
"""Freeze parameters."""
|
||||
if self.frozen_stages >= 0:
|
||||
if self.deep_stem:
|
||||
self.stem.eval()
|
||||
for param in self.stem.parameters():
|
||||
param.requires_grad = False
|
||||
else:
|
||||
self.norm1.eval()
|
||||
for m in [self.conv1, self.norm1]:
|
||||
for param in m.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
for i in range(1, self.frozen_stages + 1):
|
||||
m = getattr(self, f'layer{i}')
|
||||
m.eval()
|
||||
for param in m.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Initialize model weights."""
|
||||
super().init_weights(pretrained)
|
||||
if pretrained is None:
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
nn.init.normal_(m.weight, std=0.001)
|
||||
for name, _ in m.named_parameters():
|
||||
if name in ['bias']:
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
nn.init.constant_(m.weight, 1)
|
||||
nn.init.constant_(m.bias, 0)
|
||||
|
||||
def forward(self, x):
|
||||
"""Forward function."""
|
||||
if self.deep_stem:
|
||||
x = self.stem(x)
|
||||
else:
|
||||
x = self.conv1(x)
|
||||
x = self.norm1(x)
|
||||
x = self.relu(x)
|
||||
x = self.maxpool(x)
|
||||
outs = []
|
||||
for i, layer_name in enumerate(self.res_layers):
|
||||
res_layer = getattr(self, layer_name)
|
||||
x = res_layer(x)
|
||||
if i in self.out_indices:
|
||||
outs.append(x)
|
||||
if len(outs) == 1:
|
||||
return outs[0]
|
||||
return tuple(outs)
|
||||
|
||||
def train(self, mode=True):
|
||||
"""Convert the model into training mode."""
|
||||
super().train(mode)
|
||||
self._freeze_stages()
|
||||
if mode and self.norm_eval:
|
||||
for m in self.modules():
|
||||
# trick: eval have effect on BatchNorm only
|
||||
if isinstance(m, _BatchNorm):
|
||||
m.eval()
|
||||
@@ -0,0 +1,341 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import math
|
||||
|
||||
import torch
|
||||
from functools import partial
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch.utils.checkpoint as checkpoint
|
||||
|
||||
from timm.models.layers import drop_path, to_2tuple, trunc_normal_
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
|
||||
def get_abs_pos(abs_pos, h, w, ori_h, ori_w, has_cls_token=True):
|
||||
"""
|
||||
Calculate absolute positional embeddings. If needed, resize embeddings and remove cls_token
|
||||
dimension for the original embeddings.
|
||||
Args:
|
||||
abs_pos (Tensor): absolute positional embeddings with (1, num_position, C).
|
||||
has_cls_token (bool): If true, has 1 embedding in abs_pos for cls token.
|
||||
hw (Tuple): size of input image tokens.
|
||||
|
||||
Returns:
|
||||
Absolute positional embeddings after processing with shape (1, H, W, C)
|
||||
"""
|
||||
cls_token = None
|
||||
B, L, C = abs_pos.shape
|
||||
if has_cls_token:
|
||||
cls_token = abs_pos[:, 0:1]
|
||||
abs_pos = abs_pos[:, 1:]
|
||||
|
||||
if ori_h != h or ori_w != w:
|
||||
new_abs_pos = F.interpolate(
|
||||
abs_pos.reshape(1, ori_h, ori_w, -1).permute(0, 3, 1, 2),
|
||||
size=(h, w),
|
||||
mode="bicubic",
|
||||
align_corners=False,
|
||||
).permute(0, 2, 3, 1).reshape(B, -1, C)
|
||||
|
||||
else:
|
||||
new_abs_pos = abs_pos
|
||||
|
||||
if cls_token is not None:
|
||||
new_abs_pos = torch.cat([cls_token, new_abs_pos], dim=1)
|
||||
return new_abs_pos
|
||||
|
||||
class DropPath(nn.Module):
|
||||
"""Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).
|
||||
"""
|
||||
def __init__(self, drop_prob=None):
|
||||
super(DropPath, self).__init__()
|
||||
self.drop_prob = drop_prob
|
||||
|
||||
def forward(self, x):
|
||||
return drop_path(x, self.drop_prob, self.training)
|
||||
|
||||
def extra_repr(self):
|
||||
return 'p={}'.format(self.drop_prob)
|
||||
|
||||
class Mlp(nn.Module):
|
||||
def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):
|
||||
super().__init__()
|
||||
out_features = out_features or in_features
|
||||
hidden_features = hidden_features or in_features
|
||||
self.fc1 = nn.Linear(in_features, hidden_features)
|
||||
self.act = act_layer()
|
||||
self.fc2 = nn.Linear(hidden_features, out_features)
|
||||
self.drop = nn.Dropout(drop)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.fc1(x)
|
||||
x = self.act(x)
|
||||
x = self.fc2(x)
|
||||
x = self.drop(x)
|
||||
return x
|
||||
|
||||
class Attention(nn.Module):
|
||||
def __init__(
|
||||
self, dim, num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0.,
|
||||
proj_drop=0., attn_head_dim=None,):
|
||||
super().__init__()
|
||||
self.num_heads = num_heads
|
||||
head_dim = dim // num_heads
|
||||
self.dim = dim
|
||||
|
||||
if attn_head_dim is not None:
|
||||
head_dim = attn_head_dim
|
||||
all_head_dim = head_dim * self.num_heads
|
||||
|
||||
self.scale = qk_scale or head_dim ** -0.5
|
||||
|
||||
self.qkv = nn.Linear(dim, all_head_dim * 3, bias=qkv_bias)
|
||||
|
||||
self.attn_drop = nn.Dropout(attn_drop)
|
||||
self.proj = nn.Linear(all_head_dim, dim)
|
||||
self.proj_drop = nn.Dropout(proj_drop)
|
||||
|
||||
def forward(self, x):
|
||||
B, N, C = x.shape
|
||||
qkv = self.qkv(x)
|
||||
qkv = qkv.reshape(B, N, 3, self.num_heads, -1).permute(2, 0, 3, 1, 4)
|
||||
q, k, v = qkv[0], qkv[1], qkv[2] # make torchscript happy (cannot use tensor as tuple)
|
||||
|
||||
q = q * self.scale
|
||||
attn = (q @ k.transpose(-2, -1))
|
||||
|
||||
attn = attn.softmax(dim=-1)
|
||||
attn = self.attn_drop(attn)
|
||||
|
||||
x = (attn @ v).transpose(1, 2).reshape(B, N, -1)
|
||||
x = self.proj(x)
|
||||
x = self.proj_drop(x)
|
||||
|
||||
return x
|
||||
|
||||
class Block(nn.Module):
|
||||
|
||||
def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, qk_scale=None,
|
||||
drop=0., attn_drop=0., drop_path=0., act_layer=nn.GELU,
|
||||
norm_layer=nn.LayerNorm, attn_head_dim=None
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.norm1 = norm_layer(dim)
|
||||
self.attn = Attention(
|
||||
dim, num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale,
|
||||
attn_drop=attn_drop, proj_drop=drop, attn_head_dim=attn_head_dim
|
||||
)
|
||||
|
||||
# NOTE: drop path for stochastic depth, we shall see if this is better than dropout here
|
||||
self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
|
||||
self.norm2 = norm_layer(dim)
|
||||
mlp_hidden_dim = int(dim * mlp_ratio)
|
||||
self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)
|
||||
|
||||
def forward(self, x):
|
||||
x = x + self.drop_path(self.attn(self.norm1(x)))
|
||||
x = x + self.drop_path(self.mlp(self.norm2(x)))
|
||||
return x
|
||||
|
||||
|
||||
class PatchEmbed(nn.Module):
|
||||
""" Image to Patch Embedding
|
||||
"""
|
||||
def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768, ratio=1):
|
||||
super().__init__()
|
||||
img_size = to_2tuple(img_size)
|
||||
patch_size = to_2tuple(patch_size)
|
||||
num_patches = (img_size[1] // patch_size[1]) * (img_size[0] // patch_size[0]) * (ratio ** 2)
|
||||
self.patch_shape = (int(img_size[0] // patch_size[0] * ratio), int(img_size[1] // patch_size[1] * ratio))
|
||||
self.origin_patch_shape = (int(img_size[0] // patch_size[0]), int(img_size[1] // patch_size[1]))
|
||||
self.img_size = img_size
|
||||
self.patch_size = patch_size
|
||||
self.num_patches = num_patches
|
||||
|
||||
self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=(patch_size[0] // ratio), padding=4 + 2 * (ratio//2-1))
|
||||
|
||||
def forward(self, x, **kwargs):
|
||||
B, C, H, W = x.shape
|
||||
x = self.proj(x)
|
||||
Hp, Wp = x.shape[2], x.shape[3]
|
||||
|
||||
x = x.flatten(2).transpose(1, 2)
|
||||
return x, (Hp, Wp)
|
||||
|
||||
|
||||
class HybridEmbed(nn.Module):
|
||||
""" CNN Feature Map Embedding
|
||||
Extract feature map from CNN, flatten, project to embedding dim.
|
||||
"""
|
||||
def __init__(self, backbone, img_size=224, feature_size=None, in_chans=3, embed_dim=768):
|
||||
super().__init__()
|
||||
assert isinstance(backbone, nn.Module)
|
||||
img_size = to_2tuple(img_size)
|
||||
self.img_size = img_size
|
||||
self.backbone = backbone
|
||||
if feature_size is None:
|
||||
with torch.no_grad():
|
||||
training = backbone.training
|
||||
if training:
|
||||
backbone.eval()
|
||||
o = self.backbone(torch.zeros(1, in_chans, img_size[0], img_size[1]))[-1]
|
||||
feature_size = o.shape[-2:]
|
||||
feature_dim = o.shape[1]
|
||||
backbone.train(training)
|
||||
else:
|
||||
feature_size = to_2tuple(feature_size)
|
||||
feature_dim = self.backbone.feature_info.channels()[-1]
|
||||
self.num_patches = feature_size[0] * feature_size[1]
|
||||
self.proj = nn.Linear(feature_dim, embed_dim)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.backbone(x)[-1]
|
||||
x = x.flatten(2).transpose(1, 2)
|
||||
x = self.proj(x)
|
||||
return x
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class ViT(BaseBackbone):
|
||||
|
||||
def __init__(self,
|
||||
img_size=224, patch_size=16, in_chans=3, num_classes=80, embed_dim=768, depth=12,
|
||||
num_heads=12, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop_rate=0., attn_drop_rate=0.,
|
||||
drop_path_rate=0., hybrid_backbone=None, norm_layer=None, use_checkpoint=False,
|
||||
frozen_stages=-1, ratio=1, last_norm=True,
|
||||
patch_padding='pad', freeze_attn=False, freeze_ffn=False,
|
||||
):
|
||||
# Protect mutable default arguments
|
||||
super(ViT, self).__init__()
|
||||
norm_layer = norm_layer or partial(nn.LayerNorm, eps=1e-6)
|
||||
self.num_classes = num_classes
|
||||
self.num_features = self.embed_dim = embed_dim # num_features for consistency with other models
|
||||
self.frozen_stages = frozen_stages
|
||||
self.use_checkpoint = use_checkpoint
|
||||
self.patch_padding = patch_padding
|
||||
self.freeze_attn = freeze_attn
|
||||
self.freeze_ffn = freeze_ffn
|
||||
self.depth = depth
|
||||
|
||||
if hybrid_backbone is not None:
|
||||
self.patch_embed = HybridEmbed(
|
||||
hybrid_backbone, img_size=img_size, in_chans=in_chans, embed_dim=embed_dim)
|
||||
else:
|
||||
self.patch_embed = PatchEmbed(
|
||||
img_size=img_size, patch_size=patch_size, in_chans=in_chans, embed_dim=embed_dim, ratio=ratio)
|
||||
num_patches = self.patch_embed.num_patches
|
||||
|
||||
# since the pretraining model has class token
|
||||
self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, embed_dim))
|
||||
|
||||
dpr = [x.item() for x in torch.linspace(0, drop_path_rate, depth)] # stochastic depth decay rule
|
||||
|
||||
self.blocks = nn.ModuleList([
|
||||
Block(
|
||||
dim=embed_dim, num_heads=num_heads, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale,
|
||||
drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i], norm_layer=norm_layer,
|
||||
)
|
||||
for i in range(depth)])
|
||||
|
||||
self.last_norm = norm_layer(embed_dim) if last_norm else nn.Identity()
|
||||
|
||||
if self.pos_embed is not None:
|
||||
trunc_normal_(self.pos_embed, std=.02)
|
||||
|
||||
self._freeze_stages()
|
||||
|
||||
def _freeze_stages(self):
|
||||
"""Freeze parameters."""
|
||||
if self.frozen_stages >= 0:
|
||||
self.patch_embed.eval()
|
||||
for param in self.patch_embed.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
for i in range(1, self.frozen_stages + 1):
|
||||
m = self.blocks[i]
|
||||
m.eval()
|
||||
for param in m.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
if self.freeze_attn:
|
||||
for i in range(0, self.depth):
|
||||
m = self.blocks[i]
|
||||
m.attn.eval()
|
||||
m.norm1.eval()
|
||||
for param in m.attn.parameters():
|
||||
param.requires_grad = False
|
||||
for param in m.norm1.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
if self.freeze_ffn:
|
||||
self.pos_embed.requires_grad = False
|
||||
self.patch_embed.eval()
|
||||
for param in self.patch_embed.parameters():
|
||||
param.requires_grad = False
|
||||
for i in range(0, self.depth):
|
||||
m = self.blocks[i]
|
||||
m.mlp.eval()
|
||||
m.norm2.eval()
|
||||
for param in m.mlp.parameters():
|
||||
param.requires_grad = False
|
||||
for param in m.norm2.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Initialize the weights in backbone.
|
||||
Args:
|
||||
pretrained (str, optional): Path to pre-trained weights.
|
||||
Defaults to None.
|
||||
"""
|
||||
super().init_weights(pretrained, patch_padding=self.patch_padding)
|
||||
|
||||
if pretrained is None:
|
||||
def _init_weights(m):
|
||||
if isinstance(m, nn.Linear):
|
||||
trunc_normal_(m.weight, std=.02)
|
||||
if isinstance(m, nn.Linear) and m.bias is not None:
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.LayerNorm):
|
||||
nn.init.constant_(m.bias, 0)
|
||||
nn.init.constant_(m.weight, 1.0)
|
||||
|
||||
self.apply(_init_weights)
|
||||
|
||||
def get_num_layers(self):
|
||||
return len(self.blocks)
|
||||
|
||||
@torch.jit.ignore
|
||||
def no_weight_decay(self):
|
||||
return {'pos_embed', 'cls_token'}
|
||||
|
||||
def forward_features(self, x):
|
||||
B, C, H, W = x.shape
|
||||
x, (Hp, Wp) = self.patch_embed(x)
|
||||
|
||||
if self.pos_embed is not None:
|
||||
# fit for multiple GPU training
|
||||
# since the first element for pos embed (sin-cos manner) is zero, it will cause no difference
|
||||
x = x + self.pos_embed[:, 1:] + self.pos_embed[:, :1]
|
||||
|
||||
for blk in self.blocks:
|
||||
if self.use_checkpoint:
|
||||
x = checkpoint.checkpoint(blk, x)
|
||||
else:
|
||||
x = blk(x)
|
||||
|
||||
x = self.last_norm(x)
|
||||
|
||||
xp = x.permute(0, 2, 1).reshape(B, -1, Hp, Wp).contiguous()
|
||||
|
||||
return xp
|
||||
|
||||
def forward(self, x):
|
||||
x = self.forward_features(x)
|
||||
return x
|
||||
|
||||
def train(self, mode=True):
|
||||
"""Convert the model into training mode."""
|
||||
super().train(mode)
|
||||
self._freeze_stages()
|
||||
@@ -0,0 +1,385 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import math
|
||||
|
||||
import torch
|
||||
from functools import partial
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch.utils.checkpoint as checkpoint
|
||||
|
||||
from timm.models.layers import drop_path, to_2tuple, trunc_normal_
|
||||
|
||||
from ..builder import BACKBONES
|
||||
from .base_backbone import BaseBackbone
|
||||
|
||||
def get_abs_pos(abs_pos, h, w, ori_h, ori_w, has_cls_token=True):
|
||||
"""
|
||||
Calculate absolute positional embeddings. If needed, resize embeddings and remove cls_token
|
||||
dimension for the original embeddings.
|
||||
Args:
|
||||
abs_pos (Tensor): absolute positional embeddings with (1, num_position, C).
|
||||
has_cls_token (bool): If true, has 1 embedding in abs_pos for cls token.
|
||||
hw (Tuple): size of input image tokens.
|
||||
|
||||
Returns:
|
||||
Absolute positional embeddings after processing with shape (1, H, W, C)
|
||||
"""
|
||||
cls_token = None
|
||||
B, L, C = abs_pos.shape
|
||||
if has_cls_token:
|
||||
cls_token = abs_pos[:, 0:1]
|
||||
abs_pos = abs_pos[:, 1:]
|
||||
|
||||
if ori_h != h or ori_w != w:
|
||||
new_abs_pos = F.interpolate(
|
||||
abs_pos.reshape(1, ori_h, ori_w, -1).permute(0, 3, 1, 2),
|
||||
size=(h, w),
|
||||
mode="bicubic",
|
||||
align_corners=False,
|
||||
).permute(0, 2, 3, 1).reshape(B, -1, C)
|
||||
|
||||
else:
|
||||
new_abs_pos = abs_pos
|
||||
|
||||
if cls_token is not None:
|
||||
new_abs_pos = torch.cat([cls_token, new_abs_pos], dim=1)
|
||||
return new_abs_pos
|
||||
|
||||
class DropPath(nn.Module):
|
||||
"""Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).
|
||||
"""
|
||||
def __init__(self, drop_prob=None):
|
||||
super(DropPath, self).__init__()
|
||||
self.drop_prob = drop_prob
|
||||
|
||||
def forward(self, x):
|
||||
return drop_path(x, self.drop_prob, self.training)
|
||||
|
||||
def extra_repr(self):
|
||||
return 'p={}'.format(self.drop_prob)
|
||||
|
||||
class Mlp(nn.Module):
|
||||
def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):
|
||||
super().__init__()
|
||||
out_features = out_features or in_features
|
||||
hidden_features = hidden_features or in_features
|
||||
self.fc1 = nn.Linear(in_features, hidden_features)
|
||||
self.act = act_layer()
|
||||
self.fc2 = nn.Linear(hidden_features, out_features)
|
||||
self.drop = nn.Dropout(drop)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.fc1(x)
|
||||
x = self.act(x)
|
||||
x = self.fc2(x)
|
||||
x = self.drop(x)
|
||||
return x
|
||||
|
||||
class MoEMlp(nn.Module):
|
||||
def __init__(self, num_expert=1, in_features=1024, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0., part_features=256):
|
||||
super().__init__()
|
||||
out_features = out_features or in_features
|
||||
hidden_features = hidden_features or in_features
|
||||
self.part_features = part_features
|
||||
self.fc1 = nn.Linear(in_features, hidden_features)
|
||||
self.act = act_layer()
|
||||
self.fc2 = nn.Linear(hidden_features, out_features - part_features)
|
||||
self.drop = nn.Dropout(drop)
|
||||
|
||||
self.num_expert = num_expert
|
||||
experts = []
|
||||
|
||||
for i in range(num_expert):
|
||||
experts.append(
|
||||
nn.Linear(hidden_features, part_features)
|
||||
)
|
||||
self.experts = nn.ModuleList(experts)
|
||||
|
||||
def forward(self, x, indices):
|
||||
|
||||
expert_x = torch.zeros_like(x[:, :, -self.part_features:], device=x.device, dtype=x.dtype)
|
||||
|
||||
x = self.fc1(x)
|
||||
x = self.act(x)
|
||||
shared_x = self.fc2(x)
|
||||
indices = indices.view(-1, 1, 1)
|
||||
|
||||
# to support ddp training
|
||||
for i in range(self.num_expert):
|
||||
selectedIndex = (indices == i)
|
||||
current_x = self.experts[i](x) * selectedIndex
|
||||
expert_x = expert_x + current_x
|
||||
|
||||
x = torch.cat([shared_x, expert_x], dim=-1)
|
||||
|
||||
return x
|
||||
|
||||
class Attention(nn.Module):
|
||||
def __init__(
|
||||
self, dim, num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0.,
|
||||
proj_drop=0., attn_head_dim=None,):
|
||||
super().__init__()
|
||||
self.num_heads = num_heads
|
||||
head_dim = dim // num_heads
|
||||
self.dim = dim
|
||||
|
||||
if attn_head_dim is not None:
|
||||
head_dim = attn_head_dim
|
||||
all_head_dim = head_dim * self.num_heads
|
||||
|
||||
self.scale = qk_scale or head_dim ** -0.5
|
||||
|
||||
self.qkv = nn.Linear(dim, all_head_dim * 3, bias=qkv_bias)
|
||||
|
||||
self.attn_drop = nn.Dropout(attn_drop)
|
||||
self.proj = nn.Linear(all_head_dim, dim)
|
||||
self.proj_drop = nn.Dropout(proj_drop)
|
||||
|
||||
def forward(self, x):
|
||||
B, N, C = x.shape
|
||||
qkv = self.qkv(x)
|
||||
qkv = qkv.reshape(B, N, 3, self.num_heads, -1).permute(2, 0, 3, 1, 4)
|
||||
q, k, v = qkv[0], qkv[1], qkv[2] # make torchscript happy (cannot use tensor as tuple)
|
||||
|
||||
q = q * self.scale
|
||||
attn = (q @ k.transpose(-2, -1))
|
||||
|
||||
attn = attn.softmax(dim=-1)
|
||||
attn = self.attn_drop(attn)
|
||||
|
||||
x = (attn @ v).transpose(1, 2).reshape(B, N, -1)
|
||||
x = self.proj(x)
|
||||
x = self.proj_drop(x)
|
||||
|
||||
return x
|
||||
|
||||
class Block(nn.Module):
|
||||
|
||||
def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, qk_scale=None,
|
||||
drop=0., attn_drop=0., drop_path=0., act_layer=nn.GELU,
|
||||
norm_layer=nn.LayerNorm, attn_head_dim=None, num_expert=1, part_features=None
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.norm1 = norm_layer(dim)
|
||||
self.attn = Attention(
|
||||
dim, num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale,
|
||||
attn_drop=attn_drop, proj_drop=drop, attn_head_dim=attn_head_dim
|
||||
)
|
||||
|
||||
# NOTE: drop path for stochastic depth, we shall see if this is better than dropout here
|
||||
self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
|
||||
self.norm2 = norm_layer(dim)
|
||||
mlp_hidden_dim = int(dim * mlp_ratio)
|
||||
self.mlp = MoEMlp(num_expert=num_expert, in_features=dim, hidden_features=mlp_hidden_dim,
|
||||
act_layer=act_layer, drop=drop, part_features=part_features)
|
||||
|
||||
def forward(self, x, indices=None):
|
||||
|
||||
x = x + self.drop_path(self.attn(self.norm1(x)))
|
||||
x = x + self.drop_path(self.mlp(self.norm2(x), indices))
|
||||
return x
|
||||
|
||||
|
||||
class PatchEmbed(nn.Module):
|
||||
""" Image to Patch Embedding
|
||||
"""
|
||||
def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768, ratio=1):
|
||||
super().__init__()
|
||||
img_size = to_2tuple(img_size)
|
||||
patch_size = to_2tuple(patch_size)
|
||||
num_patches = (img_size[1] // patch_size[1]) * (img_size[0] // patch_size[0]) * (ratio ** 2)
|
||||
self.patch_shape = (int(img_size[0] // patch_size[0] * ratio), int(img_size[1] // patch_size[1] * ratio))
|
||||
self.origin_patch_shape = (int(img_size[0] // patch_size[0]), int(img_size[1] // patch_size[1]))
|
||||
self.img_size = img_size
|
||||
self.patch_size = patch_size
|
||||
self.num_patches = num_patches
|
||||
|
||||
self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=(patch_size[0] // ratio), padding=4 + 2 * (ratio//2-1))
|
||||
|
||||
def forward(self, x, **kwargs):
|
||||
B, C, H, W = x.shape
|
||||
x = self.proj(x)
|
||||
Hp, Wp = x.shape[2], x.shape[3]
|
||||
|
||||
x = x.flatten(2).transpose(1, 2)
|
||||
return x, (Hp, Wp)
|
||||
|
||||
|
||||
class HybridEmbed(nn.Module):
|
||||
""" CNN Feature Map Embedding
|
||||
Extract feature map from CNN, flatten, project to embedding dim.
|
||||
"""
|
||||
def __init__(self, backbone, img_size=224, feature_size=None, in_chans=3, embed_dim=768):
|
||||
super().__init__()
|
||||
assert isinstance(backbone, nn.Module)
|
||||
img_size = to_2tuple(img_size)
|
||||
self.img_size = img_size
|
||||
self.backbone = backbone
|
||||
if feature_size is None:
|
||||
with torch.no_grad():
|
||||
training = backbone.training
|
||||
if training:
|
||||
backbone.eval()
|
||||
o = self.backbone(torch.zeros(1, in_chans, img_size[0], img_size[1]))[-1]
|
||||
feature_size = o.shape[-2:]
|
||||
feature_dim = o.shape[1]
|
||||
backbone.train(training)
|
||||
else:
|
||||
feature_size = to_2tuple(feature_size)
|
||||
feature_dim = self.backbone.feature_info.channels()[-1]
|
||||
self.num_patches = feature_size[0] * feature_size[1]
|
||||
self.proj = nn.Linear(feature_dim, embed_dim)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.backbone(x)[-1]
|
||||
x = x.flatten(2).transpose(1, 2)
|
||||
x = self.proj(x)
|
||||
return x
|
||||
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class ViTMoE(BaseBackbone):
|
||||
|
||||
def __init__(self,
|
||||
img_size=224, patch_size=16, in_chans=3, num_classes=80, embed_dim=768, depth=12,
|
||||
num_heads=12, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop_rate=0., attn_drop_rate=0.,
|
||||
drop_path_rate=0., hybrid_backbone=None, norm_layer=None, use_checkpoint=False,
|
||||
frozen_stages=-1, ratio=1, last_norm=True,
|
||||
patch_padding='pad', freeze_attn=False, freeze_ffn=False,
|
||||
num_expert=1, part_features=None
|
||||
):
|
||||
# Protect mutable default arguments
|
||||
super(ViTMoE, self).__init__()
|
||||
norm_layer = norm_layer or partial(nn.LayerNorm, eps=1e-6)
|
||||
self.num_classes = num_classes
|
||||
self.num_features = self.embed_dim = embed_dim # num_features for consistency with other models
|
||||
self.frozen_stages = frozen_stages
|
||||
self.use_checkpoint = use_checkpoint
|
||||
self.patch_padding = patch_padding
|
||||
self.freeze_attn = freeze_attn
|
||||
self.freeze_ffn = freeze_ffn
|
||||
self.depth = depth
|
||||
|
||||
if hybrid_backbone is not None:
|
||||
self.patch_embed = HybridEmbed(
|
||||
hybrid_backbone, img_size=img_size, in_chans=in_chans, embed_dim=embed_dim)
|
||||
else:
|
||||
self.patch_embed = PatchEmbed(
|
||||
img_size=img_size, patch_size=patch_size, in_chans=in_chans, embed_dim=embed_dim, ratio=ratio)
|
||||
num_patches = self.patch_embed.num_patches
|
||||
|
||||
self.part_features = part_features
|
||||
|
||||
self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, embed_dim))
|
||||
|
||||
dpr = [x.item() for x in torch.linspace(0, drop_path_rate, depth)] # stochastic depth decay rule
|
||||
|
||||
self.blocks = nn.ModuleList([
|
||||
Block(
|
||||
dim=embed_dim, num_heads=num_heads, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale,
|
||||
drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i], norm_layer=norm_layer,
|
||||
num_expert=num_expert, part_features=part_features
|
||||
)
|
||||
for i in range(depth)])
|
||||
|
||||
self.last_norm = norm_layer(embed_dim) if last_norm else nn.Identity()
|
||||
|
||||
if self.pos_embed is not None:
|
||||
trunc_normal_(self.pos_embed, std=.02)
|
||||
|
||||
self._freeze_stages()
|
||||
|
||||
def _freeze_stages(self):
|
||||
"""Freeze parameters."""
|
||||
if self.frozen_stages >= 0:
|
||||
self.patch_embed.eval()
|
||||
for param in self.patch_embed.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
for i in range(1, self.frozen_stages + 1):
|
||||
m = self.blocks[i]
|
||||
m.eval()
|
||||
for param in m.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
if self.freeze_attn:
|
||||
for i in range(0, self.depth):
|
||||
m = self.blocks[i]
|
||||
m.attn.eval()
|
||||
m.norm1.eval()
|
||||
for param in m.attn.parameters():
|
||||
param.requires_grad = False
|
||||
for param in m.norm1.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
if self.freeze_ffn:
|
||||
self.pos_embed.requires_grad = False
|
||||
self.patch_embed.eval()
|
||||
for param in self.patch_embed.parameters():
|
||||
param.requires_grad = False
|
||||
for i in range(0, self.depth):
|
||||
m = self.blocks[i]
|
||||
m.mlp.eval()
|
||||
m.norm2.eval()
|
||||
for param in m.mlp.parameters():
|
||||
param.requires_grad = False
|
||||
for param in m.norm2.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Initialize the weights in backbone.
|
||||
Args:
|
||||
pretrained (str, optional): Path to pre-trained weights.
|
||||
Defaults to None.
|
||||
"""
|
||||
super().init_weights(pretrained, patch_padding=self.patch_padding, part_features=self.part_features)
|
||||
|
||||
if pretrained is None:
|
||||
def _init_weights(m):
|
||||
if isinstance(m, nn.Linear):
|
||||
trunc_normal_(m.weight, std=.02)
|
||||
if isinstance(m, nn.Linear) and m.bias is not None:
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.LayerNorm):
|
||||
nn.init.constant_(m.bias, 0)
|
||||
nn.init.constant_(m.weight, 1.0)
|
||||
|
||||
self.apply(_init_weights)
|
||||
|
||||
def get_num_layers(self):
|
||||
return len(self.blocks)
|
||||
|
||||
@torch.jit.ignore
|
||||
def no_weight_decay(self):
|
||||
return {'pos_embed', 'cls_token'}
|
||||
|
||||
def forward_features(self, x, dataset_source=None):
|
||||
B, C, H, W = x.shape
|
||||
x, (Hp, Wp) = self.patch_embed(x)
|
||||
|
||||
if self.pos_embed is not None:
|
||||
# fit for multiple GPU training
|
||||
# since the first element for pos embed (sin-cos manner) is zero, it will cause no difference
|
||||
x = x + self.pos_embed[:, 1:] + self.pos_embed[:, :1]
|
||||
|
||||
for blk in self.blocks:
|
||||
if self.use_checkpoint:
|
||||
x = checkpoint.checkpoint(blk, x, dataset_source)
|
||||
else:
|
||||
x = blk(x, dataset_source)
|
||||
|
||||
x = self.last_norm(x)
|
||||
|
||||
xp = x.permute(0, 2, 1).reshape(B, -1, Hp, Wp).contiguous()
|
||||
|
||||
return xp
|
||||
|
||||
def forward(self, x, dataset_source=None):
|
||||
x = self.forward_features(x, dataset_source)
|
||||
return x
|
||||
|
||||
def train(self, mode=True):
|
||||
"""Convert the model into training mode."""
|
||||
super().train(mode)
|
||||
self._freeze_stages()
|
||||
@@ -0,0 +1,44 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from mmcv.cnn import MODELS as MMCV_MODELS
|
||||
from mmcv.cnn import build_model_from_cfg
|
||||
from mmcv.utils import Registry
|
||||
|
||||
MODELS = Registry(
|
||||
'models', build_func=build_model_from_cfg, parent=MMCV_MODELS)
|
||||
|
||||
BACKBONES = MODELS
|
||||
NECKS = MODELS
|
||||
HEADS = MODELS
|
||||
LOSSES = MODELS
|
||||
POSENETS = MODELS
|
||||
MESH_MODELS = MODELS
|
||||
|
||||
|
||||
def build_backbone(cfg):
|
||||
"""Build backbone."""
|
||||
return BACKBONES.build(cfg)
|
||||
|
||||
|
||||
def build_neck(cfg):
|
||||
"""Build neck."""
|
||||
return NECKS.build(cfg)
|
||||
|
||||
|
||||
def build_head(cfg):
|
||||
"""Build head."""
|
||||
return HEADS.build(cfg)
|
||||
|
||||
|
||||
def build_loss(cfg):
|
||||
"""Build loss."""
|
||||
return LOSSES.build(cfg)
|
||||
|
||||
|
||||
def build_posenet(cfg):
|
||||
"""Build posenet."""
|
||||
return POSENETS.build(cfg)
|
||||
|
||||
|
||||
def build_mesh_model(cfg):
|
||||
"""Build mesh model."""
|
||||
return MESH_MODELS.build(cfg)
|
||||
@@ -0,0 +1,17 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .associative_embedding import AssociativeEmbedding
|
||||
from .interhand_3d import Interhand3D
|
||||
from .mesh import ParametricMesh
|
||||
from .multi_task import MultiTask
|
||||
from .multiview_pose import (DetectAndRegress, VoxelCenterDetector,
|
||||
VoxelSinglePose)
|
||||
from .pose_lifter import PoseLifter
|
||||
from .posewarper import PoseWarper
|
||||
from .top_down import TopDown
|
||||
from .top_down_moe import TopDownMoE
|
||||
|
||||
__all__ = [
|
||||
'TopDown', 'AssociativeEmbedding', 'ParametricMesh', 'MultiTask',
|
||||
'PoseLifter', 'Interhand3D', 'PoseWarper', 'DetectAndRegress',
|
||||
'VoxelCenterDetector', 'VoxelSinglePose', 'TopDownMoE'
|
||||
]
|
||||
+420
@@ -0,0 +1,420 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
import mmcv
|
||||
import torch
|
||||
from mmcv.image import imwrite
|
||||
from mmcv.utils.misc import deprecated_api_warning
|
||||
from mmcv.visualization.image import imshow
|
||||
|
||||
from mmpose.core.evaluation import (aggregate_scale, aggregate_stage_flip,
|
||||
flip_feature_maps, get_group_preds,
|
||||
split_ae_outputs)
|
||||
from mmpose.core.post_processing.group import HeatmapParser
|
||||
from mmpose.core.visualization import imshow_keypoints
|
||||
from .. import builder
|
||||
from ..builder import POSENETS
|
||||
from .base import BasePose
|
||||
|
||||
try:
|
||||
from mmcv.runner import auto_fp16
|
||||
except ImportError:
|
||||
warnings.warn('auto_fp16 from mmpose will be deprecated from v0.15.0'
|
||||
'Please install mmcv>=1.1.4')
|
||||
from mmpose.core import auto_fp16
|
||||
|
||||
|
||||
@POSENETS.register_module()
|
||||
class AssociativeEmbedding(BasePose):
|
||||
"""Associative embedding pose detectors.
|
||||
|
||||
Args:
|
||||
backbone (dict): Backbone modules to extract feature.
|
||||
keypoint_head (dict): Keypoint head to process feature.
|
||||
train_cfg (dict): Config for training. Default: None.
|
||||
test_cfg (dict): Config for testing. Default: None.
|
||||
pretrained (str): Path to the pretrained models.
|
||||
loss_pose (None): Deprecated arguments. Please use
|
||||
``loss_keypoint`` for heads instead.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
backbone,
|
||||
keypoint_head=None,
|
||||
train_cfg=None,
|
||||
test_cfg=None,
|
||||
pretrained=None,
|
||||
loss_pose=None):
|
||||
super().__init__()
|
||||
self.fp16_enabled = False
|
||||
|
||||
self.backbone = builder.build_backbone(backbone)
|
||||
|
||||
if keypoint_head is not None:
|
||||
if 'loss_keypoint' not in keypoint_head and loss_pose is not None:
|
||||
warnings.warn(
|
||||
'`loss_pose` for BottomUp is deprecated, '
|
||||
'use `loss_keypoint` for heads instead. See '
|
||||
'https://github.com/open-mmlab/mmpose/pull/382'
|
||||
' for more information.', DeprecationWarning)
|
||||
keypoint_head['loss_keypoint'] = loss_pose
|
||||
|
||||
self.keypoint_head = builder.build_head(keypoint_head)
|
||||
|
||||
self.train_cfg = train_cfg
|
||||
self.test_cfg = test_cfg
|
||||
self.use_udp = test_cfg.get('use_udp', False)
|
||||
self.parser = HeatmapParser(self.test_cfg)
|
||||
self.init_weights(pretrained=pretrained)
|
||||
|
||||
@property
|
||||
def with_keypoint(self):
|
||||
"""Check if has keypoint_head."""
|
||||
return hasattr(self, 'keypoint_head')
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Weight initialization for model."""
|
||||
self.backbone.init_weights(pretrained)
|
||||
if self.with_keypoint:
|
||||
self.keypoint_head.init_weights()
|
||||
|
||||
@auto_fp16(apply_to=('img', ))
|
||||
def forward(self,
|
||||
img=None,
|
||||
targets=None,
|
||||
masks=None,
|
||||
joints=None,
|
||||
img_metas=None,
|
||||
return_loss=True,
|
||||
return_heatmap=False,
|
||||
**kwargs):
|
||||
"""Calls either forward_train or forward_test depending on whether
|
||||
return_loss is True.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- num_img_channel: C
|
||||
- img_width: imgW
|
||||
- img_height: imgH
|
||||
- heatmaps weight: W
|
||||
- heatmaps height: H
|
||||
- max_num_people: M
|
||||
|
||||
Args:
|
||||
img (torch.Tensor[N,C,imgH,imgW]): Input image.
|
||||
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
|
||||
img_metas (dict): Information about val & test.
|
||||
By default it includes:
|
||||
|
||||
- "image_file": image path
|
||||
- "aug_data": input
|
||||
- "test_scale_factor": test scale factor
|
||||
- "base_size": base size of input
|
||||
- "center": center of image
|
||||
- "scale": scale of image
|
||||
- "flip_index": flip index of keypoints
|
||||
return loss (bool): ``return_loss=True`` for training,
|
||||
``return_loss=False`` for validation & test.
|
||||
return_heatmap (bool) : Option to return heatmap.
|
||||
|
||||
Returns:
|
||||
dict|tuple: if 'return_loss' is true, then return losses. \
|
||||
Otherwise, return predicted poses, scores, image \
|
||||
paths and heatmaps.
|
||||
"""
|
||||
|
||||
if return_loss:
|
||||
return self.forward_train(img, targets, masks, joints, img_metas,
|
||||
**kwargs)
|
||||
return self.forward_test(
|
||||
img, img_metas, return_heatmap=return_heatmap, **kwargs)
|
||||
|
||||
def forward_train(self, img, targets, masks, joints, img_metas, **kwargs):
|
||||
"""Forward the bottom-up model and calculate the loss.
|
||||
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
num_img_channel: C
|
||||
img_width: imgW
|
||||
img_height: imgH
|
||||
heatmaps weight: W
|
||||
heatmaps height: H
|
||||
max_num_people: M
|
||||
|
||||
Args:
|
||||
img (torch.Tensor[N,C,imgH,imgW]): Input image.
|
||||
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
|
||||
img_metas (dict):Information about val&test
|
||||
By default this includes:
|
||||
- "image_file": image path
|
||||
- "aug_data": input
|
||||
- "test_scale_factor": test scale factor
|
||||
- "base_size": base size of input
|
||||
- "center": center of image
|
||||
- "scale": scale of image
|
||||
- "flip_index": flip index of keypoints
|
||||
|
||||
Returns:
|
||||
dict: The total loss for bottom-up
|
||||
"""
|
||||
|
||||
output = self.backbone(img)
|
||||
|
||||
if self.with_keypoint:
|
||||
output = self.keypoint_head(output)
|
||||
|
||||
# if return loss
|
||||
losses = dict()
|
||||
if self.with_keypoint:
|
||||
keypoint_losses = self.keypoint_head.get_loss(
|
||||
output, targets, masks, joints)
|
||||
losses.update(keypoint_losses)
|
||||
|
||||
return losses
|
||||
|
||||
def forward_dummy(self, img):
|
||||
"""Used for computing network FLOPs.
|
||||
|
||||
See ``tools/get_flops.py``.
|
||||
|
||||
Args:
|
||||
img (torch.Tensor): Input image.
|
||||
|
||||
Returns:
|
||||
Tensor: Outputs.
|
||||
"""
|
||||
output = self.backbone(img)
|
||||
if self.with_keypoint:
|
||||
output = self.keypoint_head(output)
|
||||
return output
|
||||
|
||||
def forward_test(self, img, img_metas, return_heatmap=False, **kwargs):
|
||||
"""Inference the bottom-up model.
|
||||
|
||||
Note:
|
||||
- Batchsize: N (currently support batchsize = 1)
|
||||
- num_img_channel: C
|
||||
- img_width: imgW
|
||||
- img_height: imgH
|
||||
|
||||
Args:
|
||||
flip_index (List(int)):
|
||||
aug_data (List(Tensor[NxCximgHximgW])): Multi-scale image
|
||||
test_scale_factor (List(float)): Multi-scale factor
|
||||
base_size (Tuple(int)): Base size of image when scale is 1
|
||||
center (np.ndarray): center of image
|
||||
scale (np.ndarray): the scale of image
|
||||
"""
|
||||
assert img.size(0) == 1
|
||||
assert len(img_metas) == 1
|
||||
|
||||
img_metas = img_metas[0]
|
||||
|
||||
aug_data = img_metas['aug_data']
|
||||
|
||||
test_scale_factor = img_metas['test_scale_factor']
|
||||
base_size = img_metas['base_size']
|
||||
center = img_metas['center']
|
||||
scale = img_metas['scale']
|
||||
|
||||
result = {}
|
||||
|
||||
scale_heatmaps_list = []
|
||||
scale_tags_list = []
|
||||
|
||||
for idx, s in enumerate(sorted(test_scale_factor, reverse=True)):
|
||||
image_resized = aug_data[idx].to(img.device)
|
||||
|
||||
features = self.backbone(image_resized)
|
||||
if self.with_keypoint:
|
||||
outputs = self.keypoint_head(features)
|
||||
|
||||
heatmaps, tags = split_ae_outputs(
|
||||
outputs, self.test_cfg['num_joints'],
|
||||
self.test_cfg['with_heatmaps'], self.test_cfg['with_ae'],
|
||||
self.test_cfg.get('select_output_index', range(len(outputs))))
|
||||
|
||||
if self.test_cfg.get('flip_test', True):
|
||||
# use flip test
|
||||
features_flipped = self.backbone(
|
||||
torch.flip(image_resized, [3]))
|
||||
if self.with_keypoint:
|
||||
outputs_flipped = self.keypoint_head(features_flipped)
|
||||
|
||||
heatmaps_flipped, tags_flipped = split_ae_outputs(
|
||||
outputs_flipped, self.test_cfg['num_joints'],
|
||||
self.test_cfg['with_heatmaps'], self.test_cfg['with_ae'],
|
||||
self.test_cfg.get('select_output_index',
|
||||
range(len(outputs))))
|
||||
|
||||
heatmaps_flipped = flip_feature_maps(
|
||||
heatmaps_flipped, flip_index=img_metas['flip_index'])
|
||||
if self.test_cfg['tag_per_joint']:
|
||||
tags_flipped = flip_feature_maps(
|
||||
tags_flipped, flip_index=img_metas['flip_index'])
|
||||
else:
|
||||
tags_flipped = flip_feature_maps(
|
||||
tags_flipped, flip_index=None, flip_output=True)
|
||||
|
||||
else:
|
||||
heatmaps_flipped = None
|
||||
tags_flipped = None
|
||||
|
||||
aggregated_heatmaps = aggregate_stage_flip(
|
||||
heatmaps,
|
||||
heatmaps_flipped,
|
||||
index=-1,
|
||||
project2image=self.test_cfg['project2image'],
|
||||
size_projected=base_size,
|
||||
align_corners=self.test_cfg.get('align_corners', True),
|
||||
aggregate_stage='average',
|
||||
aggregate_flip='average')
|
||||
|
||||
aggregated_tags = aggregate_stage_flip(
|
||||
tags,
|
||||
tags_flipped,
|
||||
index=-1,
|
||||
project2image=self.test_cfg['project2image'],
|
||||
size_projected=base_size,
|
||||
align_corners=self.test_cfg.get('align_corners', True),
|
||||
aggregate_stage='concat',
|
||||
aggregate_flip='concat')
|
||||
|
||||
if s == 1 or len(test_scale_factor) == 1:
|
||||
if isinstance(aggregated_tags, list):
|
||||
scale_tags_list.extend(aggregated_tags)
|
||||
else:
|
||||
scale_tags_list.append(aggregated_tags)
|
||||
|
||||
if isinstance(aggregated_heatmaps, list):
|
||||
scale_heatmaps_list.extend(aggregated_heatmaps)
|
||||
else:
|
||||
scale_heatmaps_list.append(aggregated_heatmaps)
|
||||
|
||||
aggregated_heatmaps = aggregate_scale(
|
||||
scale_heatmaps_list,
|
||||
align_corners=self.test_cfg.get('align_corners', True),
|
||||
aggregate_scale='average')
|
||||
|
||||
aggregated_tags = aggregate_scale(
|
||||
scale_tags_list,
|
||||
align_corners=self.test_cfg.get('align_corners', True),
|
||||
aggregate_scale='unsqueeze_concat')
|
||||
|
||||
heatmap_size = aggregated_heatmaps.shape[2:4]
|
||||
tag_size = aggregated_tags.shape[2:4]
|
||||
if heatmap_size != tag_size:
|
||||
tmp = []
|
||||
for idx in range(aggregated_tags.shape[-1]):
|
||||
tmp.append(
|
||||
torch.nn.functional.interpolate(
|
||||
aggregated_tags[..., idx],
|
||||
size=heatmap_size,
|
||||
mode='bilinear',
|
||||
align_corners=self.test_cfg.get('align_corners',
|
||||
True)).unsqueeze(-1))
|
||||
aggregated_tags = torch.cat(tmp, dim=-1)
|
||||
|
||||
# perform grouping
|
||||
grouped, scores = self.parser.parse(aggregated_heatmaps,
|
||||
aggregated_tags,
|
||||
self.test_cfg['adjust'],
|
||||
self.test_cfg['refine'])
|
||||
|
||||
preds = get_group_preds(
|
||||
grouped,
|
||||
center,
|
||||
scale, [aggregated_heatmaps.size(3),
|
||||
aggregated_heatmaps.size(2)],
|
||||
use_udp=self.use_udp)
|
||||
|
||||
image_paths = []
|
||||
image_paths.append(img_metas['image_file'])
|
||||
|
||||
if return_heatmap:
|
||||
output_heatmap = aggregated_heatmaps.detach().cpu().numpy()
|
||||
else:
|
||||
output_heatmap = None
|
||||
|
||||
result['preds'] = preds
|
||||
result['scores'] = scores
|
||||
result['image_paths'] = image_paths
|
||||
result['output_heatmap'] = output_heatmap
|
||||
|
||||
return result
|
||||
|
||||
@deprecated_api_warning({'pose_limb_color': 'pose_link_color'},
|
||||
cls_name='AssociativeEmbedding')
|
||||
def show_result(self,
|
||||
img,
|
||||
result,
|
||||
skeleton=None,
|
||||
kpt_score_thr=0.3,
|
||||
bbox_color=None,
|
||||
pose_kpt_color=None,
|
||||
pose_link_color=None,
|
||||
radius=4,
|
||||
thickness=1,
|
||||
font_scale=0.5,
|
||||
win_name='',
|
||||
show=False,
|
||||
show_keypoint_weight=False,
|
||||
wait_time=0,
|
||||
out_file=None):
|
||||
"""Draw `result` over `img`.
|
||||
|
||||
Args:
|
||||
img (str or Tensor): The image to be displayed.
|
||||
result (list[dict]): The results to draw over `img`
|
||||
(bbox_result, pose_result).
|
||||
skeleton (list[list]): The connection of keypoints.
|
||||
skeleton is 0-based indexing.
|
||||
kpt_score_thr (float, optional): Minimum score of keypoints
|
||||
to be shown. Default: 0.3.
|
||||
pose_kpt_color (np.array[Nx3]`): Color of N keypoints.
|
||||
If None, do not draw keypoints.
|
||||
pose_link_color (np.array[Mx3]): Color of M links.
|
||||
If None, do not draw links.
|
||||
radius (int): Radius of circles.
|
||||
thickness (int): Thickness of lines.
|
||||
font_scale (float): Font scales of texts.
|
||||
win_name (str): The window name.
|
||||
show (bool): Whether to show the image. Default: False.
|
||||
show_keypoint_weight (bool): Whether to change the transparency
|
||||
using the predicted confidence scores of keypoints.
|
||||
wait_time (int): Value of waitKey param.
|
||||
Default: 0.
|
||||
out_file (str or None): The filename to write the image.
|
||||
Default: None.
|
||||
|
||||
Returns:
|
||||
Tensor: Visualized image only if not `show` or `out_file`
|
||||
"""
|
||||
img = mmcv.imread(img)
|
||||
img = img.copy()
|
||||
img_h, img_w, _ = img.shape
|
||||
|
||||
pose_result = []
|
||||
for res in result:
|
||||
pose_result.append(res['keypoints'])
|
||||
|
||||
imshow_keypoints(img, pose_result, skeleton, kpt_score_thr,
|
||||
pose_kpt_color, pose_link_color, radius, thickness)
|
||||
|
||||
if show:
|
||||
imshow(img, win_name, wait_time)
|
||||
|
||||
if out_file is not None:
|
||||
imwrite(img, out_file)
|
||||
|
||||
return img
|
||||
@@ -0,0 +1,131 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from collections import OrderedDict
|
||||
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
class BasePose(nn.Module, metaclass=ABCMeta):
|
||||
"""Base class for pose detectors.
|
||||
|
||||
All recognizers should subclass it.
|
||||
All subclass should overwrite:
|
||||
Methods:`forward_train`, supporting to forward when training.
|
||||
Methods:`forward_test`, supporting to forward when testing.
|
||||
|
||||
Args:
|
||||
backbone (dict): Backbone modules to extract feature.
|
||||
head (dict): Head modules to give output.
|
||||
train_cfg (dict): Config for training. Default: None.
|
||||
test_cfg (dict): Config for testing. Default: None.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def forward_train(self, img, img_metas, **kwargs):
|
||||
"""Defines the computation performed at training."""
|
||||
|
||||
@abstractmethod
|
||||
def forward_test(self, img, img_metas, **kwargs):
|
||||
"""Defines the computation performed at testing."""
|
||||
|
||||
@abstractmethod
|
||||
def forward(self, img, img_metas, return_loss=True, **kwargs):
|
||||
"""Forward function."""
|
||||
|
||||
@staticmethod
|
||||
def _parse_losses(losses):
|
||||
"""Parse the raw outputs (losses) of the network.
|
||||
|
||||
Args:
|
||||
losses (dict): Raw output of the network, which usually contain
|
||||
losses and other necessary information.
|
||||
|
||||
Returns:
|
||||
tuple[Tensor, dict]: (loss, log_vars), loss is the loss tensor \
|
||||
which may be a weighted sum of all losses, log_vars \
|
||||
contains all the variables to be sent to the logger.
|
||||
"""
|
||||
log_vars = OrderedDict()
|
||||
for loss_name, loss_value in losses.items():
|
||||
if isinstance(loss_value, torch.Tensor):
|
||||
log_vars[loss_name] = loss_value.mean()
|
||||
elif isinstance(loss_value, float):
|
||||
log_vars[loss_name] = loss_value
|
||||
elif isinstance(loss_value, list):
|
||||
log_vars[loss_name] = sum(_loss.mean() for _loss in loss_value)
|
||||
else:
|
||||
raise TypeError(
|
||||
f'{loss_name} is not a tensor or list of tensors or float')
|
||||
|
||||
loss = sum(_value for _key, _value in log_vars.items()
|
||||
if 'loss' in _key)
|
||||
|
||||
log_vars['loss'] = loss
|
||||
for loss_name, loss_value in log_vars.items():
|
||||
# reduce loss when distributed training
|
||||
if not isinstance(loss_value, float):
|
||||
if dist.is_available() and dist.is_initialized():
|
||||
loss_value = loss_value.data.clone()
|
||||
dist.all_reduce(loss_value.div_(dist.get_world_size()))
|
||||
log_vars[loss_name] = loss_value.item()
|
||||
else:
|
||||
log_vars[loss_name] = loss_value
|
||||
|
||||
return loss, log_vars
|
||||
|
||||
def train_step(self, data_batch, optimizer, **kwargs):
|
||||
"""The iteration step during training.
|
||||
|
||||
This method defines an iteration step during training, except for the
|
||||
back propagation and optimizer updating, which are done in an optimizer
|
||||
hook. Note that in some complicated cases or models, the whole process
|
||||
including back propagation and optimizer updating is also defined in
|
||||
this method, such as GAN.
|
||||
|
||||
Args:
|
||||
data_batch (dict): The output of dataloader.
|
||||
optimizer (:obj:`torch.optim.Optimizer` | dict): The optimizer of
|
||||
runner is passed to ``train_step()``. This argument is unused
|
||||
and reserved.
|
||||
|
||||
Returns:
|
||||
dict: It should contain at least 3 keys: ``loss``, ``log_vars``,
|
||||
``num_samples``.
|
||||
``loss`` is a tensor for back propagation, which can be a
|
||||
weighted sum of multiple losses.
|
||||
``log_vars`` contains all the variables to be sent to the
|
||||
logger.
|
||||
``num_samples`` indicates the batch size (when the model is
|
||||
DDP, it means the batch size on each GPU), which is used for
|
||||
averaging the logs.
|
||||
"""
|
||||
losses = self.forward(**data_batch)
|
||||
|
||||
loss, log_vars = self._parse_losses(losses)
|
||||
|
||||
outputs = dict(
|
||||
loss=loss,
|
||||
log_vars=log_vars,
|
||||
num_samples=len(next(iter(data_batch.values()))))
|
||||
|
||||
return outputs
|
||||
|
||||
def val_step(self, data_batch, optimizer, **kwargs):
|
||||
"""The iteration step during validation.
|
||||
|
||||
This method shares the same signature as :func:`train_step`, but used
|
||||
during val epochs. Note that the evaluation after training epochs is
|
||||
not implemented with this method, but an evaluation hook.
|
||||
"""
|
||||
results = self.forward(return_loss=False, **data_batch)
|
||||
|
||||
outputs = dict(results=results)
|
||||
|
||||
return outputs
|
||||
|
||||
@abstractmethod
|
||||
def show_result(self, **kwargs):
|
||||
"""Visualize the results."""
|
||||
raise NotImplementedError
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import mmcv
|
||||
import numpy as np
|
||||
from mmcv.utils.misc import deprecated_api_warning
|
||||
|
||||
from mmpose.core import imshow_keypoints, imshow_keypoints_3d
|
||||
from ..builder import POSENETS
|
||||
from .top_down import TopDown
|
||||
|
||||
|
||||
@POSENETS.register_module()
|
||||
class Interhand3D(TopDown):
|
||||
"""Top-down interhand 3D pose detector of paper ref: Gyeongsik Moon.
|
||||
|
||||
"InterHand2.6M: A Dataset and Baseline for 3D Interacting Hand Pose
|
||||
Estimation from a Single RGB Image". A child class of TopDown detector.
|
||||
"""
|
||||
|
||||
def forward(self,
|
||||
img,
|
||||
target=None,
|
||||
target_weight=None,
|
||||
img_metas=None,
|
||||
return_loss=True,
|
||||
**kwargs):
|
||||
"""Calls either forward_train or forward_test depending on whether
|
||||
return_loss=True. Note this setting will change the expected inputs.
|
||||
When `return_loss=True`, img and img_meta are single-nested (i.e.
|
||||
Tensor and List[dict]), and when `resturn_loss=False`, img and img_meta
|
||||
should be double nested (i.e. list[Tensor], list[list[dict]]), with
|
||||
the outer list indicating test time augmentations.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- num_img_channel: C (Default: 3)
|
||||
- img height: imgH
|
||||
- img width: imgW
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
img (torch.Tensor[NxCximgHximgW]): Input images.
|
||||
target (list[torch.Tensor]): Target heatmaps, relative hand
|
||||
root depth and hand type.
|
||||
target_weight (list[torch.Tensor]): Weights for target
|
||||
heatmaps, relative hand root depth and hand type.
|
||||
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
|
||||
return_loss (bool): Option to `return loss`. `return loss=True`
|
||||
for training, `return loss=False` for validation & test.
|
||||
|
||||
Returns:
|
||||
dict|tuple: if `return loss` is true, then return losses. \
|
||||
Otherwise, return predicted poses, boxes, image paths, \
|
||||
heatmaps, relative hand root depth and hand type.
|
||||
"""
|
||||
if return_loss:
|
||||
return self.forward_train(img, target, target_weight, img_metas,
|
||||
**kwargs)
|
||||
return self.forward_test(img, img_metas, **kwargs)
|
||||
|
||||
def forward_test(self, img, img_metas, **kwargs):
|
||||
"""Defines the computation performed at every call when testing."""
|
||||
assert img.size(0) == len(img_metas)
|
||||
batch_size, _, img_height, img_width = img.shape
|
||||
if batch_size > 1:
|
||||
assert 'bbox_id' in img_metas[0]
|
||||
|
||||
features = self.backbone(img)
|
||||
if self.with_neck:
|
||||
features = self.neck(features)
|
||||
if self.with_keypoint:
|
||||
output = self.keypoint_head.inference_model(
|
||||
features, flip_pairs=None)
|
||||
|
||||
if self.test_cfg.get('flip_test', True):
|
||||
img_flipped = img.flip(3)
|
||||
features_flipped = self.backbone(img_flipped)
|
||||
if self.with_neck:
|
||||
features_flipped = self.neck(features_flipped)
|
||||
if self.with_keypoint:
|
||||
output_flipped = self.keypoint_head.inference_model(
|
||||
features_flipped, img_metas[0]['flip_pairs'])
|
||||
output = [(out + out_flipped) * 0.5
|
||||
for out, out_flipped in zip(output, output_flipped)]
|
||||
|
||||
if self.with_keypoint:
|
||||
result = self.keypoint_head.decode(
|
||||
img_metas, output, img_size=[img_width, img_height])
|
||||
else:
|
||||
result = {}
|
||||
return result
|
||||
|
||||
@deprecated_api_warning({'pose_limb_color': 'pose_link_color'},
|
||||
cls_name='Interhand3D')
|
||||
def show_result(self,
|
||||
result,
|
||||
img=None,
|
||||
skeleton=None,
|
||||
kpt_score_thr=0.3,
|
||||
radius=8,
|
||||
bbox_color='green',
|
||||
thickness=2,
|
||||
pose_kpt_color=None,
|
||||
pose_link_color=None,
|
||||
vis_height=400,
|
||||
num_instances=-1,
|
||||
win_name='',
|
||||
show=False,
|
||||
wait_time=0,
|
||||
out_file=None):
|
||||
"""Visualize 3D pose estimation results.
|
||||
|
||||
Args:
|
||||
result (list[dict]): The pose estimation results containing:
|
||||
|
||||
- "keypoints_3d" ([K,4]): 3D keypoints
|
||||
- "keypoints" ([K,3] or [T,K,3]): Optional for visualizing
|
||||
2D inputs. If a sequence is given, only the last frame
|
||||
will be used for visualization
|
||||
- "bbox" ([4,] or [T,4]): Optional for visualizing 2D inputs
|
||||
- "title" (str): title for the subplot
|
||||
img (str or Tensor): Optional. The image to visualize 2D inputs on.
|
||||
skeleton (list of [idx_i,idx_j]): Skeleton described by a list of
|
||||
links, each is a pair of joint indices.
|
||||
kpt_score_thr (float, optional): Minimum score of keypoints
|
||||
to be shown. Default: 0.3.
|
||||
radius (int): Radius of circles.
|
||||
bbox_color (str or tuple or :obj:`Color`): Color of bbox lines.
|
||||
thickness (int): Thickness of lines.
|
||||
pose_kpt_color (np.array[Nx3]`): Color of N keypoints.
|
||||
If None, do not draw keypoints.
|
||||
pose_link_color (np.array[Mx3]): Color of M limbs.
|
||||
If None, do not draw limbs.
|
||||
vis_height (int): The image height of the visualization. The width
|
||||
will be N*vis_height depending on the number of visualized
|
||||
items.
|
||||
num_instances (int): Number of instances to be shown in 3D. If
|
||||
smaller than 0, all the instances in the pose_result will be
|
||||
shown. Otherwise, pad or truncate the pose_result to a length
|
||||
of num_instances.
|
||||
win_name (str): The window name.
|
||||
show (bool): Whether to show the image. Default: False.
|
||||
wait_time (int): Value of waitKey param.
|
||||
Default: 0.
|
||||
out_file (str or None): The filename to write the image.
|
||||
Default: None.
|
||||
|
||||
Returns:
|
||||
Tensor: Visualized img, only if not `show` or `out_file`.
|
||||
"""
|
||||
if num_instances < 0:
|
||||
assert len(result) > 0
|
||||
result = sorted(result, key=lambda x: x.get('track_id', 0))
|
||||
|
||||
# draw image and 2d poses
|
||||
if img is not None:
|
||||
img = mmcv.imread(img)
|
||||
|
||||
bbox_result = []
|
||||
pose_2d = []
|
||||
for res in result:
|
||||
if 'bbox' in res:
|
||||
bbox = np.array(res['bbox'])
|
||||
if bbox.ndim != 1:
|
||||
assert bbox.ndim == 2
|
||||
bbox = bbox[-1] # Get bbox from the last frame
|
||||
bbox_result.append(bbox)
|
||||
if 'keypoints' in res:
|
||||
kpts = np.array(res['keypoints'])
|
||||
if kpts.ndim != 2:
|
||||
assert kpts.ndim == 3
|
||||
kpts = kpts[-1] # Get 2D keypoints from the last frame
|
||||
pose_2d.append(kpts)
|
||||
|
||||
if len(bbox_result) > 0:
|
||||
bboxes = np.vstack(bbox_result)
|
||||
mmcv.imshow_bboxes(
|
||||
img,
|
||||
bboxes,
|
||||
colors=bbox_color,
|
||||
top_k=-1,
|
||||
thickness=2,
|
||||
show=False)
|
||||
if len(pose_2d) > 0:
|
||||
imshow_keypoints(
|
||||
img,
|
||||
pose_2d,
|
||||
skeleton,
|
||||
kpt_score_thr=kpt_score_thr,
|
||||
pose_kpt_color=pose_kpt_color,
|
||||
pose_link_color=pose_link_color,
|
||||
radius=radius,
|
||||
thickness=thickness)
|
||||
img = mmcv.imrescale(img, scale=vis_height / img.shape[0])
|
||||
|
||||
img_vis = imshow_keypoints_3d(
|
||||
result,
|
||||
img,
|
||||
skeleton,
|
||||
pose_kpt_color,
|
||||
pose_link_color,
|
||||
vis_height,
|
||||
axis_limit=300,
|
||||
axis_azimuth=-115,
|
||||
axis_elev=15,
|
||||
kpt_score_thr=kpt_score_thr,
|
||||
num_instances=num_instances)
|
||||
|
||||
if show:
|
||||
mmcv.visualization.imshow(img_vis, win_name, wait_time)
|
||||
|
||||
if out_file is not None:
|
||||
mmcv.imwrite(img_vis, out_file)
|
||||
|
||||
return img_vis
|
||||
@@ -0,0 +1,438 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import cv2
|
||||
import mmcv
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from mmpose.core.visualization.image import imshow_mesh_3d
|
||||
from mmpose.models.misc.discriminator import SMPLDiscriminator
|
||||
from .. import builder
|
||||
from ..builder import POSENETS
|
||||
from .base import BasePose
|
||||
|
||||
|
||||
def set_requires_grad(nets, requires_grad=False):
|
||||
"""Set requies_grad for all the networks.
|
||||
|
||||
Args:
|
||||
nets (nn.Module | list[nn.Module]): A list of networks or a single
|
||||
network.
|
||||
requires_grad (bool): Whether the networks require gradients or not
|
||||
"""
|
||||
if not isinstance(nets, list):
|
||||
nets = [nets]
|
||||
for net in nets:
|
||||
if net is not None:
|
||||
for param in net.parameters():
|
||||
param.requires_grad = requires_grad
|
||||
|
||||
|
||||
@POSENETS.register_module()
|
||||
class ParametricMesh(BasePose):
|
||||
"""Model-based 3D human mesh detector. Take a single color image as input
|
||||
and output 3D joints, SMPL parameters and camera parameters.
|
||||
|
||||
Args:
|
||||
backbone (dict): Backbone modules to extract feature.
|
||||
mesh_head (dict): Mesh head to process feature.
|
||||
smpl (dict): Config for SMPL model.
|
||||
disc (dict): Discriminator for SMPL parameters. Default: None.
|
||||
loss_gan (dict): Config for adversarial loss. Default: None.
|
||||
loss_mesh (dict): Config for mesh loss. Default: None.
|
||||
train_cfg (dict): Config for training. Default: None.
|
||||
test_cfg (dict): Config for testing. Default: None.
|
||||
pretrained (str): Path to the pretrained models.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
backbone,
|
||||
mesh_head,
|
||||
smpl,
|
||||
disc=None,
|
||||
loss_gan=None,
|
||||
loss_mesh=None,
|
||||
train_cfg=None,
|
||||
test_cfg=None,
|
||||
pretrained=None):
|
||||
super().__init__()
|
||||
|
||||
self.backbone = builder.build_backbone(backbone)
|
||||
self.mesh_head = builder.build_head(mesh_head)
|
||||
self.generator = torch.nn.Sequential(self.backbone, self.mesh_head)
|
||||
|
||||
self.smpl = builder.build_mesh_model(smpl)
|
||||
|
||||
self.with_gan = disc is not None and loss_gan is not None
|
||||
if self.with_gan:
|
||||
self.discriminator = SMPLDiscriminator(**disc)
|
||||
self.loss_gan = builder.build_loss(loss_gan)
|
||||
self.disc_step_count = 0
|
||||
|
||||
self.train_cfg = train_cfg
|
||||
self.test_cfg = test_cfg
|
||||
|
||||
self.loss_mesh = builder.build_loss(loss_mesh)
|
||||
self.init_weights(pretrained=pretrained)
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Weight initialization for model."""
|
||||
self.backbone.init_weights(pretrained)
|
||||
self.mesh_head.init_weights()
|
||||
if self.with_gan:
|
||||
self.discriminator.init_weights()
|
||||
|
||||
def train_step(self, data_batch, optimizer, **kwargs):
|
||||
"""Train step function.
|
||||
|
||||
In this function, the detector will finish the train step following
|
||||
the pipeline:
|
||||
|
||||
1. get fake and real SMPL parameters
|
||||
2. optimize discriminator (if have)
|
||||
3. optimize generator
|
||||
|
||||
If `self.train_cfg.disc_step > 1`, the train step will contain multiple
|
||||
iterations for optimizing discriminator with different input data and
|
||||
only one iteration for optimizing generator after `disc_step`
|
||||
iterations for discriminator.
|
||||
|
||||
Args:
|
||||
data_batch (torch.Tensor): Batch of data as input.
|
||||
optimizer (dict[torch.optim.Optimizer]): Dict with optimizers for
|
||||
generator and discriminator (if have).
|
||||
|
||||
Returns:
|
||||
outputs (dict): Dict with loss, information for logger,
|
||||
the number of samples.
|
||||
"""
|
||||
|
||||
img = data_batch['img']
|
||||
pred_smpl = self.generator(img)
|
||||
pred_pose, pred_beta, pred_camera = pred_smpl
|
||||
|
||||
# optimize discriminator (if have)
|
||||
if self.train_cfg['disc_step'] > 0 and self.with_gan:
|
||||
set_requires_grad(self.discriminator, True)
|
||||
fake_data = (pred_camera.detach(), pred_pose.detach(),
|
||||
pred_beta.detach())
|
||||
mosh_theta = data_batch['mosh_theta']
|
||||
real_data = (mosh_theta[:, :3], mosh_theta[:,
|
||||
3:75], mosh_theta[:,
|
||||
75:])
|
||||
fake_score = self.discriminator(fake_data)
|
||||
real_score = self.discriminator(real_data)
|
||||
|
||||
disc_losses = {}
|
||||
disc_losses['real_loss'] = self.loss_gan(
|
||||
real_score, target_is_real=True, is_disc=True)
|
||||
disc_losses['fake_loss'] = self.loss_gan(
|
||||
fake_score, target_is_real=False, is_disc=True)
|
||||
loss_disc, log_vars_d = self._parse_losses(disc_losses)
|
||||
|
||||
optimizer['discriminator'].zero_grad()
|
||||
loss_disc.backward()
|
||||
optimizer['discriminator'].step()
|
||||
self.disc_step_count = \
|
||||
(self.disc_step_count + 1) % self.train_cfg['disc_step']
|
||||
|
||||
if self.disc_step_count != 0:
|
||||
outputs = dict(
|
||||
loss=loss_disc,
|
||||
log_vars=log_vars_d,
|
||||
num_samples=len(next(iter(data_batch.values()))))
|
||||
return outputs
|
||||
|
||||
# optimize generator
|
||||
pred_out = self.smpl(
|
||||
betas=pred_beta,
|
||||
body_pose=pred_pose[:, 1:],
|
||||
global_orient=pred_pose[:, :1])
|
||||
pred_vertices, pred_joints_3d = pred_out['vertices'], pred_out[
|
||||
'joints']
|
||||
|
||||
gt_beta = data_batch['beta']
|
||||
gt_pose = data_batch['pose']
|
||||
gt_vertices = self.smpl(
|
||||
betas=gt_beta,
|
||||
body_pose=gt_pose[:, 3:],
|
||||
global_orient=gt_pose[:, :3])['vertices']
|
||||
|
||||
pred = dict(
|
||||
pose=pred_pose,
|
||||
beta=pred_beta,
|
||||
camera=pred_camera,
|
||||
vertices=pred_vertices,
|
||||
joints_3d=pred_joints_3d)
|
||||
|
||||
target = {
|
||||
key: data_batch[key]
|
||||
for key in [
|
||||
'pose', 'beta', 'has_smpl', 'joints_3d', 'joints_2d',
|
||||
'joints_3d_visible', 'joints_2d_visible'
|
||||
]
|
||||
}
|
||||
target['vertices'] = gt_vertices
|
||||
|
||||
losses = self.loss_mesh(pred, target)
|
||||
|
||||
if self.with_gan:
|
||||
set_requires_grad(self.discriminator, False)
|
||||
pred_theta = (pred_camera, pred_pose, pred_beta)
|
||||
pred_score = self.discriminator(pred_theta)
|
||||
loss_adv = self.loss_gan(
|
||||
pred_score, target_is_real=True, is_disc=False)
|
||||
losses['adv_loss'] = loss_adv
|
||||
|
||||
loss, log_vars = self._parse_losses(losses)
|
||||
optimizer['generator'].zero_grad()
|
||||
loss.backward()
|
||||
optimizer['generator'].step()
|
||||
|
||||
outputs = dict(
|
||||
loss=loss,
|
||||
log_vars=log_vars,
|
||||
num_samples=len(next(iter(data_batch.values()))))
|
||||
|
||||
return outputs
|
||||
|
||||
def forward_train(self, *args, **kwargs):
|
||||
"""Forward function for training.
|
||||
|
||||
For ParametricMesh, we do not use this interface.
|
||||
"""
|
||||
raise NotImplementedError('This interface should not be used in '
|
||||
'current training schedule. Please use '
|
||||
'`train_step` for training.')
|
||||
|
||||
def val_step(self, data_batch, **kwargs):
|
||||
"""Forward function for evaluation.
|
||||
|
||||
Args:
|
||||
data_batch (dict): Contain data for forward.
|
||||
|
||||
Returns:
|
||||
dict: Contain the results from model.
|
||||
"""
|
||||
output = self.forward_test(**data_batch, **kwargs)
|
||||
return output
|
||||
|
||||
def forward_dummy(self, img):
|
||||
"""Used for computing network FLOPs.
|
||||
|
||||
See ``tools/get_flops.py``.
|
||||
|
||||
Args:
|
||||
img (torch.Tensor): Input image.
|
||||
|
||||
Returns:
|
||||
Tensor: Outputs.
|
||||
"""
|
||||
output = self.generator(img)
|
||||
return output
|
||||
|
||||
def forward_test(self,
|
||||
img,
|
||||
img_metas,
|
||||
return_vertices=False,
|
||||
return_faces=False,
|
||||
**kwargs):
|
||||
"""Defines the computation performed at every call when testing."""
|
||||
|
||||
pred_smpl = self.generator(img)
|
||||
pred_pose, pred_beta, pred_camera = pred_smpl
|
||||
pred_out = self.smpl(
|
||||
betas=pred_beta,
|
||||
body_pose=pred_pose[:, 1:],
|
||||
global_orient=pred_pose[:, :1])
|
||||
pred_vertices, pred_joints_3d = pred_out['vertices'], pred_out[
|
||||
'joints']
|
||||
|
||||
all_preds = {}
|
||||
all_preds['keypoints_3d'] = pred_joints_3d.detach().cpu().numpy()
|
||||
all_preds['smpl_pose'] = pred_pose.detach().cpu().numpy()
|
||||
all_preds['smpl_beta'] = pred_beta.detach().cpu().numpy()
|
||||
all_preds['camera'] = pred_camera.detach().cpu().numpy()
|
||||
|
||||
if return_vertices:
|
||||
all_preds['vertices'] = pred_vertices.detach().cpu().numpy()
|
||||
if return_faces:
|
||||
all_preds['faces'] = self.smpl.get_faces()
|
||||
|
||||
all_boxes = []
|
||||
image_path = []
|
||||
for img_meta in img_metas:
|
||||
box = np.zeros(6, dtype=np.float32)
|
||||
c = img_meta['center']
|
||||
s = img_meta['scale']
|
||||
if 'bbox_score' in img_metas:
|
||||
score = np.array(img_metas['bbox_score']).reshape(-1)
|
||||
else:
|
||||
score = 1.0
|
||||
box[0:2] = c
|
||||
box[2:4] = s
|
||||
box[4] = np.prod(s * 200.0, axis=0)
|
||||
box[5] = score
|
||||
all_boxes.append(box)
|
||||
image_path.append(img_meta['image_file'])
|
||||
|
||||
all_preds['bboxes'] = np.stack(all_boxes, axis=0)
|
||||
all_preds['image_path'] = image_path
|
||||
return all_preds
|
||||
|
||||
def get_3d_joints_from_mesh(self, vertices):
|
||||
"""Get 3D joints from 3D mesh using predefined joints regressor."""
|
||||
return torch.matmul(
|
||||
self.joints_regressor.to(vertices.device), vertices)
|
||||
|
||||
def forward(self, img, img_metas=None, return_loss=False, **kwargs):
|
||||
"""Forward function.
|
||||
|
||||
Calls either forward_train or forward_test depending on whether
|
||||
return_loss=True.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_img_channel: C (Default: 3)
|
||||
- img height: imgH
|
||||
- img width: imgW
|
||||
|
||||
Args:
|
||||
img (torch.Tensor[N x C x imgH x imgW]): Input images.
|
||||
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
|
||||
return_loss (bool): Option to `return loss`. `return loss=True`
|
||||
for training, `return loss=False` for validation & test.
|
||||
|
||||
Returns:
|
||||
Return predicted 3D joints, SMPL parameters, boxes and image paths.
|
||||
"""
|
||||
|
||||
if return_loss:
|
||||
return self.forward_train(img, img_metas, **kwargs)
|
||||
return self.forward_test(img, img_metas, **kwargs)
|
||||
|
||||
def show_result(self,
|
||||
result,
|
||||
img,
|
||||
show=False,
|
||||
out_file=None,
|
||||
win_name='',
|
||||
wait_time=0,
|
||||
bbox_color='green',
|
||||
mesh_color=(76, 76, 204),
|
||||
**kwargs):
|
||||
"""Visualize 3D mesh estimation results.
|
||||
|
||||
Args:
|
||||
result (list[dict]): The mesh estimation results containing:
|
||||
|
||||
- "bbox" (ndarray[4]): instance bounding bbox
|
||||
- "center" (ndarray[2]): bbox center
|
||||
- "scale" (ndarray[2]): bbox scale
|
||||
- "keypoints_3d" (ndarray[K,3]): predicted 3D keypoints
|
||||
- "camera" (ndarray[3]): camera parameters
|
||||
- "vertices" (ndarray[V, 3]): predicted 3D vertices
|
||||
- "faces" (ndarray[F, 3]): mesh faces
|
||||
img (str or Tensor): Optional. The image to visualize 2D inputs on.
|
||||
win_name (str): The window name.
|
||||
show (bool): Whether to show the image. Default: False.
|
||||
wait_time (int): Value of waitKey param. Default: 0.
|
||||
out_file (str or None): The filename to write the image.
|
||||
Default: None.
|
||||
bbox_color (str or tuple or :obj:`Color`): Color of bbox lines.
|
||||
mesh_color (str or tuple or :obj:`Color`): Color of mesh surface.
|
||||
|
||||
Returns:
|
||||
ndarray: Visualized img, only if not `show` or `out_file`.
|
||||
"""
|
||||
|
||||
if img is not None:
|
||||
img = mmcv.imread(img)
|
||||
|
||||
focal_length = self.loss_mesh.focal_length
|
||||
H, W, C = img.shape
|
||||
img_center = np.array([[0.5 * W], [0.5 * H]])
|
||||
|
||||
# show bounding boxes
|
||||
bboxes = [res['bbox'] for res in result]
|
||||
bboxes = np.vstack(bboxes)
|
||||
mmcv.imshow_bboxes(
|
||||
img, bboxes, colors=bbox_color, top_k=-1, thickness=2, show=False)
|
||||
|
||||
vertex_list = []
|
||||
face_list = []
|
||||
for res in result:
|
||||
vertices = res['vertices']
|
||||
faces = res['faces']
|
||||
camera = res['camera']
|
||||
camera_center = res['center']
|
||||
scale = res['scale']
|
||||
|
||||
# predicted vertices are in root-relative space,
|
||||
# we need to translate them to camera space.
|
||||
translation = np.array([
|
||||
camera[1], camera[2],
|
||||
2 * focal_length / (scale[0] * 200.0 * camera[0] + 1e-9)
|
||||
])
|
||||
mean_depth = vertices[:, -1].mean() + translation[-1]
|
||||
translation[:2] += (camera_center -
|
||||
img_center[:, 0]) / focal_length * mean_depth
|
||||
vertices += translation[None, :]
|
||||
|
||||
vertex_list.append(vertices)
|
||||
face_list.append(faces)
|
||||
|
||||
# render from front view
|
||||
img_vis = imshow_mesh_3d(
|
||||
img,
|
||||
vertex_list,
|
||||
face_list,
|
||||
img_center, [focal_length, focal_length],
|
||||
colors=mesh_color)
|
||||
|
||||
# render from side view
|
||||
# rotate mesh vertices
|
||||
R = cv2.Rodrigues(np.array([0, np.radians(90.), 0]))[0]
|
||||
rot_vertex_list = [np.dot(vert, R) for vert in vertex_list]
|
||||
|
||||
# get the 3D bbox containing all meshes
|
||||
rot_vertices = np.concatenate(rot_vertex_list, axis=0)
|
||||
min_corner = rot_vertices.min(0)
|
||||
max_corner = rot_vertices.max(0)
|
||||
|
||||
center_3d = 0.5 * (min_corner + max_corner)
|
||||
ratio = 0.8
|
||||
bbox3d_size = max_corner - min_corner
|
||||
|
||||
# set appropriate translation to make all meshes appear in the image
|
||||
z_x = bbox3d_size[0] * focal_length / (ratio * W) - min_corner[2]
|
||||
z_y = bbox3d_size[1] * focal_length / (ratio * H) - min_corner[2]
|
||||
z = max(z_x, z_y)
|
||||
translation = -center_3d
|
||||
translation[2] = z
|
||||
translation = translation[None, :]
|
||||
rot_vertex_list = [
|
||||
rot_vert + translation for rot_vert in rot_vertex_list
|
||||
]
|
||||
|
||||
# render from side view
|
||||
img_side = imshow_mesh_3d(
|
||||
np.ones_like(img) * 255, rot_vertex_list, face_list, img_center,
|
||||
[focal_length, focal_length])
|
||||
|
||||
# merger images from front view and side view
|
||||
img_vis = np.concatenate([img_vis, img_side], axis=1)
|
||||
|
||||
if show:
|
||||
mmcv.visualization.imshow(img_vis, win_name, wait_time)
|
||||
|
||||
if out_file is not None:
|
||||
mmcv.imwrite(img_vis, out_file)
|
||||
|
||||
return img_vis
|
||||
@@ -0,0 +1,187 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch.nn as nn
|
||||
|
||||
from .. import builder
|
||||
from ..builder import POSENETS
|
||||
|
||||
|
||||
@POSENETS.register_module()
|
||||
class MultiTask(nn.Module):
|
||||
"""Multi-task detectors.
|
||||
|
||||
Args:
|
||||
backbone (dict): Backbone modules to extract feature.
|
||||
heads (list[dict]): heads to output predictions.
|
||||
necks (list[dict] | None): necks to process feature.
|
||||
head2neck (dict{int:int}): head index to neck index.
|
||||
pretrained (str): Path to the pretrained models.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
backbone,
|
||||
heads,
|
||||
necks=None,
|
||||
head2neck=None,
|
||||
pretrained=None):
|
||||
super().__init__()
|
||||
|
||||
self.backbone = builder.build_backbone(backbone)
|
||||
|
||||
if head2neck is None:
|
||||
assert necks is None
|
||||
head2neck = {}
|
||||
|
||||
self.head2neck = {}
|
||||
for i in range(len(heads)):
|
||||
self.head2neck[i] = head2neck[i] if i in head2neck else -1
|
||||
|
||||
self.necks = nn.ModuleList([])
|
||||
if necks is not None:
|
||||
for neck in necks:
|
||||
self.necks.append(builder.build_neck(neck))
|
||||
self.necks.append(nn.Identity())
|
||||
|
||||
self.heads = nn.ModuleList([])
|
||||
assert heads is not None
|
||||
for head in heads:
|
||||
assert head is not None
|
||||
self.heads.append(builder.build_head(head))
|
||||
|
||||
self.init_weights(pretrained=pretrained)
|
||||
|
||||
@property
|
||||
def with_necks(self):
|
||||
"""Check if has keypoint_head."""
|
||||
return hasattr(self, 'necks')
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Weight initialization for model."""
|
||||
self.backbone.init_weights(pretrained)
|
||||
if self.with_necks:
|
||||
for neck in self.necks:
|
||||
if hasattr(neck, 'init_weights'):
|
||||
neck.init_weights()
|
||||
|
||||
for head in self.heads:
|
||||
if hasattr(head, 'init_weights'):
|
||||
head.init_weights()
|
||||
|
||||
def forward(self,
|
||||
img,
|
||||
target=None,
|
||||
target_weight=None,
|
||||
img_metas=None,
|
||||
return_loss=True,
|
||||
**kwargs):
|
||||
"""Calls either forward_train or forward_test depending on whether
|
||||
return_loss=True. Note this setting will change the expected inputs.
|
||||
When `return_loss=True`, img and img_meta are single-nested (i.e.
|
||||
Tensor and List[dict]), and when `resturn_loss=False`, img and img_meta
|
||||
should be double nested (i.e. List[Tensor], List[List[dict]]), with
|
||||
the outer list indicating test time augmentations.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- num_img_channel: C (Default: 3)
|
||||
- img height: imgH
|
||||
- img weight: imgW
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
img (torch.Tensor[N,C,imgH,imgW]): Input images.
|
||||
target (list[torch.Tensor]): Targets.
|
||||
target_weight (List[torch.Tensor]): Weights.
|
||||
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
|
||||
return_loss (bool): Option to `return loss`. `return loss=True`
|
||||
for training, `return loss=False` for validation & test.
|
||||
|
||||
Returns:
|
||||
dict|tuple: if `return loss` is true, then return losses. \
|
||||
Otherwise, return predicted poses, boxes, image paths \
|
||||
and heatmaps.
|
||||
"""
|
||||
if return_loss:
|
||||
return self.forward_train(img, target, target_weight, img_metas,
|
||||
**kwargs)
|
||||
return self.forward_test(img, img_metas, **kwargs)
|
||||
|
||||
def forward_train(self, img, target, target_weight, img_metas, **kwargs):
|
||||
"""Defines the computation performed at every call when training."""
|
||||
features = self.backbone(img)
|
||||
outputs = []
|
||||
|
||||
for head_id, head in enumerate(self.heads):
|
||||
neck_id = self.head2neck[head_id]
|
||||
outputs.append(head(self.necks[neck_id](features)))
|
||||
|
||||
# if return loss
|
||||
losses = dict()
|
||||
|
||||
for head, output, gt, gt_weight in zip(self.heads, outputs, target,
|
||||
target_weight):
|
||||
loss = head.get_loss(output, gt, gt_weight)
|
||||
assert len(set(losses.keys()).intersection(set(loss.keys()))) == 0
|
||||
losses.update(loss)
|
||||
|
||||
if hasattr(head, 'get_accuracy'):
|
||||
acc = head.get_accuracy(output, gt, gt_weight)
|
||||
assert len(set(losses.keys()).intersection(set(
|
||||
acc.keys()))) == 0
|
||||
losses.update(acc)
|
||||
|
||||
return losses
|
||||
|
||||
def forward_test(self, img, img_metas, **kwargs):
|
||||
"""Defines the computation performed at every call when testing."""
|
||||
assert img.size(0) == len(img_metas)
|
||||
batch_size, _, img_height, img_width = img.shape
|
||||
if batch_size > 1:
|
||||
assert 'bbox_id' in img_metas[0]
|
||||
|
||||
results = {}
|
||||
|
||||
features = self.backbone(img)
|
||||
outputs = []
|
||||
|
||||
for head_id, head in enumerate(self.heads):
|
||||
neck_id = self.head2neck[head_id]
|
||||
if hasattr(head, 'inference_model'):
|
||||
head_output = head.inference_model(
|
||||
self.necks[neck_id](features), flip_pairs=None)
|
||||
else:
|
||||
head_output = head(
|
||||
self.necks[neck_id](features)).detach().cpu().numpy()
|
||||
outputs.append(head_output)
|
||||
|
||||
for head, output in zip(self.heads, outputs):
|
||||
result = head.decode(
|
||||
img_metas, output, img_size=[img_width, img_height])
|
||||
results.update(result)
|
||||
return results
|
||||
|
||||
def forward_dummy(self, img):
|
||||
"""Used for computing network FLOPs.
|
||||
|
||||
See ``tools/get_flops.py``.
|
||||
|
||||
Args:
|
||||
img (torch.Tensor): Input image.
|
||||
|
||||
Returns:
|
||||
list[Tensor]: Outputs.
|
||||
"""
|
||||
features = self.backbone(img)
|
||||
outputs = []
|
||||
for head_id, head in enumerate(self.heads):
|
||||
neck_id = self.head2neck[head_id]
|
||||
outputs.append(head(self.necks[neck_id](features)))
|
||||
return outputs
|
||||
+889
@@ -0,0 +1,889 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmcv.runner import load_checkpoint
|
||||
|
||||
from mmpose.core.camera import SimpleCameraTorch
|
||||
from mmpose.core.post_processing.post_transforms import (
|
||||
affine_transform_torch, get_affine_transform)
|
||||
from .. import builder
|
||||
from ..builder import POSENETS
|
||||
from .base import BasePose
|
||||
|
||||
|
||||
class ProjectLayer(nn.Module):
|
||||
|
||||
def __init__(self, image_size, heatmap_size):
|
||||
"""Project layer to get voxel feature. Adapted from
|
||||
https://github.com/microsoft/voxelpose-
|
||||
pytorch/blob/main/lib/models/project_layer.py.
|
||||
|
||||
Args:
|
||||
image_size (int or list): input size of the 2D model
|
||||
heatmap_size (int or list): output size of the 2D model
|
||||
"""
|
||||
super(ProjectLayer, self).__init__()
|
||||
self.image_size = image_size
|
||||
self.heatmap_size = heatmap_size
|
||||
if isinstance(self.image_size, int):
|
||||
self.image_size = [self.image_size, self.image_size]
|
||||
if isinstance(self.heatmap_size, int):
|
||||
self.heatmap_size = [self.heatmap_size, self.heatmap_size]
|
||||
|
||||
def compute_grid(self, box_size, box_center, num_bins, device=None):
|
||||
if isinstance(box_size, int) or isinstance(box_size, float):
|
||||
box_size = [box_size, box_size, box_size]
|
||||
if isinstance(num_bins, int):
|
||||
num_bins = [num_bins, num_bins, num_bins]
|
||||
|
||||
grid_1D_x = torch.linspace(
|
||||
-box_size[0] / 2, box_size[0] / 2, num_bins[0], device=device)
|
||||
grid_1D_y = torch.linspace(
|
||||
-box_size[1] / 2, box_size[1] / 2, num_bins[1], device=device)
|
||||
grid_1D_z = torch.linspace(
|
||||
-box_size[2] / 2, box_size[2] / 2, num_bins[2], device=device)
|
||||
grid_x, grid_y, grid_z = torch.meshgrid(
|
||||
grid_1D_x + box_center[0],
|
||||
grid_1D_y + box_center[1],
|
||||
grid_1D_z + box_center[2],
|
||||
)
|
||||
grid_x = grid_x.contiguous().view(-1, 1)
|
||||
grid_y = grid_y.contiguous().view(-1, 1)
|
||||
grid_z = grid_z.contiguous().view(-1, 1)
|
||||
grid = torch.cat([grid_x, grid_y, grid_z], dim=1)
|
||||
|
||||
return grid
|
||||
|
||||
def get_voxel(self, feature_maps, meta, grid_size, grid_center, cube_size):
|
||||
device = feature_maps[0].device
|
||||
batch_size = feature_maps[0].shape[0]
|
||||
num_channels = feature_maps[0].shape[1]
|
||||
num_bins = cube_size[0] * cube_size[1] * cube_size[2]
|
||||
n = len(feature_maps)
|
||||
cubes = torch.zeros(
|
||||
batch_size, num_channels, 1, num_bins, n, device=device)
|
||||
w, h = self.heatmap_size
|
||||
grids = torch.zeros(batch_size, num_bins, 3, device=device)
|
||||
bounding = torch.zeros(batch_size, 1, 1, num_bins, n, device=device)
|
||||
for i in range(batch_size):
|
||||
if len(grid_center[0]) == 3 or grid_center[i][3] >= 0:
|
||||
if len(grid_center) == 1:
|
||||
grid = self.compute_grid(
|
||||
grid_size, grid_center[0], cube_size, device=device)
|
||||
else:
|
||||
grid = self.compute_grid(
|
||||
grid_size, grid_center[i], cube_size, device=device)
|
||||
grids[i:i + 1] = grid
|
||||
for c in range(n):
|
||||
center = meta[i]['center'][c]
|
||||
scale = meta[i]['scale'][c]
|
||||
|
||||
width, height = center * 2
|
||||
trans = torch.as_tensor(
|
||||
get_affine_transform(center, scale / 200.0, 0,
|
||||
self.image_size),
|
||||
dtype=torch.float,
|
||||
device=device)
|
||||
|
||||
cam_param = meta[i]['camera'][c].copy()
|
||||
|
||||
single_view_camera = SimpleCameraTorch(
|
||||
param=cam_param, device=device)
|
||||
xy = single_view_camera.world_to_pixel(grid)
|
||||
|
||||
bounding[i, 0, 0, :, c] = (xy[:, 0] >= 0) & (
|
||||
xy[:, 1] >= 0) & (xy[:, 0] < width) & (
|
||||
xy[:, 1] < height)
|
||||
xy = torch.clamp(xy, -1.0, max(width, height))
|
||||
xy = affine_transform_torch(xy, trans)
|
||||
xy = xy * torch.tensor(
|
||||
[w, h], dtype=torch.float,
|
||||
device=device) / torch.tensor(
|
||||
self.image_size, dtype=torch.float, device=device)
|
||||
sample_grid = xy / torch.tensor([w - 1, h - 1],
|
||||
dtype=torch.float,
|
||||
device=device) * 2.0 - 1.0
|
||||
sample_grid = torch.clamp(
|
||||
sample_grid.view(1, 1, num_bins, 2), -1.1, 1.1)
|
||||
|
||||
cubes[i:i + 1, :, :, :, c] += F.grid_sample(
|
||||
feature_maps[c][i:i + 1, :, :, :],
|
||||
sample_grid,
|
||||
align_corners=True)
|
||||
|
||||
cubes = torch.sum(
|
||||
torch.mul(cubes, bounding), dim=-1) / (
|
||||
torch.sum(bounding, dim=-1) + 1e-6)
|
||||
cubes[cubes != cubes] = 0.0
|
||||
cubes = cubes.clamp(0.0, 1.0)
|
||||
|
||||
cubes = cubes.view(batch_size, num_channels, cube_size[0],
|
||||
cube_size[1], cube_size[2])
|
||||
return cubes, grids
|
||||
|
||||
def forward(self, feature_maps, meta, grid_size, grid_center, cube_size):
|
||||
cubes, grids = self.get_voxel(feature_maps, meta, grid_size,
|
||||
grid_center, cube_size)
|
||||
return cubes, grids
|
||||
|
||||
|
||||
@POSENETS.register_module()
|
||||
class DetectAndRegress(BasePose):
|
||||
"""DetectAndRegress approach for multiview human pose detection.
|
||||
|
||||
Args:
|
||||
backbone (ConfigDict): Dictionary to construct the 2D pose detector
|
||||
human_detector (ConfigDict): dictionary to construct human detector
|
||||
pose_regressor (ConfigDict): dictionary to construct pose regressor
|
||||
train_cfg (ConfigDict): Config for training. Default: None.
|
||||
test_cfg (ConfigDict): Config for testing. Default: None.
|
||||
pretrained (str): Path to the pretrained 2D model. Default: None.
|
||||
freeze_2d (bool): Whether to freeze the 2D model in training.
|
||||
Default: True.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
backbone,
|
||||
human_detector,
|
||||
pose_regressor,
|
||||
train_cfg=None,
|
||||
test_cfg=None,
|
||||
pretrained=None,
|
||||
freeze_2d=True):
|
||||
super(DetectAndRegress, self).__init__()
|
||||
if backbone is not None:
|
||||
self.backbone = builder.build_posenet(backbone)
|
||||
if self.training and pretrained is not None:
|
||||
load_checkpoint(self.backbone, pretrained)
|
||||
else:
|
||||
self.backbone = None
|
||||
|
||||
self.freeze_2d = freeze_2d
|
||||
self.human_detector = builder.MODELS.build(human_detector)
|
||||
self.pose_regressor = builder.MODELS.build(pose_regressor)
|
||||
|
||||
self.train_cfg = train_cfg
|
||||
self.test_cfg = test_cfg
|
||||
|
||||
@staticmethod
|
||||
def _freeze(model):
|
||||
"""Freeze parameters."""
|
||||
model.eval()
|
||||
for param in model.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def train(self, mode=True):
|
||||
"""Sets the module in training mode.
|
||||
Args:
|
||||
mode (bool): whether to set training mode (``True``)
|
||||
or evaluation mode (``False``). Default: ``True``.
|
||||
|
||||
Returns:
|
||||
Module: self
|
||||
"""
|
||||
super().train(mode)
|
||||
if mode and self.freeze_2d and self.backbone is not None:
|
||||
self._freeze(self.backbone)
|
||||
|
||||
return self
|
||||
|
||||
def forward(self,
|
||||
img=None,
|
||||
img_metas=None,
|
||||
return_loss=True,
|
||||
targets=None,
|
||||
masks=None,
|
||||
targets_3d=None,
|
||||
input_heatmaps=None,
|
||||
**kwargs):
|
||||
"""
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
num_img_channel: C
|
||||
img_width: imgW
|
||||
img_height: imgH
|
||||
feature_maps width: W
|
||||
feature_maps height: H
|
||||
volume_length: cubeL
|
||||
volume_width: cubeW
|
||||
volume_height: cubeH
|
||||
|
||||
Args:
|
||||
img (list(torch.Tensor[NxCximgHximgW])):
|
||||
Multi-camera input images to the 2D model.
|
||||
img_metas (list(dict)):
|
||||
Information about image, 3D groundtruth and camera parameters.
|
||||
return_loss: Option to `return loss`. `return loss=True`
|
||||
for training, `return loss=False` for validation & test.
|
||||
targets (list(torch.Tensor[NxKxHxW])):
|
||||
Multi-camera target feature_maps of the 2D model.
|
||||
masks (list(torch.Tensor[NxHxW])):
|
||||
Multi-camera masks of the input to the 2D model.
|
||||
targets_3d (torch.Tensor[NxcubeLxcubeWxcubeH]):
|
||||
Ground-truth 3D heatmap of human centers.
|
||||
input_heatmaps (list(torch.Tensor[NxKxHxW])):
|
||||
Multi-camera feature_maps when the 2D model is not available.
|
||||
Default: None.
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
dict: if 'return_loss' is true, then return losses.
|
||||
Otherwise, return predicted poses, human centers and sample_id
|
||||
|
||||
"""
|
||||
if return_loss:
|
||||
return self.forward_train(img, img_metas, targets, masks,
|
||||
targets_3d, input_heatmaps)
|
||||
else:
|
||||
return self.forward_test(img, img_metas, input_heatmaps)
|
||||
|
||||
def train_step(self, data_batch, optimizer, **kwargs):
|
||||
"""The iteration step during training.
|
||||
|
||||
This method defines an iteration step during training, except for the
|
||||
back propagation and optimizer updating, which are done in an optimizer
|
||||
hook. Note that in some complicated cases or models, the whole process
|
||||
including back propagation and optimizer updating is also defined in
|
||||
this method, such as GAN.
|
||||
|
||||
Args:
|
||||
data_batch (dict): The output of dataloader.
|
||||
optimizer (:obj:`torch.optim.Optimizer` | dict): The optimizer of
|
||||
runner is passed to ``train_step()``. This argument is unused
|
||||
and reserved.
|
||||
|
||||
Returns:
|
||||
dict: It should contain at least 3 keys: ``loss``, ``log_vars``,
|
||||
``num_samples``.
|
||||
``loss`` is a tensor for back propagation, which can be a
|
||||
weighted sum of multiple losses.
|
||||
``log_vars`` contains all the variables to be sent to the
|
||||
logger.
|
||||
``num_samples`` indicates the batch size (when the model is
|
||||
DDP, it means the batch size on each GPU), which is used for
|
||||
averaging the logs.
|
||||
"""
|
||||
losses = self.forward(**data_batch)
|
||||
|
||||
loss, log_vars = self._parse_losses(losses)
|
||||
if 'img' in data_batch:
|
||||
batch_size = data_batch['img'][0].shape[0]
|
||||
else:
|
||||
assert 'input_heatmaps' in data_batch
|
||||
batch_size = data_batch['input_heatmaps'][0][0].shape[0]
|
||||
|
||||
outputs = dict(loss=loss, log_vars=log_vars, num_samples=batch_size)
|
||||
|
||||
return outputs
|
||||
|
||||
def forward_train(self,
|
||||
img,
|
||||
img_metas,
|
||||
targets=None,
|
||||
masks=None,
|
||||
targets_3d=None,
|
||||
input_heatmaps=None):
|
||||
"""
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
num_img_channel: C
|
||||
img_width: imgW
|
||||
img_height: imgH
|
||||
feature_maps width: W
|
||||
feature_maps height: H
|
||||
volume_length: cubeL
|
||||
volume_width: cubeW
|
||||
volume_height: cubeH
|
||||
|
||||
Args:
|
||||
img (list(torch.Tensor[NxCximgHximgW])):
|
||||
Multi-camera input images to the 2D model.
|
||||
img_metas (list(dict)):
|
||||
Information about image, 3D groundtruth and camera parameters.
|
||||
targets (list(torch.Tensor[NxKxHxW])):
|
||||
Multi-camera target feature_maps of the 2D model.
|
||||
masks (list(torch.Tensor[NxHxW])):
|
||||
Multi-camera masks of the input to the 2D model.
|
||||
targets_3d (torch.Tensor[NxcubeLxcubeWxcubeH]):
|
||||
Ground-truth 3D heatmap of human centers.
|
||||
input_heatmaps (list(torch.Tensor[NxKxHxW])):
|
||||
Multi-camera feature_maps when the 2D model is not available.
|
||||
Default: None.
|
||||
|
||||
Returns:
|
||||
dict: losses.
|
||||
|
||||
"""
|
||||
if self.backbone is None:
|
||||
assert input_heatmaps is not None
|
||||
feature_maps = []
|
||||
for input_heatmap in input_heatmaps:
|
||||
feature_maps.append(input_heatmap[0])
|
||||
else:
|
||||
feature_maps = []
|
||||
assert isinstance(img, list)
|
||||
for img_ in img:
|
||||
feature_maps.append(self.backbone.forward_dummy(img_)[0])
|
||||
|
||||
losses = dict()
|
||||
human_candidates, human_loss = self.human_detector.forward_train(
|
||||
None, img_metas, feature_maps, targets_3d, return_preds=True)
|
||||
losses.update(human_loss)
|
||||
|
||||
pose_loss = self.pose_regressor(
|
||||
None,
|
||||
img_metas,
|
||||
return_loss=True,
|
||||
feature_maps=feature_maps,
|
||||
human_candidates=human_candidates)
|
||||
losses.update(pose_loss)
|
||||
|
||||
if not self.freeze_2d:
|
||||
losses_2d = {}
|
||||
heatmaps_tensor = torch.cat(feature_maps, dim=0)
|
||||
targets_tensor = torch.cat(targets, dim=0)
|
||||
masks_tensor = torch.cat(masks, dim=0)
|
||||
losses_2d_ = self.backbone.get_loss(heatmaps_tensor,
|
||||
targets_tensor, masks_tensor)
|
||||
for k, v in losses_2d_.items():
|
||||
losses_2d[k + '_2d'] = v
|
||||
losses.update(losses_2d)
|
||||
|
||||
return losses
|
||||
|
||||
def forward_test(
|
||||
self,
|
||||
img,
|
||||
img_metas,
|
||||
input_heatmaps=None,
|
||||
):
|
||||
"""
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
num_img_channel: C
|
||||
img_width: imgW
|
||||
img_height: imgH
|
||||
feature_maps width: W
|
||||
feature_maps height: H
|
||||
volume_length: cubeL
|
||||
volume_width: cubeW
|
||||
volume_height: cubeH
|
||||
|
||||
Args:
|
||||
img (list(torch.Tensor[NxCximgHximgW])):
|
||||
Multi-camera input images to the 2D model.
|
||||
img_metas (list(dict)):
|
||||
Information about image, 3D groundtruth and camera parameters.
|
||||
input_heatmaps (list(torch.Tensor[NxKxHxW])):
|
||||
Multi-camera feature_maps when the 2D model is not available.
|
||||
Default: None.
|
||||
|
||||
Returns:
|
||||
dict: predicted poses, human centers and sample_id
|
||||
|
||||
"""
|
||||
if self.backbone is None:
|
||||
assert input_heatmaps is not None
|
||||
feature_maps = []
|
||||
for input_heatmap in input_heatmaps:
|
||||
feature_maps.append(input_heatmap[0])
|
||||
else:
|
||||
feature_maps = []
|
||||
assert isinstance(img, list)
|
||||
for img_ in img:
|
||||
feature_maps.append(self.backbone.forward_dummy(img_)[0])
|
||||
|
||||
human_candidates = self.human_detector.forward_test(
|
||||
None, img_metas, feature_maps)
|
||||
|
||||
human_poses = self.pose_regressor(
|
||||
None,
|
||||
img_metas,
|
||||
return_loss=False,
|
||||
feature_maps=feature_maps,
|
||||
human_candidates=human_candidates)
|
||||
|
||||
result = {}
|
||||
result['pose_3d'] = human_poses.cpu().numpy()
|
||||
result['human_detection_3d'] = human_candidates.cpu().numpy()
|
||||
result['sample_id'] = [img_meta['sample_id'] for img_meta in img_metas]
|
||||
|
||||
return result
|
||||
|
||||
def show_result(self, **kwargs):
|
||||
"""Visualize the results."""
|
||||
raise NotImplementedError
|
||||
|
||||
def forward_dummy(self, img, input_heatmaps=None, num_candidates=5):
|
||||
"""Used for computing network FLOPs."""
|
||||
if self.backbone is None:
|
||||
assert input_heatmaps is not None
|
||||
feature_maps = []
|
||||
for input_heatmap in input_heatmaps:
|
||||
feature_maps.append(input_heatmap[0])
|
||||
else:
|
||||
feature_maps = []
|
||||
assert isinstance(img, list)
|
||||
for img_ in img:
|
||||
feature_maps.append(self.backbone.forward_dummy(img_)[0])
|
||||
|
||||
_ = self.human_detector.forward_dummy(feature_maps)
|
||||
|
||||
_ = self.pose_regressor.forward_dummy(feature_maps, num_candidates)
|
||||
|
||||
|
||||
@POSENETS.register_module()
|
||||
class VoxelSinglePose(BasePose):
|
||||
"""VoxelPose Please refer to the `paper <https://arxiv.org/abs/2004.06239>`
|
||||
for details.
|
||||
|
||||
Args:
|
||||
image_size (list): input size of the 2D model.
|
||||
heatmap_size (list): output size of the 2D model.
|
||||
sub_space_size (list): Size of the cuboid human proposal.
|
||||
sub_cube_size (list): Size of the input volume to the pose net.
|
||||
pose_net (ConfigDict): Dictionary to construct the pose net.
|
||||
pose_head (ConfigDict): Dictionary to construct the pose head.
|
||||
train_cfg (ConfigDict): Config for training. Default: None.
|
||||
test_cfg (ConfigDict): Config for testing. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
image_size,
|
||||
heatmap_size,
|
||||
sub_space_size,
|
||||
sub_cube_size,
|
||||
num_joints,
|
||||
pose_net,
|
||||
pose_head,
|
||||
train_cfg=None,
|
||||
test_cfg=None,
|
||||
):
|
||||
super(VoxelSinglePose, self).__init__()
|
||||
self.project_layer = ProjectLayer(image_size, heatmap_size)
|
||||
self.pose_net = builder.build_backbone(pose_net)
|
||||
self.pose_head = builder.build_head(pose_head)
|
||||
|
||||
self.sub_space_size = sub_space_size
|
||||
self.sub_cube_size = sub_cube_size
|
||||
|
||||
self.num_joints = num_joints
|
||||
self.train_cfg = train_cfg
|
||||
self.test_cfg = test_cfg
|
||||
|
||||
def forward(self,
|
||||
img,
|
||||
img_metas,
|
||||
return_loss=True,
|
||||
feature_maps=None,
|
||||
human_candidates=None,
|
||||
**kwargs):
|
||||
"""
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
num_img_channel: C
|
||||
img_width: imgW
|
||||
img_height: imgH
|
||||
feature_maps width: W
|
||||
feature_maps height: H
|
||||
volume_length: cubeL
|
||||
volume_width: cubeW
|
||||
volume_height: cubeH
|
||||
|
||||
Args:
|
||||
img (list(torch.Tensor[NxCximgHximgW])):
|
||||
Multi-camera input images to the 2D model.
|
||||
feature_maps (list(torch.Tensor[NxCxHxW])):
|
||||
Multi-camera input feature_maps.
|
||||
img_metas (list(dict)):
|
||||
Information about image, 3D groundtruth and camera parameters.
|
||||
human_candidates (torch.Tensor[NxPx5]):
|
||||
Human candidates.
|
||||
return_loss: Option to `return loss`. `return loss=True`
|
||||
for training, `return loss=False` for validation & test.
|
||||
|
||||
"""
|
||||
if return_loss:
|
||||
return self.forward_train(img, img_metas, feature_maps,
|
||||
human_candidates)
|
||||
else:
|
||||
return self.forward_test(img, img_metas, feature_maps,
|
||||
human_candidates)
|
||||
|
||||
def forward_train(self,
|
||||
img,
|
||||
img_metas,
|
||||
feature_maps=None,
|
||||
human_candidates=None,
|
||||
return_preds=False,
|
||||
**kwargs):
|
||||
"""Defines the computation performed at training.
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
num_img_channel: C
|
||||
img_width: imgW
|
||||
img_height: imgH
|
||||
feature_maps width: W
|
||||
feature_maps height: H
|
||||
volume_length: cubeL
|
||||
volume_width: cubeW
|
||||
volume_height: cubeH
|
||||
|
||||
Args:
|
||||
img (list(torch.Tensor[NxCximgHximgW])):
|
||||
Multi-camera input images to the 2D model.
|
||||
feature_maps (list(torch.Tensor[NxCxHxW])):
|
||||
Multi-camera input feature_maps.
|
||||
img_metas (list(dict)):
|
||||
Information about image, 3D groundtruth and camera parameters.
|
||||
human_candidates (torch.Tensor[NxPx5]):
|
||||
Human candidates.
|
||||
return_preds (bool): Whether to return prediction results
|
||||
|
||||
Returns:
|
||||
dict: losses.
|
||||
|
||||
"""
|
||||
batch_size, num_candidates, _ = human_candidates.shape
|
||||
pred = human_candidates.new_zeros(batch_size, num_candidates,
|
||||
self.num_joints, 5)
|
||||
pred[:, :, :, 3:] = human_candidates[:, :, None, 3:]
|
||||
|
||||
device = feature_maps[0].device
|
||||
gt_3d = torch.stack([
|
||||
torch.tensor(img_meta['joints_3d'], device=device)
|
||||
for img_meta in img_metas
|
||||
])
|
||||
gt_3d_vis = torch.stack([
|
||||
torch.tensor(img_meta['joints_3d_visible'], device=device)
|
||||
for img_meta in img_metas
|
||||
])
|
||||
valid_preds = []
|
||||
valid_targets = []
|
||||
valid_weights = []
|
||||
|
||||
for n in range(num_candidates):
|
||||
index = pred[:, n, 0, 3] >= 0
|
||||
num_valid = index.sum()
|
||||
if num_valid > 0:
|
||||
pose_input_cube, coordinates \
|
||||
= self.project_layer(feature_maps,
|
||||
img_metas,
|
||||
self.sub_space_size,
|
||||
human_candidates[:, n, :3],
|
||||
self.sub_cube_size)
|
||||
pose_heatmaps_3d = self.pose_net(pose_input_cube)
|
||||
pose_3d = self.pose_head(pose_heatmaps_3d[index],
|
||||
coordinates[index])
|
||||
|
||||
pred[index, n, :, 0:3] = pose_3d.detach()
|
||||
valid_targets.append(gt_3d[index, pred[index, n, 0, 3].long()])
|
||||
valid_weights.append(gt_3d_vis[index, pred[index, n, 0,
|
||||
3].long(), :,
|
||||
0:1].float())
|
||||
valid_preds.append(pose_3d)
|
||||
|
||||
losses = dict()
|
||||
if len(valid_preds) > 0:
|
||||
valid_targets = torch.cat(valid_targets, dim=0)
|
||||
valid_weights = torch.cat(valid_weights, dim=0)
|
||||
valid_preds = torch.cat(valid_preds, dim=0)
|
||||
losses.update(
|
||||
self.pose_head.get_loss(valid_preds, valid_targets,
|
||||
valid_weights))
|
||||
else:
|
||||
pose_input_cube = feature_maps[0].new_zeros(
|
||||
batch_size, self.num_joints, *self.sub_cube_size)
|
||||
coordinates = feature_maps[0].new_zeros(batch_size,
|
||||
*self.sub_cube_size,
|
||||
3).view(batch_size, -1, 3)
|
||||
pseudo_targets = feature_maps[0].new_zeros(batch_size,
|
||||
self.num_joints, 3)
|
||||
pseudo_weights = feature_maps[0].new_zeros(batch_size,
|
||||
self.num_joints, 1)
|
||||
pose_heatmaps_3d = self.pose_net(pose_input_cube)
|
||||
pose_3d = self.pose_head(pose_heatmaps_3d, coordinates)
|
||||
losses.update(
|
||||
self.pose_head.get_loss(pose_3d, pseudo_targets,
|
||||
pseudo_weights))
|
||||
if return_preds:
|
||||
return pred, losses
|
||||
else:
|
||||
return losses
|
||||
|
||||
def forward_test(self,
|
||||
img,
|
||||
img_metas,
|
||||
feature_maps=None,
|
||||
human_candidates=None,
|
||||
**kwargs):
|
||||
"""Defines the computation performed at training.
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
num_img_channel: C
|
||||
img_width: imgW
|
||||
img_height: imgH
|
||||
feature_maps width: W
|
||||
feature_maps height: H
|
||||
volume_length: cubeL
|
||||
volume_width: cubeW
|
||||
volume_height: cubeH
|
||||
|
||||
Args:
|
||||
img (list(torch.Tensor[NxCximgHximgW])):
|
||||
Multi-camera input images to the 2D model.
|
||||
feature_maps (list(torch.Tensor[NxCxHxW])):
|
||||
Multi-camera input feature_maps.
|
||||
img_metas (list(dict)):
|
||||
Information about image, 3D groundtruth and camera parameters.
|
||||
human_candidates (torch.Tensor[NxPx5]):
|
||||
Human candidates.
|
||||
|
||||
Returns:
|
||||
dict: predicted poses, human centers and sample_id
|
||||
|
||||
"""
|
||||
batch_size, num_candidates, _ = human_candidates.shape
|
||||
pred = human_candidates.new_zeros(batch_size, num_candidates,
|
||||
self.num_joints, 5)
|
||||
pred[:, :, :, 3:] = human_candidates[:, :, None, 3:]
|
||||
|
||||
for n in range(num_candidates):
|
||||
index = pred[:, n, 0, 3] >= 0
|
||||
num_valid = index.sum()
|
||||
if num_valid > 0:
|
||||
pose_input_cube, coordinates \
|
||||
= self.project_layer(feature_maps,
|
||||
img_metas,
|
||||
self.sub_space_size,
|
||||
human_candidates[:, n, :3],
|
||||
self.sub_cube_size)
|
||||
pose_heatmaps_3d = self.pose_net(pose_input_cube)
|
||||
pose_3d = self.pose_head(pose_heatmaps_3d[index],
|
||||
coordinates[index])
|
||||
|
||||
pred[index, n, :, 0:3] = pose_3d.detach()
|
||||
|
||||
return pred
|
||||
|
||||
def show_result(self, **kwargs):
|
||||
"""Visualize the results."""
|
||||
raise NotImplementedError
|
||||
|
||||
def forward_dummy(self, feature_maps, num_candidates=5):
|
||||
"""Used for computing network FLOPs."""
|
||||
batch_size, num_channels = feature_maps[0].shape
|
||||
pose_input_cube = feature_maps[0].new_zeros(batch_size, num_channels,
|
||||
*self.sub_cube_size)
|
||||
for n in range(num_candidates):
|
||||
_ = self.pose_net(pose_input_cube)
|
||||
|
||||
|
||||
@POSENETS.register_module()
|
||||
class VoxelCenterDetector(BasePose):
|
||||
"""Detect human center by 3D CNN on voxels.
|
||||
|
||||
Please refer to the
|
||||
`paper <https://arxiv.org/abs/2004.06239>` for details.
|
||||
Args:
|
||||
image_size (list): input size of the 2D model.
|
||||
heatmap_size (list): output size of the 2D model.
|
||||
space_size (list): Size of the 3D space.
|
||||
cube_size (list): Size of the input volume to the 3D CNN.
|
||||
space_center (list): Coordinate of the center of the 3D space.
|
||||
center_net (ConfigDict): Dictionary to construct the center net.
|
||||
center_head (ConfigDict): Dictionary to construct the center head.
|
||||
train_cfg (ConfigDict): Config for training. Default: None.
|
||||
test_cfg (ConfigDict): Config for testing. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
image_size,
|
||||
heatmap_size,
|
||||
space_size,
|
||||
cube_size,
|
||||
space_center,
|
||||
center_net,
|
||||
center_head,
|
||||
train_cfg=None,
|
||||
test_cfg=None,
|
||||
):
|
||||
super(VoxelCenterDetector, self).__init__()
|
||||
self.project_layer = ProjectLayer(image_size, heatmap_size)
|
||||
self.center_net = builder.build_backbone(center_net)
|
||||
self.center_head = builder.build_head(center_head)
|
||||
|
||||
self.space_size = space_size
|
||||
self.cube_size = cube_size
|
||||
self.space_center = space_center
|
||||
|
||||
self.train_cfg = train_cfg
|
||||
self.test_cfg = test_cfg
|
||||
|
||||
def assign2gt(self, center_candidates, gt_centers, gt_num_persons):
|
||||
""""Assign gt id to each valid human center candidate."""
|
||||
det_centers = center_candidates[..., :3]
|
||||
batch_size = center_candidates.shape[0]
|
||||
cand_num = center_candidates.shape[1]
|
||||
cand2gt = torch.zeros(batch_size, cand_num)
|
||||
|
||||
for i in range(batch_size):
|
||||
cand = det_centers[i].view(cand_num, 1, -1)
|
||||
gt = gt_centers[None, i, :gt_num_persons[i]]
|
||||
|
||||
dist = torch.sqrt(torch.sum((cand - gt)**2, dim=-1))
|
||||
min_dist, min_gt = torch.min(dist, dim=-1)
|
||||
|
||||
cand2gt[i] = min_gt
|
||||
cand2gt[i][min_dist > self.train_cfg['dist_threshold']] = -1.0
|
||||
|
||||
center_candidates[:, :, 3] = cand2gt
|
||||
|
||||
return center_candidates
|
||||
|
||||
def forward(self,
|
||||
img,
|
||||
img_metas,
|
||||
return_loss=True,
|
||||
feature_maps=None,
|
||||
targets_3d=None):
|
||||
"""
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
num_img_channel: C
|
||||
img_width: imgW
|
||||
img_height: imgH
|
||||
heatmaps width: W
|
||||
heatmaps height: H
|
||||
Args:
|
||||
img (list(torch.Tensor[NxCximgHximgW])):
|
||||
Multi-camera input images to the 2D model.
|
||||
img_metas (list(dict)):
|
||||
Information about image, 3D groundtruth and camera parameters.
|
||||
return_loss: Option to `return loss`. `return loss=True`
|
||||
for training, `return loss=False` for validation & test.
|
||||
targets_3d (torch.Tensor[NxcubeLxcubeWxcubeH]):
|
||||
Ground-truth 3D heatmap of human centers.
|
||||
feature_maps (list(torch.Tensor[NxKxHxW])):
|
||||
Multi-camera feature_maps.
|
||||
Returns:
|
||||
dict: if 'return_loss' is true, then return losses.
|
||||
Otherwise, return predicted poses
|
||||
"""
|
||||
if return_loss:
|
||||
return self.forward_train(img, img_metas, feature_maps, targets_3d)
|
||||
else:
|
||||
return self.forward_test(img, img_metas, feature_maps)
|
||||
|
||||
def forward_train(self,
|
||||
img,
|
||||
img_metas,
|
||||
feature_maps=None,
|
||||
targets_3d=None,
|
||||
return_preds=False):
|
||||
"""
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
num_img_channel: C
|
||||
img_width: imgW
|
||||
img_height: imgH
|
||||
heatmaps width: W
|
||||
heatmaps height: H
|
||||
Args:
|
||||
img (list(torch.Tensor[NxCximgHximgW])):
|
||||
Multi-camera input images to the 2D model.
|
||||
img_metas (list(dict)):
|
||||
Information about image, 3D groundtruth and camera parameters.
|
||||
targets_3d (torch.Tensor[NxcubeLxcubeWxcubeH]):
|
||||
Ground-truth 3D heatmap of human centers.
|
||||
feature_maps (list(torch.Tensor[NxKxHxW])):
|
||||
Multi-camera feature_maps.
|
||||
return_preds (bool): Whether to return prediction results
|
||||
Returns:
|
||||
dict: if 'return_pred' is true, then return losses
|
||||
and human centers. Otherwise, return losses only
|
||||
"""
|
||||
initial_cubes, _ = self.project_layer(feature_maps, img_metas,
|
||||
self.space_size,
|
||||
[self.space_center],
|
||||
self.cube_size)
|
||||
center_heatmaps_3d = self.center_net(initial_cubes)
|
||||
center_heatmaps_3d = center_heatmaps_3d.squeeze(1)
|
||||
center_candidates = self.center_head(center_heatmaps_3d)
|
||||
|
||||
device = center_candidates.device
|
||||
|
||||
gt_centers = torch.stack([
|
||||
torch.tensor(img_meta['roots_3d'], device=device)
|
||||
for img_meta in img_metas
|
||||
])
|
||||
gt_num_persons = torch.stack([
|
||||
torch.tensor(img_meta['num_persons'], device=device)
|
||||
for img_meta in img_metas
|
||||
])
|
||||
center_candidates = self.assign2gt(center_candidates, gt_centers,
|
||||
gt_num_persons)
|
||||
|
||||
losses = dict()
|
||||
losses.update(
|
||||
self.center_head.get_loss(center_heatmaps_3d, targets_3d))
|
||||
|
||||
if return_preds:
|
||||
return center_candidates, losses
|
||||
else:
|
||||
return losses
|
||||
|
||||
def forward_test(self, img, img_metas, feature_maps=None):
|
||||
"""
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
num_img_channel: C
|
||||
img_width: imgW
|
||||
img_height: imgH
|
||||
heatmaps width: W
|
||||
heatmaps height: H
|
||||
Args:
|
||||
img (list(torch.Tensor[NxCximgHximgW])):
|
||||
Multi-camera input images to the 2D model.
|
||||
img_metas (list(dict)):
|
||||
Information about image, 3D groundtruth and camera parameters.
|
||||
feature_maps (list(torch.Tensor[NxKxHxW])):
|
||||
Multi-camera feature_maps.
|
||||
Returns:
|
||||
human centers
|
||||
"""
|
||||
initial_cubes, _ = self.project_layer(feature_maps, img_metas,
|
||||
self.space_size,
|
||||
[self.space_center],
|
||||
self.cube_size)
|
||||
center_heatmaps_3d = self.center_net(initial_cubes)
|
||||
center_heatmaps_3d = center_heatmaps_3d.squeeze(1)
|
||||
center_candidates = self.center_head(center_heatmaps_3d)
|
||||
center_candidates[..., 3] = \
|
||||
(center_candidates[..., 4] >
|
||||
self.test_cfg['center_threshold']).float() - 1.0
|
||||
|
||||
return center_candidates
|
||||
|
||||
def show_result(self, **kwargs):
|
||||
"""Visualize the results."""
|
||||
raise NotImplementedError
|
||||
|
||||
def forward_dummy(self, feature_maps):
|
||||
"""Used for computing network FLOPs."""
|
||||
batch_size, num_channels, _, _ = feature_maps[0].shape
|
||||
initial_cubes = feature_maps[0].new_zeros(batch_size, num_channels,
|
||||
*self.cube_size)
|
||||
_ = self.center_net(initial_cubes)
|
||||
+392
@@ -0,0 +1,392 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
import mmcv
|
||||
import numpy as np
|
||||
from mmcv.utils.misc import deprecated_api_warning
|
||||
|
||||
from mmpose.core import imshow_bboxes, imshow_keypoints, imshow_keypoints_3d
|
||||
from .. import builder
|
||||
from ..builder import POSENETS
|
||||
from .base import BasePose
|
||||
|
||||
try:
|
||||
from mmcv.runner import auto_fp16
|
||||
except ImportError:
|
||||
warnings.warn('auto_fp16 from mmpose will be deprecated from v0.15.0'
|
||||
'Please install mmcv>=1.1.4')
|
||||
from mmpose.core import auto_fp16
|
||||
|
||||
|
||||
@POSENETS.register_module()
|
||||
class PoseLifter(BasePose):
|
||||
"""Pose lifter that lifts 2D pose to 3D pose.
|
||||
|
||||
The basic model is a pose model that predicts root-relative pose. If
|
||||
traj_head is not None, a trajectory model that predicts absolute root joint
|
||||
position is also built.
|
||||
|
||||
Args:
|
||||
backbone (dict): Config for the backbone of pose model.
|
||||
neck (dict|None): Config for the neck of pose model.
|
||||
keypoint_head (dict|None): Config for the head of pose model.
|
||||
traj_backbone (dict|None): Config for the backbone of trajectory model.
|
||||
If traj_backbone is None and traj_head is not None, trajectory
|
||||
model will share backbone with pose model.
|
||||
traj_neck (dict|None): Config for the neck of trajectory model.
|
||||
traj_head (dict|None): Config for the head of trajectory model.
|
||||
loss_semi (dict|None): Config for semi-supervision loss.
|
||||
train_cfg (dict|None): Config for keypoint head during training.
|
||||
test_cfg (dict|None): Config for keypoint head during testing.
|
||||
pretrained (str|None): Path to pretrained weights.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
backbone,
|
||||
neck=None,
|
||||
keypoint_head=None,
|
||||
traj_backbone=None,
|
||||
traj_neck=None,
|
||||
traj_head=None,
|
||||
loss_semi=None,
|
||||
train_cfg=None,
|
||||
test_cfg=None,
|
||||
pretrained=None):
|
||||
super().__init__()
|
||||
self.fp16_enabled = False
|
||||
|
||||
self.train_cfg = train_cfg
|
||||
self.test_cfg = test_cfg
|
||||
|
||||
# pose model
|
||||
self.backbone = builder.build_backbone(backbone)
|
||||
|
||||
if neck is not None:
|
||||
self.neck = builder.build_neck(neck)
|
||||
|
||||
if keypoint_head is not None:
|
||||
keypoint_head['train_cfg'] = train_cfg
|
||||
keypoint_head['test_cfg'] = test_cfg
|
||||
self.keypoint_head = builder.build_head(keypoint_head)
|
||||
|
||||
# trajectory model
|
||||
if traj_head is not None:
|
||||
self.traj_head = builder.build_head(traj_head)
|
||||
|
||||
if traj_backbone is not None:
|
||||
self.traj_backbone = builder.build_backbone(traj_backbone)
|
||||
else:
|
||||
self.traj_backbone = self.backbone
|
||||
|
||||
if traj_neck is not None:
|
||||
self.traj_neck = builder.build_neck(traj_neck)
|
||||
|
||||
# semi-supervised learning
|
||||
self.semi = loss_semi is not None
|
||||
if self.semi:
|
||||
assert keypoint_head is not None and traj_head is not None
|
||||
self.loss_semi = builder.build_loss(loss_semi)
|
||||
|
||||
self.init_weights(pretrained=pretrained)
|
||||
|
||||
@property
|
||||
def with_neck(self):
|
||||
"""Check if has keypoint_neck."""
|
||||
return hasattr(self, 'neck')
|
||||
|
||||
@property
|
||||
def with_keypoint(self):
|
||||
"""Check if has keypoint_head."""
|
||||
return hasattr(self, 'keypoint_head')
|
||||
|
||||
@property
|
||||
def with_traj_backbone(self):
|
||||
"""Check if has trajectory_backbone."""
|
||||
return hasattr(self, 'traj_backbone')
|
||||
|
||||
@property
|
||||
def with_traj_neck(self):
|
||||
"""Check if has trajectory_neck."""
|
||||
return hasattr(self, 'traj_neck')
|
||||
|
||||
@property
|
||||
def with_traj(self):
|
||||
"""Check if has trajectory_head."""
|
||||
return hasattr(self, 'traj_head')
|
||||
|
||||
@property
|
||||
def causal(self):
|
||||
if hasattr(self.backbone, 'causal'):
|
||||
return self.backbone.causal
|
||||
else:
|
||||
raise AttributeError('A PoseLifter\'s backbone should have '
|
||||
'the bool attribute "causal" to indicate if'
|
||||
'it performs causal inference.')
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Weight initialization for model."""
|
||||
self.backbone.init_weights(pretrained)
|
||||
if self.with_neck:
|
||||
self.neck.init_weights()
|
||||
if self.with_keypoint:
|
||||
self.keypoint_head.init_weights()
|
||||
if self.with_traj_backbone:
|
||||
self.traj_backbone.init_weights(pretrained)
|
||||
if self.with_traj_neck:
|
||||
self.traj_neck.init_weights()
|
||||
if self.with_traj:
|
||||
self.traj_head.init_weights()
|
||||
|
||||
@auto_fp16(apply_to=('input', ))
|
||||
def forward(self,
|
||||
input,
|
||||
target=None,
|
||||
target_weight=None,
|
||||
metas=None,
|
||||
return_loss=True,
|
||||
**kwargs):
|
||||
"""Calls either forward_train or forward_test depending on whether
|
||||
return_loss=True.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_input_keypoints: Ki
|
||||
- input_keypoint_dim: Ci
|
||||
- input_sequence_len: Ti
|
||||
- num_output_keypoints: Ko
|
||||
- output_keypoint_dim: Co
|
||||
- input_sequence_len: To
|
||||
|
||||
Args:
|
||||
input (torch.Tensor[NxKixCixTi]): Input keypoint coordinates.
|
||||
target (torch.Tensor[NxKoxCoxTo]): Output keypoint coordinates.
|
||||
Defaults to None.
|
||||
target_weight (torch.Tensor[NxKox1]): Weights across different
|
||||
joint types. Defaults to None.
|
||||
metas (list(dict)): Information about data augmentation
|
||||
return_loss (bool): Option to `return loss`. `return loss=True`
|
||||
for training, `return loss=False` for validation & test.
|
||||
|
||||
Returns:
|
||||
dict|Tensor: If `reutrn_loss` is true, return losses. \
|
||||
Otherwise return predicted poses.
|
||||
"""
|
||||
if return_loss:
|
||||
return self.forward_train(input, target, target_weight, metas,
|
||||
**kwargs)
|
||||
else:
|
||||
return self.forward_test(input, metas, **kwargs)
|
||||
|
||||
def forward_train(self, input, target, target_weight, metas, **kwargs):
|
||||
"""Defines the computation performed at every call when training."""
|
||||
assert input.size(0) == len(metas)
|
||||
|
||||
# supervised learning
|
||||
# pose model
|
||||
features = self.backbone(input)
|
||||
if self.with_neck:
|
||||
features = self.neck(features)
|
||||
if self.with_keypoint:
|
||||
output = self.keypoint_head(features)
|
||||
|
||||
losses = dict()
|
||||
if self.with_keypoint:
|
||||
keypoint_losses = self.keypoint_head.get_loss(
|
||||
output, target, target_weight)
|
||||
keypoint_accuracy = self.keypoint_head.get_accuracy(
|
||||
output, target, target_weight, metas)
|
||||
losses.update(keypoint_losses)
|
||||
losses.update(keypoint_accuracy)
|
||||
|
||||
# trajectory model
|
||||
if self.with_traj:
|
||||
traj_features = self.traj_backbone(input)
|
||||
if self.with_traj_neck:
|
||||
traj_features = self.traj_neck(traj_features)
|
||||
traj_output = self.traj_head(traj_features)
|
||||
|
||||
traj_losses = self.traj_head.get_loss(traj_output,
|
||||
kwargs['traj_target'], None)
|
||||
losses.update(traj_losses)
|
||||
|
||||
# semi-supervised learning
|
||||
if self.semi:
|
||||
ul_input = kwargs['unlabeled_input']
|
||||
ul_features = self.backbone(ul_input)
|
||||
if self.with_neck:
|
||||
ul_features = self.neck(ul_features)
|
||||
ul_output = self.keypoint_head(ul_features)
|
||||
|
||||
ul_traj_features = self.traj_backbone(ul_input)
|
||||
if self.with_traj_neck:
|
||||
ul_traj_features = self.traj_neck(ul_traj_features)
|
||||
ul_traj_output = self.traj_head(ul_traj_features)
|
||||
|
||||
output_semi = dict(
|
||||
labeled_pose=output,
|
||||
unlabeled_pose=ul_output,
|
||||
unlabeled_traj=ul_traj_output)
|
||||
target_semi = dict(
|
||||
unlabeled_target_2d=kwargs['unlabeled_target_2d'],
|
||||
intrinsics=kwargs['intrinsics'])
|
||||
|
||||
semi_losses = self.loss_semi(output_semi, target_semi)
|
||||
losses.update(semi_losses)
|
||||
|
||||
return losses
|
||||
|
||||
def forward_test(self, input, metas, **kwargs):
|
||||
"""Defines the computation performed at every call when training."""
|
||||
assert input.size(0) == len(metas)
|
||||
|
||||
results = {}
|
||||
|
||||
features = self.backbone(input)
|
||||
if self.with_neck:
|
||||
features = self.neck(features)
|
||||
if self.with_keypoint:
|
||||
output = self.keypoint_head.inference_model(features)
|
||||
keypoint_result = self.keypoint_head.decode(metas, output)
|
||||
results.update(keypoint_result)
|
||||
|
||||
if self.with_traj:
|
||||
traj_features = self.traj_backbone(input)
|
||||
if self.with_traj_neck:
|
||||
traj_features = self.traj_neck(traj_features)
|
||||
traj_output = self.traj_head.inference_model(traj_features)
|
||||
results['traj_preds'] = traj_output
|
||||
|
||||
return results
|
||||
|
||||
def forward_dummy(self, input):
|
||||
"""Used for computing network FLOPs. See ``tools/get_flops.py``.
|
||||
|
||||
Args:
|
||||
input (torch.Tensor): Input pose
|
||||
|
||||
Returns:
|
||||
Tensor: Model output
|
||||
"""
|
||||
output = self.backbone(input)
|
||||
if self.with_neck:
|
||||
output = self.neck(output)
|
||||
if self.with_keypoint:
|
||||
output = self.keypoint_head(output)
|
||||
|
||||
if self.with_traj:
|
||||
traj_features = self.traj_backbone(input)
|
||||
if self.with_neck:
|
||||
traj_features = self.traj_neck(traj_features)
|
||||
traj_output = self.traj_head(traj_features)
|
||||
output = output + traj_output
|
||||
|
||||
return output
|
||||
|
||||
@deprecated_api_warning({'pose_limb_color': 'pose_link_color'},
|
||||
cls_name='PoseLifter')
|
||||
def show_result(self,
|
||||
result,
|
||||
img=None,
|
||||
skeleton=None,
|
||||
pose_kpt_color=None,
|
||||
pose_link_color=None,
|
||||
radius=8,
|
||||
thickness=2,
|
||||
vis_height=400,
|
||||
num_instances=-1,
|
||||
win_name='',
|
||||
show=False,
|
||||
wait_time=0,
|
||||
out_file=None):
|
||||
"""Visualize 3D pose estimation results.
|
||||
|
||||
Args:
|
||||
result (list[dict]): The pose estimation results containing:
|
||||
|
||||
- "keypoints_3d" ([K,4]): 3D keypoints
|
||||
- "keypoints" ([K,3] or [T,K,3]): Optional for visualizing
|
||||
2D inputs. If a sequence is given, only the last frame
|
||||
will be used for visualization
|
||||
- "bbox" ([4,] or [T,4]): Optional for visualizing 2D inputs
|
||||
- "title" (str): title for the subplot
|
||||
img (str or Tensor): Optional. The image to visualize 2D inputs on.
|
||||
skeleton (list of [idx_i,idx_j]): Skeleton described by a list of
|
||||
links, each is a pair of joint indices.
|
||||
pose_kpt_color (np.array[Nx3]`): Color of N keypoints.
|
||||
If None, do not draw keypoints.
|
||||
pose_link_color (np.array[Mx3]): Color of M links.
|
||||
If None, do not draw links.
|
||||
radius (int): Radius of circles.
|
||||
thickness (int): Thickness of lines.
|
||||
vis_height (int): The image height of the visualization. The width
|
||||
will be N*vis_height depending on the number of visualized
|
||||
items.
|
||||
win_name (str): The window name.
|
||||
wait_time (int): Value of waitKey param.
|
||||
Default: 0.
|
||||
out_file (str or None): The filename to write the image.
|
||||
Default: None.
|
||||
|
||||
Returns:
|
||||
Tensor: Visualized img, only if not `show` or `out_file`.
|
||||
"""
|
||||
if num_instances < 0:
|
||||
assert len(result) > 0
|
||||
result = sorted(result, key=lambda x: x.get('track_id', 1e4))
|
||||
|
||||
# draw image and input 2d poses
|
||||
if img is not None:
|
||||
img = mmcv.imread(img)
|
||||
|
||||
bbox_result = []
|
||||
pose_input_2d = []
|
||||
for res in result:
|
||||
if 'bbox' in res:
|
||||
bbox = np.array(res['bbox'])
|
||||
if bbox.ndim != 1:
|
||||
assert bbox.ndim == 2
|
||||
bbox = bbox[-1] # Get bbox from the last frame
|
||||
bbox_result.append(bbox)
|
||||
if 'keypoints' in res:
|
||||
kpts = np.array(res['keypoints'])
|
||||
if kpts.ndim != 2:
|
||||
assert kpts.ndim == 3
|
||||
kpts = kpts[-1] # Get 2D keypoints from the last frame
|
||||
pose_input_2d.append(kpts)
|
||||
|
||||
if len(bbox_result) > 0:
|
||||
bboxes = np.vstack(bbox_result)
|
||||
imshow_bboxes(
|
||||
img,
|
||||
bboxes,
|
||||
colors='green',
|
||||
thickness=thickness,
|
||||
show=False)
|
||||
if len(pose_input_2d) > 0:
|
||||
imshow_keypoints(
|
||||
img,
|
||||
pose_input_2d,
|
||||
skeleton,
|
||||
kpt_score_thr=0.3,
|
||||
pose_kpt_color=pose_kpt_color,
|
||||
pose_link_color=pose_link_color,
|
||||
radius=radius,
|
||||
thickness=thickness)
|
||||
img = mmcv.imrescale(img, scale=vis_height / img.shape[0])
|
||||
|
||||
img_vis = imshow_keypoints_3d(
|
||||
result,
|
||||
img,
|
||||
skeleton,
|
||||
pose_kpt_color,
|
||||
pose_link_color,
|
||||
vis_height,
|
||||
num_instances=num_instances)
|
||||
|
||||
if show:
|
||||
mmcv.visualization.imshow(img_vis, win_name, wait_time)
|
||||
|
||||
if out_file is not None:
|
||||
mmcv.imwrite(img_vis, out_file)
|
||||
|
||||
return img_vis
|
||||
@@ -0,0 +1,244 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from ..builder import POSENETS
|
||||
from .top_down import TopDown
|
||||
|
||||
try:
|
||||
from mmcv.runner import auto_fp16
|
||||
except ImportError:
|
||||
warnings.warn('auto_fp16 from mmpose will be deprecated from v0.15.0'
|
||||
'Please install mmcv>=1.1.4')
|
||||
from mmpose.core import auto_fp16
|
||||
|
||||
|
||||
@POSENETS.register_module()
|
||||
class PoseWarper(TopDown):
|
||||
"""Top-down pose detectors for multi-frame settings for video inputs.
|
||||
|
||||
`"Learning temporal pose estimation from sparsely-labeled videos"
|
||||
<https://arxiv.org/abs/1906.04016>`_.
|
||||
|
||||
A child class of TopDown detector. The main difference between PoseWarper
|
||||
and TopDown lies in that the former takes a list of tensors as input image
|
||||
while the latter takes a single tensor as input image in forward method.
|
||||
|
||||
Args:
|
||||
backbone (dict): Backbone modules to extract features.
|
||||
neck (dict): intermediate modules to transform features.
|
||||
keypoint_head (dict): Keypoint head to process feature.
|
||||
train_cfg (dict): Config for training. Default: None.
|
||||
test_cfg (dict): Config for testing. Default: None.
|
||||
pretrained (str): Path to the pretrained models.
|
||||
loss_pose (None): Deprecated arguments. Please use
|
||||
`loss_keypoint` for heads instead.
|
||||
concat_tensors (bool): Whether to concat the tensors on the batch dim,
|
||||
which can speed up, Default: True
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
backbone,
|
||||
neck=None,
|
||||
keypoint_head=None,
|
||||
train_cfg=None,
|
||||
test_cfg=None,
|
||||
pretrained=None,
|
||||
loss_pose=None,
|
||||
concat_tensors=True):
|
||||
super().__init__(
|
||||
backbone=backbone,
|
||||
neck=neck,
|
||||
keypoint_head=keypoint_head,
|
||||
train_cfg=train_cfg,
|
||||
test_cfg=test_cfg,
|
||||
pretrained=pretrained,
|
||||
loss_pose=loss_pose)
|
||||
self.concat_tensors = concat_tensors
|
||||
|
||||
@auto_fp16(apply_to=('img', ))
|
||||
def forward(self,
|
||||
img,
|
||||
target=None,
|
||||
target_weight=None,
|
||||
img_metas=None,
|
||||
return_loss=True,
|
||||
return_heatmap=False,
|
||||
**kwargs):
|
||||
"""Calls either forward_train or forward_test depending on whether
|
||||
return_loss=True. Note this setting will change the expected inputs.
|
||||
When `return_loss=True`, img and img_meta are single-nested (i.e.
|
||||
Tensor and List[dict]), and when `resturn_loss=False`, img and img_meta
|
||||
should be double nested (i.e. List[Tensor], List[List[dict]]), with
|
||||
the outer list indicating test time augmentations.
|
||||
|
||||
Note:
|
||||
- number of frames: F
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- num_img_channel: C (Default: 3)
|
||||
- img height: imgH
|
||||
- img width: imgW
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
imgs (list[F,torch.Tensor[N,C,imgH,imgW]]): multiple input frames
|
||||
target (torch.Tensor[N,K,H,W]): Target heatmaps for one frame.
|
||||
target_weight (torch.Tensor[N,K,1]): Weights across
|
||||
different joint types.
|
||||
img_metas (list(dict)): Information about data augmentation
|
||||
By default this includes:
|
||||
|
||||
- "image_file: paths to multiple video frames
|
||||
- "center": center of the bbox
|
||||
- "scale": scale of the bbox
|
||||
- "rotation": rotation of the bbox
|
||||
- "bbox_score": score of bbox
|
||||
return_loss (bool): Option to `return loss`. `return loss=True`
|
||||
for training, `return loss=False` for validation & test.
|
||||
return_heatmap (bool) : Option to return heatmap.
|
||||
|
||||
Returns:
|
||||
dict|tuple: if `return loss` is true, then return losses. \
|
||||
Otherwise, return predicted poses, boxes, image paths \
|
||||
and heatmaps.
|
||||
"""
|
||||
if return_loss:
|
||||
return self.forward_train(img, target, target_weight, img_metas,
|
||||
**kwargs)
|
||||
return self.forward_test(
|
||||
img, img_metas, return_heatmap=return_heatmap, **kwargs)
|
||||
|
||||
def forward_train(self, imgs, target, target_weight, img_metas, **kwargs):
|
||||
"""Defines the computation performed at every call when training."""
|
||||
# imgs (list[Fxtorch.Tensor[NxCximgHximgW]]): multiple input frames
|
||||
assert imgs[0].size(0) == len(img_metas)
|
||||
num_frames = len(imgs)
|
||||
frame_weight = img_metas[0]['frame_weight']
|
||||
|
||||
assert num_frames == len(frame_weight), f'The number of frames ' \
|
||||
f'({num_frames}) and the length of weights for each frame ' \
|
||||
f'({len(frame_weight)}) must match'
|
||||
|
||||
if self.concat_tensors:
|
||||
features = [self.backbone(torch.cat(imgs, 0))]
|
||||
else:
|
||||
features = [self.backbone(img) for img in imgs]
|
||||
|
||||
if self.with_neck:
|
||||
features = self.neck(features, frame_weight=frame_weight)
|
||||
|
||||
if self.with_keypoint:
|
||||
output = self.keypoint_head(features)
|
||||
|
||||
# if return loss
|
||||
losses = dict()
|
||||
if self.with_keypoint:
|
||||
keypoint_losses = self.keypoint_head.get_loss(
|
||||
output, target, target_weight)
|
||||
losses.update(keypoint_losses)
|
||||
keypoint_accuracy = self.keypoint_head.get_accuracy(
|
||||
output, target, target_weight)
|
||||
losses.update(keypoint_accuracy)
|
||||
|
||||
return losses
|
||||
|
||||
def forward_test(self, imgs, img_metas, return_heatmap=False, **kwargs):
|
||||
"""Defines the computation performed at every call when testing."""
|
||||
# imgs (list[Fxtorch.Tensor[NxCximgHximgW]]): multiple input frames
|
||||
assert imgs[0].size(0) == len(img_metas)
|
||||
num_frames = len(imgs)
|
||||
frame_weight = img_metas[0]['frame_weight']
|
||||
|
||||
assert num_frames == len(frame_weight), f'The number of frames ' \
|
||||
f'({num_frames}) and the length of weights for each frame ' \
|
||||
f'({len(frame_weight)}) must match'
|
||||
|
||||
batch_size, _, img_height, img_width = imgs[0].shape
|
||||
|
||||
if batch_size > 1:
|
||||
assert 'bbox_id' in img_metas[0]
|
||||
|
||||
result = {}
|
||||
|
||||
if self.concat_tensors:
|
||||
features = [self.backbone(torch.cat(imgs, 0))]
|
||||
else:
|
||||
features = [self.backbone(img) for img in imgs]
|
||||
|
||||
if self.with_neck:
|
||||
features = self.neck(features, frame_weight=frame_weight)
|
||||
|
||||
if self.with_keypoint:
|
||||
output_heatmap = self.keypoint_head.inference_model(
|
||||
features, flip_pairs=None)
|
||||
|
||||
if self.test_cfg.get('flip_test', True):
|
||||
imgs_flipped = [img.flip(3) for img in imgs]
|
||||
|
||||
if self.concat_tensors:
|
||||
features_flipped = [self.backbone(torch.cat(imgs_flipped, 0))]
|
||||
else:
|
||||
features_flipped = [
|
||||
self.backbone(img_flipped) for img_flipped in imgs_flipped
|
||||
]
|
||||
|
||||
if self.with_neck:
|
||||
features_flipped = self.neck(
|
||||
features_flipped, frame_weight=frame_weight)
|
||||
|
||||
if self.with_keypoint:
|
||||
output_flipped_heatmap = self.keypoint_head.inference_model(
|
||||
features_flipped, img_metas[0]['flip_pairs'])
|
||||
output_heatmap = (output_heatmap +
|
||||
output_flipped_heatmap) * 0.5
|
||||
|
||||
if self.with_keypoint:
|
||||
keypoint_result = self.keypoint_head.decode(
|
||||
img_metas, output_heatmap, img_size=[img_width, img_height])
|
||||
result.update(keypoint_result)
|
||||
|
||||
if not return_heatmap:
|
||||
output_heatmap = None
|
||||
|
||||
result['output_heatmap'] = output_heatmap
|
||||
|
||||
return result
|
||||
|
||||
def forward_dummy(self, img):
|
||||
"""Used for computing network FLOPs.
|
||||
|
||||
See ``tools/get_flops.py``.
|
||||
|
||||
Args:
|
||||
img (torch.Tensor[N,C,imgH,imgW], or list|tuple of tensors):
|
||||
multiple input frames, N >= 2.
|
||||
|
||||
Returns:
|
||||
Tensor: Output heatmaps.
|
||||
"""
|
||||
# concat tensors if they are in a list
|
||||
if isinstance(img, (list, tuple)):
|
||||
img = torch.cat(img, 0)
|
||||
|
||||
batch_size = img.size(0)
|
||||
assert batch_size > 1, 'Input batch size to PoseWarper ' \
|
||||
'should be larger than 1.'
|
||||
if batch_size == 2:
|
||||
warnings.warn('Current batch size: 2, for pytorch2onnx and '
|
||||
'getting flops both.')
|
||||
else:
|
||||
warnings.warn(
|
||||
f'Current batch size: {batch_size}, for getting flops only.')
|
||||
|
||||
frame_weight = np.random.uniform(0, 1, batch_size)
|
||||
output = [self.backbone(img)]
|
||||
|
||||
if self.with_neck:
|
||||
output = self.neck(output, frame_weight=frame_weight)
|
||||
if self.with_keypoint:
|
||||
output = self.keypoint_head(output)
|
||||
return output
|
||||
@@ -0,0 +1,307 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
import mmcv
|
||||
import numpy as np
|
||||
from mmcv.image import imwrite
|
||||
from mmcv.utils.misc import deprecated_api_warning
|
||||
from mmcv.visualization.image import imshow
|
||||
|
||||
from mmpose.core import imshow_bboxes, imshow_keypoints
|
||||
from .. import builder
|
||||
from ..builder import POSENETS
|
||||
from .base import BasePose
|
||||
|
||||
try:
|
||||
from mmcv.runner import auto_fp16
|
||||
except ImportError:
|
||||
warnings.warn('auto_fp16 from mmpose will be deprecated from v0.15.0'
|
||||
'Please install mmcv>=1.1.4')
|
||||
from mmpose.core import auto_fp16
|
||||
|
||||
|
||||
@POSENETS.register_module()
|
||||
class TopDown(BasePose):
|
||||
"""Top-down pose detectors.
|
||||
|
||||
Args:
|
||||
backbone (dict): Backbone modules to extract feature.
|
||||
keypoint_head (dict): Keypoint head to process feature.
|
||||
train_cfg (dict): Config for training. Default: None.
|
||||
test_cfg (dict): Config for testing. Default: None.
|
||||
pretrained (str): Path to the pretrained models.
|
||||
loss_pose (None): Deprecated arguments. Please use
|
||||
`loss_keypoint` for heads instead.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
backbone,
|
||||
neck=None,
|
||||
keypoint_head=None,
|
||||
train_cfg=None,
|
||||
test_cfg=None,
|
||||
pretrained=None,
|
||||
loss_pose=None):
|
||||
super().__init__()
|
||||
self.fp16_enabled = False
|
||||
|
||||
self.backbone = builder.build_backbone(backbone)
|
||||
|
||||
self.train_cfg = train_cfg
|
||||
self.test_cfg = test_cfg
|
||||
|
||||
if neck is not None:
|
||||
self.neck = builder.build_neck(neck)
|
||||
|
||||
if keypoint_head is not None:
|
||||
keypoint_head['train_cfg'] = train_cfg
|
||||
keypoint_head['test_cfg'] = test_cfg
|
||||
|
||||
if 'loss_keypoint' not in keypoint_head and loss_pose is not None:
|
||||
warnings.warn(
|
||||
'`loss_pose` for TopDown is deprecated, '
|
||||
'use `loss_keypoint` for heads instead. See '
|
||||
'https://github.com/open-mmlab/mmpose/pull/382'
|
||||
' for more information.', DeprecationWarning)
|
||||
keypoint_head['loss_keypoint'] = loss_pose
|
||||
|
||||
self.keypoint_head = builder.build_head(keypoint_head)
|
||||
|
||||
self.init_weights(pretrained=pretrained)
|
||||
|
||||
@property
|
||||
def with_neck(self):
|
||||
"""Check if has neck."""
|
||||
return hasattr(self, 'neck')
|
||||
|
||||
@property
|
||||
def with_keypoint(self):
|
||||
"""Check if has keypoint_head."""
|
||||
return hasattr(self, 'keypoint_head')
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Weight initialization for model."""
|
||||
self.backbone.init_weights(pretrained)
|
||||
if self.with_neck:
|
||||
self.neck.init_weights()
|
||||
if self.with_keypoint:
|
||||
self.keypoint_head.init_weights()
|
||||
|
||||
@auto_fp16(apply_to=('img', ))
|
||||
def forward(self,
|
||||
img,
|
||||
target=None,
|
||||
target_weight=None,
|
||||
img_metas=None,
|
||||
return_loss=True,
|
||||
return_heatmap=False,
|
||||
**kwargs):
|
||||
"""Calls either forward_train or forward_test depending on whether
|
||||
return_loss=True. Note this setting will change the expected inputs.
|
||||
When `return_loss=True`, img and img_meta are single-nested (i.e.
|
||||
Tensor and List[dict]), and when `resturn_loss=False`, img and img_meta
|
||||
should be double nested (i.e. List[Tensor], List[List[dict]]), with
|
||||
the outer list indicating test time augmentations.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- num_img_channel: C (Default: 3)
|
||||
- img height: imgH
|
||||
- img width: imgW
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
img (torch.Tensor[NxCximgHximgW]): Input images.
|
||||
target (torch.Tensor[NxKxHxW]): Target heatmaps.
|
||||
target_weight (torch.Tensor[NxKx1]): Weights across
|
||||
different joint types.
|
||||
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
|
||||
return_loss (bool): Option to `return loss`. `return loss=True`
|
||||
for training, `return loss=False` for validation & test.
|
||||
return_heatmap (bool) : Option to return heatmap.
|
||||
|
||||
Returns:
|
||||
dict|tuple: if `return loss` is true, then return losses. \
|
||||
Otherwise, return predicted poses, boxes, image paths \
|
||||
and heatmaps.
|
||||
"""
|
||||
if return_loss:
|
||||
return self.forward_train(img, target, target_weight, img_metas,
|
||||
**kwargs)
|
||||
return self.forward_test(
|
||||
img, img_metas, return_heatmap=return_heatmap, **kwargs)
|
||||
|
||||
def forward_train(self, img, target, target_weight, img_metas, **kwargs):
|
||||
"""Defines the computation performed at every call when training."""
|
||||
output = self.backbone(img)
|
||||
if self.with_neck:
|
||||
output = self.neck(output)
|
||||
if self.with_keypoint:
|
||||
output = self.keypoint_head(output)
|
||||
|
||||
# if return loss
|
||||
losses = dict()
|
||||
if self.with_keypoint:
|
||||
keypoint_losses = self.keypoint_head.get_loss(
|
||||
output, target, target_weight)
|
||||
losses.update(keypoint_losses)
|
||||
keypoint_accuracy = self.keypoint_head.get_accuracy(
|
||||
output, target, target_weight)
|
||||
losses.update(keypoint_accuracy)
|
||||
|
||||
return losses
|
||||
|
||||
def forward_test(self, img, img_metas, return_heatmap=False, **kwargs):
|
||||
"""Defines the computation performed at every call when testing."""
|
||||
assert img.size(0) == len(img_metas)
|
||||
batch_size, _, img_height, img_width = img.shape
|
||||
if batch_size > 1:
|
||||
assert 'bbox_id' in img_metas[0]
|
||||
|
||||
result = {}
|
||||
|
||||
features = self.backbone(img)
|
||||
if self.with_neck:
|
||||
features = self.neck(features)
|
||||
if self.with_keypoint:
|
||||
output_heatmap = self.keypoint_head.inference_model(
|
||||
features, flip_pairs=None)
|
||||
|
||||
if self.test_cfg.get('flip_test', True):
|
||||
img_flipped = img.flip(3)
|
||||
features_flipped = self.backbone(img_flipped)
|
||||
if self.with_neck:
|
||||
features_flipped = self.neck(features_flipped)
|
||||
if self.with_keypoint:
|
||||
output_flipped_heatmap = self.keypoint_head.inference_model(
|
||||
features_flipped, img_metas[0]['flip_pairs'])
|
||||
output_heatmap = (output_heatmap +
|
||||
output_flipped_heatmap) * 0.5
|
||||
|
||||
if self.with_keypoint:
|
||||
keypoint_result = self.keypoint_head.decode(
|
||||
img_metas, output_heatmap, img_size=[img_width, img_height])
|
||||
result.update(keypoint_result)
|
||||
|
||||
if not return_heatmap:
|
||||
output_heatmap = None
|
||||
|
||||
result['output_heatmap'] = output_heatmap
|
||||
|
||||
return result
|
||||
|
||||
def forward_dummy(self, img):
|
||||
"""Used for computing network FLOPs.
|
||||
|
||||
See ``tools/get_flops.py``.
|
||||
|
||||
Args:
|
||||
img (torch.Tensor): Input image.
|
||||
|
||||
Returns:
|
||||
Tensor: Output heatmaps.
|
||||
"""
|
||||
output = self.backbone(img)
|
||||
if self.with_neck:
|
||||
output = self.neck(output)
|
||||
if self.with_keypoint:
|
||||
output = self.keypoint_head(output)
|
||||
return output
|
||||
|
||||
@deprecated_api_warning({'pose_limb_color': 'pose_link_color'},
|
||||
cls_name='TopDown')
|
||||
def show_result(self,
|
||||
img,
|
||||
result,
|
||||
skeleton=None,
|
||||
kpt_score_thr=0.3,
|
||||
bbox_color='green',
|
||||
pose_kpt_color=None,
|
||||
pose_link_color=None,
|
||||
text_color='white',
|
||||
radius=4,
|
||||
thickness=1,
|
||||
font_scale=0.5,
|
||||
bbox_thickness=1,
|
||||
win_name='',
|
||||
show=False,
|
||||
show_keypoint_weight=False,
|
||||
wait_time=0,
|
||||
out_file=None):
|
||||
"""Draw `result` over `img`.
|
||||
|
||||
Args:
|
||||
img (str or Tensor): The image to be displayed.
|
||||
result (list[dict]): The results to draw over `img`
|
||||
(bbox_result, pose_result).
|
||||
skeleton (list[list]): The connection of keypoints.
|
||||
skeleton is 0-based indexing.
|
||||
kpt_score_thr (float, optional): Minimum score of keypoints
|
||||
to be shown. Default: 0.3.
|
||||
bbox_color (str or tuple or :obj:`Color`): Color of bbox lines.
|
||||
pose_kpt_color (np.array[Nx3]`): Color of N keypoints.
|
||||
If None, do not draw keypoints.
|
||||
pose_link_color (np.array[Mx3]): Color of M links.
|
||||
If None, do not draw links.
|
||||
text_color (str or tuple or :obj:`Color`): Color of texts.
|
||||
radius (int): Radius of circles.
|
||||
thickness (int): Thickness of lines.
|
||||
font_scale (float): Font scales of texts.
|
||||
win_name (str): The window name.
|
||||
show (bool): Whether to show the image. Default: False.
|
||||
show_keypoint_weight (bool): Whether to change the transparency
|
||||
using the predicted confidence scores of keypoints.
|
||||
wait_time (int): Value of waitKey param.
|
||||
Default: 0.
|
||||
out_file (str or None): The filename to write the image.
|
||||
Default: None.
|
||||
|
||||
Returns:
|
||||
Tensor: Visualized img, only if not `show` or `out_file`.
|
||||
"""
|
||||
img = mmcv.imread(img)
|
||||
img = img.copy()
|
||||
|
||||
bbox_result = []
|
||||
bbox_labels = []
|
||||
pose_result = []
|
||||
for res in result:
|
||||
if 'bbox' in res:
|
||||
bbox_result.append(res['bbox'])
|
||||
bbox_labels.append(res.get('label', None))
|
||||
pose_result.append(res['keypoints'])
|
||||
|
||||
if bbox_result:
|
||||
bboxes = np.vstack(bbox_result)
|
||||
# draw bounding boxes
|
||||
imshow_bboxes(
|
||||
img,
|
||||
bboxes,
|
||||
labels=bbox_labels,
|
||||
colors=bbox_color,
|
||||
text_color=text_color,
|
||||
thickness=bbox_thickness,
|
||||
font_scale=font_scale,
|
||||
show=False)
|
||||
|
||||
if pose_result:
|
||||
imshow_keypoints(img, pose_result, skeleton, kpt_score_thr,
|
||||
pose_kpt_color, pose_link_color, radius,
|
||||
thickness)
|
||||
|
||||
if show:
|
||||
imshow(img, win_name, wait_time)
|
||||
|
||||
if out_file is not None:
|
||||
imwrite(img, out_file)
|
||||
|
||||
return img
|
||||
+351
@@ -0,0 +1,351 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
import mmcv
|
||||
import numpy as np
|
||||
from mmcv.image import imwrite
|
||||
from mmcv.utils.misc import deprecated_api_warning
|
||||
from mmcv.visualization.image import imshow
|
||||
|
||||
from mmpose.core import imshow_bboxes, imshow_keypoints
|
||||
from .. import builder
|
||||
from ..builder import POSENETS
|
||||
from .base import BasePose
|
||||
|
||||
try:
|
||||
from mmcv.runner import auto_fp16
|
||||
except ImportError:
|
||||
warnings.warn('auto_fp16 from mmpose will be deprecated from v0.15.0'
|
||||
'Please install mmcv>=1.1.4')
|
||||
from mmpose.core import auto_fp16
|
||||
|
||||
|
||||
@POSENETS.register_module()
|
||||
class TopDownMoE(BasePose):
|
||||
"""Top-down pose detectors.
|
||||
|
||||
Args:
|
||||
backbone (dict): Backbone modules to extract feature.
|
||||
keypoint_head (dict): Keypoint head to process feature.
|
||||
train_cfg (dict): Config for training. Default: None.
|
||||
test_cfg (dict): Config for testing. Default: None.
|
||||
pretrained (str): Path to the pretrained models.
|
||||
loss_pose (None): Deprecated arguments. Please use
|
||||
`loss_keypoint` for heads instead.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
backbone,
|
||||
neck=None,
|
||||
keypoint_head=None,
|
||||
associate_keypoint_head=None,
|
||||
train_cfg=None,
|
||||
test_cfg=None,
|
||||
pretrained=None,
|
||||
loss_pose=None):
|
||||
super().__init__()
|
||||
self.fp16_enabled = False
|
||||
|
||||
self.backbone = builder.build_backbone(backbone)
|
||||
|
||||
self.train_cfg = train_cfg
|
||||
self.test_cfg = test_cfg
|
||||
|
||||
if neck is not None:
|
||||
self.neck = builder.build_neck(neck)
|
||||
|
||||
if keypoint_head is not None:
|
||||
keypoint_head['train_cfg'] = train_cfg
|
||||
keypoint_head['test_cfg'] = test_cfg
|
||||
|
||||
if 'loss_keypoint' not in keypoint_head and loss_pose is not None:
|
||||
warnings.warn(
|
||||
'`loss_pose` for TopDown is deprecated, '
|
||||
'use `loss_keypoint` for heads instead. See '
|
||||
'https://github.com/open-mmlab/mmpose/pull/382'
|
||||
' for more information.', DeprecationWarning)
|
||||
keypoint_head['loss_keypoint'] = loss_pose
|
||||
|
||||
self.keypoint_head = builder.build_head(keypoint_head)
|
||||
|
||||
|
||||
associate_keypoint_heads = []
|
||||
keypoint_heads_cnt = 1
|
||||
|
||||
if associate_keypoint_head is not None:
|
||||
if not isinstance(associate_keypoint_head, list):
|
||||
associate_keypoint_head = [associate_keypoint_head]
|
||||
for single_keypoint_head in associate_keypoint_head:
|
||||
single_keypoint_head['train_cfg'] = train_cfg
|
||||
single_keypoint_head['test_cfg'] = test_cfg
|
||||
associate_keypoint_heads.append(builder.build_head(single_keypoint_head))
|
||||
keypoint_heads_cnt += 1
|
||||
|
||||
self.associate_keypoint_heads = nn.ModuleList(associate_keypoint_heads)
|
||||
|
||||
self.keypoint_heads_cnt = keypoint_heads_cnt
|
||||
|
||||
self.init_weights(pretrained=pretrained)
|
||||
|
||||
@property
|
||||
def with_neck(self):
|
||||
"""Check if has neck."""
|
||||
return hasattr(self, 'neck')
|
||||
|
||||
@property
|
||||
def with_keypoint(self):
|
||||
"""Check if has keypoint_head."""
|
||||
return hasattr(self, 'keypoint_head')
|
||||
|
||||
def init_weights(self, pretrained=None):
|
||||
"""Weight initialization for model."""
|
||||
self.backbone.init_weights(pretrained)
|
||||
if self.with_neck:
|
||||
self.neck.init_weights()
|
||||
if self.with_keypoint:
|
||||
self.keypoint_head.init_weights()
|
||||
for item in self.associate_keypoint_heads:
|
||||
item.init_weights()
|
||||
|
||||
@auto_fp16(apply_to=('img', ))
|
||||
def forward(self,
|
||||
img,
|
||||
target=None,
|
||||
target_weight=None,
|
||||
img_metas=None,
|
||||
return_loss=True,
|
||||
return_heatmap=False,
|
||||
**kwargs):
|
||||
"""Calls either forward_train or forward_test depending on whether
|
||||
return_loss=True. Note this setting will change the expected inputs.
|
||||
When `return_loss=True`, img and img_meta are single-nested (i.e.
|
||||
Tensor and List[dict]), and when `resturn_loss=False`, img and img_meta
|
||||
should be double nested (i.e. List[Tensor], List[List[dict]]), with
|
||||
the outer list indicating test time augmentations.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- num_img_channel: C (Default: 3)
|
||||
- img height: imgH
|
||||
- img width: imgW
|
||||
- heatmaps height: H
|
||||
- heatmaps weight: W
|
||||
|
||||
Args:
|
||||
img (torch.Tensor[NxCximgHximgW]): Input images.
|
||||
target (torch.Tensor[NxKxHxW]): Target heatmaps.
|
||||
target_weight (torch.Tensor[NxKx1]): Weights across
|
||||
different joint types.
|
||||
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
|
||||
return_loss (bool): Option to `return loss`. `return loss=True`
|
||||
for training, `return loss=False` for validation & test.
|
||||
return_heatmap (bool) : Option to return heatmap.
|
||||
|
||||
Returns:
|
||||
dict|tuple: if `return loss` is true, then return losses. \
|
||||
Otherwise, return predicted poses, boxes, image paths \
|
||||
and heatmaps.
|
||||
"""
|
||||
if return_loss:
|
||||
return self.forward_train(img, target, target_weight, img_metas,
|
||||
**kwargs)
|
||||
return self.forward_test(
|
||||
img, img_metas, return_heatmap=return_heatmap, **kwargs)
|
||||
|
||||
def forward_train(self, img, target, target_weight, img_metas, **kwargs):
|
||||
"""Defines the computation performed at every call when training."""
|
||||
|
||||
img_sources = torch.from_numpy(np.array([ele['dataset_idx'] for ele in img_metas])).to(img.device)
|
||||
|
||||
output = self.backbone(img, img_sources)
|
||||
if self.with_neck:
|
||||
output = self.neck(output)
|
||||
# if return loss
|
||||
losses = dict()
|
||||
|
||||
main_stream_select = (img_sources == 0)
|
||||
# if torch.sum(main_stream_select) > 0:
|
||||
output_select = self.keypoint_head(output)
|
||||
|
||||
target_select = target * main_stream_select.view(-1, 1, 1, 1)
|
||||
target_weight_select = target_weight * main_stream_select.view(-1, 1, 1)
|
||||
|
||||
keypoint_losses = self.keypoint_head.get_loss(
|
||||
output_select, target_select, target_weight_select)
|
||||
losses['main_stream_loss'] = keypoint_losses['heatmap_loss']
|
||||
keypoint_accuracy = self.keypoint_head.get_accuracy(
|
||||
output_select, target_select, target_weight_select)
|
||||
losses['main_stream_acc'] = keypoint_accuracy['acc_pose']
|
||||
|
||||
for idx in range(1, self.keypoint_heads_cnt):
|
||||
idx_select = (img_sources == idx)
|
||||
target_select = target * idx_select.view(-1, 1, 1, 1)
|
||||
target_weight_select = target_weight * idx_select.view(-1, 1, 1)
|
||||
output_select = self.associate_keypoint_heads[idx - 1](output)
|
||||
keypoint_losses = self.associate_keypoint_heads[idx - 1].get_loss(
|
||||
output_select, target_select, target_weight_select)
|
||||
losses[f'{idx}_loss'] = keypoint_losses['heatmap_loss']
|
||||
keypoint_accuracy = self.associate_keypoint_heads[idx - 1].get_accuracy(
|
||||
output_select, target_select, target_weight_select)
|
||||
losses[f'{idx}_acc'] = keypoint_accuracy['acc_pose']
|
||||
|
||||
return losses
|
||||
|
||||
def forward_test(self, img, img_metas, return_heatmap=False, **kwargs):
|
||||
"""Defines the computation performed at every call when testing."""
|
||||
assert img.size(0) == len(img_metas)
|
||||
batch_size, _, img_height, img_width = img.shape
|
||||
if batch_size > 1:
|
||||
assert 'bbox_id' in img_metas[0]
|
||||
|
||||
result = {}
|
||||
img_sources = torch.from_numpy(np.array([ele['dataset_idx'] for ele in img_metas])).to(img.device)
|
||||
|
||||
features = self.backbone(img, img_sources)
|
||||
|
||||
if self.with_neck:
|
||||
features = self.neck(features)
|
||||
if self.with_keypoint:
|
||||
output_heatmap = self.keypoint_head.inference_model(
|
||||
features, flip_pairs=None)
|
||||
|
||||
if self.test_cfg.get('flip_test', True):
|
||||
img_flipped = img.flip(3)
|
||||
features_flipped = self.backbone(img_flipped, img_sources)
|
||||
if self.with_neck:
|
||||
features_flipped = self.neck(features_flipped)
|
||||
if self.with_keypoint:
|
||||
output_flipped_heatmap = self.keypoint_head.inference_model(
|
||||
features_flipped, img_metas[0]['flip_pairs'])
|
||||
output_heatmap = (output_heatmap +
|
||||
output_flipped_heatmap) * 0.5
|
||||
|
||||
if self.with_keypoint:
|
||||
keypoint_result = self.keypoint_head.decode(
|
||||
img_metas, output_heatmap, img_size=[img_width, img_height])
|
||||
result.update(keypoint_result)
|
||||
|
||||
if not return_heatmap:
|
||||
output_heatmap = None
|
||||
|
||||
result['output_heatmap'] = output_heatmap
|
||||
|
||||
return result
|
||||
|
||||
def forward_dummy(self, img):
|
||||
"""Used for computing network FLOPs.
|
||||
|
||||
See ``tools/get_flops.py``.
|
||||
|
||||
Args:
|
||||
img (torch.Tensor): Input image.
|
||||
|
||||
Returns:
|
||||
Tensor: Output heatmaps.
|
||||
"""
|
||||
output = self.backbone(img)
|
||||
if self.with_neck:
|
||||
output = self.neck(output)
|
||||
if self.with_keypoint:
|
||||
output = self.keypoint_head(output)
|
||||
return output
|
||||
|
||||
@deprecated_api_warning({'pose_limb_color': 'pose_link_color'},
|
||||
cls_name='TopDown')
|
||||
def show_result(self,
|
||||
img,
|
||||
result,
|
||||
skeleton=None,
|
||||
kpt_score_thr=0.3,
|
||||
bbox_color='green',
|
||||
pose_kpt_color=None,
|
||||
pose_link_color=None,
|
||||
text_color='white',
|
||||
radius=4,
|
||||
thickness=1,
|
||||
font_scale=0.5,
|
||||
bbox_thickness=1,
|
||||
win_name='',
|
||||
show=False,
|
||||
show_keypoint_weight=False,
|
||||
wait_time=0,
|
||||
out_file=None):
|
||||
"""Draw `result` over `img`.
|
||||
|
||||
Args:
|
||||
img (str or Tensor): The image to be displayed.
|
||||
result (list[dict]): The results to draw over `img`
|
||||
(bbox_result, pose_result).
|
||||
skeleton (list[list]): The connection of keypoints.
|
||||
skeleton is 0-based indexing.
|
||||
kpt_score_thr (float, optional): Minimum score of keypoints
|
||||
to be shown. Default: 0.3.
|
||||
bbox_color (str or tuple or :obj:`Color`): Color of bbox lines.
|
||||
pose_kpt_color (np.array[Nx3]`): Color of N keypoints.
|
||||
If None, do not draw keypoints.
|
||||
pose_link_color (np.array[Mx3]): Color of M links.
|
||||
If None, do not draw links.
|
||||
text_color (str or tuple or :obj:`Color`): Color of texts.
|
||||
radius (int): Radius of circles.
|
||||
thickness (int): Thickness of lines.
|
||||
font_scale (float): Font scales of texts.
|
||||
win_name (str): The window name.
|
||||
show (bool): Whether to show the image. Default: False.
|
||||
show_keypoint_weight (bool): Whether to change the transparency
|
||||
using the predicted confidence scores of keypoints.
|
||||
wait_time (int): Value of waitKey param.
|
||||
Default: 0.
|
||||
out_file (str or None): The filename to write the image.
|
||||
Default: None.
|
||||
|
||||
Returns:
|
||||
Tensor: Visualized img, only if not `show` or `out_file`.
|
||||
"""
|
||||
img = mmcv.imread(img)
|
||||
img = img.copy()
|
||||
|
||||
bbox_result = []
|
||||
bbox_labels = []
|
||||
pose_result = []
|
||||
for res in result:
|
||||
if 'bbox' in res:
|
||||
bbox_result.append(res['bbox'])
|
||||
bbox_labels.append(res.get('label', None))
|
||||
pose_result.append(res['keypoints'])
|
||||
|
||||
if bbox_result:
|
||||
bboxes = np.vstack(bbox_result)
|
||||
# draw bounding boxes
|
||||
imshow_bboxes(
|
||||
img,
|
||||
bboxes,
|
||||
labels=bbox_labels,
|
||||
colors=bbox_color,
|
||||
text_color=text_color,
|
||||
thickness=bbox_thickness,
|
||||
font_scale=font_scale,
|
||||
show=False)
|
||||
|
||||
if pose_result:
|
||||
imshow_keypoints(img, pose_result, skeleton, kpt_score_thr,
|
||||
pose_kpt_color, pose_link_color, radius,
|
||||
thickness)
|
||||
|
||||
if show:
|
||||
imshow(img, win_name, wait_time)
|
||||
|
||||
if out_file is not None:
|
||||
imwrite(img, out_file)
|
||||
|
||||
return img
|
||||
@@ -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))
|
||||
@@ -0,0 +1,16 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .classfication_loss import BCELoss
|
||||
from .heatmap_loss import AdaptiveWingLoss
|
||||
from .mesh_loss import GANLoss, MeshLoss
|
||||
from .mse_loss import JointsMSELoss, JointsOHKMMSELoss
|
||||
from .multi_loss_factory import AELoss, HeatmapLoss, MultiLossFactory
|
||||
from .regression_loss import (BoneLoss, L1Loss, MPJPELoss, MSELoss,
|
||||
SemiSupervisionLoss, SmoothL1Loss, SoftWingLoss,
|
||||
WingLoss)
|
||||
|
||||
__all__ = [
|
||||
'JointsMSELoss', 'JointsOHKMMSELoss', 'HeatmapLoss', 'AELoss',
|
||||
'MultiLossFactory', 'MeshLoss', 'GANLoss', 'SmoothL1Loss', 'WingLoss',
|
||||
'MPJPELoss', 'MSELoss', 'L1Loss', 'BCELoss', 'BoneLoss',
|
||||
'SemiSupervisionLoss', 'SoftWingLoss', 'AdaptiveWingLoss'
|
||||
]
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from ..builder import LOSSES
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class BCELoss(nn.Module):
|
||||
"""Binary Cross Entropy loss."""
|
||||
|
||||
def __init__(self, use_target_weight=False, loss_weight=1.):
|
||||
super().__init__()
|
||||
self.criterion = F.binary_cross_entropy
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def forward(self, output, target, target_weight=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_labels: K
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K]): Output classification.
|
||||
target (torch.Tensor[N, K]): Target classification.
|
||||
target_weight (torch.Tensor[N, K] or torch.Tensor[N]):
|
||||
Weights across different labels.
|
||||
"""
|
||||
|
||||
if self.use_target_weight:
|
||||
assert target_weight is not None
|
||||
loss = self.criterion(output, target, reduction='none')
|
||||
if target_weight.dim() == 1:
|
||||
target_weight = target_weight[:, None]
|
||||
loss = (loss * target_weight).mean()
|
||||
else:
|
||||
loss = self.criterion(output, target)
|
||||
|
||||
return loss * self.loss_weight
|
||||
@@ -0,0 +1,86 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from ..builder import LOSSES
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class AdaptiveWingLoss(nn.Module):
|
||||
"""Adaptive wing loss. paper ref: 'Adaptive Wing Loss for Robust Face
|
||||
Alignment via Heatmap Regression' Wang et al. ICCV'2019.
|
||||
|
||||
Args:
|
||||
alpha (float), omega (float), epsilon (float), theta (float)
|
||||
are hyper-parameters.
|
||||
use_target_weight (bool): Option to use weighted MSE loss.
|
||||
Different joint types may have different target weights.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
alpha=2.1,
|
||||
omega=14,
|
||||
epsilon=1,
|
||||
theta=0.5,
|
||||
use_target_weight=False,
|
||||
loss_weight=1.):
|
||||
super().__init__()
|
||||
self.alpha = float(alpha)
|
||||
self.omega = float(omega)
|
||||
self.epsilon = float(epsilon)
|
||||
self.theta = float(theta)
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def criterion(self, pred, target):
|
||||
"""Criterion of wingloss.
|
||||
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor[NxKxHxW]): Predicted heatmaps.
|
||||
target (torch.Tensor[NxKxHxW]): Target heatmaps.
|
||||
"""
|
||||
H, W = pred.shape[2:4]
|
||||
delta = (target - pred).abs()
|
||||
|
||||
A = self.omega * (
|
||||
1 / (1 + torch.pow(self.theta / self.epsilon, self.alpha - target))
|
||||
) * (self.alpha - target) * (torch.pow(
|
||||
self.theta / self.epsilon,
|
||||
self.alpha - target - 1)) * (1 / self.epsilon)
|
||||
C = self.theta * A - self.omega * torch.log(
|
||||
1 + torch.pow(self.theta / self.epsilon, self.alpha - target))
|
||||
|
||||
losses = torch.where(
|
||||
delta < self.theta,
|
||||
self.omega *
|
||||
torch.log(1 +
|
||||
torch.pow(delta / self.epsilon, self.alpha - target)),
|
||||
A * delta - C)
|
||||
|
||||
return torch.mean(losses)
|
||||
|
||||
def forward(self, output, target, target_weight):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[NxKxHxW]): Output heatmaps.
|
||||
target (torch.Tensor[NxKxHxW]): Target heatmaps.
|
||||
target_weight (torch.Tensor[NxKx1]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
if self.use_target_weight:
|
||||
loss = self.criterion(output * target_weight.unsqueeze(-1),
|
||||
target * target_weight.unsqueeze(-1))
|
||||
else:
|
||||
loss = self.criterion(output, target)
|
||||
|
||||
return loss * self.loss_weight
|
||||
@@ -0,0 +1,340 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from ..builder import LOSSES
|
||||
from ..utils.geometry import batch_rodrigues
|
||||
|
||||
|
||||
def perspective_projection(points, rotation, translation, focal_length,
|
||||
camera_center):
|
||||
"""This function computes the perspective projection of a set of 3D points.
|
||||
|
||||
Note:
|
||||
- batch size: B
|
||||
- point number: N
|
||||
|
||||
Args:
|
||||
points (Tensor([B, N, 3])): A set of 3D points
|
||||
rotation (Tensor([B, 3, 3])): Camera rotation matrix
|
||||
translation (Tensor([B, 3])): Camera translation
|
||||
focal_length (Tensor([B,])): Focal length
|
||||
camera_center (Tensor([B, 2])): Camera center
|
||||
|
||||
Returns:
|
||||
projected_points (Tensor([B, N, 2])): Projected 2D
|
||||
points in image space.
|
||||
"""
|
||||
|
||||
batch_size = points.shape[0]
|
||||
K = torch.zeros([batch_size, 3, 3], device=points.device)
|
||||
K[:, 0, 0] = focal_length
|
||||
K[:, 1, 1] = focal_length
|
||||
K[:, 2, 2] = 1.
|
||||
K[:, :-1, -1] = camera_center
|
||||
|
||||
# Transform points
|
||||
points = torch.einsum('bij,bkj->bki', rotation, points)
|
||||
points = points + translation.unsqueeze(1)
|
||||
|
||||
# Apply perspective distortion
|
||||
projected_points = points / points[:, :, -1].unsqueeze(-1)
|
||||
|
||||
# Apply camera intrinsics
|
||||
projected_points = torch.einsum('bij,bkj->bki', K, projected_points)
|
||||
projected_points = projected_points[:, :, :-1]
|
||||
return projected_points
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class MeshLoss(nn.Module):
|
||||
"""Mix loss for 3D human mesh. It is composed of loss on 2D joints, 3D
|
||||
joints, mesh vertices and smpl parameters (if any).
|
||||
|
||||
Args:
|
||||
joints_2d_loss_weight (float): Weight for loss on 2D joints.
|
||||
joints_3d_loss_weight (float): Weight for loss on 3D joints.
|
||||
vertex_loss_weight (float): Weight for loss on 3D verteices.
|
||||
smpl_pose_loss_weight (float): Weight for loss on SMPL
|
||||
pose parameters.
|
||||
smpl_beta_loss_weight (float): Weight for loss on SMPL
|
||||
shape parameters.
|
||||
img_res (int): Input image resolution.
|
||||
focal_length (float): Focal length of camera model. Default=5000.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
joints_2d_loss_weight,
|
||||
joints_3d_loss_weight,
|
||||
vertex_loss_weight,
|
||||
smpl_pose_loss_weight,
|
||||
smpl_beta_loss_weight,
|
||||
img_res,
|
||||
focal_length=5000):
|
||||
|
||||
super().__init__()
|
||||
# Per-vertex loss on the mesh
|
||||
self.criterion_vertex = nn.L1Loss(reduction='none')
|
||||
|
||||
# Joints (2D and 3D) loss
|
||||
self.criterion_joints_2d = nn.SmoothL1Loss(reduction='none')
|
||||
self.criterion_joints_3d = nn.SmoothL1Loss(reduction='none')
|
||||
|
||||
# Loss for SMPL parameter regression
|
||||
self.criterion_regr = nn.MSELoss(reduction='none')
|
||||
|
||||
self.joints_2d_loss_weight = joints_2d_loss_weight
|
||||
self.joints_3d_loss_weight = joints_3d_loss_weight
|
||||
self.vertex_loss_weight = vertex_loss_weight
|
||||
self.smpl_pose_loss_weight = smpl_pose_loss_weight
|
||||
self.smpl_beta_loss_weight = smpl_beta_loss_weight
|
||||
self.focal_length = focal_length
|
||||
self.img_res = img_res
|
||||
|
||||
def joints_2d_loss(self, pred_joints_2d, gt_joints_2d, joints_2d_visible):
|
||||
"""Compute 2D reprojection loss on the joints.
|
||||
|
||||
The loss is weighted by joints_2d_visible.
|
||||
"""
|
||||
conf = joints_2d_visible.float()
|
||||
loss = (conf *
|
||||
self.criterion_joints_2d(pred_joints_2d, gt_joints_2d)).mean()
|
||||
return loss
|
||||
|
||||
def joints_3d_loss(self, pred_joints_3d, gt_joints_3d, joints_3d_visible):
|
||||
"""Compute 3D joints loss for the examples that 3D joint annotations
|
||||
are available.
|
||||
|
||||
The loss is weighted by joints_3d_visible.
|
||||
"""
|
||||
conf = joints_3d_visible.float()
|
||||
if len(gt_joints_3d) > 0:
|
||||
gt_pelvis = (gt_joints_3d[:, 2, :] + gt_joints_3d[:, 3, :]) / 2
|
||||
gt_joints_3d = gt_joints_3d - gt_pelvis[:, None, :]
|
||||
pred_pelvis = (pred_joints_3d[:, 2, :] +
|
||||
pred_joints_3d[:, 3, :]) / 2
|
||||
pred_joints_3d = pred_joints_3d - pred_pelvis[:, None, :]
|
||||
return (
|
||||
conf *
|
||||
self.criterion_joints_3d(pred_joints_3d, gt_joints_3d)).mean()
|
||||
return pred_joints_3d.sum() * 0
|
||||
|
||||
def vertex_loss(self, pred_vertices, gt_vertices, has_smpl):
|
||||
"""Compute 3D vertex loss for the examples that 3D human mesh
|
||||
annotations are available.
|
||||
|
||||
The loss is weighted by the has_smpl.
|
||||
"""
|
||||
conf = has_smpl.float()
|
||||
loss_vertex = self.criterion_vertex(pred_vertices, gt_vertices)
|
||||
loss_vertex = (conf[:, None, None] * loss_vertex).mean()
|
||||
return loss_vertex
|
||||
|
||||
def smpl_losses(self, pred_rotmat, pred_betas, gt_pose, gt_betas,
|
||||
has_smpl):
|
||||
"""Compute SMPL parameters loss for the examples that SMPL parameter
|
||||
annotations are available.
|
||||
|
||||
The loss is weighted by has_smpl.
|
||||
"""
|
||||
conf = has_smpl.float()
|
||||
gt_rotmat = batch_rodrigues(gt_pose.view(-1, 3)).view(-1, 24, 3, 3)
|
||||
loss_regr_pose = self.criterion_regr(pred_rotmat, gt_rotmat)
|
||||
loss_regr_betas = self.criterion_regr(pred_betas, gt_betas)
|
||||
loss_regr_pose = (conf[:, None, None, None] * loss_regr_pose).mean()
|
||||
loss_regr_betas = (conf[:, None] * loss_regr_betas).mean()
|
||||
return loss_regr_pose, loss_regr_betas
|
||||
|
||||
def project_points(self, points_3d, camera):
|
||||
"""Perform orthographic projection of 3D points using the camera
|
||||
parameters, return projected 2D points in image plane.
|
||||
|
||||
Note:
|
||||
- batch size: B
|
||||
- point number: N
|
||||
|
||||
Args:
|
||||
points_3d (Tensor([B, N, 3])): 3D points.
|
||||
camera (Tensor([B, 3])): camera parameters with the
|
||||
3 channel as (scale, translation_x, translation_y)
|
||||
|
||||
Returns:
|
||||
Tensor([B, N, 2]): projected 2D points \
|
||||
in image space.
|
||||
"""
|
||||
batch_size = points_3d.shape[0]
|
||||
device = points_3d.device
|
||||
cam_t = torch.stack([
|
||||
camera[:, 1], camera[:, 2], 2 * self.focal_length /
|
||||
(self.img_res * camera[:, 0] + 1e-9)
|
||||
],
|
||||
dim=-1)
|
||||
camera_center = camera.new_zeros([batch_size, 2])
|
||||
rot_t = torch.eye(
|
||||
3, device=device,
|
||||
dtype=points_3d.dtype).unsqueeze(0).expand(batch_size, -1, -1)
|
||||
joints_2d = perspective_projection(
|
||||
points_3d,
|
||||
rotation=rot_t,
|
||||
translation=cam_t,
|
||||
focal_length=self.focal_length,
|
||||
camera_center=camera_center)
|
||||
return joints_2d
|
||||
|
||||
def forward(self, output, target):
|
||||
"""Forward function.
|
||||
|
||||
Args:
|
||||
output (dict): dict of network predicted results.
|
||||
Keys: 'vertices', 'joints_3d', 'camera',
|
||||
'pose'(optional), 'beta'(optional)
|
||||
target (dict): dict of ground-truth labels.
|
||||
Keys: 'vertices', 'joints_3d', 'joints_3d_visible',
|
||||
'joints_2d', 'joints_2d_visible', 'pose', 'beta',
|
||||
'has_smpl'
|
||||
|
||||
Returns:
|
||||
dict: dict of losses.
|
||||
"""
|
||||
losses = {}
|
||||
|
||||
# Per-vertex loss for the shape
|
||||
pred_vertices = output['vertices']
|
||||
|
||||
gt_vertices = target['vertices']
|
||||
has_smpl = target['has_smpl']
|
||||
loss_vertex = self.vertex_loss(pred_vertices, gt_vertices, has_smpl)
|
||||
losses['vertex_loss'] = loss_vertex * self.vertex_loss_weight
|
||||
|
||||
# Compute loss on SMPL parameters, if available
|
||||
if 'pose' in output.keys() and 'beta' in output.keys():
|
||||
pred_rotmat = output['pose']
|
||||
pred_betas = output['beta']
|
||||
gt_pose = target['pose']
|
||||
gt_betas = target['beta']
|
||||
loss_regr_pose, loss_regr_betas = self.smpl_losses(
|
||||
pred_rotmat, pred_betas, gt_pose, gt_betas, has_smpl)
|
||||
losses['smpl_pose_loss'] = \
|
||||
loss_regr_pose * self.smpl_pose_loss_weight
|
||||
losses['smpl_beta_loss'] = \
|
||||
loss_regr_betas * self.smpl_beta_loss_weight
|
||||
|
||||
# Compute 3D joints loss
|
||||
pred_joints_3d = output['joints_3d']
|
||||
gt_joints_3d = target['joints_3d']
|
||||
joints_3d_visible = target['joints_3d_visible']
|
||||
loss_joints_3d = self.joints_3d_loss(pred_joints_3d, gt_joints_3d,
|
||||
joints_3d_visible)
|
||||
losses['joints_3d_loss'] = loss_joints_3d * self.joints_3d_loss_weight
|
||||
|
||||
# Compute 2D reprojection loss for the 2D joints
|
||||
pred_camera = output['camera']
|
||||
gt_joints_2d = target['joints_2d']
|
||||
joints_2d_visible = target['joints_2d_visible']
|
||||
pred_joints_2d = self.project_points(pred_joints_3d, pred_camera)
|
||||
|
||||
# Normalize keypoints to [-1,1]
|
||||
# The coordinate origin of pred_joints_2d is
|
||||
# the center of the input image.
|
||||
pred_joints_2d = 2 * pred_joints_2d / (self.img_res - 1)
|
||||
# The coordinate origin of gt_joints_2d is
|
||||
# the top left corner of the input image.
|
||||
gt_joints_2d = 2 * gt_joints_2d / (self.img_res - 1) - 1
|
||||
loss_joints_2d = self.joints_2d_loss(pred_joints_2d, gt_joints_2d,
|
||||
joints_2d_visible)
|
||||
losses['joints_2d_loss'] = loss_joints_2d * self.joints_2d_loss_weight
|
||||
|
||||
return losses
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class GANLoss(nn.Module):
|
||||
"""Define GAN loss.
|
||||
|
||||
Args:
|
||||
gan_type (str): Support 'vanilla', 'lsgan', 'wgan', 'hinge'.
|
||||
real_label_val (float): The value for real label. Default: 1.0.
|
||||
fake_label_val (float): The value for fake label. Default: 0.0.
|
||||
loss_weight (float): Loss weight. Default: 1.0.
|
||||
Note that loss_weight is only for generators; and it is always 1.0
|
||||
for discriminators.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
gan_type,
|
||||
real_label_val=1.0,
|
||||
fake_label_val=0.0,
|
||||
loss_weight=1.0):
|
||||
super().__init__()
|
||||
self.gan_type = gan_type
|
||||
self.loss_weight = loss_weight
|
||||
self.real_label_val = real_label_val
|
||||
self.fake_label_val = fake_label_val
|
||||
|
||||
if self.gan_type == 'vanilla':
|
||||
self.loss = nn.BCEWithLogitsLoss()
|
||||
elif self.gan_type == 'lsgan':
|
||||
self.loss = nn.MSELoss()
|
||||
elif self.gan_type == 'wgan':
|
||||
self.loss = self._wgan_loss
|
||||
elif self.gan_type == 'hinge':
|
||||
self.loss = nn.ReLU()
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f'GAN type {self.gan_type} is not implemented.')
|
||||
|
||||
@staticmethod
|
||||
def _wgan_loss(input, target):
|
||||
"""wgan loss.
|
||||
|
||||
Args:
|
||||
input (Tensor): Input tensor.
|
||||
target (bool): Target label.
|
||||
|
||||
Returns:
|
||||
Tensor: wgan loss.
|
||||
"""
|
||||
return -input.mean() if target else input.mean()
|
||||
|
||||
def get_target_label(self, input, target_is_real):
|
||||
"""Get target label.
|
||||
|
||||
Args:
|
||||
input (Tensor): Input tensor.
|
||||
target_is_real (bool): Whether the target is real or fake.
|
||||
|
||||
Returns:
|
||||
(bool | Tensor): Target tensor. Return bool for wgan, \
|
||||
otherwise, return Tensor.
|
||||
"""
|
||||
|
||||
if self.gan_type == 'wgan':
|
||||
return target_is_real
|
||||
target_val = (
|
||||
self.real_label_val if target_is_real else self.fake_label_val)
|
||||
return input.new_ones(input.size()) * target_val
|
||||
|
||||
def forward(self, input, target_is_real, is_disc=False):
|
||||
"""
|
||||
Args:
|
||||
input (Tensor): The input for the loss module, i.e., the network
|
||||
prediction.
|
||||
target_is_real (bool): Whether the targe is real or fake.
|
||||
is_disc (bool): Whether the loss for discriminators or not.
|
||||
Default: False.
|
||||
|
||||
Returns:
|
||||
Tensor: GAN loss value.
|
||||
"""
|
||||
target_label = self.get_target_label(input, target_is_real)
|
||||
if self.gan_type == 'hinge':
|
||||
if is_disc: # for discriminators in hinge-gan
|
||||
input = -input if target_is_real else input
|
||||
loss = self.loss(1 + input).mean()
|
||||
else: # for generators in hinge-gan
|
||||
loss = -input.mean()
|
||||
else: # other gan types
|
||||
loss = self.loss(input, target_label)
|
||||
|
||||
# loss_weight is always 1.0 for discriminators
|
||||
return loss if is_disc else loss * self.loss_weight
|
||||
@@ -0,0 +1,153 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from ..builder import LOSSES
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class JointsMSELoss(nn.Module):
|
||||
"""MSE loss for heatmaps.
|
||||
|
||||
Args:
|
||||
use_target_weight (bool): Option to use weighted MSE loss.
|
||||
Different joint types may have different target weights.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self, use_target_weight=False, loss_weight=1.):
|
||||
super().__init__()
|
||||
self.criterion = nn.MSELoss()
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def forward(self, output, target, target_weight):
|
||||
"""Forward function."""
|
||||
batch_size = output.size(0)
|
||||
num_joints = output.size(1)
|
||||
|
||||
heatmaps_pred = output.reshape(
|
||||
(batch_size, num_joints, -1)).split(1, 1)
|
||||
heatmaps_gt = target.reshape((batch_size, num_joints, -1)).split(1, 1)
|
||||
|
||||
loss = 0.
|
||||
|
||||
for idx in range(num_joints):
|
||||
heatmap_pred = heatmaps_pred[idx].squeeze(1)
|
||||
heatmap_gt = heatmaps_gt[idx].squeeze(1)
|
||||
if self.use_target_weight:
|
||||
loss += self.criterion(heatmap_pred * target_weight[:, idx],
|
||||
heatmap_gt * target_weight[:, idx])
|
||||
else:
|
||||
loss += self.criterion(heatmap_pred, heatmap_gt)
|
||||
|
||||
return loss / num_joints * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class CombinedTargetMSELoss(nn.Module):
|
||||
"""MSE loss for combined target.
|
||||
CombinedTarget: The combination of classification target
|
||||
(response map) and regression target (offset map).
|
||||
Paper ref: Huang et al. The Devil is in the Details: Delving into
|
||||
Unbiased Data Processing for Human Pose Estimation (CVPR 2020).
|
||||
|
||||
Args:
|
||||
use_target_weight (bool): Option to use weighted MSE loss.
|
||||
Different joint types may have different target weights.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self, use_target_weight, loss_weight=1.):
|
||||
super().__init__()
|
||||
self.criterion = nn.MSELoss(reduction='mean')
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def forward(self, output, target, target_weight):
|
||||
batch_size = output.size(0)
|
||||
num_channels = output.size(1)
|
||||
heatmaps_pred = output.reshape(
|
||||
(batch_size, num_channels, -1)).split(1, 1)
|
||||
heatmaps_gt = target.reshape(
|
||||
(batch_size, num_channels, -1)).split(1, 1)
|
||||
loss = 0.
|
||||
num_joints = num_channels // 3
|
||||
for idx in range(num_joints):
|
||||
heatmap_pred = heatmaps_pred[idx * 3].squeeze()
|
||||
heatmap_gt = heatmaps_gt[idx * 3].squeeze()
|
||||
offset_x_pred = heatmaps_pred[idx * 3 + 1].squeeze()
|
||||
offset_x_gt = heatmaps_gt[idx * 3 + 1].squeeze()
|
||||
offset_y_pred = heatmaps_pred[idx * 3 + 2].squeeze()
|
||||
offset_y_gt = heatmaps_gt[idx * 3 + 2].squeeze()
|
||||
if self.use_target_weight:
|
||||
heatmap_pred = heatmap_pred * target_weight[:, idx]
|
||||
heatmap_gt = heatmap_gt * target_weight[:, idx]
|
||||
# classification loss
|
||||
loss += 0.5 * self.criterion(heatmap_pred, heatmap_gt)
|
||||
# regression loss
|
||||
loss += 0.5 * self.criterion(heatmap_gt * offset_x_pred,
|
||||
heatmap_gt * offset_x_gt)
|
||||
loss += 0.5 * self.criterion(heatmap_gt * offset_y_pred,
|
||||
heatmap_gt * offset_y_gt)
|
||||
return loss / num_joints * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class JointsOHKMMSELoss(nn.Module):
|
||||
"""MSE loss with online hard keypoint mining.
|
||||
|
||||
Args:
|
||||
use_target_weight (bool): Option to use weighted MSE loss.
|
||||
Different joint types may have different target weights.
|
||||
topk (int): Only top k joint losses are kept.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self, use_target_weight=False, topk=8, loss_weight=1.):
|
||||
super().__init__()
|
||||
assert topk > 0
|
||||
self.criterion = nn.MSELoss(reduction='none')
|
||||
self.use_target_weight = use_target_weight
|
||||
self.topk = topk
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def _ohkm(self, loss):
|
||||
"""Online hard keypoint mining."""
|
||||
ohkm_loss = 0.
|
||||
N = len(loss)
|
||||
for i in range(N):
|
||||
sub_loss = loss[i]
|
||||
_, topk_idx = torch.topk(
|
||||
sub_loss, k=self.topk, dim=0, sorted=False)
|
||||
tmp_loss = torch.gather(sub_loss, 0, topk_idx)
|
||||
ohkm_loss += torch.sum(tmp_loss) / self.topk
|
||||
ohkm_loss /= N
|
||||
return ohkm_loss
|
||||
|
||||
def forward(self, output, target, target_weight):
|
||||
"""Forward function."""
|
||||
batch_size = output.size(0)
|
||||
num_joints = output.size(1)
|
||||
if num_joints < self.topk:
|
||||
raise ValueError(f'topk ({self.topk}) should not '
|
||||
f'larger than num_joints ({num_joints}).')
|
||||
heatmaps_pred = output.reshape(
|
||||
(batch_size, num_joints, -1)).split(1, 1)
|
||||
heatmaps_gt = target.reshape((batch_size, num_joints, -1)).split(1, 1)
|
||||
|
||||
losses = []
|
||||
for idx in range(num_joints):
|
||||
heatmap_pred = heatmaps_pred[idx].squeeze(1)
|
||||
heatmap_gt = heatmaps_gt[idx].squeeze(1)
|
||||
if self.use_target_weight:
|
||||
losses.append(
|
||||
self.criterion(heatmap_pred * target_weight[:, idx],
|
||||
heatmap_gt * target_weight[:, idx]))
|
||||
else:
|
||||
losses.append(self.criterion(heatmap_pred, heatmap_gt))
|
||||
|
||||
losses = [loss.mean(dim=1).unsqueeze(dim=1) for loss in losses]
|
||||
losses = torch.cat(losses, dim=1)
|
||||
|
||||
return self._ohkm(losses) * self.loss_weight
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Adapted from https://github.com/HRNet/HigherHRNet-Human-Pose-Estimation
|
||||
# Original licence: Copyright (c) Microsoft, under the MIT License.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from ..builder import LOSSES
|
||||
|
||||
|
||||
def _make_input(t, requires_grad=False, device=torch.device('cpu')):
|
||||
"""Make zero inputs for AE loss.
|
||||
|
||||
Args:
|
||||
t (torch.Tensor): input
|
||||
requires_grad (bool): Option to use requires_grad.
|
||||
device: torch device
|
||||
|
||||
Returns:
|
||||
torch.Tensor: zero input.
|
||||
"""
|
||||
inp = torch.autograd.Variable(t, requires_grad=requires_grad)
|
||||
inp = inp.sum()
|
||||
inp = inp.to(device)
|
||||
return inp
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class HeatmapLoss(nn.Module):
|
||||
"""Accumulate the heatmap loss for each image in the batch.
|
||||
|
||||
Args:
|
||||
supervise_empty (bool): Whether to supervise empty channels.
|
||||
"""
|
||||
|
||||
def __init__(self, supervise_empty=True):
|
||||
super().__init__()
|
||||
self.supervise_empty = supervise_empty
|
||||
|
||||
def forward(self, pred, gt, mask):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- heatmaps weight: W
|
||||
- heatmaps height: H
|
||||
- max_num_people: M
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor[N,K,H,W]):heatmap of output.
|
||||
gt (torch.Tensor[N,K,H,W]): target heatmap.
|
||||
mask (torch.Tensor[N,H,W]): mask of target.
|
||||
"""
|
||||
assert pred.size() == gt.size(
|
||||
), f'pred.size() is {pred.size()}, gt.size() is {gt.size()}'
|
||||
|
||||
if not self.supervise_empty:
|
||||
empty_mask = (gt.sum(dim=[2, 3], keepdim=True) > 0).float()
|
||||
loss = ((pred - gt)**2) * empty_mask.expand_as(
|
||||
pred) * mask[:, None, :, :].expand_as(pred)
|
||||
else:
|
||||
loss = ((pred - gt)**2) * mask[:, None, :, :].expand_as(pred)
|
||||
loss = loss.mean(dim=3).mean(dim=2).mean(dim=1)
|
||||
return loss
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class AELoss(nn.Module):
|
||||
"""Associative Embedding loss.
|
||||
|
||||
`Associative Embedding: End-to-End Learning for Joint Detection and
|
||||
Grouping <https://arxiv.org/abs/1611.05424v2>`_.
|
||||
"""
|
||||
|
||||
def __init__(self, loss_type):
|
||||
super().__init__()
|
||||
self.loss_type = loss_type
|
||||
|
||||
def singleTagLoss(self, pred_tag, joints):
|
||||
"""Associative embedding loss for one image.
|
||||
|
||||
Note:
|
||||
- heatmaps weight: W
|
||||
- heatmaps height: H
|
||||
- max_num_people: M
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
pred_tag (torch.Tensor[KxHxW,1]): tag of output for one image.
|
||||
joints (torch.Tensor[M,K,2]): joints information for one image.
|
||||
"""
|
||||
tags = []
|
||||
pull = 0
|
||||
for joints_per_person in joints:
|
||||
tmp = []
|
||||
for joint in joints_per_person:
|
||||
if joint[1] > 0:
|
||||
tmp.append(pred_tag[joint[0]])
|
||||
if len(tmp) == 0:
|
||||
continue
|
||||
tmp = torch.stack(tmp)
|
||||
tags.append(torch.mean(tmp, dim=0))
|
||||
pull = pull + torch.mean((tmp - tags[-1].expand_as(tmp))**2)
|
||||
|
||||
num_tags = len(tags)
|
||||
if num_tags == 0:
|
||||
return (
|
||||
_make_input(torch.zeros(1).float(), device=pred_tag.device),
|
||||
_make_input(torch.zeros(1).float(), device=pred_tag.device))
|
||||
elif num_tags == 1:
|
||||
return (_make_input(
|
||||
torch.zeros(1).float(), device=pred_tag.device), pull)
|
||||
|
||||
tags = torch.stack(tags)
|
||||
|
||||
size = (num_tags, num_tags)
|
||||
A = tags.expand(*size)
|
||||
B = A.permute(1, 0)
|
||||
|
||||
diff = A - B
|
||||
|
||||
if self.loss_type == 'exp':
|
||||
diff = torch.pow(diff, 2)
|
||||
push = torch.exp(-diff)
|
||||
push = torch.sum(push) - num_tags
|
||||
elif self.loss_type == 'max':
|
||||
diff = 1 - torch.abs(diff)
|
||||
push = torch.clamp(diff, min=0).sum() - num_tags
|
||||
else:
|
||||
raise ValueError('Unknown ae loss type')
|
||||
|
||||
push_loss = push / ((num_tags - 1) * num_tags) * 0.5
|
||||
pull_loss = pull / (num_tags)
|
||||
|
||||
return push_loss, pull_loss
|
||||
|
||||
def forward(self, tags, joints):
|
||||
"""Accumulate the tag loss for each image in the batch.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- heatmaps weight: W
|
||||
- heatmaps height: H
|
||||
- max_num_people: M
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
tags (torch.Tensor[N,KxHxW,1]): tag channels of output.
|
||||
joints (torch.Tensor[N,M,K,2]): joints information.
|
||||
"""
|
||||
pushes, pulls = [], []
|
||||
joints = joints.cpu().data.numpy()
|
||||
batch_size = tags.size(0)
|
||||
for i in range(batch_size):
|
||||
push, pull = self.singleTagLoss(tags[i], joints[i])
|
||||
pushes.append(push)
|
||||
pulls.append(pull)
|
||||
return torch.stack(pushes), torch.stack(pulls)
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class MultiLossFactory(nn.Module):
|
||||
"""Loss for bottom-up models.
|
||||
|
||||
Args:
|
||||
num_joints (int): Number of keypoints.
|
||||
num_stages (int): Number of stages.
|
||||
ae_loss_type (str): Type of ae loss.
|
||||
with_ae_loss (list[bool]): Use ae loss or not in multi-heatmap.
|
||||
push_loss_factor (list[float]):
|
||||
Parameter of push loss in multi-heatmap.
|
||||
pull_loss_factor (list[float]):
|
||||
Parameter of pull loss in multi-heatmap.
|
||||
with_heatmap_loss (list[bool]):
|
||||
Use heatmap loss or not in multi-heatmap.
|
||||
heatmaps_loss_factor (list[float]):
|
||||
Parameter of heatmap loss in multi-heatmap.
|
||||
supervise_empty (bool): Whether to supervise empty channels.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
num_joints,
|
||||
num_stages,
|
||||
ae_loss_type,
|
||||
with_ae_loss,
|
||||
push_loss_factor,
|
||||
pull_loss_factor,
|
||||
with_heatmaps_loss,
|
||||
heatmaps_loss_factor,
|
||||
supervise_empty=True):
|
||||
super().__init__()
|
||||
|
||||
assert isinstance(with_heatmaps_loss, (list, tuple)), \
|
||||
'with_heatmaps_loss should be a list or tuple'
|
||||
assert isinstance(heatmaps_loss_factor, (list, tuple)), \
|
||||
'heatmaps_loss_factor should be a list or tuple'
|
||||
assert isinstance(with_ae_loss, (list, tuple)), \
|
||||
'with_ae_loss should be a list or tuple'
|
||||
assert isinstance(push_loss_factor, (list, tuple)), \
|
||||
'push_loss_factor should be a list or tuple'
|
||||
assert isinstance(pull_loss_factor, (list, tuple)), \
|
||||
'pull_loss_factor should be a list or tuple'
|
||||
|
||||
self.num_joints = num_joints
|
||||
self.num_stages = num_stages
|
||||
self.ae_loss_type = ae_loss_type
|
||||
self.with_ae_loss = with_ae_loss
|
||||
self.push_loss_factor = push_loss_factor
|
||||
self.pull_loss_factor = pull_loss_factor
|
||||
self.with_heatmaps_loss = with_heatmaps_loss
|
||||
self.heatmaps_loss_factor = heatmaps_loss_factor
|
||||
|
||||
self.heatmaps_loss = \
|
||||
nn.ModuleList(
|
||||
[
|
||||
HeatmapLoss(supervise_empty)
|
||||
if with_heatmaps_loss else None
|
||||
for with_heatmaps_loss in self.with_heatmaps_loss
|
||||
]
|
||||
)
|
||||
|
||||
self.ae_loss = \
|
||||
nn.ModuleList(
|
||||
[
|
||||
AELoss(self.ae_loss_type) if with_ae_loss else None
|
||||
for with_ae_loss in self.with_ae_loss
|
||||
]
|
||||
)
|
||||
|
||||
def forward(self, outputs, heatmaps, masks, joints):
|
||||
"""Forward function to calculate losses.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- heatmaps weight: W
|
||||
- heatmaps height: H
|
||||
- max_num_people: M
|
||||
- num_keypoints: K
|
||||
- output_channel: C C=2K if use ae loss else K
|
||||
|
||||
Args:
|
||||
outputs (list(torch.Tensor[N,C,H,W])): outputs of stages.
|
||||
heatmaps (list(torch.Tensor[N,K,H,W])): target of heatmaps.
|
||||
masks (list(torch.Tensor[N,H,W])): masks of heatmaps.
|
||||
joints (list(torch.Tensor[N,M,K,2])): joints of ae loss.
|
||||
"""
|
||||
heatmaps_losses = []
|
||||
push_losses = []
|
||||
pull_losses = []
|
||||
for idx in range(len(outputs)):
|
||||
offset_feat = 0
|
||||
if self.heatmaps_loss[idx]:
|
||||
heatmaps_pred = outputs[idx][:, :self.num_joints]
|
||||
offset_feat = self.num_joints
|
||||
heatmaps_loss = self.heatmaps_loss[idx](heatmaps_pred,
|
||||
heatmaps[idx],
|
||||
masks[idx])
|
||||
heatmaps_loss = heatmaps_loss * self.heatmaps_loss_factor[idx]
|
||||
heatmaps_losses.append(heatmaps_loss)
|
||||
else:
|
||||
heatmaps_losses.append(None)
|
||||
|
||||
if self.ae_loss[idx]:
|
||||
tags_pred = outputs[idx][:, offset_feat:]
|
||||
batch_size = tags_pred.size()[0]
|
||||
tags_pred = tags_pred.contiguous().view(batch_size, -1, 1)
|
||||
|
||||
push_loss, pull_loss = self.ae_loss[idx](tags_pred,
|
||||
joints[idx])
|
||||
push_loss = push_loss * self.push_loss_factor[idx]
|
||||
pull_loss = pull_loss * self.pull_loss_factor[idx]
|
||||
|
||||
push_losses.append(push_loss)
|
||||
pull_losses.append(pull_loss)
|
||||
else:
|
||||
push_losses.append(None)
|
||||
pull_losses.append(None)
|
||||
|
||||
return heatmaps_losses, push_losses, pull_losses
|
||||
+448
@@ -0,0 +1,448 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from ..builder import LOSSES
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class SmoothL1Loss(nn.Module):
|
||||
"""SmoothL1Loss loss.
|
||||
|
||||
Args:
|
||||
use_target_weight (bool): Option to use weighted MSE loss.
|
||||
Different joint types may have different target weights.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self, use_target_weight=False, loss_weight=1.):
|
||||
super().__init__()
|
||||
self.criterion = F.smooth_l1_loss
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def forward(self, output, target, target_weight=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- dimension of keypoints: D (D=2 or D=3)
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, D]): Output regression.
|
||||
target (torch.Tensor[N, K, D]): Target regression.
|
||||
target_weight (torch.Tensor[N, K, D]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
if self.use_target_weight:
|
||||
assert target_weight is not None
|
||||
loss = self.criterion(output * target_weight,
|
||||
target * target_weight)
|
||||
else:
|
||||
loss = self.criterion(output, target)
|
||||
|
||||
return loss * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class WingLoss(nn.Module):
|
||||
"""Wing Loss. paper ref: 'Wing Loss for Robust Facial Landmark Localisation
|
||||
with Convolutional Neural Networks' Feng et al. CVPR'2018.
|
||||
|
||||
Args:
|
||||
omega (float): Also referred to as width.
|
||||
epsilon (float): Also referred to as curvature.
|
||||
use_target_weight (bool): Option to use weighted MSE loss.
|
||||
Different joint types may have different target weights.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
omega=10.0,
|
||||
epsilon=2.0,
|
||||
use_target_weight=False,
|
||||
loss_weight=1.):
|
||||
super().__init__()
|
||||
self.omega = omega
|
||||
self.epsilon = epsilon
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
# constant that smoothly links the piecewise-defined linear
|
||||
# and nonlinear parts
|
||||
self.C = self.omega * (1.0 - math.log(1.0 + self.omega / self.epsilon))
|
||||
|
||||
def criterion(self, pred, target):
|
||||
"""Criterion of wingloss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- dimension of keypoints: D (D=2 or D=3)
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor[N, K, D]): Output regression.
|
||||
target (torch.Tensor[N, K, D]): Target regression.
|
||||
"""
|
||||
delta = (target - pred).abs()
|
||||
losses = torch.where(
|
||||
delta < self.omega,
|
||||
self.omega * torch.log(1.0 + delta / self.epsilon), delta - self.C)
|
||||
return torch.mean(torch.sum(losses, dim=[1, 2]), dim=0)
|
||||
|
||||
def forward(self, output, target, target_weight=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- dimension of keypoints: D (D=2 or D=3)
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, D]): Output regression.
|
||||
target (torch.Tensor[N, K, D]): Target regression.
|
||||
target_weight (torch.Tensor[N,K,D]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
if self.use_target_weight:
|
||||
assert target_weight is not None
|
||||
loss = self.criterion(output * target_weight,
|
||||
target * target_weight)
|
||||
else:
|
||||
loss = self.criterion(output, target)
|
||||
|
||||
return loss * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class SoftWingLoss(nn.Module):
|
||||
"""Soft Wing Loss 'Structure-Coherent Deep Feature Learning for Robust Face
|
||||
Alignment' Lin et al. TIP'2021.
|
||||
|
||||
loss =
|
||||
1. |x| , if |x| < omega1
|
||||
2. omega2*ln(1+|x|/epsilon) + B, if |x| >= omega1
|
||||
|
||||
Args:
|
||||
omega1 (float): The first threshold.
|
||||
omega2 (float): The second threshold.
|
||||
epsilon (float): Also referred to as curvature.
|
||||
use_target_weight (bool): Option to use weighted MSE loss.
|
||||
Different joint types may have different target weights.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
omega1=2.0,
|
||||
omega2=20.0,
|
||||
epsilon=0.5,
|
||||
use_target_weight=False,
|
||||
loss_weight=1.):
|
||||
super().__init__()
|
||||
self.omega1 = omega1
|
||||
self.omega2 = omega2
|
||||
self.epsilon = epsilon
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
# constant that smoothly links the piecewise-defined linear
|
||||
# and nonlinear parts
|
||||
self.B = self.omega1 - self.omega2 * math.log(1.0 + self.omega1 /
|
||||
self.epsilon)
|
||||
|
||||
def criterion(self, pred, target):
|
||||
"""Criterion of wingloss.
|
||||
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
dimension of keypoints: D (D=2 or D=3)
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor[N, K, D]): Output regression.
|
||||
target (torch.Tensor[N, K, D]): Target regression.
|
||||
"""
|
||||
delta = (target - pred).abs()
|
||||
losses = torch.where(
|
||||
delta < self.omega1, delta,
|
||||
self.omega2 * torch.log(1.0 + delta / self.epsilon) + self.B)
|
||||
return torch.mean(torch.sum(losses, dim=[1, 2]), dim=0)
|
||||
|
||||
def forward(self, output, target, target_weight=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
dimension of keypoints: D (D=2 or D=3)
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, D]): Output regression.
|
||||
target (torch.Tensor[N, K, D]): Target regression.
|
||||
target_weight (torch.Tensor[N, K, D]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
if self.use_target_weight:
|
||||
assert target_weight is not None
|
||||
loss = self.criterion(output * target_weight,
|
||||
target * target_weight)
|
||||
else:
|
||||
loss = self.criterion(output, target)
|
||||
|
||||
return loss * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class MPJPELoss(nn.Module):
|
||||
"""MPJPE (Mean Per Joint Position Error) loss.
|
||||
|
||||
Args:
|
||||
use_target_weight (bool): Option to use weighted MSE loss.
|
||||
Different joint types may have different target weights.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self, use_target_weight=False, loss_weight=1.):
|
||||
super().__init__()
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def forward(self, output, target, target_weight=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- dimension of keypoints: D (D=2 or D=3)
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, D]): Output regression.
|
||||
target (torch.Tensor[N, K, D]): Target regression.
|
||||
target_weight (torch.Tensor[N,K,D]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
|
||||
if self.use_target_weight:
|
||||
assert target_weight is not None
|
||||
loss = torch.mean(
|
||||
torch.norm((output - target) * target_weight, dim=-1))
|
||||
else:
|
||||
loss = torch.mean(torch.norm(output - target, dim=-1))
|
||||
|
||||
return loss * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class L1Loss(nn.Module):
|
||||
"""L1Loss loss ."""
|
||||
|
||||
def __init__(self, use_target_weight=False, loss_weight=1.):
|
||||
super().__init__()
|
||||
self.criterion = F.l1_loss
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def forward(self, output, target, target_weight=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, 2]): Output regression.
|
||||
target (torch.Tensor[N, K, 2]): Target regression.
|
||||
target_weight (torch.Tensor[N, K, 2]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
if self.use_target_weight:
|
||||
assert target_weight is not None
|
||||
loss = self.criterion(output * target_weight,
|
||||
target * target_weight)
|
||||
else:
|
||||
loss = self.criterion(output, target)
|
||||
|
||||
return loss * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class MSELoss(nn.Module):
|
||||
"""MSE loss for coordinate regression."""
|
||||
|
||||
def __init__(self, use_target_weight=False, loss_weight=1.):
|
||||
super().__init__()
|
||||
self.criterion = F.mse_loss
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def forward(self, output, target, target_weight=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, 2]): Output regression.
|
||||
target (torch.Tensor[N, K, 2]): Target regression.
|
||||
target_weight (torch.Tensor[N, K, 2]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
if self.use_target_weight:
|
||||
assert target_weight is not None
|
||||
loss = self.criterion(output * target_weight,
|
||||
target * target_weight)
|
||||
else:
|
||||
loss = self.criterion(output, target)
|
||||
|
||||
return loss * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class BoneLoss(nn.Module):
|
||||
"""Bone length loss.
|
||||
|
||||
Args:
|
||||
joint_parents (list): Indices of each joint's parent joint.
|
||||
use_target_weight (bool): Option to use weighted bone loss.
|
||||
Different bone types may have different target weights.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self, joint_parents, use_target_weight=False, loss_weight=1.):
|
||||
super().__init__()
|
||||
self.joint_parents = joint_parents
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
self.non_root_indices = []
|
||||
for i in range(len(self.joint_parents)):
|
||||
if i != self.joint_parents[i]:
|
||||
self.non_root_indices.append(i)
|
||||
|
||||
def forward(self, output, target, target_weight=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- dimension of keypoints: D (D=2 or D=3)
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, D]): Output regression.
|
||||
target (torch.Tensor[N, K, D]): Target regression.
|
||||
target_weight (torch.Tensor[N, K-1]):
|
||||
Weights across different bone types.
|
||||
"""
|
||||
output_bone = torch.norm(
|
||||
output - output[:, self.joint_parents, :],
|
||||
dim=-1)[:, self.non_root_indices]
|
||||
target_bone = torch.norm(
|
||||
target - target[:, self.joint_parents, :],
|
||||
dim=-1)[:, self.non_root_indices]
|
||||
if self.use_target_weight:
|
||||
assert target_weight is not None
|
||||
loss = torch.mean(
|
||||
torch.abs((output_bone * target_weight).mean(dim=0) -
|
||||
(target_bone * target_weight).mean(dim=0)))
|
||||
else:
|
||||
loss = torch.mean(
|
||||
torch.abs(output_bone.mean(dim=0) - target_bone.mean(dim=0)))
|
||||
|
||||
return loss * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class SemiSupervisionLoss(nn.Module):
|
||||
"""Semi-supervision loss for unlabeled data. It is composed of projection
|
||||
loss and bone loss.
|
||||
|
||||
Paper ref: `3D human pose estimation in video with temporal convolutions
|
||||
and semi-supervised training` Dario Pavllo et al. CVPR'2019.
|
||||
|
||||
Args:
|
||||
joint_parents (list): Indices of each joint's parent joint.
|
||||
projection_loss_weight (float): Weight for projection loss.
|
||||
bone_loss_weight (float): Weight for bone loss.
|
||||
warmup_iterations (int): Number of warmup iterations. In the first
|
||||
`warmup_iterations` iterations, the model is trained only on
|
||||
labeled data, and semi-supervision loss will be 0.
|
||||
This is a workaround since currently we cannot access
|
||||
epoch number in loss functions. Note that the iteration number in
|
||||
an epoch can be changed due to different GPU numbers in multi-GPU
|
||||
settings. So please set this parameter carefully.
|
||||
warmup_iterations = dataset_size // samples_per_gpu // gpu_num
|
||||
* warmup_epochs
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
joint_parents,
|
||||
projection_loss_weight=1.,
|
||||
bone_loss_weight=1.,
|
||||
warmup_iterations=0):
|
||||
super().__init__()
|
||||
self.criterion_projection = MPJPELoss(
|
||||
loss_weight=projection_loss_weight)
|
||||
self.criterion_bone = BoneLoss(
|
||||
joint_parents, loss_weight=bone_loss_weight)
|
||||
self.warmup_iterations = warmup_iterations
|
||||
self.num_iterations = 0
|
||||
|
||||
@staticmethod
|
||||
def project_joints(x, intrinsics):
|
||||
"""Project 3D joint coordinates to 2D image plane using camera
|
||||
intrinsic parameters.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor[N, K, 3]): 3D joint coordinates.
|
||||
intrinsics (torch.Tensor[N, 4] | torch.Tensor[N, 9]): Camera
|
||||
intrinsics: f (2), c (2), k (3), p (2).
|
||||
"""
|
||||
while intrinsics.dim() < x.dim():
|
||||
intrinsics.unsqueeze_(1)
|
||||
f = intrinsics[..., :2]
|
||||
c = intrinsics[..., 2:4]
|
||||
_x = torch.clamp(x[:, :, :2] / x[:, :, 2:], -1, 1)
|
||||
if intrinsics.shape[-1] == 9:
|
||||
k = intrinsics[..., 4:7]
|
||||
p = intrinsics[..., 7:9]
|
||||
|
||||
r2 = torch.sum(_x[:, :, :2]**2, dim=-1, keepdim=True)
|
||||
radial = 1 + torch.sum(
|
||||
k * torch.cat((r2, r2**2, r2**3), dim=-1),
|
||||
dim=-1,
|
||||
keepdim=True)
|
||||
tan = torch.sum(p * _x, dim=-1, keepdim=True)
|
||||
_x = _x * (radial + tan) + p * r2
|
||||
_x = f * _x + c
|
||||
return _x
|
||||
|
||||
def forward(self, output, target):
|
||||
losses = dict()
|
||||
|
||||
self.num_iterations += 1
|
||||
if self.num_iterations <= self.warmup_iterations:
|
||||
return losses
|
||||
|
||||
labeled_pose = output['labeled_pose']
|
||||
unlabeled_pose = output['unlabeled_pose']
|
||||
unlabeled_traj = output['unlabeled_traj']
|
||||
unlabeled_target_2d = target['unlabeled_target_2d']
|
||||
intrinsics = target['intrinsics']
|
||||
|
||||
# projection loss
|
||||
unlabeled_output = unlabeled_pose + unlabeled_traj
|
||||
unlabeled_output_2d = self.project_joints(unlabeled_output, intrinsics)
|
||||
loss_proj = self.criterion_projection(unlabeled_output_2d,
|
||||
unlabeled_target_2d, None)
|
||||
losses['proj_loss'] = loss_proj
|
||||
|
||||
# bone loss
|
||||
loss_bone = self.criterion_bone(unlabeled_pose, labeled_pose, None)
|
||||
losses['bone_loss'] = loss_bone
|
||||
|
||||
return losses
|
||||
@@ -0,0 +1 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
@@ -0,0 +1,307 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Adapted from https://github.com/akanazawa/hmr
|
||||
# Original licence: Copyright (c) 2018 akanazawa, under the MIT License.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from abc import abstractmethod
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import normal_init, xavier_init
|
||||
|
||||
from mmpose.models.utils.geometry import batch_rodrigues
|
||||
|
||||
|
||||
class BaseDiscriminator(nn.Module):
|
||||
"""Base linear module for SMPL parameter discriminator.
|
||||
|
||||
Args:
|
||||
fc_layers (Tuple): Tuple of neuron count,
|
||||
such as (9, 32, 32, 1)
|
||||
use_dropout (Tuple): Tuple of bool define use dropout or not
|
||||
for each layer, such as (True, True, False)
|
||||
drop_prob (Tuple): Tuple of float defined the drop prob,
|
||||
such as (0.5, 0.5, 0)
|
||||
use_activation(Tuple): Tuple of bool define use active function
|
||||
or not, such as (True, True, False)
|
||||
"""
|
||||
|
||||
def __init__(self, fc_layers, use_dropout, drop_prob, use_activation):
|
||||
super().__init__()
|
||||
self.fc_layers = fc_layers
|
||||
self.use_dropout = use_dropout
|
||||
self.drop_prob = drop_prob
|
||||
self.use_activation = use_activation
|
||||
self._check()
|
||||
self.create_layers()
|
||||
|
||||
def _check(self):
|
||||
"""Check input to avoid ValueError."""
|
||||
if not isinstance(self.fc_layers, tuple):
|
||||
raise TypeError(f'fc_layers require tuple, '
|
||||
f'get {type(self.fc_layers)}')
|
||||
|
||||
if not isinstance(self.use_dropout, tuple):
|
||||
raise TypeError(f'use_dropout require tuple, '
|
||||
f'get {type(self.use_dropout)}')
|
||||
|
||||
if not isinstance(self.drop_prob, tuple):
|
||||
raise TypeError(f'drop_prob require tuple, '
|
||||
f'get {type(self.drop_prob)}')
|
||||
|
||||
if not isinstance(self.use_activation, tuple):
|
||||
raise TypeError(f'use_activation require tuple, '
|
||||
f'get {type(self.use_activation)}')
|
||||
|
||||
l_fc_layer = len(self.fc_layers)
|
||||
l_use_drop = len(self.use_dropout)
|
||||
l_drop_prob = len(self.drop_prob)
|
||||
l_use_activation = len(self.use_activation)
|
||||
|
||||
pass_check = (
|
||||
l_fc_layer >= 2 and l_use_drop < l_fc_layer
|
||||
and l_drop_prob < l_fc_layer and l_use_activation < l_fc_layer
|
||||
and l_drop_prob == l_use_drop)
|
||||
|
||||
if not pass_check:
|
||||
msg = 'Wrong BaseDiscriminator parameters!'
|
||||
raise ValueError(msg)
|
||||
|
||||
def create_layers(self):
|
||||
"""Create layers."""
|
||||
l_fc_layer = len(self.fc_layers)
|
||||
l_use_drop = len(self.use_dropout)
|
||||
l_use_activation = len(self.use_activation)
|
||||
|
||||
self.fc_blocks = nn.Sequential()
|
||||
|
||||
for i in range(l_fc_layer - 1):
|
||||
self.fc_blocks.add_module(
|
||||
name=f'regressor_fc_{i}',
|
||||
module=nn.Linear(
|
||||
in_features=self.fc_layers[i],
|
||||
out_features=self.fc_layers[i + 1]))
|
||||
|
||||
if i < l_use_activation and self.use_activation[i]:
|
||||
self.fc_blocks.add_module(
|
||||
name=f'regressor_af_{i}', module=nn.ReLU())
|
||||
|
||||
if i < l_use_drop and self.use_dropout[i]:
|
||||
self.fc_blocks.add_module(
|
||||
name=f'regressor_fc_dropout_{i}',
|
||||
module=nn.Dropout(p=self.drop_prob[i]))
|
||||
|
||||
@abstractmethod
|
||||
def forward(self, inputs):
|
||||
"""Forward function."""
|
||||
msg = 'the base class [BaseDiscriminator] is not callable!'
|
||||
raise NotImplementedError(msg)
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize model weights."""
|
||||
for m in self.fc_blocks.named_modules():
|
||||
if isinstance(m, nn.Linear):
|
||||
xavier_init(m, gain=0.01)
|
||||
|
||||
|
||||
class ShapeDiscriminator(BaseDiscriminator):
|
||||
"""Discriminator for SMPL shape parameters, the inputs is (batch_size x 10)
|
||||
|
||||
Args:
|
||||
fc_layers (Tuple): Tuple of neuron count, such as (10, 5, 1)
|
||||
use_dropout (Tuple): Tuple of bool define use dropout or
|
||||
not for each layer, such as (True, True, False)
|
||||
drop_prob (Tuple): Tuple of float defined the drop prob,
|
||||
such as (0.5, 0)
|
||||
use_activation(Tuple): Tuple of bool define use active
|
||||
function or not, such as (True, False)
|
||||
"""
|
||||
|
||||
def __init__(self, fc_layers, use_dropout, drop_prob, use_activation):
|
||||
if fc_layers[-1] != 1:
|
||||
msg = f'the neuron count of the last layer ' \
|
||||
f'must be 1, but got {fc_layers[-1]}'
|
||||
raise ValueError(msg)
|
||||
|
||||
super().__init__(fc_layers, use_dropout, drop_prob, use_activation)
|
||||
|
||||
def forward(self, inputs):
|
||||
"""Forward function."""
|
||||
return self.fc_blocks(inputs)
|
||||
|
||||
|
||||
class PoseDiscriminator(nn.Module):
|
||||
"""Discriminator for SMPL pose parameters of each joint. It is composed of
|
||||
discriminators for each joints. The inputs is (batch_size x joint_count x
|
||||
9)
|
||||
|
||||
Args:
|
||||
channels (Tuple): Tuple of channel number,
|
||||
such as (9, 32, 32, 1)
|
||||
joint_count (int): Joint number, such as 23
|
||||
"""
|
||||
|
||||
def __init__(self, channels, joint_count):
|
||||
super().__init__()
|
||||
if channels[-1] != 1:
|
||||
msg = f'the neuron count of the last layer ' \
|
||||
f'must be 1, but got {channels[-1]}'
|
||||
raise ValueError(msg)
|
||||
self.joint_count = joint_count
|
||||
|
||||
self.conv_blocks = nn.Sequential()
|
||||
len_channels = len(channels)
|
||||
for idx in range(len_channels - 2):
|
||||
self.conv_blocks.add_module(
|
||||
name=f'conv_{idx}',
|
||||
module=nn.Conv2d(
|
||||
in_channels=channels[idx],
|
||||
out_channels=channels[idx + 1],
|
||||
kernel_size=1,
|
||||
stride=1))
|
||||
|
||||
self.fc_layer = nn.ModuleList()
|
||||
for idx in range(joint_count):
|
||||
self.fc_layer.append(
|
||||
nn.Linear(
|
||||
in_features=channels[len_channels - 2], out_features=1))
|
||||
|
||||
def forward(self, inputs):
|
||||
"""Forward function.
|
||||
|
||||
The input is (batch_size x joint_count x 9).
|
||||
"""
|
||||
# shape: batch_size x 9 x 1 x joint_count
|
||||
inputs = inputs.transpose(1, 2).unsqueeze(2).contiguous()
|
||||
# shape: batch_size x c x 1 x joint_count
|
||||
internal_outputs = self.conv_blocks(inputs)
|
||||
outputs = []
|
||||
for idx in range(self.joint_count):
|
||||
outputs.append(self.fc_layer[idx](internal_outputs[:, :, 0, idx]))
|
||||
|
||||
return torch.cat(outputs, 1), internal_outputs
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize model weights."""
|
||||
for m in self.conv_blocks:
|
||||
if isinstance(m, nn.Conv2d):
|
||||
normal_init(m, std=0.001, bias=0)
|
||||
for m in self.fc_layer.named_modules():
|
||||
if isinstance(m, nn.Linear):
|
||||
xavier_init(m, gain=0.01)
|
||||
|
||||
|
||||
class FullPoseDiscriminator(BaseDiscriminator):
|
||||
"""Discriminator for SMPL pose parameters of all joints.
|
||||
|
||||
Args:
|
||||
fc_layers (Tuple): Tuple of neuron count,
|
||||
such as (736, 1024, 1024, 1)
|
||||
use_dropout (Tuple): Tuple of bool define use dropout or not
|
||||
for each layer, such as (True, True, False)
|
||||
drop_prob (Tuple): Tuple of float defined the drop prob,
|
||||
such as (0.5, 0.5, 0)
|
||||
use_activation(Tuple): Tuple of bool define use active
|
||||
function or not, such as (True, True, False)
|
||||
"""
|
||||
|
||||
def __init__(self, fc_layers, use_dropout, drop_prob, use_activation):
|
||||
if fc_layers[-1] != 1:
|
||||
msg = f'the neuron count of the last layer must be 1,' \
|
||||
f' but got {fc_layers[-1]}'
|
||||
raise ValueError(msg)
|
||||
|
||||
super().__init__(fc_layers, use_dropout, drop_prob, use_activation)
|
||||
|
||||
def forward(self, inputs):
|
||||
"""Forward function."""
|
||||
return self.fc_blocks(inputs)
|
||||
|
||||
|
||||
class SMPLDiscriminator(nn.Module):
|
||||
"""Discriminator for SMPL pose and shape parameters. It is composed of a
|
||||
discriminator for SMPL shape parameters, a discriminator for SMPL pose
|
||||
parameters of all joints and a discriminator for SMPL pose parameters of
|
||||
each joint.
|
||||
|
||||
Args:
|
||||
beta_channel (tuple of int): Tuple of neuron count of the
|
||||
discriminator of shape parameters. Defaults to (10, 5, 1)
|
||||
per_joint_channel (tuple of int): Tuple of neuron count of the
|
||||
discriminator of each joint. Defaults to (9, 32, 32, 1)
|
||||
full_pose_channel (tuple of int): Tuple of neuron count of the
|
||||
discriminator of full pose. Defaults to (23*32, 1024, 1024, 1)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
beta_channel=(10, 5, 1),
|
||||
per_joint_channel=(9, 32, 32, 1),
|
||||
full_pose_channel=(23 * 32, 1024, 1024, 1)):
|
||||
super().__init__()
|
||||
self.joint_count = 23
|
||||
# The count of SMPL shape parameter is 10.
|
||||
assert beta_channel[0] == 10
|
||||
# Use 3 x 3 rotation matrix as the pose parameters
|
||||
# of each joint, so the input channel is 9.
|
||||
assert per_joint_channel[0] == 9
|
||||
assert self.joint_count * per_joint_channel[-2] \
|
||||
== full_pose_channel[0]
|
||||
|
||||
self.beta_channel = beta_channel
|
||||
self.per_joint_channel = per_joint_channel
|
||||
self.full_pose_channel = full_pose_channel
|
||||
self._create_sub_modules()
|
||||
|
||||
def _create_sub_modules(self):
|
||||
"""Create sub discriminators."""
|
||||
|
||||
# create theta discriminator for each joint
|
||||
self.pose_discriminator = PoseDiscriminator(self.per_joint_channel,
|
||||
self.joint_count)
|
||||
|
||||
# create full pose discriminator for total joints
|
||||
fc_layers = self.full_pose_channel
|
||||
use_dropout = tuple([False] * (len(fc_layers) - 1))
|
||||
drop_prob = tuple([0.5] * (len(fc_layers) - 1))
|
||||
use_activation = tuple([True] * (len(fc_layers) - 2) + [False])
|
||||
|
||||
self.full_pose_discriminator = FullPoseDiscriminator(
|
||||
fc_layers, use_dropout, drop_prob, use_activation)
|
||||
|
||||
# create shape discriminator for betas
|
||||
fc_layers = self.beta_channel
|
||||
use_dropout = tuple([False] * (len(fc_layers) - 1))
|
||||
drop_prob = tuple([0.5] * (len(fc_layers) - 1))
|
||||
use_activation = tuple([True] * (len(fc_layers) - 2) + [False])
|
||||
self.shape_discriminator = ShapeDiscriminator(fc_layers, use_dropout,
|
||||
drop_prob,
|
||||
use_activation)
|
||||
|
||||
def forward(self, thetas):
|
||||
"""Forward function."""
|
||||
_, poses, shapes = thetas
|
||||
|
||||
batch_size = poses.shape[0]
|
||||
shape_disc_value = self.shape_discriminator(shapes)
|
||||
|
||||
# The first rotation matrix is global rotation
|
||||
# and is NOT used in discriminator.
|
||||
if poses.dim() == 2:
|
||||
rotate_matrixs = \
|
||||
batch_rodrigues(poses.contiguous().view(-1, 3)
|
||||
).view(batch_size, 24, 9)[:, 1:, :]
|
||||
else:
|
||||
rotate_matrixs = poses.contiguous().view(batch_size, 24,
|
||||
9)[:, 1:, :].contiguous()
|
||||
pose_disc_value, pose_inter_disc_value \
|
||||
= self.pose_discriminator(rotate_matrixs)
|
||||
full_pose_disc_value = self.full_pose_discriminator(
|
||||
pose_inter_disc_value.contiguous().view(batch_size, -1))
|
||||
return torch.cat(
|
||||
(pose_disc_value, full_pose_disc_value, shape_disc_value), 1)
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize model weights."""
|
||||
self.full_pose_discriminator.init_weights()
|
||||
self.pose_discriminator.init_weights()
|
||||
self.shape_discriminator.init_weights()
|
||||
@@ -0,0 +1,5 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .gap_neck import GlobalAveragePooling
|
||||
from .posewarper_neck import PoseWarperNeck
|
||||
|
||||
__all__ = ['GlobalAveragePooling', 'PoseWarperNeck']
|
||||
@@ -0,0 +1,37 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from ..builder import NECKS
|
||||
|
||||
|
||||
@NECKS.register_module()
|
||||
class GlobalAveragePooling(nn.Module):
|
||||
"""Global Average Pooling neck.
|
||||
|
||||
Note that we use `view` to remove extra channel after pooling. We do not
|
||||
use `squeeze` as it will also remove the batch dimension when the tensor
|
||||
has a batch dimension of size 1, which can lead to unexpected errors.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.gap = nn.AdaptiveAvgPool2d((1, 1))
|
||||
|
||||
def init_weights(self):
|
||||
pass
|
||||
|
||||
def forward(self, inputs):
|
||||
if isinstance(inputs, tuple):
|
||||
outs = tuple([self.gap(x) for x in inputs])
|
||||
outs = tuple(
|
||||
[out.view(x.size(0), -1) for out, x in zip(outs, inputs)])
|
||||
elif isinstance(inputs, list):
|
||||
outs = [self.gap(x) for x in inputs]
|
||||
outs = [out.view(x.size(0), -1) for out, x in zip(outs, inputs)]
|
||||
elif isinstance(inputs, torch.Tensor):
|
||||
outs = self.gap(inputs)
|
||||
outs = outs.view(inputs.size(0), -1)
|
||||
else:
|
||||
raise TypeError('neck inputs should be tuple or torch.tensor')
|
||||
return outs
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import mmcv
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn import (build_conv_layer, build_norm_layer, constant_init,
|
||||
normal_init)
|
||||
from mmcv.utils import digit_version
|
||||
from torch.nn.modules.batchnorm import _BatchNorm
|
||||
|
||||
from mmpose.models.utils.ops import resize
|
||||
from ..backbones.resnet import BasicBlock, Bottleneck
|
||||
from ..builder import NECKS
|
||||
|
||||
try:
|
||||
from mmcv.ops import DeformConv2d
|
||||
has_mmcv_full = True
|
||||
except (ImportError, ModuleNotFoundError):
|
||||
has_mmcv_full = False
|
||||
|
||||
|
||||
@NECKS.register_module()
|
||||
class PoseWarperNeck(nn.Module):
|
||||
"""PoseWarper neck.
|
||||
|
||||
`"Learning temporal pose estimation from sparsely-labeled videos"
|
||||
<https://arxiv.org/abs/1906.04016>`_.
|
||||
|
||||
Args:
|
||||
in_channels (int): Number of input channels from backbone
|
||||
out_channels (int): Number of output channels
|
||||
inner_channels (int): Number of intermediate channels of the res block
|
||||
deform_groups (int): Number of groups in the deformable conv
|
||||
dilations (list|tuple): different dilations of the offset conv layers
|
||||
trans_conv_kernel (int): the kernel of the trans conv layer, which is
|
||||
used to get heatmap from the output of backbone. Default: 1
|
||||
res_blocks_cfg (dict|None): config of residual blocks. If None,
|
||||
use the default values. If not None, it should contain the
|
||||
following keys:
|
||||
|
||||
- block (str): the type of residual block, Default: 'BASIC'.
|
||||
- num_blocks (int): the number of blocks, Default: 20.
|
||||
|
||||
offsets_kernel (int): the kernel of offset conv layer.
|
||||
deform_conv_kernel (int): the kernel of defomrable conv layer.
|
||||
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 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.
|
||||
|
||||
freeze_trans_layer (bool): Whether to freeze the transition layer
|
||||
(stop grad and set eval mode). Default: True.
|
||||
norm_eval (bool): Whether to set norm layers to eval mode, namely,
|
||||
freeze running stats (mean and var). Note: Effect on Batch Norm
|
||||
and its variants only. Default: False.
|
||||
im2col_step (int): the argument `im2col_step` in deformable conv,
|
||||
Default: 80.
|
||||
"""
|
||||
blocks_dict = {'BASIC': BasicBlock, 'BOTTLENECK': Bottleneck}
|
||||
minimum_mmcv_version = '1.3.17'
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
inner_channels,
|
||||
deform_groups=17,
|
||||
dilations=(3, 6, 12, 18, 24),
|
||||
trans_conv_kernel=1,
|
||||
res_blocks_cfg=None,
|
||||
offsets_kernel=3,
|
||||
deform_conv_kernel=3,
|
||||
in_index=0,
|
||||
input_transform=None,
|
||||
freeze_trans_layer=True,
|
||||
norm_eval=False,
|
||||
im2col_step=80):
|
||||
super().__init__()
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
self.inner_channels = inner_channels
|
||||
self.deform_groups = deform_groups
|
||||
self.dilations = dilations
|
||||
self.trans_conv_kernel = trans_conv_kernel
|
||||
self.res_blocks_cfg = res_blocks_cfg
|
||||
self.offsets_kernel = offsets_kernel
|
||||
self.deform_conv_kernel = deform_conv_kernel
|
||||
self.in_index = in_index
|
||||
self.input_transform = input_transform
|
||||
self.freeze_trans_layer = freeze_trans_layer
|
||||
self.norm_eval = norm_eval
|
||||
self.im2col_step = im2col_step
|
||||
|
||||
identity_trans_layer = False
|
||||
|
||||
assert trans_conv_kernel in [0, 1, 3]
|
||||
kernel_size = trans_conv_kernel
|
||||
if kernel_size == 3:
|
||||
padding = 1
|
||||
elif kernel_size == 1:
|
||||
padding = 0
|
||||
else:
|
||||
# 0 for Identity mapping.
|
||||
identity_trans_layer = True
|
||||
|
||||
if identity_trans_layer:
|
||||
self.trans_layer = nn.Identity()
|
||||
else:
|
||||
self.trans_layer = build_conv_layer(
|
||||
cfg=dict(type='Conv2d'),
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=1,
|
||||
padding=padding)
|
||||
|
||||
# build chain of residual blocks
|
||||
if res_blocks_cfg is not None and not isinstance(res_blocks_cfg, dict):
|
||||
raise TypeError('res_blocks_cfg should be dict or None.')
|
||||
|
||||
if res_blocks_cfg is None:
|
||||
block_type = 'BASIC'
|
||||
num_blocks = 20
|
||||
else:
|
||||
block_type = res_blocks_cfg.get('block', 'BASIC')
|
||||
num_blocks = res_blocks_cfg.get('num_blocks', 20)
|
||||
|
||||
block = self.blocks_dict[block_type]
|
||||
|
||||
res_layers = []
|
||||
downsample = nn.Sequential(
|
||||
build_conv_layer(
|
||||
cfg=dict(type='Conv2d'),
|
||||
in_channels=out_channels,
|
||||
out_channels=inner_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
bias=False),
|
||||
build_norm_layer(dict(type='BN'), inner_channels)[1])
|
||||
res_layers.append(
|
||||
block(
|
||||
in_channels=out_channels,
|
||||
out_channels=inner_channels,
|
||||
downsample=downsample))
|
||||
|
||||
for _ in range(1, num_blocks):
|
||||
res_layers.append(block(inner_channels, inner_channels))
|
||||
self.offset_feats = nn.Sequential(*res_layers)
|
||||
|
||||
# build offset layers
|
||||
self.num_offset_layers = len(dilations)
|
||||
assert self.num_offset_layers > 0, 'Number of offset layers ' \
|
||||
'should be larger than 0.'
|
||||
|
||||
target_offset_channels = 2 * offsets_kernel**2 * deform_groups
|
||||
|
||||
offset_layers = [
|
||||
build_conv_layer(
|
||||
cfg=dict(type='Conv2d'),
|
||||
in_channels=inner_channels,
|
||||
out_channels=target_offset_channels,
|
||||
kernel_size=offsets_kernel,
|
||||
stride=1,
|
||||
dilation=dilations[i],
|
||||
padding=dilations[i],
|
||||
bias=False,
|
||||
) for i in range(self.num_offset_layers)
|
||||
]
|
||||
self.offset_layers = nn.ModuleList(offset_layers)
|
||||
|
||||
# build deformable conv layers
|
||||
assert digit_version(mmcv.__version__) >= \
|
||||
digit_version(self.minimum_mmcv_version), \
|
||||
f'Current MMCV version: {mmcv.__version__}, ' \
|
||||
f'but MMCV >= {self.minimum_mmcv_version} is required, see ' \
|
||||
f'https://github.com/open-mmlab/mmcv/issues/1440, ' \
|
||||
f'Please install the latest MMCV.'
|
||||
|
||||
if has_mmcv_full:
|
||||
deform_conv_layers = [
|
||||
DeformConv2d(
|
||||
in_channels=out_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=deform_conv_kernel,
|
||||
stride=1,
|
||||
padding=int(deform_conv_kernel / 2) * dilations[i],
|
||||
dilation=dilations[i],
|
||||
deform_groups=deform_groups,
|
||||
im2col_step=self.im2col_step,
|
||||
) for i in range(self.num_offset_layers)
|
||||
]
|
||||
else:
|
||||
raise ImportError('Please install the full version of mmcv '
|
||||
'to use `DeformConv2d`.')
|
||||
|
||||
self.deform_conv_layers = nn.ModuleList(deform_conv_layers)
|
||||
|
||||
self.freeze_layers()
|
||||
|
||||
def freeze_layers(self):
|
||||
if self.freeze_trans_layer:
|
||||
self.trans_layer.eval()
|
||||
|
||||
for param in self.trans_layer.parameters():
|
||||
param.requires_grad = False
|
||||
|
||||
def init_weights(self):
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
normal_init(m, std=0.001)
|
||||
elif isinstance(m, (_BatchNorm, nn.GroupNorm)):
|
||||
constant_init(m, 1)
|
||||
elif isinstance(m, DeformConv2d):
|
||||
filler = torch.zeros([
|
||||
m.weight.size(0),
|
||||
m.weight.size(1),
|
||||
m.weight.size(2),
|
||||
m.weight.size(3)
|
||||
],
|
||||
dtype=torch.float32,
|
||||
device=m.weight.device)
|
||||
for k in range(m.weight.size(0)):
|
||||
filler[k, k,
|
||||
int(m.weight.size(2) / 2),
|
||||
int(m.weight.size(3) / 2)] = 1.0
|
||||
m.weight = torch.nn.Parameter(filler)
|
||||
m.weight.requires_grad = True
|
||||
|
||||
# posewarper offset layer weight initialization
|
||||
for m in self.offset_layers.modules():
|
||||
constant_init(m, 0)
|
||||
|
||||
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 forward(self, inputs, frame_weight):
|
||||
assert isinstance(inputs, (list, tuple)), 'PoseWarperNeck inputs ' \
|
||||
'should be list or tuple, even though the length is 1, ' \
|
||||
'for unified processing.'
|
||||
|
||||
output_heatmap = 0
|
||||
if len(inputs) > 1:
|
||||
inputs = [self._transform_inputs(input) for input in inputs]
|
||||
inputs = [self.trans_layer(input) for input in inputs]
|
||||
|
||||
# calculate difference features
|
||||
diff_features = [
|
||||
self.offset_feats(inputs[0] - input) for input in inputs
|
||||
]
|
||||
|
||||
for i in range(len(inputs)):
|
||||
if frame_weight[i] == 0:
|
||||
continue
|
||||
warped_heatmap = 0
|
||||
for j in range(self.num_offset_layers):
|
||||
offset = (self.offset_layers[j](diff_features[i]))
|
||||
warped_heatmap_tmp = self.deform_conv_layers[j](inputs[i],
|
||||
offset)
|
||||
warped_heatmap += warped_heatmap_tmp / \
|
||||
self.num_offset_layers
|
||||
|
||||
output_heatmap += warped_heatmap * frame_weight[i]
|
||||
|
||||
else:
|
||||
inputs = inputs[0]
|
||||
inputs = self._transform_inputs(inputs)
|
||||
inputs = self.trans_layer(inputs)
|
||||
|
||||
num_frames = len(frame_weight)
|
||||
batch_size = inputs.size(0) // num_frames
|
||||
ref_x = inputs[:batch_size]
|
||||
ref_x_tiled = ref_x.repeat(num_frames, 1, 1, 1)
|
||||
|
||||
offset_features = self.offset_feats(ref_x_tiled - inputs)
|
||||
|
||||
warped_heatmap = 0
|
||||
for j in range(self.num_offset_layers):
|
||||
offset = self.offset_layers[j](offset_features)
|
||||
|
||||
warped_heatmap_tmp = self.deform_conv_layers[j](inputs, offset)
|
||||
warped_heatmap += warped_heatmap_tmp / self.num_offset_layers
|
||||
|
||||
for i in range(num_frames):
|
||||
if frame_weight[i] == 0:
|
||||
continue
|
||||
output_heatmap += warped_heatmap[i * batch_size:(i + 1) *
|
||||
batch_size] * frame_weight[i]
|
||||
|
||||
return output_heatmap
|
||||
|
||||
def train(self, mode=True):
|
||||
"""Convert the model into training mode."""
|
||||
super().train(mode)
|
||||
self.freeze_layers()
|
||||
if mode and self.norm_eval:
|
||||
for m in self.modules():
|
||||
if isinstance(m, _BatchNorm):
|
||||
m.eval()
|
||||
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
from .builder import BACKBONES, HEADS, LOSSES, NECKS, POSENETS
|
||||
|
||||
__all__ = ['BACKBONES', 'HEADS', 'LOSSES', 'NECKS', 'POSENETS']
|
||||
|
||||
warnings.simplefilter('once', DeprecationWarning)
|
||||
warnings.warn(
|
||||
'Registries (BACKBONES, NECKS, HEADS, LOSSES, POSENETS) have '
|
||||
'been moved to mmpose.models.builder. Importing from '
|
||||
'mmpose.models.registry will be deprecated in the future.',
|
||||
DeprecationWarning)
|
||||
@@ -0,0 +1,4 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .smpl import SMPL
|
||||
|
||||
__all__ = ['SMPL']
|
||||
@@ -0,0 +1,68 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
from torch.nn import functional as F
|
||||
|
||||
|
||||
def rot6d_to_rotmat(x):
|
||||
"""Convert 6D rotation representation to 3x3 rotation matrix.
|
||||
|
||||
Based on Zhou et al., "On the Continuity of Rotation
|
||||
Representations in Neural Networks", CVPR 2019
|
||||
Input:
|
||||
(B,6) Batch of 6-D rotation representations
|
||||
Output:
|
||||
(B,3,3) Batch of corresponding rotation matrices
|
||||
"""
|
||||
x = x.view(-1, 3, 2)
|
||||
a1 = x[:, :, 0]
|
||||
a2 = x[:, :, 1]
|
||||
b1 = F.normalize(a1)
|
||||
b2 = F.normalize(a2 - torch.einsum('bi,bi->b', b1, a2).unsqueeze(-1) * b1)
|
||||
b3 = torch.cross(b1, b2)
|
||||
return torch.stack((b1, b2, b3), dim=-1)
|
||||
|
||||
|
||||
def batch_rodrigues(theta):
|
||||
"""Convert axis-angle representation to rotation matrix.
|
||||
Args:
|
||||
theta: size = [B, 3]
|
||||
Returns:
|
||||
Rotation matrix corresponding to the quaternion
|
||||
-- size = [B, 3, 3]
|
||||
"""
|
||||
l2norm = torch.norm(theta + 1e-8, p=2, dim=1)
|
||||
angle = torch.unsqueeze(l2norm, -1)
|
||||
normalized = torch.div(theta, angle)
|
||||
angle = angle * 0.5
|
||||
v_cos = torch.cos(angle)
|
||||
v_sin = torch.sin(angle)
|
||||
quat = torch.cat([v_cos, v_sin * normalized], dim=1)
|
||||
return quat_to_rotmat(quat)
|
||||
|
||||
|
||||
def quat_to_rotmat(quat):
|
||||
"""Convert quaternion coefficients to rotation matrix.
|
||||
Args:
|
||||
quat: size = [B, 4] 4 <===>(w, x, y, z)
|
||||
Returns:
|
||||
Rotation matrix corresponding to the quaternion
|
||||
-- size = [B, 3, 3]
|
||||
"""
|
||||
norm_quat = quat
|
||||
norm_quat = norm_quat / norm_quat.norm(p=2, dim=1, keepdim=True)
|
||||
w, x, y, z = norm_quat[:, 0], norm_quat[:, 1],\
|
||||
norm_quat[:, 2], norm_quat[:, 3]
|
||||
|
||||
B = quat.size(0)
|
||||
|
||||
w2, x2, y2, z2 = w.pow(2), x.pow(2), y.pow(2), z.pow(2)
|
||||
wx, wy, wz = w * x, w * y, w * z
|
||||
xy, xz, yz = x * y, x * z, y * z
|
||||
|
||||
rotMat = torch.stack([
|
||||
w2 + x2 - y2 - z2, 2 * xy - 2 * wz, 2 * wy + 2 * xz, 2 * wz + 2 * xy,
|
||||
w2 - x2 + y2 - z2, 2 * yz - 2 * wx, 2 * xz - 2 * wy, 2 * wx + 2 * yz,
|
||||
w2 - x2 - y2 + z2
|
||||
],
|
||||
dim=1).view(B, 3, 3)
|
||||
return rotMat
|
||||
@@ -0,0 +1,29 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
def resize(input,
|
||||
size=None,
|
||||
scale_factor=None,
|
||||
mode='nearest',
|
||||
align_corners=None,
|
||||
warning=True):
|
||||
if warning:
|
||||
if size is not None and align_corners:
|
||||
input_h, input_w = tuple(int(x) for x in input.shape[2:])
|
||||
output_h, output_w = tuple(int(x) for x in size)
|
||||
if output_h > input_h or output_w > output_h:
|
||||
if ((output_h > 1 and output_w > 1 and input_h > 1
|
||||
and input_w > 1) and (output_h - 1) % (input_h - 1)
|
||||
and (output_w - 1) % (input_w - 1)):
|
||||
warnings.warn(
|
||||
f'When align_corners={align_corners}, '
|
||||
'the output would more aligned if '
|
||||
f'input size {(input_h, input_w)} is `x+1` and '
|
||||
f'out size {(output_h, output_w)} is `nx+1`')
|
||||
if isinstance(size, torch.Size):
|
||||
size = tuple(int(x) for x in size)
|
||||
return F.interpolate(input, size, scale_factor, mode, align_corners)
|
||||
@@ -0,0 +1,184 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from ..builder import MESH_MODELS
|
||||
|
||||
try:
|
||||
from smplx import SMPL as SMPL_
|
||||
has_smpl = True
|
||||
except (ImportError, ModuleNotFoundError):
|
||||
has_smpl = False
|
||||
|
||||
|
||||
@MESH_MODELS.register_module()
|
||||
class SMPL(nn.Module):
|
||||
"""SMPL 3d human mesh model of paper ref: Matthew Loper. ``SMPL: A skinned
|
||||
multi-person linear model''. This module is based on the smplx project
|
||||
(https://github.com/vchoutas/smplx).
|
||||
|
||||
Args:
|
||||
smpl_path (str): The path to the folder where the model weights are
|
||||
stored.
|
||||
joints_regressor (str): The path to the file where the joints
|
||||
regressor weight are stored.
|
||||
"""
|
||||
|
||||
def __init__(self, smpl_path, joints_regressor):
|
||||
super().__init__()
|
||||
|
||||
assert has_smpl, 'Please install smplx to use SMPL.'
|
||||
|
||||
self.smpl_neutral = SMPL_(
|
||||
model_path=smpl_path,
|
||||
create_global_orient=False,
|
||||
create_body_pose=False,
|
||||
create_transl=False,
|
||||
gender='neutral')
|
||||
|
||||
self.smpl_male = SMPL_(
|
||||
model_path=smpl_path,
|
||||
create_betas=False,
|
||||
create_global_orient=False,
|
||||
create_body_pose=False,
|
||||
create_transl=False,
|
||||
gender='male')
|
||||
|
||||
self.smpl_female = SMPL_(
|
||||
model_path=smpl_path,
|
||||
create_betas=False,
|
||||
create_global_orient=False,
|
||||
create_body_pose=False,
|
||||
create_transl=False,
|
||||
gender='female')
|
||||
|
||||
joints_regressor = torch.tensor(
|
||||
np.load(joints_regressor), dtype=torch.float)[None, ...]
|
||||
self.register_buffer('joints_regressor', joints_regressor)
|
||||
|
||||
self.num_verts = self.smpl_neutral.get_num_verts()
|
||||
self.num_joints = self.joints_regressor.shape[1]
|
||||
|
||||
def smpl_forward(self, model, **kwargs):
|
||||
"""Apply a specific SMPL model with given model parameters.
|
||||
|
||||
Note:
|
||||
B: batch size
|
||||
V: number of vertices
|
||||
K: number of joints
|
||||
|
||||
Returns:
|
||||
outputs (dict): Dict with mesh vertices and joints.
|
||||
- vertices: Tensor([B, V, 3]), mesh vertices
|
||||
- joints: Tensor([B, K, 3]), 3d joints regressed
|
||||
from mesh vertices.
|
||||
"""
|
||||
|
||||
betas = kwargs['betas']
|
||||
batch_size = betas.shape[0]
|
||||
device = betas.device
|
||||
output = {}
|
||||
if batch_size == 0:
|
||||
output['vertices'] = betas.new_zeros([0, self.num_verts, 3])
|
||||
output['joints'] = betas.new_zeros([0, self.num_joints, 3])
|
||||
else:
|
||||
smpl_out = model(**kwargs)
|
||||
output['vertices'] = smpl_out.vertices
|
||||
output['joints'] = torch.matmul(
|
||||
self.joints_regressor.to(device), output['vertices'])
|
||||
return output
|
||||
|
||||
def get_faces(self):
|
||||
"""Return mesh faces.
|
||||
|
||||
Note:
|
||||
F: number of faces
|
||||
|
||||
Returns:
|
||||
faces: np.ndarray([F, 3]), mesh faces
|
||||
"""
|
||||
return self.smpl_neutral.faces
|
||||
|
||||
def forward(self,
|
||||
betas,
|
||||
body_pose,
|
||||
global_orient,
|
||||
transl=None,
|
||||
gender=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
B: batch size
|
||||
J: number of controllable joints of model, for smpl model J=23
|
||||
K: number of joints
|
||||
|
||||
Args:
|
||||
betas: Tensor([B, 10]), human body shape parameters of SMPL model.
|
||||
body_pose: Tensor([B, J*3] or [B, J, 3, 3]), human body pose
|
||||
parameters of SMPL model. It should be axis-angle vector
|
||||
([B, J*3]) or rotation matrix ([B, J, 3, 3)].
|
||||
global_orient: Tensor([B, 3] or [B, 1, 3, 3]), global orientation
|
||||
of human body. It should be axis-angle vector ([B, 3]) or
|
||||
rotation matrix ([B, 1, 3, 3)].
|
||||
transl: Tensor([B, 3]), global translation of human body.
|
||||
gender: Tensor([B]), gender parameters of human body. -1 for
|
||||
neutral, 0 for male , 1 for female.
|
||||
|
||||
Returns:
|
||||
outputs (dict): Dict with mesh vertices and joints.
|
||||
- vertices: Tensor([B, V, 3]), mesh vertices
|
||||
- joints: Tensor([B, K, 3]), 3d joints regressed from
|
||||
mesh vertices.
|
||||
"""
|
||||
|
||||
batch_size = betas.shape[0]
|
||||
pose2rot = True if body_pose.dim() == 2 else False
|
||||
if batch_size > 0 and gender is not None:
|
||||
output = {
|
||||
'vertices': betas.new_zeros([batch_size, self.num_verts, 3]),
|
||||
'joints': betas.new_zeros([batch_size, self.num_joints, 3])
|
||||
}
|
||||
|
||||
mask = gender < 0
|
||||
_out = self.smpl_forward(
|
||||
self.smpl_neutral,
|
||||
betas=betas[mask],
|
||||
body_pose=body_pose[mask],
|
||||
global_orient=global_orient[mask],
|
||||
transl=transl[mask] if transl is not None else None,
|
||||
pose2rot=pose2rot)
|
||||
output['vertices'][mask] = _out['vertices']
|
||||
output['joints'][mask] = _out['joints']
|
||||
|
||||
mask = gender == 0
|
||||
_out = self.smpl_forward(
|
||||
self.smpl_male,
|
||||
betas=betas[mask],
|
||||
body_pose=body_pose[mask],
|
||||
global_orient=global_orient[mask],
|
||||
transl=transl[mask] if transl is not None else None,
|
||||
pose2rot=pose2rot)
|
||||
output['vertices'][mask] = _out['vertices']
|
||||
output['joints'][mask] = _out['joints']
|
||||
|
||||
mask = gender == 1
|
||||
_out = self.smpl_forward(
|
||||
self.smpl_male,
|
||||
betas=betas[mask],
|
||||
body_pose=body_pose[mask],
|
||||
global_orient=global_orient[mask],
|
||||
transl=transl[mask] if transl is not None else None,
|
||||
pose2rot=pose2rot)
|
||||
output['vertices'][mask] = _out['vertices']
|
||||
output['joints'][mask] = _out['joints']
|
||||
else:
|
||||
return self.smpl_forward(
|
||||
self.smpl_neutral,
|
||||
betas=betas,
|
||||
body_pose=body_pose,
|
||||
global_orient=global_orient,
|
||||
transl=transl,
|
||||
pose2rot=pose2rot)
|
||||
|
||||
return output
|
||||
Reference in New Issue
Block a user