基于keras的Yolov3最全详解
参考Github源码链接
Yolov3论文原文链接一,0二
Yolov3官网
最近在做Yolov3相关项目,看了很多资料。所以写一篇总结体会,以便以后回顾查询。
YOLO,即 You Only Look Once 的缩写,是一个基于卷积神经网络(CNN)的目标检测算法 。
yolo设计理念
yolo算法整体来说是采用CNN对目标进行end-to-end的检测。流程如图所示
具体来说(基于YOLOV3)
- 输入一张任意大小图片,保持长宽比不变的情况下,缩放至 w 或 h达到416,再覆盖在416 * 416的新图上,作为网络的输入。即网络的输入是一张416 * 416,3通道的RGB图。
- 运行网络。YOLO的CNN网络把图片分成 S * S 个网格(yolov3多尺度预测,输出3层,每层 S * S个网格,分别为13 * 13 ,26 * 26 ,52 * 52),然后每个单元格负责去检测那些中心点落在该格子内的目标,如图二所示。 每个单元格需要预测3*(4+1+B)个值。如果将输入图片划分为 SS 网格,那么每层最终预测值为 SS3(4+1+B) 大小的张量。类别数(coco集为80类),即B=80. 3 为每层anchorbox数量,4 为边界框大小和位置(x , y , w ,h )1 为置信度。
- 通过NMS,非极大值抑制,筛选出框boxes,输出框class_boxes和置信度class_box_scores,再生成类别信息classes,生成最终的检测数据框,并返回
Yolov3网络结构图
下面讲一下训练样本的设置和loss的计算。
训练样本设置
参考上面图2,对于一个输入图像,比如4164163,相应的会输出 13133 + 26263 + 52523 = 10647 个预测框。我们希望这些预测框的信息能够尽量准确的反应出哪些位置存在对象,是哪种对象,其边框位置在哪里。
在设置标签y(10647个预测框 * (4+1+类别数) 张量)的时候,YOLO的设计思路是,对于输入图像中的每个对象,该对象实际边框(groud truth)的中心落在哪个网格,就由该网格负责预测该对象。不过,由于设计了3种不同大小的尺度,每个网格又有3个先验框,所以对于一个对象中心点,可以对应9个先验框。但最终只选择与实际边框IOU最大的那个先验框负责预测该对象(该先验框的置信度=1),所有其它先验框都不负责预测该对象(置信度=0)。同时,该先验框所在的输出向量中,边框位置设置为对象实际边框,以及该对象类型设置为1。
loss计算
loss主要有3个部分,置信度、边框位置、对象类型。
首先需要注意的还是置信度的问题。上面说到对于一个实际对象,除了与它IOU最大的那个先验框其置信度设为1,其它先验框的置信度都是0。但是,还有一些先验框与该对象实际边框位置是比较接近的,它们依然有可能检测到该对象,只不过并非最接近实际边框。所以,这部分边框的目标置信度不应该期望输出0。但YOLO也不希望它们输出1。所以,在计算loss的时候,就设置一个IOU阈值,超过阈值的(接近目标边框但又不是IOU最大的)那些边框不计入loss。低于阈值的那些边框就要求置信度为0,也就是检测到背景。
同时,对于检测到对象的边框,要计算其边框位置的loss,以及对象类型的loss。对于那些检测到背景的边框,就只计算其置信度loss了,它的边框位置和对象类型都没有意义。
另外注意一下的是边框位置计算要根据论文的设计做一些变换,参考下面图。
anchor box:
yolov3 anchor box一共有9个,由k-means聚类得到。在COCO数据集上,9个聚类是:(10 * 13);(16 * 30);(33 * 23);(30 * 61);(62 * 45); (59 * 119); (116 * 90); (156 * 198); (373 * 326)。
不同尺寸特征图对应不同大小的先验框。
13 * 13feature map对应【(116 * 90),(156 * 198),(373 * 326)】
26 * 26feature map对应【(30 * 61),(62 * 45),(59 * 119)】
52 * 52feature map对应【(10 * 13),(16 * 30),(33 * 23)】
原因:
- 特征图越大,感受野越小。对小目标越敏感,所以选用小的anchor box。
- 特征图越小,感受野越大。对大目标越敏感,所以选用大的anchor box。
边框预测:
预测tx ty tw th
对tx和ty进行sigmoid,并加上对应的offset(下图Cx, Cy)
对th和tw进行exp,并乘以对应的锚点值
对tx,ty,th,tw乘以对应的步幅,即:416/13, 416 ⁄ 26, 416 ⁄ 52
最后,使用sigmoid对Objectness和Classes confidence进行sigmoid得到0~1的概率,之所以用sigmoid取代之前版本的softmax,原因是softmax会扩大最大类别概率值而抑制其他类别概率值
(tx,ty) :目标中心点相对于该点所在网格左上角的偏移量,经过sigmoid归一化。即值属于【0,1】。如图约(0.3 , 0.4)
(cx,cy):该点所在网格的左上角距离最左上角相差的格子数。如图(1,1)
(pw,ph):anchor box 的边长
(tw,th):预测边框的宽和高
PS:最终得到的边框坐标值是bx,by,bw,bh.而网络学习目标是tx,ty,tw,th
Loss
YOLOv3中有一个参数是ignore_thresh,在ultralytics版版的YOLOv3中对应的是train.py文件中的iou_t参数(默认为0.225)。
正负样本是按照以下规则决定的:
- 如果一个预测框与所有的Ground Truth的最大IoU<ignore_thresh时,那这个预测框就是负样本。
- 如果Ground Truth的中心点落在一个区域中,该区域就负责检测该物体。将与该物体有最大IoU的预测框作为正样本(注意这里没有用到ignore thresh,即使该最大IoU<ignore thresh也不会影响该预测框为正样本)
在YOLOv3中,Loss分为三个部分:
- 一个是xywh部分带来的误差,也就是bbox带来的loss ,在ultralytics版版的YOLOv3中,使用的是GIOU
- 一个是置信度带来的误差,也就是obj带来的loss。lobj代表置信度,即该bounding box中是否含有物体的概率。在yolov3代码中obj loss可以通过arc来指定,有两种模式:
如果采用default模式,使用BCEWithLogitsLoss,将obj loss和cls loss分开计算:
BCEobj = nn.BCEWithLogitsLoss(pos_weight=ft([h['obj_pw']]), reduction=red)
if 'default' in arc: # separate obj and cls
lobj += BCEobj(pi[..., 4], tobj) # obj loss
# pi[...,4]对应的是该框中含有目标的置信度,和giou计算BCE
# 相当于将obj loss和cls loss分开计算
如果采用BCE模式,使用的也是BCEWithLogitsLoss, 计算对象是所有的cls loss:
BCE = nn.BCEWithLogitsLoss(reduction=red)
elif 'BCE' in arc: # unified BCE (80 classes)
t = torch.zeros_like(pi[..., 5:]) # targets
if nb:
t[b, a, gj, gi, tcls[i]] = 1.0 # 对应正样本class置信度设置为1
lobj += BCE(pi[..., 5:], t)#pi[...,5:]对应的是所有的class
- 最后一个是类别带来的误差,也就是class带来的loss。如果是单类的情况,cls loss=0,如果是多类的情况,也分两个模式:
如果采用default模式,使用的是BCEWithLogitsLoss计算class loss。
BCEcls = nn.BCEWithLogitsLoss(pos_weight=ft([h['cls_pw']]), reduction=red)
# cls loss 只计算多类之间的loss,单类不进行计算
if 'default' in arc and model.nc > 1:
t = torch.zeros_like(ps[:, 5:]) # targets
t[range(nb), tcls[i]] = 1.0 # 设置对应class为1
lcls += BCEcls(ps[:, 5:], t) # 使用BCE计算分类loss
如果采用CE模式,使用的是CrossEntropy同时计算obj loss和cls loss。
CE = nn.CrossEntropyLoss(reduction=red)
elif 'CE' in arc: # unified CE (1 background + 80 classes)
t = torch.zeros_like(pi[..., 0], dtype=torch.long) # targets
if nb:
t[b, a, gj, gi] = tcls[i] + 1 # 由于cls是从零开始计数的,所以+1
lcls += CE(pi[..., 4:].view(-1, model.nc + 1), t.view(-1))
# 这里将obj loss和cls loss一起计算,使用CrossEntropy Loss
三部分总结下来就是下图: