4原文:Mask R-CNN with OpenCV - 2018.11.19

作者:Adrian Rosebrock

这篇博文主要是介绍如何基于 OpenCV 使用 Mask R-CNN.

Mask R-CNN 可以自动对图片中每个目标的进行分割,并构建像素级的 masks.

基于OpenCV DNN的 MaskRCNN 目标检测与实例分割 - AIUAI

在博文 YOLOV3 - 基于 OpenCV 的 YOLO 目标检测 - AIUAI 中介绍了基于 OpenCV 的 YOLO 目标检测算法. 目标检测器,如 YOLO,Faster R-CNNs 和 SSDs,输出的是图片中每个目标物体的边界框的 (x, y) 坐标集合. 但是边界框缺乏一定的像素级信息,如 (1)哪些像素属于前景目标;(2) 哪些像素属于背景.

博文主要包括:

[1] - Mask R-CNN 概览

[2] - Mask R-CNN 图片

[3] - Mask R-CNN 视频流

1. Mask R-CNN 概览

1.1. 实例分割 vs. 语义分割

如下图,很好的说明了图像分类、目标检测、语义分割和实例分割之间的差异.

[1] - 图像分类,旨在预测能够刻画输入图像内容的标签集.

[2] - 目标检测,基于图像分类,但是同时会定位图片中目标的位置,则图像的刻画即包括两部分: (1) 每个目标的边界框(x,y) 坐标; (2) 对应每个边界框的相关类别标签.

[3] - 语义分割,预测输入图像的每个像素的类别标签(包括背景的类别标签.) 注:上图(c)中 cube 目标均是相同的颜色. 虽然语义分割算法能够对每个目标的像素进行分类,但是不能区分相同类别的两个目标.

[4] - 实例分割,即使图像中存在相同类别标签的目标,也能够预测图片中每个目标的像素级 mask. 如上图(d), 每个 cube 都是不同的颜色. 即:实例分割不仅能够定位每个独立的 cube,还能够预测其边界位置.

Mask R-CNN 是一种实例分割算法.

1.2. Mask R-CNN 简介

Mask R-CNN 算法是 He 等在论文 Mask R-CNN(2017) 中提出的.

Mask R-CNN 是基于 Girshick 等人在 R-CNN (2013), Fast R-CNN (2015) 和 Faster R-CNN (2015) 中的目标检测工作的.

为了更好的理解 Mask R-CNN 算法,这里简单回顾下 R-CNNs:

[1] - R-CNN

主要包括四步处理:

(1)-Step1: 输入一张图片到网络;

(2)-Step2: 提取区域候选框(region proposals),例如采用 Selective Search 算法得到的可能包含目标物体的图片区域.

(3)-Step3: 采用迁移学习,即特征提取部分,采用预训练的CNN计算每个候选框(ROI)的特征.

(4)-Step4: 采用 SVM 对每个候选框的提取特征进行分类.

R-CNN 有效的原因在于 CNN 提取的鲁棒、判别性强的特征. 但,R-CNN 的问题在于其速度相当的慢. 此外,其并不是真正通过深度神经网络来学习定位目标,实际上只是构建了更高级的 HOG + Linear SVM detector 方法.

[2] - Fast R-CNN

类似于 R-CNN,Fast R-CNN 也采用了 Selective Search 算法来获得区域候选框;不过,其创新点在于 Region of Interest (ROI) Pooling 模块.

ROI Pooling 通过对 CNN 的输出的 feature map 提取固定尺寸的窗口(fixed-size window),并采用窗口内的特征得到最终的类别标签和边界框. Fast R-CNN 最重要的提升是,有效的实现了网络的 end-to-end 训练,其过程主要为:

(1)-给定输入图片及对应的 ground-truth 边界框;

(2)-提取特征图(feature map);

(3)-采用 ROI Pooling 处理,得到 ROI 特征向量;

(4)-最后,采用两个 FC 层,同时得到每个候选框的类别标签预测和边界框位置.

虽然 Fast R-CNN 是 end-to-end 训练的,但是由于依赖于 Selective Search 算法,其在预测推断时,速度明显受影响.

[3] - Faster R-CNN

为了进一步提升 R-CNN 结构的速度,Faster R-CNN 直接将区域候选框选取(region propoasal) 整合进网络结构中.

**

Faster R-CNN 提出 Region Proposal Network (RPN),以实现将区域候选框选取(region propoasal) 直接整合进网络中,取代 Selective Search 算法.

Faster R-CNN 结构能够取得 7-10 FPS 的速度,将基于深度学习的实时目标检测提升了一大步.

[4] - Mask R-CNN

Mask R-CNN 是基于 Faster R-CNN 结构构建的,主要有两个创新:

(1)-将 ROI Pooling 模块替换为 ROI Align 模块;

(2)-在ROI Align 模块的输出端新增一个分支. 该分支的输入为 ROI Align 的输出,该分支的输出送入到两个 Conv 层. Conv 层的输出即为 mask.

如图:

正如在 Faster R-CNN/Mask R-CNN 结构中,采用RPN 网络来生成图片中可能包含物体的区域候选框. 每个候选区域根据其"目标分数(objectness score)" 进行排名(如,表征了给定区域可能包含目标的可能性), 然后保留 top N 个最可能包含目标的区域.

在 Faster R-CNN 论文中,Girshick 等人设置 N=2000. 但实际上,可以采用较小的 N 值,如 N=(10, 100, 200, 300),仍能得到比较好的结果.

在 Mask R-CNN 论文中,He 等设置 N=300,这里也采用该设置.

所选择的 300 个 ROIs 被送入到三个并行的网络分支:

  • (1)类别标签预测
  • (2)边界框预测
  • (3)Mask 预测

在 Mask R-CNN 预测阶段,所选择的 300 个 ROIs 先送入 NMS( non-maxima suppression) 处理,并只保留 top 100 的检测框,最终得到 100xLx15x15 的 4D Tensor. 其中,L 是数据集中类别标签的总数,15x15 是每个 mask 的尺寸.

COCO dataset 为例,L=90 个目标物体类别标签,mask 预测分支得到的结果为 100x90x15x15.

Mask R-CNN 预测 mask 的过程如图:

更多细节参考:

[1] - Mask R-CNN

[2] - Deep Learning for Computer Vision with Python

2. Mask R-CNN 图片实例分割

具体实现如下 - mask_rcnn.py

#!/usr/bin/python3 #--*-- coding:utf-8 --*-- import numpy as np import argparse import random import time import cv2 import os #参数配置 ap = argparse.ArgumentParser() ap.add_argument("-i", "--image", required=True, help="path to input image") ap.add_argument("-m", "--mask-rcnn", required=True, help="base path to mask-rcnn directory") ap.add_argument("-v", "--visualize", type=int, default=0, help="whether or not we are going to visualize each instance") ap.add_argument("-c", "--confidence", type=float, default=0.5, help="minimum probability to filter weak detections") ap.add_argument("-t", "--threshold", type=float, default=0.3, help="minimum threshold for pixel-wise mask segmentation") args = vars(ap.parse_args()) #加载 COCO 类别标签 labelsPath = os.path.sep.join([args["mask_rcnn"], "object_detection_classes_coco.txt"]) LABELS = open(labelsPath).read().strip().split("\n") #加载用于可视化给定实例分割的颜色集合 colorsPath = os.path.sep.join([args["mask_rcnn"], "colors.txt"]) COLORS = open(colorsPath).read().strip().split("\n") COLORS = [np.array(c.split(",")).astype("int") for c in COLORS] COLORS = np.array(COLORS, dtype="uint8") #Mask R-CNN 权重路径及模型配置文件 weightsPath = os.path.sep.join([args["mask_rcnn"], "frozen_inference_graph.pb"]) configPath = os.path.sep.join([args["mask_rcnn"], "mask_rcnn_inception_v2_coco_2018_01_28.pbtxt"]) #加载预训练的 Mask R-CNN 模型(90 classes) print("[INFO] loading Mask R-CNN from disk...") net = cv2.dnn.readNetFromTensorflow(weightsPath, configPath) #读取图片 image = cv2.imread(args["image"]) (H, W) = image.shape[:2] #构建输入图片 blob blob = cv2.dnn.blobFromImage(image, swapRB=True, crop=False) net.setInput(blob) start = time.time() #forward 计算,输出图片中目标的边界框坐标以及每个目标的像素级分割 (boxes, masks) = net.forward(["detection_out_final", "detection_masks"]) end = time.time() # Mask R-CNN 的时间统计 print("[INFO] Mask R-CNN took {:.6f} seconds".format(end - start)) print("[INFO] boxes shape: {}".format(boxes.shape)) print("[INFO] masks shape: {}".format(masks.shape)) # loop over the number of detected objects for i in range(0, boxes.shape[2]): #检测的 class ID 及对应的置信度(概率) classID = int(boxes[0, 0, i, 1]) confidence = boxes[0, 0, i, 2] #过滤低置信度预测结果 if confidence > args["confidence"]: # 用于可视化 clone = image.copy() #将边界框坐标缩放回相对于图片的尺寸,然后计算边界框的width和height box = boxes[0, 0, i, 3:7] * np.array([W, H, W, H]) (startX, startY, endX, endY) = box.astype("int") boxW = endX - startX boxH = endY - startY #提取目标的像素级分割 mask = masks[i, classID] #resize mask以保持与边界框的维度一致 mask = cv2.resize(mask, (boxW, boxH), interpolation=cv2.INTER_NEAREST) #根据设定阈值,得到二值化mask. mask = (mask > args["threshold"]) #提取图片的 ROI roi = clone[startY:endY, startX:endX] #可视化 if args["visualize"] > 0: #将二值mask转换为:0和255 visMask = (mask * 255).astype("uint8") instance = cv2.bitwise_and(roi, roi, mask=visMask) #可视化提取的 ROI、mask 以及对应的分割实例 cv2.imshow("ROI", roi) cv2.imshow("Mask", visMask) cv2.imshow("Segmented", instance) #只提取 ROI 的 masked 区域 roi = roi[mask] #随机选择一种颜色,用于可视化特定的实例分割 color = random.choice(COLORS) #通过融合选择的颜色和 ROI 进行融合,创建透明覆盖图 blended = ((0.4 * color) + (0.6 * roi)).astype("uint8") #替换原始图片的融合 ROI 区域 clone[startY:endY, startX:endX][mask] = blended #画出图片中实例的边界框 color = [int(c) for c in color] cv2.rectangle(clone, (startX, startY), (endX, endY), color, 2) #画出预测的类别标签以及对应的实例概率 text = "{}: {:.4f}".format(LABELS[classID], confidence) cv2.putText(clone, text, (startX, startY - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) #show cv2.imshow("Output", clone) cv2.waitKey(0)

运行,如:

python mask_rcnn.py --mask-rcnn mask-rcnn-coco --image images/example_01.jpg python mask_rcnn.py --mask-rcnn mask-rcnn-coco --image images/example_03.jpg --visualize 1

输出如:

中间输入如:

3. Mask R-CNN 视频流实例分割

具体实现如下 - mask_rcnn_video.py:

import numpy as np import argparse import imutils import time import cv2 import os # ap = argparse.ArgumentParser() ap.add_argument("-i", "--input", required=True, help="path to input video file") ap.add_argument("-o", "--output", required=True, help="path to output video file") ap.add_argument("-m", "--mask-rcnn", required=True, help="base path to mask-rcnn directory") ap.add_argument("-c", "--confidence", type=float, default=0.5, help="minimum probability to filter weak detections") ap.add_argument("-t", "--threshold", type=float, default=0.3, help="minimum threshold for pixel-wise mask segmentation") args = vars(ap.parse_args()) # labelsPath = os.path.sep.join([args["mask_rcnn"], "object_detection_classes_coco.txt"]) LABELS = open(labelsPath).read().strip().split("\n") # np.random.seed(42) COLORS = np.random.randint(0, 255, size=(len(LABELS), 3), dtype="uint8") # weightsPath = os.path.sep.join([args["mask_rcnn"], "frozen_inference_graph.pb"]) configPath = os.path.sep.join([args["mask_rcnn"], "mask_rcnn_inception_v2_coco_2018_01_28.pbtxt"]) # print("[INFO] loading Mask R-CNN from disk...") net = cv2.dnn.readNetFromTensorflow(weightsPath, configPath) #读取视频流 vs = cv2.VideoCapture(args["input"]) writer = None #计算视频流总帧数 try: prop = cv2.cv.CV_CAP_PROP_FRAME_COUNT if imutils.is_cv2() \ else cv2.CAP_PROP_FRAME_COUNT total = int(vs.get(prop)) print("[INFO] {} total frames in video".format(total)) except: print("[INFO] could not determine # of frames in video") total = -1 # loop over frames from the video file stream while True: # read the next frame from the file (grabbed, frame) = vs.read() if not grabbed: break # blob = cv2.dnn.blobFromImage(frame, swapRB=True, crop=False) net.setInput(blob) start = time.time() (boxes, masks) = net.forward(["detection_out_final", "detection_masks"]) end = time.time() # loop over the number of detected objects for i in range(0, boxes.shape[2]): classID = int(boxes[0, 0, i, 1]) confidence = boxes[0, 0, i, 2] if confidence > args["confidence"]: (H, W) = frame.shape[:2] box = boxes[0, 0, i, 3:7] * np.array([W, H, W, H]) (startX, startY, endX, endY) = box.astype("int") boxW = endX - startX boxH = endY - startY # mask = masks[i, classID] mask = cv2.resize(mask, (boxW, boxH), interpolation=cv2.INTER_NEAREST) mask = (mask > args["threshold"]) # roi = frame[startY:endY, startX:endX][mask] # color = COLORS[classID] blended = ((0.4 * color) + (0.6 * roi)).astype("uint8") # frame[startY:endY, startX:endX][mask] = blended # color = [int(c) for c in color] cv2.rectangle(frame, (startX, startY), (endX, endY), color, 2) # text = "{}: {:.4f}".format(LABELS[classID], confidence) cv2.putText(frame, text, (startX, startY - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) # check if the video writer is None if writer is None: # initialize our video writer fourcc = cv2.VideoWriter_fourcc(*"MJPG") writer = cv2.VideoWriter(args["output"], fourcc, 30, (frame.shape[1], frame.shape[0]), True) # some information on processing single frame if total > 0: elap = (end - start) print("[INFO] single frame took {:.4f} seconds".format(elap)) print("[INFO] estimated total time to finish: {:.4f}".format( elap * total)) # write the output frame to disk writer.write(frame) # release the file pointers print("[INFO] cleaning up...") writer.release() vs.release()

运行,如:

python mask_rcnn_video.py --input videos/cats_and_dogs.mp4 \ --output output/cats_and_dogs_output.avi \ --mask-rcnn mask-rcnn-coco
Last modification:June 13th, 2019 at 04:13 pm