一.概述
YOLO算法的基本思想是:首先通过特征提取网络对输入图像提取特征,得到一定size的feature map,比如13*13,然后将输入图像分成13*13个grid cell,接着如果ground truth中某个object的中心坐标落在哪个grid cell中,那么就由该grid cell来预测该object,因为每个grid cell都会预测固定数量的bounding box(YOLO v1中是2个,YOLO v2中是5个,YOLO v3中是3个,这几个bounding box的初始size是不一样的),那么这几个bounding box中最终是由哪一个来预测该object?答案是:这几个bounding box中只有和ground truth的IOU最大的bounding box才是用来预测该object的。可以看出预测得到的输出feature map有两个维度是提取到的特征的维度,比如13*13,还有一个维度(深度)是B*(5+C),注:YOLO v1中是(B*5+C),其中B表示每个grid cell预测的bounding box的数量,比如YOLO v1中是2个,YOLO v2中是5个,YOLO v3中是3个,C表示bounding box的类别数(没有背景类,所以对于VOC数据集是20),5表示4个坐标信息和一个置信度(objectness score)。
算法在速度和精度上的提升可以看Figure1。
二.详解
A Fully Convolutional Neural Network
yolo_v3只使用了卷积层,没有使用任何形式的pooling,其一共有75个卷积层,再结合skip connections和下采样层。用stride=2的卷积操作去代替pooling。
yolo_v3按理说是可以不用限制输入图片的大小的,但是为了batch的并行化计算,所以对输入图像的大小做了限制。
Interpreting the output
输入尺寸为416*416,图像的下采样率为32,则最后的用于预测的featuremap尺寸为13*13。
将输入图像划分为13*13的grid cell,其目的在于这个grid cell与featuremap上的13*13的grid cell一一对应(感受野)。
featuremap上的每一个grid cell预测3个bbox,在训练时,如果一个目标的中心落入某个grid cell中,则该grid cell中的3个bbox与该目标相关,其中与gt有最高iou的bbox负责预测该目标。
Anchor Boxes
直接预测最后的w和h容易导致梯度的不稳定性,因为不同的目标可能尺度差异比较大,所以这里引入anchor,利用anchor的先验位置,求取相对于其的offset来解决这个问题。
这里的anchor只是为了提供先验,即下面公式中的pw,ph。
Making Predictions
yolo_v3的预测方式与yolo_v2一样,如下所示:
tx, ty, tw, th是网络的输出,cx,cy是当前grid cell的左上角的点的坐标,pw,ph是anchor的尺寸。
Center Coordinates
以上的坐标预测使用了sigmoid激活函数,使得坐标值被归一化到了0-1之间。
就拿上面那副图来举例子,红色的cell的左上角坐标是(6,6),如果tx,ty经过sigmoid函数的作用后的值为(0.4,0.7),那么这个bbox的坐标值cx,cy为(6.4,6.7)。
这里有一个细节需要注意,即为什么使用sigmoid函数处理tx,ty。因为tx,ty的值如果大于1,则再加上cx,cy会超出原来的cell,所以这里用sigmoid将其限制在0-1之间。
Dimensions of the Bounding Box
bw,bh是经过图像的w和h归一化处理过的, 如果预测得到的bw和bh在featuremap上是(0.3,0.8)的话,那么在featuremap上实际得到的w和h是(13 x 0.3, 13 x 0.8)。
Objectness Score
Object score代表了该bounding box包含目标的可能性,在红色的cell和其周围的cell,这个值接近1,而其他cell则接近0。这个Object score也是经过sigmoid函数处理的,使得其值位于0-1之间,代表了有目标的可能性。
Class Confidences
用sigmoid分类代替softmax分类,因为softmax分类的概率输出总和为1,有类间相关性,所以yolo_v3使用sigmoid分类代替。
Prediction across different scales
从多尺度的featuremap上获取预测结果。yolo_v3是从三种尺度的featuremap上获取预测结果,这三种尺度分别为32,16和8,由于输入是416*416,所以对应的三种featuremap的大小分别为13 x 13, 26 x 26 and 52 x 52。
具体做法如下:
首先一直下采样,到13*13的featuremap,然后将其上采样2倍,与之前26*26的featuremap相连,然后再上采样2倍,与之前52*52的featuremap相连。在每一个scale的featuremap上,每一个cell都用3个anchor预测3个bounding box。由于3个尺度对应的anchor也不同,所以每一个cell在三种featuremap上总共得到9个anchor。
这种上采样结合之前较大featuremap做最后预测的方式,可以提升对小目标的检测能力,所以yolo_v3对于小物体的检测效果还是不错的。
Output Processing
对于大小为416*416的Input image,yolo_v3总共预测(52*52+26*26+13*13)*3总共10647 个bounding box。
考虑我们上面的图像,只有1个目标--狗,那么如果将这么多的检测结果降低为最后的1个检测框呢?
Thresholding by Object Confidence
筛掉boject conifidence低于一定阈值的bounding box。
Non-maximum Suppression
nms负责解决对于同一目标的重复预测问题。例如红色cell的三个bounding box可以检测到同一个目标,或者相邻的cell可以检测到同一个目标。
三.总结
训练时:
输入图片尺寸416*416,stride=32,所以最后用于预测的featuremap=13*13,对于每一个grid cell引入3个尺寸的anchor,对于gt,每一个目标中心所在的那个cell负责该目标,在这个cell中有3个预先设置的anchor尺寸,即pw,ph,选择其中与gt的wh_iou最大的那个anchor作为best_anchor(即形状最相似的anchor)。
对于gt,target[:,2:6]中存储的是各个目标中心点的gx,gy,gw,gh,这些都是在0-1之间的值,因为原图被划分为13*13的grid cell,gw和gh是目标的gt除以原图的大小,即为416所得到的归一化值,gx,gy是目标的中心点坐标减去所在cell的左上角坐标归一化得到的值,全都在0-1之间,下面做如下处理:
target_boxes = target[:, 2:6] * nG
ng是相应的featuremap的尺寸(例如13),这样target_boxes中存储的即为在相应的featuremap上的gx,gy,gw,gh,然后求取相应的tx,ty,如下式,其中floor表示取整数部分,即得到相对于当前cell的左上角坐标的offset。
tx[b, best_n, gj, gi] = gx - gx.floor() ty[b, best_n, gj, gi] = gy - gy.floor()
对于gw,gh,求取其相对于best_anchor的w和h的尺度offset为tw,th。
tw[b, best_n, gj, gi] = torch.log(gw / anchors[best_n][:, 0] + 1e-16) th[b, best_n, gj, gi] = torch.log(gh / anchors[best_n][:, 1] + 1e-16)
最后在计算loss的时候,计算关于tx,ty,tw,th的loss值。
对于预测得到的confidence和cls,用sigmoid函数处理,使其位于0-1之间。
对于所有的目标中心所在的cell,将其对应的best_anchor的位置的obj_mask设为1,即该cell的该mask负责预测该目标的位置和计算该目标的回传loss。
obj_mask[b, best_n, gj, gi] = 1 noobj_mask[b, best_n, gj, gi] = 0
将wh_iou超过阈值的anchor的位置的noobj_mask值设为0,即为该cell位置处的该种尺度的anchor与best_anchor的wh_iou超过一定阈值,一般为0.5,有一定的干扰作用,不参与noobj的confidence的loss回传。
# Set noobj mask to zero where iou exceeds ignore threshold for i, anchor_ious in enumerate(ious.t()): noobj_mask[b[i], anchor_ious > ignore_thres, gj[i], gi[i]] = 0
对于分类,进行one-hot编码,用于计算loss。
# One-hot encoding of label tcls[b, best_n, gj, gi, target_labels] = 1
如果目标中心所在的cell中的best_anchor的预测类别的分数中最大的值所对应的类别与标签一致,即预测的类别正确,则该位置的class_mask值为1,不参与类别loss计算,否则为0,计算分类loss。
iou_scores中存储的是目标中心所在的cell中的best_anchor与相应的目标gt的iou值。
class_mask[b, best_n, gj, gi] = (pred_cls[b, best_n, gj, gi].argmax(-1) == target_labels).float() iou_scores[b, best_n, gj, gi] = bbox_iou(pred_boxes[b, best_n, gj, gi], target_boxes, x1y1x2y2=False)
接下来开始计算loss值:
1.计算目标中心所在的cell中的best_anchor所对应的tx,ty,tw,th的mse_loss。
loss_x = self.mse_loss(x[obj_mask], tx[obj_mask]) loss_y = self.mse_loss(y[obj_mask], ty[obj_mask]) loss_w = self.mse_loss(w[obj_mask], tw[obj_mask]) loss_h = self.mse_loss(h[obj_mask], th[obj_mask])
2.计算有没有目标所对应的confidence score,两者的权重为1和100.
loss_conf_obj = self.bce_loss(pred_conf[obj_mask], tconf[obj_mask]) loss_conf_noobj = self.bce_loss(pred_conf[noobj_mask], tconf[noobj_mask]) loss_conf = self.obj_scale * loss_conf_obj + self.noobj_scale * loss_conf_noobj
3.计算分类的loss
loss_cls = self.bce_loss(pred_cls[obj_mask], tcls[obj_mask])
4.计算总的loss
total_loss = loss_x + loss_y + loss_w + loss_h + loss_conf + loss_cls
![]()
即不是best_anchor,但是与gt的iou大于阈值,则我们舍弃该预测,即不考虑其noobj的confidence的loss计算,如果不是best_anchor,我们不考虑其坐标loss和cls loss,仅仅考虑其noobj的confidence loss。
测试时:
output = torch.cat( ( pred_boxes.view(num_samples, -1, 4) * self.stride, pred_conf.view(num_samples, -1, 1), pred_cls.view(num_samples, -1, self.num_classes), ), -1, )
对output做出如下的处理:
detections = model(input_imgs) detections = non_max_suppression(detections, opt.conf_thres, opt.nms_thres)
首先删除那些confidence<conf_thres=0.5的检测结果。
然后再进一步进行nms,得到最后的输出结果。即为每一个目标只有一个框输出。
四.网络结构详解
1.新的网络结构Darknet-53
在基本的图像特征提取方面,YOLO3采用了称之为Darknet-53的网络结构(含有53个卷积层),它借鉴了残差网络residual network的做法,在一些层之间设置了快捷链路(shortcut connections)。
从第0层一直到74层,一共有53个卷积层,其余为res层。这就是Joseph Redmon大神提出的darknet-53经典的卷积层了。作为yolov3特征提取的主要网络结构。预训练(以imagenet数据集为训练基础)的权重文件可以通过官网下载。该结构使用一系列的3*3和1*1的卷积的卷积层。这些卷积层是从各个主流网络结构选取性能比较好的卷积层进行整合得到。它比darknet-19效果好很多,同时,它在效果更好的情况下,是resnet-101效率的1.5倍,几乎与resnet-152的效果相同的情况下,保持2倍于resnet-152的效率。
res层(shortcut操作):
layer filters size input output
4 res 1 208 x 208 x 64 -> 208 x 208 x 64
res层:
输入与输出:输入与输出一般保持一致,并且不进行其他操作,只是求差。
处理操作:res层来源于resnet,为了解决网络的梯度弥散或者梯度爆炸的现象,提出将深层神经网络的逐层训练改为逐阶段训练,将深层神经网络分为若干个子段,每个小段包含比较浅的网络层数,然后用shortcut的连接方式使得每个小段对于残差进行训练,每一个小段学习总差(总的损失)的一部分,最终达到总体较小的loss,同时,很好的控制梯度的传播,避免出现梯度消失或者爆炸等不利于训练的情形。
2.YOLO部分
从75到105层我为yolo网络的特征交互层,分为三个尺度,每个尺度内,通过卷积核的方式实现局部的特征交互,作用类似于全连接层但是是通过卷积核(3*3和1*1)的方式实现feature map之间的局部特征(fc层实现的是全局的特征交互)交互。
最小尺度yolo层:
- 输入:13*13的feature map ,一共1024个通道。
- 操作:一系列的卷积操作,feature map的大小不变,但是通道数最后减少为75个。
- 输出;输出13*13大小的feature map,75个通道,在此基础上进行分类和位置回归。
中尺寸yolo层:
- 输入:将79层的13*13、512通道的feature map进行卷积操作,生成13*13、256通道的feature map,然后进行上采样,生成26*26、256通道的feature map,同时于61层的26*26、512通道的中尺度的feature map合并。再进行一系列卷积操作,
- 操作:一系列的卷积操作,feature map的大小不变,但是通道数最后减少为75个。
- 输出:26*26大小的feature map,75个通道,然后在此进行分类和位置回归。
大尺度的yolo层:
- 输入:将91层的26*26、256通道的feature map进行卷积操作,生成26*26、128通道的feature map,然后进行上采样生成52*52、128通道的feature map,同时于36层的52*52、256通道的中尺度的feature map合并。再进行一系列卷积操作,
- 操作:一系列的卷积操作,feature map的大小不变,但是通道数最后减少为75个。
- 输出:52*52大小的feature map,75个通道,然后在此进行分类和位置回归。
9种尺度的先验框
随着输出的特征图的数量和尺度的变化,先验框的尺寸也需要相应的调整。YOLO2已经开始采用K-means聚类得到先验框的尺寸,YOLO3延续了这种方法,为每种下采样尺度设定3种先验框,总共聚类出9种尺寸的先验框。在COCO数据集这9个先验框是:(10x13),(16x30),(33x23),(30x61),(62x45),(59x119),(116x90),(156x198),(373x326)。
分配上,在最小的13*13特征图上(有最大的感受野)应用较大的先验框(116x90),(156x198),(373x326),适合检测较大的对象。中等的26*26特征图上(中等感受野)应用中等的先验框(30x61),(62x45),(59x119),适合检测中等大小的对象。较大的52*52特征图上(较小的感受野)应用较小的先验框(10x13),(16x30),(33x23),适合检测较小的对象。
感受一下9种先验框的尺寸,下图中蓝色框为聚类得到的先验框。黄色框式ground truth,红框是对象中心点所在的网格。
对象分类softmax改成logistic
预测对象类别时不使用softmax,改成使用logistic的输出进行预测。这样能够支持多标签对象(比如一个人有Woman 和 Person两个标签)。
不考虑神经网络结构细节的话,总的来说,对于一个输入图像,YOLO3将其映射到3个尺度的输出张量,代表图像各个位置存在各种对象的概率。
我们看一下YOLO3共进行了多少个预测。对于一个416*416的输入图像,在每个尺度的特征图的每个网格设置3个先验框,总共有 13*13*3 + 26*26*3 + 52*52*3 = 10647 个预测。每一个预测是一个(4+1+80)=85维向量,这个85维向量包含边框坐标(4个数值),边框置信度(1个数值),对象类别的概率(对于COCO数据集,有80种对象)。
对比一下,YOLO2采用13*13*5 = 845个预测,YOLO3的尝试预测边框数量增加了10多倍,而且是在不同分辨率上进行,所以mAP以及对小物体的检测效果有一定的提升。