使用 RetinaNet 进行航空影像目标检测

2019-08-23 18:41:26 浏览数 (1)

通过使用金字塔池化模块(Pyramid Pooling Module),在整合基于不同区域的上下文后,PSPNet在效果上超过了FCN、DeepLab和DilatedNet等时下最佳的方法。

原标题 | Object Detection On Aerial Imagery Using RetinaNet

作者 | Kapil Varshney

编辑 | Pita

(左边)原始图片。(右边) 使用RetinaNet进行车辆检测(以绿色框注释)

使用RetinaNet检测汽车和游泳池

介绍

出于税收评估的目的,通常情况下,调查是在实地进行的。这些调查对于计算房产的真实价值非常重要。例如,拥有一个游泳池可以增加房价。同样,附近或商店周围的汽车数量可以表明该地区的经济活动水平。能够通过航空图像和人工智能实现这一目标,可以消除低效率以及人类所需的高成本和时间来明显改善这些过程。

为了解决这个问题,我们将尝试用224x224像素航拍图像的RGB芯片检测汽车和游泳池。训练数据集有3748个带有边界框注释和PASCAL VOC格式标签的图像。

这个问题以及数据集由ESRI在HackerEarth上发布,作为ESRI数据科学挑战2019的题目。我参与并获得了公共排行榜的第三名,其中RetinaNet模型的mAP(平均精度)为77.99,atIoU = 0.3。在下面的文章中,我将解释我是如何尝试这个问题的。

RetinaNet

RetinaNet是通过对现有的单目标检测模型(如YOLO和SSD)进行两次改进而形成的:

1.Feature Pyramid Networks for Object Detection(https://arxiv.org/abs/1612.03144)

2.Focal Loss for Dense Object Detection(https://arxiv.org/abs/1708.02002)

特征金字塔网络

金字塔网络通常被用来识别不同规模的物体。特征金字塔网络(FPN)利用深度卷积神经网络中固有的多尺度金字塔层次来生成特征金字塔。

one-stage RetinaNet网络结构在前馈ResNet结构(a)的基础上使用特征金字塔网络(FPN)来生成丰富的多尺度卷积特征金字塔(b)。在这个主干网上有两个子网络,一个用于分类锚盒(C),另一个用于从锚盒回归到真实的对象盒(d)。我们的网络设计的十分简单,这使得这项工作能够专注于一种新的focal loss函数,从而消除了我们的one-stage检测器与最先进的two-stage检测器之间的精度差距,比如Faster R-CNN与FPN同时以更快的速度运行。

Focal Loss

Focal Loss是对交叉熵损失的改进,有助于减小分类良好的实例的相对损失,并将更多的注意力集中在错误分类的实例上。

在存在大量简单的背景示例的情况下,focal loss能够训练高度精确的密集目标检测器。

Focal Loss损失函数

如果你对模型的细节更感兴趣,我建议你阅读原始论文和这个非常有用、描述性很强的博客 'The intuition behind RetinaNet'(https://medium.com/@14prakash/the-intuition-behind-retinanet-eb636755607d)。

现在,让我们开始实现并编写代码。以下是您可以遵循的Github存储库:

kapil-varshney/esri_retinanet Contribute to kapil-varshney/esri_retinanet development by creating an account on GitHub

(https://github.com/kapil-varshney/esri_retinanet)

配置 Retinanet

我们将使用 Fizyr实现 Keras implementation of RetinaNet。如果你有深度学习的服务器,你可以运行上面的代码,如果你没有可以运行深度学习的服务器,你可以使用这里的代码 here。此外,我建议使用虚拟环境。下面的脚本将安装RetinaNet和其他必需的包。

或者,你可以在AWS上运行GPU实例(p2.xlarge)与AMI的“deep-learning-for-computer-vision-with-python”。这个AMI预装了keras-retinanet和其他必需的包。你可以在使用 workon retinanet 命令激活RetinaNet的虚拟环境之后开始使用该模型。

注意:Retinanet的计算量很大。当计算批量大小为4的图像(224x224)块时,它将要求至少7-8GBs的GPU内存。

一旦安装了RetinaNet,为该项目创建以下目录结构。

我将详细解释其中的每一个,但这里只是一个概述:

  • build_dataset.py—用于创建训练/测试数据集的Python脚本。
  • config/esri_retinanet_config.py —构建脚本使用的配置文件。
  • dataset/annotations —保存所有图像注释的目录
  • dataset/images —保存所有图像的目录
  • dataset/submission_test_data_images —Esri Data Science 挑战提交的测试目录。如果您正在处理自己的数据集和其他项目,那么可以忽略这一点。
  • snapshots —每次迭代后保存所有训练记录的目录。
  • models —保存用于评估和测试记录的目录。
  • tensorboard —保存训练日志以供tensorboard使用的目录。
  • predict.py —对提交测试文件进行预测的脚本。

创建数据集

首先,我们需要编写一个配置文件,该文件将保存图像、注释、输出CSVs ——训练,测试和种类的路径,以及test-train split值。有了这样一个配置文件,代码就可以用于不同的数据集。

在这个配置文件中,TRAIN_TEST_SPLIT=0.75。标准做法是在训练数据集和测试数据集之间从原始数据集中分离出75-25或70-30,在某些情况下甚至是80-20。但是对于这次比赛,我没有制作测试数据集,而是使用完整的数据集进行训练。这是因为仅仅提供了3748图像数据集。此外,提供了2703个图像的测试数据集(没有注释),通过在线提交预测可以测试模型。

接下来,让我们编写一个python代码,它将读取所有图像路径和注释,并输出在训练和评估模型期间所需的三个CSVs:

  • train.csv — 此文件将以下列格式保存用于训练的所有注释<path/to/image>,<xmin>,<ymin>,<xmax>,<ymax>,<label>,每一行将表示一个边界框,因此,根据图像中注释对象的数量,可以在多个行中显示一个图像。
  • test.csv — 类似于train.csv的格式,该文件将保存用于测试模型的所有注释。
  • classes.csv —一个具有索引分配数据集中所有唯一类标签的文件 (从0开始,忽略background)

让我们首先创建一个builddatet.py文件并导入所需的包。注意,我们导入了在config目录中创建的 esri_retinanet_config.py 文件,并给出了它的alias配置。

在上面的代码中,我们创建了一个参数解析器,可以选择接收图像和注释路径、输出 CSV 的路径以及train-test split。虽然我们已经在配置文件中定义了这些参数。但是,我也意识到,有时我想要为实验创建图像的子样本,或者有不同的train-test split等。当时,在不更改配置文件的情况下,在执行脚本时可以选择更快地传递这些参数。可以看到的是,我为配置文件本身的每个参数提供了默认值。因此,除非您想提供这些参数,否则不需要提供这些参数。解析完参数后,为每个参数分配简单的变量名。

在前面的代码中,我们将图像路径读取到一个列表中,对列表进行随机化,将其拆分为训练集和测试集,并以格式(<dataset_type>, <list_of_paths>, <outpuCSV>)将它们存储在另一个列表数据集中。我们还将初始CLASS集,以保存数据集中的所有唯一类标签。

接下来,我们循环遍历每个数据集(训练和测试),并打开要写入的输出CSV文件。对于每个数据集,我们循环遍历每个图像路径。对于每一张图像,提取文件名并构建相应的注释路径。这是因为,通常情况下,图像和注释文件具有相同的名称,但扩展名不同。例如dataset/images/0000001.jpg

在 dataset/annotations/0000001.xml 中有它的注释。如果数据集遵循不同的命名约定,请修改本节。使用 BeautifulSoup 解析注释文件(XML)。然后,我们可以从解析的XML中找到“width”、“height”和“object(s)”。

对于每个图像,请查找所有对象并遍历其中的每一个对象。然后,在注释中查找每个对象的边界框(xmin, ymin, xmax, ymax)和类标签(名称)。并通过截断超出图像边界的任何边界框坐标来进行清理。另外做一次正确的检查,如果程序出错,那么任何最小值都大于最大值,反之亦然。如果我们找到这样的值,我们将忽略这些对象并继续到下一个对象。

现在,我们有了所有的信息,我们可以继续写到输出CSV,一次一行。另外,继续将标签添加到 CLASSES 集中。这最终会有所有唯一的类标签。

以所需的格式构建数据集的最后一件事是将类标签及其各自的索引写入CSV。在ESRI数据集中,只有两个类 —— cars(label:'1',index:1)和swimming pool(label:'2',index:0)。classes.csv 就是这样查找Esri数据集的。

模型的训练与评估

至此,数据集和RetinaNet项目代码及所需环境已经准备完成,让我们继续在该数据集上训练RetinaNet模型吧。

代码语言:javascript复制
# For a list of all arguments$ retinanet-train --help

我使用以下命令训练模型:

代码语言:javascript复制
$ retinanet-train --weights resnet50_coco_best_v2.1.0.h5 --batch-size 4 --steps 4001 --epochs 20 --snapshot-path snapshots --tensorboard-dir tensorboard csv dataset/train.csv dataset/classes.csv

建议下载一个预训练模型或者权重文件替代随机初始化权重这样可以加速训练过程(损失会较早收敛)。我使用在COCO数据集上得到的ResNet50 作为预训练模型初始化权重。可以使用下面的链接下载ResNet50模型:

代码语言:javascript复制
https://github.com/fizyr/keras-retinanet/releases/download/0.5.0/resnet50_coco_best_v2.1.0.h5

batch-size 和 steps 的值依赖于你的系统配置(主要是GPU存储空间)和数据集大小。通常在初始时我会将batch-size 设置为8,然后依据启动训练的成功与否将batch-size扩大或缩小为原来的2倍。如当训练正常启动时,就中断训练过程(使用CTR C),然后使用一个较大batch-size再次训练。

一旦你确定了batch-size 的大小,就可以计算每次遍历整个数据集所需的steps了。如下的命令可以告诉你train.csv中的行数也即样本数,train.csv之前已经创建在dataset目录中。

代码语言:javascript复制
wc -l datatset/train.csv

计算step的大小非常简单: steps = 样本数/batch-size。接着,设置迭代次数epochs。根据我的经验,RetinaNet收敛的很快,不出意外的话一个小的epochs就可以了。如果不行,你可以使用最后一次迭代的结果继续训练你的模型。因此,我们将提供一个快照路径(snapshot-path)来保存你每次迭代之后的模型。

我们同样提供一个tensorflow-dir目录将所有的日志存放到这里,并且可以使用tensorboard来可视化训练过程。在确定你的tensorboard已经装好后,就可以打开一个新的终端界面并使用以下命令可以启动tensorboard。

代码语言:javascript复制
# To launch tensorboard$ tensorboard --logdir <path/to/logs/dir>

最后, 提供训练数据集和类别标签的csv 文件并执行训练命令。训练期间你可以看个钢铁侠电影、小睡片刻、做一些其他事情等。使用亚马逊平台的AWS p2.xlarge instance GPU为 tesla K80, 在一个3748(224X224)张图片的数据集上的一次迭代会超过2个小时。

一旦模型被训练到你满意的程度,就可以将模型转换为可以用来验证和预测的格式。

代码语言:javascript复制
代码语言:javascript复制
# To convert the model$ retinanet-convert-model <path/to/desired/snapshot.h5> <path/to/output/model.h5>
# To evaluate the model$ retinanet-evaluate <path/to/output/model.h5> csv <path/to/train.csv> <path/to/classes.csv>
# Sample evaluation95 instances of class 2 with average precision: 0.8874494 instances of class 1 with average precision: 0.7200mAP: 0.8037

上面这个示例使用375张图片的数据集迭代18次,并在一个125张图片的测试集中进行验证,平均正确率能够达到80.37%。在这么小的数据集上这个结果算是不错啦。

预测

我们创建一个脚本predict.py,使用已训练的模型在最终提交结果的数据集上做预测并将结果写入磁盘中。

在预测之前需要用一些方法对图像进行预处理,这些方法包含在keras_retinanet工具中。并且,导入我们前面创建的配置文件,以加载一些路径。

构造参数解析器,以便在执行脚本时接收参数,并解析参数 。参数model是已经训练后的模型文件的路径,这个模型文件将被用来进行预测。类标签和预测输出的目录,默认从配置文件中获取,因此这里不需要这些参数。参数input为包含图片的路径,用于预测。参数confidence用来过滤不可信的预测结果。

接下来,从类标签CSV文件中加载类标签的映射,并且将其保存在一个字典中。加载用于预测的模型。图像目录由input参数提供 ,提取路径并生成所有图片路径的列表。

遍历数据集中的每一张图片,对每一张图片进行预测。上面代码中的6-9行从图像路径中提取图片名称,并创建一个txt格式的输出文件,图片的预测结果将会放到该文件中。11-15行,我们加载图片,在将其送入模型之前,进行图像的预处理、调整大小、扩展维度。在第18行,我们将预处理过的图片送进模型中,返回预测的边框坐标,以及每个边框属于每个标签的概率值。在上述代码的最后一行,根据原始图像的大小重新调整边框的坐标。

接着,遍历模型输出的每个检测结果。抛弃那些得分小于置信度阈值的结果。然而,如果你想计算平均正确率,就要保留所有的预测结果,可通过将confidence参数设置为0实现。边框的坐标值为float类型,需要转换成int类型的。将每一个预测的结果构造成需要的格式:<类别名称> <可信度> <ymin> <xmin> <ymax> <xmax> 并将其写入到文件中。一张图片的所有预测信息都被写入相应的文件后,就要关闭文件。

代码语言:javascript复制
$ python predict.py --model models/output.h5 --input dataset/submission_test_data_images --confidence 0.0

运行上述命令运行predict.py脚本。可以根据你自己的数据集和项目适当的修改参数。

实验及结果

最初,我使用的训练数据仅为数据集的十分之一(375张图片),迭代18次后得到模型。当置信度的阈值为0.5时,这个模型在测试集上的平均正确率为0.71。我在整个数据集上(3748张图片)恢复模型的训练,继续迭代10次后平均值正确率增加为0.74。我决定对模型的anchor boxes进行一些更改。因为数据集中仅仅有正方形的边框,所以我将边框的长宽比的取值范围由[0.5,1.2]更改为[1]。这似乎是一个不错的尝试,但我很快意识到,anchor的长宽比不会随着数据补充而发生变换。随着网络大小的降低,在整个数据集上网络的训练速度就会增加。预测的正确率也会小幅提升,但随后开始下降。我决定使用第二次的测试结果,其中将confidence 的值设置为0,使其包含所有的预测结果。这使得平均正确率达到了77.99%确保了我第三名的成绩。我也尝试了一些其他的实验,包括使用FPN得到图像的多尺度特征、数据扩充增强等但都不成功,最终还是提交了之前的实验结果。

总结

在这篇文章中,我们讨论了RetinaNet模型,以及我如何在Esri 2019数据科学挑战赛中使用它在224x224的航空图像中检测汽车和游泳池的。我们从构建项目目录开始。接下来,我们构建了徐那联模型所必须的训练/测试数据集。用适当的参数对模型进行训练,然后将训练后的模型转换为评价和预测模型。我们创建了另一个脚本,在要提交的测试集进行检测并将结果保存到磁盘中。最后,简要描述了我所做的实验和取得的结果。

参考文献

Focal Loss for Dense Object Detection(基于两阶段的R-CNN扩展的高精度目标检测器):https://arxiv.org/abs/1708.02002

Feature Pyramid Networks for Object Detection(使用特征金字塔在不同的尺度中检测目标):https://arxiv.org/abs/1612.03144

Deep Learning for Computer Vision with Python: Master Deep Learning Using My New Book(一本介绍计算机视觉的书):https://www.pyimagesearch.com/deep-learning-computer-vision-python-book/

非常感谢,你能看到这里。希望能够对你有所帮助。请留下你的意见和建议。你也可以通过LinkedIn联系我(https://www.linkedin.com/in/kapilvarshney14/)。代码放在github中:

kapil-varshney/esri_retinanet Contribute to kapil-varshney/esri_retinanet development by creating an account on GitHub.(https://github.com/kapil-varshney/esri_retinanet)

0 人点赞