【代码解读】超详细,YOLOV5之build_targets函数解读。

build_targets作用

build_targets函数用于网络训练时计算loss所需要的目标框,即正样本

注意

  1. 与yolov3/yolov4不同,yolv5支持跨网格预测。即每一个bbox,正对于任何一个输出层,都可能有anchor与之匹配。
  2. 该函数输出的正样本框比传入的GT数目要
  3. 当前解读版本为6.1

可视化结果

  • TODO

过程

  1. 首先通过bbox与当前层anchor做一遍过滤。对于任何一层计算当前bbox与当前层anchor的匹配程度,不采用IoU,而采用shape比例。如果anchor与bbox的宽高比差距大于4,则认为不匹配,保留下匹配的bbox。
 r = t[..., 4:6] / anchors[:, None]  # wh ratio
 j = torch.max(r, 1 / r).max(2)[0] < self.hyp['anchor_t']  # compare
 # j = wh_iou(anchors, t[:, 4:6]) > model.hyp['iou_t']  # iou(3,n)=wh_iou(anchors(3,2), gwh(n,2))
 t = t[j]  # filter
  1. 最后根据留下的bbox,在上下左右四个网格四个方向扩增采样。
 gxy = t[:, 2:4]  # grid xy
 gxi = gain[[2, 3]] - gxy  # inverse
 j, k = ((gxy % 1 < g) & (gxy > 1)).T
 l, m = ((gxi % 1 < g) & (gxi > 1)).T
 j = torch.stack((torch.ones_like(j), j, k, l, m))
 t = t.repeat((5, 1, 1))[j]

详细代码解读

准备
def build_targets(self, p, targets):

P是网络预测的输出。
p的shape为:(batch_size,anchor_num,grid_cell,grid_cell,xywh+obj_confidence+classes_num)
在这里插入图片描述
P[0]的shape
在这里插入图片描述
P[1]的shape
在这里插入图片描述
P[2]的shape
在这里插入图片描述


targets是经过数据增强(mosaic等)后总的bbox。
targets的shape为:[num_obj, 6] , that number 6 means -> (img_index, obj_index, x, y, w, h)
在这里插入图片描述
在这里插入图片描述


na, nt = self.na, targets.shape[0]  # number of anchors, targets

在这里插入图片描述

tcls, tbox, indices, anch = [], [], [], []  

tcls:用来存储类别。
tbox:用来存储bbox
indices:用来存储第几张图片,当前层的第几个anchor,以及当前层grid的下标。

gain = torch.ones(7, device=self.device)  # normalized to gridspace gain

初始化为1,用来还原bbox为当前层的尺度大小。

 ai = torch.arange(na, device=self.device).float().view(na, 1).repeat(1, nt)  # same as .repeat_interleave(nt)

扩充anchor数量和当前bbox一样多。
ai是anchor的下标
在这里插入图片描述在这里插入图片描述

targets = torch.cat((targets.repeat(na, 1, 1), ai[..., None]), 2)  # append anchor indices

targets的shape变为(3,101,7)。
targets[0]对应第一个anchor对应的(image_id, cls, center_x,center_y, w, h,第个anchor)
在这里插入图片描述
targets[1]对应第一个anchor对应的(image_id, cls, center_x,center_y, w, h,第个anchor)在这里插入图片描述
targets[2]对应第一个anchor对应的(image_id, cls, center_x,center_y, w, h,第个anchor)在这里插入图片描述

# 预定义的偏移量
 g = 0.5  # bias
 off = torch.tensor(
     [
         [0, 0],
         [1, 0],
         [0, 1],
         [-1, 0],
         [0, -1],  # j,k,l,m
         # [1, 1], [1, -1], [-1, 1], [-1, -1],  # jk,jm,lk,lm
     ],
     device=self.device).float() * g  # offsets
for i in range(self.nl):    # 枚举每一层
anchors = self.anchors[i]   # 当前层anchor

self.anchors
在这里插入图片描述
self.anchors[0]得到第一层归一化后的anchor
在这里插入图片描述
乘8得到的
在这里插入图片描述
self.anchors[1]得到第二层归一化后的anchor
在这里插入图片描述
乘16得到的
在这里插入图片描述
self.anchors[2]得到第三层归一化后的anchor
在这里插入图片描述
乘以32得到的
在这里插入图片描述

gain[2:6] = torch.tensor(p[i].shape)[[3, 2, 3, 2]]  # xyxy gain

生成一个当前层的方格大小。
如果i=0
在这里插入图片描述
如果i=1,
在这里插入图片描述
如果i=2
在这里插入图片描述

t = targets * gain

将targets的大小映射到当前层,第六列是当前层的第几个anchor,第0列是位于哪张图片,第1列代表的是类别,2-5列是目标在当前层x,y,w,h。
下采样八倍的层
在这里插入图片描述

第一遍筛选
if nt:   # 如果存在目标
r = t[..., 4:6] / anchors[:, None]

r是指bbox与当前层三个anchor的高宽的比值。
在这里插入图片描述
r[0]
在这里插入图片描述
r[1]
在这里插入图片描述
r[2]
在这里插入图片描述

j = torch.max(r, 1 / r).max(2)[0] < self.hyp['anchor_t']  # compare

torch.max(r, 1 / r).max(2)[0] 为什么是[0]不是[1].[0]代表的是value,[1]代表的index。

在这里插入图片描述

torch.max(r, 1 / r).max(2)[1]

在这里插入图片描述

torch.max(r, 1 / r).max(1)[0]

按行获取最大值。
在这里插入图片描述

torch.max(r, 1 / r).max(1)[1]

按行获取最大值,返回索引。
在这里插入图片描述

t = t[j]  # filter

经过过滤后,全部汇总到来了一起。按照第六列anchor的顺序排列。
在这里插入图片描述

扩增正样本

接下来是扩增正样本

gxy = t[:, 2:4]  # grid xy  # 获取x,y
gxi = gain[[2, 3]] - gxy  # inverse

假设最后的特征图大小是8x8,有a-h8个目标边框如下。
在这里插入图片描述
下图中深灰色的表示满足条件的。
在这里插入图片描述

j, k = ((gxy % 1 < g) & (gxy > 1)).T
l, m = ((gxi % 1 < g) & (gxi > 1)).T

gxy % 1 < ggxi % 1 < g包含两个方向,x和y方向。
在这里插入图片描述

((gxy % 1 < g) & (gxy > 1)) #条件合并得到下图

在这里插入图片描述

(gxi % 1 < g) & (gxi > 1) # 条件合并得到下图

在这里插入图片描述

j = torch.stack((torch.ones_like(j), j, k, l, m))
t = t.repeat((5, 1, 1))[j] 
# yolov5不仅用目标中心点所在的网格预测该目标,还采用了距目标中心点的最近两个网格
# 所以有五种情况,网格本身,上下左右
|----------------------------------------------------------------------|
|			这里将t复制5个,然后使用j来过滤						   	       |
|	第一个t是保留经过第一步过滤留下的gtbox,因为上一步里面增加了一个全为true的维度|
|			第二个t保留了靠近方格左边的gtbox,						       |
|			第三个t保留了靠近方格上方的gtbox,						       |
|			第四个t保留了靠近方格右边的gtbox,						       |
|			第五个t保留了靠近方格下边的gtbox,						       |
|----------------------------------------------------------------------|

offsets = (torch.zeros_like(gxy)[None] + off[:, None])[j]  # 生成偏移矩阵

j的第一行全为1,意思是指经过第一步保留下的bbox所在的grid_cell为1.
在这里插入图片描述

 else:
     t = targets[0]
     offsets = 0
# Define
 bc, gxy, gwh, a = t.chunk(4, 1)  # (image, class), grid xy, grid wh, anchors
 a, (b, c) = a.long().view(-1), bc.long().T  # anchors, image, class
 gij = (gxy - offsets).long()     #减去偏置,得到更多的正样本所在的网格。
 gi, gj = gij.T  # grid indices

下面的四张图展示了gij = (gxy - offsets).long() 做了啥。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

**最终得到的结果如下**

在这里插入图片描述

# Append,将对应的结果存储下来。
indices.append((b, a, gj.clamp_(0, gain[3] - 1), gi.clamp_(0, gain[2] - 1)))  # image, anchor, grid indices
tbox.append(torch.cat((gxy - gij, gwh), 1))  # box
anch.append(anchors[a])  # anchors
tcls.append(c)  # class

tbox.append(torch.cat((gxy - gij, gwh), 1)) # box这句话做的如下:
在这里插入图片描述

Reference

  1. 感谢这位UP主的详细解释,本文的正样本采样细节参考了此UP主的PPT。yolo v5 解读,训练,复现
以下是 YOLOv5build_targets 函数的源码,该函数用于生成训练时所需的目标标签,其输入是一个包含了所有 Ground Truth 边界框的列表和一个预测的输出张量。它的输出是一个包含了每个预测边界框的目标类别、置信度得分以及边界框坐标调整量的张量。 ```python def build_targets(pred, targets, hyp): # pred : (batch_size, num_anchors, grid_size, grid_size, 5 + num_classes) # targets : (num_targets, 6) [batch_index, class_id, x, y, w, h] # hyp : dict containing hyperparameters ignore_threshold = hyp['ignore_thresh'] device = pred.device num_classes = hyp['num_classes'] anchors = hyp['anchors'] anchor_t = torch.tensor(anchors).float().to(device).view(1, -1, 1, 1, 2) b, a, gj, gi = targets[:, :4].long().t() target_cls = targets[:, 4].long() txywh = targets[:, 2:6] # ground truth box gxy, gwh = txywh[:, :2], txywh[:, 2:] gij = (gxy * hyp['nw']).floor() # iou of targets-anchors (using wh only) box1 = txywh[:, None, :].repeat(1, anchor_t.shape[1], 1) box2 = anchor_t.repeat(gxy.shape[0], 1, 1, 1, 1).view(-1, 4) box2 = torch.cat((torch.zeros_like(box2[:, :2]), box2[:, 2:]), 1) # convert [x,y,w,h] to [0,0,w,h] iou = bbox_iou(box1.view(-1, 4), box2, x1y1x2y2=False) # iou(box, anchor) # anchor boxes with highest IoU topi, topk = iou.topk(1) # ensure each target is matched to highest iou a = topk % anchor_t.shape[1] # anchor index b = topk // anchor_t.shape[1] # batch index # XY coordinates gxy /= hyp['stride'][0] gi *= 0 gj *= 0 # target GT box XY coordinates with respect to cell txy = gxy - torch.cat((gxy.floor(),), 1) # Width and height (yolo method) tw, th = torch.sqrt(gwh / anchor_t[b, a, gj, gi]).unbind(1) # tw, th = torch.log(gwh / anchor_t[b, a, gj, gi]).unbind(1) # yolo method # target GT box normalized width and height (yolo method) # twh = torch.log(gwh / anchor_t[b, a, gj, gi]) twh = torch.sqrt(gwh / anchor_t[b, a, gj, gi]) # cls, weight, class_mask tcls = torch.zeros_like(pred[..., 5:]) tcls[b, a, gj, gi, target_cls] = 1.0 tconf = torch.ones_like(pred[..., 4:5]) tconf[b, a, gj, gi] = 0.0 # iou of targets (using wh only) iou = bbox_iou(txywh, torch.cat((gij.float(), twh.float()), 1), x1y1x2y2=False) # iou(target, anchor) # reject anchors below ignore_threshold iou tconf[iou < ignore_threshold] = 0.0 # xy delta txy = txy - gij # wh delta twh = torch.log(gwh / anchor_t[b, a, gj, gi]) twh[(iou < ignore_threshold).view(twh.shape)] = 0.0 # set non-matching boxes to 0 # create output tensor output = torch.cat((txy, twh, tconf, tcls), -1) return output ``` 该函数首先将预测的输出张量和目标 Ground Truth 边界框的信息进行解析和匹配,然后计算每个预测边界框与其对应的 Ground Truth 边界框的 IoU 值,最后根据阈值筛选出需要忽略的边界框以及需要进行训练的边界框,并生成相应的训练标签。具体实现可以参考以上源码注释。
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gorgeous(๑>؂<๑)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值