OpenCVのcalcOpticalFlowPyrLK関数を使った光流法による動き検出
概要
OpenCVのcalcOpticalFlowPyrLK
という機能は、Lucas-Kanade
法という方法を使って、ビデオの中で物体がどう動いているかを検出することができます。これは、ビデオの一つの画面から次の画面へと移るときに、物体がどう動いたかを推測する方法です。これは、ビデオの中で何が起こっているかを理解するのにとても役立ちます。特に、物体がどこに移動したかを追跡したり、ビデオを小さくする(圧縮する)ために使われます。
「calcOpticalFlowPyrLK」の機能は、ビデオの一つの画面で特に注目すべき点(特徴点)を見つけ、それが次の画面でどう動いたかを計算します。これにより、物体がどのように動いているかのパターンを理解し、それをもとに物体がこれからどう動くかを予測したり、ビデオの中で何が起こっているかを分析したりすることができます。
使用する画像
ファイル:windmill_-_112957 (540p).mp4
出力結果
プログラム全体
#解説1
import cv2
import numpy as np
#解説2
# ShiTomasiのコーナー検出のためのパラメータ
fparams = dict( maxCorners = 100,
qualityLevel = 0.3,
minDistance = 7,
blockSize = 7 )
# lucas kanadeのオプティカルフローのためのパラメータ
lparams = dict( winSize = (15,15),
maxLevel = 2,
criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
#解説3
# ランダムな色を生成
color = np.random.randint(0,255,(100,3))
# ビデオを読み込む
cap = cv2.VideoCapture('windmill_-_112957 (540p).mp4')
# 最初のフレームを取得し、それに含まれるコーナーを見つける
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
p0 = cv2.goodFeaturesToTrack(old_gray, mask = None, **fparams)
# 描画用のマスク画像を作成
mask = np.zeros_like(old_frame)
#解説4
while(1):
ret,frame = cap.read()
if not ret or frame is None:
break
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# オプティカルフローを計算
p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lparams)
# 良い点を選択
good_new = p1[st==1]
good_old = p0[st==1]
# トラックを描画
for i,(new,old) in enumerate(zip(good_new,good_old)):
a,b = new.ravel()
c,d = old.ravel()
a,b,c,d = int(a), int(b), int(c), int(d) # 座標を整数に変換
mask = cv2.line(mask, (a,b),(c,d), color[i].tolist(), 2)
frame = cv2.circle(frame,(a,b),5,color[i].tolist(),-1)
img = cv2.add(frame,mask)
# 画像を表示
cv2.imshow('Optical Flow', img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 前のフレームと前の点を更新
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1,1,2)
cap.release()
cv2.destroyAllWindows()
解説
解説1: ライブラリのインポート
import cv2
import numpy as np
この部分では、プログラムで使用するライブラリをインポートしています。cv2
はOpenCVという画像処理ライブラリで、画像や動画の読み込み、加工、表示などを行うために使用します。numpy
は数値計算ライブラリで、配列や行列の操作、数学的な計算などを行うために使用します。これらのライブラリをインポートすることで、その後のコードでこれらのライブラリの機能を利用できます。
解説2: パラメータの設定
# ShiTomasiのコーナー検出のためのパラメータ
fparams = dict( maxCorners = 100,
qualityLevel = 0.3,
minDistance = 7,
blockSize = 7 )
# lucas kanadeのオプティカルフローのためのパラメータ
lparams = dict( winSize = (15,15),
maxLevel = 2,
criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
このプログラムでは、2つの主要な部分、ShiTomasiのコーナー検出とLucas-Kanadeのオプティカルフロー計算のためのパラメータを設定しています。
ShiTomasi | このアルゴリズムは写真の中で色や形が大きく変わるコーナーを検出しています。ShiTomasiの方法は、写真の各ピクセルを見て、その周囲がどれだけ特徴があるのかを数値で評価します。そして、最も特徴のあるピクセル(つまり、最も高い評価を得たピクセル)をコーナーとして選びます。下記がそれぞれの引数に関する説明です。
|
||||||||
---|---|---|---|---|---|---|---|---|---|
Lucas-Kanade |
このアルゴリズムは動画の連続する2つのフレーム間で物体がどのように動いたかを検出しています。例えば、ゲームでキャラクターが画面上を動くとき、その動きを追跡するのに役立ちます。Lucas-Kanade の方法は、最初のフレームで見つけたコーナー(ShiTomasi の方法で見つけたもの)が次のフレームでどこに移動したかを計算します。これにより、物体の動きのパターンを理解し、それに基づいて予測や分析を行うことができます。下記がそれぞれの引数に関する説明です。
|
解説3: 初期設定と最初のフレームの処理
# ランダムな色を生成
color = np.random.randint(0,255,(100,3))
# ビデオを読み込む
cap = cv2.VideoCapture('video.avi')
# 最初のフレームを取得し、それに含まれるコーナーを見つける
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
p0 = cv2.goodFeaturesToTrack(old_gray, mask = None, **fparams)
# 描画用のマスク画像を作成
mask = np.zeros_like(old_frame)
ここでは、プログラムの初期設定と最初のフレームの処理を行っています。まず、color = np.random.randint(0,255,(100,3))
でランダムな色を生成し、cap = cv2.VideoCapture('video.avi')
でビデオを読み込んでいます。次に、ret, old_frame = cap.read()
で最初のフレームを取得し、そのフレームからコーナー(特徴的な点)を検出し、cv2.cvtColor
でモノクロ変換します。
次に、p0 = cv2.goodFeaturesToTrack(old_gray, mask = None, **fparams)
では、グレースケールに変換したフレームから「特徴的な点」(コーナー)を見つけています。これらの点は、物体の形状を表すのに役立ち、また動きを追跡するのにも使われます。最後にmask = np.zeros_like(old_frame)
で、old_frameと同じ形状とデータ型を持つ、すべての要素が0の配列(つまり黒い画像)を作成しています。これらの処理は、この後のフレーム間のオプティカルフローの計算や描画のためで使用されます。
解説4: フレーム間のオプティカルフローの計算と描画
while(1):
ret,frame = cap.read()
if not ret or frame is None:
break
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# オプティカルフローを計算
p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lparams)
# 良い点を選択
good_new = p1[st==1]
good_old = p0[st==1]
# トラックを描画
for i,(new,old) in enumerate(zip(good_new,good_old)):
a,b = new.ravel()
c,d = old.ravel()
a,b,c,d = int(a), int(b), int(c), int(d) # 座標を整数に変換
mask = cv2.line(mask, (a,b),(c,d), color[i].tolist(), 2)
frame = cv2.circle(frame,(a,b),5,color[i].tolist(),-1)
img = cv2.add(frame,mask)
# 画像を表示
cv2.imshow('Optical Flow', img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 前のフレームと前の点を更新
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1,1,2)
この部分では、ビデオの各フレーム間でのオプティカルフロー(物体の動き)を計算し、その結果を描画しています。まず、新しいフレームを読み込み、そのフレームでのオプティカルフロー(ピクセルの動き)を計算しています。次に、計算結果から良い点(追跡すべき点)を選択し、それらの点の移動を描画しています。最後に、描画結果を表示し、前のフレームと前の点を更新しています。これらの処理を繰り返すことで、ビデオ全体でのオプティカルフローを計算し、その結果をリアルタイムで描画しています。
解説5: リソースの解放
cap.release()
cv2.destroyAllWindows()
この部分では、プログラムの最後にリソースを解放しています。cap.release()
はビデオキャプチャのリソースを解放し、cv2.destroyAllWindows()
は作成したウィンドウを全て閉じるための関数です。これらの処理を行うことで、プログラムが終了したときにリソースが適切に解放され、メモリリーク等の問題を防ぎます。
おまけ
産業用カメラは高フレームレートをサポートしており、グローバルシャッターという撮影方式を採用したモデルもあります。これにより、高速に動く対象でも歪みなくクリアに捉えることが可能です。一方、Webカメラのフレームレートは大体60fps程度で、高速に動く対象を撮影すると、ブラー(ぼやけ)や歪みが発生することがあります。
なお、TheImagingSource社の下記のUSBカメラでは、解像度を256×24に設定することで、フレームレートを最大6310fpsまで上げて撮影することが可能です。これにより、非常に高速な動きを捉えることができます。
https://www.argocorp.com/cam/usb3/tis/DxK33UP1300.html