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对应的类别

详细信息见上边的代码详解