OpenCVのOptical Flowを試してみる
画像からの動き検出をやってみたいと思っています。
OpenCVの公式チュートリアルの中で、Optical Flowが取り上げられていて、これでできそうなので、やってみます。
対象データ
同じデータを使っても面白くないので、別のデータで。
MOTChallengeの中のMOT16のデータを使ってみたいと思います。
https://motchallenge.net/data/MOT16/
色々と動画データがありますが、特に密なOptical Flowを計算するときは背景が動いていない(カメラが固定されている)ほうが効果が分かりやすそうなので、そういうデータを選びます。
- MOT16-09
- MOT16-04
- MOT16-02
- MOT16-08
- MOT16-03
- MOT16-01
今回は、この中のMOT16-09を使ってみます。
動画データは上記リンクからダウンロード。
Optical Flow計算実施
チュートリアルのコード通りでできると思いますが、途中の状態も見ながらやってみようと思います。
まずはLucas-Kanadeの手法から。
import numpy as np import cv2 from matplotlib import pyplot as plt img_path = 'C:/work/MOT16/train/MOT16-09/img1/%06d.jpg' cap = cv2.VideoCapture(img_path) print(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) print(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
1920.0
1080.0
def plot_image(img): img0 = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) plt.imshow(img0), plt.xticks([]), plt.yticks([]) plt.show() print('MOT16 - https://motchallenge.net/data/MOT16/')
ret, old_frame = cap.read() plot_image(old_frame)
↑ MOTChallengeの動画はCreative Commons Attribution-NonCommercial-ShareAlike 3.0 Licenseなので、とりあえずいちいちリンクを貼っておきます。
これでいいのかな…
Creative Commons — Attribution-NonCommercial-ShareAlike 3.0 Unported — CC BY-NC-SA 3.0
ブログに画像を入れたい方必見!「クリエイティブ・コモンズ」の意味と画像の探し方を徹底解説! | インターネットビジネスラボ
クリエイティブ・コモンズ・ライセンスとは | クリエイティブ・コモンズ・ジャパン
cv2.VideoCapture
では、上記の通り連番画像ファイルの読み込みが可能。
OpenCV: cv::VideoCapture Class Reference
以下は下準備。
# params for ShiTomasi corner detection feature_params = dict( maxCorners = 100, qualityLevel = 0.3, minDistance = 7, blockSize = 7 ) # Parameters for lucas kanade optical flow lk_params = dict( winSize = (15, 15), maxLevel = 2, criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)) # Create some random colors color = np.random.randint(0, 255, (100, 3))
以下は、cv2.goodFeaturesToTrack()
関数で特徴点を検出しています。
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_RGB2GRAY)
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
print(type(p0)) print(p0.shape) print(p0[:5,:,:]) print(p0.dtype)
<class 'numpy.ndarray'>
(100, 1, 2)
[[[1437. 179.]]
[[1421. 174.]]
[[1305. 144.]]
[[1390. 137.]]
[[ 401. 39.]]]
float32
maxCorners
引数を100にしているので、100点出てきています。
img_with_corners = old_frame.copy() for p in p0[:,0,:]: cv2.circle(img_with_corners, center=(int(p[0]), int(p[1])), radius=10, color=(0,255,0), thickness=3) plot_image(img_with_corners)
人じゃないところに特徴点検出してしまっている…
背景がガチャガチャしているとあまり良くないか。
チュートリアルのコード続きです。
ただし、結果を動画データに保存できるようにコード追加しています。
codec = cv2.VideoWriter_fourcc(*'mp4v') writer = cv2.VideoWriter('MOT16-09-LK.mp4', codec, 30.0, (1920,1080)) # Create a mask image for drawing purposes mask = np.zeros_like(old_frame) while(1): ret, frame = cap.read() if not ret: print('No frames grabbed!') break frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # calculate optical flow p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params) # Select good points if p1 is not None: good_new = p1[st==1] good_old = p0[st==1] # draw the tracks for i, (new, old) in enumerate(zip(good_new, good_old)): a, b = new.ravel() c, d = old.ravel() mask = cv2.line(mask, (int(a), int(b)), (int(c), int(d)), color[i].tolist(), 2) frame = cv2.circle(frame, (int(a), int(b)), 5, color[i].tolist(), -1) img = cv2.add(frame, mask) writer.write(img) img = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA) cv2.imshow('frame', img) k = cv2.waitKey(30) & 0xff if k == 27: break # Now update the previous frame and previous points old_gray = frame_gray.copy() p0 = good_new.reshape(-1, 1, 2) cv2.destroyAllWindows() writer.release()
No frames grabbed!
それらしい動きが取得できているよう。
動画データも残せました。(一部のみ切り出して下記に示しています。)
処理は結構重たく、上記のコードの実行時間は46.6(s)でした。(動画自体は18秒。PC性能は低い。)
全部の軌跡を残しているので、後半のほうはかなりぐちゃぐちゃ。
とりあえずオプティカルフローの雰囲気はこんな感じということで。
ある程度のフレーム数、同じ点を追尾してくれていますが、軌跡が途中で飛んだりすることもあります。
チュートリアルのコードでは、cv2.calcOpticalFlowPyrLK()
で推定した次の特徴点位置を繰り返し同じ関数に与えて、軌跡の計算をしているので、次々と新しい人物が現れてくるような今回のデータは不適切だったかも。
ときどき特徴点検出をやり直したほうが良かったかも。
今回はそこまで深堀しませんが。
密なオプティカルフロー
チュートリアルのもう一つの項目「密なオプティカルフロー」(Dense Optical Flow)をやってみます。
データは同じMOT16のものを使用。
実施
まずは動画データを先頭に巻き戻し。
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
True
前と同様に、チュートリアルのコードに動画保存を追加。
ret, frame1 = cap.read() prvs = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY) hsv = np.zeros_like(frame1) hsv[..., 1] = 255 codec = cv2.VideoWriter_fourcc(*'mp4v') writer = cv2.VideoWriter('MOT16-09-FB.mp4', codec, 30.0, (1920,1080*2)) while(1): ret, frame2 = cap.read() if not ret: print('No frames grabbed!') break next = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY) flow = cv2.calcOpticalFlowFarneback(prvs, next, None, 0.5, 3, 15, 3, 5, 1.2, 0) mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1]) hsv[..., 0] = ang*180/np.pi/2 hsv[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX) bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) frame2 = np.vstack((frame2, bgr)) writer.write(frame2) frame2 = cv2.resize(frame2, None, fx=0.25, fy=0.25, interpolation=cv2.INTER_AREA) cv2.imshow('frame2', frame2) k = cv2.waitKey(30) & 0xff if k == 27: break prvs = next cv2.destroyAllWindows() writer.release()
No frames grabbed!
これもなんとなく動きが得られているような。
- エッジ部分で特に動きが見られる。(黒っぽい服、ズボンだと顕著)
- 動きの方向も区別できている(左→右: 赤い色、右→左: 青っぽい色、すれ違いが発生しているところは特に見どころ)
処理はより重たく、上記のコードの実行時間は6m23.9(s)でした。(動画自体は18秒。PC性能は低い。)
以上
今回はここまで。
きちんと評価はできていませんが、Optical Flowを体感しました。