自分のキャリアをあれこれ考えながら、Pythonで様々なデータを分析していくブログです

Mask R-CNNによる物体検知を試してみる

Python
PythonData Analytics

Mask R-CNN(Mask Region Convolutional Neural Network)は、物体検出とセグメンテーションのための深層学習モデルで、R-CNN(Region Convolutional Neural Network)の拡張バージョンとして開発されました。

R-CNNは、物体検出のための深層学習モデルの一つで、物体が存在する可能性のある領域を検出し、それぞれの領域に対して畳み込みニューラルネットワーク(CNN)を適用して物体のクラスと位置を推定します。

Mask R-CNNは、画像内の物体を検出し、それぞれの物体のバウンディングボックスを推定するだけでなく、各物体をピクセル単位で位置推定したセグメンテーションマスクも生成します。

ピクセル単位で推定することにより、画像内の物体を高精度に検出して境界をバウンディングボックスより正確に抽出することができます。

Mask R-CNNは物体検出、セマンティックセグメンテーション、インスタンスセグメンテーションなどのタスクに応用されます。具体的には、道路の車両検出や医療画像の病変検出など幅広く活用されています。

スポンサーリンク

R-CNNの歴史

Wikipediaにありました。R-CNN(2013) → Fast R-CNN(2015) → Faster R-CNN(2015) → Mask R-CNN(2017) → Mesh R-CNN(2019)と進化してきたようです。

・November 2013: R-CNN. Given an input image, R-CNN begins by applying a mechanism called Selective Search to extract regions of interest (ROI) ... each ROI is fed through a neural network to produce output features. For each ROI's output features, a collection of support-vector machine classifiers is used to determine what type of object (if any) is contained within the ROI.
・April 2015: ... Fast R-CNN runs the neural network once on the whole image ... the Fast R-CNN uses Selective Search to generate its region proposals.
・June 2015: Faster R-CNN. While Fast R-CNN used Selective Search to generate ROIs, Faster R-CNN integrates the ROI generation into the neural network itself.
・March 2017: Mask R-CNN. While previous versions of R-CNN focused on object detection, Mask R-CNN adds instance segmentation. Mask R-CNN also replaced ROIPooling with a new method called ROIAlign, which can represent fractions of a pixel.
・June 2019: Mesh R-CNN adds the ability to generate a 3D mesh from a 2D image.
引用: https://en.wikipedia.org/wiki/Region_Based_Convolutional_Neural_Networks

他にもCascade R-CNNというのもありますので紹介だけしておきます。2017年にFaster R-CNNの改良版として発表されているようです。またインスタンスセグメンテーションにも適用したCascade R-CNNが2019年に公開されているようです。Mask R-CNNより顕著な精度向上が見込まれる様ですがその分処理時間は増えるのかも知れません。

the Cascade R-CNN is generalized to instance segmentation, with nontrivial improvements over the Mask R-CNN
引用: https://arxiv.org/abs/1906.09756

Fundamentals of Object Detection: Faster, Mask, Cascade R-CNNがR-CNNについて詳しくまとまっているので勉強になりました。

スポンサーリンク

Matterport社のMask R-CNN

本記事では、Matterport社がgithubで公開しているMaskRCNNを使って物体検出して見ようと思います。

GitHub - matterport/Mask_RCNN: Mask R-CNN for object detection and instance segmentation on Keras and TensorFlow
Mask R-CNN for object detection and instance segmentation on Keras and TensorFlow - matterport/Mask_RCNN

他にもdetectron2pytorchなどのライブラリでもMask R-CNNは利用可能です。

tensorflowは1系ではないと AttributeError: module 'keras.engine' has no attribute 'Layer' のようなエラーが発生しました。そのため、requirements.txtでtensorflowの最新版(23年6月現在は2.11.0)をダウンロードした後に1.15.5にダウングレードしてあげる必要があります。参考: module 'keras.engine' has no attribute 'Layer'

また、GPUを使う場合はtensorflow-gpuもtensorflowと同じバージョンをインストールしてあげる必要があります。(cudnnとCUDAのバージョンはTensorflowの公式ページのTested build configurationsを確認してメジャーバージョンは少なくとも合わせた方がいいと思います)

個人的にはNVIDIAのGPUでnvidia-dockerが使えるのであればnvidia/cuda:10.0-cudnn7-devel-ubuntu18.04のようなdockerイメージを使って環境構築をした方が楽だと思うのでお勧めです。

今回はCPUで確認してみます。

Matterport社のMaskRCNNの環境準備
python3.7 -m venv venv-maskrcnn
source venv-maskrcnn/bin/activate
(venv-maskrcnn) pip install pip -U
(venv-maskrcnn) pip install wheel setuptools
(venv-maskrcnn) pip install ipykernel
(venv-maskrcnn) git clone https://github.com/matterport/Mask_RCNN.git
(venv-maskrcnn) cd Mask_RCNN
(venv-maskrcnn) pip3 install -r requirements.txt
(venv-maskrcnn) pip uninstall keras -y
(venv-maskrcnn) pip uninstall tensorflow -y
(venv-maskrcnn) pip uninstall h5py -y
(venv-maskrcnn) pip install tensorflow==1.15.5
(venv-maskrcnn) pip install keras==2.0.8
(venv-maskrcnn) # pip install tensorflow-gpu==1.15.5 # gpu使う場合
(venv-maskrcnn) # pip install pycocotools # cocoデータセット使う場合
(venv-maskrcnn) python3 setup.py install
(venv-maskrcnn) wget https://github.com/matterport/Mask_RCNN/releases/download/v2.0/mask_rcnn_coco.h5
(venv-maskrcnn) python3 -m ipykernel install --user --name venv-maskrcnn --display-name "venv-maskrcnn"
スポンサーリンク

Mask R-CNNで推論してみる

画像はYOLOv8でも使った車の画像を使います。

githubにサンプル(Mask R-CNN - Inspect Trained Model)がありますので、参考にしつつ検知してみようと思います

ライブラリの読み込み
import os
import sys
import random
import math
import re
import time
import numpy as np
import tensorflow as tf
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.patches as patches

# ルートの設定
ROOT_DIR = os.path.abspath("/Users/hinomaruc/Desktop/blog/Mask_RCNN")

# Mask R-CNN関連のパッケージのインポート
sys.path.append(ROOT_DIR)
from mrcnn import utils
from mrcnn import visualize
from mrcnn.visualize import display_images
import mrcnn.model as modellib
from mrcnn.model import log

# ログディレクトリ
MODEL_DIR = os.path.join(ROOT_DIR, "logs")

# 学習用のモデルweightファイル
COCO_MODEL_PATH = os.path.join(ROOT_DIR, "mask_rcnn_coco.h5")
Out[0]
Using TensorFlow backend.

モデルパラメータの設定

Mask R-CNNのコンフィグを設定します。

mrcnn/config.pyの設定を変更することにより学習のスピードを速くしたり、推論時の精度向上が見込める可能性があります。

下記は主なパラメータの設定例 (※最適値はデータセットによりけりです):

  • RPN_ANCHOR_SCALES = (32, 64, 128, 256, 512)
    → 小さい物体を検知したい場合は(8,16,32, 64, 128)などに変更してみる

  • POST_NMS_ROIS_INFERENCE = 1000
    → 検知時の負荷を減らした場合は減らす。(若干の速度向上を見込めるが精度が下がる可能性あり)

  • BACKBONE = "resnet101"
    → resnet50に変更することにより速度向上が見込まれる。(ただし学習データが少ないと私の場合は精度低下したことあり)

  • GPU_COUNT = 1、IMAGES_PER_GPU = 2
    → 1GPUあたり2枚ずつ処理するという意味。変更によって処理効率があがる、、場合があるかも。1GPU1枚と1GPU2枚の処理はそこまで差がなかったと思う。学習データのサイズによるかも。

  • STEPS_PER_EPOCH = 1000、VALIDATION_STEPS = 50
    → GPU_COUNTとIMAGES_PER_GPUに合わせて設定する。
    例:
    STEPS_PER_EPOCH = 訓練データ枚数 // (GPU_COUNT IMAGES_PER_GPU)
    VALIDATION_STEPS = 検証データ枚数 // (GPU_COUNT
    IMAGES_PER_GPU)

  • TRAIN_ROIS_PER_IMAGE = 200
    → あまり理解が進んでいないが値を増やすと精度が向上したように思う

  • DETECTION_MIN_CONFIDENCE = 0.7
    → 確信度が0.7以下は除外、タスクによって上げ下げする。

  • DETECTION_NMS_THRESHOLD = 0.3
    → 増やすと物体同士の重なりをより許容し検知しやすくなる

コンフィグの設定
## 既存のクラスの設定を読み込む(samples/coco/coco.py)
# 学習用に下記が設定されている
# NAME = "coco"
# IMAGES_PER_GPU = 2
# NUM_CLASSES = 1 + 80  # COCO has 80 classes
from samples.coco import coco
config = coco.CocoConfig()

print("---config = coco.CocoConfig()---")
config.display()

## (オプション)独自にclassを定義してもOK
#from mrcnn.config import Config
#class myOwnCocoConfig(Config):
#    NAME = "myOwnCocoConfig"
#    IMAGES_PER_GPU = 100
#    NUM_CLASSES = 1 + 80  # COCO has 80 classes
# config = myOwnCocoConfig()

## 学習用の設定を上書きし推論用のパラメータを設定する
class InferenceConfig(config.__class__):
    GPU_COUNT = 1
    IMAGES_PER_GPU = 1
config = InferenceConfig()

print("---config = InferenceConfig()---")
config.display()

## デバイスとモードの設定
DEVICE = "/cpu:0"  # /cpu:0 or /gpu:0
TEST_MODE = "inference"
Out[0]
---config = coco.CocoConfig()---

Configurations:
BACKBONE                       resnet101
BACKBONE_STRIDES               [4, 8, 16, 32, 64]
BATCH_SIZE                     2
BBOX_STD_DEV                   [0.1 0.1 0.2 0.2]
COMPUTE_BACKBONE_SHAPE         None
DETECTION_MAX_INSTANCES        100
DETECTION_MIN_CONFIDENCE       0.7
DETECTION_NMS_THRESHOLD        0.3
FPN_CLASSIF_FC_LAYERS_SIZE     1024
GPU_COUNT                      1
GRADIENT_CLIP_NORM             5.0
IMAGES_PER_GPU                 2
IMAGE_CHANNEL_COUNT            3
IMAGE_MAX_DIM                  1024
IMAGE_META_SIZE                93
IMAGE_MIN_DIM                  800
IMAGE_MIN_SCALE                0
IMAGE_RESIZE_MODE              square
IMAGE_SHAPE                    [1024 1024    3]
LEARNING_MOMENTUM              0.9
LEARNING_RATE                  0.001
LOSS_WEIGHTS                   {'rpn_class_loss': 1.0, 'rpn_bbox_loss': 1.0, 'mrcnn_class_loss': 1.0, 'mrcnn_bbox_loss': 1.0, 'mrcnn_mask_loss': 1.0}
MASK_POOL_SIZE                 14
MASK_SHAPE                     [28, 28]
MAX_GT_INSTANCES               100
MEAN_PIXEL                     [123.7 116.8 103.9]
MINI_MASK_SHAPE                (56, 56)
NAME                           coco
NUM_CLASSES                    81
POOL_SIZE                      7
POST_NMS_ROIS_INFERENCE        1000
POST_NMS_ROIS_TRAINING         2000
PRE_NMS_LIMIT                  6000
ROI_POSITIVE_RATIO             0.33
RPN_ANCHOR_RATIOS              [0.5, 1, 2]
RPN_ANCHOR_SCALES              (32, 64, 128, 256, 512)
RPN_ANCHOR_STRIDE              1
RPN_BBOX_STD_DEV               [0.1 0.1 0.2 0.2]
RPN_NMS_THRESHOLD              0.7
RPN_TRAIN_ANCHORS_PER_IMAGE    256
STEPS_PER_EPOCH                1000
TOP_DOWN_PYRAMID_SIZE          256
TRAIN_BN                       False
TRAIN_ROIS_PER_IMAGE           200
USE_MINI_MASK                  True
USE_RPN_ROIS                   True
VALIDATION_STEPS               50
WEIGHT_DECAY                   0.0001

---config = InferenceConfig()---

Configurations:
BACKBONE                       resnet101
BACKBONE_STRIDES               [4, 8, 16, 32, 64]
BATCH_SIZE                     1
BBOX_STD_DEV                   [0.1 0.1 0.2 0.2]
COMPUTE_BACKBONE_SHAPE         None
DETECTION_MAX_INSTANCES        100
DETECTION_MIN_CONFIDENCE       0.7
DETECTION_NMS_THRESHOLD        0.3
FPN_CLASSIF_FC_LAYERS_SIZE     1024
GPU_COUNT                      1
GRADIENT_CLIP_NORM             5.0
IMAGES_PER_GPU                 1
IMAGE_CHANNEL_COUNT            3
IMAGE_MAX_DIM                  1024
IMAGE_META_SIZE                93
IMAGE_MIN_DIM                  800
IMAGE_MIN_SCALE                0
IMAGE_RESIZE_MODE              square
IMAGE_SHAPE                    [1024 1024    3]
LEARNING_MOMENTUM              0.9
LEARNING_RATE                  0.001
LOSS_WEIGHTS                   {'rpn_class_loss': 1.0, 'rpn_bbox_loss': 1.0, 'mrcnn_class_loss': 1.0, 'mrcnn_bbox_loss': 1.0, 'mrcnn_mask_loss': 1.0}
MASK_POOL_SIZE                 14
MASK_SHAPE                     [28, 28]
MAX_GT_INSTANCES               100
MEAN_PIXEL                     [123.7 116.8 103.9]
MINI_MASK_SHAPE                (56, 56)
NAME                           coco
NUM_CLASSES                    81
POOL_SIZE                      7
POST_NMS_ROIS_INFERENCE        1000
POST_NMS_ROIS_TRAINING         2000
PRE_NMS_LIMIT                  6000
ROI_POSITIVE_RATIO             0.33
RPN_ANCHOR_RATIOS              [0.5, 1, 2]
RPN_ANCHOR_SCALES              (32, 64, 128, 256, 512)
RPN_ANCHOR_STRIDE              1
RPN_BBOX_STD_DEV               [0.1 0.1 0.2 0.2]
RPN_NMS_THRESHOLD              0.7
RPN_TRAIN_ANCHORS_PER_IMAGE    256
STEPS_PER_EPOCH                1000
TOP_DOWN_PYRAMID_SIZE          256
TRAIN_BN                       False
TRAIN_ROIS_PER_IMAGE           200
USE_MINI_MASK                  True
USE_RPN_ROIS                   True
VALIDATION_STEPS               50
WEIGHT_DECAY                   0.0001

画像の読み込みと推論の実行

Mask R-CNNで推論してみます。私の環境だと20秒ほどかかりました。

# モデルの設定読み込み
with tf.device(DEVICE):
    model = modellib.MaskRCNN(mode="inference", model_dir=MODEL_DIR,config=config)
model.load_weights(COCO_MODEL_PATH, by_name=True)

# 画像読み込み
import skimage
image = skimage.io.imread("/Users/hinomaruc/Desktop/blog/dataset/aidetection_cars/alexander-schimmeck-W3MXYIfyxno-unsplash.jpg")

# 推論実行
results = model.detect([image], verbose=1)
Out[0]
Processing 1 images
image                    shape: (426, 640, 3)         min:    0.00000  max:  255.00000  uint8
molded_images            shape: (1, 1024, 1024, 3)    min: -123.70000  max:  151.10000  float64
image_metas              shape: (1, 93)               min:    0.00000  max: 1024.00000  float64
anchors                  shape: (1, 261888, 4)        min:   -0.35390  max:    1.29134  float32

推論結果の確認と描画

検知結果の確認をします。

推論結果の確認
results
Out[0]
[{'rois': array([[356, 544, 391, 596],
         [274, 426, 292, 449],
         [284, 400, 302, 424],
         [373,  87, 414, 140],
         [236, 266, 246, 280],
         [270, 344, 286, 365],
         [255, 191, 269, 209],
         [312, 424, 334, 455],
         [352,  33, 385,  79],
         [222, 257, 231, 269],
         [239, 238, 252, 255],
         [215, 445, 234, 455],
         [285, 149, 305, 173],
         [215,  52, 247, 121],
         [225, 324, 231, 333],
         [233, 247, 242, 259],
         [226, 271, 234, 282],
         [206, 314, 212, 320],
         [215, 136, 234, 152],
         [236, 330, 246, 342],
         [218, 336, 225, 344],
         [252, 362, 265, 379],
         [247, 209, 260, 225]], dtype=int32),
  'class_ids': array([3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 3, 6, 3, 3, 3, 3, 6, 3, 3, 3,
         3], dtype=int32),
  'scores': array([0.9956813 , 0.9863213 , 0.98488164, 0.9750514 , 0.97497547,
         0.97016734, 0.9466475 , 0.9256975 , 0.92058176, 0.91517216,
         0.91489047, 0.9051806 , 0.888174  , 0.88095284, 0.8688674 ,
         0.8684296 , 0.8498058 , 0.83744735, 0.7947139 , 0.79195696,
         0.7700979 , 0.7605452 , 0.7176056 ], dtype=float32),
  'masks': array([[[False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          ...,
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False]],

         [[False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          ...,
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False]],

         [[False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          ...,
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False]],

         ...,

         [[False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          ...,
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False]],

         [[False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          ...,
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False]],

         [[False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          ...,
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False],
          [False, False, False, ..., False, False, False]]])}]

roisはbbox、masksはmaskを意味している様で、それぞれ出力してくれています。
bboxはみた感じxyxyフォーマットですかね。

独自に描画していもいいのですが、Matterportでは描画用のメソッドも用意されていますので活用します。

検知結果を描画
# 描画用
def get_ax(rows=1, cols=1, size=16):
    """Return a Matplotlib Axes array to be used in
    all visualizations in the notebook. Provide a
    central point to control graph sizes.

    Adjust the size attribute to control how big to render images
    """
    _, ax = plt.subplots(rows, cols, figsize=(size*cols, size*rows))
    return ax

# 学習時に設定したクラス名の一覧 (mask_rcnn_coco.h5だと下記が使われている)
class_names=['BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush']

# 検知結果を描画
ax = get_ax(1)
r = results[0]
visualize.display_instances(image, r['rois'], r['masks'], r['class_ids'], 
                            class_names, r['scores'], ax=ax,
                            title="Predictions")
Out[0]

個別の車両が色分けされていて分かりやすいですね。

推論はできたので、次は精度を確認したいと思います。

タイトルとURLをコピーしました