build_target函数是将box标签和anchor进行匹配,其中FPN特征图为三层,分别为降采样8,16,32,代码展示与解释如下:
def build_targets(p, targets, model):
# Build targets for compute_loss(), input targets(image,class,x,y,w,h)
获取yolov5网络输出
det = model.module.model[-1] if is_parallel(model) else model.model[-1] # Detect() module
获取每层特征图anchor的数量na(=3),和每个batch box标签的个数nt
na, nt = det.na, targets.shape[0] # number of anchors, targets
初始化每个batch box的信息:
tcls表示类别;
tbox表示标记的box和生成的box的左边(x,y,w,h)
indices:图像索引,选取的anchor的索引(每层特征图中每个网格初始化3个),每层特征图上选取的网格点坐标(i,j)
anch选取的anchor的索引
tcls, tbox, indices, anch = [], [], [], []
将targets由(n*7)变为(3*n*7) 3个anchor,每个anchor都对应所有的box标签
gain = torch.ones(7, device=targets.device) # normalized to gridspace gain
ai = torch.arange(na, device=targets.device).float().view(na, 1).repeat(1, nt) # same as .repeat_interleave(nt)
targets = torch.cat((targets.repeat(na, 1, 1), ai[:, :, None]), 2) # append anchor indices
为扩充标记的box添加偏置,具体扩充规则为在下边
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=targets.device).float() * g # offsets
对每个特征图进行操作,顺序为降采样8-16-32
for i in range(det.nl):
获取该层特征图中的anchor
anchors = det.anchors[i]
获取该层特征图的尺寸(1 1 w h w h 1)
gain[2:6] = torch.tensor(p[i].shape)[[3, 2, 3, 2]] # xyxy gain
# Match targets to anchors
将box坐标转换到特征图上(在box标签生成中,对box坐标进行了归一化,即除以图像的宽高)
通过将归一化的box乘以特征图尺度,从而将box坐标投影到特征图上
t = targets * gain
if nt:
当该batch中存在box标签时,获取每个box对应的anchor,并生成符合规定的anchor
# Matches
获取box和三个anchor对应的长宽比
r = t[:, :, 4:6] / anchors[:, None] # wh ratio
如果每个box和anchor的长宽比中最大值小于model.hyp['anchor_t'](4.0),则该box为合适的box,并获取到对应的anchor
j = torch.max(r, 1. / r).max(2)[0] < model.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
# Offsets
获取选择完成的box的中心点左边-gxy(以图像左上角为坐标原点),并转换为以特征图右下角为坐标原点的坐标-gxi
gxy = t[:, 2:4] # grid xy
gxi = gain[[2, 3]] - gxy # inverse
分别判断box的(x,y)坐标是否大于1,并距离网格左上角的距离(准确的说是y距离网格上边或x距离网格左边的距离)距离小于0.5,如果(x,y)中满足上述两个条件,则选中
j, k = ((gxy % 1. < g) & (gxy > 1.)).T
对转换之后的box的(x,y)坐标分别进行判断是否大于1,并距离网格右下角的距离(准确的说是y距离网格下边或x距离网格右边的距离)距离小于0.5,如果(x,y)中满足上述两个条件,为Ture
l, m = ((gxi % 1. < g) & (gxi > 1.)).T
j = torch.stack((torch.ones_like(j), j, k, l, m))
获取所有符合要求的box
t = t.repeat((5, 1, 1))[j]
生成所有box对应的偏置,原始box偏置为0
offsets = (torch.zeros_like(gxy)[None] + off[:, None])[j]
else:
t = targets[0]
offsets = 0
# Define
获取每个box的图像索引和类别
b, c = t[:, :2].long().T # image, class
获取box的xy和wh
gxy = t[:, 2:4] # grid xy
gwh = t[:, 4:6] # grid wh
获取每个box所在的网格点坐标
gij = (gxy - offsets).long()
gi, gj = gij.T # grid xy indices
# Append
获取每个anchor索引
a = t[:, 6].long() # anchor indices
保存图像序号 anchor序号和网格点坐标
indices.append((b, a, gj.clamp_(0, gain[3] - 1), gi.clamp_(0, gain[2] - 1))) # image, anchor, grid indices
获取(x,y)相对于网格点的偏置,以及box的宽高
tbox.append(torch.cat((gxy - gij, gwh), 1)) # box
anch.append(anchors[a]) # anchors
tcls.append(c) # class
return tcls, tbox, indices, anch
重点:对box标记进行扩充:
首先获取box的中心点坐标gxy(以图像左上角为坐标原点),并转换为以特征图右下角为坐标原点的坐标-gxi
gxy = t[:, 2:4] # grid xy
gxi = gain[[2, 3]] - gxy # inverse
对box的(x,y)坐标分别进行判断是否大于1,并距离网格左上角的距离(准确的说是y距离网格上边或x距离网格左边的距离)距离小于0.5,如果(x,y)中满足上述两个条件,为Ture
j, k = ((gxy % 1. < g) & (gxy > 1.)).T
eg:存在4个box
[ [4.3,2.1]
[2.61,2.45]
[0.37,3.57]
[3.59,4.21] ]
则j=[T F F F] k=[T T F T]
对转换之后的box的(x,y)坐标分别进行判断是否大于1,并距离网格右下角的距离(准确的说是y距离网格下边或x距离网格右边的距离)距离小于0.5,如果(x,y)中满足上述两个条件,为Ture
l, m = ((gxi % 1. < g) & (gxi > 1.)).T
然后将原始box和扩增的box进行合并
j = torch.stack((torch.ones_like(j), j, k, l, m))
*********************************************************************************************************************************************************************************************************************
2021.5.7补充
anchor与gt匹配
1.将batch中的gt(box+class)扩张3倍,对应每层featuremap中的3个anchor(n*5-->3*n*5),假定现有的3个anchor和每个gt都一一对应
2.对每层特征图进行相同的操作:
a.将gt信息投影到特征图上
b.将gt中每个box的宽高和anchor的宽高进行做比,将最大比例小于预先设定值的gt保存下来,并与anchor一一对应(只要gt和anchor的宽高比小于某个阈值,则认为该anchor和gt匹配上了)
c.对b中保存下来的gt进行扩充: 1) 保存现有所有的gt 2)保存box中心点坐标Xc距离网格左边的距离小于0.5且坐标大于1的box 3)保存box中心点坐标Yc距离网格上边的距离小于0.5且坐标大于1的box 4)保存box中心点坐标Xc距离网格右边的距离小于0.5且坐标大于1的box 5)保存box中心点坐标Yc距离网格下边的距离小于0.5且坐标大于1的box 将该5中box构成需要的gt
补充:为什么会取距离四边小于0.5的点,是因为等于0.5时,我们认为该box正好落到该网格中,但是小于0.5时,可能是因为在网络不断降采样时,对特征图尺度进行取整导致box中心产生了偏差,所以作者将小于0.5的box减去偏执1,使得box中心移动到相邻的特征图网格中,从而对正样本进行扩充,保证了偏差导致的box错位以及扩充了正样本的个数.<个人理解,不一定对>
3.获取保留的gt中中心点坐标和网格点坐标的偏执与box的wh构成新的box信息(Cx-X,Cy-Y,w,h)
4.最后获取4个向量 a.indice[图像序号,anchor序号,网格点坐标x,网格点坐标y] b.tbox box的对应坐标[Cx-X,Cy-Y,w,h] c.anch tbox对应的anchor索引 d.tcls tbox对应的类别
详细信息见上边的代码详解