以下链接是个人关于mmdetection(Foveabox-目标检测框架)所有见解,如有错误欢迎大家指出,我会第一时间纠正。有兴趣的朋友可以加微信:a944284742相互讨论技术。若是帮助到了你什么,一定要记得点赞!因为这是对我最大的鼓励。
目标检测00-00:mmdetection(Foveabox为例)-目录-史上最新无死角讲解
从前面的博客,我们已经知道了数据读取,数据增强,以及训练架构等等。那么接下来,久要深入的了解 Foveabox 这个网络了。那么这篇博客。我们就来讲解一下其训练的流程吧。主要的相关代码位于 mmdet/models 文件夹(后续默认都以该文件夹为主-如果没有特别提示)。在了解模型是如何构件之前,我们先查看项目根目录 configs\foveabox\my_fovea_r50_fpn_4x4_2x_coco.py,该文件为本人自己编写,可以通过如下链接复制:目标检测00-04:mmdetection(Foveabox为例)-config文件注释-持续更新。找到其中的 model 字典,内容如下:
# model settings
model = dict(
type='FOVEA', # 设置为FOVEA,则其最终会调用到类mmdet.models.detectors.fovea.FOVEA
backbone=dict( # 主干网络相关配置
type='ResNet', # 主干网络的类型
......)
neck=dict(
type='FPN', # FPN,特征金字塔
......)
bbox_head=dict(
type='FoveaHead', # 头部网络类型
......
loss_cls=dict( # FocalLoss 的相关配置
type='FocalLoss',
......)
loss_bbox=dict(
type='SmoothL1Loss',
.......)
)
总的来说,一个网络的构件,主要包含了三个部分,分别为 backbone(主干网络),neck(衔接网络),bbox_head(头部网络)。下面我们就来看看 Foveabox 究竟是如何构件的吧。
根据上面的 model 字典结构,其首先执行的是 type=‘FOVEA’,对应 mmdet/models/detectors/fovea.py 中的 FOVEA。其代码十分的简单,就是继承于 SingleStageDetector(单阶段目标检测),然后调用其父类的初始化函数,那么我们就来看看 SingleStageDetector 的流程吧。本人注释代码如下:
@DETECTORS.register_module()
class SingleStageDetector(BaseDetector):
"""Base class for single-stage detectors.
Single-stage detectors directly and densely predict bounding boxes on the
output features of the backbone+neck.
"""
def __init__(self,
backbone,
neck=None,
bbox_head=None,
train_cfg=None,
test_cfg=None,
pretrained=None):
super(SingleStageDetector, self).__init__()
# 根据主干网网络参数构建主干网络
self.backbone = build_backbone(backbone)
# 如果设置了衔接网络,则构件衔接网络
if neck is not None:
self.neck = build_neck(neck)
# 对头部网络的参数进行更新(训练和推理的配置参数有些不一样)
bbox_head.update(train_cfg=train_cfg)
bbox_head.update(test_cfg=test_cfg)
# 根据配置参数构件头部网络
self.bbox_head = build_head(bbox_head)
# 赋值训练以及测试参数
self.train_cfg = train_cfg
self.test_cfg = test_cfg
# 如果设置了预训练模型,则加载预训练模型。
self.init_weights(pretrained=pretrained)
def init_weights(self, pretrained=None):
"""Initialize the weights in detector.
权重初始化
Args:
pretrained (str, optional): Path to pre-trained weights.
Defaults to None.
"""
super(SingleStageDetector, self).init_weights(pretrained)
self.backbone.init_weights(pretrained=pretrained)
if self.with_neck:
if isinstance(self.neck, nn.Sequential):
for m in self.neck:
m.init_weights()
else:
self.neck.init_weights()
self.bbox_head.init_weights()
def extract_feat(self, img):
"""Directly extract features from the backbone+neck.
使用主干网络提取特征,如果存在衔接网络,则同时采用衔接网络
"""
x = self.backbone(img)
if self.with_neck:
x = self.neck(x)
return x
def forward_dummy(self, img):
"""Used for computing network flops.
See `mmdetection/tools/get_flops.py`
用于计算网络的浮点型大小,通过 mmdetection/tools/get_flops.py 可以看到具体介绍
"""
x = self.extract_feat(img)
outs = self.bbox_head(x)
return outs
def forward_train(self,
img,
img_metas,
gt_bboxes,
gt_labels,
gt_bboxes_ignore=None):
"""
Args:
img (Tensor): Input images of shape (N, C, H, W).
Typically these should be mean centered and std scaled.
img_metas (list[dict]): A List of image info dict where each dict
has: 'img_shape', 'scale_factor', 'flip', and may also contain
'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.
For details on the values of these keys see
:class:`mmdet.datasets.pipelines.Collect`.
gt_bboxes (list[Tensor]): Each item are the truth boxes for each
image in [tl_x, tl_y, br_x, br_y] format.
gt_labels (list[Tensor]): Class indices corresponding to each box
gt_bboxes_ignore (None | list[Tensor]): Specify which bounding
boxes can be ignored when computing the loss.
Returns:
dict[str, Tensor]: A dictionary of loss components.
"""
# 提取图像的金字塔特征,如果输入图像大小为[640,480],batch_size=b,那么其输出为存在五个元素的一个列表,
# x = [(b,256,60,80), (b,256,30,40), (b,256,15,20), (b,256,8,10), (b,256,4,5)]
x = self.extract_feat(img)
# 如果是进行训练,则调用头部网络的forward_train,获得loss返回,同于反向传播
losses = self.bbox_head.forward_train(x, img_metas, gt_bboxes,
gt_labels, gt_bboxes_ignore)
return losses
def simple_test(self, img, img_metas, rescale=False):
"""Test function without test time augmentation.
单张图片进行测试,测试功能不带有数据增强
Args:
imgs (list[torch.Tensor]): List of multiple images
img_metas (list[dict]): List of image information.
rescale (bool, optional): Whether to rescale the results.
Defaults to False.
Returns:
np.ndarray: proposals
"""
# 提出图像金字塔特征
x = self.extract_feat(img)
# 通过头部网络获得论文中的(tx1, ty1, tx2, ty2),以及其对应的类别概率, 但是大家要注意,这里出来是特征金字塔,如下(假设输入图像大小为640x480):
# box的偏移值: outs[1]=[(1,4,60,80), (1,4,30,40), (1,4,15,20), (1,4,8,10), (1,4,4,5)]
# box对应的类别概率 outs[0]=[(1,num_class,60,80), (1,num_class,30,40), (1,num_class,15,20), (1,num_class,8,10), (1,num_class,4,5)]
outs = self.bbox_head(x)
# 把(tx1, ty1, tx2, ty2) 转换为对应的 box 坐标,同时进行了 nms 处理。
# img_metas 主要记录了输入图像的路径, 原始大小,当前大小,缩放因子,正则化参数。
# 返回的 bbox_list 包含了两个元素,第一个元素存储所有 box 坐标以及概率值,第二元素存储 box 对应的类别。
bbox_list = self.bbox_head.get_bboxes(
*outs, img_metas, rescale=rescale)
# skip post-processing when exporting to ONNX,如果导出为 ONNX,则跳过后期处理
if torch.onnx.is_in_onnx_export():
return bbox_list
# 把结果转化成numpy数组的形式。
bbox_results = [
bbox2result(det_bboxes, det_labels, self.bbox_head.num_classes)
for det_bboxes, det_labels in bbox_list
]
return bbox_results[0]
def aug_test(self, imgs, img_metas, rescale=False):
"""Test function with test time augmentation."""
raise NotImplementedError
其实代码还是十分简单的,主要核心部分为 _init_ 函数的如下部分:
# 根据主干网网络参数构建主干网络
self.backbone = build_backbone(backbone)
# 如果设置了衔接网络,则构件衔接网络
if neck is not None:
self.neck = build_neck(neck)
# 根据配置参数构件头部网络
self.bbox_head = build_head(bbox_head)
其构件的过程,跟前面配置文件中的 model 字典是一致的,如下:
# model settings
model = dict(
type='FOVEA', # 设置为FOVEA,则其最终会调用到类mmdet.models.detectors.fovea.FOVEA
backbone=dict( # 主干网络相关配置
type='ResNet', # 主干网络的类型
......)
neck=dict(
type='FPN', # FPN,特征金字塔
......)
bbox_head=dict(
type='FoveaHead', # 头部网络类型
......)
)
ResNet 位于代码 mmdet\models\backbones\resnet.py 之中,FPN 位于 mmdet\models\necks\fpn.py 之中。经过 self.neck 网络输出的最终结果为一个列表,形状如下如下(假设网络输入图片大小为640x480):
[(b,256,60,80), (b,256,30,40), (b,256,15,20), (b,256,8,10), (b,256,4,5)]
拿到这个结果之后,其会送入到 self.bbox_head 进行出来。这里就是我们下篇博客的重点了,同时也是这片论文的重点。对于 self.backbone 以及 self.neck 的具体过程,本人就不进行讲解了,因为这些并不是 Foveabox 这片论文的重点。有兴趣的朋友可以自己分析一下源码。