け日記

最近はPythonでいろいろやってます

OpenCV: Optical Flowで物体の動きをトラックする

先週に引き続き、OpenCVを使っていきます。

Optical Flow

Optical Flowを使って、物体の動きを検出していきます。OpenCVのチュートリアルと↓の記事を参考にしました。

Optical Flowは動画から切り出されたフレーム画像間の特徴点の差分 (= 動き) をベクトル化することで、主に動画内の物体の動きのトラッキングなどに用いられます。
車が走っている動画1から2枚のフレームを切り出したとき、車の動きを左下へのベクトルとして表現します。

Optical Flowには2つのポイントがあります。

  1. 特徴点をどうやって検出するか
  2. フレーム間の特徴点の一致をどうやって判別するか

Optical Flowの特徴点の検出では、Shi-Tomasiのコーナ検出がよく使われるそうです。 (提案論文2のタイトルもそのものズバリの"Good Features to Track"です。ストレート。)

ベースとなっているのはHarris3のコーナ検出です (Harrisのコーナ検出についてはこちらのPDFの解説が詳しいです) 。Harrisのコーナ検出では最後に得られる2つの固有値が大きい画素 (複数のエッジ = コーナ) として判定しています。Shi-Tomasiはこの閾値の計算方法が異なるだけです。

特徴点の一致を判定する方法として、今回はLucas-Kanade法[^3]を使いました。以下の条件に基づいて、フレーム前後で特徴点をトラックします。こちらのスライドが参考になります。

  • 特徴点の周囲の画素も同様に動く (空間的整合性) と仮定している
  • ピラミッド (解像度が1, 1/2, 1/4, ... となる画像の集合) を使って、大きな動きも小さな動きに変換することで追随させる

実装

それでは実装していきます。最初に動画をロードします。

  • OpenCVではVideoCaptureクラスで動画ファイルを扱います
import numpy as np
import matplotlib.pyplot as plt
import cv2
from google.colab.patches import cv2_imshow  # Google Colaboratory用

# NHKクリエイティブライブラリの動画
# https://www2.nhk.or.jp/archives/creative/material/view.cgi?m=D0002060316_00000
file_path = "D0002060316_00000_V_000.mp4"

# 動画ファイルのロード
video = cv2.VideoCapture(file_path)

特徴点抽出

最初のフレームで特徴点を抽出します。動画は150フレーム目から210フレーム目まで使用します (合計60フレーム) 。

  • setメソッドで、1つ目の引数に定数CAP_PROP_POS_FRAMES、2つ目の引数に取得したいフレームインデックスを指定
  • Shi-TomashiのアルゴリズムはgoodFeaturesToTrackメソッドで実装されてます
    • パラメータはコメントのとおりです
# 150フレームから210フレームまで5フレームごとに切り出す
start_frame = 150
end_frame = 210
interval_frames = 5
i = start_frame + interval_frames

# 最初のフレームに移動して取得
video.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
ret, prev_frame = video.read()

# グレースケールにしてコーナ特徴点を抽出
prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_RGB2GRAY)

feature_params = {
    "maxCorners": 200,  # 特徴点の上限数
    "qualityLevel": 0.2,  # 閾値 (高いほど特徴点数は減る)
    "minDistance": 12,  # 特徴点間の距離 (近すぎる点は除外)
    "blockSize": 12  # 
}
p0 = cv2.goodFeaturesToTrack(prev_gray, mask=None, **feature_params)

# 特徴点をプロットして可視化
for p in p0:
    x,y = p.ravel()
    cv2.circle(prev_frame, (x, y), 5, (0, 255, 255) , -1)
    
cv2_imshow(prev_frame)

抽出後の画像を見ると傾向がわかります。

  • 文字 (左上) や木や枝はコーナーが多いので、そういった部分は特徴点として抽出されやすくなってます
  • 自動車もボンネットとバンパーの間、フェンダの折目など、カーブしている部分が抽出されてます

f:id:ohke:20190810215828p:plain
150フレーム目 (最初のフレーム)

Optical Flow

最後にOptical Flowの実装です。

  • Lucas-Kanade法はcalcOpticalFlowPyrLKで実装されてます
    • 前後のフレームと前のフレームの特徴点を渡すと、後のフレームにおいて対応する各特徴点のインデックスが返されます
      • 対応する特徴点の有無は2つ目の返り値で判定 (0:無い, 1:ある)
    • パラメータはコメントのとおりです
# OpticalFlowのパラメータ
lk_params = {
    "winSize": (15, 15),  # 特徴点の計算に使う周辺領域サイズ
    "maxLevel": 2,  # ピラミッド数 (デフォルト0で、2の場合は1/4の画像まで使われる)
    "criteria": (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)  # 探索アルゴリズムの終了条件
}

# 可視化用
color = np.random.randint(0, 255, (200, 3))
mask = np.zeros_like(prev_frame)

for i in range(start_frame + interval_frames, end_frame + 1, interval_frames):
    # 次のフレームを取得してグレースケールにする
    video.set(cv2.CAP_PROP_POS_FRAMES, i)
    ret, frame = video.read()
    frame_gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
    
    # OpticalFlowの計算
    p1, status, err = cv2.calcOpticalFlowPyrLK(prev_gray, frame_gray, p0, None, **lk_params)
    
    # フレーム前後でトラックが成功した特徴点のみを
    identical_p1 = p1[status==1]
    identical_p0 = p0[status==1]
    
    # 可視化用
    for i, (p1, p0) in enumerate(zip(identical_p1, identical_p0)):
        p1_x, p1_y = p1.ravel()
        p0_x, p0_y = p0.ravel()
        mask = cv2.line(mask, (p1_x, p1_y), (p0_x, p0_y), color[i].tolist(), 2)
        frame = cv2.circle(frame, (p1_x, p1_y), 5, color[i].tolist(), -1)
    
    # 可視化用の線・円を重ねて表示
    image = cv2.add(frame, mask)
    cv2_imshow(image)

    # トラックが成功した特徴点のみを引き継ぐ
    prev_gray = frame_gray.copy()
    p0 = identical_p1.reshape(-1, 1, 2)

トラックした結果は以下です。

180フレーム目 (特徴点抽出から30フレーム経過後) を見ると、自動車の特徴点は自動車とともに動いていますが、風景の特徴点は動かずそのままであることがわかります。

f:id:ohke:20190810192503p:plain
180フレーム目

さらに30フレームが経過した210フレーム目を見ると、今度は自動車と風景の特徴点が重なり、異なるところにトラックされている点が多いことがわかります。また自動車の動きから外れてしまった特徴点なども見られます。
ある程度のフレーム数が経過したら特徴点を取り直す、といった工夫が必要なようです。

f:id:ohke:20190810192610p:plain
210フレーム目

まとめ

今回はOpenCVを使ってOptical Flowで物体の動きをトラッキングしました。


  1. NHKクリエイティブ・ライブラリーの動画を使用しました。https://www2.nhk.or.jp/archives/creative/material/view.cgi?m=D0002060316_00000

  2. Shi, Jianbo & Tomasi, Carlo. (2000). Good Features to Track. Proceedings / CVPR, IEEE Computer Society Conference on Computer Vision and Pattern Recognition. IEEE Computer Society Conference on Computer Vision and Pattern Recognition. 600. 10.1109/CVPR.1994.323794.

  3. Chris Harris and Mike Stephens. A combined corner and edge detector. In Proc. of Fourth Alvey Vision Conference. 1988.