はじめまして。技術研究所の堀越と申します。

 

皆さんは動画から物体を検知し、その移動軌跡を取得する方法をご存じでしょうか?

今回は、Optical Flowを使用して物体の移動軌跡を描画するPythonプログラムを作成したので紹介します。

Optical Flowとは

オプティカルフロー(Optical Flow)とは、フレーム間の物体の物体の動きを検出して速度をベクトルで表示する手法のことをいいます。

オプティカルフローは次のような仮定に基づいて計算を行っています。

  • 連続フレーム間で物体の画像上の明るさは変わらない
  • 隣接する画素は似たような動きをする

オプティカルフローの推定法としては、勾配法とブロックマッチング法があります。

【 勾配法 】

画像の時空間微分の拘束方程式による条件からフローベクトルを推定する手法。          短い処理時間で検出できるが、検出の精度が低い。

【 ブロックマッチング法 】

画像をある大きさの領域で分割して次のフレームの画像中を検索し、前フレームの注目領域との類似度が最も高い領域を検出する手法。                               処理時間が長いが濃度値の変化やノイズに強く高い精度で検出を行うことが出来る。

今回のPythonプログラムでは、勾配法の一種であるLucas-Kanade法という方法を使用して実装を行いました。

処理内容

Optical Flowの処理の流れは以下のようになります

① 最初のフレームの特徴点を検出(Shi-Tomasi法を使用)

Shi-Tomasi法のパラメータは以下のように設定しました

ST_param = dict( maxCorners = 50, #特徴点の最大数
qualityLevel = 0.1, #特徴点を選択する閾値
minDistance = 7, #特徴点間の最小距離
blockSize = 7) #特徴点の計算に使う周辺領域のサイズ

【 各パラメータの説明 】

maxCorners:特徴点の最大数(値が大きいほど多くの特徴点を見つけます)       qualityLevel:特徴点を選択する閾値(値が大きいほど特徴点が厳選されます)      minDistance:特徴点間の最小距離(他の特徴点がこの距離より近い場合は特徴点としない)blockSize:特徴点の計算に使う周辺領域のサイズ(値が大きいほど計算に使う領域が大きくなる

切り出したフレームをグレースケールへ変換します

gray1 = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

切り出したフレームに対し、Shi-Tomasi法で特徴点の検出します

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

画像の特徴点がgoodFeaturesToTrackの戻り値としてft1に格納されます。

移動軌跡描画用のマスク画像を取得します

# 軌跡描画用のマスクを生成
mask = np.zeros_like(frame)

② 次のフレームのOptical Flowを計算(Lucas-Kanade法を使用)

Lucas-Kanade法のパラメータは以下のように設定しました

LK_param = dict( winSize = (15, 15), #オプティカルフローの推定の計算に使う周辺領域サイズ(小:敏感、大:鈍感)
maxLevel = 2, #ピラミッド数
criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)) #繰り返しの終了条件

【 各パラメータの説明 】

winSize:オプティカルフローの推定の計算に使う周辺領域サイズ(小さくするとノイズに敏感にな り、大きな動きを見逃す可能性がある)                        maxLevel:ピラミッド数(0の場合、ピラミッドを使用しない。ピラミッドを使用ことで、画像のさまざまな解像度でオプティカルフローを見つけることができる)               criteria:繰り返しの終了条件(条件が満たされたらアルゴリズムの計算を終了する。cv2.TERM_CRITERIA_EPS:指定した精度(epsilon)に到達したら計算を終了する。cv2.TERM_CRITERIA_COUNT:指定した繰り返しの最大回数(count)に到達したら計算を終了。  今回は、計算の繰り返し回数を10回、精度を0.03に設定。)

次のフレームを切り出してグレースケールへ変換します

gray2 = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

Lucas-Kanade法でフレーム間の特徴点のオプティカルフローを計算します

ft2, status, err = cv2.calcOpticalFlowPyrLK(gray1, gray2, ft1, None, **LK_param )

ft2:画像の特徴点がcalcOpticalFlowPyrLKの戻り値としてft2に格納されます。        status:Optical Flowが検知できた場合は1、検知できなかった場合は0が格納されます。    err:移動前の特徴点の周辺領域と,移動後の特徴点の周辺領域との差を含む出力 vectorが格納されます。

③ フレームにOptical Flowを描画

計算したオプティカルフローを画像に描画していきます

#オプティカルフローが計算できた場合
if status is not None:
#オプティカルフローを検出した特徴点を取得
#1フレーム目
g1 = ft1[status == 1]
#2フレーム目
g2 = ft2[status == 1]
#特徴点とオプティカルフローをマスクに描画する
for i, (pt2, pt1) in enumerate(zip(g2, g1)):
#1フレーム目の特徴点座標
x1, y1 = pt1.ravel()
#2フレーム目の特徴点座標
x2, y2 = pt2.ravel()
#軌跡をマスクに描画
mask = cv2.line(mask, (int(x2), int(y2)), (int(x1), int(y1)), [0, 0, 200], 2)
#現フレームにオプティカルフローを描画
frame = cv2.circle(frame, (int(x2), int(y2)), 5, [0, 0, 200], -1)
#フレームとマスクを合成
img = cv2.add(frame, mask)

④ ①~③をフレーム最後まで繰り返す

オプティカルフロー処理結果

オプティカルフローの処理結果を出力すると以下のような画像が得られます。

一番最初のフレーム

少し時間がたった後のフレーム

最後のフレーム

このように動画上の物体を検知し、その移動軌跡を描画することができます。

まとめ

いかがだったでしょうか。今回は、オプティカルフローという計算方法を使用して動画上の物体移動軌跡の描画を行ってみました。オプティカルフローの計算方法は他にもありますので、それはまた別の機会でご紹介させていただきます。