|
import torch.nn as nn |
|
from mmcv.cnn import ConvModule, normal_init |
|
from mmcv.ops import DeformConv2d |
|
|
|
from mmdet.core import multi_apply |
|
from ..builder import HEADS, build_loss |
|
from .corner_head import CornerHead |
|
|
|
|
|
@HEADS.register_module() |
|
class CentripetalHead(CornerHead): |
|
"""Head of CentripetalNet: Pursuing High-quality Keypoint Pairs for Object |
|
Detection. |
|
|
|
CentripetalHead inherits from :class:`CornerHead`. It removes the |
|
embedding branch and adds guiding shift and centripetal shift branches. |
|
More details can be found in the `paper |
|
<https://arxiv.org/abs/2003.09119>`_ . |
|
|
|
Args: |
|
num_classes (int): Number of categories excluding the background |
|
category. |
|
in_channels (int): Number of channels in the input feature map. |
|
num_feat_levels (int): Levels of feature from the previous module. 2 |
|
for HourglassNet-104 and 1 for HourglassNet-52. HourglassNet-104 |
|
outputs the final feature and intermediate supervision feature and |
|
HourglassNet-52 only outputs the final feature. Default: 2. |
|
corner_emb_channels (int): Channel of embedding vector. Default: 1. |
|
train_cfg (dict | None): Training config. Useless in CornerHead, |
|
but we keep this variable for SingleStageDetector. Default: None. |
|
test_cfg (dict | None): Testing config of CornerHead. Default: None. |
|
loss_heatmap (dict | None): Config of corner heatmap loss. Default: |
|
GaussianFocalLoss. |
|
loss_embedding (dict | None): Config of corner embedding loss. Default: |
|
AssociativeEmbeddingLoss. |
|
loss_offset (dict | None): Config of corner offset loss. Default: |
|
SmoothL1Loss. |
|
loss_guiding_shift (dict): Config of guiding shift loss. Default: |
|
SmoothL1Loss. |
|
loss_centripetal_shift (dict): Config of centripetal shift loss. |
|
Default: SmoothL1Loss. |
|
""" |
|
|
|
def __init__(self, |
|
*args, |
|
centripetal_shift_channels=2, |
|
guiding_shift_channels=2, |
|
feat_adaption_conv_kernel=3, |
|
loss_guiding_shift=dict( |
|
type='SmoothL1Loss', beta=1.0, loss_weight=0.05), |
|
loss_centripetal_shift=dict( |
|
type='SmoothL1Loss', beta=1.0, loss_weight=1), |
|
**kwargs): |
|
assert centripetal_shift_channels == 2, ( |
|
'CentripetalHead only support centripetal_shift_channels == 2') |
|
self.centripetal_shift_channels = centripetal_shift_channels |
|
assert guiding_shift_channels == 2, ( |
|
'CentripetalHead only support guiding_shift_channels == 2') |
|
self.guiding_shift_channels = guiding_shift_channels |
|
self.feat_adaption_conv_kernel = feat_adaption_conv_kernel |
|
super(CentripetalHead, self).__init__(*args, **kwargs) |
|
self.loss_guiding_shift = build_loss(loss_guiding_shift) |
|
self.loss_centripetal_shift = build_loss(loss_centripetal_shift) |
|
|
|
def _init_centripetal_layers(self): |
|
"""Initialize centripetal layers. |
|
|
|
Including feature adaption deform convs (feat_adaption), deform offset |
|
prediction convs (dcn_off), guiding shift (guiding_shift) and |
|
centripetal shift ( centripetal_shift). Each branch has two parts: |
|
prefix `tl_` for top-left and `br_` for bottom-right. |
|
""" |
|
self.tl_feat_adaption = nn.ModuleList() |
|
self.br_feat_adaption = nn.ModuleList() |
|
self.tl_dcn_offset = nn.ModuleList() |
|
self.br_dcn_offset = nn.ModuleList() |
|
self.tl_guiding_shift = nn.ModuleList() |
|
self.br_guiding_shift = nn.ModuleList() |
|
self.tl_centripetal_shift = nn.ModuleList() |
|
self.br_centripetal_shift = nn.ModuleList() |
|
|
|
for _ in range(self.num_feat_levels): |
|
self.tl_feat_adaption.append( |
|
DeformConv2d(self.in_channels, self.in_channels, |
|
self.feat_adaption_conv_kernel, 1, 1)) |
|
self.br_feat_adaption.append( |
|
DeformConv2d(self.in_channels, self.in_channels, |
|
self.feat_adaption_conv_kernel, 1, 1)) |
|
|
|
self.tl_guiding_shift.append( |
|
self._make_layers( |
|
out_channels=self.guiding_shift_channels, |
|
in_channels=self.in_channels)) |
|
self.br_guiding_shift.append( |
|
self._make_layers( |
|
out_channels=self.guiding_shift_channels, |
|
in_channels=self.in_channels)) |
|
|
|
self.tl_dcn_offset.append( |
|
ConvModule( |
|
self.guiding_shift_channels, |
|
self.feat_adaption_conv_kernel**2 * |
|
self.guiding_shift_channels, |
|
1, |
|
bias=False, |
|
act_cfg=None)) |
|
self.br_dcn_offset.append( |
|
ConvModule( |
|
self.guiding_shift_channels, |
|
self.feat_adaption_conv_kernel**2 * |
|
self.guiding_shift_channels, |
|
1, |
|
bias=False, |
|
act_cfg=None)) |
|
|
|
self.tl_centripetal_shift.append( |
|
self._make_layers( |
|
out_channels=self.centripetal_shift_channels, |
|
in_channels=self.in_channels)) |
|
self.br_centripetal_shift.append( |
|
self._make_layers( |
|
out_channels=self.centripetal_shift_channels, |
|
in_channels=self.in_channels)) |
|
|
|
def _init_layers(self): |
|
"""Initialize layers for CentripetalHead. |
|
|
|
Including two parts: CornerHead layers and CentripetalHead layers |
|
""" |
|
super()._init_layers() |
|
self._init_centripetal_layers() |
|
|
|
def init_weights(self): |
|
"""Initialize weights of the head.""" |
|
super().init_weights() |
|
for i in range(self.num_feat_levels): |
|
normal_init(self.tl_feat_adaption[i], std=0.01) |
|
normal_init(self.br_feat_adaption[i], std=0.01) |
|
normal_init(self.tl_dcn_offset[i].conv, std=0.1) |
|
normal_init(self.br_dcn_offset[i].conv, std=0.1) |
|
_ = [x.conv.reset_parameters() for x in self.tl_guiding_shift[i]] |
|
_ = [x.conv.reset_parameters() for x in self.br_guiding_shift[i]] |
|
_ = [ |
|
x.conv.reset_parameters() for x in self.tl_centripetal_shift[i] |
|
] |
|
_ = [ |
|
x.conv.reset_parameters() for x in self.br_centripetal_shift[i] |
|
] |
|
|
|
def forward_single(self, x, lvl_ind): |
|
"""Forward feature of a single level. |
|
|
|
Args: |
|
x (Tensor): Feature of a single level. |
|
lvl_ind (int): Level index of current feature. |
|
|
|
Returns: |
|
tuple[Tensor]: A tuple of CentripetalHead's output for current |
|
feature level. Containing the following Tensors: |
|
|
|
- tl_heat (Tensor): Predicted top-left corner heatmap. |
|
- br_heat (Tensor): Predicted bottom-right corner heatmap. |
|
- tl_off (Tensor): Predicted top-left offset heatmap. |
|
- br_off (Tensor): Predicted bottom-right offset heatmap. |
|
- tl_guiding_shift (Tensor): Predicted top-left guiding shift |
|
heatmap. |
|
- br_guiding_shift (Tensor): Predicted bottom-right guiding |
|
shift heatmap. |
|
- tl_centripetal_shift (Tensor): Predicted top-left centripetal |
|
shift heatmap. |
|
- br_centripetal_shift (Tensor): Predicted bottom-right |
|
centripetal shift heatmap. |
|
""" |
|
tl_heat, br_heat, _, _, tl_off, br_off, tl_pool, br_pool = super( |
|
).forward_single( |
|
x, lvl_ind, return_pool=True) |
|
|
|
tl_guiding_shift = self.tl_guiding_shift[lvl_ind](tl_pool) |
|
br_guiding_shift = self.br_guiding_shift[lvl_ind](br_pool) |
|
|
|
tl_dcn_offset = self.tl_dcn_offset[lvl_ind](tl_guiding_shift.detach()) |
|
br_dcn_offset = self.br_dcn_offset[lvl_ind](br_guiding_shift.detach()) |
|
|
|
tl_feat_adaption = self.tl_feat_adaption[lvl_ind](tl_pool, |
|
tl_dcn_offset) |
|
br_feat_adaption = self.br_feat_adaption[lvl_ind](br_pool, |
|
br_dcn_offset) |
|
|
|
tl_centripetal_shift = self.tl_centripetal_shift[lvl_ind]( |
|
tl_feat_adaption) |
|
br_centripetal_shift = self.br_centripetal_shift[lvl_ind]( |
|
br_feat_adaption) |
|
|
|
result_list = [ |
|
tl_heat, br_heat, tl_off, br_off, tl_guiding_shift, |
|
br_guiding_shift, tl_centripetal_shift, br_centripetal_shift |
|
] |
|
return result_list |
|
|
|
def loss(self, |
|
tl_heats, |
|
br_heats, |
|
tl_offs, |
|
br_offs, |
|
tl_guiding_shifts, |
|
br_guiding_shifts, |
|
tl_centripetal_shifts, |
|
br_centripetal_shifts, |
|
gt_bboxes, |
|
gt_labels, |
|
img_metas, |
|
gt_bboxes_ignore=None): |
|
"""Compute losses of the head. |
|
|
|
Args: |
|
tl_heats (list[Tensor]): Top-left corner heatmaps for each level |
|
with shape (N, num_classes, H, W). |
|
br_heats (list[Tensor]): Bottom-right corner heatmaps for each |
|
level with shape (N, num_classes, H, W). |
|
tl_offs (list[Tensor]): Top-left corner offsets for each level |
|
with shape (N, corner_offset_channels, H, W). |
|
br_offs (list[Tensor]): Bottom-right corner offsets for each level |
|
with shape (N, corner_offset_channels, H, W). |
|
tl_guiding_shifts (list[Tensor]): Top-left guiding shifts for each |
|
level with shape (N, guiding_shift_channels, H, W). |
|
br_guiding_shifts (list[Tensor]): Bottom-right guiding shifts for |
|
each level with shape (N, guiding_shift_channels, H, W). |
|
tl_centripetal_shifts (list[Tensor]): Top-left centripetal shifts |
|
for each level with shape (N, centripetal_shift_channels, H, |
|
W). |
|
br_centripetal_shifts (list[Tensor]): Bottom-right centripetal |
|
shifts for each level with shape (N, |
|
centripetal_shift_channels, H, W). |
|
gt_bboxes (list[Tensor]): Ground truth bboxes for each image with |
|
shape (num_gts, 4) in [left, top, right, bottom] format. |
|
gt_labels (list[Tensor]): Class indices corresponding to each box. |
|
img_metas (list[dict]): Meta information of each image, e.g., |
|
image size, scaling factor, etc. |
|
gt_bboxes_ignore (list[Tensor] | None): Specify which bounding |
|
boxes can be ignored when computing the loss. |
|
|
|
Returns: |
|
dict[str, Tensor]: A dictionary of loss components. Containing the |
|
following losses: |
|
|
|
- det_loss (list[Tensor]): Corner keypoint losses of all |
|
feature levels. |
|
- off_loss (list[Tensor]): Corner offset losses of all feature |
|
levels. |
|
- guiding_loss (list[Tensor]): Guiding shift losses of all |
|
feature levels. |
|
- centripetal_loss (list[Tensor]): Centripetal shift losses of |
|
all feature levels. |
|
""" |
|
targets = self.get_targets( |
|
gt_bboxes, |
|
gt_labels, |
|
tl_heats[-1].shape, |
|
img_metas[0]['pad_shape'], |
|
with_corner_emb=self.with_corner_emb, |
|
with_guiding_shift=True, |
|
with_centripetal_shift=True) |
|
mlvl_targets = [targets for _ in range(self.num_feat_levels)] |
|
[det_losses, off_losses, guiding_losses, centripetal_losses |
|
] = multi_apply(self.loss_single, tl_heats, br_heats, tl_offs, |
|
br_offs, tl_guiding_shifts, br_guiding_shifts, |
|
tl_centripetal_shifts, br_centripetal_shifts, |
|
mlvl_targets) |
|
loss_dict = dict( |
|
det_loss=det_losses, |
|
off_loss=off_losses, |
|
guiding_loss=guiding_losses, |
|
centripetal_loss=centripetal_losses) |
|
return loss_dict |
|
|
|
def loss_single(self, tl_hmp, br_hmp, tl_off, br_off, tl_guiding_shift, |
|
br_guiding_shift, tl_centripetal_shift, |
|
br_centripetal_shift, targets): |
|
"""Compute losses for single level. |
|
|
|
Args: |
|
tl_hmp (Tensor): Top-left corner heatmap for current level with |
|
shape (N, num_classes, H, W). |
|
br_hmp (Tensor): Bottom-right corner heatmap for current level with |
|
shape (N, num_classes, H, W). |
|
tl_off (Tensor): Top-left corner offset for current level with |
|
shape (N, corner_offset_channels, H, W). |
|
br_off (Tensor): Bottom-right corner offset for current level with |
|
shape (N, corner_offset_channels, H, W). |
|
tl_guiding_shift (Tensor): Top-left guiding shift for current level |
|
with shape (N, guiding_shift_channels, H, W). |
|
br_guiding_shift (Tensor): Bottom-right guiding shift for current |
|
level with shape (N, guiding_shift_channels, H, W). |
|
tl_centripetal_shift (Tensor): Top-left centripetal shift for |
|
current level with shape (N, centripetal_shift_channels, H, W). |
|
br_centripetal_shift (Tensor): Bottom-right centripetal shift for |
|
current level with shape (N, centripetal_shift_channels, H, W). |
|
targets (dict): Corner target generated by `get_targets`. |
|
|
|
Returns: |
|
tuple[torch.Tensor]: Losses of the head's differnet branches |
|
containing the following losses: |
|
|
|
- det_loss (Tensor): Corner keypoint loss. |
|
- off_loss (Tensor): Corner offset loss. |
|
- guiding_loss (Tensor): Guiding shift loss. |
|
- centripetal_loss (Tensor): Centripetal shift loss. |
|
""" |
|
targets['corner_embedding'] = None |
|
|
|
det_loss, _, _, off_loss = super().loss_single(tl_hmp, br_hmp, None, |
|
None, tl_off, br_off, |
|
targets) |
|
|
|
gt_tl_guiding_shift = targets['topleft_guiding_shift'] |
|
gt_br_guiding_shift = targets['bottomright_guiding_shift'] |
|
gt_tl_centripetal_shift = targets['topleft_centripetal_shift'] |
|
gt_br_centripetal_shift = targets['bottomright_centripetal_shift'] |
|
|
|
gt_tl_heatmap = targets['topleft_heatmap'] |
|
gt_br_heatmap = targets['bottomright_heatmap'] |
|
|
|
|
|
|
|
|
|
tl_mask = gt_tl_heatmap.eq(1).sum(1).gt(0).unsqueeze(1).type_as( |
|
gt_tl_heatmap) |
|
br_mask = gt_br_heatmap.eq(1).sum(1).gt(0).unsqueeze(1).type_as( |
|
gt_br_heatmap) |
|
|
|
|
|
tl_guiding_loss = self.loss_guiding_shift( |
|
tl_guiding_shift, |
|
gt_tl_guiding_shift, |
|
tl_mask, |
|
avg_factor=tl_mask.sum()) |
|
br_guiding_loss = self.loss_guiding_shift( |
|
br_guiding_shift, |
|
gt_br_guiding_shift, |
|
br_mask, |
|
avg_factor=br_mask.sum()) |
|
guiding_loss = (tl_guiding_loss + br_guiding_loss) / 2.0 |
|
|
|
tl_centripetal_loss = self.loss_centripetal_shift( |
|
tl_centripetal_shift, |
|
gt_tl_centripetal_shift, |
|
tl_mask, |
|
avg_factor=tl_mask.sum()) |
|
br_centripetal_loss = self.loss_centripetal_shift( |
|
br_centripetal_shift, |
|
gt_br_centripetal_shift, |
|
br_mask, |
|
avg_factor=br_mask.sum()) |
|
centripetal_loss = (tl_centripetal_loss + br_centripetal_loss) / 2.0 |
|
|
|
return det_loss, off_loss, guiding_loss, centripetal_loss |
|
|
|
def get_bboxes(self, |
|
tl_heats, |
|
br_heats, |
|
tl_offs, |
|
br_offs, |
|
tl_guiding_shifts, |
|
br_guiding_shifts, |
|
tl_centripetal_shifts, |
|
br_centripetal_shifts, |
|
img_metas, |
|
rescale=False, |
|
with_nms=True): |
|
"""Transform network output for a batch into bbox predictions. |
|
|
|
Args: |
|
tl_heats (list[Tensor]): Top-left corner heatmaps for each level |
|
with shape (N, num_classes, H, W). |
|
br_heats (list[Tensor]): Bottom-right corner heatmaps for each |
|
level with shape (N, num_classes, H, W). |
|
tl_offs (list[Tensor]): Top-left corner offsets for each level |
|
with shape (N, corner_offset_channels, H, W). |
|
br_offs (list[Tensor]): Bottom-right corner offsets for each level |
|
with shape (N, corner_offset_channels, H, W). |
|
tl_guiding_shifts (list[Tensor]): Top-left guiding shifts for each |
|
level with shape (N, guiding_shift_channels, H, W). Useless in |
|
this function, we keep this arg because it's the raw output |
|
from CentripetalHead. |
|
br_guiding_shifts (list[Tensor]): Bottom-right guiding shifts for |
|
each level with shape (N, guiding_shift_channels, H, W). |
|
Useless in this function, we keep this arg because it's the |
|
raw output from CentripetalHead. |
|
tl_centripetal_shifts (list[Tensor]): Top-left centripetal shifts |
|
for each level with shape (N, centripetal_shift_channels, H, |
|
W). |
|
br_centripetal_shifts (list[Tensor]): Bottom-right centripetal |
|
shifts for each level with shape (N, |
|
centripetal_shift_channels, H, W). |
|
img_metas (list[dict]): Meta information of each image, e.g., |
|
image size, scaling factor, etc. |
|
rescale (bool): If True, return boxes in original image space. |
|
Default: False. |
|
with_nms (bool): If True, do nms before return boxes. |
|
Default: True. |
|
""" |
|
assert tl_heats[-1].shape[0] == br_heats[-1].shape[0] == len(img_metas) |
|
result_list = [] |
|
for img_id in range(len(img_metas)): |
|
result_list.append( |
|
self._get_bboxes_single( |
|
tl_heats[-1][img_id:img_id + 1, :], |
|
br_heats[-1][img_id:img_id + 1, :], |
|
tl_offs[-1][img_id:img_id + 1, :], |
|
br_offs[-1][img_id:img_id + 1, :], |
|
img_metas[img_id], |
|
tl_emb=None, |
|
br_emb=None, |
|
tl_centripetal_shift=tl_centripetal_shifts[-1][ |
|
img_id:img_id + 1, :], |
|
br_centripetal_shift=br_centripetal_shifts[-1][ |
|
img_id:img_id + 1, :], |
|
rescale=rescale, |
|
with_nms=with_nms)) |
|
|
|
return result_list |
|
|