YOLO V3的正向传播过程

包括

 

YOLO V3是目标检测,目标检测本质上是对一幅图像的不同区域做分类。使用目标分类的网络对对图像的不同区域做分类,就可以得知图像中是否包含某类物体,物体的概率是什么。

所以,最开始是滑动窗口技术

滑动窗口技术:它将检测问题转化为了图像分类问题 ,采用不同大小和比例(宽高比)的窗口在整张图片上以一定的步长进行滑动,然后对这些窗口对应的区域做图像分类,这样就可以实现对整张图片的检测了(用窗口在整张图片上滑动,实现对整张图片的目标检测) ,但是这个方法有致命的缺点,就是你并不知道要检测的目标大小是什么规模,所以你要设置不同大小和比例的窗口去滑动,而且还要选取合适的步长。这样,对一张图片目标检测可能需要调用图像分类的网络模型成百上千次,速度太慢。

参考:https://blog.csdn.net/Katherine_hsr/article/details/80119120 

https://flat2010.github.io/2018/02/16/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%EF%BC%9A%E7%9B%AE%E6%A0%87%E6%A3%80%E6%B5%8B%E4%B9%8B%E4%B8%80%EF%BC%88%E5%9F%BA%E7%A1%80%E7%AF%87%EF%BC%89/

滑动窗口太慢,将全连接层转为卷积层,使得一次CNN就可以实现窗口滑动的所有子区域的分类预测。这样,对于一种形状只需要运行一次图片分类的网络模型。但是,对于 不同形状,不同分辨率,则需要再次调用该网络模型去预测。

 

面对不同形状的怎么办?

R-CNN提出了候选区域的做法,先从每张图生成多个候选区域,同时在网络模型中加入对位置框的回归。

但是2阶段的检测还是比较慢,所以YOLO提出一阶段的检测。YOLO不需要事前从整个图片中提取候选区域,而是直接指定多个候选区域。通过预先在coco数据集上聚类,得到9种尺度的锚点(候选框),几乎可以检测到所有的物体。

然后,将原始图片按分割为互不重合的小方块grid,然后通过网络模型产生各个小方块的特征图,特征图的每个元素对应原始图片的一个小方块。如上面的全连接层转为卷积层的图所示。然后各个grid以指定的锚点框为基础,用网络模型的后续部分回归,对指定的锚点框微调,得到更接近真是的物体坐标。

 

锚点框:

 

蓝色框为聚类得到的先验框。黄色框式ground truth,红框是对象中心点所在的网格

程序过程:(yolov3.cfg中的yolo层)
该程序对应YOLO V3的yolo层:

[yolo]
mask = 3,4,5
anchors = 10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326
classes=80
num=9
jitter=.3
ignore_thresh = .5
truth_thresh = 1
random=1

 

#把检测特征图(原来的prediction)根据锚点框的长宽转换后,得到新的prediction,包含各个grid预测的左上角坐标,右下角坐标,各个类的概率
#当把原始图片分为13*13的grid时,prediction的维度为[1,507,85],anchors为3个矩形锚点框(候选区域)的长宽,num_class是要分类的个数
def predict_transform(prediction, inp_dim, anchors, num_classes, CUDA = True):

    
    batch_size = prediction.size(0)
    stride =  inp_dim // prediction.size(2)
    grid_size = inp_dim // stride
    bbox_attrs = 5 + num_classes
    num_anchors = len(anchors)
    
    prediction = prediction.view(batch_size, bbox_attrs*num_anchors, grid_size*grid_size)
    prediction = prediction.transpose(1,2).contiguous()
    prediction = prediction.view(batch_size, grid_size*grid_size*num_anchors, bbox_attrs)
    anchors = [(a[0]/stride, a[1]/stride) for a in anchors]

    #对x,y,objectness confidence执行sigmoid操作(常规操作)         
    prediction[:,:,0] = torch.sigmoid(prediction[:,:,0])
    prediction[:,:,1] = torch.sigmoid(prediction[:,:,1])
    prediction[:,:,4] = torch.sigmoid(prediction[:,:,4])
    
    #将每个框预测到的偏移与各个框的坐标结合起来 每个框在每个锚点都有一个预测的结果,13*13个框在3个锚点共预测到507个    Add the center offsets
    grid = np.arange(grid_size)
    a,b = np.meshgrid(grid, grid)

    x_offset = torch.FloatTensor(a).view(-1,1)#torch.FloatTensor讲numpy转为tensor,view表示resize该tensor,-1 表示根据其他维度推算
    y_offset = torch.FloatTensor(b).view(-1,1)

    if CUDA:
        x_offset = x_offset.cuda()
        y_offset = y_offset.cuda()

    x_y_offset = torch.cat((x_offset, y_offset), 1).repeat(1,num_anchors).view(-1,2).unsqueeze(0)

    prediction[:,:,:2] += x_y_offset

    #log space transform height and the width
    anchors = torch.FloatTensor(anchors)
    anchors = anchors.repeat(grid_size*grid_size, 1).unsqueeze(0)
    prediction[:,:,2:4] = torch.exp(prediction[:,:,2:4])*anchors
    #将 sigmoid 激活函数应用到类别分数中(和上面的object confidence做sigmoid一致)
    prediction[:,:,5: 5 + num_classes] = torch.sigmoid((prediction[:,:, 5 : 5 + num_classes]))
    #我们要将检测图的大小调整到与输入图像大小一致。边界框属性根据特征图的大小而定(如 13 x 13)。如果输入图像大小是 416 x 416,那么我们将属性乘 32,或乘 stride 变量
    prediction[:,:,:4] *= stride
    
    return prediction

85个输出:1(是否有物体)+4(物体的坐标预测)+80(80个类别的概率)

 

prediction = prediction.view(batch_size, bbox_attrs*num_anchors, grid_size*grid_size)
prediction = prediction.transpose(1,2).contiguous()
prediction = prediction.view(batch_size, grid_size*grid_size*num_anchors, bbox_attrs)#prediction的shape为(1,507,85) 507=13*13*3 表示416*416的图像被分割为13*13个grid,每个grid预测时按照3个anchors预测

第一个grid预测3个anchor的值
第一个anchor为:print(prediction[0,0,:])
tensor([ 0.1687,  0.2160, -0.1598, -0.0468, -0.0427,  0.0126,  0.0879,
        -0.0058, -0.3109,  0.0568,  0.3030, -0.0187,  0.0209, -0.0308,
         0.0979,  0.0542, -0.0951,  0.2602, -0.0937,  0.3513,  0.0284,
        -0.0948,  0.3172,  0.0031,  0.1381,  0.0873,  0.1272, -0.2116,
         0.1677,  0.1234,  0.0774, -0.1037,  0.0842, -0.1206, -0.2690,
        -0.0981,  0.1359,  0.0957, -0.0036,  0.1547,  0.1099,  0.0510,
        -0.1767, -0.0137, -0.0110,  0.1137,  0.0124, -0.1334,  0.1761,
        -0.1075, -0.0598,  0.1686, -0.0652,  0.2351,  0.2858, -0.1416,
        -0.2159,  0.1783,  0.2271,  0.1334,  0.0835,  0.1985, -0.2241,
        -0.0598, -0.1408, -0.3485,  0.1380,  0.3306,  0.1565,  0.2011,
        -0.0125, -0.2907,  0.1501, -0.0888, -0.2974,  0.0899,  0.1205,
        -0.2026, -0.0852, -0.0699, -0.1379, -0.0172, -0.2514, -0.3822,
        -0.0380])

print(prediction[0,1,:])
tensor([-0.1210,  0.1884,  0.0698, -0.1114, -0.2296, -0.1658,  0.2630,
        -0.1176, -0.0896, -0.0862,  0.2513,  0.1706, -0.1084,  0.1039,
        -0.1577, -0.0691,  0.0443,  0.1573, -0.1162,  0.0832,  0.1518,
         0.3243, -0.2090,  0.0805, -0.0794,  0.2112,  0.0047,  0.0120,
         0.1140,  0.1384, -0.0390, -0.1619,  0.1386,  0.0261, -0.1638,
        -0.0241, -0.0300,  0.2209, -0.0207,  0.2450,  0.0203, -0.0445,
        -0.3129, -0.1907,  0.1097, -0.1787, -0.0279,  0.1678,  0.1394,
        -0.1979, -0.2457,  0.2520, -0.1199, -0.0397, -0.1455,  0.1495,
         0.2098,  0.0341, -0.0358,  0.1093, -0.1630, -0.2492,  0.0214,
        -0.0564,  0.0293,  0.2362, -0.0081,  0.0428, -0.3114,  0.1570,
        -0.1551,  0.0260, -0.4375,  0.1089,  0.0207, -0.2286,  0.0641,
        -0.1527,  0.1783, -0.0611, -0.0135,  0.2314,  0.2777, -0.1280,
        -0.2005])

print(prediction[0,2,:])
tensor([ 0.0945, -0.0436, -0.2044, -0.0005, -0.0211,  0.3712,  0.0942,
        -0.0609,  0.3588,  0.0289, -0.0122,  0.2524, -0.0306, -0.1354,
        -0.0279, -0.0972,  0.3366, -0.3283,  0.0898,  0.0066,  0.1496,
        -0.1533, -0.1872, -0.0561, -0.0982,  0.0837, -0.0135, -0.0318,
        -0.0830,  0.0366, -0.0075, -0.1409, -0.0882,  0.0624, -0.0007,
         0.2245, -0.0043, -0.0566, -0.0229,  0.0237, -0.2776,  0.0352,
        -0.3460, -0.1240,  0.0436,  0.0476, -0.1128, -0.0576,  0.1176,
        -0.1074, -0.0769,  0.0880,  0.1126, -0.3152,  0.4340, -0.1158,
        -0.1556,  0.1147, -0.0986, -0.1774, -0.0336,  0.0139,  0.2729,
        -0.1291,  0.1897, -0.0304, -0.1641,  0.1577, -0.2878,  0.0057,
        -0.0598, -0.1042,  0.0453,  0.0318,  0.1070, -0.0700, -0.1345,
        -0.0203, -0.2573,  0.0953, -0.0724, -0.0897, -0.1414, -0.1390,
         0.1357])


anchors = [(a[0]/stride, a[1]/stride) for a in anchors]   #讲anchors的实际大小转变为grid的倍数[(3.625, 2.8125), (4.875, 6.1875), (11.65625, 10.1875)]

 

#对x,y,objectness执行sigmoid操作  Sigmoid the  centre_X, centre_Y. and object confidencce
    prediction[:,:,0] = torch.sigmoid(prediction[:,:,0])
    prediction[:,:,1] = torch.sigmoid(prediction[:,:,1])
    prediction[:,:,4] = torch.sigmoid(prediction[:,:,4])

print(prediction[0,0,:]):tensor([ 0.5421,  0.5538, -0.1598, -0.0468,  0.4893,......
print(prediction[0,1,:]): tensor([ 0.4698,  0.5470,  0.0698, -0.1114,  0.4429,......                            
print(prediction[0,2,:]): tensor([ 0.5236,  0.4891, -0.2044, -0.0005,  0.4947......

 

#将每个框预测到的偏移与各个框的坐标结合起来 每个框在每个锚点都有一个预测的结果,13*13个框在3个锚点共预测到507个    Add the center offsets
    grid = np.arange(grid_size)
    a,b = np.meshgrid(grid, grid)
	x_offset = torch.FloatTensor(a).view(-1,1)#torch.FloatTensor讲numpy转为tensor,view表示resize该tensor,-1 表示根据其他维度推算
    y_offset = torch.FloatTensor(b).view(-1,1)
a:
Out[5]: 
array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12],
       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12],
       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12],
       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12],
       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12],
       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12],
       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12],
       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12],
       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12],
       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12],
       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12],
       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12],
       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12]])

b
Out[6]: 
array([[ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1],
       [ 2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2],
       [ 3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3],
       [ 4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4],
       [ 5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5],
       [ 6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6,  6],
       [ 7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7],
       [ 8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8,  8],
       [ 9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9],
       [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10],
       [11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11],
       [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12]])

x_offset,y_offset是将a,b转为一维向量,向量的shape为169*1

 

x_y_offset = torch.cat((x_offset, y_offset), 1).repeat(1,num_anchors).view(-1,2).unsqueeze(0)
向量维数为(1,507,2)其中507=169*num_anchors(3),2是因为(x_offset, y_offset)的组合

 

prediction[:,:,:2] += x_y_offset#将prediction预测的各个坐标偏移与坐标相加
print(prediction[0,0,:2]):tensor([ 0.5236,  0.4891])  与坐标(0,0)相加
print(prediction[0,3,:2]):tensor([ 1.5064,  0.5071]) 与坐标(1,0)相加
print(prediction[0,6,:2]):tensor([ 2.5359,  0.5364])   与坐标(2,0)相加

 

#log space transform height and the width
    anchors = torch.FloatTensor(anchors)
	anchors = anchors.repeat(grid_size*grid_size, 1).unsqueeze(0)#anchors的shape为(1,507,2)507=3×grid_size*grid_size,3是有3个锚点,grid_size*grid_size是说每个grid都用这3个锚点去预测,2是anchors本来就是2维的,分别表示anchor的长和宽相对于grid的倍数
    prediction[:,:,2:4] = torch.exp(prediction[:,:,2:4])*anchors#prediction[:,:,2:4]的shape为(1,507,2)。507是3*13*13,3是每个grid有3个对应的anchors,13*13是该图分割的所有的grids,2是预测的bx,by(即预测宽度,高度)。anchors的shape为(1,507,2),torch.exp(prediction[:,:,2:4])*anchors是对预测的宽度高度做变换得到实际的宽度和高度相对与grid的倍数。预测的框是小于anchors的

anchors:

prediction[:,:,2:4]

图2 指数函数增减性

3.0897 = e**(-0.1598)*3.6250
2.6838 = e**(-0.0468)*2.8125

 

 #将 sigmoid 激活函数应用到各个类别概率中
    prediction[:,:,5: 5 + num_classes] = torch.sigmoid((prediction[:,:, 5 : 5 + num_classes]))
    #我们要将检测图的大小调整到与输入图像大小一致,上面的计算是按grid计算,转变为按照resolution计算(将0-3都乘以stride)。
    prediction[:,:,:4] *= stride
return prediction
#维度 [1,507,85]
预测到的中心x坐标 预测到的中心y坐标  物体的长度 物体的宽度 是否包含目标  如果包含目标,各个目标的置信度(条件概率)
[0:0,]第一个grid按照第一个anchor预测时 预测到的值 tensor([ 17.3465,  17.7216,  98.8718,  85.8822,   0.4893,   0.5032,....
[0:1,]第一个grid按照第二个anchor预测时 预测到的值 tensor([  15.0336,   17.5026,  167.2754,  177.1349,    0.4429,    0.4587,...  [0:2,]第一个grid按照第三个anchor预测时 预测到的值 tensor([  16.7550,   15.6516,  304.0566,  325.8223,    0.4947,    0.5917,... 

根据上述分析,锚框不会影响各个grid是否有object的概率,也不会影响各个类别的物体的概率。这些概率由划分的grid在预测时直接确定。锚框的选择只会影响预测的坐标。预测的坐标是在锚框确定的矩形上的微调。

 

将不同的分割组合(3种分割,13*1,26*26,52*52,)得到张量的形状为 1×10647×85,第一个维度为批量大小,这里我们只使用了单张图像。对于批量中的图像,我们会有一个 100647×85 的表,它的每一行表示一个边界框(4 个边界框属性、1 个 objectness 分数和 80 个类别分数)

 

边框的预测过程是边框回归(就是根据大量的输入预测边框相对锚点的变化),参考https://www.jianshu.com/p/cad68ca85e27

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux创始人LinusTorvalds有一句名言:Talk is cheap, Show me the code.(冗谈不够,放码过来!)。 代码阅读是从入门到提高的必由之路。尤其对深度学习,许多框架隐藏了神经网络底层的实现,只能在上层调包使用,对其内部原理很难认识清晰,不利于进一步优化和创新。  YOLOv3是一种基于深度学习的端到端实时目标检测方法,以速度快见长。YOLOv3的实现Darknet是使用C语言开发的轻型开源深度学习框架,依赖少,可移植性好,可以作为很好的代码阅读案例,让我们深入探究其实现原理。  本课程将解析YOLOv3的实现原理和源码,具体内容包括: YOLO目标检测原理  神经网络及Darknet的C语言实现,尤其是反向传播的梯度求解和误差计算 代码阅读工具及方法 深度学习计算的利器:BLAS和GEMM GPU的CUDA编程方法及在Darknet的应用 YOLOv3的程序流程及各层的源码解析本课程将提供注释后的Darknet的源码程序文件。  除本课程《YOLOv3目标检测:原理与源码解析》外,本人推出了有关YOLOv3目标检测的系列课程,包括:   《YOLOv3目标检测实战:训练自己的数据集》  《YOLOv3目标检测实战:交通标志识别》  《YOLOv3目标检测:原理与源码解析》  《YOLOv3目标检测:网络模型改进方法》 建议先学习课程《YOLOv3目标检测实战:训练自己的数据集》或课程《YOLOv3目标检测实战:交通标志识别》,对YOLOv3的使用方法了解以后再学习本课程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值