【代码】mmdetection源码解读(Anchor head部分)

本文深入探讨了Anchorhead损失函数的实现细节,包括基于Anchor的损失计算、回归任务的处理,以及在fasterr-cnn模型中的应用。文章详细介绍了如何通过多层卷积进行分类与回归,Anchor生成的过程,以及如何计算正负样本的损失。

0.简介

Anchor head主要是为了计算基于Anchor的损失函数,以及简单的回归。其中含有两个单层卷积,一个用来分类,一个用来回归,可以添加多层, 例如Retina head。
以下代码的解读均依靠断点调试获得,使用的模型为faster r-cnn

1.self.loss

    def loss(self,
             cls_scores,
             bbox_preds,
             gt_bboxes,
             gt_labels,
             img_metas,
             cfg,
             gt_bboxes_ignore=None):
        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]
        assert len(featmap_sizes) == len(self.anchor_generators)

        anchor_list, valid_flag_list = self.get_anchors(
            featmap_sizes, img_metas)
        label_channels = self.cls_out_channels if self.use_sigmoid_cls else 1
        cls_reg_targets = anchor_target(
            anchor_list,
            valid_flag_list,
            gt_bboxes,
            img_metas,
            self.target_means,
            self.target_stds,
            cfg,
            gt_bboxes_ignore_list=gt_bboxes_ignore,
            gt_labels_list=gt_labels,
            label_channels=label_channels,
            sampling=self.sampling)
        if cls_reg_targets is None:
            return None
        (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list,
         num_total_pos, num_total_neg) = cls_reg_targets
        num_total_samples = (
            num_total_pos + num_total_neg if self.sampling else num_total_pos)
        losses_cls, losses_bbox = multi_apply(
            self.loss_single,
            cls_scores,
            bbox_preds,
            labels_list,
            label_weights_list,
            bbox_targets_list,
            bbox_weights_list,
            num_total_samples=num_total_samples,
            cfg=cfg)
        return dict(loss_cls=losses_cls, loss_bbox=losses_bbox)

img_meta

  1. ori_shape:原始图片的shape:(480*640)
  2. img_shape:缩放后的shape:(800*1067)
  3. scale_factor:缩放因子:1.667
  4. flip:是否翻转?

featuremap_sizes
根据cls_scores的大小获得
0:200* 272(stride = 4)
1:100* 136(stride = 8)
。。。
4:13* 17(stride=16*4=64)

self.anchor_base_sizes:保存了stride的大小:
[4, 8, 16, 32, 64]

self.anchor_scales:[8],代表了anchor的大小吧,因为是RPN,只有一个。

self.anchor_ratios:[0.5, 1, 2],代表了anchor的长宽比。

self.anchor_generators:anchor生成器,使用上面三个量进行生成。因为有5个layer,所以generator共有五个。


1.1. AnchorGenerator

在初始化的时候,base_size = 4(基础长宽),scales = [8](放大倍数),ratios=[0.5, 1, 2](长宽比),scale_major = True(排列方式,前m个代表m种scale还是m种ratio)
base_anchor有三个,这时因为有三个ratio,一个scale,中心在base_size / 2处。是以左上右下表示的。
生成方式在 self.gen_base_anchors

1.1.2. self.gen_base_anchors
  1. w, h 为基础长宽。x_ctr = y_ctr = self.base_size / 2,代表中心。由于base_size是用stride赋值的,如果anchor的大小是base_size/2的话,那么这些anchor构成了一个划分(不相交)。
  2. h_ratios = sqrt(ratios), w_ratios = 1 / sqrt(ratios)。ratio指的是高 / 宽。取一个根号。
  3. scale_major与否:如果是scale_major,那么ratios变为3*1的矩阵,scale变为1 * 1(scale个数)的矩阵。最后相乘,以基础长宽为基础,最后拉长,得到w_s。假设scale的个数为2,那么ws的前2个为ratios[0]乘上两个scales的w,之后两个为ratios[1]乘上两个scale的w。
  4. 最终返回的是左上角和右下角,并且取整。

返回anchor_head
原始代码如下:

    def gen_base_anchors(self):
        w = self.base_size
        h = self.base_size
        if self.ctr is None:
            x_ctr = 0.5 * (w - 1)
            y_ctr = 0.5 * (h - 1)
        else:
            x_ctr, y_ctr = self.ctr

        h_ratios = torch.sqrt(self.ratios)
        w_ratios = 1 / h_ratios
        if self.scale_major:
            ws = (w * w_ratios[:, None] * self.scales[None, :]).view(-1)
            hs = (h * h_ratios[:, None] * self.scales[None, :]).view(-1)
        else:
            ws = (w * self.scales[:, None] * w_ratios[None, :]).view(-1)
            hs = (h * self.scales[:, None] * h_ratios[None, :]).view(-1)

        # yapf: disable
        base_anchors = torch.stack(
            [
                x_ctr - 0.5 * (ws - 1), y_ctr - 0.5 * (hs - 1),
                x_ctr + 0.5 * (ws - 1), y_ctr + 0.5 * (hs - 1)
            ],
            dim=-1).round()
        # yapf: enable

        return base_anchors
1.2 self.get_anchors
Get anchors according to feature map sizes.
Args:
            featmap_sizes (list[tuple]): Multi-level feature map sizes.
            img_metas (list[dict]): Image meta info.
Returns:
            tuple: anchors of each image, valid flags of each image

num_imgs:多少张图片
num_levels:FPN的level个数(5)
对于每一个level,使用self.anchor_generator的grid_anchors 获得anchors


1.2.1. grid_anchors

输入:特征图尺寸,stride
比如说[200, 272],4

  1. 生成meshgrid,以(0, 0)为左上角起点生成grid的(x, y)坐标,距离为stride。shift_xx,shift_yy
  2. shifts为[200 * 272 = 54400, 4] shape的向量,4是因为左上角,右下角,有4个值。
  3. shift加上才能base_anchors,得到了all_anchors,大小为[54400 * 3 = 163200, 4],排列方式为前3个为base_anchor,之后这三个base anchor向右移动。得到接下来的3个anchor。

grid_anchors获得的anchor按照level从低到高append起来,得到anchor_list。

1.3 anchor_target(anchor_target文件中)
Compute regression and classification targets for anchors.

    Args:
        anchor_list (list[list]): Multi level anchors of each image.
        valid_flag_list (list[list]): Multi level valid flags of each image.
        gt_bboxes_list (list[Tensor]): Ground truth bboxes of each image.
        img_metas (list[dict]): Meta info of each image.
        target_means (Iterable): Mean value of regression targets.
        target_stds (Iterable): Std value of regression targets.
        cfg (dict): RPN train configs.

    Returns:
        tuple
  1. 将每张图片的gt_bboxes都cat到一起。以及valid_flag_list。
  2. 对每一张图片调用anchor_target_simple

1.3.1. anchor_target_simple
  1. 利用inside_flags筛选掉在边界外的框,191012个
  2. 调用assign_and_samplem
def assign_and_sample(bboxes, gt_bboxes, gt_bboxes_ignore, gt_labels, cfg):
    bbox_assigner = build_assigner(cfg.assigner)
    bbox_sampler = build_sampler(cfg.sampler)
    assign_result = bbox_assigner.assign(bboxes, gt_bboxes, gt_bboxes_ignore,
                                         gt_labels)
    sampling_result = bbox_sampler.sample(assign_result, bboxes, gt_bboxes,
                                          gt_labels)
    return assign_result, sampling_result
  1. 构建一个assigner,一个sampler,
    assigner的构造如下:
    {‘type’: ‘MaxIoUAssigner’, ‘pos_iou_thr’: 0.7, ‘neg_iou_thr’: 0.3, ‘min_pos_iou’: 0.3, ‘ignore_iof_thr’: -1}。
    之后依次assign、sampling。
  2. assign_result中包含max_overlaps、gt_inds,num_gts。其中overlap代表的应该是分配的IOU,而gt_inds是一个和anchor_list同样大小的数组,1代表pos,0代表忽略,-1代表反例。
  3. sampling_result中包含的信息就多了
    在这里插入图片描述
  4. 这个时候,anchor、gt都是在图(原图放大之后)上,通过bbox2delta将bbox转换成pos_bbox_targets:
pos_bbox_targets = bbox2delta(sampling_result.pos_bboxes,
sampling_result.pos_gt_bboxes,
  target_means, target_stds)
  1. 接下来的代码:
		# 将bbox装回所有的bbox列表中
        bbox_targets[pos_inds, :] = pos_bbox_targets
        # weights设置为1
        bbox_weights[pos_inds, :] = 1.0
        # 主要是区分Retina和RPN吧,两者的label不同。
        if gt_labels is None:
            labels[pos_inds] = 1
        else:
            labels[pos_inds] = gt_labels[sampling_result.pos_assigned_gt_inds]
        # 设置权重,为1,或者是指定的值
        if cfg.pos_weight <= 0:
            label_weights[pos_inds] = 1.0
        else:
            label_weights[pos_inds] = cfg.pos_weight
    # 设置neg的权重
    if len(neg_inds) > 0:
        label_weights[neg_inds] = 1.0

    # map up to original set of anchors
    if unmap_outputs:
        num_total_anchors = flat_anchors.size(0)
        labels = unmap(labels, num_total_anchors, inside_flags)
        label_weights = unmap(label_weights, num_total_anchors, inside_flags)
        bbox_targets = unmap(bbox_targets, num_total_anchors, inside_flags)
        bbox_weights = unmap(bbox_weights, num_total_anchors, inside_flags)

    return (labels, label_weights, bbox_targets, bbox_weights, pos_inds,
            neg_inds)
<think>嗯,用户想了解如何基于mmdetection训练DETR模型以及源码实现的详细教程。首先,我需要回忆一下mmdetection的基本结构,以及DETR模型的特点。DETR是Facebook提出的基于Transformer的目标检测模型,它使用Transformer的encoder-decoder结构,不需要传统的anchor机制,而是通过二分图匹配来直接预测目标。 用户提到的是mmdetection,所以需要确认mmdetection中是否已经支持DETR模型。根据我之前的知识,mmdetection从某个版本开始已经集成了DETR的实现,所以可能需要查看官方文档或者源码中的配置文件和模型定义。 接下来,用户需要训练DETR模型的步骤。首先应该准备数据集,比如COCO格式的数据集,然后在配置文件中进行相应的路径设置。然后,需要选择合适的配置文件,可能是在configs/detr目录下找到相关的配置文件,比如detr_r50_8x2_150e_coco.py。用户可能需要修改配置文件中的一些参数,比如学习率、batch size、数据增强方式等。 然后是训练命令,通常使用tools/train.py脚本,指定配置文件和其他参数,比如分布式训练。例如,使用分布式训练的话,命令可能是./tools/dist_train.sh configs/detr/detr_r50_8x2_150e_coco.py 8,其中8表示GPU的数量。 关于源码解析部分,需要分模块讲解。首先是模型的构建,可能在mmdet/models/detectors/detr.py中定义了DETR类,继承自BaseDetector。然后看backbone部分,通常是ResNet,接着是Transformer的encoder和decoder,这部分可能在mmdet/models/utils/transformer.py或者类似的路径下。然后是匈牙利匹配的损失函数,分类和回归头的位置。 训练流程的话,数据加载会通过注册的Dataset类,比如CocoDataset,数据经过预处理后输入到DETR模型中。前向传播过程中,backbone提取特征,然后通过Transformer处理,得到预测结果。计算损失时,使用匈牙利匹配算法将预测和真实框匹配,然后计算分类损失和回归损失。优化器通常使用AdamW,学习率调度器可能采用step方式。 可能还需要提到一些关键点,比如二分图匹配的实现,损失函数的具体形式,以及训练中的注意事项,比如学习率预热、长训练周期等。此外,可以引用mmdetection的官方文档或者源码中的注释来支持说明。 用户可能还会关心如何调试和验证模型是否正确实现,这时候可以建议使用tools/test.py脚本进行评估,或者可视化检测结果。另外,可以提到常见的错误,比如配置文件中路径设置错误,或者版本不兼容的问题,建议用户确保mmdetection的版本支持DETR,并正确安装依赖。 最后,生成相关问题的时候,需要考虑用户可能的后续问题,比如如何改进DETR的训练速度,如何调整超参数,或者如何将DETR应用到其他数据集上。还可能涉及与其他模型的对比,比如与Faster R-CNN的区别,或者DETR的变体如Deformable DETR的实现。</think># mmdetection训练DETR模型源码解析 ## 一、环境准备与数据配置 1. 安装mmdetection v2.14.0+(需支持DETR) ```bash git clone https://github.com/open-mmlab/mmdetection.git cd mmdetection && pip install -e . ``` 2. 数据集配置(以COCO为例) ```python # configs/_base_/datasets/coco_detection.py data = dict( samples_per_gpu=2, # 根据GPU显存调整 train=dict( ann_file='data/coco/annotations/instances_train2017.json', img_prefix='data/coco/train2017/'), val=dict(...), test=dict(...)) ``` ## 二、模型配置解析 关键配置文件:`configs/detr/detr_r50_8x2_150e_coco.py` ```python model = dict( type='DETR', backbone=dict( type='ResNet', # ResNet-50 backbone depth=50, norm_cfg=dict(type='BN')), bbox_head=dict( type='DETRHead', num_classes=80, # COCO类别数 transformer=dict( type='Transformer', # 核心组件 encoder=dict(...), decoder=dict(...)), loss_cls=dict(...), # 分类损失 loss_bbox=dict(...))) # 回归损失 ``` ## 三、训练流程实现 ### 1. 主训练入口 ```python # tools/train.py def main(): cfg = Config.fromfile(args.config) # 加载配置文件 build_detector(cfg.model) # 构建DETR模型 datasets = [build_dataset(cfg.data.train)] # 构建数据集 train_detector(model, datasets, cfg, validate=True) # 核心训练函数 ``` ### 2. 核心训练逻辑 ```python # mmdet/apis/train.py def train_detector(model, dataset, cfg): optimizer = build_optimizer(model, cfg.optimizer) # 通常使用AdamW lr_scheduler = build_lr_scheduler(cfg.lr_config) # 学习率调度 # 数据加载器配置 data_loader = DataLoader( dataset, batch_size=cfg.data.samples_per_gpu, collate_fn=partial(collate, samples_per_gpu=...)) # 迭代训练 for epoch in range(max_epochs): for i, data_batch in enumerate(data_loader): losses = model(return_loss=True, **data_batch) # 前向传播计算损失 loss.backward() # 反向传播 optimizer.step() # 参数更新 ``` ## 四、DETR核心组件实现 ### 1. Transformer结构(mmdet/models/utils/transformer.py) ```python class Transformer(nn.Module): def __init__(self, encoder, decoder): self.encoder = build_transformer_layer_sequence(encoder) self.decoder = build_transformer_layer_sequence(decoder) def forward(self, src, mask, query_embed, pos_embed): memory = self.encoder(src, src_mask=mask, pos=pos_embed) hs = self.decoder(query_embed, memory, memory_mask=mask, pos=pos_embed) return hs.transpose(1, 2) ``` ### 2. 匈牙利匹配损失(mmdet/models/losses/matching_loss.py) $$ \mathcal{L}_{Hungarian} = \sum_{i=1}^N [-\log p_{\hat{\sigma}(i)}(c_i) + \mathbb{1}_{c_i\neq\varnothing} \mathcal{L}_{box}(b_i,b_{\hat{\sigma}(i)})] $$ 其中$\hat{\sigma}$为最优匹配排列,通过匈牙利算法计算得到[^1] ### 3. 位置编码实现 ```python class PositionEmbeddingSine(nn.Module): def forward(self, mask): # 生成正弦位置编码 not_mask = ~mask y_embed = not_mask.cumsum(1, dtype=torch.float32) x_embed = not_mask.cumsum(2, dtype=torch.float32) # 生成维度编码 pos_x = x_embed[:, :, :, None] / div_term pos_y = y_embed[:, :, :, None] / div_term return torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2) ``` ## 五、训练优化建议 1. **学习率设置**:初始lr=1e-4,使用warmup策略 2. **长周期训练**:通常需要500epoch收敛 3. **数据增强**: ```python train_pipeline = [ dict(type='RandomFlip', flip_ratio=0.5), dict(type='Resize', img_scale=(1333, 800), keep_ratio=True), dict(type='Normalize', mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375]) ] ``` ## 六、模型验证 ```bash # 单GPU测试 python tools/test.py configs/detr/detr_r50_8x2_150e_coco.py work_dirs/detr/latest.pth --eval bbox ```
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值