#Object Detection

首先,我们先来回忆一下YOLO网络在做什么。

现在,我们有一张大小为512*512的狗的图片,现在我想预测出狗的bounding box。我们先把图片按照步长32划分成16*16的网格,通过YOLO网络,这个网格中的每个格子都会预测出3个bounding box,这3个bounding box的中心都落在这个格子里。如果网络效果不错的话,红色格子对应的3个预测里就有一个是狗的bounding box(也就是黄色框)。对于每一个bounding box,我们都会预测出(4+1+n)个参数,其中前面4个参数和bounding box的中心坐标以及长宽有关,第五个参数表示这个bounding box里有物体的概率和这个bounding box和真实bounding box的IOU的成绩,最后n个参数表示如果这个bounding box里有物体,那么这个物体属于某一类的概率有多大,也就是分类任务的输出。

因此,对于这张图片,我们会预测出16*16*3个bounding box,首先我们先根据第五个参数,筛出可能包含物体的bounding box。然后使用非最大抑制(non-maximum suppression)找到这些bounding box里比较准确的那些bounding box。

使用的YOLOv3-tiny网络结果如图所示,其中蓝色的部分用于特征提取,然后经过几个卷积层的调整接到YOLO层来预测出小尺度的bounding box。此外,我们特征提取部分的特征上采样然后和更浅的层的特征连接起来用于预测尺度大一些的bounding box。由于训练过程中观察到网络不太好收敛,所以可以先不管右侧这个分支,只训练左侧这一路,经过3000个epoch之后,再训练整个网络,这样可以使网络收敛得更快一些。

YOLO主页上提供了很详细的教程,讲解如何使用作者提供的YOLO实现,其中包括如何使用pretrain的model,如何在VOC和COCO数据集上训练。在这篇文章中,我们来看一下如何能将作者提供的YOLO实现应用到自己的数据集上。

安装YOLO

首先,我们需要跟着主页上的教程安装YOLO。

1
2
3
git clone https://github.com/pjreddie/darknet
cd darknet
make

如果想使用GPU的话,要在make之前把Makefile中前两行置1。

1
2
GPU=1
CUDNN=1

调整我们的数据集

此链接中有我制作的数据集。我们来看一下数据集里都需要什么内容:

  • 训练集和验证集的图片images/
  • 训练集和验证集的label labels/:目录下的每个label文件名和图片对应,把图片文件名的格式.png/jpg/...改成.txt。label文件中,从第一行开始每行表示一个bounding box。每个bounding box包含5个参数,依次是类别(从0开始),x,y,w,h。其中(x,y)是bounding box的中心点位置,w表示bounding box的宽度,h表示bounding box的高度。注意,后四个参数都要除以图片的尺寸以获得[0,1]的数。

    1
    0 0.4140625 0.51953125 0.125 0.17578125
  • 一个文本文件,包含了训练集中所有图片的路径train.txt

    1
    2
    3
    4
    5
    /home/robot/Desktop/PVZ/data/images/1.png
    /home/robot/Desktop/PVZ/data/images/2.png
    /home/robot/Desktop/PVZ/data/images/3.png
    /home/robot/Desktop/PVZ/data/images/4.png
    ......
  • 一个文本文件,包含了验证集中所有图片的路径valid.txt:和train.txt类似。

  • 一个文本文件,包含了所有类别的名字zombie.name:从第一行开始每行写一个名字,第一行为第0类的名字,第二行为第1类的名字,以此类推。比如在我们的数据集中只有一类,这类名字叫Marty,因此只有一行,上面写着Marty。
    1
    Marty

到现在为止,我们的数据集就已经创建好了,接下来我们要做的就是把数据喂进YOLO中进行训练。

使用YOLO训练

接下来,我们来看一下如何训练。由于我在项目中使用的是YOLOv3-tiny这个网络,所以下面都以这个网络为例。

darknet/cfg/中有很多.cfg文件,这个文件描述了网络结构和一些训练参数,因为darknet实现了一套自己的底层框架,所以如果你先构建新的网络,也需要按照这个格式来。

打开darknet/cfg/yolov3-tiny.cfg文件,为了能使用我们自己的数据集,还要对其做一些调整。为了方便起见,我们创建两个.cfg文件,yolov3-tiny.cfg用于训练和yolov3-tina-testing.cfg用于测试。

  • yolov3-tiny.cfg文件中我们将[net]中的batch size设为64,subdivision设为2。在yolov3-tiny.cfg-testing文件中我们将[net]中的batch size设为1,subdivision设为1。
    yolov3-tiny.cfg

    1
    2
    3
    # Testing
    batch=1
    subdivisions=1

    yolov3-tiny-testing.cfg

    1
    2
    3
    # Testing
    batch=64
    subdivisions=2
  • 根据自己的数据集调整[net]中的widthheight,比如在我的数据集中图片是512*512的,所以有

    1
    2
    width=512
    height=512
  • 接下来,我们要把所有yolo层里的classes改成自己的数据集中的类别数。比如我的数据集中只有一类,因此classes=1

  • 最后也是最重要的一步,我们需要对每个yolo层前的卷积层的输出channel数做调整。根据上一篇文章,我们知道输出时每个格子对应的channel为\3*(4+1+num_classes\),所以要把filters改为这个数。比如在我的数据集中,只有一类,那输出channel数为18,因此改成filters=18

在修改完网络后,我们还需要建立一个新文件zombie.data,里面包含了训练时所用的一些目录的信息:

1
2
3
4
5
classes = 1
train = /home/robot/Desktop/PVZ/data/train.txt
valid = /home/robot/Desktop/PVZ/data/valid.txt
names = /home/robot/Desktop/PVZ/data/zombie.name
backup = backup

其中classes表示数据集中的类别数目,train指向上一节提到的train.txt所在位置,valid指向上一节提到的valid.txt所在位置,names指向类别名字的文件zombie.name所在位置,backup表示中间存储的目录,之后训练过程中存储下来的weights文件都可以在这个目录下找到。

现在我们就可以开始训练了,参照主页中的格式运行darknet:

1
./darknet detector train zombie.data yolov3-tiny.cfg darknet.conv.weights

最后一个命令行参数是pretrain的权重文件,关于YOLOv3-tiny的pretrain model可以在此链接处下载。

训练好后,我们在backup的目录下找到weights文件,然后进行测试:

1
./darknet detector test zombie.data yolov3-tiny-testing.cfg backup/[weights file]

然后就可以看到目录下出现了一个predictions.jpg文件,如图:


predictions.jpg

原文链接:YOLOv3: An Incremental Improvement

YOLOv3是YOLO系列的最新版本,总体来讲变化不算特别大。(I didn’t do a whole lot of research this year. Spent a lot of time on Twitter. Played around with GANs a little. ——作者如是说)但是为了完成这个项目,我还是读了一遍paper。事实上,参考文献中列出了一个非常详细YOLOv3的教程,教程中包括了一些关于YOLOv3的细节说明,并手把手教读者如何从cfg文件开始构建网络,加载模型参数并完成预测。这个教程使用PyTorch实现的,非常有参考意义。美中不足的是,这个教程只包含了detection部分,而没有告诉你如何使用PyTorch从头开始训练网络。

在这篇博客中,我依旧按照paper中的逻辑讲解YOLOv3,更多的细节可以在后面的文章中看到。

YOLOv3

预测Bounding Box

在YOLOv3中,我们依旧使用类似于YOLOv2的方法预测bounding box,对于每个bounding box,给出四个参数\(t_x,t_y,t_w,t_h\),然后通过变换

$$\begin{align}
b_x&=\sigma(t_x)+c_x \\
b_y&=\sigma(t_y)+c_y \\
b_w&=p_we^{t_w} \\
b_h&=p_he^{t_h} \\
\end{align}$$

就可以得到bounding box的真实值。关于参数的解释,详见上一篇文章,这里不再解释。

在训练中,我们使用误差的平方和作为loss。如果真实值是\(\hat{t_{*}}\)而预测值是\(t_{*}\),那么误差就是\((\hat{t_{*}} -t_{*}\)。需要额外说明的是和bounding box有关的第五个参数\(t_o\),在YOLOv3中这个参数依旧表示置信值\(Pr(\text{Object})*\text{IOU}(b, \text{Object}))\)。\(t_o\)的真实值\(\hat{t}_o\)是这样定义的:如果这个\(t_o\)对应的bounding box prior和某个物体的真实的bounding box重叠的比例高于其他bounding box prior,那么\(\hat{t}_o=1\),我们称之为这个bounding box prior分配给某个物体的真实值。(注:按照我的理解,bounding box prior和真实的bounding box的重叠比例是这么得到的。和YOLOv2相同,YOLOv3中的每个prediction都对应于一个anchor,这个anchor将用于计算bounding box的宽度和高度。bounding box prior以anchor作为宽度和高度,以真实bounding box的中心作为中心,然后就可以求bounding box prior和真实bounding box的重叠比例了。)

在计算loss的时候,如果一个预测和某个真实物体的bounding box重叠超过某个threshold(0.5),那么我们忽略掉这个预测的loss。如果一个预测和某个真实物体的bounding box prior没有被分配给某个物体的真实值,那么我们只将置信值\(t_o\)的误差加入loss,而忽略掉分类误差和定位误差。

预测分类

考虑到使用softmax作为分类器时,我们假设了每个bounding box只包含一个类别的物体,而实际上每个bounding box可能包含多个类别的物体,因此我们对每个类别使用独立的逻辑分类器。我们使用binary cross-entropy loss作为预测分类的loss。

基于不同尺度的预测

YOLOv3依旧使用一组anchor来辅助不同尺度的预测,不同之处在于,YOLOv3使用了9个anchor,将这9个anchor分成3组看作3个不同的尺度。在每个预测层中,我们只使用其中一组3个anchor,相应的每个格子给出3个预测bounding box。

我们按照如下方法使用这3个不同的尺度。首先在基础特征提取器上,我们连接几个卷积层,然后接第一个尺度的预测层。然后,取出之前两层的特征图升采样两倍,再和之前某一层的特征图接起来,再通过几个卷积层,接入下一个尺度的预测层。以此类推。

特征提取

YOLOv3融合了YOLOv2中Darknet-19和ResNet的想法,提出了新的特征提取器,如图。


Darknet-53

效果


不同model比较

无效的尝试

作者在文中还提到了一些尝试过但效果不好的做法:

  • 使用anchor box预测机制来预测anchor box的x和y偏移。
  • 使用linear activation作为x和y预测的激励而不是使用logistic activation。
  • 使用focal loss。
  • 使用双IOU threshold。当一个预测和某个真实值的threshold大于0.7或小于0.3时,考虑这个预测中\(t_o\)的误差。这个操作会使得mAP降低。

原文链接:YOLO9000: Better, Faster,Stronger

YOLO9000是YOLO系列的第二版,在后面都是用YOLOv2表示。在YOLO的基础上,YOLOv2提出了诸多改进,核心就是让YOLO更快、更准确。YOLOv2中的模型YOLO9000号称可以检测9000个类别,并且可以支持一些类别没有detection label的情况下,来进行detection的工作,当然这部分的准确率还不算太高。

在这篇博客里,我将按照论文里的逻辑,分成Better、Faster和Stronger三个部分来描述论文里提出的改进。

Better

YOLOv2和YOLOv1相比,准确率得到了一些提升。在YOLOv1中比较显著的localization error较高以及recall低的问题,在YOLOv2中都得到了一些改善。为了使模型准确率更高,YOLOv2做出了以下改变:

  • Batch Normalization:YOLOv2在所有卷积层后面加了batch normalization,并删除了原来的dropout层,这使得mAP有2%的提升。

  • High Resolution Classifier:在YOLOv2中,使用了448*448的分辨率。训练时,首先在ImageNet上对分类网络finetune 10个epoch,然后再finetune detection部分。这个操作使得mAP上涨4个点。

  • Anchor Boxes and Prediction:YOLOv1中使用全连接层来直接预测bounding box,但在YOLOv2中,我们改用anchor boxes来预测bounding box。

    如果我们打开yolo的代码仓库,找到里面的.cfg文件,可以看到在网络配置里有这么一行(以yolov2-tiny.cfg为例)。

    1
    anchors =  0.57273, 0.677385, 1.87446, 2.06253, 3.33843, 5.47434, 7.88282, 3.52778, 9.77052, 9.16828

    这就是yolov2-tiny网络中使用的anchor,这其中每两个为一对,分别对应于x方向和y方向,上面这行显示的就是5个anchor。

    那么这些anchor是怎么生成的呢?首先我们拿到训练数据里所有bounding box的宽度和高度\((w,h)\),并假设这些bounding box的中心都在同一个点上,这样我们就得到了一组以同一个点为中心的长方形,对这些\((w,h)\)做k-means clustering,就可以得到\(k\)个聚类中心,也就是我们要的\(k\)个anchor,在实验中,我们发现\(k=5\)时的效果最好。为了更符合我们的任务,做k-means clustering时不使用欧几里得距离作为距离的衡量,而是使用IOU的大小作为距离的衡量,也即:
    $$d(\text{box},\text{centroid})=1-\text{IOU}(\text{box},\text{centroid})$$

    现在,我们获得了所有的anchor,接下来,我们需要调整网络的输出。


    Bounding Box

    和YOLOv1一样,如果输入图像分辨率为416*416,我们可以将输入图像划分为13*13个格子,其中每个格子大小为32*32,这样我们就会输出一个13*13的feature map,其中feature map的每一维也是一个向量,对应于中心点落在这个格子里的那些bounding box。在YOLOv2中,我们在每个格子里预测出5个bounding box,也就是五组中心坐标\((b_x,b_y)\),宽度\(b_w\),高度\(b_h\)以及置信值\(Pr(\text{Object})*\text{IOU}(b, \text{Object}))\)。值得注意的是,YOLOv2中不在直接输出这5个数,而是输出变化前的5个值\(t_x,t_y,t_w,t_h,t_o\),然后经过变换

    $$\begin{align}
    b_x&=\sigma(t_x)+c_x \\
    b_y&=\sigma(t_y)+c_y \\
    b_w&=p_we^{t_w} \\
    b_h&=p_he^{t_h} \\
    Pr(\text{Object})*\text{IOU}(b, \text{Object}))&=\sigma(t_o)
    \end{align}$$

    就可以得到\(b_x,b_y,b_w,b_h,Pr(\text{Object})*\text{IOU}(b, \text{Object}))\)。其中\(\sigma(x)\)表示Sigmoid函数,\((c_x,c_y)\)是这个格子左上角的坐标,\(p_w,p_h\)就是我们直接求出的anchor,5个bounding box正好对应于5个不同的anchor。

  • Fine-Grained Features:修改后的YOLOv2在13*13的特征图上进行预测,但是这样的分辨率可能和对于小尺度不利。Faster R-CNN和SSD在不同的特征图上跑proposal network来获得不同的分辨率,但在YOLOv2里,我们直接使用一个passthrough layer将前面层里的26*26分辨率的特征图和低分辨率的特征图用类似于ResNet中连Identity Mapping的方法连起来(我们把26*26*512特征图转换成13*13*2048,然后就可以和13*13的特征图连起来)。然后我们把这个特征图上做检测。这可以使performance提高1%。

  • Multi-Scale Training:YOLOv2把416*416作为输入的分辨率,但是由于网络里只有卷积核和max pooling,因此希望YOLOv2在应对不同分辨率的输入时有鲁棒性。我们可以在训练里不固定输入的大小,而是每10个epoch就随机选取另一个大小的图像,由于我们在网络里降采样的倍数是32,所以我们让所有的分辨率都是32的倍数({320; 352; …; 608}),这样网络就需要不停的学习预测不同分辨率的图像。

    在低分辨率的图像中,YOLOv2可以达到90FPS的速度,mAP和Fast R-CNN差不多。在高分辨率的图像中,YOLOv2仍可以达到实时的速度,78.6mAP。

Faster

YOLOv2没有使用VGG-16作为baseline,而是构建了一个新网络Darknet-19,这个网络包含19和卷积层和5个maxpooling,结构如下:


Darknet-19

Stronger

YOLOv2使用一种机制,可以同时训练classification和detection两部分的数据。细节还没有研究清楚。

原文链接:You Only Look Once: Unified, Real-Time Object Detection

机器人小学期做了一个奇怪的项目,大概是用v-rep这个平台仿真了一个简化版植物大战僵尸(当然,除了有植物有僵尸并且植物会打僵尸,僵尸试图吃点植物外没有什么相同之处)。事实证明使用一个机器人仿真平台来搞游戏虚拟器还是非常灾难的。

我在项目里负责目标检测部分,简单来说,就是给植物装了一个视觉传感器,我要使用视觉传感器传回来的图像,检测出图像中每个僵尸的bounding box。由于对检测速度要求比较高,因此就使用了YOLO这个网络。

YOLO已经有了三版(v1、v2、v3)。每一个版本在上一版本的基础上做出了一些修改,使得速度更快,效果更好。本文只包括YOLOv1,也就是最早的一版,和最新的YOLOv3相比,还是有不少差异的。

YOLOv1

回忆R-CNN的做法,R-CNN首先使用regional proposal方法选出一些可能的bounding box,然后对这些bounding box做分类,最后再对这些bounding box做筛选、微调。R-CNN把object detection这个任务分成了几个部分,包括选择bounding box和分类等,每部分都由一个专门的网络负责。这样带来的问题是,速度慢并且很难优化。因此YOLO针对这个问题作出了改进:在YOLO里,我们将整张图片作为输入,获得bounding box和分类都在同一个网络中完成(look once),也就是说我们把一整个detection部分看成了一个回归问题。这个做法带来的好处包括:

  • 速度快:Titan X上可以做到45fps,fast version可以做到150fps。
  • 直接使用整张图片使得我们可以获得更多的全局信息,相比于Fast R-CNN,YOLO的背景识别错误更少。
  • 泛化能力更强。

网络

结构

YOLOv1有两种网络,一个是24个卷积层的YOLO,还有一个简化版的只有9个卷积层的Fast YOLO。下面以24个卷积层的版本为例。

Network

网络包含24个卷积层和2个全连接层,通过卷积层提取特征,然后用全连接层预测概率和坐标。

在具体实现中,除了最后一层的激活函数为ReLU外,其他各层的激活函数均为leaky ReLU,定义如下:

$$\phi(x)=
\begin{cases}
x, &\text{if $x>0$} \\
0.1x, &\text{otherwise} \
\end{cases}$$

输入和输出

网络将整个图片作为输入,最后输出一个大小为\(S*S*(B*5+C)\)(注意Tensor大小与v3有所不同)的Tensor。

我们对于这个输出Tensor做一点解释。我们将输入图像划分成\(S*S\)的网格,对于网格中的每个格子,我们要预测\(B\)个中心落在这个格子里的bounding box。每个bounding box有5个参数,分别是中点的\(x\)、\(y\)坐标以及宽度\(w\)和高度\(h\),此外,还包含一个置信值\(Pr(Object)*IOU_{pred}^{truth}\)表示这个bounding box里包含一个物体的概率乘上它和真实值的IOU。最后,对于每个格子,我们还要预测\(C\)个参数,其中第\(i\)个参数\(Pr(C_i|Object)\)表示在格子里有物体的情况下,这个物体属于第\(i\)类的概率。

根据置信值和属于各类别的概率,我们可以求出各类别的置信值:

$$Pr(Class_i|Object)*Pr(Object)*IOU_{pred}^{truth}$$

额外说明一点,\(x\)、\(y\)是相对这个格子的坐标,并且归一化到了\([0,1]\)区间;宽度\(w\)和高度\(h\)是相对于正常图片的,也归一化到了\([0,1]\)区间。


输出

损失函数

最后,我们还需要定义损失函数。

为了便于优化,我们使用误差的平方和作为损失函数,但是这会带来两方面的问题:

  • 定位错误和分类错误同等对待。
  • 在一张图片中往往有很多格子是不包含物体的,这写格子的置信值会逐渐趋于0,这会淹没那些包含物体的格子的误差。(置信值confidence score为为\(Pr(Object)*IOU_{pred}^{truth}\)。)
  • 不同尺寸的物体在计算错误时也被平等对待了。但是很明显,相同的bounding box的误差出现在预测尺寸较大的物体可能并不显眼,但是如果出现在预测尺寸较小的物体上时就会很严重。

为了解决前两个问题,我们赋予定位错误和预测是否包含物体的错误不同的权重,其中定位错误的权重\(\lambda_{coord}=5\),预测是否包含物体的错误的权重\(\lambda_{noobj}\)。

为了解决第三个问题,我们不直接使用bounding box的高度\(w\)和宽度\(h\)计算,而是使用它们开根号后计算误差。

最后,我们得到一个形如下式的损失函数,

$$\begin{align} &\lambda_{coord}\sum_{i=0}^{S^2}\sum_{j=1}^{B}\mathbb{1}_{ij}^{obj}[(x_i-\hat{x}_i)^2+(y_i-\hat{y}_i)^2] \\ +&\lambda_{coord}\sum_{i=0}^{S^2}\sum_{j=1}^{B}\mathbb{1}_{ij}^{obj}[(\sqrt{w_i}-\sqrt{\hat{w}_i})^2+(\sqrt{h_i}-\sqrt{\hat{h}_i})^2] \\ +&\sum_{i=0}^{S^2}\sum_{j=1}^{B}\mathbb{1}_{ij}^{obj}(C_i-\hat{C}_i)^2+\lambda_{noobj}\sum_{i=0}^{S^2}\sum_{j=1}^{B}\mathbb{1}_{ij}^{obj}(C_i-\hat{C}_i)^2 \\ +&\sum_{i=1}^{S^2}\mathbb{1}_{i}^{obj}\sum_{c\in classes}(p_i(c)-\hat{p}_i(c))^2 \end{align}$$

\(\mathbb{1}_{ij}^{obj}\)表示第\(i\)个格子里的第\(j\)个预测“负责”预测这个物体,\(\mathbb{1}_i^{obj}\)表示物体\(obj\)出现在第\(i\)个格子里。(“负责”预测这个物体是指这个预测的bounding box和某个真实值的IOU是所有预测中最大的,IOU为Intersection of Units,即两个bounding box面积的交/并。)

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×