当前位置:   article > 正文

[代码解析]Mask R-CNN介绍与实现(转)

mask r-cnn

文章来源

DFann

版权声明:如果你觉得写的还可以,可以考虑打赏一下。转载请联系。 https://blog.csdn.net/u011974639/article/details/78483779

这里写图片描述

简介

论文地址:Mask R-CNN
源代码:matterport - github

代码源于matterport的工作组,可以在github上fork它们组的工作。

软件必备

复现的Mask R-CNN是基于Python3,Keras,TensorFlow

  • Python 3.4+
  • TensorFlow 1.3+
  • Keras 2.0.8+
  • Jupyter Notebook
  • Numpy, skimage, scipy

建议配置一个高版本的Anaconda3+TensorFlow-GPU版本。



Mask R-CNN论文回顾

Mask R-CNN(简称MRCNN)是基于R-CNN系列、FPN、FCIS等工作之上的,MRCNN的思路很简洁:Faster R-CNN针对每个候选区域有两个输出:种类标签和bbox的偏移量。那么MRCNN就在Faster R-CNN的基础上通过增加一个分支进而再增加一个输出,即物体掩膜(object mask)。

先回顾一下Faster R-CNN, Faster R-CNN主要由两个阶段组成:区域候选网络(Region Proposal Network,RPN)和基础的Fast R-CNN模型。

  • RPN用于产生候选区域
    这里写图片描述

  • Fast R-CNN通过RoIPool层对每个候选区域提取特征,从而实现目标分类和bbox回归
    这里写图片描述

MRCNN采用和Faster R-CNN相同的两个阶段,具有相同的第一层(即RPN),第二阶段,除了预测种类和bbox回归,并且并行的对每个RoI预测了对应的二值掩膜(binary mask)。示意图如下:

这里写图片描述

这样做可以将整个任务简化为mulit-stage pipeline,解耦了多个子任务的关系,现阶段来看,这样做好处颇多。

主要工作

损失函数的定义

依旧采用的是多任务损失函数,针对每个每个RoI定义为

与Faster R-CNN的定义类似,这里主要看

掩膜分支针对每个RoI产生一个的输出,即K个分辨率为的二值的掩膜为分类物体的种类数目。依据预测类别分支预测的类型,只将第的二值掩膜输出记为
掩膜分支的损失计算如下示意图:

  1. mask branch 预测个种类的二值掩膜输出
  2. 依据种类预测分支(Faster R-CNN部分)预测结果:当前RoI的物体种类为
  3. 个二值掩膜输出就是该RoI的损失

这里写图片描述

对于预测的二值掩膜输出,我们对每个像素点应用sigmoid函数,整体损失定义为平均二值交叉损失熵。
引入预测个输出的机制,允许每个类都生成独立的掩膜,避免类间竞争。这样做解耦了掩膜和种类预测。不像是FCN的方法,在每个像素点上应用softmax函数,整体采用的多任务交叉熵,这样会导致类间竞争,最终导致分割效果差。

掩膜表示到RoIAlign层

在Faster R-CNN上预测物体标签或bbox偏移量是将feature map压缩到FC层最终输出vector,压缩的过程丢失了空间上(平面结构)的信息,而掩膜是对输入目标做空间上的编码,直接用卷积形式表示像素点之间的对应关系那是最好的了。

输出掩膜的操作是不需要压缩输出vector,所以可以使用FCN(Full Convolutional Network),不仅效率高,而且参数量还少。为了更好的表示出RoI输入和FCN输出的feature之间的像素对应关系,提出了RoIAlign层。

先回顾一下RoIPool层:

其核心思想是将不同大小的RoI输入到RoIPool层,RoIPool层将RoI量化成不同粒度的特征图(量化成一个一个bin),在此基础上使用池化操作提取特征。

下图是SPPNet内对RoI的操作,在Faster R-CNN中只使用了一种粒度的特征图:

这里写图片描述

平面示意图如下:

这里写图片描述

这里面存在一些问题,在上面量操作上,实际计算中是使用的是的量化的步长,是舍入操作(rounding)。这套量化舍入操作在提取特征时有着较好的鲁棒性(检测物体具有平移不变性等),但是这很不利于掩膜定位,有较大负面效果。

针对这个问题,提出了RoIAlign层:避免了对RoI边界或bin的量化操作,在扩展feature map时使用双线性插值算法。这里实现的架构要看FPN论文:

这里写图片描述

一开始的Faster R-CNN是基于最上层的特征映射做分割和预测的,这会丢失高分辨下的信息,直观的影响就是丢失小目标检测,对细节部分丢失不敏感。受到SSD的启发,FPN也使用了多层特征做预测。这里使用的top-down的架构,是将高层的特征反卷积带到低层的特征(即有了语义,也有精度),而在MRCNN论文里面说的双线性差值算法就是这里的top-down反卷积是用的插值算法。

总结

MRCNN有着优异的效果,除去了掩膜分支的作用,很大程度上是因为基础特征网络的增强,论文使用的是ResNeXt101+FPN的top-down组合,有着极强的特征学习能力,并且在实验中夹杂这多种工程调优技巧。

但是吧,MRCNN的缺点也很明显,需要大的计算能力并且速度慢,这离实际应用还是有很长的路,坐等大神们发力!



如何使用代码

项目的源代码地址为:github/Mask R-CNN

  1. 满足运行环境

    • Python 3.4+
    • TensorFlow 1.3+
    • Keras 2.0.8+
    • Jupyter Notebook
    • Numpy, skimage, scipy, Pillow(安装Anaconda3直接完事)
    • cv2
  2. 下载代码

    • linux环境下直接clone到本地

      git clone https://github.com/matterport/Mask_RCNN.git
      • 1
    • Windows下下载代码即可,地址在上面

  3. 下载模型在COCO数据集上预训练权重(mask_rcnn_coco.h5),下载地址releasses Page.

  4. 如果需要在COCO数据集上训练或测试,需要安装pycocotoolsclone下来,make生成对应的文件,拷贝下工程目录下即可(方法可参考下面repos内的README.md文件)。

  5. 如果使用COCO数据集,需要:

下面的代码分析运行环境都是jupyter。



代码分析-数据预处理

项目源代码:matterport - github

inspect_data.ipynb展示了准备训练数据的预处理步骤.

导包

导入的coco包需要从coco/PythonAPI上下载操作数据代码,并在本地使用make指令编译.将生成的pycocotools拷贝至工程的主目录下,即和该inspect_data.ipynb文件同一目录。

  1. import os
  2. import sys
  3. import itertools
  4. import math
  5. import logging
  6. import json
  7. import re
  8. import random
  9. from collections import OrderedDict
  10. import numpy as np
  11. import matplotlib
  12. import matplotlib.pyplot as plt
  13. import matplotlib.patches as patches
  14. import matplotlib.lines as lines
  15. from matplotlib.patches import Polygon
  16. import utils
  17. import visualize
  18. from visualize import display_images
  19. import model as modellib
  20. from model import log
  21. %matplotlib inline
  22. ROOT_DIR = os.getcwd()
  23. # 选择任意一个代码块
  24. # import shapes
  25. # config = shapes.ShapesConfig() # 使用代码创建数据集,后面会有介绍
  26. # MS COCO 数据集
  27. import coco
  28. config = coco.CocoConfig()
  29. COCO_DIR = "/root/模型复现/Mask_RCNN-master/coco" # COCO数据存放位置
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

加载数据集

COCO数据集的训练集内有82081张图片,共81类。

  1. # 这里使用的是COCO
  2. if config.NAME == 'shapes':
  3. dataset = shapes.ShapesDataset()
  4. dataset.load_shapes(500, config.IMAGE_SHAPE[0], config.IMAGE_SHAPE[1])
  5. elif config.NAME == "coco":
  6. dataset = coco.CocoDataset()
  7. dataset.load_coco(COCO_DIR, "train")
  8. # Must call before using the dataset
  9. dataset.prepare()
  10. print("Image Count: {}".format(len(dataset.image_ids)))
  11. print("Class Count: {}".format(dataset.num_classes))
  12. for i, info in enumerate(dataset.class_info):
  13. print("{:3}. {:50}".format(i, info['name']))
  14. >>>
  15. >>>
  16. loading annotations into memory...
  17. Done (t=7.68s)
  18. creating index...
  19. index created!
  20. Image Count: 82081
  21. Class Count: 81
  22. 0. BG
  23. 1. person
  24. 2. bicycle
  25. ...
  26. 77. scissors
  27. 78. teddy bear
  28. 79. hair drier
  29. 80. toothbrush
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

随机找几张照片看看:

  1. # 加载和展示随机几张照片和对应的mask
  2. image_ids = np.random.choice(dataset.image_ids, 4)
  3. for image_id in image_ids:
  4. image = dataset.load_image(image_id)
  5. mask, class_ids = dataset.load_mask(image_id)
  6. visualize.display_top_masks(image, mask, class_ids, dataset.class_names)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

Bounding Boxes(bbox)

这里我们不使用数据集本身提供的bbox坐标数据,取而代之的是通过mask计算出bbox,这样可以在不同的数据集下对bbox使用相同的处理方法。因为我们是从mask上计算bbox,相比与从图片计算bbox转换来说,更便于放缩,旋转,裁剪图像。

  1. # Load random image and mask.
  2. image_id = random.choice(dataset.image_ids)
  3. image = dataset.load_image(image_id)
  4. mask, class_ids = dataset.load_mask(image_id)
  5. # Compute Bounding box
  6. bbox = utils.extract_bboxes(mask)
  7. # Display image and additional stats
  8. print("image_id ", image_id, dataset.image_reference(image_id))
  9. log("image", image)
  10. log("mask", mask)
  11. log("class_ids", class_ids)
  12. log("bbox", bbox)
  13. # Display image and instances
  14. visualize.display_instances(image, bbox, mask, class_ids, dataset.class_names)
  15. >>>
  16. >>>
  17. image_id 41194 http://cocodataset.org/#explore?id=190360
  18. image shape: (428, 640, 3) min: 0.00000 max: 255.00000
  19. mask shape: (428, 640, 5) min: 0.00000 max: 1.00000
  20. class_ids shape: (5,) min: 1.00000 max: 59.00000
  21. bbox shape: (5, 4) min: 1.00000 max: 640.00000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

这里写图片描述

调整图片大小

因为训练时是批量处理的,每次batch要处理多张图片,模型需要一个固定的输入大小。故将训练集的图片放缩到一个固定的大小(1024×1024),放缩的过程要保持不变的宽高比,如果照片本身不是正方形,那边就在边缘填充0.(这在R-CNN论文里面论证过)

需要注意的是:原图片做了放缩,对应的mask也需要放缩,因为我们的bbox是依据mask计算出来的,这样省了修改程序了~

  1. # Load random image and mask.
  2. image_id = np.random.choice(dataset.image_ids, 1)[0]
  3. image = dataset.load_image(image_id)
  4. mask, class_ids = dataset.load_mask(image_id)
  5. original_shape = image.shape
  6. # 调整到固定大小
  7. image, window, scale, padding = utils.resize_image(
  8. image,
  9. min_dim=config.IMAGE_MIN_DIM,
  10. max_dim=config.IMAGE_MAX_DIM,
  11. padding=config.IMAGE_PADDING)
  12. mask = utils.resize_mask(mask, scale, padding) # mask也要放缩
  13. # Compute Bounding box
  14. bbox = utils.extract_bboxes(mask)
  15. # Display image and additional stats
  16. print("image_id: ", image_id, dataset.image_reference(image_id))
  17. print("Original shape: ", original_shape)
  18. log("image", image)
  19. log("mask", mask)
  20. log("class_ids", class_ids)
  21. log("bbox", bbox)
  22. # Display image and instances
  23. visualize.display_instances(image, bbox, mask, class_ids, dataset.class_names)
  24. >>>
  25. >>>
  26. image_id: 6104 http://cocodataset.org/#explore?id=139889
  27. Original shape: (426, 640, 3)
  28. image shape: (1024, 1024, 3) min: 0.00000 max: 255.00000
  29. mask shape: (1024, 1024, 2) min: 0.00000 max: 1.00000
  30. class_ids shape: (2,) min: 24.00000 max: 24.00000
  31. bbox shape: (2, 4) min: 169.00000 max: 917.00000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

原图片从(426, 640, 3)放大到(1024, 1024, 3),图片的上下两端都填充了0(黑色的部分):

这里写图片描述

Mini Mask

训练高分辨率的图片时,表示每个目标的二值mask也会非常大。例如,训练一张1024×1024的图片,其目标物体对应的mask需要1MB的内存(用boolean变量表示单点),如果1张图片有100个目标物体就需要100MB。讲道理,如果是五颜六色就算了,但实际上表示mask的图像矩阵上大部分都是0,很浪费空间。

为了节省空间同时提升训练速度,我们优化mask的表示方式,不直接存储那么多0,而是通过存储有值坐标的相对位置来压缩表示数据的内存,原理和压缩算法差类似。

  • 我们存储在对象边界框内(bbox内)的mask像素,而不是存储整张图片的mask像素,大多数物体相对比于整张图片是较小的,节省存储空间是通过少存储目标周围的0实现的。
  • 将mask调整到小尺寸56×56,对于大尺寸的物体会丢失一些精度,但是大多数对象的注解并不是很准确,所以大多数情况下这些损失是可以忽略的。(可以在config类中设置mini mask的size。)

说白了就是在处理数据的时候,我们先利用标注的mask信息计算出对应的bbox框,而后利用计算的bbox框反过来改变mask的表示方法,目的就是操作规范化,同时降低存储空间和计算复杂度。

  1. image_id = np.random.choice(dataset.image_ids, 1)[0]
  2. # 使用load_image_gt方法获取bbox和mask
  3. image, image_meta, bbox, mask = modellib.load_image_gt(
  4. dataset, config, image_id, use_mini_mask=False)
  5. log("image", image)
  6. log("image_meta", image_meta)
  7. log("bbox", bbox)
  8. log("mask", mask)
  9. display_images([image]+[mask[:,:,i] for i in range(min(mask.shape[-1], 7))])
  10. >>>
  11. >>>
  12. image shape: (1024, 1024, 3) min: 0.00000 max: 252.00000
  13. image_meta shape: (89,) min: 0.00000 max: 66849.00000
  14. bbox shape: (1, 5) min: 62.00000 max: 987.00000
  15. mask shape: (1024, 1024, 1) min: 0.00000 max: 1.00000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

随机选取一张图片,可以看到图片目标相对与图片本身较小:

这里写图片描述

visualize.display_instances(image, bbox[:,:4], mask, bbox[:,4], dataset.class_names)
  • 1

这里写图片描述

使用load_image_gt方法,传入use_mini_mask=True实现mini mask操作:

  1. # load_image_gt方法集成了mini_mask的操作
  2. image, image_meta, bbox, mask = modellib.load_image_gt(
  3. dataset, config, image_id, augment=True, use_mini_mask=True)
  4. log("mask", mask)
  5. display_images([image]+[mask[:,:,i] for i in range(min(mask.shape[-1], 7))])
  6. >>>
  7. >>>
  8. mask shape: (56, 56, 1) min: 0.00000 max: 1.00000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这里写图片描述

这里为了展现效果,将mini_mask表示方法通过expand_mask方法扩大到大图像下的mask,再绘制试试:

  1. mask = utils.expand_mask(bbox, mask, image.shape)
  2. visualize.display_instances(image, bbox[:,:4], mask, bbox[:,4], dataset.class_names)
  • 1
  • 2

这里写图片描述
可以看到边界是锯齿状,这也是压缩的副作用,总体来说效果还可以~

Anchors

Anchors是Faster R-CNN内提出的方法
模型在运行过程中有多层feature map,同时也会有非常多的Anchors,处理好Anchors的顺序非常重要。例如使用anchors的顺序要匹配卷积处理的顺序等规则。

对于FPN网络,anchor的顺序要与卷积层的输出相匹配:

  • 先按金字塔等级排序,第一层的所有anchors,第二层所有anchors,etc..通过按层次可以很容易分开所有的anchors
  • 对于每个层,通过feature map处理序列来排列anchors,通常,一个卷积层处理一个feature map 是从左上角开始,向右一行一行来整
  • 对于feature map的每个cell,可为不同比例的Anchors采用随意顺序,这里我们将采用不同比例的顺序当参数传递给相应的函数

Anchor步长:在FPN架构下,前几层的feature map是高分辨率的。例如,如果输入是1024×1024,那么第一层的feature map大小为256×256,这会产生约200K的anchors(2562563),这些anchor都是32×32,相对于图片像素的步长为4(1024/256=4),这里面有很多重叠,如果我们能够为feature map的每个点生成独有的anchor,就会显著的降低负载,如果设置anchor的步长为2,那么anchor的数量就会下降4倍。

这里我们使用的strides为2,这和论文不一样,在Config类中,我们配置了3中比例([0.5, 1, 2])的anchors,以第一层feature map举例,其大小为256×256,故有

  1. # 生成 Anchors
  2. anchors = utils.generate_pyramid_anchors(config.RPN_ANCHOR_SCALES,
  3. config.RPN_ANCHOR_RATIOS,
  4. config.BACKBONE_SHAPES,
  5. config.BACKBONE_STRIDES,
  6. config.RPN_ANCHOR_STRIDE)
  7. # Print summary of anchors
  8. print("Scales: ", config.RPN_ANCHOR_SCALES)
  9. print("ratios: {}, \nAnchors_per_cell:{}".format(config.RPN_ANCHOR_RATIOS , len(config.RPN_ANCHOR_RATIOS)))
  10. print("backbone_shapes: ",config.BACKBONE_SHAPES)
  11. print("backbone_strides: ",config.BACKBONE_STRIDES)
  12. print("rpn_anchor_stride: ",config.RPN_ANCHOR_STRIDE)
  13. num_levels = len(config.BACKBONE_SHAPES)
  14. print("Count: ", anchors.shape[0])
  15. print("Levels: ", num_levels)
  16. anchors_per_level = []
  17. for l in range(num_levels):
  18. num_cells = config.BACKBONE_SHAPES[l][0] * config.BACKBONE_SHAPES[l][1]
  19. anchors_per_level.append(anchors_per_cell * num_cells // config.RPN_ANCHOR_STRIDE**2)
  20. print("Anchors in Level {}: {}".format(l, anchors_per_level[l]))
  21. >>>
  22. >>>
  23. Scales: (32, 64, 128, 256, 512)
  24. ratios: [0.5, 1, 2],
  25. anchors_per_cell:3
  26. backbone_shapes: [[256 256] [128 128] [ 64 64] [ 32 32] [ 16 16]]
  27. backbone_strides: [4, 8, 16, 32, 64]
  28. rpn_anchor_stride: 2
  29. Count: 65472
  30. Levels: 5
  31. Anchors in Level 0: 49152
  32. Anchors in Level 1: 12288
  33. Anchors in Level 2: 3072
  34. Anchors in Level 3: 768
  35. Anchors in Level 4: 192
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

看看位置图片中心点cell的不同层anchor表示:

  1. # Load and draw random image
  2. image_id = np.random.choice(dataset.image_ids, 1)[0]
  3. image, image_meta, _, _ = modellib.load_image_gt(dataset, config, image_id)
  4. fig, ax = plt.subplots(1, figsize=(10, 10))
  5. ax.imshow(image)
  6. levels = len(config.BACKBONE_SHAPES) # 共有5层 15个anchors
  7. for level in range(levels):
  8. colors = visualize.random_colors(levels)
  9. # Compute the index of the anchors at the center of the image
  10. level_start = sum(anchors_per_level[:level]) # sum of anchors of previous levels
  11. level_anchors = anchors[level_start:level_start+anchors_per_level[level]]
  12. print("Level {}. Anchors: {:6} Feature map Shape: {}".format(level, level_anchors.shape[0],
  13. config.BACKBONE_SHAPES[level]))
  14. center_cell = config.BACKBONE_SHAPES[level] // 2
  15. center_cell_index = (center_cell[0] * config.BACKBONE_SHAPES[level][1] + center_cell[1])
  16. level_center = center_cell_index * anchors_per_cell
  17. center_anchor = anchors_per_cell * (
  18. (center_cell[0] * config.BACKBONE_SHAPES[level][1] / config.RPN_ANCHOR_STRIDE**2) \
  19. + center_cell[1] / config.RPN_ANCHOR_STRIDE)
  20. level_center = int(center_anchor)
  21. # Draw anchors. Brightness show the order in the array, dark to bright.
  22. for i, rect in enumerate(level_anchors[level_center:level_center+anchors_per_cell]):
  23. y1, x1, y2, x2 = rect
  24. p = patches.Rectangle((x1, y1), x2-x1, y2-y1, linewidth=2, facecolor='none',
  25. edgecolor=(i+1)*np.array(colors[level]) / anchors_per_cell)
  26. ax.add_patch(p)
  27. >>>
  28. >>>
  29. Level 0. Anchors: 49152 Feature map Shape: [256 256]
  30. Level 1. Anchors: 12288 Feature map Shape: [128 128]
  31. Level 2. Anchors: 3072 Feature map Shape: [64 64]
  32. Level 3. Anchors: 768 Feature map Shape: [32 32]
  33. Level 4. Anchors: 192 Feature map Shape: [16 16]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

这里写图片描述

代码分析-在自己的数据集上训练模型

项目源代码:matterport - github

train_shapes.ipynb展示了如何在自己的数据集上训练Mask R-CNN.

如果想在你的个人训练集上训练模型,需要分别创建两个子类继承下面两个父类:

  • Config类,该类包含了默认的配置,子类继承该类在针对数据集定制配置。
  • Dataset类,该类提供了一套api,新的数据集继承该类,同时覆写相关方法即可,这样可以在不修改模型代码的情况下,使用多种数据集(包括同时使用)。

无论是Dataset还是Config都是基类,使用是要继承并做相关定制,使用案例可参考下面的demo。

导包

因为demo中使用的数据集是使用opencv创建出来的,故不需要另外在下载数据集了。为了保证模型运行正常,此demo依旧需要在GPU上运行。

  1. import os
  2. import sys
  3. import random
  4. import math
  5. import re
  6. import time
  7. import numpy as np
  8. import cv2
  9. import matplotlib
  10. import matplotlib.pyplot as plt
  11. from config import Config
  12. import utils
  13. import model as modellib
  14. import visualize
  15. from model import log
  16. %matplotlib inline
  17. ROOT_DIR = os.getcwd() # Root directory of the project
  18. MODEL_DIR = os.path.join(ROOT_DIR, "logs") # Directory to save logs and trained model
  19. COCO_MODEL_PATH = os.path.join(ROOT_DIR, "mask_rcnn_coco.h5") # Path to COCO trained weights
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

构建个人数据集

这里直接使用opencv创建一个数据集,数据集是由画布和简单的几何形状(三角形,正方形,圆形)组成。

构造的数据集需要继承utils.Dataset类,使用load_shapes()方法向外提供加载数据的方法,并需要重写下面的方法:

  • load_image()
  • load_mask()
  • image_reference()

构造数据集的代码:

  1. class ShapesDataset(utils.Dataset):
  2. """
  3. 生成一个数据集,数据集由简单的(三角形,正方形,圆形)放置在空白画布的图片组成。
  4. """
  5. def load_shapes(self, count, height, width):
  6. """
  7. 产生对应数目的固定大小图片
  8. count: 生成数据的数量
  9. height, width: 产生图片的大小
  10. """
  11. # 添加种类信息
  12. self.add_class("shapes", 1, "square")
  13. self.add_class("shapes", 2, "circle")
  14. self.add_class("shapes", 3, "triangle")
  15. # 生成随机规格形状,每张图片依据image_id指定
  16. for i in range(count):
  17. bg_color, shapes = self.random_image(height, width)
  18. self.add_image("shapes", image_id=i, path=None,
  19. width=width, height=height,
  20. bg_color=bg_color, shapes=shapes)
  21. def load_image(self, image_id):
  22. """
  23. 依据给定的iamge_id产生对应图片。
  24. 通常这个函数是读取文件的,这里我们是依据image_id到image_info里面查找信息,再生成图片
  25. """
  26. info = self.image_info[image_id]
  27. bg_color = np.array(info['bg_color']).reshape([1, 1, 3])
  28. image = np.ones([info['height'], info['width'], 3], dtype=np.uint8)
  29. image = image * bg_color.astype(np.uint8)
  30. for shape, color, dims in info['shapes']:
  31. image = self.draw_shape(image, shape, dims, color)
  32. return image
  33. def image_reference(self, image_id):
  34. """Return the shapes data of the image."""
  35. info = self.image_info[image_id]
  36. if info["source"] == "shapes":
  37. return info["shapes"]
  38. else:
  39. super(self.__class__).image_reference(self, image_id)
  40. def load_mask(self, image_id):
  41. """依据给定的image_id产生相应的规格形状的掩膜"""
  42. info = self.image_info[image_id]
  43. shapes = info['shapes']
  44. count = len(shapes)
  45. mask = np.zeros([info['height'], info['width'], count], dtype=np.uint8)
  46. for i, (shape, _, dims) in enumerate(info['shapes']):
  47. mask[:, :, i:i+1] = self.draw_shape(mask[:, :, i:i+1].copy(),
  48. shape, dims, 1)
  49. # Handle occlusions
  50. occlusion = np.logical_not(mask[:, :, -1]).astype(np.uint8)
  51. for i in range(count-2, -1, -1):
  52. mask[:, :, i] = mask[:, :, i] * occlusion
  53. occlusion = np.logical_and(occlusion, np.logical_not(mask[:, :, i]))
  54. # Map class names to class IDs.
  55. class_ids = np.array([self.class_names.index(s[0]) for s in shapes])
  56. return mask, class_ids.astype(np.int32)
  57. def draw_shape(self, image, shape, dims, color):
  58. """绘制给定的形状."""
  59. # Get the center x, y and the size s
  60. x, y, s = dims
  61. if shape == 'square':
  62. image = cv2.rectangle(image, (x-s, y-s), (x+s, y+s), color, -1)
  63. elif shape == "circle":
  64. image = cv2.circle(image, (x, y), s, color, -1)
  65. elif shape == "triangle":
  66. points = np.array([[(x, y-s),
  67. (x-s/math.sin(math.radians(60)), y+s),
  68. (x+s/math.sin(math.radians(60)), y+s),
  69. ]], dtype=np.int32)
  70. image = cv2.fillPoly(image, points, color)
  71. return image
  72. def random_shape(self, height, width):
  73. """
  74. 依据给定的长宽边界生成随机形状
  75. 返回一个有三个值的元组:
  76. * shape: 形状名称(square, circle, ...)
  77. * color: 形状颜色(a tuple of 3 values, RGB.)
  78. * dimensions: 随机形状的中心位置和大小(center_x,center_y,size)
  79. """
  80. # Shape
  81. shape = random.choice(["square", "circle", "triangle"])
  82. # Color
  83. color = tuple([random.randint(0, 255) for _ in range(3)])
  84. # Center x, y
  85. buffer = 20
  86. y = random.randint(buffer, height - buffer - 1)
  87. x = random.randint(buffer, width - buffer - 1)
  88. # Size
  89. s = random.randint(buffer, height//4)
  90. return shape, color, (x, y, s)
  91. def random_image(self, height, width):
  92. """
  93. 产生有多种形状的随机规格的图片
  94. 返回背景色 和 可以用于绘制图片的形状规格列表
  95. """
  96. # 随机生成三个通道颜色
  97. bg_color = np.array([random.randint(0, 255) for _ in range(3)])
  98. # 生成一些随机形状并记录它们的bbox
  99. shapes = []
  100. boxes = []
  101. N = random.randint(1, 4)
  102. for _ in range(N):
  103. shape, color, dims = self.random_shape(height, width)
  104. shapes.append((shape, color, dims))
  105. x, y, s = dims
  106. boxes.append([y-s, x-s, y+s, x+s])
  107. # 使用非极大值抑制避免各种形状之间覆盖 阈值为:0.3
  108. keep_ixs = utils.non_max_suppression(np.array(boxes), np.arange(N), 0.3)
  109. shapes = [s for i, s in enumerate(shapes) if i in keep_ixs]
  110. return bg_color, shapes
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120

用上面的数据类构造一组数据,看看:

  1. # 构建训练集,大小为500
  2. dataset_train = ShapesDataset()
  3. dataset_train.load_shapes(500, config.IMAGE_SHAPE[0], config.IMAGE_SHAPE[1])
  4. dataset_train.prepare()
  5. # 构建验证集,大小为50
  6. dataset_val = ShapesDataset()
  7. dataset_val.load_shapes(50, config.IMAGE_SHAPE[0], config.IMAGE_SHAPE[1])
  8. dataset_val.prepare()
  9. # 随机选取4个样本
  10. image_ids = np.random.choice(dataset_train.image_ids, 4)
  11. for image_id in image_ids:
  12. image = dataset_train.load_image(image_id)
  13. mask, class_ids = dataset_train.load_mask(image_id)
  14. visualize.display_top_masks(image, mask, class_ids, dataset_train.class_names)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

为上面构造的数据集配置一个对应的ShapesConfig类,该类的作用统一模型配置参数。该类需要继承Config类:

  1. class ShapesConfig(Config):
  2. """
  3. 为数据集添加训练配置
  4. 继承基类Config
  5. """
  6. NAME = "shapes" # 该配置类的识别符
  7. #Batch size is 8 (GPUs * images/GPU).
  8. GPU_COUNT = 1 # GPU数量
  9. IMAGES_PER_GPU = 8 # 单GPU上处理图片数(这里我们构造的数据集图片小,可以多处理几张)
  10. # 分类种类数目 (包括背景)
  11. NUM_CLASSES = 1 + 3 # background + 3 shapes
  12. # 使用小图片可以更快的训练
  13. IMAGE_MIN_DIM = 128 # 图片的小边长
  14. IMAGE_MAX_DIM = 128 # 图片的大边长
  15. # 使用小的anchors,因为数据图片和目标都小
  16. RPN_ANCHOR_SCALES = (8, 16, 32, 64, 128) # anchor side in pixels
  17. # 减少训练每张图片上的ROIs,因为图片很小且目标很少,
  18. # Aim to allow ROI sampling to pick 33% positive ROIs.
  19. TRAIN_ROIS_PER_IMAGE = 32
  20. STEPS_PER_EPOCH = 100 # 因为数据简单,使用小的epoch
  21. VALIDATION_STPES = 5 # 因为epoch较小,使用小的交叉验证步数
  22. config = ShapesConfig()
  23. config.print()
  24. >>>
  25. >>>
  26. Configurations:
  27. BACKBONE_SHAPES [[32 32]
  28. [16 16]
  29. [ 8 8]
  30. [ 4 4]
  31. [ 2 2]]
  32. BACKBONE_STRIDES [4, 8, 16, 32, 64]
  33. BATCH_SIZE 8
  34. BBOX_STD_DEV [ 0.1 0.1 0.2 0.2]
  35. DETECTION_MAX_INSTANCES 100
  36. DETECTION_MIN_CONFIDENCE 0.7
  37. DETECTION_NMS_THRESHOLD 0.3
  38. GPU_COUNT 1
  39. IMAGES_PER_GPU 8
  40. IMAGE_MAX_DIM 128
  41. IMAGE_MIN_DIM 128
  42. IMAGE_PADDING True
  43. IMAGE_SHAPE [128 128 3]
  44. LEARNING_MOMENTUM 0.9
  45. LEARNING_RATE 0.002
  46. MASK_POOL_SIZE 14
  47. MASK_SHAPE [28, 28]
  48. MAX_GT_INSTANCES 100
  49. MEAN_PIXEL [ 123.7 116.8 103.9]
  50. MINI_MASK_SHAPE (56, 56)
  51. NAME shapes
  52. NUM_CLASSES 4
  53. POOL_SIZE 7
  54. POST_NMS_ROIS_INFERENCE 1000
  55. POST_NMS_ROIS_TRAINING 2000
  56. ROI_POSITIVE_RATIO 0.33
  57. RPN_ANCHOR_RATIOS [0.5, 1, 2]
  58. RPN_ANCHOR_SCALES (8, 16, 32, 64, 128)
  59. RPN_ANCHOR_STRIDE 2
  60. RPN_BBOX_STD_DEV [ 0.1 0.1 0.2 0.2]
  61. RPN_TRAIN_ANCHORS_PER_IMAGE 256
  62. STEPS_PER_EPOCH 100
  63. TRAIN_ROIS_PER_IMAGE 32
  64. USE_MINI_MASK True
  65. USE_RPN_ROIS True
  66. VALIDATION_STPES 5
  67. WEIGHT_DECAY 0.0001
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

加载模型并训练

上面配置好了个人数据集和对应的Config了,下面加载预训练模型:

  1. # 模型有两种模式: training inference
  2. # 创建模型并设置training模式
  3. model = modellib.MaskRCNN(mode="training", config=config,
  4. model_dir=MODEL_DIR)
  5. # 选择权重类型,这里我们的预训练权重是COCO的
  6. init_with = "coco" # imagenet, coco, or last
  7. if init_with == "imagenet":
  8. model.load_weights(model.get_imagenet_weights(), by_name=True)
  9. elif init_with == "coco":
  10. # 载入在MS COCO上的预训练模型,跳过不一样的分类数目层
  11. model.load_weights(COCO_MODEL_PATH, by_name=True,
  12. exclude=["mrcnn_class_logits", "mrcnn_bbox_fc",
  13. "mrcnn_bbox", "mrcnn_mask"])
  14. elif init_with == "last":
  15. # 载入你最后训练的模型,继续训练
  16. model.load_weights(model.find_last()[1], by_name=True)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

训练模型

我们前面基础层是加载预训练模型的,在预训练模型的基础上再训练,分为两步:

  • 只训练head部分,为了不破坏基础层的提取能力,我们冻结所有backbone layers,只训练随机初始化的层,为了达成只训练head部分,训练时需要向train()方法传入layers='heads'参数。
  • Fine-tune所有层,上面训练了一会head部分,为了更好的适配新的数据集,需要fine-tune,使用layers='all'参数。

这两个步骤也是做迁移学习的必备套路了~

1. 训练head部分

  1. # 通过传入参数layers="heads" 冻结处理head部分的所有层。可以通过传入一个正则表达式选择要训练的层
  2. model.train(dataset_train, dataset_val,
  3. learning_rate=config.LEARNING_RATE,
  4. epochs=1,
  5. layers='heads')
  6. >>>
  7. >>>
  8. Starting at epoch 0. LR=0.002
  9. Checkpoint Path: /root/Mask_RCNNmaster/logs/shapes20171103T2047/mask_rcnn_shapes_{epoch:04d}.h5
  10. Selecting layers to train
  11. fpn_c5p5 (Conv2D)
  12. fpn_c4p4 (Conv2D)
  13. fpn_c3p3 (Conv2D)
  14. fpn_c2p2 (Conv2D)
  15. fpn_p5 (Conv2D)
  16. fpn_p2 (Conv2D)
  17. fpn_p3 (Conv2D)
  18. fpn_p4 (Conv2D)
  19. In model: rpn_model
  20. rpn_conv_shared (Conv2D)
  21. rpn_class_raw (Conv2D)
  22. rpn_bbox_pred (Conv2D)
  23. mrcnn_mask_conv1 (TimeDistributed)
  24. ...
  25. mrcnn_mask_conv4 (TimeDistributed)
  26. mrcnn_mask_bn4 (TimeDistributed)
  27. mrcnn_bbox_fc (TimeDistributed)
  28. mrcnn_mask_deconv (TimeDistributed)
  29. mrcnn_class_logits (TimeDistributed)
  30. mrcnn_mask (TimeDistributed)
  31. Epoch 1/1
  32. 100/100 [==============================] - 37s 371ms/step - loss: 2.5472 - rpn_class_loss: 0.0244 - rpn_bbox_loss: 1.1118 - mrcnn_class_loss: 0.3692 - mrcnn_bbox_loss: 0.3783 - mrcnn_mask_loss: 0.3223 - val_loss: 1.7634 - val_rpn_class_loss: 0.0143 - val_rpn_bbox_loss: 0.9989 - val_mrcnn_class_loss: 0.1673 - val_mrcnn_bbox_loss: 0.0857 - val_mrcnn_mask_loss: 0.1559
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

2. Fine tune 所有层

  1. # 通过传入参数layers="all"所有层
  2. model.train(dataset_train, dataset_val,
  3. learning_rate=config.LEARNING_RATE / 10,
  4. epochs=2,
  5. layers="all")
  6. >>>
  7. >>>
  8. Starting at epoch 1. LR=0.0002
  9. Checkpoint Path: /root/Mask_RCNN-master/logs/shapes20171103T2047/mask_rcnn_shapes_{epoch:04d}.h5
  10. Selecting layers to train
  11. conv1 (Conv2D)
  12. bn_conv1 (BatchNorm)
  13. res2a_branch2a (Conv2D)
  14. bn2a_branch2a (BatchNorm)
  15. res2a_branch2b (Conv2D)
  16. ...
  17. ...
  18. res5c_branch2c (Conv2D)
  19. bn5c_branch2c (BatchNorm)
  20. fpn_c5p5 (Conv2D)
  21. fpn_c4p4 (Conv2D)
  22. fpn_c3p3 (Conv2D)
  23. fpn_c2p2 (Conv2D)
  24. fpn_p5 (Conv2D)
  25. fpn_p2 (Conv2D)
  26. fpn_p3 (Conv2D)
  27. fpn_p4 (Conv2D)
  28. In model: rpn_model
  29. rpn_conv_shared (Conv2D)
  30. rpn_class_raw (Conv2D)
  31. rpn_bbox_pred (Conv2D)
  32. mrcnn_mask_conv1 (TimeDistributed)
  33. mrcnn_mask_bn1 (TimeDistributed)
  34. mrcnn_mask_conv2 (TimeDistributed)
  35. mrcnn_class_conv1 (TimeDistributed)
  36. mrcnn_mask_bn2 (TimeDistributed)
  37. mrcnn_class_bn1 (TimeDistributed)
  38. mrcnn_mask_conv3 (TimeDistributed)
  39. mrcnn_mask_bn3 (TimeDistributed)
  40. mrcnn_class_conv2 (TimeDistributed)
  41. mrcnn_class_bn2 (TimeDistributed)
  42. mrcnn_mask_conv4 (TimeDistributed)
  43. mrcnn_mask_bn4 (TimeDistributed)
  44. mrcnn_bbox_fc (TimeDistributed)
  45. mrcnn_mask_deconv (TimeDistributed)
  46. mrcnn_class_logits (TimeDistributed)
  47. mrcnn_mask (TimeDistributed)
  48. Epoch 2/2
  49. 100/100 [==============================] - 38s 381ms/step - loss: 11.4351 - rpn_class_loss: 0.0190 - rpn_bbox_loss: 0.9108 - mrcnn_class_loss: 0.2085 - mrcnn_bbox_loss: 0.1606 - mrcnn_mask_loss: 0.2198 - val_loss: 11.2957 - val_rpn_class_loss: 0.0173 - val_rpn_bbox_loss: 0.8740 - val_mrcnn_class_loss: 0.1590 - val_mrcnn_bbox_loss: 0.0997 - val_mrcnn_mask_loss: 0.2296
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

模型预测

模型预测也需要配置一个类InferenceConfig类,大部分配置和train相同:

  1. class InferenceConfig(ShapesConfig):
  2. GPU_COUNT = 1
  3. IMAGES_PER_GPU = 1
  4. inference_config = InferenceConfig()
  5. # 重新创建模型设置为inference模式
  6. model = modellib.MaskRCNN(mode="inference",
  7. config=inference_config,
  8. model_dir=MODEL_DIR)
  9. # 获取保存的权重,或者手动指定目录位置
  10. # model_path = os.path.join(ROOT_DIR, ".h5 file name here")
  11. model_path = model.find_last()[1]
  12. # 加载权重
  13. assert model_path != "", "Provide path to trained weights"
  14. print("Loading weights from ", model_path)
  15. model.load_weights(model_path, by_name=True)
  16. # 测试随机图片
  17. image_id = random.choice(dataset_val.image_ids)
  18. original_image, image_meta, gt_bbox, gt_mask =\
  19. modellib.load_image_gt(dataset_val, inference_config,
  20. image_id, use_mini_mask=False)
  21. log("original_image", original_image)
  22. log("image_meta", image_meta)
  23. log("gt_bbox", gt_bbox)
  24. log("gt_mask", gt_mask)
  25. visualize.display_instances(original_image, gt_bbox[:,:4], gt_mask, gt_bbox[:,4],
  26. dataset_train.class_names, figsize=(8, 8))
  27. >>>
  28. >>>
  29. original_image shape: (128, 128, 3) min: 18.00000 max: 231.00000
  30. image_meta shape: (12,) min: 0.00000 max: 128.00000
  31. gt_bbox shape: (2, 5) min: 1.00000 max: 115.00000
  32. gt_mask shape: (128, 128, 2) min: 0.00000 max: 1.00000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

随机几张验证集图片看看:

这里写图片描述

使用模型预测:

  1. def get_ax(rows=1, cols=1, size=8):
  2. """返回Matplotlib Axes数组用于可视化.提供中心点控制图形大小"""
  3. _, ax = plt.subplots(rows, cols, figsize=(size*cols, size*rows))
  4. return ax
  5. results = model.detect([original_image], verbose=1) # 预测
  6. r = results[0]
  7. visualize.display_instances(original_image, r['rois'], r['masks'], r['class_ids'],
  8. dataset_val.class_names, r['scores'], ax=get_ax())
  9. >>>
  10. >>>
  11. Processing 1 images
  12. image shape: (128, 128, 3) min: 18.00000 max: 231.00000
  13. molded_images shape: (1, 128, 128, 3) min: -98.80000 max: 127.10000
  14. image_metas shape: (1, 12) min: 0.00000 max: 128.00000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

这里写图片描述

计算ap值:

  1. # Compute VOC-Style mAP @ IoU=0.5
  2. # Running on 10 images. Increase for better accuracy.
  3. image_ids = np.random.choice(dataset_val.image_ids, 10)
  4. APs = []
  5. for image_id in image_ids:
  6. # 加载数据
  7. image, image_meta, gt_bbox, gt_mask =\
  8. modellib.load_image_gt(dataset_val, inference_config,
  9. image_id, use_mini_mask=False)
  10. molded_images = np.expand_dims(modellib.mold_image(image, inference_config), 0)
  11. # Run object detection
  12. results = model.detect([image], verbose=0)
  13. r = results[0]
  14. # Compute AP
  15. AP, precisions, recalls, overlaps =\
  16. utils.compute_ap(gt_bbox[:,:4], gt_bbox[:,4],
  17. r["rois"], r["class_ids"], r["scores"])
  18. APs.append(AP)
  19. print("mAP: ", np.mean(APs))
  20. >>>
  21. >>>
  22. mAP: 0.9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24


代码分析-Mask R-CNN 模型分析

测试,调试和评估Mask R-CNN模型。

导包

这里会用到自定义的COCO子数据集,5K的minival和35K的validation-minus-minival。(这两个数据集下载比较慢,没有贴原地址,而是我的CSDN地址,分不够下载的可以私信我~)

  1. import os
  2. import sys
  3. import random
  4. import math
  5. import re
  6. import time
  7. import numpy as np
  8. import scipy.misc
  9. import tensorflow as tf
  10. import matplotlib
  11. import matplotlib.pyplot as plt
  12. import matplotlib.patches as patches
  13. import utils
  14. import visualize
  15. from visualize import display_images
  16. import model as modellib
  17. from model import log
  18. %matplotlib inline
  19. ROOT_DIR = os.getcwd() # Root directory of the project
  20. MODEL_DIR = os.path.join(ROOT_DIR, "logs") # Directory to save logs and trained model
  21. COCO_MODEL_PATH = os.path.join(ROOT_DIR, "coco/mask_rcnn_coco.h5") # Path to trained weights file
  22. SHAPES_MODEL_PATH = os.path.join(ROOT_DIR, "log/shapes20171103T2047/mask_rcnn_shapes_0002.h5") # Path to Shapes trained weights
  23. # Shapes toy dataset
  24. # import shapes
  25. # config = shapes.ShapesConfig()
  26. # MS COCO Dataset
  27. import coco
  28. config = coco.CocoConfig()
  29. COCO_DIR = os.path.join(ROOT_DIR, "coco") # TODO: enter value here
  30. def get_ax(rows=1, cols=1, size=16):
  31. """控制绘图大小"""
  32. _, ax = plt.subplots(rows, cols, figsize=(size*cols, size*rows))
  33. return ax
  34. # 创建一个预测配置类InferenceConfig,用于测试预训练模型
  35. class InferenceConfig(config.__class__):
  36. # Run detection on one image at a time
  37. GPU_COUNT = 1
  38. IMAGES_PER_GPU = 1
  39. config = InferenceConfig()
  40. DEVICE = "/cpu:0" # /cpu:0 or /gpu:0
  41. TEST_MODE = "inference" # values: 'inference' or 'training'
  42. # 加载验证集
  43. if config.NAME == 'shapes':
  44. dataset = shapes.ShapesDataset()
  45. dataset.load_shapes(500, config.IMAGE_SHAPE[0], config.IMAGE_SHAPE[1])
  46. elif config.NAME == "coco":
  47. dataset = coco.CocoDataset()
  48. dataset.load_coco(COCO_DIR, "minival")
  49. # Must call before using the dataset
  50. dataset.prepare()
  51. # 创建模型并设置inference mode
  52. with tf.device(DEVICE):
  53. model = modellib.MaskRCNN(mode="inference", model_dir=MODEL_DIR,
  54. config=config)
  55. # Set weights file path
  56. if config.NAME == "shapes":
  57. weights_path = SHAPES_MODEL_PATH
  58. elif config.NAME == "coco":
  59. weights_path = COCO_MODEL_PATH
  60. # Or, uncomment to load the last model you trained
  61. # weights_path = model.find_last()[1]
  62. # Load weights
  63. print("Loading weights ", weights_path)
  64. model.load_weights(weights_path, by_name=True)
  65. image_id = random.choice(dataset.image_ids)
  66. image, image_meta, gt_bbox, gt_mask =\
  67. modellib.load_image_gt(dataset, config, image_id, use_mini_mask=False)
  68. info = dataset.image_info[image_id]
  69. print("image ID: {}.{} ({}) {}".format(info["source"], info["id"], image_id,
  70. dataset.image_reference(image_id)))
  71. gt_class_id = gt_bbox[:, 4]
  72. # Run object detection
  73. results = model.detect([image], verbose=1)
  74. # Display results
  75. ax = get_ax(1)
  76. r = results[0]
  77. # visualize.display_instances(image, gt_bbox[:,:4], gt_mask, gt_bbox[:,4],
  78. # dataset.class_names, ax=ax[0], title="Ground Truth")
  79. visualize.display_instances(image, r['rois'], r['masks'], r['class_ids'],
  80. dataset.class_names, r['scores'], ax=ax,
  81. title="Predictions")
  82. log("gt_class_id", gt_class_id)
  83. log("gt_bbox", gt_bbox)
  84. log("gt_mask", gt_mask)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102

随机在数据集中选张照片看看:

这里写图片描述

区域候选网络(Region Proposal Network,RPN)

RPN网络的任务就是做目标区域推荐,从R-CNN中使用的Selective Search方法到Faster R-CNN中使用的Anchor方法,目的就是用更快的方法产生更好的RoI。

RPN在图像上创建大量的boxes(anchors),并在anchors上运行一个轻量级的二值分类器返回有目标/无目标的分数。具有高分数的anchors(positive anchors,正样本)会被传到下一阶段用于分类。

通常,positive anchors也不会完全覆盖目标,所以RPN在对anchor打分的同时会回归一个偏移量和放缩值,用于修正anchors位置和大小

RPN Target

RPN Target是需要找到有目标的anchor,传递到模型后面用于分类等任务。RPN会在一个完整的图片上覆盖多种不同形状的anchors,通过计算anchors与标注的ground truth(GT box)的IoU,认为IoU≥0.7为正样本,IoU≤0.3为负样本,卡在中间的丢弃为中立样本,训练模型不使用。

上面提到了训练RPN的同时会回归一个偏移量和放缩值,目的就是用来修正anchor的位置和大小,最终更好的与ground truth相cover。

  1. # 生成RPN trainig targets
  2. # target_rpn_match 值为1代表positive anchors, -1代表negative,0代表neutral.
  3. target_rpn_match, target_rpn_bbox = modellib.build_rpn_targets(
  4. image.shape, model.anchors, gt_bbox, model.config)
  5. log("target_rpn_match", target_rpn_match)
  6. log("target_rpn_bbox", target_rpn_bbox)
  7. # 分类所有anchor
  8. positive_anchor_ix = np.where(target_rpn_match[:] == 1)[0]
  9. negative_anchor_ix = np.where(target_rpn_match[:] == -1)[0]
  10. neutral_anchor_ix = np.where(target_rpn_match[:] == 0)[0]
  11. positive_anchors = model.anchors[positive_anchor_ix]
  12. negative_anchors = model.anchors[negative_anchor_ix]
  13. neutral_anchors = model.anchors[neutral_anchor_ix]
  14. log("positive_anchors", positive_anchors)
  15. log("negative_anchors", negative_anchors)
  16. log("neutral anchors", neutral_anchors)
  17. # 对positive anchor做修正
  18. refined_anchors = utils.apply_box_deltas(
  19. positive_anchors,
  20. target_rpn_bbox[:positive_anchors.shape[0]] * model.config.RPN_BBOX_STD_DEV)
  21. log("refined_anchors", refined_anchors, )
  22. >>>
  23. >>>
  24. target_rpn_match shape: (65472,) min: -1.00000 max: 1.00000
  25. target_rpn_bbox shape: (256, 4) min: -3.66348 max: 7.29204
  26. positive_anchors shape: (19, 4) min: -53.01934 max: 1030.62742
  27. negative_anchors shape: (237, 4) min: -90.50967 max: 1038.62742
  28. neutral anchors shape: (65216, 4) min: -362.03867 max: 1258.03867
  29. refined_anchors shape: (19, 4) min: -0.00000 max: 1024.00000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

看看positive anchors和修正后的positive anchors:

visualize.draw_boxes(image, boxes=positive_anchors, refined_boxes=refined_anchors, ax=get_ax())
  • 1
  • 2

这里写图片描述

RPN Prediction

  1. # Run RPN sub-graph
  2. pillar = model.keras_model.get_layer("ROI").output # node to start searching from
  3. rpn = model.run_graph([image], [
  4. ("rpn_class", model.keras_model.get_layer("rpn_class").output),
  5. ("pre_nms_anchors", model.ancestor(pillar, "ROI/pre_nms_anchors:0")),
  6. ("refined_anchors", model.ancestor(pillar, "ROI/refined_anchors:0")),
  7. ("refined_anchors_clipped", model.ancestor(pillar, "ROI/refined_anchors_clipped:0")),
  8. ("post_nms_anchor_ix", model.ancestor(pillar, "ROI/rpn_non_max_suppression:0")),
  9. ("proposals", model.keras_model.get_layer("ROI").output),
  10. ])
  11. >>>
  12. >>>
  13. rpn_class shape: (1, 65472, 2) min: 0.00000 max: 1.00000
  14. pre_nms_anchors shape: (1, 10000, 4) min: -362.03867 max: 1258.03870
  15. refined_anchors shape: (1, 10000, 4) min: -1030.40588 max: 2164.92578
  16. refined_anchors_clipped shape: (1, 10000, 4) min: 0.00000 max: 1024.00000
  17. post_nms_anchor_ix shape: (1000,) min: 0.00000 max: 1879.00000
  18. proposals shape: (1, 1000, 4) min: 0.00000 max: 1.00000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

看看高分的anchors(没有修正前):

  1. limit = 100
  2. sorted_anchor_ids = np.argsort(rpn['rpn_class'][:,:,1].flatten())[::-1]
  3. visualize.draw_boxes(image, boxes=model.anchors[sorted_anchor_ids[:limit]], ax=get_ax())
  • 1
  • 2
  • 3

这里写图片描述

看看修正后的高分anchors,超过的图片边界的会被截止:

  1. limit = 50
  2. ax = get_ax(1, 2)
  3. visualize.draw_boxes(image, boxes=rpn["pre_nms_anchors"][0, :limit],
  4. refined_boxes=rpn["refined_anchors"][0, :limit], ax=ax[0])
  5. visualize.draw_boxes(image, refined_boxes=rpn["refined_anchors_clipped"][0, :limit], ax=ax[1])
  • 1
  • 2
  • 3
  • 4
  • 5

这里写图片描述

对上面的anchors做非极大值抑制:

  1. limit = 50
  2. ixs = rpn["post_nms_anchor_ix"][:limit]
  3. visualize.draw_boxes(image, refined_boxes=rpn["refined_anchors_clipped"][0, ixs], ax=get_ax())
  • 1
  • 2
  • 3

这里写图片描述

最终的proposal和上面的步骤一致,只是在坐标上做了归一化操作:

  1. limit = 50
  2. # Convert back to image coordinates for display
  3. h, w = config.IMAGE_SHAPE[:2]
  4. proposals = rpn['proposals'][0, :limit] * np.array([h, w, h, w])
  5. visualize.draw_boxes(image, refined_boxes=proposals, ax=get_ax())
  • 1
  • 2
  • 3
  • 4
  • 5

这里写图片描述

测量RPN的召回率(目标被anchors覆盖的比例),这里我们计算召回率有三种方法:

  • 所有的 anchors
  • 所有修正的anchors
  • 经过极大值抑制后的修正Anchors
  1. iou_threshold = 0.7
  2. recall, positive_anchor_ids = utils.compute_recall(model.anchors, gt_bbox, iou_threshold)
  3. print("All Anchors ({:5}) Recall: {:.3f} Positive anchors: {}".format(
  4. model.anchors.shape[0], recall, len(positive_anchor_ids)))
  5. recall, positive_anchor_ids = utils.compute_recall(rpn['refined_anchors'][0], gt_bbox, iou_threshold)
  6. print("Refined Anchors ({:5}) Recall: {:.3f} Positive anchors: {}".format(
  7. rpn['refined_anchors'].shape[1], recall, len(positive_anchor_ids)))
  8. recall, positive_anchor_ids = utils.compute_recall(proposals, gt_bbox, iou_threshold)
  9. print("Post NMS Anchors ({:5}) Recall: {:.3f} Positive anchors: {}".format(
  10. proposals.shape[0], recall, len(positive_anchor_ids)))
  11. >>>
  12. >>>
  13. All Anchors (65472) Recall: 0.263 Positive anchors: 5
  14. Refined Anchors (10000) Recall: 0.895 Positive anchors: 126
  15. Post NMS Anchors ( 50) Recall: 0.526 Positive anchors: 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

Proposal 分类

前面RPN Target是生成region proposal,这里就要对其分类了~

Proposal Classification

将RPN推选出来的Proposal送到分类部分,最终生成种类概率分布和bbox回归。

  1. # Get input and output to classifier and mask heads.
  2. mrcnn = model.run_graph([image], [
  3. ("proposals", model.keras_model.get_layer("ROI").output),
  4. ("probs", model.keras_model.get_layer("mrcnn_class").output),
  5. ("deltas", model.keras_model.get_layer("mrcnn_bbox").output),
  6. ("masks", model.keras_model.get_layer("mrcnn_mask").output),
  7. ("detections", model.keras_model.get_layer("mrcnn_detection").output),
  8. ])
  9. >>>
  10. >>>
  11. proposals shape: (1, 1000, 4) min: 0.00000 max: 1.00000
  12. probs shape: (1, 1000, 81) min: 0.00000 max: 0.99825
  13. deltas shape: (1, 1000, 81, 4) min: -3.31265 max: 2.86541
  14. masks shape: (1, 100, 28, 28, 81) min: 0.00003 max: 0.99986
  15. detections shape: (1, 100, 6) min: 0.00000 max: 930.00000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

获取检测种类,除去填充的0部分:

  1. det_class_ids = mrcnn['detections'][0, :, 4].astype(np.int32)
  2. det_count = np.where(det_class_ids == 0)[0][0]
  3. det_class_ids = det_class_ids[:det_count]
  4. detections = mrcnn['detections'][0, :det_count]
  5. print("{} detections: {}".format(
  6. det_count, np.array(dataset.class_names)[det_class_ids]))
  7. captions = ["{} {:.3f}".format(dataset.class_names[int(c)], s) if c > 0 else ""
  8. for c, s in zip(detections[:, 4], detections[:, 5])]
  9. visualize.draw_boxes(
  10. image,
  11. refined_boxes=detections[:, :4],
  12. visibilities=[2] * len(detections),
  13. captions=captions, title="Detections",
  14. ax=get_ax())
  15. >>>
  16. >>>
  17. 11 detections: ['person' 'person' 'person' 'person' 'person' 'orange' 'person' 'orange'
  18. 'dog' 'handbag' 'apple']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

这里写图片描述

Step by Step Detection

  1. # Proposals是标准坐标, 放缩回图片坐标
  2. h, w = config.IMAGE_SHAPE[:2]
  3. proposals = np.around(mrcnn["proposals"][0] * np.array([h, w, h, w])).astype(np.int32)
  4. # Class ID, score, and mask per proposal
  5. roi_class_ids = np.argmax(mrcnn["probs"][0], axis=1)
  6. roi_scores = mrcnn["probs"][0, np.arange(roi_class_ids.shape[0]), roi_class_ids]
  7. roi_class_names = np.array(dataset.class_names)[roi_class_ids]
  8. roi_positive_ixs = np.where(roi_class_ids > 0)[0]
  9. # How many ROIs vs empty rows?
  10. print("{} Valid proposals out of {}".format(np.sum(np.any(proposals, axis=1)), proposals.shape[0]))
  11. print("{} Positive ROIs".format(len(roi_positive_ixs)))
  12. # Class counts
  13. print(list(zip(*np.unique(roi_class_names, return_counts=True))))
  14. >>>
  15. >>>
  16. 1000 Valid proposals out of 1000
  17. 106 Positive ROIs
  18. [('BG', 894), ('apple', 25), ('cup', 2), ('dog', 4), ('handbag', 2), ('orange', 36), ('person', 36), ('sandwich', 1)]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

看一些随机取出的proposal样本,BG的不做显示,主要看有类别的,还有其对应的分数:

  1. limit = 200
  2. ixs = np.random.randint(0, proposals.shape[0], limit)
  3. captions = ["{} {:.3f}".format(dataset.class_names[c], s) if c > 0 else ""
  4. for c, s in zip(roi_class_ids[ixs], roi_scores[ixs])]
  5. visualize.draw_boxes(image, boxes=proposals[ixs],
  6. visibilities=np.where(roi_class_ids[ixs] > 0, 2, 1),
  7. captions=captions, title="ROIs Before Refinment",
  8. ax=get_ax())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这里写图片描述

做bbox修正:

  1. # Class-specific bounding box shifts.
  2. roi_bbox_specific = mrcnn["deltas"][0, np.arange(proposals.shape[0]), roi_class_ids]
  3. log("roi_bbox_specific", roi_bbox_specific)
  4. # Apply bounding box transformations
  5. # Shape: [N, (y1, x1, y2, x2)]
  6. refined_proposals = utils.apply_box_deltas(
  7. proposals, roi_bbox_specific * config.BBOX_STD_DEV).astype(np.int32)
  8. log("refined_proposals", refined_proposals)
  9. # Show positive proposals
  10. # ids = np.arange(roi_boxes.shape[0]) # Display all
  11. limit = 5
  12. ids = np.random.randint(0, len(roi_positive_ixs), limit) # Display random sample
  13. captions = ["{} {:.3f}".format(dataset.class_names[c], s) if c > 0 else ""
  14. for c, s in zip(roi_class_ids[roi_positive_ixs][ids], roi_scores[roi_positive_ixs][ids])]
  15. visualize.draw_boxes(image, boxes=proposals[roi_positive_ixs][ids],
  16. refined_boxes=refined_proposals[roi_positive_ixs][ids],
  17. visibilities=np.where(roi_class_ids[roi_positive_ixs][ids] > 0, 1, 0),
  18. captions=captions, title="ROIs After Refinment",
  19. ax=get_ax())
  20. >>>
  21. >>>
  22. roi_bbox_specific shape: (1000, 4) min: -3.31265 max: 2.86541
  23. refined_proposals shape: (1000, 4) min: -1.00000 max: 1024.00000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

这里写图片描述

滤掉低分的检测目标:

  1. # Remove boxes classified as background
  2. keep = np.where(roi_class_ids > 0)[0]
  3. print("Keep {} detections:\n{}".format(keep.shape[0], keep))
  4. # Remove low confidence detections
  5. keep = np.intersect1d(keep, np.where(roi_scores >= config.DETECTION_MIN_CONFIDENCE)[0])
  6. print("Remove boxes below {} confidence. Keep {}:\n{}".format(
  7. config.DETECTION_MIN_CONFIDENCE, keep.shape[0], keep))
  8. >>>
  9. >>>
  10. Keep 106 detections:
  11. [ 0 1 2 3 4 5 6 7 9 10 11 12 13 14 15 16 17 18
  12. 19 22 23 24 25 26 27 28 31 34 35 36 37 38 41 43 47 51
  13. 56 65 66 67 68 71 73 75 82 87 91 92 101 102 105 109 110 115
  14. 117 120 123 138 156 164 171 175 177 184 197 205 241 253 258 263 265 280
  15. 287 325 367 430 451 452 464 469 491 514 519 527 554 597 610 686 697 712
  16. 713 748 750 780 815 871 911 917 933 938 942 947 949 953 955 981]
  17. Remove boxes below 0.7 confidence. Keep 44:
  18. [ 0 1 2 3 4 5 6 9 12 13 14 17 19 26 31 34 38 41
  19. 43 47 67 75 82 87 92 120 123 164 171 175 177 205 258 325 452 469
  20. 519 697 713 815 871 911 917 949]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

做非极大值抑制操作:

  1. # Apply per-class non-max suppression
  2. pre_nms_boxes = refined_proposals[keep]
  3. pre_nms_scores = roi_scores[keep]
  4. pre_nms_class_ids = roi_class_ids[keep]
  5. nms_keep = []
  6. for class_id in np.unique(pre_nms_class_ids):
  7. # Pick detections of this class
  8. ixs = np.where(pre_nms_class_ids == class_id)[0]
  9. # Apply NMS
  10. class_keep = utils.non_max_suppression(pre_nms_boxes[ixs],
  11. pre_nms_scores[ixs],
  12. config.DETECTION_NMS_THRESHOLD)
  13. # Map indicies
  14. class_keep = keep[ixs[class_keep]]
  15. nms_keep = np.union1d(nms_keep, class_keep)
  16. print("{:22}: {} -> {}".format(dataset.class_names[class_id][:20],
  17. keep[ixs], class_keep))
  18. keep = np.intersect1d(keep, nms_keep).astype(np.int32)
  19. print("\nKept after per-class NMS: {}\n{}".format(keep.shape[0], keep))
  20. >>>
  21. >>>
  22. person : [ 0 1 2 3 5 9 12 13 14 19 26 41 43 47 82 92 120 123
  23. 175 177 258 452 469 519 871 911 917] -> [ 5 12 1 2 3 19]
  24. dog : [ 6 75 171] -> [75]
  25. handbag : [815] -> [815]
  26. apple : [38] -> [38]
  27. orange : [ 4 17 31 34 67 87 164 205 325 697 713 949] -> [ 4 87]
  28. Kept after per-class NMS: 11
  29. [ 1 2 3 4 5 12 19 38 75 87 815]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

看看最后的结果:

  1. ixs = np.arange(len(keep)) # Display all
  2. # ixs = np.random.randint(0, len(keep), 10) # Display random sample
  3. captions = ["{} {:.3f}".format(dataset.class_names[c], s) if c > 0 else ""
  4. for c, s in zip(roi_class_ids[keep][ixs], roi_scores[keep][ixs])]
  5. visualize.draw_boxes(
  6. image, boxes=proposals[keep][ixs],
  7. refined_boxes=refined_proposals[keep][ixs],
  8. visibilities=np.where(roi_class_ids[keep][ixs] > 0, 1, 0),
  9. captions=captions, title="Detections after NMS",
  10. ax=get_ax())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这里写图片描述

生成Mask

在上一阶段产生的实例基础上,通过mask head为每个实例产生分割mask。

Mask Target

即Mask分支的训练目标:

display_images(np.transpose(gt_mask, [2, 0, 1]), cmap="Blues")
  • 1

这里写图片描述

Predicted Masks

  1. # Get predictions of mask head
  2. mrcnn = model.run_graph([image], [
  3. ("detections", model.keras_model.get_layer("mrcnn_detection").output),
  4. ("masks", model.keras_model.get_layer("mrcnn_mask").output),
  5. ])
  6. # Get detection class IDs. Trim zero padding.
  7. det_class_ids = mrcnn['detections'][0, :, 4].astype(np.int32)
  8. det_count = np.where(det_class_ids == 0)[0][0]
  9. det_class_ids = det_class_ids[:det_count]
  10. print("{} detections: {}".format(
  11. det_count, np.array(dataset.class_names)[det_class_ids]))
  12. # Masks
  13. det_boxes = mrcnn["detections"][0, :, :4].astype(np.int32)
  14. det_mask_specific = np.array([mrcnn["masks"][0, i, :, :, c]
  15. for i, c in enumerate(det_class_ids)])
  16. det_masks = np.array([utils.unmold_mask(m, det_boxes[i], image.shape)
  17. for i, m in enumerate(det_mask_specific)])
  18. log("det_mask_specific", det_mask_specific)
  19. log("det_masks", det_masks)
  20. display_images(det_mask_specific[:4] * 255, cmap="Blues", interpolation="none")
  21. >>>
  22. >>>
  23. detections shape: (1, 100, 6) min: 0.00000 max: 930.00000
  24. masks shape: (1, 100, 28, 28, 81) min: 0.00003 max: 0.99986
  25. 11 detections: ['person' 'person' 'person' 'person' 'person' 'orange' 'person' 'orange'
  26. 'dog' 'handbag' 'apple']
  27. det_mask_specific shape: (11, 28, 28) min: 0.00016 max: 0.99985
  28. det_masks shape: (11, 1024, 1024) min: 0.00000 max: 1.00000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

这里写图片描述

display_images(det_masks[:4] * 255, cmap="Blues", interpolation="none")
  • 1

这里写图片描述

转载于:https://www.cnblogs.com/kk17/p/9991458.html

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号