上年 11 月,matterport 开源了 Mask R-CNN 实现,它在 GitHub 已 fork1400 次,被用于很多项目,同时也获得了完善。作者将在本文中解释 Mask R-CNN 的工作原理,并介绍了颜色填充器的应用案例和实现过程。
-
代码(包括作者构建的数据集和已训练的模型):https://github.com/matterport/Mask_RCNN/tree/master/samples/balloon
什么是实例分割?
实例分割是一种在像素层面识别目标轮廓的任务,相比其他相关任务,实例分割是较难解决的计算机视觉任务之一:
-
分类:这张图像中有一个气球。
-
语义分割:这些全是气球像素。
-
目标检测:这张图像中的这些位置上有 7 个气球。
-
实例分割:这些位置上有 7 个气球,并且这些像素分别属于每个气球。
Mask R-CNN
Mask R-CNN 是一个两阶段的框架,第一个阶段扫描图像并生成提议(proposals,即有可能包含一个目标的区域),第二阶段分类提议并生成边界框和掩码。Mask R-CNN 扩展自 Faster R-CNN,由同一作者在去年提出。Faster R-CNN 是一个流行的目标检测框架,Mask R-CNN 将其扩展为实例分割框架。
Mask R-CNN 的主要构建模块:
1. 主干架构
主干网络的简化图示
这是一个标准的卷积神经网络(通常来说是 ResNet50 和 ResNet101),作为特征提取器。底层检测的是低级特征(边缘和角等),较高层检测的是更高级的特征(汽车、人、天空等)。
经过主干网络的前向传播,图像从 1024x1024x3(RGB)的张量被转换成形状为 32x32x2048 的特征图。该特征图将作为下一个阶段的输入。
-
代码提示:主干网络在 resnet_graph() 函数中。代码支持 ResNet50 和 ResNet101。
特征金字塔网络(FPN)
来源:Feature Pyramid Networks for Object Detection
上述的主干网络还可以进一步提升。由 Mask R-CNN 的同一作者引入的特征金字塔网络(FPN)是对该主干网络的扩展,可以在多个尺度上更好地表征目标。
FPN 通过添加第二个金字塔提升了标准特征提取金字塔的性能,第二个金字塔可以从第一个金字塔选择高级特征并传递到底层上。通过这个过程,它允许每一级的特征都可以和高级、低级特征互相结合。
在我们的 Mask R-CNN 实现中使用的是 ResNet101+FPN 主干网络。
-
代码提示:FPN 在 MaskRCNN.build() 中创建,位于构建 ResNet 的部分之后。FPN 引入了额外的复杂度:在 FPN 中第二个金字塔拥有一个包含每一级特征的特征图,而不是标准主干中的单个主干特征图(即第一个金字塔中的最高层)。选用哪一级的特征是由目标的尺寸动态地确定的。
2. 区域建议网络(RPN)
展示 49 个 anchor box 的简化图示
RPN 是一个轻量的神经网络,它用滑动窗口来扫描图像,并寻找存在目标的区域。
RPN 扫描的区域被称为 anchor,这是在图像区域上分布的矩形,如上图所示。这只是一个简化图。实际上,在不同的尺寸和长宽比下,图像上会有将近 20 万个 anchor,并且它们互相重叠以尽可能地覆盖图像。
RPN 扫描这些 anchor 的速度有多快呢?非常快。滑动窗口是由 RPN 的卷积过程实现的,可以使用 GPU 并行地扫描所有区域。此外,RPN 并不会直接扫描图像,而是扫描主干特征图。这使得 RPN 可以有效地复用提取的特征,并避免重复计算。通过这些优化手段,RPN 可以在 10ms 内完成扫描(根据引入 RPN 的 Faster R-CNN 论文中所述)。在 Mask R-CNN 中,我们通常使用的是更高分辨率的图像以及更多的 anchor,因此扫描过程可能会更久。
-
代码提示:RPN 在 rpn_graph() 中创建。anchor 的尺度和长宽比由 config.py 中的 RPN_ANCHOR_SCALES 和 RPN_ANCHOR_RATIOS 控制。
RPN 为每个 anchor 生成两个输出:
-
anchor 类别:前景或背景(FG/BG)。前景类别意味着可能存在一个目标在 anchor box 中。
-
边框精调:前景 anchor(或称正 anchor)可能并没有完美地位于目标的中心。因此,RPN 评估了 delta 输出(x、y、宽、高的变化百分数)以精调 anchor box 来更好地拟合目标。
使用 RPN 的预测,我们可以选出最好地包含了目标的 anchor,并对其位置和尺寸进行精调。如果有多个 anchor 互相重叠,我们将保留拥有最高前景分数的 anchor,并舍弃余下的(非极大值抑制)。然后我们就得到了最终的区域建议,并将其传递到下一个阶段。
-
代码提示:ProposalLayer 是一个自定义的 Keras 层,可以读取 RPN 的输出,选取最好的 anchor,并应用边框精调。
3. ROI 分类器和边界框回归器
这个阶段是在由 RPN 提出的 ROI 上运行的。正如 RPN 一样,它为每个 ROI 生成了两个输出:
阶段 2 的图示。来源:Fast R-CNN
-
类别:ROI 中的目标的类别。和 RPN 不同(两个类别,前景或背景),这个网络更深并且可以将区域分类为具体的类别(人、车、椅子等)。它还可以生成一个背景类别,然后就可以弃用 ROI 了。
-
边框精调:和 RPN 的原理类似,它的目标是进一步精调边框的位置和尺寸以将目标封装。
-
代码提示:分类器和边框回归器已在 fpn_classifier_graph() 中创建。
ROI 池化
在我们继续之前,需要先解决一些问题。分类器并不能很好地处理多种输入尺寸。它们通常只能处理固定的输入尺寸。但是,由于 RPN 中的边框精调步骤,ROI 框可以有不同的尺寸。因此,我们需要用 ROI 池化来解决这个问题。
图中展示的特征图来自较底层。
ROI 池化是指裁剪出特征图的一部分,然后将其重新调整为固定的尺寸。这个过程实际上和裁剪图片并将其缩放是相似的(在实现细节上有所不同)。
Mask R-CNN 的作者提出了一种方法 ROIAlign,在特征图的不同点采样,并应用双线性插值。在我们的实现中,为简单起见,我们使用 TensorFlow 的 crop_and_resize 函数来实现这个过程。
-
代码提示:ROI 池化在类 PyramidROIAlign 中实现。
4. 分割掩码
到第 3 节为止,我们得到的正是一个用于目标检测的 Faster R-CNN。而分割掩码网络正是 Mask R-CNN 的论文引入的附加网络。
掩码分支是一个卷积网络,取 ROI 分类器选择的正区域为输入,并生成它们的掩码。其生成的掩码是低分辨率的:28x28 像素。但它们是由浮点数表示的软掩码,相对于二进制掩码有更多的细节。掩码的小尺寸属性有助于保持掩码分支网络的轻量性。在训练过程中,我们将真实的掩码缩小为 28x28 来计算损失函数,在推断过程中,我们将预测的掩码放大为 ROI 边框的尺寸以给出最终的掩码结果,每个目标有一个掩码。
-
代码提示:掩码分支网络在 build_fpn_mask_graph() 中。
建立一个颜色填充过滤器
和大多数图像编辑 app 中包含的过滤器不同,我们的过滤器更加智能一些:它能自动找到目标。当你希望把它应用到视频上而不是图像上时,这种技术更加有用。
训练数据集
通常我会从寻找包含所需目标的公开数据集开始。但在这个案例中,我想向你展示这个项目的构建循环过程,因此我将介绍如何从零开始构建一个数据集。
我在 flickr 上搜索气球图片,并选取了 75 张图片,将它们分成了训练集和验证集。找到图片很容易,但标注阶段才是困难的部分。
等等,我们不是需要数百万张图片来训练深度学习模型吗?实际上,有时候需要,有时候则不需要。我是考虑到以下两点而显著地减小了训练集的规模:
首先,迁移学习。简单来说,与其从零开始训练一个新模型,我从已在 COCO 数据集(在 repo 中已提供下载)上训练好的权重文件开始。虽然 COCO 数剧集不包含气球类别,但它包含了大量其它图像(约 12 万张),因此训练好的图像已经包含了自然图像中的大量常见特征,这些特征很有用。其次,由于这里展示的应用案例很简单,我并不需要令这个模型达到很高的准确率,很小的数据集就已足够。
有很多工具可以用来标注图像。由于其简单性,我最终使用了 VIA(VGG 图像标注器)。这是一个 HTML 文件,你可以下载并在浏览器中打开。标注最初几张图像时比较慢,不过一旦熟悉了用户界面,就能达到一分钟一个目标的速度。
VGG 图像标注器工具的用户界面
如果你不喜欢 VIA 工具,可以试试下列工具,我都测试过了:
-
LabelMe:最著名的标注工具之一,虽然其用户界面有点慢,特别是缩放高清图像时。
-
RectLabel:简单易用,只在 Mac 可用。
-
LabelBox:对于大型标记项目很合适,提供不同类型标记任务的选项。
-
COCO UI:用于标注 COCO 数据集的工具。
加载数据集
分割掩码的保存格式并没有统一的标准。有些数据集中以 PNG 图像保存,其它以多边形点保存等。为了处理这些案例,在我们的实现中提供了一个 Dataset 类,你可以通过重写几个函数来读取任意格式的图像。
VIA 工具将标注保存为 JSON 文件,每个掩码都是一系列多边形点。
-
代码提示:通过复制 coco.py 并按你的需要修改是应用新数据集的简单方法,我将新的文件保存为 ballons.py。
我的 BalloonDataset 类是这样定义的:
load_balloons 读取 JSON 文件,提取标注,然后迭代地调用内部的 add_class 和 add_image 函数来构建数据集。
load_mask 通过画出多边形为图像中的每个目标生成位图掩码。
image_reference 返回鉴别图像的字符串结果,以进行调试。这里返回的是图像文件的路径。
你可能已经注意到我的类不包含加载图像或返回边框的函数。基础的 Dataset 类中默认的 load_image 函数可以用于加载图像,边框是通过掩码动态地生成的。
验证该数据集
为了验证我的新代码可以正确地实现,我添加了这个 Jupyter notebook:inspect_balloon_data.ipynb。它加载了数据集,并可视化了掩码、边框,还可视化了 anchor 来验证 anchor 的大小是否拟合了目标大小。以下是一个 good example。
来自 inspect_balloon_data notebook 的样本
-
代码提示:为了创建这个 notebook 我复制了 inspect_data.ipynb(这是为 COCO 数据集写的),然后修改了代码的初始部分来加载 Balloons 数据集。
配置
这个项目的配置和训练 COCO 数据集的基础配置很相似,因此我只需要修改 3 个值。正如我对 Dataset 类所设置的,我复制了基础的 Config 类,然后添加了我的覆写:
基础的配置使用的是 1024x1024 px 的输入图像尺寸以获得最高的准确率。我保持了相同的配置,虽然图像相对较小,但模型可以自动地将它们重新缩放。
-
代码提示:基础的 Config 类在 config.py 中,BalloonConfig 在 balloons.py 中。
训练
Mask R-CNN 是一个规模很大的模型。尤其是在我们的实现中使用了 ResNet101 和 FPN,因此你需要一个 12GB 显存的 GPU 才能训练这个模型。我使用的是 Amazon P2 实例来训练这个模型,在小规模的数据集上,训练时间不到 1 个小时。
用以下命令开始训练,以从 balloon 的目录开始运行。这里,我们需要指出训练过程应该从预训练的 COCO 权重开始。代码将从我们的 repo 中自动下载权重。
如果训练停止了,用以下命令让训练继续:
-
代码提示:除了 balloon.py 以外,该 repo 还有两个例子:train_shapes.ipynb,它训练了一个小规模模型来检测几何形状;coco.py,它是在 COCO 数据集上训练的。
检查结果
inspect_balloon_model notebook 展示了由训练好的模型生成的结果。查看该 notebook 可以获得更多的可视化选项,并一步一步检查检测流程。
-
代码提示:这个 notebook 是 inspect_model.ipynb 的简化版本,包含可视化选项和对 COCO 数据集代码的调试。
颜色填充
现在我们已经得到了目标掩码,让我们将它们应用于颜色填充效果。方法很简单:创建一个图像的灰度版本,然后在目标掩码区域,将原始图像的颜色像素复制上去。以下是一个 good example:
-
代码提示:应用填充效果的代码在 color_splash() 函数中。detect_and_color_splash() 可以实现加载图像、运行实例分割和应用颜色填充过滤器的完整流程。
FAQ 环节
Q:我希望了解更多该实现的细节,有什么可读的?
A:按这个顺序阅读论文:RCNN、Fast RCNN、Faster RCNN、FPN、Mask RCNN。
Q:我能在哪里提更多的问题?
A:我们的 repo 的 Issue 页面:https://github.com/matterport/Mask_RCNN/issues
from:https://mp.weixin.qq.com/s?__biz=MzA3MzI4MjgzMw==&mid=2650739707&idx=2&sn=feadded92a57db2aa890e6ad2f187cc8&chksm=871ad785b06d5e93a08200024b32c8d6c1b0939be4cd95940ae463a21943a7ced1e1f5b1e7d7&mpshare=1&scene=23&srcid=03299otIPi69sMUqrrxX3Ijv%23rd