# 使用卷积神经网络的物体检测

当我们讨论对图片进行预测时，到目前为止我们都是谈论分类。我们问过这个数字是0到9之间的哪一个，这个图片是鞋子还是衬衫，或者下面这张图片里面是猫还是狗。

![](../img/dog2.jpg)

但现实图片会更加复杂，它们可能不仅仅包含一个主体物体。**物体检测**就是针对这类问题提出，它不仅是要分析图片里有什么，而且需要识别它在什么位置。我们使用在[机器学习简介](../chapter_crashcourse/introduction.md)那章讨论过的图片作为样例，并对它标上主要物体和位置。

![](../img/catdog_label.svg)

可以看出物体检测跟图片分类有几个不同点：

1. 图片分类器通常只需要输出对图片中的主物体的分类。但物体检测必须能够识别多个物体，即使有些物体可能在图片中不是占主要版面。严格上来说，这个任务一般叫**多类物体检测**，但绝大部分研究都是针对多类的设置，所以我们这里为了简单去掉了”多类“
1. 图片分类器只需要输出将图片物体识别成某类的概率，但物体检测不仅需要输出识别概率，还需要识别物体在图片中的位置。这个通常是一个括住这个物体的方框,通常也被称之为**边界框**（bounding box）。

但也看到物体检测跟图片分类有类似之处，都是对一块图片区域判断其包含的主要物体。因此可以想象我们在前面介绍的基于卷积神经网络的图片分类可以被应用到这里。

这一章我们将介绍数个基于卷积神经网络的物体检测算法的思想：

- R-CNN: https://arxiv.org/abs/1311.2524
- Fast R-CNN: https://arxiv.org/abs/1504.08083
- Faster R-CNN: https://arxiv.org/abs/1506.01497
- Mask R-CNN: https://arxiv.org/abs/1703.06870
- SSD: https://arxiv.org/abs/1512.02325
- YOLO: https://arxiv.org/abs/1506.02640
- YOLOv2: https://arxiv.org/abs/1612.08242

## R-CNN：区域卷积神经网络

这是基于卷积神经网络的物体检测的奠基之作。其核心思想是在对每张图片选取多个区域，然后每个区域作为一个样本进入一个卷积神经网络来抽取特征，最后使用分类器来对齐分类，和一个回归器来得到准确的边框。

![R-CNN](../img/rcnn.svg)

具体来说，这个算法有如下几个步骤：

1. 对每张输入图片使用一个基于规则的“选择性搜索”算法来选取多个提议区域
1. 跟[微调迁移学习](./fine-tuning.md)里那样，选取一个预先训练好的卷积神经网络并去掉最后一个输入层。每个区域被调整成这个网络要求的输入大小并计算输出。这个输出将作为这个区域的特征。
1. 使用这些区域特征来训练多个SVM来做物体识别，每个SVM预测一个区域是不是包含某个物体
1. 使用这些区域特征来训练线性回归器将提议区域

直观上R-CNN很好理解，但问题是它可能特别慢。一张图片我们可能选出上千个区域，导致一张图片需要做上千次预测。虽然跟微调不一样，这里训练可以不用更新用来抽特征的卷积神经网络，从而我们可以事先算好每个区域的特征并保存。但对于预测，我们无法避免这个。从而导致R-CNN很难实际中被使用。

## Fast R-CNN：快速的区域卷积神经网络

Fast R-CNN对R-CNN主要做了两点改进来提升性能。


1. 考虑到R-CNN里面的大量区域可能是相互覆盖，每次重新抽取特征过于浪费。因此Fast R-CNN先对输入图片抽取特征，然后再选取区域
1. 代替R-CNN使用多个SVM来做分类，Fast R-CNN使用单个多类逻辑回归，这也是前面教程里默认使用的。

![Fast R-CNN](../img/fast-rcnn.svg)

从示意图可以看到，使用选择性搜索选取的区域是作用在卷积神经网络提取的特征上。这样我们只需要对原始的输入图片做一次特征提取即可，如此节省了大量重复计算。

Fast R-CNN提出兴趣区域池化层（Region of Interest (RoI) pooling），它的输入为特征和一系列的区域，对每个区域它将其均匀划分成$n \times m$的小区域，并对每个小区域做最大池化，从而得到一个$n\times m$的输出。因此不管输入区域的大小，RoI池化层都将其池化成固定大小输出。

下面我们仔细看一下RoI池化层是如何工作的，假设对于一张图片我们提出了一个$4\times 4$的特征，并且通道数为1.

```{.python .input  n=7}
from mxnet import nd

x = nd.arange(16).reshape((1,1,4,4))
x
```

然后我们创建两个区域，每个区域由一个长为5的向量表示。第一个元素是其对应的物体的标号，之后分别是`x_min`，`y_min`，`x_max`，和`y_max`。这里我们生成了$3\times 3$和$4\times 3$大小的两个区域。

RoI池化层的输出大小是`num_regions x num_channels x n x m`。它可以当做一个样本个数是`num_regions`的普通批量进入到其他层进行训练。

```{.python .input  n=10}
rois = nd.array([[0,0,0,2,2], [0,0,1,3,3]])
nd.ROIPooling(x, rois, pooled_size=(2,2), spatial_scale=1)
```

## Faster R-CNN：更快速的区域卷积神经网络

Fast R-CNN沿用了R-CNN的选择性搜索方法来选择区域。这个通常很慢。Faster R-CNN做的主要改进是提出了**区域提议网络**（region proposal network, RPN）来替代选择性搜索。它是这么工作的：

1. 在输入特征上放置一个$填充为1通道是256的3\times 3$卷积。这样每个像素，连同它的周围8个像素，都没映射成一个长为256的向量。
1. 以对每个像素为中心生成数个大小和长宽比预先设计好的$k$个默认边框，通常也叫**锚框**。
1. 对每个边框，使用其中心像素对应的256维向量作为特征，RPN训练一个2类分类器来判断这个区域是不是含有任何感兴趣的物体还是只是背景，和一个4维输出的回归器来预测一个更准确的边框。
1. 对于所有的锚框，个数为$nmk$如果输入大小是$n\times m$，选出被判断成还有物体的，然后前他们对应的回归器预测的边框作为输入放进接下来的RoI池化层

![Faster R-CNN](../img/faster-rcnn.svg)

虽然看上有些复杂，但RPN思想非常直观。首先提议预先配置好的一些区域，然后通过神经网络来判断这些区域是不是感兴趣的，如果是，那么再预测一个更加准确的边框。这样我们能有效降低搜索任何形状的边框的代价。

## Mask R-CNN

Mask R-CNN在Faster R-CNN上加入了一个新的像素级别的预测层，它不仅对一个锚框预测它对应的类和真实的边框，而且它会判断这个锚框类每个像素对应的哪个物体还是只是背景。后者是语义分割要解决的问题。Mask R-CNN使用了之后我们将介绍的[全连接卷积网络(FCN)](./fcn.md)来完成这个预测。当然这也意味这训练数据必须有像素级别的标注，而不是简单的边框。

![Mask R-CNN](../img/mask-rcnn.svg)

因为FCN会精确预测每个像素的类别，就是输入图片中的每个像素都会在标注中对应一个类别。对于输入图片中的一个锚框，我们可以精确的匹配到像素标注中对应的区域。但是PoI池化是作用在卷积之后的特征上，其默认是将锚框做了定点化。例如假设选择的锚框是$(x,y,w,h)$，且特征抽取将图片变小了16倍，就是如果原始图片是$256\times 256$，那么特征大小就是$16\times 16$。这时候在特征上对应的锚框就是变成了$(\lfloor x/16 \rfloor, \lfloor y/16 \rfloor, \lfloor w/16 \rfloor, \lfloor h/16 \rfloor)$。如果$x,y,w,h$中有任何一个不被16整除，那么就可能发生错位。同样道理，在上面的样例中我们看到，如果锚框的长宽不被池化大小整除，那么同样会定点化，从而带来错位。

通常这样的错位只是在几个像素之间，对于分类和边框预测影响不大。但对于像素级别的预测，这样的错位可能会带来大问题。Mask R-CNN提出一个RoI Align层，它类似于RoI池化层，但是去除掉了定点化步骤，就是移除了所有$\lfloor \cdot \rfloor$。如果计算得到的表框不是刚好在像素之间，那么我们就用四周的像素来线性插值得到这个点上的值。

对于一维情况，假设我们要计算$x$点的值$f(x)$，那么我们可以用$x$左右的整点的值来插值：

$$f(x) = (\lfloor x \rfloor + 1-x)f(\lfloor x \rfloor) + (x-\lfloor x \rfloor)f(\lfloor x \rfloor + 1)$$

我们实际要使用的是二维差值来估计$f(x,y)$，我们首先在$x$轴上差值得到$f(x,\lfloor y \rfloor)$和$f(x,\lfloor y \rfloor+1)$，然后根据这两个值来差值得到$f(x, y)$.


## SSD: 单发多框检测器

在R-CNN系列模型里。区域提议和分类是分作两块来进行的。SSD则将其统一成一个步骤来使得模型更加简单并且速度更快，这也是为什么它被称之为**单发**的原因。

它跟Faster R-CNN主要有两点不一样

1. 对于锚框，我们不再首先判断它是不是含有感兴趣物体，再将正类锚框放入真正物体分类。SSD里我们直接使用一个`num_class+1`类分类器来判断它对应的是哪类物体，还是只是背景。我们也不再有额外的回归器对边框再进一步预测，而是直接使用单个回归器来预测真实边框。
1. SSD不只是对卷积神经网络输出的特征做预测，它会进一步将特征通过卷积和池化层变小来做预测。这样达到多尺度预测的效果。

![SSD](../img/ssd.svg)

SSD的具体实现将在[下一章](./ssd.md)详细阐述。

## YOLO：只需要看一遍

不管是Faster R-CNN还是SSD，它们生成的锚框仍然有大量是相互重叠的，从而导致仍然有大量的区域被重复计算了。YOLO试图来解决这个问题。它将图片特征均匀的切成 $S\times S$ 块，每一块当做一个锚框。每个锚框预测$B$个边框，以及这个锚框主要包含哪个物体。

![Yolo](../img/yolo.svg)


## YOLO v2：更好，更块，更强

YOLO v2对YOLO进行一些地方的改进，其主要包括：

1. 使用更好的卷积神经网络来做特征提取，使用更大输入图片$448\times 448$使得特征输出大小增大到$13\times 13$
1. 不再使用均匀切来的锚框，而是对训练数据里的真实锚框做聚类，然后使用聚类中心作为锚框。相对于SSD和Faster R-CNN来说可以大幅降低锚框的个数。
1. 不再使用YOLO的全连接层来预测，而是同SSD一样使用卷积。例如假设使用5个锚框（聚类为5类），那么物体分类使用通道数是`5*(1+num_classes)`的$1\times 1$卷积，边框回归使用通道数`4*5`. 

## 总结

我们描述了基于卷积神经网络的几个物体检测算法。他们之间的共同点在于首先提出锚框，使用卷积神经网络抽取特征后来预测其包含的主要物体和更准确的边框。但他们在锚框的选择和预测上各有不同，导致他们在计算实际和精度上也各有权衡。


**吐槽和讨论欢迎点**[这里](https://discuss.gluon.ai/t/topic/2510)
