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法4を使いました。以下の条件に基づいて、フレーム前後で特徴点をトラックします。こちらのスライドが参考になります。

  • 特徴点の周囲の画素も同様に動く (空間的整合性) と仮定している
  • ピラミッド (解像度が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.

  4. B. D. Lucas, T. Kanade, et al. An iterative image registration technique with an application to stereo vision. In IJCAI, 1981.