使用 tensorflow2.0 实现的 YOLO(you only look once) v2算法
YOLO 是一个实时目标检测系统
该项目实现了该系统的全部细节,包括模型构建、Loss函数、目标检测、以及如何从零训练一个目标检测系统,以便扩展至训练自定义的目标检测系统。
检测结果
- Anconda 2020.11
- Conda 4.9.2
- Python 3.7.10
- Tensorflow 2.0.0
- 检测图片中的目标
python test_tect.py
- 保存yolo_v2模型
python save_model.py
- 使用保存的yolo_v2模型检测图片
python load_model_and_test_detect.py
- 从零训练网络,在2个样本的训练集上训练一个过拟合的模型
python train_model_overfit.py
- 使用前面训练的过拟合模型进行目标检测
python load_overfit_model_and_test_detect.py
网络最终输出一个(19,19,425)
的张量,我们可以将其转换成(19,19,5,85)
的张量,
其中[i,j,a,:]
表示在第[i,j]
单元格的第a
个anchor-box
的预测结果,由4
部分组成
[0:2]
存储了对象中心在单元格内的位置(x,y)
坐标- 经过
sigmoid
函数映射,范围限制在0~1
- 经过
[2:4]
存储了对象的宽高(w,h)
- 经过
exp
函数映射得到关于对应anchor-box
的宽高系数,必须大于0
- 经过
[4:5]
存储了该anchor-box
包含对象的置信度(概率)- 经过
sigmoid
函数映射,范围限制在0~1
- 经过
[5:85]
存储了该anchor-box
包含的对象关于80
个类别的概率分布- 经过
softmax
归一化,范围限制在0~1
,总和为1
- 经过
注意网络输出值并没有经过上面所描述的映射,也就是说我们让网络学会的是这些函数的输入值。
维度是(K,5)
的张量,K
为图片中包含的对象数量,每个对象由3
部分决定
[0:2]
存储了对象中心在整张图片的相对位置(lx,ly)
,范围在0~1
[2:4]
存储了对象的宽高(lw,lh)
,范围在0~1
[4:5]
存储了对象的类别class
,范围在0~79
人工标签的维度是(K,5)
,显然与网络输出维度不符合,为了计算网络loss
,我们将
人工标签转换得到(19,19,5)
维度的张量detectors_mask
和
(19,19,5,5)
维度的张量matching_true_boxes
detectors_mask
元素为bool
类型,detectors_mask[i,j,a]
为True
,
则表明在第[i,j]
单元格的第a
个anchor-box
的存在对象
matching_true_boxes
若detectors_mask[i,j,a]
为True
,
则matching_true_boxes[i,j,a,:]
存储了对象信息,由3
部分决定
[0:2]
存储了对象中心在单元格内的位置(x,y)
坐标- 变换规则,
(lx,ly) * (19,19) = (19lx ,19ly) = (u,v)
,(x,y) = (u,v) - floor((u,v))
- 变换规则,
[2:4]
存储了对象的宽高(w,h)
,与网络输出的宽高含义一致- 变换规则,
(lw,lh) ⊙ (19,19) = (19lw ,19lh) = (p,q)
,(w,h) = log((p,q) / (anchorW,anchorH))
- 变换规则,
[4:5]
存储了对象的类别class
,范围在0~79
- 可以使用
one_hot
函数转换成相应的softmax
标签
- 可以使用
总Loss
= 识别Loss
+分类Loss
+定位Loss
,均采用平方差Loss
,上述表达式中detectors_mask
前面提到过
object_detections
object_detections(i,j,a)
为True
,表示该位置预测框与一个真实对象框很吻合(具体是IOU>threshold=0.6)
,此时即使该位置本不应存在对象即detectors_mask(i,j,a)=False
也不做no-obj loss
计算。原因如下:
- 训练网络的时候,人工标注的对象是分配到一对
(grid cell,anchor-box)
中,然而一个单元格中包含多个anchor-box, 实际上如果存在一个目标形状和多个anchor-box
都接近(IOU
接近),那么对象具体分配到哪一个anchor-box
都是合理的, 因此网络在多个位置都输出了预测框也都是合理的,尽管我们标注的位置仍然只会选择一个最优IOU
的(grid cell,anchor-box)
位置, 因此我们可以放宽要求,如果在人工标注位置的附近网络也说存在对象,并且预测框和人工标注框很吻合,那么我们将既不惩罚也不激励网络,保持中立。 并且这些多余的预测结果可被非最大值印制算法滤去。 另外一方面如果我们要求的输出非常严格,对这些地方进行no-obj loss
惩罚,这样会拥有太多的负例,因为一张图片, 网络将预测19*19*5=1805
个框,通常人工标注的对象少于100
个,那么负例将会是1705
个,这可能导致网络最终学会了检测某个位置无对象。
-
有
4
个权重系数,这里实现上分别取值为lambda_obj=5
、lambda_noobj=1
、lambda_coord=1
、lambda_class=1
-
字母
N
表示类别的数量,yolov2
系统中是80
关于反向传播算法如何计算参数梯度的实现可以参考我的另外两个项目实现
如果使用tensorflow
框架,梯度计算将由框架自动完成,意味着我们只需要实现向前传播算法和损失函数,这是使用框架实现模型的一个极大好处。
输入图片是一个(608,608,3)
的张量,经过我们训练的D-CNN
(具有23
个卷积层的深度卷积网络)后,
网络最终输出一个(19,19,425)
的张量,转换成(19,19,5,85)
的张量,也就是(19,19,5)
个目标检测结果,
其中每个检测结果有 该位置存在对象的置信度conf = [4:5]
,80
个类别的概率分布prob=[5:85]
,
因此每个类别的得分为score = conf * prob
,然后我们取得分最高的类别作为检测结果,
因为有(19,19,5)
个目标检测结果,因此有19*19*5=1805
个得分,我们过滤掉得分较低的预测(score<threshold=0.6)
,
剩下的都是具有较高得分的检测结果,但因为训练时,我们没惩罚包含对象的附近的检测结果,因此网络存在多位置检测同一个对象的可能,
所以接下来我们对各个类使用非最大值印制算法过滤,得出最终的目标检测结果。这部分原理和实现可以
参考utils.py
文件中的convert_filter_and_non_max_suppression
函数