YOLO3代码解析

有一个重要的点,在YOLO中每个gt box只选择一个anchor或者pred box进行训练,而在SSD或者其他的里面是可能选择多个的

 

 

定位算法中解码和Loss的计算是核心内容,各个算法在解码时,虽然思想差不多,但是还是 有些小的区别,这里进行一个汇总比较

(1) 所谓的解码,就是从feature map解码到检测box的过程

(2) 算loss的时候,也是包含了一个解码过程,同时也包含了loss的target的计算,最后是loss

1: Single shot 算法, YoLo1, YoLo3, SSD

1.2.1 Yolo3的解码

   80 conv   1024  3 x 3 / 1    13 x  13 x 512   ->    13 x  13 x1024
   81 conv     75  1 x 1 / 1    13 x  13 x1024   ->    13 x  13 x  75
   82 detection

   92 conv    512  3 x 3 / 1    26 x  26 x 256   ->    26 x  26 x 512
   93 conv     75  1 x 1 / 1    26 x  26 x 512   ->    26 x  26 x  75
   94 detection

  104 conv    256  3 x 3 / 1    52 x  52 x 128   ->    52 x  52 x 256
  105 conv     75  1 x 1 / 1    52 x  52 x 256   ->    52 x  52 x  75
  106 detection

Yolo3的最后包含坐标含义的feature map输出的形状是 (nB,75, nH, nW) 是在3个不同的feature map上来解码出Box值,为 (1,75,13,13) (1,75,26,26), (1,75,52,52), 其中75 = 3x(5+class numer 20) , 这里的5代表的是4个坐标值和Conf置信度值

(1) 这里20是20个VOC类别,如果是更换类别,那么需要修改81,93,105层的filter个数即可

(2) 这里的(nB,75, nH, nW) 是代表了有75个平面,每个平面大小是(nH,nW), 相当于是把原图划分成了(nH,nW)个cell. 每个cell是预测3个box, 所以是 3x(5+20) = 75个平面

(3)每个cell在预测3个Box的时候,是以一定的参考Box来进行预测的,这里的参考主要是参考Box的高度和宽度信息, 叫做anchor box, 或者所谓的prior box, Yolo3 中的anchor box 是 [10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326]. 其中[13x13]的feature map是对应的anchor[ 116,90,  156,198,  373,326]以此类推

对于前向传播, 解码的话,是对3个不同feature map做同样处理,最后把各个feature map得到的所有Box进行汇总,最后根据conf值来做一个NMS得到最终的结果

对于前向传播解码过程如下:

# output 的形状是(1,255,13,13)
# anchors 的形状是(6,)
# num_anchors 是一个int数据 = 3
# conf_thresh = 0.5
# num_classes = 80
# 这个函数的作用是对于输入的预测数据,结合anchor数据,来对预测数据进行解码,恢复到真正的检测坐标数据

def get_region_boxes(output, conf_thresh, num_classes, anchors, num_anchors, only_objectness=1, validation=False, use_cuda=True):
    device = torch.device("cuda" if use_cuda else "cpu")
    anchors = anchors.to(device)
    anchor_step = anchors.size(0)//num_anchors
    if output.dim() == 3:
        output = output.unsqueeze(0)
    batch = output.size(0)
    assert(output.size(1) == (5+num_classes)*num_anchors)
    h = output.size(2)#13
    w = output.size(3)#13
    cls_anchor_dim = batch*num_anchors*h*w #nB*nA*nH*nW

    t0 = time.time()
    all_boxes = []#存放的是所有batch的box数据,是一个[[[]]]类型数据,[batch[[box]]]
    ##-----------------------做解码工作-------begin-------------------------------------------------------------------------------------------
    ## 核心的思路是所有的处理都归结到(batch*num_anchors*h*w,)上

    #本来数据的形状是(batch,num_anchors*(5+num_classes),h,w)
    #先变到(batch*num_anchors, 5+num_classes,h*w)
    #然后变到(5+num_classes,batch*num_anchors,h*w)
    #然后是(5+num_classes,batch*num_anchors*h*w) --- 相当关于是一列是一个Box所具有的数据,列的索引是batch*num_anchors*h*w
    output = output.view(batch*num_anchors, 5+num_classes, h*w).transpose(0,1).contiguous().view(5+num_classes, cls_anchor_dim)

    #把[0,w-1]之间分成w份,相当于就是生成0,1,3,4在w=5时
    #grid_x = torch.linspace(0, w-1, w).repeat(h,1)相当于是对图像进行分成网格,然后生成每个格子的x坐标,数据大小是(h,w)每个位置是网络的x坐标值
    #repeat(batch*num_anchors, 1, 1)对这个平面坐标数据,进行整体平面复制
    #然后把(batch*num_anchors, h, w)的平面x坐标数据展成1维,变成(batch*num_anchors*h*w)
    grid_x = torch.linspace(0, w-1, w).repeat(batch*num_anchors, h, 1).view(cls_anchor_dim).to(device)
    #(batch*num_anchors, h, w)的平面y坐标数据
    grid_y = torch.linspace(0, h-1, h).repeat(w,1).t().repeat(batch*num_anchors, 1, 1).view(cls_anchor_dim).to(device)
    ix = torch.LongTensor(range(0,2)).to(device)

    #anchors包含的是原始的方框,针对stride进行归一化后的数据
    #变成3行两列,然后取第一个元素,得到所有anchor的w 结果形状是(3,1),结果是2维数据
    #pytorch的index_select得到的结果是和原来Tensor一样的
    anchor_w = anchors.view(num_anchors, anchor_step).index_select(1, ix[0]).repeat(1, batch, h*w).view(cls_anchor_dim)#1维度数据(nB*nA*nH*nW,)
    anchor_h = anchors.view(num_anchors, anchor_step).index_select(1, ix[1]).repeat(1, batch, h*w).view(cls_anchor_dim)#1维度数据(nB*nA*nH*nW,)

    # output[0]对应的是(0,batch*num_anchors*h*w) x坐标数据
    # output[1]对应的是(1,batch*num_anchors*h*w) y坐标数据
    #这里的xs,ys,ws,hs都是(nB*nA*nH*nW,)形状
    xs, ys = torch.sigmoid(output[0]) + grid_x, torch.sigmoid(output[1]) + grid_y
    ws, hs = torch.exp(output[2]) * anchor_w.detach(), torch.exp(output[3]) * anchor_h.detach()#Returns a new Tensor, detached from the current graph
    det_confs = torch.sigmoid(output[4])

    # by ysyun, dim=1 means input is 2D or even dimension else dim=0
    cls_confs = torch.nn.Softmax(dim=1)(output[5:5+num_classes].transpose(0,1)).detach()
    cls_max_confs, cls_max_ids = torch.max(cls_confs, 1)
    #(nB*nA*nH*nW,)形状
    cls_max_confs = cls_max_confs.view(-1)# view(-1)是把数据变成了1维
    cls_max_ids = cls_max_ids.view(-1)
    t1 = time.time()
    
    sz_hw = h*w
    sz_hwa = sz_hw*num_anchors
    det_confs = convert2cpu(det_confs)
    cls_max_confs = convert2cpu(cls_max_confs)
    cls_max_ids = convert2cpu_long(cls_max_ids)
    xs, ys = convert2cpu(xs), convert2cpu(ys)
    ws, hs = convert2cpu(ws), convert2cpu(hs)

    if validation:
        cls_confs = convert2cpu(cls_confs.view(-1, num_classes))

    t2 = time.time()
    for b in range(batch):#对所有的图像遍历
        boxes = []#存放了一个图像的所有预测Box,结果是[[box data]]
        #对当前图像的对所有预测Box进行遍历,把置信度满足条件的保存下来
        for cy in range(h):
            for cx in range(w):
                for i in range(num_anchors):

                    ind = b*sz_hwa + i*sz_hw + cy*w + cx
                    det_conf =  det_confs[ind]
                    if only_objectness:
                        conf = det_confs[ind]
                    else:
                        conf = det_confs[ind] * cls_max_confs[ind]
    
                    if conf > conf_thresh:
                        bcx = xs[ind]
                        bcy = ys[ind]
                        bw = ws[ind]
                        bh = hs[ind]
                        cls_max_conf = cls_max_confs[ind]
                        cls_max_id = cls_max_ids[ind]
                        #box中存放的是(x,y,w,h,conf,max cls conf, cls_max_id)
                        box = [bcx/w, bcy/h, bw/w, bh/h, det_conf, cls_max_conf, cls_max_id]

                        if (not only_objectness) and validation:
                            for c in range(num_classes):
                                tmp_conf = cls_confs[ind][c]
                                if c != cls_max_id and det_confs[ind]*tmp_conf > conf_thresh:
                                    box.append(tmp_conf)
                                    box.append(c)

                        boxes.append(box)
        all_boxes.append(boxes)


 
 
 
#output形状是(5+cls_num,nB*nA*nH*nW)
#gird_x grid_y 是 (nB*nA*nH*nW)
#这下面所有参与运算的数据形状都是(nB*nA*nH*nW),相当于对于各个pred box在列的方向上进行索引
xs, ys = torch.sigmoid(output[0]) + grid_x, torch.sigmoid(output[1]) + grid_y
ws, hs = torch.exp(output[2]) * anchor_w.detach(), torch.exp(output[3]) * anchor_h.detach()
#Returns a new Tensor, detached from the current graph
det_confs = torch.sigmoid(output[4])

上面的代码反应的是对所有个pred box进行一起计算,一共所具有的pred box个数是(nB*nA*nH*nW)个, 其中grid_x, grid_y是各个pred box在cell上的索引,是整数,比如0,1,2,....12.

anchor_w 是anchor像素大小对应到feature map的大小,就是除以feature map的stride = 13

相当于对于xs, grid_x, anchor_w这些和大小位置相关的数据,都会映射到同一个参考的feature map上. 具体公式就是

其中的Tx为网络的预测值, Bx是加上anchor数据以后的值, Pw, Ph是anchor的高宽度映射到特征图上的大小, b值是实际计算出来的数据,具体解析如下图

 

对于训练过程Loss计算如下:

 

 

 

 

 

 

 

2:

 

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值