c4w3_目标检测

发布时间 2023-11-18 13:24:38作者: newbe3three

目标检测 Object detection

目标检测的第一步是目标定位(Object localization)。对于图片分类器我们已经十分了解,而目标定位不仅需要判断图片中是否有某一类对象,还需要在图片中标记出它的位置。

如果图片中有多个对象,就需要识别出图片中所有的对象,并标记出来位置,也就是目标检测。

image

(图片分类的思路有助于学习目标分类定位,目标定位的思路有助于学习目标检测。)

所以要实现目标检测,就需要先实现目标的分类和定位。目标分类和定位的基础也就是此前学到的图片分类器。

图像分类和定位的算法

目标定位

根据要定位的目标数量,分为单目标定位和多目标定位。(这里只研究单目标定位)

用边框(bounding box)把目标在图中标注出来。边框就是一个定位物体的矩形框。

首先将图片置于0-1的坐标系中,用四个变量描述边框在图中的位置,如下图所示。

image

其中\(b_x、b_y\)描述目标中心点在坐标系中的位置,\(b_h、b_w\)描述目标边框的长度和宽度,边框的长和宽是在图片尺度内的,就是相对于整个图片长和宽的比例。

一个用于分类的神经网络模型,最终的输出结果可以是一个softmax,用来表示分类的结果。例如在交通道路图片中的分类,要识别出行人、汽车、摩托车以及背景(没有上述上个类别),那么这样的分类器最终的输出是softmax(4)

现在,要构建目标定位的神经网络模型,在上述分类器的输出结果中,还需要给出边框四个变量的值,也就是还需要输出目标在图中的位置信息(这个位置使用矩形边框框出来的)。

image

根据这样的输出结果,我们对于标签y定义也必然不同于分类器。

标签y需要有的输出首先要有有\(p_c\)描述图片中是否有要检测的目标。在上述例子中是汽车、行人摩托车。如果有这个值为1,没有这个值为0(只是一个背景图)。其次还要有描述目标位置信息的四个项\(b_x、b_y、b_w、b_h\)。最后还需要指明目标的类型的项\(c_1、c_2、c_3\)。所以y可以表示为如下(列向量)。

\[y^T=(p_c,b_x,b_y,b_w,b_h,c_1,c_2,c_3) \]

如果图中是一个车,那么y标签可以是\(y^T=(1,b_x,b_y,b_w,b_h,1,0,0)\) (并没有给具体的位置信息)。需要特别说明的是,如果只有背景图y标签应该是这样的\(y^T=(0,?,?,?,?,?,?,?)\),因为如果没有目标,那么我们就不关注其他的数值应该是怎样的所以都用了?标记。

损失函数可以用平方误差和的形式,根据图中是否有目标即\(p_c\)是否为1分别计算

如果\(p_c=1\),则\(L(\hat y,y)=(\hat y_1-y_1)^2+(\hat y_2-y_2)^2+\cdots+(\hat y_8-y_8)^2\)

如果\(p_c=0\),我们是不关心除了\(p_c\)以外其他项的,所以有\(L(\hat y,y)=(\hat y_1-y_1)^2\)

image

上面举例时,每个分量都用平方误差计算,但实际可以每个分量使用不同的损失函数,比如(p_c)使用逻辑回归损失函数,分类标签使用softmax损失函数。边框四个分量使用平方误差。

特征点检测

目标定位给出了检测出图中目标位置的边框,更一般的情况,神经网络可以通过输出图片上特征点(Landmark)的位\((x,y)\)坐标,来实现对目标特征的识别,也就是特征点检测(Landmark Detection)。神经网络可以通过学习特征点的坐标,识别特征的位置。

比如面部识别,可以输出的特征点有眼睛、鼻子、嘴巴等轮廓上的关键点,根据需要识别的精度,调整需要输出的关键坐标点个数。又比如肢体动作识别。

image

\((x_n,y_n)\)表示每个特征点的坐标,需要注意的是特征点在所有样本中的定义要一致,比如第一个特征点坐标的位置所表达的必须是左眼角的位置。

基于滑动窗口的目标检测

  • 首先将对原始图片进行裁剪,用紧贴着目标剪裁好的图片训练一个卷积网络,可以识别这些紧凑剪裁的图片中是否有目标,如下图中目标为汽车。

    image

  • 其次取一个大小固定的矩形窗口,用这个窗口以一定步长从左到右,从上到下依次扫过原始图片。

  • 把每次窗口扫到的区域,交给通过紧凑剪裁训练好的卷积神经网络,识别区域中是否有汽车。

  • 不断改变窗口的位置,重复上述操作,最后能在一定大小的窗口从原始图片的某位置检测出目标对象。这个位置就是边框了。

    image

滑动窗口目标检测的缺点:

  • 计算成本高,如果选用的窗口小步幅小,那么窗口截取的一个个区域都要给神经网络识别。
  • 如果窗口比较大、步幅比较大又会影响目标检测的精度。

滑动窗口的卷积实现

转换成卷积层

为了构建滑动窗口的卷积应用,要先把神经网络的全连接层转换成卷积层。

在下图的上半部分,卷积计算结束后,将得到的volume,展开后交给全连接层,最后输出是softmax层,输出的内容是图片的类别。

在下半部分,通过使用400个和volume维度相同的filter把全连接层转换成了卷积层,以及最后使用1x1的filter将softmax层也转变成了卷积层,同时最终的输出结果变成了1x1x4的volume。

image

可以总结出,当filter的维度和输入的volume的维度相同时,卷积层的作用就和全连接层一致,卷积层中filter的个数也就对应于全连接层神经单元的个数。这样的转变本质上作用并没有改变,主要是改变了对输出结果观察的角度。

image

所以为什么要将全连接层转换成卷积层呢?

输出结果不再是固定的,而是会根据输入图片的维度变化。卷积网络可以更大的输入图片上滑动,将分类结果输出到最终的volume。与之前的全连接层最终只输出图片的分类结果不同,改成卷积层后,输出的结果是更大图片上卷积网络划过区域的分类结果。

卷积实现滑动窗口目标检测算法

假设滑动窗口的卷积网络结构如下(这个卷积网络是上文中提到的通过对目标紧凑剪裁的图片训练得到的)。最后的全连接层换成了卷积层,图中的volume只画了平面没有画通道这个维度。

image

输入的图片大小为16x16x3时,用上面的卷积网络以步长为2滑过图片,就可以切分成四个区域, 也就对应于图片经过卷积网络后的输出结果中volume的四个区域。

输入的图片大小为28x28x3时,用上面的卷积网路以步长为2滑过图片,就切分成了八个区域,这也和经过卷积网络最后的输出对得上。

image

滑动窗口在滑过图片时,不同位置截取到的数据有很多重合的部分。如果把这些窗口截取的部分,每次都交给训练好的网络去计算,那么就有很多重复计算,造成了重复检测的计算成本。通过卷积网络,可以共享对这些重复部分的计算,并把这些重复部分的数据特征给压缩到不同的通道上去,这样只需让原始图片经过一次卷积网络就完成了不同窗口截取图片的检测,一次性得到了所有区域的预测值。提高了算法的效率。

也就是说,通过把全连接层转换成卷积层这样的网络结构,实现了对图片中不同区域的目标检测。这就是卷积对滑动窗口检测算法的具体实现。

但是显然,这种方法存在一个问题,就是边框的位置可能不够准确,可能没有一个边框能完美匹配到目标的位置。

image

更好的边框预测--Yolo算法

在滑动窗口算法中,由于滑动窗口的大小是固定选取的,所以最终检测出目标位置的边框可能不够准确,不能总是完美匹配到目标的位置。

Yolo(You only look once)算法是一个比较好的方法获得更精准的边框

Yolo算法的基本思路

  • 用一个网格(grid)将图片拆分成若干个区域。如下图中的示例用3x3拆分图片成了9个格子,实际上可能用19x19的网格进行拆分或更多。

    image

  • 把图像分类和定位的算法应用在划分好的9个格子上。所以需要指定每个格子标签y的呢内容。所以对应与每个格子都应该有这样的一个有8个维度的y标签。 \(y^T=(p_c,b_x,b_y,b_w,b_h,c_1,c_2,c_3)\)

    需要注意的是目标对象是否存在于某一个格子的标准是其中心点,中心点在哪个格子,他就属于哪个格子。

    对上图的例子,经过yolo算法之后,输出的结果应该是3x3x8的一个volume

    image

    在测试的时候,当把图片喂给算法,经过正向传播,就会输出对应每个格子的向量,从这些结果中就能知道某个位置有目标对象,以及对象的是那类,还有精确的边框。(假设图中只有一个对象)

    当网格划分的很精细的时候,就会降低了多个对象出现在同一个格子的概率

  • yolo算法的优点

    • 可以显式地输出精确的边界框,不受滑动窗口大小和步长的限制。
    • yolo算法是一个卷积实现,并不在3x3网格上跑9次算法,因为在卷积网络中很多计算步骤是共享的。所以运行速度很快,可以达到实时识别。

边框坐标的编码

  • 目标对象是否存在于某一个格子的标准是其中心点,中心点在那个格子,他就属于那个格子。
  • 边框的的坐标是放在每个格子的尺度内,每个格子的左上角是\((0,0)\),右下角是\((1,1)\),所以目标对象中心点的坐标是小于1的
  • 边框的长和宽是放在整个图片的尺度内,一个格子的长和宽定义尺度为1,边框的长和宽用格子的长宽比例表示。所以边框的长宽可以大于1,因为一个边框可能跨越了多个格子。

当然也有其他更高级的参数化的方式,可能效果会更好,这里只给出了一个比较合理的约定,should work okey.

交并比 IoU

如何检验目标检测算法给出的边框是否合理呢?比如下面的图片中,红色是对汽车实际标注好的汽车边框,紫色是算法给出的边框位置,如何判定算法给出结果的好坏呢?

image

一个很自然的想法是看两个边框的重合度,具体来说是用交集和并集的比(交并比IoU)来评判。在上图的例子中,IoU就是黄色部分比绿色部分。

image

一般来说,认为IoU的值大于等于0.5认为算法给出的边框位置是一个比较合理的位置。IoU越接近1越准确。

非极大值抑制

目标检测算法可能遇到的一个问题是,对同一个对象做出了多个检测。非极大值抑制(Non-max Suppression)这个方法可以确保算法对每个对象只检测一次。

比如使用yolo算法,用19x19的网格把图片划分成了361个格子。图中有两辆车,绿色点和黄色点分别是两车真实的中心,但是算法可能认为旁边的几个格子也存在目标对象,也就是对一个目标检测了多次。

image

最终给除了多个边框,下图中数字是\(p_c\)也就是算法认为存在目标的概率

image

非极大值抑制的基本想法就是,对于交并比达到一定程度的几个边框(交并比高就是说明两个边框 框的是同一个对象),只保留\(p_c\)最大的。

具体的实现方法是:

  • 首先丢弃所有\(p_c\)值小于0.6(该阈值可调整)的格子的预测。(\(p_c\)太小的,就是预测不准,直接丢了)

  • 用while循环遍历剩下的格子,直到没有其他剩余格子

    • 选取\(p_c\)最大的格子作为预测的输出
    • 其他剩下的格子的边框与上一步跳出来的边框交并比大于0.5的就需要丢弃(交并比比较大说明这些边框和上一步最大\(p_c\)的边框框住的是同一个物体,当然0.5也是可以调整的阈值)

上述的步骤只是针对单个目标的检测,如果有多个目标,则对输出的分量按照每个类型独立进行遍历。

Anchor Boxes

此前,我们只讨论了图像中只有一类目标对象的情况,如果在一个格子里出现了两个物体的中心点,就需要使用Ancho Boxes来检测多个目标。

image

Anchor Boxes的基本思想是,预定义几个形状不同的边框作Anchor Box。将想要预测的多个对象分别关联到这些形状不同的Anchor Boxes上。

在上图的例子中,就相当于定义两个Anchor Box,其中Anchor Box2扁长形的关联到汽车,竖长形Anchor Box1的关联到人。

image

这种情况下,需要标签y的表达,因为有两个边框去定位两个不同的目标,所以相比原先只定位一个对象,现在y就要扩充到原先的两倍。

\[y^T=(p_{c1},b_{x1},b_{y1},b_{w1},b_{h1},c_1,c_2,c_3,p_{c2},b_{x2},b_{y2},b_{w2},b_{h2},c_{1},c_2,c_3) \]

Anchor Box 算法的结果中,对每个格子分配先会给每个对象的中心点分配一个格子(grid cell),还会给每个对象分配一个anchor box,这个anchor box与该对象的形状IoU最高的。相当于增加了一个匹配维度(grid cell, anchor box)--> 3x3x2x8

网络认为这个格子里有存在对象的中心点的可能性,然后网络会根据学到的内容,就会给对象画一个边框,(如果这个格子有两个对象中心点,那就画两个边框),然后看哪个anchor box 和刚才网络给他画的边框交并比更高就给他分配哪个anchor box里边(也就是对应向量上边和下边部分)。所以对于一个可能的对象中心点,会给分配到两个东西,一个是表示中心点所在的格子,另一个是IoU比较高的anchor box。为什么这里说的是可能的中心点?因为网络的预测不总是准的。 对P_c 为0的格子,也会有两个边框,因为会填充一些没有意义的值。

AnchorBox无法处理的情况

  • 如果设定了两个anchor box,而一个格子里有三个类型的对象
  • 如果有两种类型的检测对象,形状上大体一致,anchor box就不行了

虽然我们用Anchor Box解决两个物体在同一个中心点的问题。但实践中网格切分的比较多,比如19x19个网格,出现两个物体在相同的中心点的概率并不大。除此之外,Anchor Box的作用之一是让你算法更专注训练某种特定物体,比如让某些输出单元专注于高瘦的行人,而另一些输出单元专注于训练宽长的汽车。

如何选择Anchor Box?一般人们都手动选择,还有一些高级的办法,K-means算法聚类,根据聚类的结果选择anchor box。。

Yolo算法

根据前面学到的各种组件,现在可以把他们组合在一起形成Yolo算法,

如何构造训练集

以要从图片中识别汽车、人、摩托车为例子说明。如果使用两个anchor boxes,\(y^T=(p_{c1},b_{x1},b_{y1},b_{w1},b_{h1},c_1,c_2,c_3,p_{c2},b_{x2},b_{y2},b_{w2},b_{h2},c_{1},c_2,c_3)\)

y的维度就是3x3x2x8,也可以描述为3x3x16。

image

所以训练集,就要遍历9个格子然后标注出对应的目标向量y。比如图中的汽车,他的边框和anchorbox2的IoU更高,所以就和向量的下半部分相关。

预测yhat

通过设计的好的卷积网络,就可以预测出每一个格子上y向量的情况,最后需要进行非最大值抑制,留下对每个对象置信度最高的预测。

image

首先对于使用了两个anchor box 的算法,对于每一个格子都会有两个预测的边框,只不过有些格子的\(p_c=0\),因为网络学习的过程中不会因为\(p_c=0\)就不给这个格子画边框了,只是\(p_c=0\)我们不在意这些数据。

image

其次,除去预测结果y中两个\(p_c\)为0或都比较小的格子中边框数据(这就相当于把这些数给改成0)。

image

最后,对每种类型的对象使用非极大值抑制的方法,去除那些对同一个对象多次检测的情况。

在本例中,为什么人像中横框和汽车的竖框直到最后才删掉?我认为这两个框其实都是我们不关心的无效预测,其对应的pc应该是0。

上一步去除的是y向量里两个\(p_c\)都是0的情况。对于有一个\(p_c=0\)的情况要留在第三步才对不同类型目标的无效预测的边框进行剔除。

候选区域 Region Proposals

前面介绍的滑动窗口算法,都有一个缺点:在一些空白的,明显没有物体的区域做检测。所以有一种叫作R-CNN(带区域的卷积网络)的算法,尝试选出一些对运行分类器有意义的区域,这样以来不再针对每个滑动窗口检测算法。

R-CNN(Regions with CNNs)的思想是,通过Segmentation algorithm算法(即产生最右面的色块图)找出候选区域,只对有可能出现目标的色块进行检测,输出label和bounding box。相比sliding window的逐行逐列,减少了检测量。

image

R-CNN运算很慢,衍生出了改良算法:

  • Fast R-CNN:使用Convolutional Implementation of Sliding Windows去识别候选区域。
  • Faster R-CNN:使用CNN做候选区域的识别。