cv2 Multi Object Tracker

# Multiple Object Trackingとは？
Multiple Object Tracking（MOT）とは、名前の通り映像に写っている複数の物体を追跡する手法の総称です。MOTではそれぞれの追跡物体にIDを割り振りますが、同じ対象物には可能な限り同じIDを与え続けることを目標とします。

![Animation_tmp3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577794/121c1915-8cdf-3762-cadb-dc5db21ca725.gif)

# cv2 Select Multiple ROIs
別記事にて作成したSelect Multiple ROIsを使用すると、簡易的なMOTを実現できます。

https://qiita.com/youichi_io/items/2424e5c79af54ccb119a

コードを実行するには事前にモジュールのインストールが必要です。
```py
pip install numpy
pip install keyboard
pip install opencv-contrib-python==4.6.0.66
```

コードを再掲します。
```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)
```

# Object Tracker

opencv-contrib-python には古典的なObject Trackerが用意されています。
今回は例としてKCFを使用します。

https://www.slideshare.net/hitoshinishimura75/kcf-75287109

コードは以下の通り。
```py
import numpy as np
import cv2
from copy import deepcopy
from random import randint
import keyboard
import time
import copy

def is_end(window_names=["Video"]):
    return_value = False
    # イメージウインドウの x ボタンを押すか、ESC キー入力で終了処理
    window_property = [cv2.getWindowProperty(window_name, cv2.WINDOW_AUTOSIZE) for window_name in window_names]
    if max(window_property) > 0 or (cv2.waitKey(1) & 0xff == 27):
        return_value = True
    return return_value


def main(filename):
    # 動画ファイルsetting
    cap = cv2.VideoCapture(filename)
    bboxes = []
    colors = []
    multiTracker = None
    track_object_flg = False
    cv2.namedWindow("Video", cv2.WINDOW_NORMAL)
    
    if (cap.isOpened()== False):
        print("ビデオファイルを開くとエラーが発生しました")

    while(cap.isOpened()):
        if len(bboxes) == 0:
            track_object_flg = False
            multiTracker = None
        
        ret, frame = cap.read()
        if ret == True:
            
            if track_object_flg:
                # get updated location of objects in subsequent frames
                # multiTrackerの出力は bbox = [x0, y0, width(x1-x0), hight(y1-y0)]
                success, boxes = multiTracker.update(frame)
                print(boxes)
                
                # draw tracked objects
                for i, newbox in enumerate(boxes):
                    p1 = (int(newbox[0]), int(newbox[1]))
                    p2 = (int(newbox[0] + newbox[2]), int(newbox[1] + newbox[3]))
                    cv2.rectangle(frame, p1, p2, colors[i], 2, 1)
            cv2.imshow("Video", frame)
            
            if keyboard.is_pressed("t"):
                multiboxdrawer = MultiBoxDrawer()
                bboxes = multiboxdrawer.draw(frame)
                print(bboxes)
                track_object_flg = True
                multiTracker = cv2.legacy.MultiTracker_create()
                for bbox in bboxes:
                    # bbox = [x0, y0, x1, y1]
                    bbox = [bbox[0], bbox[1], bbox[2], bbox[3]]
                    # multiTrackerへの追加は bbox = [x0, y0, width(x1-x0), hight(y1-y0)]
                    multiTracker.add(cv2.legacy.TrackerKCF_create(), frame, bbox)
                    colors.append((randint(0, 255), randint(0, 255), randint(0, 255)))
            
            # # 画像表示のため遅延を入れておく
            cv2.waitKey(30)
            # qキーが押されたときbreak
            if keyboard.is_pressed("q"):
                break
            if is_end():
                break
        else:
            break
    # 後始末
    cap.release()

    cv2.destroyAllWindows()

if __name__ == "__main__":
    # https://learnopencv.com/multitracker-multiple-object-tracking-using-opencv-c-python/
    filename = 'sample.mp4'
    main(filename)
```
![Animation2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577794/8f7845f1-4bb6-31e2-a419-09604a735c34.gif)

t キー入力で追跡領域設定モードに入ります。領域を指定したらスペースキーで抜けます。指定領域の追跡が始まります。q キー入力で終了します。

gifが荒くてすみません。元画質ではアップロードが不可能でしたので、ファイルサイズを1/20に圧縮しています。

githubにソースコードがあります。

https://github.com/youichi-io/multi-object-tracker

# 参考

https://farml1.com/object_tracking/

https://tech-blog.optim.co.jp/entry/2021/07/07/100000