OpenCVのTrackerを使って動画内のオブジェクトをトラッキングする

映像内に現れるマーカーやその他のオブジェクトを基準に何か処理をしたい場合は、物体検知・マーカー検知の手法を使ってフレーム内の物体の検出・位置を特定するという手順を踏むことが多いと思いますが、物体追跡(トラッキング)を併用すると物体検知・マーカー検知に失敗したフレームにおけるマーカー・オブジェクトの検出を補間できる場合があります。

OpenCVのTrackerを使って、動画内のオブジェクトのトラッキングをやってみます。

環境構築の手間がないので、Google Colaboratory上で実験します。

# Google Driveのマウント
解析対象の動画をGoogle Driveから読み込みたいので、Google Driveをマウントします。

```python
from google.colab import drive
drive.mount("/content/gdrive")
```

# FPSの指定
```python
fps = 5
```

FPSを高く設定した方が基本的には追跡の精度が上がりますが、処理時間も増えてしまうのでまずは5FPSで解析を行います。あとで変更できるように変数にしておきます。

# 動画からフレームを抽出(静止画に変換)
```python
!ffmpeg -i "/content/gdrive/My Drive/TrackerExp/src.mp4" -vf fps=$fps "/content/gdrive/My Drive/TrackerExp/src/%06d.jpg"
```

ffmpegを使って、動画から静止画を抽出します。抽出先はGoogle Drive上じゃなくても良いのですが、あとで過程をチェックするときに便利なのでGoogle Drive上に保存するようにしています。参照する必要がない場合はGoogle Drive上のディレクトリではなく一時ディレクトリなどを使った方がGoogle Driveの同期のキューが詰まらないので良いです。

# 抽出した画像のリストを取得する
```python 
import glob
import os
files = glob.glob("/content/gdrive/My Drive/TrackerExp/src/*.jpg")
files.sort() # 見つかったファイルをソートする
print(len(files))
```

抽出先のフォルダから画像のリストを取得します。取得された画像はフレーム番号順にソートされている保証はないので、ソートしておきます。

# 解析結果の保存先のディレクトリを作る
```python 
dst_dir = "/content/gdrive/My Drive/TrackerExp/dst"
if os.path.exists(dst_dir) == False:
  os.makedirs(dst_dir)
```

今回は解析結果(トラッキングの結果)をオリジナル画像の上に描画して画像で保存していくようにします。保存先のディレクトリを作成しておきます。

# トラッキングしたい範囲(時間)の指定
```python 
start_sec = 5.0 # トラッキング開始は5秒時点
end_sec = 15.0 # トラッキング終了は15秒時点

start_frame = fps * start_sec
end_frame = fps * end_sec
```

動画のうち、トラッキングを行いたい範囲(時間)を指定します。実際のフレーム番号(何フレーム目から何フレーム目か)は、FPSに応じて変わってくるので、秒にFPSをかけてフレーム番号を求めています。

# トラッカーの生成
```python 
tracker = cv2.TrackerMedianFlow_create()
```

トラッカーを生成します。OpenCVにはMedianFrow以外にも複数のトラッキングアルゴリズムが用意されているので、トラッキング対象映像の特性に合わせて切り替えます。例: [cv2.TrackerKCF_create()](https://docs.opencv.org/3.4/d2/dff/classcv_1_1TrackerKCF.html#details)

各トラッキングアルゴリズムの甲乙については、[こちらの投稿](https://www.learnopencv.com/object-tracking-using-opencv-cpp-python/)などがわかりやすいです。

# トラッキング初期位置の設定
```python 
start_rect = (100, 200, 30, 30) # x: 100, y: 200, width: 30, height: 30
start_img = cv2.imread(files[start_frame])
tracker.init(start_img, start_rect)
```

最初のフレームの画像を読み込み、そのフレームにおけるトラッキングしたいオブジェクトの範囲を指定します。

# トラッキングの実行
```python 
for i in range(start_frame, end_frame):
  frame = cv2.imread(files[i])
  located, bounds = tracker.update(frame)
  if located:
    x = bounds[0]
    y = bounds[1]
    w = bounds[2]
    h = bounds[3]
    cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 255, 0), 2)
  cv2.imwrite(os.path.join(dst_dir, os.path.basename(files[i])), frame)
```

初期位置の設定を行ったら、1フレームずつトラッキングを行っていきます。`cv2.imread`でフレームの画像を読み込み、読み込んだ画像に対してtrackerの[update](https://docs.opencv.org/3.4/d0/d0a/classcv_1_1Tracker.html#a549159bd0553e6a8de356f3866df1f18)を実行していきます。

```python 
located, bounds = tracker.update(frame)
```

updateメソッドの一つ目の返り値は「目標を見つけることができたか」がTrue/Falseで表現されます。見つかった場合は第二の返り値に目標の位置(範囲の矩形)が`(x, y, width, height)`の形でセットされます。

```python
x = bounds[0]
y = bounds[1]
w = bounds[2]
h = bounds[3]
cv2.rectangle(frame, (int(x), int(y)), (int(x + w), int(y + h)), (255, 255, 0), 2)
```

目標が見つかった場合は元画像の上に`cv2.rectangle`で黄色の枠を描画します。最後の引数`2`は線の太さです。

```python
cv2.imwrite(os.path.join(dst_dir, os.path.basename(files[i])), frame)
```

最後に結果を上で作った出力用ディレクトリに保存します。