10-目标检测

目标检测(Object detection)

目标检测是计算机视觉领域一个重要的方向,首先了解一下目标检测的相关问题。

image-20220930085303251

以上图为例。

图片分类已经学习过了,算法遍历图片,判断图中是否是汽车。

而定位分类就是,不仅判断图片中是不是一辆汽车,还要在图片中标记出它的位置,用方框框起来。

图片分类和定位分类都只判断一个对象。

而目标检测是同时检测图片中的多个对象并标记出位置。

先从目标分类并定位讲起。

目标定位(Object localization)

在图片分类中,输入一张图片到多层卷积神经网络,然后输出一个特征向量,反馈给softmax单元来预测图片类型。

例如对象有这么几类:行人、汽车、摩托车和背景,背景表示不包含前三种对象。

那么softmax函数会输出四个结果。

这就是标准的分类问题。

如果想要定位图片中对象的位置,可以让神经元多输出4个数字,一个边界框,标记为bx,by,bh,bwb_x,b_y,b_h,b_w,这四个数字确定了方框的位置和大小。

先来约定一些符号表示:

  • 图片左上角坐标为(0,0),右下角坐标为(1,1)
  • (bx,by)(b_x,b_y)表示边界框的中心位置
  • bh,bwb_h,b_w分别表示边界框的高度和宽度
image-20220930091451423

如果有上述四种类别,那么目标定位任务的目标标签yy定义为:

y=[pcbxbybhbwc1c2c3]y = \begin{bmatrix} p_c \\ b_x \\ b_y \\ b_h \\ b_w \\ c_1 \\ c_2 \\ c_3 \\ \end{bmatrix}

其中,pcp_c表示是否含有对象,如果对象属于前三类(行人,汽车,摩托车),那么pc=1p_c = 1,如果是背景,那么pc=0p_c=0。可以将pcp_c理解为被检测对象属于每一分类的概率,背景分类除外。

如果检测到对象,就输出被检测对象的边界框参数bx,by,bh,bwb_x,b_y,b_h,b_w

最后如果存在某个对象,那么pc=1p_c = 1,同时输出c1,c2,c3c_1,c_2,c_3,表示该对象属于哪一个分类,如果存在行人,那么pc=1,c1=1,c2=c3=0p_c=1,c_1=1,c_2=c_3=0

对于要处理的目标定位问题,假设图片中只有一个对象,因此c1,c2,c3c_1,c_2,c_3最多有一个为1。

例如:

image-20220930092305456

该图片的标签为:[1bxbybhbw010]T\begin{bmatrix}1 & b_x &b_y &b_h &b_w &0 &1 & 0\end{bmatrix}^T

而对于下面这幅图:

image-20220930092536064

标签为:[0]T\begin{bmatrix}0 & - &- &- &- &- &- & -\end{bmatrix}^T

pc=0p_c = 0,因此标签的其他参数没有意义。图片中不存在检测对象,也就不用考虑边界框大小和属于哪一类。


最后介绍损失函数,参数是实际标签yy和预测标签y^\widehat{y}

如果采用平方误差策略,那么损失函数为:

L(y^,y)={(y^1y1)2+(y^2y2)2++(y^8y8)2y1=1(y^1y1)2y1=0L(\widehat{y}, y ) = \begin{cases} (\widehat{y}_1 - y_1)^2 + (\widehat{y}_2 - y_2)^2 + \cdots + (\widehat{y}_8 - y_8)^2 \qquad y_1 = 1 \\ \\ (\widehat{y}_1 - y_1)^2 \qquad y_1 = 0 \end{cases}

如果图片中存在定位对象,那么pc=y1=1p_c = y_1 = 1,损失值是不同元素的平方差求和;

如果图片中不存在定位对象,那么pc=y1=0p_c = y_1 = 0,不同考虑其他标签值,只关注神经网络输出pcp_c的准确度。

通常的做法是对边界框坐标使用平方差或类似方法,对pcp_c使用逻辑回归函数,或者平方预测误差。

特征点检测(Landmark detection)

如果不仅要定位目标对象,并且要定位一些特殊的特征点,这种问题叫做特征点检测。

例如构建一个人脸识别应用,希望能够定位眼角的具体位置,一共有四个眼角,那么从左到右,可以使用四个特征点来表示,第一个特征点(l1x,l1y)(l_{1x},l_{1y}),第二个特征点(l2x,l2y)(l_{2x},l_{2y}),以此类推。让神经网络多输出8个值。

当然脸部特征可以更多,例如嘴角的特征点,眉毛的特征点等等,那么神经网络就要多输出相应的数值。

在训练时,必须要保证标签的顺序,例如第二个标签值是左眼角的左边特征点,第三个特征值是左眼角的右边特征值等等,必须保证所有样本的特征点顺序都相同。

目标检测

在学习了对象对位和特征点检测后,接下来学习基于滑动窗口的目标检测算法。

假如要构建一个汽车检测算法,那么首先要创建一个训练集,x表示图片,y表示标签:0表示没有汽车,1表示有。

image-20220930130802012

如上图,出于对这个训练集的期望,一开始可以适当剪切图片,使得整张图片都被汽车占据,汽车居于中间位置。

然后使用这个训练集训练卷积网络。

训练完成后,就可以用该网络实现滑动窗口目标检测。

滑动窗口目标检测

image-20220930131320916

如上图,首先选定一个特定大小的窗口,然后从左上角开始,将红色窗口框定的小图片输入到卷积网络中,开始预测,判断红色方框内有没有汽车,

然后红色方框稍微向右滑动,再次将框定的图片输入到卷积网络中进行预测,以此类推,直到这个窗口滑过图片的每一部分,到右下角。

不论汽车在图片的什么位置,总有一个窗口可以检测到。

窗口的大小和滑动步幅可以变化。


滑动窗口目标检测算法有很明显的缺点就是:计算成本较高。

如果在一幅图片中剪切出太多小方块,卷积网络需要一个个进行处理。如果选用较大的步幅,会减少卷积网络需要处理的窗口个数,但是粗糙间隔尺寸可能会影响预测性能;反之如果采用小粒度或者小步幅,传递给卷积网络的小窗口会很多,但计算成本会提高。

不过目前计算成本已经有了很好的解决方案,大大提高了卷积层上应用滑动窗口目标检测的效率。

滑动窗口的卷积实现(重要)

上面已经说明了如何通过卷积网络来实现滑动窗口对象检测算法,但是效率很低。下面来学习如何在卷积层上应用该网络。

首先学习如何把神经网络的全连接层转换成卷积层。

假设目标检测算法输入一个14×14×3的图像,有16个大小为5×5的过滤器,过滤器处理后,输出维度变为10×10×16,然后经过参数为2×2的最大池化操作,输出维度变为5×5×16,然后添加两个有400个隐藏单元的全连接层,最后通过softmax单元输出y,如下图,softmax的四个单元对应四个分类。

image-20220930135445220

那么将全连接层转换为卷积层,如上图,前面部分的卷积层不变,两个全连接层都改为使用400个大小为5×5×16的过滤器,那么输出维度是1×1×400,接着使用softmax进行输出。

从数学角度来看,这样做和原来的全连接层是一样的,因为这400个节点中,每个节点都有一个5×5×16维度的过滤器,所以每个值都是上一层这5×5×16个激活值经过某个线性函数的输出结果。


学会了如何将全连接层转换为卷积层,接下来学习如何通过卷积实现滑动窗口对象检测算法。

假设训练集的输入图片维度为14×14×3,和上图一样,这里为了容易说明,只画出第一个通道的平面图。

image-20220930143443596

训练集图片维度为14×14×3,假设测试集图片维度为16×16×3,使用训练集图片的维度作为滑动窗口的大小,步幅为2。

image-20220930142353795

分别将窗口覆盖的四张图片输入到卷积网络中进行预测,卷积网络运行了四次,输出了4个标签。

但是可以发现,这四次卷积操作中,有很多计算是重复的,因为这四个局部图片有很多像素重叠。

所以可以将16×16×3的整个照片输入到卷积网络中,使用训练时完全相同的过滤器参数和池化参数等,最终得到一个2×2×4的输出。

image-20220930144010298

其中2×2×4的左上角向量值对应着原始输入16×16×3的左上角,窗口大小为14×14的局部图片输入。

同样的,2×2×4输出的其他几个元素对应着原始输入的不同窗口位置。

image-20220930145322231

所以卷积操作的原理是:不需要把输入图片依据滑动窗口大小分割成多个子集,分别执行前向传播,而是把输入图片作为一个整体输入到卷积网络中计算,其中的公共区域共享计算。

例如上面的识别汽车算法,将大小为28×28的整张图片做卷积操作,一次性得到所有的预测值。

image-20220930145708242

以上就是在卷积层上应用滑动窗口算法的内容,它提高了算法效率。但是存在一个缺点是边界框的位置可能不够准确。使用Bounding Box预测可以解决。

Bounding Box(边界框)预测

在之前的滑动窗口卷积实现中,存在的问题是不能输出最精准的边界框。

image-20220930151514855

例如上图,在所有的蓝色滑动窗口框中,都不能精确地框住汽车,红框才是最精确的框,长宽比向水平方向延伸了一些。

能得到更精确边界框的一个算法时YOLO算法,YOLO(You only look once)的意思是“你只看一次”。

实现原理如下:

例如输入图像是100×100的,在图像上放一个网格,为了介绍简单,这里使用3×3的网格,实际实现中会使用更精细的网格。

image-20220930153132423

然后将前面讲到的图像分类和定位算法逐一应用到划分的9个格子中。

在定义训练标签时,对于9个格子的每一个图像都指定一个标签yyyy是8维的。

和之前讲的一样:y=[pcbxbybhbwc1c2c3]Ty=\begin{bmatrix}p_c & b_x & b_y& b_h& b_w& c_1& c_2& c_3\end{bmatrix}^T

那么对于上图划分的九个格子来说,第一行和最后一行的六个格子什么都没有,标签向量为y=[0]Ty=\begin{bmatrix}0 & - & -& -& -& -& -& -\end{bmatrix}^T


对于中间一行包含的两个对象,YOLO算法的做法是:分别取两个对象的中点,然后将这个对象分配给包含该对象中点的格子(注意这里是指人工标注训练集,而不是卷积网络的任务)。

image-20220930154133246

如上图,左边汽车的中点在4号格子中,因此左边汽车会分配到该格子中。

而右边汽车中点在6号格子中,即使这个汽车左边的一小部分在5号格子中,该汽车也会被分配到6号格子中。

4号和6号格子对应的标签都是y=[1bxbybhbw010]Ty=\begin{bmatrix}1 & b_x & b_y& b_h& b_w& 0& 1& 0\end{bmatrix}^T

对于每一个格子,都会有一个大小为8的标签向量,因此3×3的网格总的输出尺寸为3×3×8。

image-20220930155317837

对于左上角的长条格子,维度为1×1×8,对应的是原始图片九个格子中左上角的那个格子的标签向量。

因此相比于之前的卷积层滑动窗口实现,YOLO算法并没有改变网络结构,输入图片依然是作为一个整体输入到卷积网络中。YOLO算法只是对输入图片(训练集和测试集)做了逻辑上的网格划分,同时将训练集的标签进行了精细化处理,每个格子都有一个标签,卷积网络的输出也一一对应。


只要一个格子中的对象数目不超过1个,这个算法就没有问题。

在这里使用的是3×3的网格,但在实践中,会使用更精细的网格,例如19×19,那么输出就是19×19×8,因此多个对象分配到同一个格子的概率就会小很多。


重申一下,把对象分配到一个格子的过程是:观察对象的中点,然后将对象分配到该中点所在的格子中,所以即使对象横跨多个格子,也只会被分配到一个格子中,同样的,在更精细的网格中,两个对象的中点处在同一个格子的概率会更小。


这种方式,与图像分类和定位算法非常像,能够显式地输出边界框坐标,并且具有任意宽高比,不会受到滑动窗口分类器的步幅大小限制,并且YOLO算法是一个卷积实现,并行计算,因此运行速度非常快,可以达到实时识别。

边界框的编码

最后说明在构造训练集标签时,如何编码边界框坐标bx,by,bh,bwb_x,b_y,b_h,b_w

还以上面的两辆车图片为例,6号框子包括了右边的车。

对于任意一个格子,同样约定左上点为(0,0),右上点为(1,1)

image-20220930171012425

图中给出了6号格子的标签向量,可以大致看出,红色的中点位置约为bx=0.4,by=0.3b_x = 0.4,b_y = 0.3,然后用红色边界框的高度除以格子的高度得到bh\boldsymbol{b_h}约为0.5,同样,用蓝色边界框除以格子宽度得到bw\boldsymbol{b_w}约为0.9。

bx,byb_x,b_y必须在0~1之间,否则就会在6号格子外,但是bh,bwb_h,b_w有可能会大于1,表示高度或宽度跨越了多个格子。


指定边界框的方式有很多,但这种约定是较为合理的。

在YOLO算法的论文中,给出了其他参数化的方法,可能效果会更好。

交并比

使用交并比(Intersection over union)来判断目标检测算法的运行效果。

image-20221002093656566

例如上图,实际的汽车边界框是红色,而算法预测的是蓝色的边界框,那么判断这个预测结果好坏的方法是使用交并比函数(loU)。

两个边界框的并集设为SuS_u,交集设为SiS_i,那么交并比的大小就是交集面积大小除以并集面积的大小。

loU=SiSuloU = \frac{S_i}{S_u}

一般人为约定,如果loU0.5loU \ge 0.5,则检测正确。

如果预测框和实际边界框完美重叠,那么loU=1loU=1

注意:0.5是一个人为约定的阈值,如果希望更严格一点,也可以约定0.6或更大数字,loUloU越高,边界框越精确。

非极大值抑制

目前为止,学到的YOLO算法可能会对同一个对象作出多次检测,非极大值抑制可以确保算法对每个对象只检测一次。

例如在下图中检测汽车,网格大小为19×19。

理论上来说,每辆车只有一个中点,应该只被分配到一个格子里。

image-20221002100611956

然后实际在运行目标分类和定位算法时,每一个格子都会运行一次,所以可能有多个格子会认为这辆车中点在自己内部,就如上图,左边车,有多个绿色格子认为中点在该格子内部,右边车也一样。

因此可以使用非极大值抑制方法来解决这种情况。

非极大值抑制,简单来说,就是只留下预测概率最大的那一个格子,其余全部丢弃。

还以上图为例子。

image-20221002101415915

算法给出了多个预测结果,但是预测概率不同。例如左边车辆,有两个预测结果,概率分别为0.8和0.7。那么非极大值抑制就会留下概率最大的那个,即0.8,然后去掉其他的预测结果。

同样右边车辆,会留下0.9的结果,去掉0.7和0.6的预测结果。


接下来说明算法的具体步骤,为了简化说明,假设只进行汽车检测,去掉了c1,c2,c3c_1,c_2,c_3类别,输出标签为y=[pcbxbybhbw]Ty=\begin{bmatrix}p_c & b_x & b_y & b_h & b_w\end{bmatrix}^Tpcp_c就是格子有对象的概率。

那么非极大值抑制具体步骤为:

  • 去掉所有pcp_c预测概率小于阈值的边界框,例如阈值是0.6,那么pc0.6p_c \le 0.6的边界框会被去掉
  • 选择pcp_c预测概率最大的结果作为输出
  • 剩下的是大于阈值,但是小于最大预测概率的边界框,全部抛弃

注意:在这里的说明,只介绍了算法检测单个对象的情况,如果同时检测多个对象,例如行人、汽车、摩托车,那么输出向量会增加三个分量c1,c2,c3c_1,c_2,c_3,需要进行三次非极大值抑制,对每个输出类别都做一次。

Anchor Boxes

到目前为止,目标检测存在的一个问题是每个格子是能检测出一个对象,如果多个对象中点在同一个格子里,就没办法进行预测。

使用anchor box可以解决这个问题。

来看一个具体例子,假设有下面这样一张图片。

image-20221002103634487

使用3×3网格,汽车中点和行人中点几乎在同一个地方,两个对象落到同一个格子里,如果还使用之前的检测方法,将无法输出检测结果,必须从两个检测结果中选择一个。

而使用anchor box的思路是:

首先预先定义两个不同形状的anchor box,然后将预测结果和这两个anchor box关联起来,一般来说,需要使用更多anchor box,但这里为了简单起见,就使用两个anchor box

image-20221002104829060

同时,重新定义输出标签,需要重复两次之前的输出标签向量。

即:y=[pcbxbybhbwc1c2c3anchorbox1pcbxbybhbwc1c2c3anchorbox2]T\boldsymbol{ y=[\overbrace{ \begin{matrix} p_c & b_x & b_y & b_h & b_w & c_1 & c_2 & c_3 \end{matrix} }^{anchorbox1} \quad|\quad\overbrace{ \begin{matrix} & p_c & b_x & b_y & b_h & b_w & c_1 & c_2 & c_3 \end{matrix} }^{anchorbox2} ]^T }

前面8个分量代表anchor box1,后面8个相同的分量代表anchor box2

因此前面的行人和汽车都在一个格子的标签为:

y=[1bxbybhbw1001bxbybhbw010]T\boldsymbol{ y= \begin{bmatrix} 1 & b_x & b_y & b_h & b_w & 1 & 0 & 0 &|& 1 & b_x & b_y & b_h & b_w & 0 & 1 & 0 \end{bmatrix}^T }

前面8个分量代表行人,后面8个分量代表汽车。


因此使用anchor box的来检测多个对象在同一个格子的方法是:每个对象都和之前一样,根据对象中点分配到了同一个格子中,然后根据该对象的边界框和anchor box的交并比来判断应该将该对象分配到哪个anchor box中。

例如上图的行人和汽车,首先使用汽车的预测边界框和两个anchor box分别计算交并比,将交并比更高的那个anchor box分配给行人,这样每个对象就会对应一个anchor box

如果输出类别有三个,有多个anchor box类型,那么y的维度就会更多,例如如果有5个anchor box,使用9×9的网格,那么输出标签维度就是9×9×40。


anchor box有两个处理不好的情况:

  • 有两个anchor box,但是同一个格子内有三个对象
  • 两个对象都分配到一个格子,并且anchor box形状也一样

anchor box的出现是为了解决多个对象出现在同一个格子内的情况,但在实践中这种情况很少发生,如果网格划分很精细,那么会更少出现。

但设立anchor box的好处在于能够让算法更有针对性,例如数据集中有一些很高很瘦的对象,也有一些很宽的对象,那么算法就可以使用anchor box进行更有针对性的处理。


最后,关于anchor box的选择,一般是手工指定anchor box形状,覆盖多种不同的类型。

但有一个更高级的版本,使用k-平均算法,将两类对象的形状聚类,自动选择anchor box

候选区域

在计算机视觉领域,有时候会看到候选区域(Region proposals)的概念,是另一个目标检测的方法。

首先回顾之前使用的滑动窗口检测方法。

image-20221002120743215

滑动窗口会选择不同的区域,作为输入一个一个进行预测,当然也可以使用卷积网络。

这种算法的一个缺点是:在很多没有对象的区域进行计算,浪费了计算成本和时间。

例如上图框选的两个区域,没有任何对象,但是算法仍然会在这两个区域进行计算。

因此研究人员提出了R-CNN算法,全称为convolutional neural network with region,即带区域的卷积神经网络。

核心思想是:首先选出一些可能存在对象的候选区域,然后只在这些候选区域上运行检测算法。

而不是针对每个滑动窗口运行检测算法,只选择一些窗口,在这些少数窗口上运行。

选出候选区域的方式是运行图像分割算法。

image-20221002121642483

然后根据各类色块进行候选区域的选择。

image-20221002122027902

这种方法就是所谓的分割算法,先找出各类色块,然后在每个色块上放置边界框,作为候选区域,运行检测算法,这样需要处理的区域可能比滑动窗口发方法要少得多,可以减少卷积网络的运行时间。

因此R-CNN算法主要分为两步:

  • 使用图像分割算法,得到候选区域色块
  • 分别将候选区域色块作为输入运行检测算法

注意:R-CNN算法不会信任输入的边界框(也就是输入之前在色块上放置的边界框),它也会输出一个边界框坐标,这样得到的边界框比较精确。

这就是R-CNN的概念,但是R-CNN算法运行的很慢,所以有一系列的研究工作改进算法。

包括:

  • Fast R-CNN:和R-CNN基本一样,R-CNN是逐一对区域进行计算,是串行的,Fast R-CNN使用卷积实现R-CNN,运行速度更快
  • Faster R-CNN:使用卷积神经网络,而不是传统的分割算法来得到候选区域色块

不过大部分的R-CNN算法实现还是比YOLO算法慢得多。


10-目标检测
https://zhaoquaner.github.io/2022/10/02/DeepLearning/CourseNote/10-目标检测/
更新于
2022年10月2日
许可协议