前言1、baseBBoxCoder父类2、DeltaXYWHBBoxCoder类
2.1. 理论基础2.2、初始化部分2.3、编码过程2.4、解码过程 总结
前言 本篇是MMdet逐行解读第三篇,代码地址:mmdet/core/bbox/coder/delta_xywh_bbox_coder.py。历史文章如下:
AnchorGenerator解读
MaxIOUAssigner解读
该类是所有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 """ pass2、DeltaXYWHBBoxCoder类 2.1. 理论基础
大多数基于anchor的目标检测算法均使用的该类。在目标检测算法中,为了利于网络的收敛,借助了anchor并回归的是anchor和gtbbox之间的偏差。所以,网络预测的是偏差,因此,在训练过程中,需要计算gtbbox和anchor之间的偏差真值t*。真实t*的计算方式如下:
其中 [x,y,w,h] 表示gtbbox的中心宽和高;[xa,ya,wa,ha] 表示anchor的中心宽和高。简单来说,tx* ,ty* 表示二者做差除以宽高做了归一化;tw*,th*就是取了个对数。
我们首先构造一个对象:
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部分。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)