包括
YOLO V3是目标检测,目标检测本质上是对一幅图像的不同区域做分类。使用目标分类的网络对对图像的不同区域做分类,就可以得知图像中是否包含某类物体,物体的概率是什么。
所以,最开始是滑动窗口技术
滑动窗口技术:它将检测问题转化为了图像分类问题 ,采用不同大小和比例(宽高比)的窗口在整张图片上以一定的步长进行滑动,然后对这些窗口对应的区域做图像分类,这样就可以实现对整张图片的检测了(用窗口在整张图片上滑动,实现对整张图片的目标检测) ,但是这个方法有致命的缺点,就是你并不知道要检测的目标大小是什么规模,所以你要设置不同大小和比例的窗口去滑动,而且还要选取合适的步长。这样,对一张图片目标检测可能需要调用图像分类的网络模型成百上千次,速度太慢。
参考:https://blog.csdn.net/Katherine_hsr/article/details/80119120
滑动窗口太慢,将全连接层转为卷积层,使得一次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]
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