MMDet逐行解读之DeltaXYWHBBoxCoder

文章目录

  • 前言
  • 1、BaseBBoxCoder父类
  • 2、DeltaXYWHBBoxCoder类
    • 2.1. 理论基础
    • 2.2、初始化部分
    • 2.3、编码过程
    • 2.4、解码过程
  • 总结

前言

  本篇是MMdet逐行解读第三篇,代码地址:mmdet/core/bbox/coder/delta_xywh_bbox_coder.py。历史文章如下:
 AnchorGenerator解读
 MaxIOUAssigner解读

1、BaseBBoxCoder父类

  该类是所有bbox编解码类的父类,代码比较容易理解,即所有继承该类的子类均需要实现encode和decode两个方法。

class BaseBBoxCoder(metaclass=ABCMeta):
    """Base bounding box coder"""

    def __init__(self, **kwargs):
        pass

    @abstractmethod
    def encode(self, bboxes, gt_bboxes):
        """Encode deltas between bboxes and ground truth boxes"""
        pass

    @abstractmethod
    def decode(self, bboxes, bboxes_pred):
        """
        Decode the predicted bboxes according to prediction and base boxes
        """
        pass

2、DeltaXYWHBBoxCoder类

2.1. 理论基础

  大多数基于anchor的目标检测算法均使用的该类。在目标检测算法中,为了利于网络的收敛,借助了anchor并回归的是anchor和gtbbox之间的偏差。所以,网络预测的是偏差,因此,在训练过程中,需要计算gtbbox和anchor之间的偏差真值t*。真实t*的计算方式如下:
MMDet逐行解读之DeltaXYWHBBoxCoder_第1张图片
  其中 [x,y,w,h] 表示gtbbox的中心宽和高;[xa,ya,wa,ha] 表示anchor的中心宽和高。简单来说,tx* ,ty* 表示二者做差除以宽高做了归一化;tw*,th*就是取了个对数。

2.2、初始化部分

  我们首先构造一个对象:

import torch
from mmdet.core.bbox import build_bbox_coder
if __name__ == '__main__':
    bbox_coder = dict(
        type='DeltaXYWHBBoxCoder',
        target_means=[.0, .0, .0, .0],
        target_stds=[1.0, 1.0, 1.0, 1.0])
    coder = build_bbox_coder(bbox_coder)
    # 构造两个预测向量和真值
    proposals = torch.tensor([[1,1,3,3],[4,4,6,6]])
    gt = torch.tensor([[2,2,3,3],[2,2,5,5]])
    target_t = coder.encode(proposals,gt)    # 调用解码方法

其中target_means和target_stds就是对上述的t*减均值除以标准差。

2.3、编码过程

  这部分代码我已经做了注释,总体思路就是首先将proposals和gtbbox由[xmin, ymin, xmax, ymax]变成[cx, cy, w,h],之后计算t*,然后将t*减均值除以标准差。

    # 候选框数量和gt数量必须一致
    assert proposals.size() == gt.size() # [N,4]
    proposals = proposals.float()
    gt = gt.float()
    # proposals: [xmin, ymin, xmax, ymax] --> [cx, cy, w, h]
    px = (proposals[..., 0] + proposals[..., 2]) * 0.5   # [N]
    py = (proposals[..., 1] + proposals[..., 3]) * 0.5
    pw = proposals[..., 2] - proposals[..., 0]
    ph = proposals[..., 3] - proposals[..., 1]
    # gt: [xmin, ymin, xmax, ymax] --> [cx, cy, w, h]
    gx = (gt[..., 0] + gt[..., 2]) * 0.5
    gy = (gt[..., 1] + gt[..., 3]) * 0.5
    gw = gt[..., 2] - gt[..., 0]
    gh = gt[..., 3] - gt[..., 1]
	# 计算t*
    dx = (gx - px) / pw
    dy = (gy - py) / ph
    dw = torch.log(gw / pw)
    dh = torch.log(gh / ph)
    deltas = torch.stack([dx, dy, dw, dh], dim=-1)   # [N] --> [N,4]
	# 减均值除以标准差
    means = deltas.new_tensor(means).unsqueeze(0)    # [1,4]
    stds = deltas.new_tensor(stds).unsqueeze(0)      # [1,4]
    deltas = deltas.sub_(means).div_(stds)           # [N,4]

2.4、解码过程

  解码过程常发生在测试阶段。将网络预测出的偏差t加到anchor上得到proposal(一阶算法)或者roi(二阶)用的。该过程就是编码过程相反操作,首先乘标准差在加上均值得到t,之后将t加到anchor上即可。

    # 均值和标准差: [4] --> [1,4]
    means = deltas.new_tensor(means).repeat(1, deltas.size(1) // 4)
    stds = deltas.new_tensor(stds).repeat(1, deltas.size(1) // 4)
    denorm_deltas = deltas * stds + means   # [N,4]
    # 得到dx,dy,dw,dh
    dx = denorm_deltas[:, 0::4]    # [N,1]  
    dy = denorm_deltas[:, 1::4]
    dw = denorm_deltas[:, 2::4]
    dh = denorm_deltas[:, 3::4]
    max_ratio = np.abs(np.log(wh_ratio_clip))
    dw = dw.clamp(min=-max_ratio, max=max_ratio) # 裁减下
    dh = dh.clamp(min=-max_ratio, max=max_ratio)
    # 将rois/proposal转成[cx,cy,w,h]格式:[N,] --> [N,1] --> [N,1]
    px = ((rois[:, 0] + rois[:, 2]) * 0.5).unsqueeze(1).expand_as(dx) 
    py = ((rois[:, 1] + rois[:, 3]) * 0.5).unsqueeze(1).expand_as(dy)
    # Compute width/height of each roi
    pw = (rois[:, 2] - rois[:, 0]).unsqueeze(1).expand_as(dw)
    ph = (rois[:, 3] - rois[:, 1]).unsqueeze(1).expand_as(dh)
    # 解码过程
    gw = pw * dw.exp()
    gh = ph * dh.exp()
    gx = px + pw * dx
    gy = py + ph * dy
    # 将[cx,cy,w,h] --> [xmin, ymin, xmax, ymax]格式
    x1 = gx - gw * 0.5
    y1 = gy - gh * 0.5
    x2 = gx + gw * 0.5
    y2 = gy + gh * 0.5
    # 裁减一下bbox,过大则裁减掉
    if max_shape is not None:
        x1 = x1.clamp(min=0, max=max_shape[1])
        y1 = y1.clamp(min=0, max=max_shape[0])
        x2 = x2.clamp(min=0, max=max_shape[1])
        y2 = y2.clamp(min=0, max=max_shape[0])
    # 返回修正过大预测框
    bboxes = torch.stack([x1, y1, x2, y2], dim=-1).view_as(deltas)

总结

  总体来说该部分源码比较简单,下篇介绍anchor的sampler部分。若有问题欢迎+vx:wulele2541612007,拉你进群探讨交流。

你可能感兴趣的:(mmcv和mmdet源码注释版,python,深度学习,开发语言)