【Python/OpenCV】オプティカルフロー(Lucas-Kanade法)で物体追跡

Python版OpenCVでLucas-Kanade法を実装し、物体追跡(オプティカルフローを計算)する方法をソースコード付きで解説します。

オプティカルフローで物体追跡

オプティカルフローとは、デジタル画像中の物体の動きを「ベクトル」で表したものです。
主に移動物体の検出や、その動作の解析などによく用いられています。以下は振り子のオプティカルフローを赤い線で描画しているものです。

【実行例】

しかしオプティカルフロー(=物体の移動ベクトル)を一意的に求めることは困難です。
一般的には推定によって動き(ベクトル)を求めます。
オプティカルフローを推定する手法は代表的なモノに「LucasKanade法」や「Horn-Schunk法」があります。今回は、Shi-Tomasi法で求めた特徴点を「LucasKanade法」で追跡してみます。

なお、オプティカルフローの原理については、以下ページで別途解説しています。

【オプティカルフローとは】推定の原理・特徴・計算式
オプティカルフローとは?移動量の推定方法、原理、計算式についてまとめました。

また、Shi-Tomasi法による特徴点(コーナー)の検出原理については以下ページで別途解説しています。

Shi-Tomasi法で特徴点を検出する原理・計算式
Shi-Tomasi法で特徴点を検出する原理や計算式についてまとめました。

サンプルコード

サンプルプログラムのソースコードです。


コード解説

サンプルコードの重要箇所を詳しく解説します。

  • 指定されたビデオファイルを読み込みます。ここでは、input2.mp4というファイルを読み込んでいます。
cap = cv2.VideoCapture("/Users/github/sample/python/opencv/video/input2.mp4")
  • Shi-Tomasi法のパラメータを設定しています。
ft_params = dict(maxCorners=100, qualityLevel=0.3, minDistance=7, blockSize=7)
パラメータ 説明
maxCorners 検出する最大の特徴点数を指定します。ここでは最大100個の特徴点を検出します。
qualityLevel 特徴点の品質を決めるしきい値です。値が高いほど、品質の高い特徴点のみが選ばれます。
minDistance 特徴点間の最小距離を指定します。これにより、近接する特徴点が除外されます。
blockSize 特徴点を計算するためのブロックサイズを指定します。

以下では、Lucas-Kanade法のパラメータを設定します。

lk_params = dict(winSize=(15, 15), maxLevel=2, criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
パラメータ 説明
winSize オプティカルフローを計算するためのウィンドウサイズを指定します。ここでは15×15ピクセルのウィンドウを使用します。
maxLevel ピラミッドのレベル数を指定します。ここでは2レベルのピラミッドを使用します。
criteria 探索アルゴリズムの終了条件を指定します。ここでは、10回の反復または0.03の精度に達するまで計算を続けます。

以下で、動画から最初のフレームを取得し、グレースケール変換しています。

ret, frame = cap.read()
gray1 = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

以下では、 Shi-Tomasi法を使用して、グレースケール画像から特徴点を検出します。

ft1 = cv2.goodFeaturesToTrack(gray1, mask=None, **ft_params)

以下では、フレームと同じサイズのゼロ配列(黒い画像)を生成します。これは特徴点の軌跡を描画するために使用します。

mask = np.zeros_like(frame)

以下では、動画からフレームを取得し、オプティカルフローを求めて画面に描画することを繰り返します。

# 動画終了まで繰り返し
while(cap.isOpened()):
    # 次のフレームを取得し、グレースケールに変換
    ret, frame = cap.read()
    gray2 = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # Lucas-Kanade法でフレーム間の特徴点のオプティカルフローwp計算
    ft2, status, err = cv2.calcOpticalFlowPyrLK(
        gray1, gray2, ft1, None, **lk_params)

    # オプティカルフローを検出した特徴点を取得(1なら検出)
    good1 = ft1[status == 1]  # 1フレーム目
    good2 = ft2[status == 1]  # 2フレーム目

    # 特徴点とオプティカルフローをフレーム・マスクに描画
    for i, (pt2, pt1) in enumerate(zip(good2, good1)):
        x1, y1 = pt1.ravel()  # 1フレーム目の特徴点座標
        x2, y2 = pt2.ravel()  # 2フレーム目の特徴点座標

        # 軌跡を描画(過去の軌跡も残すためにmaskに描く)
        mask = cv2.line(mask, (x2, y2), (x1, y1), [0, 0, 200], 2)

        # 現フレームにオプティカルフローを描画
        frame = cv2.circle(frame, (x2, y2), 5,  [0, 0, 200], -1)

    # フレームとマスクの論理積(合成)
    img = cv2.add(frame, mask)

    # ウィンドウに表示
    cv2.imshow('mask', img)       

    # 次のフレーム、ポイントの準備
    gray1 = gray2.copy() # 次のフレームを最初のフレームに設定
    ft1 = good2.reshape(-1, 1, 2) # 次の点を最初の点に設定

    # qキーが押されたら途中終了
    if cv2.waitKey(30) & 0xFF == ord('q'):
        break
  • cv2.calcOpticalFlowPyrLK
    • Lucas-Kanade法を使用して、前のフレームと現在のフレーム間の特徴点のオプティカルフローを計算します。
  • status
    • 特徴点が追跡されたかどうかを示すステータス配列です。
  • good1good2
    • 追跡された特徴点の座標を取得します。
  • cv2.line
    • 特徴点の軌跡をマスクに描画します。
  • cv2.circle
    • 現在のフレームに特徴点を描画します。
  • cv2.add
    • フレームとマスクを合成します。
  • cv2.imshow
    • 合成画像をウィンドウに表示します。
  • cv2.waitKey(30)
    • 30ミリ秒待機し、’q’キーが押されたらループを終了します。

以下は終了処理です。すべてのウィンドウを閉じ、ビデオキャプチャーを解放します。

cv2.destroyAllWindows()
cap.release()

関連ページ

【PythonとOpenCVで画像処理超入門】使い方とサンプルコードを解説
Python版OpenCVで画像処理プログラミングを行う方法を入門者向けにソースコード付きで解説するページです。
この記事を書いた人
西住技研

Python使用歴10年以上。研究、仕事、趣味でデータ分析や作業自動化などに活用してきたノウハウを情報発信しています。
詳しいプロフィールやお問合せはこちらのページまで。
YoutubeX(旧Twitter)でも情報発信中です!

西住技研をフォローする
OpenCV

コメント