深度学习(四)卷积神经网络-卷积神经网络(3) -Andrew Ng

目录

一、基础知识

1.1 目标定位

1.2  特征点检测

1.3 目标检测

1.4 卷积的滑动窗口实现

1.5 Bounding Box 预测

1.6 交并比

1.7 非极大值抑制

1.8 Anchor Boxes                            ​

1.9 YOLO 算法

二、测验

三、编程

3.1 YOLO 算法

3.1.1  模型细节

3.1.2  在类别分数上进行过滤

3.1.3 非最大抑制

3.1.4 结束过滤

3.2 在图像上测试YOLO预训练模型

3.2.1 定义 classes, anchors 和 image shape

3.2.2  加载预训练的模型

3.2.3 将模型的输出转换为可用的边界框 tensors

3.2.4 过滤 boxes

3.2.5 在图像上运行graph


一、基础知识

1.1 目标定位

前两节课程中,我们介绍的是利用CNN模型进行图像分类。除此之外,本周课程将继续深入介绍目标定位目标检测(包含多目标检测)。

                                       

图片分类问题你已经并不陌生了,如:

输入一张图片到多层卷积神经网络。这就是卷积神经网络,它会输出一个特征向量,并反馈给 softmax 单元来预测图片类型。

                                                

原始图片经过CONV卷积层后,Softmax层输出4 x 1向量,分别是:

                                                               

注意,class label也可能是概率。上述四个向量分别对应pedestrain,car,motorcycle和background四类。

 这就是标准的分类过程,如果你还想定位图片中汽车的位置,该怎么做呢?

我们可以让 神经网络多输出几个单元,输出一个边界框。具体说就是让神经网络再多输出 4 个数字,标 记为𝑏𝑥 ,𝑏𝑦 ,𝑏h 和𝑏𝑤 ,这四个数字是被检测对象的边界框的参数化表示。

对于目标定位和目标检测问题,其模型如下所示:

                           

 我们先来约定下面使用的符号表示,图片左上角的坐标为(0,0),右下角标记为 (1,1)。要确定边界框的具体位置,需要指定红色方框的中心点,这个点表示为(𝑏𝑥 ,𝑏𝑦 ),边界框的高度为𝑏h ,宽度为𝑏𝑤 。因此训练集不仅包含神经网络要预测的对象分类标签,还要包含表示边界框的这四个数字,接着采用监督学习算法,输出一个分类标签,还有四个参数值, 从而给出检测对象的边框位置。

输出label可表示为:

                                                

若Pc=0,表示没有检测到目标,则输出label后面的7个参数都可以忽略。

若Pc= 1,表示存在某个对象,同时输出𝑐1、𝑐2和𝑐3,表示该对象属于 1-3 类中的哪一类,𝑐1、𝑐2和𝑐3中最多只有一个等于 1;是行人,汽车还是摩托车。

对于损失函数Loss function,若使用平方误差形式,有两种情况:

神经网络的损失:

对于损失函数Loss function,若使用平方误差形式,有两种情况:

  • Pc=1,即y1=1:

                                                          

  • Pc=0,即y1=0:

                                                                                

当𝑦1 = 1时,平方误差策略可以减少这 8 个元素预测值和实际输出结果之间差值的平方。

如果𝑦1 = 0,𝑦 矩阵中的后 7 个元素都不用考虑,只需要考虑神经网络评估𝑦1(即𝑝𝑐)的准确度。

当然,除了使用平方误差之外,还可以使用逻辑回归损失函数,类标签c1,c2,c3也可以通过softmax输出。比较而言,平方误差已经能够取得比较好的效果。

1.2  特征点检测

除了使用矩形区域检测目标类别和位置外,我们还可以仅对目标的关键特征点坐标进行定位,这些关键点被称为landmarks。

例如人脸识别,可以对人脸部分特征点坐标进行定位检测,并标记出来,如下图所示:

                                       

该网络模型共检测人脸上64处特征点,加上是否为face的标志位,输出label共有64x2+1=129个值。通过检测人脸特征点可以进行情绪分类与判断,或者应用于AR领域等等。

除了人脸特征点检测之外,还可以检测人体姿势动作,如下图所示:

                                

要明确一点,特征点 1 的特性在所有图片中 必须保持一致,就好比,特征点 1 始终是右眼的外眼角,特征点 2 是右眼的内眼角,特征点 3 是左眼内眼角,特征点 4 是左眼外眼角等等。所以标签在所有图片中必须保持一致,假如 你雇用他人或自己标记了一个足够大的数据集,那么神经网络便可以输出上述所有特征点, 你可以利用它们实现其他有趣的效果,比如判断人物的动作姿态,识别图片中的人物表情等等。

1.3 目标检测

目标检测的一种简单方法是滑动窗算法。这种算法首先在训练样本集上搜集相应的各种目标图片和非目标图片。注意训练集图片尺寸较小,尽量仅包含相应目标,如下图所示:

                                    

有了这个标签训练集,你就可以开始训练卷积网络了,训练完这个卷积网络,就可以用它来实现滑动窗口目标检测,具体步骤如下。

在测试图片上,选择大小适宜的窗口、合适的步幅长度,进行从左到右、从上倒下的滑动。每个窗口区域都送入之前构建好的CNN模型进行识别判断。若判断有目标,则此窗口即为目标区域;若判断没有目标,则此窗口为非目标区域。

                                  

滑动窗算法的优点:

原理简单,且不需要人为选定目标区域(检测出目标的滑动窗即为目标区域)。

缺点:

滑动窗的大小和步幅长度都需要人为直观设定。滑动窗过大,步幅长度过大均会降低目标检测正确率。而且,每次滑动窗区域都要进行一次CNN网络计算,如果滑动窗和步幅长度较小,整个目标检测的算法运行时间会很长。

庆幸的是,计算成本问题已经有了很好的解决方案,大大提高了卷积层上应用滑动窗口目标检测器的效率,关于它的具体实现,下面再讲。

1.4 卷积的滑动窗口实现

滑动窗算法可以使用卷积方式实现,以提高运行速度,节约重复运算成本。

首先,单个滑动窗口区域进入CNN网络模型时,包含全连接层。那么滑动窗口算法卷积实现的第一步就是将全连接层转变成为卷积层,如下图所示:

                             

全连接层转变成卷积层的操作很简单,只需要使用与上层尺寸一致的滤波算子进行卷积运算即可。最终得到的输出层维度是1 x 1 x 4,代表4类输出值。

单个窗口区域卷积网络结构建立完毕之后,对于待检测图片,即可使用该网络参数和结构进行运算。例如16 x 16 x 3的图片,步进长度为2,CNN网络得到的输出层为2 x 2 x 4。其中,2 x 2表示共有4个窗口结果。对于更复杂的28 x 28 x3的图片,CNN网络得到的输出层为8 x 8 x 4,共64个窗口结果。

                        

之前的滑动窗算法需要反复进行CNN正向计算,例如16 x 16 x 3的图片需进行4次,28 x 28 x3的图片需进行64次。而利用卷积操作代替滑动窗算法,则不管原始图片有多大,只需要进行一次CNN正向计算,因为其中共享了很多重复计算部分,这大大节约了运算成本。值得一提的是,窗口步进长度与选择的MAX POOL大小有关。如果需要步进长度为4,只需设置MAX POOL为4 x 4即可。

 1.5 Bounding Box 预测

上面我们学到了滑动窗口法的卷积实现,这个算法效率更高,但仍然存在问题,不能输出最精准的边界框。接下来,我们看看如何得到更精准的边界框。

滑动窗口算法有时会出现滑动窗不能完全涵盖目标的问题,如下图蓝色窗口所示。

                                                          

YOLO(You Only Look Once)算法可以解决这类问题,生成更加准确的目标区域(如上图红色窗口)。

YOLO算法首先将原始图片分割成n x n网格,每个网格代表一块区域。为简化说明,下图中将图片分成3 x 3网格。

                                                              

然后,利用上一节卷积形式实现滑动窗口算法的思想,对该原始图片构建CNN网络,得到的的输出层维度为3 x 3 x 8。其中,3 x 3对应9个网格,每个网格的输出包含8个元素:

                                                                             

如果目标中心坐标(bx,by)不在当前网格内,则当前网格Pc=0;相反,则当前网格Pc=1(即只看中心坐标是否在当前网格内)。判断有目标的网格中,bx,by,bh,bw限定了目标区域。值得注意的是,当前网格左上角坐标设定为(0, 0),右下角坐标设定为(1, 1),(bx,by)范围限定在[0,1]之间,但是bh,bw可以大于1。因为目标可能超出该网格,横跨多个区域,如上图所示。目标占几个网格没有关系,目标中心坐标必然在一个网格之内。

划分的网格可以更密一些。网格越小,则多个目标的中心坐标被划分到一个网格内的概率就越小,这恰恰是我们希望看到的。

1.6 交并比

你如何判断对象检测算法运作良好呢?接下来,你将了解到并交比函数,可以用来评价对象检测算法。

后续,我们用它来插入一个分量来进一步改善检测算法。

IoU,即交集与并集之比,可以用来评价目标检测区域的准确性。

                                   

 

如上图所示,红色方框为真实目标区域,蓝色方框为检测目标区域。两块区域的交集为绿色部分,并集为紫色部分。蓝色方框与红色方框的接近程度可以用IoU比值来定义:

                                                                 

IoU可以表示任意两块区域的接近程度。IoU值介于0~1之间,且越接近1表示两块区域越接近。

一般约定,在计算机检测任务中,如果𝑙𝑜𝑈 ≥ 0.5,就说检测正确,如果预测器和实际边 界框完美重叠,loU 就是 1,因为交集就等于并集。但一般来说只要𝑙𝑜𝑈 ≥ 0.5,那么结果是 可以接受的,看起来还可以。一般约定,0.5 是阈值,用来判断预测的边界框是否正确。一 般是这么约定,但如果你希望更严格一点,你可以将 loU 定得更高,比如说大于 0.6 或者更 大的数字,但 loU 越高,边界框越精确。

1.7 非极大值抑制

YOLO算法中,可能对同一个对象做出多次检测,所以算法不是对某个对象检测出一次,而是检测出多次。非极大值抑制这个方法可以确保你的算法对每个对象只检测一次。

                 

假设你需要在这张图片里检测行人和汽车,你可能会在上面放个 19×19 网格,理论上这辆车只有一个中点,所以它应该只被分配到一个格子里,左边的车子也只有一个中点,所以理论上应该只有一个格子做出有车的预测。 

                                       

实践中当你运行对象分类和定位算法时,对于每个格子都运行一次,所以这个格子(编 号 1)可能会认为这辆车中点应该在格子内部,这几个格子(编号 2、3)也会这么认为。对于左边的车子也一样,所以不仅仅是这个格子,如果这是你们以前见过的图像,不仅这个格 (编号 4)子会认为它里面有车,也许这个格子(编号 5)和这个格子(编号 6)也会,也许其他格子也会这么认为,觉得它们格子内有车。

分步介绍一下非极大值抑制是怎么起效的?

因为你要在 361 个格子上都运行一次图像检测和定位算法,那么可能很多格子都会举手说我的𝑝𝑐 ,我这个格子里有车的概率很高, 而不是 361 个格子中仅有两个格子会报告它们检测出一个对象。所以当你运行算法的时候, 最后可能会对同一个对象做出多次检测,所以非极大值抑制做的就是清理这些检测结果。这 样一辆车只检测一次,而不是每辆车都触发多次检测。

                   

所以具体上,这个算法做的是,首先看看每次报告每个检测结果相关的概率𝑝𝑐 ,在本周的编程练习中有更多细节,实际上是𝑝𝑐 乘以𝑐1 、𝑐2 或𝑐3 。现在我们就说,这个𝑝𝑐 检测概率, 首先看概率最大的那个,这个例子(右边车辆)中是 0.9,然后就说这是最可靠的检测,所以我们就用高亮标记,就说我这里找到了一辆车。这么做之后,非极大值抑制就会逐一审视剩下的矩形,所有和这个最大的边框有很高交并比,高度重叠的其他边界框,那么这些输出就会被抑制。所以这两个矩形𝑝𝑐分别是 0.6 和 0.7,这两个矩形和淡蓝色矩形重叠程度很高, 所以会被抑制,变暗,表示它们被抑制了。

                    

接下来,逐一审视剩下的矩形,找出概率最高,𝑝𝑐最高的一个,在这种情况下是 0.8, 我们就认为这里检测出一辆车(左边车辆),然后非极大值抑制算法就会去掉其他 loU 值很 高的矩形。所以现在每个矩形都会被高亮显示或者变暗,如果你直接抛弃变暗的矩形,那就 剩下高亮显示的那些,这就是最后得到的两个预测结果。

总结一下非最大值抑制算法的流程:

  1. 剔除Pc值小于某阈值(例如0.6)的所有网格;

  2. 选取Pc值最大的网格,利用IoU,摒弃与该网格交叠较大的网格;

  3. 对剩下的网格,重复步骤2。

总结:

上面我们介绍了算法检测单个对象的情况,如果你尝试同时检测三个对象, 比如说行人、汽车、摩托,那么输出向量就会有三个额外的分量。事实证明,正确的做法是独立进行三次非极大值抑制,对每个输出类别都做一次,但这个细节就留给本周的编程练习 吧,其中你可以自己尝试实现,我们可以自己试试在多个对象类别检测时做非极大值抑制。

1.8 Anchor Boxes

到目前为止,对象检测中存在的一个问题是每个格子只能检测出一个对象,如果你想让一个格子检测出多个对象,你可以这么做,就是使用 anchor box 这个概念,我们从一个例子开始讲吧。

                            

假设你有这样一张图片,对于这个例子,我们继续使用 3×3 网格,注意行人的中点和汽车的中点几乎在同一个地方,两者都落入到同一个格子中。所以对于那个格子,如果 𝑦 输出这个向量:

                                                                                

你可以检测这三个类别,行人、汽车和摩托车,它将无法输出检测结果,所以我必须从两个检测结果中选一个。

                                                

而 anchor box 的思路是,这样子,预先定义两个不同形状的 anchor box,或者 anchor box 形状,你要做的是把预测结果和这两个 anchor box 关联起来。一般来说,你可能会用更多的 anchor box,可能要 5 个甚至更多,但对于这个视频,我们就用两个 anchor box,这样 介绍起来简单一些。

如下图所示,同一网格出现了两个目标:人和车。为了同时检测两个目标,我们可以设置两个Anchor Boxes,Anchor box 1检测人,Anchor box 2检测车。也就是说,每个网格多加了一层输出。原来的输出维度是 3 x 3 x 8,现在是3 x 3 x 2 x 8(也可以写成3 x 3 x 16的形式)。这里的2表示有两个Anchor Boxes,用来在一个网格中同时检测多个目标。每个Anchor box都有一个Pc值,若两个Pc值均大于某阈值,则检测到了两个目标。

                                                                    

在使用YOLO算法时,只需对每个Anchor box使用上一节的非最大值抑制即可。Anchor Boxes之间并行实现。

顺便提一下,Anchor Boxes形状的选择可以通过人为选取,也可以使用其他机器学习算法,例如k聚类算法对待检测的所有目标进行形状分类,选择主要形状作为Anchor Boxes。

1.9 YOLO 算法

到目前为止,你们已经学到对象检测算法的大部分组件了,接下来,我们会把所有组件组装在一起构成 YOLO 对象检测算法。

                                   

我们先看看如何构造你的训练集,假设你要训练一个算法去检测三种对象,行人、汽车 和摩托车。这里有 3 个类别标签,如果你要用两个 anchor box,那么输出 𝑦 就是 3×3×2×8,其中 3×3 表示 3×3 个网格,2 是 anchor box 的数量,8 是向量维度,8 实际上先是 5(𝑝𝑐,𝑏𝑥,𝑏𝑦,𝑏h,𝑏𝑤)再加上类别的数量(𝑐1,𝑐2,𝑐3)。你可以将它看成是 3×3×2×8,或者 3×3×16。要构造训练集,你需要遍历 9 个格子,然后构成对应的目标向量𝑦。

                                  

简单起见,我在这里用的是 3×3 网格,实践中用的可能是 19×19×16,或者需要用到更多的 anchor box,可能是 19×19×5×8, 即 19×19×40,用了 5 个 anchor box。这就是训练集,然后你训练一个卷积网络,输入是图片,可能是 100×100×3,然后你的卷积网络最后输出尺寸是,在我们例子中是 3×3×16 或者 3×3×2×8。

算法是怎样做出预测的???

输入图像,你的神经网络的输出尺寸是这 个 3××3×2×8,对于 9 个格子,每个都有对应的向量。对于左上的格子(编号 1),那里没有 任何对象,那么我们希望你的神经网络在那里(第一个𝑝𝑐)输出的是 0,这里(第二个𝑝𝑐) 是 0,然后我们输出一些值,你的神经网络不能输出问号,不能输出 don’t care-s,剩下的我 输入一些数字,但这些数字基本上会被忽略,因为神经网络告诉你,那里没有任何东西,所 以输出是不是对应一个类别的边界框无关紧要,所以基本上是一组数字,多多少少都是噪音 (输出 𝑦 如编号 3 所示)。

                      

那个左下格子(编号 2)的输出𝑦(编号 4 所 示),形式是,对于边界框 1 来说(𝑝𝑐 )是 0,然后就是一组数字,就是噪音(anchor box 1 对应行人,此格子中无行人,𝑝𝑐 =0,𝑏𝑥 =?,𝑏𝑦 =?,𝑏h =?,𝑏𝑤 =?,𝑐1 =?𝑐2 =?,𝑐3 =?)。希望你的算法能输出一些数字,可以对车子指定一个相当准确的边界框(anchor box 2 对应汽车, 此格子中有车,𝑝𝑐 = 1,𝑏𝑥,𝑏𝑦,𝑏h,𝑏𝑤,𝑐1 = 0,𝑐2 = 1,𝑐3 = 0),这就是神经网络做出预测的过程。

非极大值抑制的过程???

为了让内容更有趣一些,我们看看一张新的测试图像,这就是运行非极大值抑制的过程。

如果你使用两个 anchor box,那么对于 9 个格子中 任何一个都会有两个预测的边界框,其中一个的概率𝑝𝑐很低。但 9 个格子中,每个都有两个 预测的边界框,比如说我们得到的边界框是是这样的,注意有一些边界框可以超出所在格子 的高度和宽度(编号 1 所示)。接下来你抛弃概率很低的预测,去掉这些连神经网络都说, 这里很可能什么都没有,所以你需要抛弃这些(编号 2 所示)。

                 

最后,如果你有三个对象检测类别,你希望检测行人,汽车和摩托车,那么你要做的是, 对于每个类别单独运行非极大值抑制,处理预测结果所属类别的边界框,用非极大值抑制来 处理行人类别,用非极大值抑制处理车子类别,然后对摩托车类别进行非极大值抑制,运行 三次来得到最终的预测结果。所以算法的输出最好能够检测出图像里所有的车子,还有所有 的行人(编号 3 所示)。

这就是 YOLO 对象检测算法,这实际上是最有效的对象检测算法之一,包含了整个计算机视觉对象检测领域文献中很多最精妙的思路。你可以在本周的编程作业中尝试现实这个算法。

二、测验

1. 现在你要构建一个能够识别三个对象并定位位置的算法,这些对象分别是:行人(c=1),汽车(c=2),摩托车(c=3)。下图中的标签哪个是正确的?注:y=[pc,bx,by,bh,bw,c1,c2,c3]

                                             

  1. y=[1, 0.3, 0.7, 0.3, 0.3, 0, 1, 0]
  2. y=[1, 0.7, 0.5, 0.3, 0.3, 0, 1, 0]
  3. y=[1, 0.3, 0.7, 0.5, 0.5, 0, 1, 0]
  4. y=[1, 0.3, 0.7, 0.5, 0.5, 1, 0, 0]
  5. y=[0, 0.2, 0.4, 0.5, 0.5, 0, 1, 0]

2. 继续上一个问题,下图中y的值是多少?注:“?”是指“不关心这个值”,这意味着神经网络的损失函数不会关心神经网络对输出的结果,和上面一样,y=[pc,bx,by,bh,bw,c1,c2,c3]。

                                             

 

  1. y=[1, ?, ?, ?, ?, 0, 0, 0]
  2. y=[0, ?, ?, ?, ?, ?, ?, ?]
  3. y=[?, ?, ?, ?, ?, ?, ?, ?]
  4. y=[0, ?, ?, ?, ?, 0, 0, 0]
  5. y=[1, ?, ?, ?, ?, ?, ?, ?]

3. 你现在任职于自动化工厂中,你的系统会看到一罐饮料从传送带上下来,你想要对其进行拍照,然后确定照片中是否有饮料罐,如果有的话就对其进行包装。饮料罐头是圆的,而包装盒是方的,每一罐饮料的大小是一样的,每个图像中最多只有一罐饮料,现在你有下面的方案可供选择,这里有一些训练集图像:

                               

  1. Logistic unit (用于分类图像中是否有罐头)
  2. Logistic unit,bx 和 by
  3. Logistic unit,bx,by,bh (因为bw =bh,所以只需要一个就行了)
  4. Logistic unit,bx,by,bh,bw

4. 如果你想要构建一个能够输入人脸图片输出为N个标记的神经网络(假设图像只包含一张脸),那么你的神经网络有多少个输出节点?

  1. N
  2. 2N
  3. 3N
  4. N^2

5. 当你训练一个视频中描述的对象检测系统时,里需要一个包含了检测对象的许多图片的训练集,然而边界框不需要在训练集中提供,因为算法可以自己学习检测对象,这个说法对吗?

错误。

6. 假如你正在应用一个滑动窗口分类器(非卷积实现),增加步伐不仅会提高准确性,也会降低成本。

错误

7. 在YOLO算法中,在训练时,只有一个单元(该单元包含对象的中心/中点)负责检测这个对象。

正确。

8. 这两个框中IoU大小是多少?左上角的框是2×2大小,右下角的框是2×3大小,重叠部分是1×1。

                                                         

1/9     

9.假如你在下图中的预测框中使用非最大值抑制,其参数是放弃概率≤ 0.4的框,并决定两个框IoU的阈值为0.5,使用非最大值抑制后会保留多少个预测框? 

                                   

 

 

5个。

10.假如你使用YOLO算法,使用19x19格子来检测20个分类,使用5个锚框(anchor box)。在训练的过程中,对于每个图像你需要输出卷积后的结果y作为神经网络目标值(这是最后一层), yyy 可能包括一些“?”或者“不关心的值”。请问最后的输出维度是多少?

19x19x(5×25) (25=20+1+4)

三、编程

自动驾驶-汽车检测

下面你将学习使用非常强大的YOLO模型进行对象检测。 YOLO的两篇论文描述了许多想法:

您将学习:

  • 在汽车检测数据集中使用对象检测
  • 处理边界框

加载依赖包

import argparse
import os
import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow
import scipy.io
import scipy.misc
import numpy as np
import pandas as pd
import PIL
import tensorflow as tf
from keras import backend as K
from keras.layers import Input, Lambda, Conv2D
from keras.models import load_model, Model
from yolo_utils import read_classes, read_anchors, generate_colors, preprocess_image, draw_boxes, scale_boxes
from yad2k.models.keras_yolo import yolo_head, yolo_boxes_to_corners, preprocess_true_boxes, yolo_loss, yolo_body

%matplotlib inline

 问题声明

您正在开发自动驾驶汽车。 作为该项目的重要组成部分,您想首先构建一个汽车检测系统。 为了收集数据,您已经在汽车的引擎盖(即前部)上安装了摄像头,该摄像头会在您行驶时每隔几秒钟拍摄一次前方道路的照片。

您已将所有这些图像收集到一个文件夹中,并通过在找到的每辆汽车周围绘制边框来标记它们。 这是您的边界框的示例。

                                

如果您希望YOLO算法识别80个类别,则可以用𝑐表示类别。要么是1到80的整数,要么是80维向量(具有80个数字),其中一个分量为1,其余部分为0;标签视频讲座使用后一种表示形式。

在此NoteBook中,我们将使用这两种表示形式,具体要取决于哪种方式在特定步骤中更方便。

在本练习中,您将学习YOLO的工作原理,然后将其应用于汽车检测。 由于YOLO模型的训练在计算上非常昂贵,因此我们将加载预训练的权重供您使用。

3.1 YOLO 算法

YOLO(“您只看一次”)是一种流行的算法,因为它可以实现高精度同时还可以实时运行。 从仅需要一次正向传播通过网络进行预测这一意义上讲,该算法“仅看一次图像”。 在非最大抑制之后,它将与边界框一起输出识别的对象。

3.1.1  模型细节

首先要知道的是:

  • 输入的是一批 shape 为(m,608,608,3)的图像
  • 输出是边界框以及已识别类的列表。 每个边界框由6个数字 (𝑝𝑐,𝑏𝑥,𝑏𝑦,𝑏ℎ,𝑏𝑤,𝑐) 表示,如上所述。 如果展开 C
    在一个80维向量中,每个边界框都由85个数字表示。

我们将使用5个anchor boxes。 因此,您可以将YOLO架构视为以下内容:

IMAGE(m,608,608,3)-> DEEP CNN-> ENCODING(m,19,19,5,85)。

让我们详细了解一下此编码表示什么。

                                    

如果对象的中心/中点落入网格单元,则该网格单元负责检测该对象。

由于我们使用的是5个 anchor boxes,因此19 x19单元格中的每个单元都编码了有关5个框的信息。 锚框仅由其宽度和高度定义。 

为简单起见,我们将扁平化 shape 为(19,19,5,85) 编码的的最后两个维度。 因此,深CNN的输出为(19,19,425)。

                          

现在,对于(每个单元格的)每个 box,我们将计算以下元素乘积,并提取该 box 包含某个类的概率。 

                          

这是一种可视化YOLO在图像上预测的方法:

  • 对于每个19x19网格单元,找到概率得分中的最大值(在5个 anchor boxex 和不同类别中均取最大值)。
  • 根据网格单元认为最可能的对象然后为网格单元着色。

执行此操作将得到以下图片:

                                                         

请注意,这种可视化不是YOLO算法本身进行预测的核心部分。 这只是可视化算法中间结果的一种好方法。

可视化YOLO输出的另一种方法是绘制其输出的边界框。 这样做将产生如下可视化效果:

                                                          

每个单元格给您5个 boxes。 该模型总共预测:仅查看一次图像即可一次获得19x19x5 = 1805个盒子(向前通过网络)! 不同的颜色表示不同的类别。
在上图中,我们仅绘制了模型已分配高概率的 boxes ,但是 boxes 仍然太多。 您希望将算法的输出过滤到数量更少的检测对象。 为此,您将使用非最大抑制。 具体来说,您将执行以下步骤:

  • 剔除分数低的盒子(这意味着 boxes 对检测 类别 不是很自信)
  • 当多个 boxes 彼此重叠并检测相同的对象时,仅选择一个框。

3.1.2  在类别分数上进行过滤

您将通过阈值进行第一个过滤。 去掉类“得分”小于指定阈值的所有 box。

该模型总共输出 19x19x5x85 个数字,每个 box 由85个数字描述。 将(19,19,5,85)(或(19,19,425))维度张量重新排列为以下变量将很方便:

  • box_confidence:  shape 为(19×19,5,1)的tensor, 包含 𝑝𝑐 (存在某个对象的置信概率),在19x19单元格中5个boxes 中的每个预测。
  • boxes: shape 为 (19×19,5,4)的tensor 包含 (𝑏𝑥,𝑏𝑦,𝑏ℎ,𝑏𝑤)  每个单元格 的 5个  boxes。
  • box_class_probs: shape 为(19×19,5,80)的tensor 包含检测概率 (𝑐1,𝑐2,...𝑐80)每个单元格中5个 boxes 中的每个 box的 80个类别。

练习:实现yolo_filter_boxes()。

                                

1)通过执行图中所述的元素乘积来计算 box 分数。以下代码可以帮助您选择合适的运算符:

a = np.random.randn(19*19, 5, 1)
b = np.random.randn(19*19, 5, 80)
c = a * b # shape of c will be (19*19, 5, 80)

2)对于每个  box,找到:
具有最高 box 分数 的 类别索引(请小心选择哪个轴;请考虑使用axis = -1)
相应的 box 分数(请小心选择哪个轴;请考虑使用axis = -1)

3)通过使用阈值创建 mask。 提醒一下:([0.9,0.3,0.4,0.5,0.1] <0.4)返回:[False,True,False,False,True]。 对于要保留的 box,mask 应该为True。

4)使用TensorFlow将 mask 应用于box_class_scores,box和box_classes,以过滤掉我们不需要的 box。 您应该只剩下要保留的部分 box。 

提醒:要调用Keras函数,应使用K.function(...)。

import argparse
import os
import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow
import scipy.io
import scipy.misc
import numpy as np
import pandas as pd
import PIL
import tensorflow as tf
from keras import backend as K
from keras.layers import Input, Lambda, Conv2D
from keras.models import load_model, Model
from yolo_utils import read_classes, read_anchors, generate_colors, preprocess_image, draw_boxes, scale_boxes
from yad2k.models.keras_yolo import yolo_head, yolo_boxes_to_corners, preprocess_true_boxes, yolo_loss, yolo_body


def yolo_filter_boxes(box_confidence, boxes, box_class_probs, threshold=.6):
    """通过对对象和类的置信度设置阈值 来过滤YOLO框。

    参数:
    box_confidence -- tensor of shape (19, 19, 5, 1)
    boxes -- tensor of shape (19, 19, 5, 4)
    box_class_probs -- tensor of shape (19, 19, 5, 80)
    threshold -- 如果[最高类别概率得分<阈值],则去掉相应的框

    返回:
    scores -- tensor of shape (None,), 包含所选 boxes 的类别概率分数
    boxes -- tensor of shape (None, 4), 包含选定框的(b_x,b_y,b_h,b_w)坐标
    classes -- tensor of shape (None,), 包含所选框检测到的类的索引

    注意:此处“None”是因为您不知道所选框的确切数量,因为它取决于阈值。
    例如,如果有10个框,则分数的实际输出大小将为(10,)。
    """

    # Step 1:
    box_scores = box_confidence * box_class_probs # shape

    # Step 2: 通过最大的box_scores查找box_classes,跟踪相应的 score
    box_classes = K.argmax(box_scores, axis=-1) # shape (19,19,5)
    box_class_scores = K.max(box_scores, axis=-1)#shape (19,19,5)

    # Step 3: 使用“阈值”基于“ box_class_scores”创建过滤 mask。 mask的尺寸应与box_class_scores相同,并且对于要保留的框为True(概率> =阈值)
    filtering_mask = box_class_scores >= threshold  # don't use A.eval() >= B 只保留 每个单元格中的每个Box类别最大分数 大于 阈值 的单元格

    # Step 4: 应用  mask to scores, boxes and classes
    scores = tf.boolean_mask(box_class_scores, filtering_mask)
    boxes = tf.boolean_mask(boxes, filtering_mask)
    classes = tf.boolean_mask(box_classes, filtering_mask)

    return scores, boxes, classes


if __name__ == '__main__':
    with tf.Session() as test_a:
        box_confidence = tf.random_normal([19, 19, 5, 1], mean=1, stddev=4, seed=1)
        boxes = tf.random_normal([19, 19, 5, 4], mean=1, stddev=4, seed=1)
        box_class_probs = tf.random_normal([19, 19, 5, 80], mean=1, stddev=4, seed=1)
        scores, boxes, classes = yolo_filter_boxes(box_confidence, boxes, box_class_probs, threshold=0.5)
        print("scores[2] = " + str(scores[2].eval()))
        print("boxes[2] = " + str(boxes[2].eval()))
        print("classes[2] = " + str(classes[2].eval()))
        print("scores.shape = " + str(scores.shape))
        print("boxes.shape = " + str(boxes.shape))
        print("classes.shape = " + str(classes.shape))

输出结果:

scores[2] = 10.750582
boxes[2] = [ 8.426533   3.2713668 -0.5313436 -4.9413733]
classes[2] = 7
scores.shape = (?,)
boxes.shape = (?, 4)
classes.shape = (?,)

3.1.3 非最大抑制

即使在通过对 类别分数进行阈值过滤后,您仍然会遇到很多重叠的boxes。 用于选择正确 boxes 的第二个过滤器称为非最大抑制(NMS)。

                                            

在此示例中,该模型预测了3辆汽车,但实际上是同一辆汽车的3个预测。 运行非最大抑制(NMS)将仅选择3个框中最准确的(概率最高)之一。

非最大抑制使用非常重要的方法,称为“交并集”或IoU。

                                             

  • 仅在本练习中,我们使用两个角(左上角和右下角)(x1,y1,x2,y2)而不是中点和高度/宽度来定义一个框。
  • 要计算矩形的面积,您需要将其高度(y2-y1)乘以其宽度(x2-x1)
  • 您还需要找到两个框的交点的坐标(xi1,yi1,xi2,yi2)。 请记住:
    • xi1 =两个boxes的x1坐标的最大值
    • yi1 =两个boxes的y1坐标的最大值
    • xi2 =两个boxes的x2坐标的最小值
    • yi2 =两个boxes的y2坐标的最小值 

在此代码中,我们使用以下约定:(0,0)是图像的左上角,(1,0)是右上角,(1,1)是右下角。

def iou(box1, box2):
    """在box1和box2之间实现交并集(IoU)
    参数:
    box1 -- 第一个 box,列出具有坐标(x1,y1,x2,y2)的对象
    box2 -- 第二个 box,列出具有坐标(x1,y1,x2,y2)的对象
    """

    # 计算box1和box2相交的 面积。
    xi1 = max(box1[0], box2[0])
    yi1 = max(box1[1], box2[1])
    xi2 = min(box1[2], box2[2])
    yi2 = min(box1[3], box2[3])
    inter_area = (yi2 - yi1) * (xi2 - xi1)

    # 通过使用公式计算并集面积:Union(A,B)= A + B-Inter(A,B)
    box1_area = (box1[2] - box1[0]) * (box1[3] - box1[1])
    box2_area = (box2[2] - box2[0]) * (box2[3] - box2[1])
    union_area = box1_area + box2_area - inter_area

    # 计算 IoU
    iou = inter_area / union_area
    return iou


if __name__ == '__main__':
        box1 = (2, 1, 4, 3)
        box2 = (1, 2, 3, 4)
        print("iou = " + str(iou(box1, box2)))

现在您可以实现非最大抑制。 关键步骤是:

  • 选择具有最高分数的 box 。
  • 计算其与所有其他 box  的重叠,并删除与其重叠超过iou_threshold的 box 。
  • 返回第1步并进行迭代,直到没有比当前所选 box  更低的得分的 box 为止。

这将删除所有与所选 boxes  重叠较大的 boxes 。 仅保留“最佳”boxes 。

练习:使用TensorFlow实现yolo_non_max_suppression()。 TensorFlow具有两个内置函数,用于实现非最大抑制(因此您实际上不需要使用iou()实现):


def yolo_non_max_suppression(scores, boxes, classes, max_boxes=10, iou_threshold=0.5):
    """
    将非最大抑制(NMS)应用到 boxes 组

    参数:
    scores -- tensor of shape (None,), output of yolo_filter_boxes()
    boxes -- tensor of shape (None, 4), output of yolo_filter_boxes() that have been scaled to the image size (see later)
    classes -- tensor of shape (None,), output of yolo_filter_boxes()
    max_boxes -- integer, maximum number of predicted boxes you'd like
    iou_threshold -- real value, "intersection over union" threshold used for NMS filtering

    Returns:
    scores -- tensor of shape (, None), predicted score for each box
    boxes -- tensor of shape (4, None), predicted box coordinates
    classes -- tensor of shape (, None), predicted class for each box

    Note: 输出 tensors 的“None”维显然必须小于max_boxes。
    另请注意:函数将转置 scores, boxes, classes的shape。 这是为了方便。
    """

    max_boxes_tensor = K.variable(max_boxes, dtype='int32')  # tensor 被使用在 tf.image.non_max_suppression()
    K.get_session().run(tf.variables_initializer([max_boxes_tensor]))  # 初始化变量 max_boxes_tensor

    # 使用tf.image.non_max_suppression()获取与您保留的 boxes 相对应的索引列表
    nms_indices = tf.image.non_max_suppression(boxes, scores, max_boxes, iou_threshold)

    # 使用K.gather()从scores, boxes 和 classes 仅选择nms_indices
    scores = K.gather(scores, nms_indices)
    boxes = K.gather(boxes, nms_indices)
    classes = K.gather(classes, nms_indices)

    return scores, boxes, classes


if __name__ == '__main__':
    with tf.Session() as test_a:
        box_confidence = tf.random_normal([19, 19, 5, 1], mean=1, stddev=4, seed=1)
        boxes = tf.random_normal([19, 19, 5, 4], mean=1, stddev=4, seed=1)
        box_class_probs = tf.random_normal([19, 19, 5, 80], mean=1, stddev=4, seed=1)
        scores, boxes, classes = yolo_filter_boxes(box_confidence, boxes, box_class_probs, threshold=0.5)

    with tf.Session() as test_b:
        scores = tf.random_normal([54,], mean=1, stddev=4, seed = 1)
        boxes = tf.random_normal([54, 4], mean=1, stddev=4, seed = 1)
        classes = tf.random_normal([54,], mean=1, stddev=4, seed = 1)
        scores, boxes, classes = yolo_non_max_suppression(scores, boxes, classes)
        print("scores[2] = " + str(scores[2].eval()))
        print("boxes[2] = " + str(boxes[2].eval()))
        print("classes[2] = " + str(classes[2].eval()))
        print("scores.shape = " + str(scores.eval().shape))
        print("boxes.shape = " + str(boxes.eval().shape))
        print("classes.shape = " + str(classes.eval().shape))

输出结果:

scores[2] = 6.938395
boxes[2] = [-5.299932    3.1379814   4.450367    0.95942086]
classes[2] = -2.2452729
scores.shape = (10,)
boxes.shape = (10, 4)
classes.shape = (10,)

3.1.4 结束过滤

是时候实现采用深层CNN输出(19x19x5x85维度)并使用刚刚实现的方法对所有 boxes 进行过滤的功能了。

练习:实现yolo_eval(),它获取YOLO编码的输出并使用 score 阈值和NMS过滤 boxes。 您只需要了解最后一个实施细节。 有几种表示 boxes 的方法,例如通过它们的角或中点和高度/宽度。 YOLO使用以下功能(我们已提供)在不同时间在几种此类格式之间进行转换:

boxes = yolo_boxes_to_corners(box_xy, box_wh) 

YOLO的网络经过训练可以在608x608的图像上运行。 如果要在其他尺寸的图像上测试此数据(例如,汽车检测数据集具有720x1280的图像),此步骤将重新调整 boxes 的比例,以便可以将其绘制在原始720x1280图像的顶部。 

不用担心这两个方法; 下面将向您展示需要在什么时候调用他们。

def yolo_eval(yolo_outputs, image_shape=(720., 1280.), max_boxes=10, score_threshold=.6, iou_threshold=.5):
    """
    将YOLO编码的输出(很多boxes)连同它们的scores, box coordinates 和 classes一起转换为预测的 boxes。

    参数:
    yolo_outputs -- output of the encoding model (for image_shape of (608, 608, 3)), contains 4 tensors:
                    box_confidence: tensor of shape (None, 19, 19, 5, 1)
                    box_xy: tensor of shape (None, 19, 19, 5, 2)
                    box_wh: tensor of shape (None, 19, 19, 5, 2)
                    box_class_probs: tensor of shape (None, 19, 19, 5, 80)
    image_shape -- tensor of shape (2,) containing the input shape, in this notebook we use (608., 608.) (has to be float32 dtype)
    max_boxes -- integer, maximum number of predicted boxes you'd like
    score_threshold -- real value, if [ highest class probability score < threshold], then get rid of the corresponding box
    iou_threshold -- real value, "intersection over union" threshold used for NMS filtering

    Returns:
    scores -- tensor of shape (None, ), predicted score for each box
    boxes -- tensor of shape (None, 4), predicted box coordinates
    classes -- tensor of shape (None,), predicted class for each box
    """


    # 检索YOLO模型的输出
    box_confidence, box_xy, box_wh, box_class_probs = yolo_outputs[:]

    # 准备转换boxes进行过滤功能
    boxes = yolo_boxes_to_corners(box_xy, box_wh)

    # Use one of the functions you've implemented to perform Score-filtering with a threshold of score_threshold (≈1 line)
    # 使用已实现的方法:用阈值score_threshold 执行分数过滤
    scores, boxes, classes = yolo_filter_boxes(box_confidence, boxes, box_class_probs, score_threshold)

    # 将 boxes 缩放回原始图像形状。
    boxes = scale_boxes(boxes, image_shape)

    # 使用已实现的功能:执行非最大抑制,阈值为iou_threshold
    scores, boxes, classes = yolo_non_max_suppression(scores, boxes, classes, max_boxes, iou_threshold)

    return scores, boxes, classes


if __name__ == '__main__':


    with tf.Session() as test_b:
        yolo_outputs = (tf.random_normal([19, 19, 5, 1], mean=1, stddev=4, seed=1),
                        tf.random_normal([19, 19, 5, 2], mean=1, stddev=4, seed=1),
                        tf.random_normal([19, 19, 5, 2], mean=1, stddev=4, seed=1),
                        tf.random_normal([19, 19, 5, 80], mean=1, stddev=4, seed=1))
        scores, boxes, classes = yolo_eval(yolo_outputs)
        print("scores[2] = " + str(scores[2].eval()))
        print("boxes[2] = " + str(boxes[2].eval()))
        print("classes[2] = " + str(classes[2].eval()))
        print("scores.shape = " + str(scores.eval().shape))
        print("boxes.shape = " + str(boxes.eval().shape))
        print("classes.shape = " + str(classes.eval().shape))

输出结果:

scores[2] = 138.79124
boxes[2] = [1292.3297  -278.52167 3876.9893  -835.56494]
classes[2] = 54
scores.shape = (10,)
boxes.shape = (10, 4)
classes.shape = (10,)

** YOLO的摘要**:

 -输入图像(608、608、3)

-输入图像经过CNN,从而产生(19,19,5,85)维度输出。

-将最后两个维度展平后,输出的是 shape为(19、19、425)

-输入图像上方19x19网格中的每个单元格均给出425个数字。

-425 = 5 x 85,因为每个单元格都包含5个预测 boxes,对应于5个anchor boxes,如在教程中所示。

-85 = 5 + 80,其中5是因为(𝑝𝑐,𝑏𝑥,𝑏𝑦,𝑏ℎ,𝑏𝑤)有5个数字,而我们要检测的 类别 数量是80个

-您可以根据以下条件只选择几个 boxes:

-score阈值:丢弃检测到得分低于阈值的类别的 boxes

-非max抑制:计算并集上的交点,并避免选择重叠的 boxes

-这将为您提供YOLO的最终输出。

3.2 在图像上测试YOLO预训练模型

在本部分中,您将使用预训练的模型并在汽车检测数据集上对其进行测试。 像往常一样,您首先创建一个会话来启动图形。 运行以下单元格。

sess = K.get_session()

3.2.1 定义 classes, anchors 和 image shape

回想一下,我们正在尝试检测80个类别,并使用5个 anchor boxes。 我们已经在两个文件“ coco_classes.txt”和“ yolo_anchors.txt”中收集了有关80个类和5个 boxes 的信息。 让我们通过运行下面的代码将这些数据加载到模型中。

汽车检测数据集包含720x1280图像,我们已将其预处理为608x608图像。

class_names = read_classes("model_data/coco_classes.txt")
anchors = read_anchors("model_data/yolo_anchors.txt")
image_shape = (720., 1280.)   

3.2.2  加载预训练的模型

训练YOLO模型需要花费很长时间,并且需要用于较大范围目标类的相当大的带标签的边界boxes 数据集。 您将要加载存储在“ yolo.h5”中的现有预先训练的Keras YOLO模型。 (这些权重来自YOLO官方网站,并使用Allan Zelener编写的函数进行了转换。参考在本笔记本的末尾。从技术上讲,这些是“ YOLOv2”模型的参数,但我们将更简单地参考 (在此笔记本中为“ YOLO”)。)运行下面的单元格,从该文件中加载模型。

yolo.h5生成:

git clone https://github.com/allanzelener/YAD2K.git

cd YAD2K

下载 yolo.weights和yolo.cfg放到文件夹,命令行执行:python yad2k.py yolo.cfg yolo.weights model_data/yolo.h5

下载地址:http://pjreddie.com/media/files/yolo.weights

https://raw.githubusercontent.com/pjreddie/darknet/master/cfg/yolo.cfg

或者直接使用已经生成的 yolo.h5:  https://pan.baidu.com/s/1q6kqQIYjLQRENKjxZGKNyg  密码:dq3k

yolo_model = load_model("model_data/yolo.h5")

这会加载经过训练的YOLO模型的权重。 这是模型包含的图层的摘要。

yolo_model.summary()
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_1 (InputLayer)            (None, 608, 608, 3)  0                                            
__________________________________________________________________________________________________
conv2d_1 (Conv2D)               (None, 608, 608, 32) 864         input_1[0][0]                    
__________________________________________________________________________________________________
batch_normalization_1 (BatchNor (None, 608, 608, 32) 128         conv2d_1[0][0]                   
__________________________________________________________________________________________________
leaky_re_lu_1 (LeakyReLU)       (None, 608, 608, 32) 0           batch_normalization_1[0][0]      
__________________________________________________________________________________________________
max_pooling2d_1 (MaxPooling2D)  (None, 304, 304, 32) 0           leaky_re_lu_1[0][0]              
__________________________________________________________________________________________________
conv2d_2 (Conv2D)               (None, 304, 304, 64) 18432       max_pooling2d_1[0][0]            
__________________________________________________________________________________________________
batch_normalization_2 (BatchNor (None, 304, 304, 64) 256         conv2d_2[0][0]                   
__________________________________________________________________________________________________
leaky_re_lu_2 (LeakyReLU)       (None, 304, 304, 64) 0           batch_normalization_2[0][0]      
__________________________________________________________________________________________________
max_pooling2d_2 (MaxPooling2D)  (None, 152, 152, 64) 0           leaky_re_lu_2[0][0]              
__________________________________________________________________________________________________
conv2d_3 (Conv2D)               (None, 152, 152, 128 73728       max_pooling2d_2[0][0]            
__________________________________________________________________________________________________
batch_normalization_3 (BatchNor (None, 152, 152, 128 512         conv2d_3[0][0]                   
__________________________________________________________________________________________________
leaky_re_lu_3 (LeakyReLU)       (None, 152, 152, 128 0           batch_normalization_3[0][0]      
__________________________________________________________________________________________________
conv2d_4 (Conv2D)               (None, 152, 152, 64) 8192        leaky_re_lu_3[0][0]              
__________________________________________________________________________________________________
batch_normalization_4 (BatchNor (None, 152, 152, 64) 256         conv2d_4[0][0]                   
__________________________________________________________________________________________________
leaky_re_lu_4 (LeakyReLU)       (None, 152, 152, 64) 0           batch_normalization_4[0][0]      
__________________________________________________________________________________________________
conv2d_5 (Conv2D)               (None, 152, 152, 128 73728       leaky_re_lu_4[0][0]              
__________________________________________________________________________________________________
batch_normalization_5 (BatchNor (None, 152, 152, 128 512         conv2d_5[0][0]                   
__________________________________________________________________________________________________
leaky_re_lu_5 (LeakyReLU)       (None, 152, 152, 128 0           batch_normalization_5[0][0]      
__________________________________________________________________________________________________
max_pooling2d_3 (MaxPooling2D)  (None, 76, 76, 128)  0           leaky_re_lu_5[0][0]              
__________________________________________________________________________________________________
conv2d_6 (Conv2D)               (None, 76, 76, 256)  294912      max_pooling2d_3[0][0]            
__________________________________________________________________________________________________
batch_normalization_6 (BatchNor (None, 76, 76, 256)  1024        conv2d_6[0][0]                   
__________________________________________________________________________________________________
leaky_re_lu_6 (LeakyReLU)       (None, 76, 76, 256)  0           batch_normalization_6[0][0]      
__________________________________________________________________________________________________
conv2d_7 (Conv2D)               (None, 76, 76, 128)  32768       leaky_re_lu_6[0][0]              
__________________________________________________________________________________________________
batch_normalization_7 (BatchNor (None, 76, 76, 128)  512         conv2d_7[0][0]                   
__________________________________________________________________________________________________
leaky_re_lu_7 (LeakyReLU)       (None, 76, 76, 128)  0           batch_normalization_7[0][0]      
__________________________________________________________________________________________________
conv2d_8 (Conv2D)               (None, 76, 76, 256)  294912      leaky_re_lu_7[0][0]              
__________________________________________________________________________________________________
batch_normalization_8 (BatchNor (None, 76, 76, 256)  1024        conv2d_8[0][0]                   
__________________________________________________________________________________________________
leaky_re_lu_8 (LeakyReLU)       (None, 76, 76, 256)  0           batch_normalization_8[0][0]      
__________________________________________________________________________________________________
max_pooling2d_4 (MaxPooling2D)  (None, 38, 38, 256)  0           leaky_re_lu_8[0][0]              
__________________________________________________________________________________________________
conv2d_9 (Conv2D)               (None, 38, 38, 512)  1179648     max_pooling2d_4[0][0]            
__________________________________________________________________________________________________
batch_normalization_9 (BatchNor (None, 38, 38, 512)  2048        conv2d_9[0][0]                   
__________________________________________________________________________________________________
leaky_re_lu_9 (LeakyReLU)       (None, 38, 38, 512)  0           batch_normalization_9[0][0]      
__________________________________________________________________________________________________
conv2d_10 (Conv2D)              (None, 38, 38, 256)  131072      leaky_re_lu_9[0][0]              
__________________________________________________________________________________________________
batch_normalization_10 (BatchNo (None, 38, 38, 256)  1024        conv2d_10[0][0]                  
__________________________________________________________________________________________________
leaky_re_lu_10 (LeakyReLU)      (None, 38, 38, 256)  0           batch_normalization_10[0][0]     
__________________________________________________________________________________________________
conv2d_11 (Conv2D)              (None, 38, 38, 512)  1179648     leaky_re_lu_10[0][0]             
__________________________________________________________________________________________________
batch_normalization_11 (BatchNo (None, 38, 38, 512)  2048        conv2d_11[0][0]                  
__________________________________________________________________________________________________
leaky_re_lu_11 (LeakyReLU)      (None, 38, 38, 512)  0           batch_normalization_11[0][0]     
__________________________________________________________________________________________________
conv2d_12 (Conv2D)              (None, 38, 38, 256)  131072      leaky_re_lu_11[0][0]             
__________________________________________________________________________________________________
batch_normalization_12 (BatchNo (None, 38, 38, 256)  1024        conv2d_12[0][0]                  
__________________________________________________________________________________________________
leaky_re_lu_12 (LeakyReLU)      (None, 38, 38, 256)  0           batch_normalization_12[0][0]     
__________________________________________________________________________________________________
conv2d_13 (Conv2D)              (None, 38, 38, 512)  1179648     leaky_re_lu_12[0][0]             
__________________________________________________________________________________________________
batch_normalization_13 (BatchNo (None, 38, 38, 512)  2048        conv2d_13[0][0]                  
__________________________________________________________________________________________________
leaky_re_lu_13 (LeakyReLU)      (None, 38, 38, 512)  0           batch_normalization_13[0][0]     
__________________________________________________________________________________________________
max_pooling2d_5 (MaxPooling2D)  (None, 19, 19, 512)  0           leaky_re_lu_13[0][0]             
__________________________________________________________________________________________________
conv2d_14 (Conv2D)              (None, 19, 19, 1024) 4718592     max_pooling2d_5[0][0]            
__________________________________________________________________________________________________
batch_normalization_14 (BatchNo (None, 19, 19, 1024) 4096        conv2d_14[0][0]                  
__________________________________________________________________________________________________
leaky_re_lu_14 (LeakyReLU)      (None, 19, 19, 1024) 0           batch_normalization_14[0][0]     
__________________________________________________________________________________________________
conv2d_15 (Conv2D)              (None, 19, 19, 512)  524288      leaky_re_lu_14[0][0]             
__________________________________________________________________________________________________
batch_normalization_15 (BatchNo (None, 19, 19, 512)  2048        conv2d_15[0][0]                  
__________________________________________________________________________________________________
leaky_re_lu_15 (LeakyReLU)      (None, 19, 19, 512)  0           batch_normalization_15[0][0]     
__________________________________________________________________________________________________
conv2d_16 (Conv2D)              (None, 19, 19, 1024) 4718592     leaky_re_lu_15[0][0]             
__________________________________________________________________________________________________
batch_normalization_16 (BatchNo (None, 19, 19, 1024) 4096        conv2d_16[0][0]                  
__________________________________________________________________________________________________
leaky_re_lu_16 (LeakyReLU)      (None, 19, 19, 1024) 0           batch_normalization_16[0][0]     
__________________________________________________________________________________________________
conv2d_17 (Conv2D)              (None, 19, 19, 512)  524288      leaky_re_lu_16[0][0]             
__________________________________________________________________________________________________
batch_normalization_17 (BatchNo (None, 19, 19, 512)  2048        conv2d_17[0][0]                  
__________________________________________________________________________________________________
leaky_re_lu_17 (LeakyReLU)      (None, 19, 19, 512)  0           batch_normalization_17[0][0]     
__________________________________________________________________________________________________
conv2d_18 (Conv2D)              (None, 19, 19, 1024) 4718592     leaky_re_lu_17[0][0]             
__________________________________________________________________________________________________
batch_normalization_18 (BatchNo (None, 19, 19, 1024) 4096        conv2d_18[0][0]                  
__________________________________________________________________________________________________
leaky_re_lu_18 (LeakyReLU)      (None, 19, 19, 1024) 0           batch_normalization_18[0][0]     
__________________________________________________________________________________________________
conv2d_19 (Conv2D)              (None, 19, 19, 1024) 9437184     leaky_re_lu_18[0][0]             
__________________________________________________________________________________________________
batch_normalization_19 (BatchNo (None, 19, 19, 1024) 4096        conv2d_19[0][0]                  
__________________________________________________________________________________________________
conv2d_21 (Conv2D)              (None, 38, 38, 64)   32768       leaky_re_lu_13[0][0]             
__________________________________________________________________________________________________
leaky_re_lu_19 (LeakyReLU)      (None, 19, 19, 1024) 0           batch_normalization_19[0][0]     
__________________________________________________________________________________________________
batch_normalization_21 (BatchNo (None, 38, 38, 64)   256         conv2d_21[0][0]                  
__________________________________________________________________________________________________
conv2d_20 (Conv2D)              (None, 19, 19, 1024) 9437184     leaky_re_lu_19[0][0]             
__________________________________________________________________________________________________
leaky_re_lu_21 (LeakyReLU)      (None, 38, 38, 64)   0           batch_normalization_21[0][0]     
__________________________________________________________________________________________________
batch_normalization_20 (BatchNo (None, 19, 19, 1024) 4096        conv2d_20[0][0]                  
__________________________________________________________________________________________________
space_to_depth_x2 (Lambda)      (None, 19, 19, 256)  0           leaky_re_lu_21[0][0]             
__________________________________________________________________________________________________
leaky_re_lu_20 (LeakyReLU)      (None, 19, 19, 1024) 0           batch_normalization_20[0][0]     
__________________________________________________________________________________________________
concatenate_1 (Concatenate)     (None, 19, 19, 1280) 0           space_to_depth_x2[0][0]          
                                                                 leaky_re_lu_20[0][0]             
__________________________________________________________________________________________________
conv2d_22 (Conv2D)              (None, 19, 19, 1024) 11796480    concatenate_1[0][0]              
__________________________________________________________________________________________________
batch_normalization_22 (BatchNo (None, 19, 19, 1024) 4096        conv2d_22[0][0]                  
__________________________________________________________________________________________________
leaky_re_lu_22 (LeakyReLU)      (None, 19, 19, 1024) 0           batch_normalization_22[0][0]     
__________________________________________________________________________________________________
conv2d_23 (Conv2D)              (None, 19, 19, 425)  435625      leaky_re_lu_22[0][0]             
==================================================================================================
Total params: 50,983,561
Trainable params: 50,962,889
Non-trainable params: 20,672

注意:在某些计算机上,您可能会看到Keras发出的警告消息。 不必担心

提醒:此模型将经过预处理的一批输入图像(shape:(m,608,608,3))转换为 shape 为(m,19,19,5,85)的 tensor,如图(2)所示。

3.2.3 将模型的输出转换为可用的边界框 tensors

yolo_model的输出是(m,19、19、5、85)tensor,需要通过non-trivial的处理和转换。

yolo_outputs = yolo_head(yolo_model.output, anchors, len(class_names))

您已将yolo_outputs添加到 graph 中。 这组4 tensors 准备好用作yolo_eval函数的输入。

3.2.4 过滤 boxes

yolo_outputs以正确的格式为您提供了yolo_model的所有预测  boxes 。 现在,您可以执行过滤并仅选择最佳  boxes 。 现在,让我们调用先前已实现的yolo_eval来执行此操作。

scores, boxes, classes = yolo_eval(yolo_outputs, image_shape)

3.2.5 在图像上运行graph

您创建了一个(sess)图,可以将其总结如下:

  • yolo_model.input被给到 yolo_model。 该模型用于计算输出yolo_model.output
  • yolo_model.output由yolo_head处理。 它给你yolo_outputs
  • yolo_outputs通过过滤方法yolo_eval。 它输出您的预测:scores, boxes, classes.

下面的代码还使用以下方法:

image, image_data = preprocess_image("images/" + image_file, model_image_size = (608, 608))

输出:

  • 图像:用于绘图 boxes 的图像的python(PIL)表示形式。 您不需要使用它。
  • image_data:表示图像的numpy数组。 这将是CNN的输入。


重要说明:当模型使用BatchNorm(在YOLO中就是这种情况)时,您将需要在feed_dict {K.learning_phase():0}中传递一个额外的占位符。

def predict(sess, image_file):
    """
    运行存储在“ sess”中的 graph 来预测“ image_file”的boxes。 打印并绘制预测结果。

    参数:
    sess -- 包含YOLO图的tensorflow / Keras会话
    image_file -- 存储在“图像”文件夹中的图像的名称。

    返回:
    out_scores -- tensor of shape (None, ), scores of the predicted boxes
    out_boxes -- tensor of shape (None, 4), coordinates of the predicted boxes
    out_classes -- tensor of shape (None, ), class index of the predicted boxes

    注意:“None”实际上代表预测的 boxes 数,在0到max_boxes之间变化。
    """

    # 预处理图像
    image, image_data = preprocess_image("images/" + image_file, model_image_size=(608, 608))

    # 使用正确的 tensors 运行 session,并在feed_dict中选择正确的 placeholders。
    # 你需要使用 feed_dict={yolo_model.input: ... , K.learning_phase(): 0})
    out_scores, out_boxes, out_classes = sess.run([scores, boxes, classes],
                                                  feed_dict={yolo_model.input: image_data, K.learning_phase(): 0})

    # 打印预测信息
    print('Found {} boxes for {}'.format(len(out_boxes), image_file))
    # 生成绘图bounding boxes的颜色。
    colors = generate_colors(class_names)
    # 在图像文件上绘制boxes
    draw_boxes(image, out_scores, out_boxes, out_classes, class_names, colors)
    # 将预测的 bounding box 保存在图像上
    image.save(os.path.join("out", image_file), quality=90)
    # 显示结果
    output_image = scipy.misc.imread(os.path.join("out", image_file))
    imshow(output_image)

    return out_scores, out_boxes, out_classes

if __name__ == '__main__':
        # 测试yolo
        with K.get_session() as sess:
            class_names = read_classes('model_data/coco_classes.txt')
            anchors = read_anchors("model_data/yolo_anchors.txt")
            image_shape = (720., 1280.)
            yolo_model = load_model("model_data/yolo-2.h5")
            # 将yolo输出转换为边界框
            yolo_outputs = yolo_head(yolo_model.output, anchors, len(class_names))
            scores, boxes, classes = yolo_eval(yolo_outputs, image_shape)
            out_scores, out_boxes, out_classes = predict(sess, "test.jpg")

在“ test.jpg”图像上运行验证您的功能是否正确。

输出结果:

Found 7 boxes for test.jpg
car 0.60 (925, 285) (1045, 374)
car 0.66 (706, 279) (786, 350)
bus 0.67 (5, 266) (220, 407)
car 0.70 (947, 324) (1280, 705)
car 0.74 (159, 303) (346, 440)
car 0.80 (761, 282) (942, 412)
car 0.89 (367, 300) (745, 648)

                                            

如果要在for循环中运行所有图像的会话。 这是您将得到的:

**您应该记住的**:-YOLO是一种快速,准确的先进对象检测模型-它通过CNN运行输入图像,该CNN输出19x19x5x85的维度。

-可以将编码视为一个网格,其中19x19单元格中的每个单元格包含有关5个boxes的信息

-使用非最大抑制功能过滤所有框。 具体来说:

-对仅检测到准确类别(高概率)的类别进行检测的概率的分数阈值

-交并集(IoU)的阈值来消除重叠的 boxes

-因为从随机初始化的权重中训练YOLO模型并非易事,并且需要 大型数据集以及大量计算,我们在此练习中使用了以前训练的模型参数。 如果您愿意,您也可以尝试使用自己的数据集对YOLO模型进行微调,尽管这不是一件容易的事。

参考文献:本NoteBook中提出的想法主要来自两篇YOLO论文。 此处的实现也获得了重大启发,并使用了Allan Zelener的github存储库中的许多组件。 本练习中使用的预训练权重来自YOLO官方网站。

四、问题的解决

1)AttributeError: module 'scipy.misc' has no attribute 'imread'报错问题

经过查询和尝试,发现是scipy的版本问题, 降级到scipy==1.2.1就可以完美解决了。

命令如下:

pip install scipy==1.2.1

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值