OpenCVを使ったPythonでの画像処理について、Lucas-Kanade法を使った物体の追跡(Object Tracking)についてはすでに扱いました。
このLucas-Kanade法の出力するオプティカルフローの特徴はShi-Tomasiアルゴリズムを使ったコーナーの検出なので「疎」であることがわかります。
ここでは逆に密なオプティカルフローの検出アルゴリズムを扱っていきます。すでにOpenCVに用意されているアルゴリズムで、画像中の全画素に対してオプティカルフローを計算します。
calcOpticalFlowFarneback()
高密度オプティカルフローはcalcOpticalFlowFarneback()を使って計算します。
このアルゴリズムの引数は次のようになります。
calcOpticalFlowFarneback(prevImg, nextImg, flow, pyrScale, levels, winsize, iterations, polyN, polySigma, flags)
- prevImg – 8ビットシングルチャンネルの1番目の入力画像。
- nextImg – prevImg と同じサイズで同じ型の2番目の入力画像。
- flow – サイズはprevImgと同じサイズで、タイプCV_32FC2の計算済みフロー画像。
- pyrScale – 各画像に対する画像ピラミッドを構築するためのスケール (<1) を指定します。pyrScale=0.5は隣り合う各層において、次の各層が前の層の半分のサイズになっている古典的画像ピラミッドを意味します。
- levels – 最初の画像を含む画像ピラミッドの層の数。 levels=1は追加の層が作成されず,元画像だけが利用されることを意味します。
- winsize – 平均化ウインドウサイズ。この値が大きいほど画像のノイズに対するアルゴリズムの堅牢性が向上し、高速な動きを検出できる場合が多くなります。しかし、ボケたモーションフィールドを生成することになります。
- iterations – アルゴリズムが画像ピラミッドの各レベルで行う反復回数。
- polyN – 各ピクセルにおける多項式展開を求めるために利用されるピクセル近傍領域のサイズ。この値が大きくなると画像はより滑らかなサーフェイスで近似され、アルゴリズムの堅牢性は増します。しかし同時にボケたモーションフィールドも増加します。一般的にpolyNは5あるいは7とされいます。
- polySigma – 多項式展開の基底として利用される導関数を滑らかにするためのガウス分布の標準偏差。polyN=5ならばpolySigma=1.1、polyN=7ならばpolySigma=1.5が適当な値とされています。
- flags – 処理フラグ。以下の値の組み合わせとなります。
- OPTFLOW_USE_INITIAL_FLOW – 入力flowをフローの初期推定値として利用します。
- OPTFLOW_FARNEBACK_GAUSSIAN – オプティカルフロー推定のために、同サイズのボックスフィルタの代わりとしてwinsize × winsizeのサイズのガウシアンフィルタを利用します。通常、このオプションによって処理速度は低下しますが、ボックスフィルタを利用する場合よりも正確なフローを求めることができます。また、同程度の堅牢性を得るためには、ガウシアン窓のwinsizeをより大きくする必要があります。
密なオプティカルフロー
calcOpticalFlowFarneback()を使って密なオプティカルフローをみていきましょう。
このアルゴリズムは、出力として、オプティカルフローベクトルを格納した2チャンネルの配列が返ります。オプティカルフローの強度と方向を計算し、カラーコード化した画像を表示することになります。方向はHue成分、強度はValue成分によって表されています。
カラーコードの成分方向についてはこちらを参照してください。
では、コードを書いていきましょう。
import cv2
import numpy as np
cap = cv2.VideoCapture(0)
ret, frame1 = cap.read()
ライブラリをインポートして、cv2.VideoCapture(0)で内臓webカメラに接続し、cap.read()で映像を読み込んでいます。
prvsImg = cv2.cvtColor(frame1,cv2.COLOR_BGR2GRAY)
hsv_mask = np.zeros_like(frame1)
hsv_mask[:,:,1] = 255
読み込んだ画像のframe1を直前のイメージとして、cvtColor()を使ってグレースケールにします。HSVベースのマスクをnp.zeros_like()を使って作ります。これをHSVのSaturationの部分に255を入れてマスクにします。
while True:
ret, frame2 = cap.read()
nextImg = cv2.cvtColor(frame2,cv2.COLOR_BGR2GRAY)
flow = cv2.calcOpticalFlowFarneback(prvsImg,nextImg, None, 0.5, 3, 15, 3, 5, 1.2, 0)
mag, ang = cv2.cartToPolar(flow[:,:,0], flow[:,:,1],angleInDegrees=True)
hsv_mask[:,:,0] = ang/2
hsv_mask[:,:,2] = cv2.normalize(mag,None,0,255,cv2.NORM_MINMAX)
bgr = cv2.cvtColor(hsv_mask,cv2.COLOR_HSV2BGR)
cv2.imshow('frame2',bgr)
if cv2.waitKey(30) & 0xff == 27:
break
prvsImg = nextImg
cap.release()
cv2.destroyAllWindows()
while文を使って、キャプチャーを実行し、read()で読み込みます。retはデータの読み込みの可否のture/false、frame2で映像のパラメータを取得しています。これを直後の2番目のイメージとしてnextImgとしています。
calcOpticalFlowFarneback()に各パラメータを渡してアルゴリズムの計算をします。各パラメータはほぼ適当な値とされているものを入れているだけです。
アルゴリズムで返された値を、cv2.cartToPolar()を使って2次元ベクトルの大きさと角度を求めています。順にx座標の配列、y座標の配列で、angleInDegreesはラジアン表記にしています。
hsv_maskのhue成分とValue成分に角度と大きさのデータを入れています。hue成分をマスクに扱うあたっては角度自体は大きくなくてもいいので半分にしています。Value成分については正規化関数cv2.normalize()でコントラストを低減しています。0から255の範囲で、NORM_MINMAXを使ってその幅に収めるようにしています。
hsv_maskをcv2.cvtColor()を使ってBGR形式に変換しています。これをimshow()を使って描画しています。
if cv2.waitKey(30) & 0xff == 27: で[esc]キーを押された時にbreak処理をします。
prvsImg = nextImgで前の画像を次の画像に更新させてループさせます。
ここまでがwhile文の処理です。
最後に、cap.release()でデバイスを解放し、cv2.destroyAllWindows()で全て終了させます。
このPythonスクリプトをターミナルで実行すると次のようなイメージの動画が映し出されます。
物体の動きの追跡がLucas-Kanade法と性格が違うのがわかると思います。オプティカルフローの疎と密の違いがよくわかると思います。
最後に
OpenCVを使ったPythonでの画像処理について、Lucas-Kanade法は「疎」の物体追跡の検出でしたが、ここでは逆に密なオプティカルフローを扱いました。
密なオプティカルフローの検出アルゴリズムにはcalcOpticalFlowFarneback()を利用しました。
Lucas-Kanade法の出力するオプティカルフローの特徴はShi-Tomasiアルゴリズムを使ったコーナーの検出であったのに対して、calcOpticalFlowFarneback()では画像中の全画素に対してオプティカルフローを計算しています。