Detectron2ではじめる画像の物体検出とセグメンテーション

## はじめに
最近, Detectron2を用いて画像の物体検出とセグメンテーションを行ったのですが, 日本語の記事が少なく実装に苦労した部分があったため, 今回は物体検出とセグメンテーションに関して基本的な操作をまとめておきたいと思います.

:::note info
<b>補足</b>
今回は実装に焦点を当てるため画像処理やニューラルネットワークの専門的な話は盛り込んでいません 
:::

## Detectron2とは?
<img src="https://raw.githubusercontent.com/facebookresearch/detectron2/main/.github/Detectron2-Logo-Horz.svg"></img>
Detectron2とは,FacebookAIが開発したPyTorchベースの物体検出ライブラリで,物体検出・セグメンテーション・その他の視覚認識タスクのためのプラットフォームです. 現在はオープンソースとして公開されています. 詳細は[こちら]( https://github.com/facebookresearch/detectron2)で確認できます.

## 記事概要
・Google Colaboratoryの立ち上げ
・Detectron2の設定
・画像の物体検出とセグメントの実装

## 必要事項
・Googleアカウント
・Pythonの基礎知識
・PyTorch,Numpy,CV2の基礎知識
・対象の画像ファイル

## GoogleColaboratoryの立ち上げ
#### GoogleColaboratory (以下Colab) とは<br><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/o/oikakerublog/20180516/20180516134847.png"></img>
Googleが提供しているGoogleドライブ上でのpythonの実行環境 (JupyterNotebook仕様) で,時間制限はあるものの高性能なTeslaのGPUを無料で使うことができます. Colabの詳細は[こちら](https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja)で確認できます. 

#### GoogleDriveとの接続

```python:image_processing.ipynbのセル1
from google.colab import drive
drive.mount("/content/drive")
```
1. 上記セル実行後表示されるURLにアクセス
1. アカウントを選択しドライブの接続を許可
1. ドライブが実行環境にマウント

以上の手順を踏むことでドライブ上のファイルやフォルダーをColab上で操作できるようになります.

:::note warn
<b>注意</b>
以降のコードはランタイムにGPUを使用します
:::
## Detectron2の設定
#### Detectron2のインストール
>コードはDetectron2の[チュートリアル](https://colab.research.google.com/drive/16jcaJoc6bCFAQ96jDe2HwtXj7BMD_-m5)を参照します

```python:image_processing.ipynbのセル2
!pip install pyyaml==5.1
import torch
# Install detectron2 that matches the above pytorch version
!pip install detectron2 -f https://dl.fbaipublicfiles.com/detectron2/wheels/$CUDA_VERSION/torch$TORCH_VERSION/index.html
```

Detectron2のモジュールをインストール

```python:image_processing.ipynbのセル3
import detectron2
from detectron2.utils.logger import setup_logger
setup_logger()

# 訓練済みモデル選択モジュールModelZoo(https://github.com/facebookresearch/detectron2/blob/main/MODEL_ZOO.md)
from detectron2 import model_zoo
from detectron2.engine import DefaultPredictor
from detectron2.config import get_cfg
from detectron2.utils.visualizer import Visualizer
from detectron2.data import MetadataCatalog, DatasetCatalog
```

続いて画像を扱うモジュールをインストール

```python:image_processing.ipynbのセル4
# import some common libraries
import numpy as np
import os, json, cv2, random
# colab上ではcv2オブジェクトの表示に特にcv2_imshowを用いる
from google.colab.patches import cv2_imshow
```
#### 学習器の作成(model)

```python:image_processing.ipynbのセル5
# 学習モデルの読み込み(モデルはCOCOフォーマット)
cfg = get_cfg()
cfg.merge_from_file(model_zoo.get_config_file("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml"))
# threshold:閾値
cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.5
cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url("COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml")

# 学習器を作成(model)
predictor = DefaultPredictor(cfg)
```
今回の目的は物体検出とセグメントなので, モデルは訓練時間が比較的早く, 精度も割に高めのmask_rcnnのR_50FPNを用いる設定になっています.  その他のモデルについては[こちら](https://github.com/facebookresearch/detectron2/blob/main/MODEL_ZOO.md#coco-instance-segmentation-baselines-with-mask-r-cnn)で確認できます.
## 画像の物体検出とセグメントの実装
それでは実装していきます. 今回使用する画像はこちらです
![宇宙猫withブロッコリー.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/862467/bbdce20f-5c32-6196-1592-57fdf4af1f4e.jpeg)
この画像からcv2オブジェクトを作成し, 学習器を通してみましょう.

```python:image_processing.ipynbのセル6
file_dir = "/content/drive/MyDrive/Detectron2_test/宇宙猫withブロッコリー.jpg"
img = cv2.imread(file_dir)
# 学習器で推定する
output = predictor(img)
```
outputについて出力してみます.
![2022-02-12_22h17_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/862467/e361a076-b7a7-e8f1-b8bf-f09e0d76108f.png)
#### 出力結果の詳細を確認する
ここから, outputの中身を覗いていきます. 
まず, outputの構造は辞書形式でその中にInstanceオブジェクトが入っています. ( Instanceオブジェクトの詳細は[こちら](https://detectron2.readthedocs.io/en/latest/_modules/detectron2/structures/instances.html)) そして, Instanceオブジェクトの中にはnum_instance, image_height, image_width, fieldsが確認できます. また, fieldsにおいて次の要素が確認できます.

| 名前 | 構造 | 内容 |
|:----:|:----:|:----:|
| pred_box | tensor( list ) | 推定した物体のBoundig Box |
| scores | tensor( list ) | 推定した物体の類似度 |
| pred_classes | tensor( list ) | 推定された物体名のカテゴリーID |
| fields | tensor( list ) |  推定した物体のBitmap |

#### 推定された物体名を確認
この要素をもとに, 推定された物体名を確認してみましょう.
物体名はリストで保存されています. これらはdetectron2のdataにおけるMetadataCatalogで取得することができます.

```python:image_processing.ipynbのセル7
# このclasses_listのindexと物体のカテゴリーIDが対応しています
classes_list= MetadataCatalog.get(cfg.DATASETS.TRAIN[0]).thing_classes

objects = []
for id in output["instances"]._fields["pred_classes"].tensor.cpu().numpy():
  obj = classes_list[id]
  objects.append(obj)
# 推定した物体名のリスト
object_est = [(k,i) for k,i in zip(objects,output["instances"]._fields["scores"].tensor.cpu().numpy())]
```
出力結果( object_est )

|  | 物体名 | 類似度 |
|:-:|:-:|:-:|
| 0 | broccoli | 0.99425983 |
| 1 | cat | 0.99423665 |
| 2 | broccoli | 0.9931277 |
| 3 | broccoli | 0.99076086 |
| 4 | broccoli | 0.9890274 |
| 5 | bird | 0.9842605 |
| 6 | bird | 0.68304986 |
| 7 | broccoli | 0.4676876 |

#### セグメンテーションを行う
先ほど予測した物体が画像ではどこになるのか可視化してみましょう.

##### 1. BoundingBoxでセグメント

```python:image_processing.ipynbのセル9
# boundign boxの取得 -> 物体の[左上座標, 左下座標, 右上座標, 右下座標]
bounding_boxes = outputs["instances"]._fields["pred_boxes"].tensor.cpu().numpy()
# 上書き用に画像をコピー
img_copy = img
# 検出した物体数反復する
for i in range(len(bounding_boxes)):
  # 左上座標
  left_pt = tuple(bounding_boxes[i][0:2])
  # 右下座標
  right_pt = tuple(bounding_boxes[i][2:4])
  # cv2.putText(img_copy, f'{i}', left_pt, cv2.FONT_HERSHEY_PLAIN, 4, (255, 255, 255), 5, cv2.LINE_AA)
  # 範囲を枠線で囲む
  cv2.rectangle(img_copy,left_pt,right_pt,(0,0,155),1)

cv2_imshow(img_copy)
```
出力
![neko_bounding_box.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/862467/60134fb7-848b-e51d-e779-71532785be18.jpeg)
ここで, 物体名の際に作成した表と, 位置を比べてみると5,6が鳥と誤認されていますね. 予測した物体名が必ずしも正しいとは言えないでしょう. 
  [ 再掲 ]

|  | 物体名 | 類似度 |
|:-:|:-:|:-:|
| 0 | broccoli | 0.99425983 |
| 1 | cat | 0.99423665 |
| 2 | broccoli | 0.9931277 |
| 3 | broccoli | 0.99076086 |
| 4 | broccoli | 0.9890274 |
| 5 | bird | 0.9842605 |
| 6 | bird | 0.68304986 |
| 7 | broccoli | 0.4676876 |

より特定の物体を正しく推定するためにファインチューニングを行うといった方法がありますが, 今回は触れません. 興味のある方は[こちら](https://qiita.com/persimmon-persimmon/items/0c744f5566e5ec9dd21c)の記事を参考にするとよいかもしれません.

##### 2. BitMapでセグメント

```python:image_processing.ipynbのセル10
object_estimation = object_est
dct = {file_dir:object_estimation}
# ビットマップの抽出
bool_array = output["instances"]._fields["pred_masks"]

for j,b_array in enumerate(bool_array):
  try:
    # tensorをnumpy配列に変換
    array = b_array.cpu().numpy()
    # 論理値の反転
    inv_bool_array = []
    for l in array:
      inv_b = []
      for b in l:
        if b == False:
          inv_b.append(True)
        else:
          inv_b.append(False)
      inv_bool_array.append(inv_b)
    # cv2オブジェクトはnumpy配列なので,正(true)を灰色に変換
    copy_img = img.copy()
    copy_img[inv_bool_array] = [128,128,128]
```
出力( 左上から右に出力順 )
![2022-02-13_00h30_44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/862467/fabbc302-c047-bd97-de54-d51b3eee4198.png)
##### 3. Visulizerモジュールでセグメント( Bounding box + Bitmap)

```python:image_processing.ipynbのセル8
# We can use `Visualizer` to draw the predictions on the image.
v = Visualizer(img[:, :, ::-1], MetadataCatalog.get(cfg.DATASETS.TRAIN[0]), scale=1.0)
out = v.draw_instance_predictions(output["instances"].to("cpu"))
# image[:, :, ::-1]はRGBをBGRへ変換している
cv2_imshow(out.get_image()[:, :, ::-1])
```
出力結果
![neko.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/862467/33850ec6-c738-2cde-ab5d-ff6ae062fa17.jpeg)
ここまで出来たらいろいろな画像処理ができそうですね.
## さいごに
今回は画像の物体検出とセグメンテ―ションをDetectron2を用いて実装していきました. 画像処理やニューラルネットワークの専門的な知識がなくても手軽に実装でき, 画像処理の技術は誰でも使える時代になりつつあるなと感じました. 技術者でなくとも画像処理の活用を積極的に検討していきたいですね. 
## 参考URL
https://detectron2.readthedocs.io/en/latest/index.html

https://note.nkmk.me/python-opencv-imread-imwrite/

https://note.nkmk.me/python-opencv-bgr-rgb-cvtcolor/

https://qiita.com/persimmon-persimmon/items/0c744f5566e5ec9dd21c