计算机视觉领域中,没有看似无法克服的挑战,尤其是在识别场景中的像素并将其用于多样化和非常实用的用途方面。本文将探讨其中一个应用,并尝试使用Python来理解其工作原理。本文的目标是构建一个定制的Mask R-CNN模型,用于检测汽车上的损伤区域(如上图所示)。这样的模型可以被保险公司用于快速处理索赔,如果用户可以上传图片,他们可以从中评估损伤。此外,该模型也可以被贷款机构用于评估二手车贷款。
Mask R-CNN是一种实例分割模型,它允许识别类别的像素级位置。“实例分割”意味着在场景中分割单个对象,无论它们是否为同一类型——例如,识别单独的汽车、人等。下面是一个在COCO数据集上训练的Mask-RCNN模型的GIF。如所见,可以识别汽车、人、水果等的像素位置。
Mask R-CNN与传统的对象检测模型(如Faster R-CNN)不同,除了识别类别和其边界框位置外,它还可以为边界框中对应类别的像素着色。何时需要这种额外的细节?能想到的一些例子包括:自动驾驶汽车需要知道道路的确切像素位置;可能还需要知道其他汽车的像素位置以避免碰撞;机器人可能需要它们想要拾取的对象的像素位置(亚马逊的无人机在这里浮现在脑海中)。
尝试在COCO类别上构建的Mask R-CNN模型的最简单方法是使用Tensorflow对象检测API。可以参考这篇文章(由撰写),它包含了如何使用API并在YouTube视频上运行模型的信息。
在构建Mask R-CNN模型之前,让首先了解它实际上是如何工作的。将Mask R-CNN视为Faster R-CNN(进行对象检测,类别+边界框)和FCN(全卷积网络)进行像素级边界的组合是一个好方法。请参见下图:Mask RCNN是Faster RCNN和FCN的结合。
Mask R-CNN在概念上很简单:Faster R-CNN对每个候选对象有两个输出,一个类别标签和一个边界框偏移;在此基础上添加第三个分支,输出对象掩码——这是一个二进制掩码,指示边界框中对象所在的像素。但是,额外的掩码输出与类别和框输出不同,需要提取对象的更精细的空间布局。为此,Mask R-CNN使用了下面描述的全卷积网络(FCN)。
FCN是一种流行的语义分割算法。该模型使用各种卷积和最大池化层首先将图像解压到原始大小的1/32。然后它在这个粒度级别上进行类别预测。最后,它使用上采样和反卷积层将图像调整回原始尺寸。
简而言之,可以这样说,Mask R-CNN将两个网络——Faster R-CNN和FCN结合在一个大型架构中。模型的损失函数是进行分类、生成边界框和生成掩码的总损失。
Mask RCNN有一些额外的改进,使其比FCN更准确。可以在他们的论文中阅读更多关于这些改进的信息。
对于构建自定义Mask R-CNN,将利用Matterport Github仓库。最新的TensorFlow对象检测仓库也提供了构建Mask R-CNN的选项。然而,只推荐给有决心的人使用!TensorFlow的版本、对象检测、掩码的格式等可能需要调试错误。能够成功地使用它训练Mask R-CNN。
但看到很多人在各种错误中挣扎。所以强烈推荐任何进入这个领域的人使用Matterport Mask R-CNN仓库。
对于这个练习,从谷歌收集了66张损坏汽车的图像(50张训练和16张验证)。查看下面的一些示例。
Mask R-CNN模型要求用户注释图像并识别损伤区域。使用的注释工具是VGG Image Annotator——v 1.0.6。可以使用这个链接提供的HTML版本。使用这个工具,可以创建一个多边形掩码,如下所示:一旦创建了所有的注释,可以下载注释并将其保存为JSON格式。可以在这里查看图像和注释。
现在开始进行实际训练模型的有趣工作!首先克隆'Matterport Mask R-CNN'仓库——https://github.com/matterport/Mask_RCNN。接下来将加载图像和注释。
class CustomDataset(utils.Dataset):
def load_custom(self, dataset_dir, subset):
"""Load a subset of the Balloon dataset.
dataset_dir: Root directory of the dataset.
subset: Subset to load: train or val
"""
# Add classes. We have only one class to add.
self.add_class("damage", 1, "damage")
# Train or validation dataset?
assert subset in ["train", "val"]
dataset_dir = os.path.join(dataset_dir, subset)
# We mostly care about the x and y coordinates of each region
annotations1 = json.load(open(os.path.join(dataset_dir, "via_region_data.json")))
annotations = list(annotations1.values()) # don't need the dict keys
# The VIA tool saves images in the JSON even if they don't have any
# annotations. Skip unannotated images.
annotations = [a for a in annotations if a['regions']]
# Add images
for a in annotations:
# Get the x, y coordinaets of points of the polygons that make up
# the outline of each object instance. There are stores in the
# shape_attributes (see json format above)
polygons = [r['shape_attributes'] for r in a['regions'].values()]
# load_mask() needs the image size to convert polygons to masks.
image_path = os.path.join(dataset_dir, a['filename'])
image = skimage.io.imread(image_path)
height, width = image.shape[:2]
self.add_image(
"damage", ## for a single class just add the name here
image_id=a['filename'], # use file name as a unique image id
path=image_path,
width=width, height=height,
polygons=polygons)
使用了Matterport共享的balloon.py文件,并修改它以创建自定义代码,加载图像和注释并将它们添加到CustomDataset类中。查看完整的代码。请注意,此代码仅适用于一个类别。此外,可以使用这个笔记本来可视化给定图像上的掩码。
可以使用笔记本Inspect Custom Weights来检查模型权重。请在此笔记本中链接最后一个检查点。这个笔记本可以帮助执行一个基本检查,以确保权重和偏差正确分布。
使用笔记本inspect_custom_model在测试/验证集的图像上运行模型并查看模型预测。