cv2 Select Multiple ROIsの自作

# はじめに
マウスクリックで画像から複数のROI Boxを描画します。

ROI（Region of Interest）とは、画像データのうち、操作の対象として選ぶ領域のことです。「対象領域」「注目領域」「関心領域」などともいいます。

例えば、画像の一部分にだけ処理を施す場合にROIという言葉を使います。

![キャプチャ.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577794/4f1be710-5edb-4675-a213-250c93ec7170.jpeg)

# 既存の関数を利用した場合
opencvを使用すると比較的簡単に複数のROI Boxを描画可能です。
cv2.selectROIs()を使用します。

事前にモジュールのインストールが必要です。
```
pip install opencv-python
```

適当な画像で試してみます。コードは以下の通り。
```py
from random import randint
import cv2

# 画像ファイルのパス
path = 'abc.JPG'

# 画像読み込み
img_raw = cv2.imread(path)

# 複数領域の指定
ROIs = cv2.selectROIs("Select Rois", img_raw, 0, 0)
cv2.destroyWindow("Select Rois")
print(ROIs)

# boxの描画
for roi in ROIs:
    x1, y1, x2, y2 = roi
    B, G, R = randint(0, 255), randint(0, 255), randint(0, 255)
    cv2.rectangle(img_raw,
              pt1=(x1, y1),
              pt2=(x1+x2, y1+y2),
              color=(B, G, R),
              thickness=3,
              lineType=cv2.LINE_4,
              shift=0)

# boxを描画した画像の表示
cv2.imshow('img', img_raw)
# キー入力で閉じる
cv2.waitKey(0)
cv2.destroyAllWindows()

"""
print(ROIs)
[[x0, y0, width, hight]]
>>
[[136 137  63  53]
 [320  93  77  77]
 [261 241  76  79]
 [ 70 281  71  52]]
"""
```

![Animation_tmp.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577794/45120f07-7989-83b5-59f6-1770892f0c95.gif)

gifでは分かりにくいですが、ROI boxを1つ描画するごとにスペースキーで領域選択を確定しています(少し面倒)。そして2つ目以降のROI boxを描画しているときに前のboxが消えてしまいます。このあたりが不便。

# cv2.selectROIsの自作

事前にモジュールのインストールが必要です。
```
pip install opencv-python
pip install numpy
pip install keyboard
```

```py
import numpy as np
import cv2
from copy import deepcopy
from random import randint
import keyboard
import time

class MultiBoxDrawer:
    # 参考　https://qiita.com/ryo_ryo/items/973007667c528ef23abb
    def draw(self, img):
        m = MouseEventHandler(img)

        cv2.imshow('Select Rois',img)
        cv2.setMouseCallback ("Select Rois",
                            lambda event, x, y, flags, param:
                            m.mouse_event(event, x, y, flags, param, img))

        while True:
            img = m.clone_img
            cv2.imshow("Select Rois", img)
            k = cv2.waitKey(10)
            if keyboard.is_pressed("space") | keyboard.is_pressed("enter"):
                time.sleep(0.3)
                break

        cv2.destroyWindow("Select Rois")
        # 複数のバウンディングボックスの座標情報をreturn
        return m.ROIRegion

class MouseEventHandler:
    def __init__(self, img):
        self.drawing = False # true if mouse is pressed
        self.ix, self.iy = -1, -1
        self.ROIRegion = []
        self.init_img = deepcopy(img)
        self.clone_img = deepcopy(img)
        self.tmp_img = deepcopy(img)

    def mouse_event(self, event, x, y, flags, param, img):
        if event == cv2.EVENT_LBUTTONDOWN: # 左クリック(押し込み)した瞬間の処理
            self.drawing = True
            self.ix, self.iy = x,y
            self.tmp_img = deepcopy(img)

        elif event == cv2.EVENT_MOUSEMOVE: #ドラッグ中の処理
            if self.drawing == True:
                self.clone_img = deepcopy(self.tmp_img)
                cv2.rectangle(self.clone_img, (self.ix, self.iy), (x, y), (0, 0, 255), 2)

        elif event == cv2.EVENT_LBUTTONUP: # 左クリック(離し)した瞬間の処理
            self.drawing = False
            if self.ix != x and self.iy != y:
                self.ROIRegion.append([min(self.ix, x), min(self.iy, y), abs(x-self.ix), abs(y-self.iy)])

        elif event == cv2.EVENT_RBUTTONDOWN: # 右クリック(離し)した瞬間の処理
            self.ROIRegion = []
            self.clone_img = deepcopy(self.init_img)


multiboxdrawer = MultiBoxDrawer()
#スペースキーもしくはエンターキーで画像を閉じる
# 画像ファイルのパス
path = 'abc.JPG'
# 画像読み込み
img_raw = cv2.imread(path)
ROIs = multiboxdrawer.draw(img_raw)
print(ROIs)

# boxの描画
for roi in ROIs:
    x1, y1, x2, y2 = roi
    B, G, R = randint(0, 255), randint(0, 255), randint(0, 255)
    cv2.rectangle(img_raw,
            pt1=(x1, y1),
            pt2=(x1+x2, y1+y2),
            color=(B, G, R),
            thickness=3,
            lineType=cv2.LINE_4,
            shift=0)

while True:
    # boxを描画した画像の表示
    cv2.imshow('img', img_raw)
    k = cv2.waitKey(10)
    # Escキー入力で閉じる
    if keyboard.is_pressed("escape"):
        time.sleep(0.3)
        break

cv2.destroyAllWindows()

"""
print(ROIs)
[[x0, y0, width, hight]]
>>
[[136 137  63  53],
 [320  93  77  77],
 [261 241  76  79],
 [ 70 281  71  52]]
"""
```

![Animation_tmp2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577794/2e05099b-2b82-cf1c-ab2a-e92fa2fd43c1.gif)

ROI boxを1つ描画するごとに入れていた確定処理が必要なくなり、2つ目以降のROI boxを描画しているときに前のboxが残るようになりました。右クリックで初期化も可能です。スペースキーで複数の領域選択を確定します。複数のROI box描画中の四角の色がすべて同じなのが気になるところ。改善の余地ありです。

# 応用例はこちら

https://qiita.com/youichi_io/items/eb3b03a585a80cb99d03

# 参考
https://learnopencv.com/multitracker-multiple-object-tracking-using-opencv-c-python/

https://stackoverflow.com/questions/71873241/opencv-selectroi-function-works-but-selectrois-function-does-not

https://qiita.com/kotai2003/items/acfe925a3c535cc526e4

https://stackoverflow.com/questions/72140517/regarding-multiple-rois-in-an-image-using-python-opencv