目标定位
图片分类任务我们已经熟悉了,就是算法遍历图片,判断其中的对象是不是汽车,这就是图片分类。这节课我们要学习构建神经网络的另一个问题,即定位分类问题。这意味着,我们不仅要用算法判断图片中是不是一辆汽车,还要在图片中标记出它的位置,用边框或红色方框把汽车圈起来,这就是定位分类问题。其中“定位”的意思是判断汽车在图片中的具体位置。之后,我们再讲讲当图片中有多个对象时,应该如何检测它们,并确定出位置。比如,你正在做一个自动驾驶程序,程序不但要检测其它车辆,还要检测其它对象,如行人、摩托车等等,稍后我们再详细讲。
本节我们要研究的分类定位问题,通常只有一个较大的对象位于图片中间位置,我们要对它进行识别和定位。而在对象检测问题中,图片可以含有多个对象,甚至单张图片中会有多个不同分类的对象。因此,图片分类的思路可以帮助学习分类定位,而对象定位的思路又有助于学习对象检测,我们先从分类和定位开始讲起。
图片分类问题你已经并不陌生了,例如,输入一张图片到多层卷积神经网络。这就是卷积神经网络,它会输出一个特征向量,并反馈给softmax单元来预测图片类型。
如果你正在构建汽车自动驾驶系统,那么对象可能包括以下几类:行人、汽车、摩托车和背景,这意味着图片中不含有前三种对象,也就是说图片中没有行人、汽车和摩托车,输出结果会是背景对象,这四个分类就是softmax函数可能输出的结果。
下面是如何为监督学习任务定义目标标签y,请注意,这有四个分类,神经网络输出的是这四个数字和一个分类标签,或分类标签出现的概率。目标标签y的定义如下:
它是一个向量,第一个组件pc表示是否含有对象,如果对象属于前三类(行人、汽车、摩托车),则pc=1,如果是背景,则图片中没有要检测的对象,则pc=0 。我们可以这样理解pc,它表示被检测对象属于某一分类的概率,背景分类除外。
如果检测到对象,就输出被检测对象的边界框参数bx、by、bh和bw。最后,如果存在某个对象,那么pc,同时输出c1、c2 和c3,表示该对象属于1-3类中的哪一类,是行人,汽车还是摩托车。鉴于我们所要处理的问题,我们假设图片中只含有一个对象,所以针对这个分类定位问题,图片最多只会出现其中一个对象。
我们再看几个例子,假如下图是一张训练集图片。在y当中,第一个元素pc,因为图中有一辆车,bx、by、bh和bw会指明边界框的位置,所以标签训练集需要标签的边界框。图片中是一辆车,所以结果属于分类2,因为定位目标不是行人或摩托车,而是汽车,所以c1=0,c2=1,c3=0,c1、c2和c3中最多只有一个等于1。
上图是只有一个检测对象的情况,如果图片中没有检测对象呢?看下图
这种情况下,pc=0,y的其它参数将变得毫无意义。这里我全部写成问号,表示”毫无意义“的参数,因为图片中不存在检测对象,所以不用考虑网络输出中边界框的大小。也不用考虑图片中的对象是属于c1、c2、c3中的哪一类。
最后,我们介绍一下神经网络的损失函数,其参数为类别y和网络输出y帽,如果采用平方误差策略,则
损失值等于每个元素相应差值的平方和。
如果图片中存在定位对象,那么y1 = 1,所以y1 = pc,同样地,如果图片中存在定位对象,pc = 1,损失值就是不同元素的平方和。
另一种情况是,y1=0,也就是pc=0,损失值是(y1帽 - y1)^2,因为对于这种情况,我们不用考虑其它元素,只需要关注神经网络输出pc的准确度。
特征点检测
假设我们正在构建一个人脸识别应用,如下图。出于某种原因,我们希望算法可以给出眼角的具体位置。眼角坐标为(x,y),你可以让神经网络的最后一层多输出两个数字 lx 和 ly,作为眼角的坐标值。如果你想知道两只眼睛的四个眼角的具体位置,那么从左到右,依次用四个特征点来表示这四个眼角。对神经网络稍做些修改,输出第一个特征点(l1x,l1y),第二个特征点(l2x,l2y),依此类推,这四个脸部特征点的位置就可以通过神经网络输出了。
也许除了这四个特征点,你还想得到更多的特征点输出值,这些(图中眼眶上的红色特征点)都是眼睛的特征点,你还可以根据嘴部的关键点输出值来确定嘴的形状,从而判断人物是在微笑还是皱眉,也可以提取鼻子周围的关键特征点。如下图,为了便于说明,你可以设定特征点的个数,假设脸部有64个特征点,有些点甚至可以帮助你定义脸部轮廓或下颌轮廓。选定特征点个数,并生成包含这些特征点的标签训练集,然后利用神经网络输出脸部关键特征点的位置。
具体做法是,准备一个卷积网络和一些特征集,将人脸图片输入卷积网络,输出1或0,1表示有人脸,0表示没有人脸,然后输出(l1x,l1y)……直到(l64x,l64y)。这里我用 l 代表一个特征,这里有129个输出单元,其中1表示图片中有人脸,因为有64个特征,64×2=128,所以最终输出128+1=129个单元。
最后一个例子,看下图。如果你对人体姿态检测感兴趣,你还可以定义一些关键特征点,假设有32个,如胸部的中点,左肩,左肘,腰等等。然后通过神经网络标注人物姿态的关键特征点,再输出这些标注过的特征点,就相当于输出了人物的姿态动作。当然,要实现这个功能,你需要设定这些关键特征点,从胸部中心(l1x,l1y)一直向下,直到(l32x,l32y)。
目标检测
学过了对象定位和特征点检测,今天我们来构建一个对象检测算法。接下来,我们将学习如何通过卷积网络进行对象检测,采用的是基于滑动窗口的目标检测算法。
假如你想构建一个汽车检测算法,步骤是,首先创建一个标签训练集,也就是x和y表示适当剪切的汽车图片样本,这张图片(编号1)x是一个正样本,因为它是一辆汽车图片,这几张图片(编号2、3)也有汽车,但这两张(编号4、5)没有汽车。出于我们对这个训练集的期望,你一开始可以使用适当剪切的图片,就是整张图片x几乎都被汽车占据,你可以照张照片,然后剪切,剪掉汽车以外的部分,使汽车居于中间位置,并基本占据整张图片。有了这个标签训练集,你就可以开始训练卷积网络了,输入这些适当剪切过的图片(编号6),卷积网络输出y,0或1表示图片中有汽车或没有汽车。训练完这个卷积网络,就可以用它来实现滑动窗口目标检测,具体步骤如下。
假设上图这是一张测试图片,首先选定一个特定大小的窗口,比如图片下方这个窗口,将这个红色小方块输入卷积神经网络,卷积网络开始进行预测,即判断红色方框内有没有汽车。
滑动窗口目标检测算法接下来会继续处理第二个图像,即红色方框稍向右滑动之后的区域,并输入给卷积网络,因此输入给卷积网络的只有红色方框内的区域,再次运行卷积网络,然后处理第三个图像,依次重复操作,直到这个窗口滑过图像的每一个角落。
接着我们重复上述过程,不过这次我们选择一个更大的窗口,截取更大的区域,并输入给卷积神经网络处理,你可以根据卷积网络对输入大小调整这个区域,然后输入给卷积网络,输出0或1。如下图。
再以某个固定步幅滑动窗口,重复以上操作,遍历整个图像,输出结果。
这种算法叫作滑动窗口目标检测,因为我们以某个步幅滑动这些方框窗口遍历整张图片,对这些方形区域进行分类,判断里面有没有汽车。
滑动窗口目标检测算法也有很明显的缺点,就是计算成本,因为你在图片中剪切出太多小方块,卷积网络要一个个地处理。如果你选用的步幅很大,显然会减少输入卷积网络的窗口个数,但是粗糙间隔尺寸可能会影响性能。反之,如果采用小粒度或小步幅,传递给卷积网络的小窗口会特别多,这意味着超高的计算成本。
滑动窗口的卷积实现
为了构建滑动窗口的卷积应用,首先要知道如何把神经网络的全连接层转化为卷积层。
假设对象检测算法输入一个14×14×3的图像,图像很小,不过演示起来方便。在这里过滤器大小为5×5,数量是16,14×14×3的图像在过滤器处理之后映射为10×10×16。然后通过参数为2×2的最大池化操作,图像减小到5×5×16。然后添加一个连接400个单元的全连接层,接着再添加一个全连接层,最后通过softmax单元输出。为了跟下图区分开,我先做一点改动,用4个数字来表示,它们分别对应softmax单元所输出的4个分类出现的概率。这4个分类可以是行人、汽车、摩托车和背景或其它对象。
画一个这样的卷积网络,它的前几层和之前的一样,而对于下一层,也就是这个全连接层,我们可以用5×5的过滤器来实现,数量是400个(编号1所示),输入图像大小为5×5×16,用5×5的过滤器对它进行卷积操作,过滤器实际上是5×5×16,因为在卷积过程中,过滤器会遍历这16个通道,所以这两处的通道数量必须保持一致,输出结果为1×1。假设应用400个这样的5×5×16过滤器,输出维度就是1×1×400,我们不再把它看作一个含有400个节点的集合,而是一个1×1×400的输出层。从数学角度看,它和全连接层是一样的,因为这400个节点中每个节点都有一个5×5×16维度的过滤器,所以每个值都是上一层这些5×5×16激活值经过某个任意线性函数的输出结果。接着我们再添加另外一个卷积层(编号2所示),这里用的是1×1卷积,假设有400个1×1的过滤器,在这400个过滤器的作用下,下一层的维度是1×1×400,它其实就是上个网络中的这一全连接层。最后经由1×1过滤器的处理,得到一个softmax激活值,通过卷积网络,我们最终得到这个1×1×4的输出层,而不是这4个数字(编号3所示)。以上就是用卷积层代替全连接层的过程,结果这几个单元集变成了1×1×400和1×1×4的维度。
掌握了卷积知识,我们再看看如何通过卷积实现滑动窗口对象检测算法。
假设向滑动窗口卷积网络输入14×14×3的图片,为了简化演示和计算过程,这里我们依然用14×14的小图片。和前面一样,神经网络最后的输出层,即softmax单元的输出是1×1×4,我画得比较简单,严格来说,14×14×3应该是一个长方体,第二个10×10×16也是一个长方体。为了方便,立体部分随便画了。
上图是输入给卷积网络的图片大小是14×14×3,测试集图片是16×16×3,如下图。现在给这个输入图片加上黄色条块,在最初的滑动窗口算法中,你会把这片蓝色区域输入卷积网络(红色笔标记)生成0或1分类。接着滑动窗口,步幅为2个像素,向右滑动2个像素,将这个绿框区域输入给卷积网络,运行整个卷积网络,得到另外一个标签0或1。继续将这个橘色区域输入给卷积网络,卷积后得到另一个标签,最后对右下方的紫色区域进行最后一次卷积操作。我们在这个16×16×3的小图像上滑动窗口,卷积网络运行了4次,于是输出了了4个标签。
把这两个图放一起:
结果发现,这4次卷积操作中很多计算都是重复的。所以执行滑动窗口的卷积时使得卷积网络在这4次前向传播过程中共享很多计算,尤其是在这一步操作中(编号1),卷积网络运行同样的参数,使得相同的5×5×16过滤器进行卷积操作,得到12×12×16的输出层。然后执行同样的最大池化(编号2),输出结果6×6×16。照旧应用400个5×5的过滤器(编号3),得到一个2×2×400的输出层,现在输出层为2×2×400,而不是1×1×400。应用1×1过滤器(编号4)得到另一个2×2×400的输出层。再做一次全连接的操作(编号5),最终得到2×2×4的输出层,而不是1×1×4。最终,在输出层这4个子方块中,蓝色的是图像左上部分14×14的输出(红色箭头标识),右上角方块是图像右上部分(绿色箭头标识)的对应输出,左下角方块是输入层左下角(橘色箭头标识),也就是这个14×14区域经过卷积网络处理后的结果,同样,右下角这个方块是卷积网络处理输入层右下角14×14区域(紫色箭头标识)的结果。
具体的计算步骤是以绿色方块为例,假设你剪切出这块区域(编号1),传递给卷积网络,第一层的激活值就是这块区域(编号2),最大池化后的下一层的激活值是这块区域(编号3),这块区域对应着后面几层输出的右上角方块(编号4,5,6)
所以该卷积操作的原理是我们不需要把输入图像分割成四个子集,分别执行前向传播,而是把它们作为一张图片输入给卷积网络进行计算,其中的公共区域可以共享很多计算。
以上就是在卷积层上应用滑动窗口算法的内容,它提高了整个算法的效率。不过这种算法仍然存在一个缺点,就是边界框的位置可能不够准确。
Bounding Box预测
看上面的预测结果,这些边界框没有一个能完美匹配汽车的位置。
其中一个能得到更精准边框的算法是YOLO算法。它是这么做的,比如你的输入图像是100×100的,然后在图像上放一个网格。为了介绍起来简单一些,我用3×3网格,实际实现时会用更精细的网格,可能是19×19,可能更精细。基本思路是:采用图像分类和定位算法,逐一应用到图像的九个格子中。
所以对于这里9个格子中任何一个,你都会得到一个8维输出向量,因为这里是3×3的网格,所以有9个格子,总的输出尺寸是3×3×8,所以目标输出是3×3×8。因为这里有3×3格子,然后对于每个格子,你都有一个8维向量,所以目标输出尺寸是3×3×8。
注意:把对象分配到一个格子的过程是,你观察对象的中点,然后将这个对象分配到其中点所在的格子。所以即使对象可以横跨多个格子,也只会被分配到9个格子其中之一,就是3×3网络的其中一个格子,或者19×19网络的其中一个格子。如果我们在现实实践时,采用19x19的网格会更精细。在19×19网格中,两个对象的中点(图中蓝色点所示)处于同一个格子的概率就会更低。
交并比(IOU)
在对象检测任务中,你希望能够同时定位对象,所以如果实际边界框是这样的,你的算法给出这个紫色的边界框,那么这个结果是好还是坏?所以交并比(lOU)函数做的是计算两个边界框交集和并集之比。两个边界框的并集是这个区域,就是属于包含两个边界框区域(绿色阴影表示区域),而交集就是这个比较小的区域(橙色阴影表示区域),那么交并比就是交集的大小,这个橙色阴影面积,然后除以绿色阴影的并集面积。
一般约定,在计算机检测任务中,如果IOU>=0.5,就说检测正确,如果预测器和实际边界框完美重叠,lOU就是1,因为交集就等于并集。但一般来说只要IOU>=0.5,那么结果是可以接受的,看起来还可以。一般约定,0.5是阈值,用来判断预测的边界框是否正确。一般是这么约定,但如果你希望更严格一点,你可以将IOU定得更高,比如说大于0.6或者更大的数字,但IOU越高,边界框越精确。
非极大值抑制(NMS)
到目前为止你们学到的对象检测中的一个问题是,你的算法可能对同一个对象做出多次检测,所以算法不是对某个对象检测出一次,而是检测出多次。非极大值抑制这个方法可以确保你的算法对每个对象只检测一次。
假设你需要在这张图片里检测行人和汽车,你可能会在上面放个19×19网格,理论上右边这辆车只有一个中点,所以它应该只被分配到一个格子里,左边的车子也只有一个中点,所以理论上应该只有一个格子做出有车的预测。
图中的绿色点和黄色点分别为两辆车的中点。
实践中当你运行对象分类和定位算法时,对于每个格子都运行一次,所以这个格子(编号1)可能会认为这辆车中点应该在格子内部,这几个格子(编号2、3)也会这么认为。对于左边的车子也一样,所以不仅仅是这个格子,如果这是你们以前见过的图像,不仅这个格(编号4)子会认为它里面有车,也许这个格子(编号5)和这个格子(编号6)也会,也许其他格子也会这么认为,觉得它们格子内有车。
我们分步介绍一下非极大抑制是怎么起效的,因为你要在361个格子上都运行一次图像检测和定位算法,那么可能很多格子都会举手说我的pc,我这个格子里有车的概率很高,而不是361个格子中仅有两个格子会报告它们检测出一个对象。所以当你运行算法的时候,最后可能会对同一个对象做出多次检测,所以非极大值抑制做的就是清理这些检测结果。这样一辆车只检测一次,而不是每辆车都触发多次检测。
所以具体上,这个算法做的是,首先看看每次报告每个检测结果相关的概率pc,首先看概率最大的那个,这个例子(右边车辆)中是0.9,然后就说这是最可靠的检测,所以我们就用高亮标记,就说我这里找到了一辆车。这么做之后,非极大值抑制就会逐一审视剩下的矩形,所有和这个最大的边框有很高交并比,高度重叠的其他边界框,那么这些输出就会被抑制。所以这两个矩形分别是0.6和0.7,这两个矩形和淡蓝色矩形重叠程度很高,所以会被抑制,变暗,表示它们被抑制了。
接下来,逐一审视剩下的矩形,找出概率最高pc= 0.8,我们就认为这里检测出一辆车(左边车辆),然后非极大值抑制算法就会去掉其他lOU值很高的矩形。所以现在每个矩形都会被高亮显示或者变暗,如果你直接抛弃变暗的矩形,那就剩下高亮显示的那些,这就是最后得到的两个预测结果。
所以这就是非极大值抑制,非最大值意味着你只输出概率最大的分类结果,但抑制很接近,但不是最大的其他预测结果,所以这方法叫做非极大值抑制。
在这里只介绍了算法检测单个对象的情况,如果你尝试同时检测三个对象,比如说行人、汽车、摩托,那么输出向量就会有三个额外的分量。事实证明,正确的做法是独立进行三次非极大值抑制,对每个输出类别都做一次。
Anchor Boxes
到目前为止,对象检测中存在的一个问题是每个格子只能检测出一个对象,如果想让一个格子检测出多个对象,你可以这么做,就是使用anchor box这个概念。
假设有这样一张图片,对于这个例子,我们继续使用3x3网格。
注意人的中点和汽车的中点几乎在同一个地方,两者都落入同一个格子。对于那个同一个格子,y输出
我们可以检测着三个类别:行人、汽车、摩托车。它将无法输出检测结果,所以我必须从两个检测结果中选一个。
而anchor box的思路是,这样子,预先定义两个不同形状的anchor box,或者anchor box形状,要做的是把预测结果和这两个anchor box关联起来。一般来说,你可能会用更多的anchor box,可能要5个甚至更多,但对于这个视频,我们就用两个anchor box,这样介绍起来简单一些。
我们要做的是定义类别标签。用的向量不再是上面那个y=[pc bx by bh bw c1 c2 c3]^T,而是重复两次,y=[pc bx by bh bw c1 c2 c3 pc bx by bh bw c1 c2 c3]^T。前面的pc bx by bh bw c1 c2 c3 是和anchor box1关联的8个参数,后面的8个参数适合anchor box2相关联。
因为行人的形状更类似于anchor box 1的形状,而不是anchor box 2的形状,所以可以用这8个数值(前8个参数),这么编码pc=1代表有个行人,用bx,by,bh,bw编码包住行人的边界框,然后用c1=1,c2=0,c3=0来说明这个对象是个行人。然后是车子,因为车子的边界框比起anchor box 1更像anchor box 2的形状。这样编码,pc=1,bx,by,bh,bw, c1=0,c2=1,c3=0。
假设车子的边界框形状是这样,更像anchor box 2,如果这里只有一辆车,行人走开了,那么anchor box 2分量还是一样的,要记住这是向量对应anchor box 2的分量和anchor box 1对应的向量分量,我们要填的就是,里面没有任何对象,所以 ,然后剩下的就是?。编码:y=[0 ? ? ? ? ? ? ? 1 bx by bh bw 0 1 0]。
最后,应该怎么选择anchor box呢?人们一般手工指定anchor box形状,我们可以选择5到10个anchor box形状,覆盖到多种不同的形状,可以涵盖我们自己想要检测的对象的各种形状。