Dense Optical Flowの結果が分かりにくかったので分かりやすくしてみた

# 1. 動機
研究でDence Optical Flowを使っているのですが、Dense Optical Flowの結果表示って人間の視覚的に分かりにくくないですか？
![opticalfb.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/576581/5267e4d1-79e9-9b17-604a-96da31d2f8f4.jpeg)
> 引用：オプティカルフロー（Optical Flow） [http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_video/py_lucas_kanade/py_lucas_kanade.html]

これって入力と出力を同時に出力して見比べないとちゃんと実装できているのか分からないですよね。
そこで、取得した動画上の__移動している物体に対して直線を記載する__ことにしました。これによって線の方向と長さで物体の移動方向と移動量が分かるようになったため、1つの結果から1つのフレームで入力と結果が見ることができるようになりました！

# 2. コード
早速ですがコードです。いくつかに区切って解説を書いていきます。
まずは動画の読み込みです。今回はOpenCVのGithubにて公開されている「vtest.avi」を取り込みました。
まずは取り込んだ動画（frame1）を直前のイメージとしてグレースケール化します。

```python:opticalflow.py
import cv2
import numpy as np
import os

cap = cv2.VideoCapture("./data/src/vtest.avi")
ret, frame1 = cap.read()
prev = cv2.cvtColor(frame1,cv2.COLOR_BGR2GRAY)
```
次に描画処理の関数です。入力動画から移動の基準となる数値を算出し、自身で決定した閾値よりも大きい場合に直線を追加するという流れです。

```python:opticalflow.py
def drawOpticalFlow(frame2, flow, step):
    h, w = frame2.shape[:2]
    x, y = np.mgrid[0:w:step, 0:h:step].reshape(2, -1).astype(np.int32)
    dx, dy = flow[y, x].T
    dist = np.sqrt(dx**2 + dy**2)
    
    # whereを用いて表示する移動量の閾値を決定 -> threshold
    threshold = 1
    index = np.where(threshold < dist)
    x, y = x[index], y[index]
    dx, dy = dx[index], dy[index]
    line = np.vstack([x, y, x + dx, y + dy]).T.reshape(-1, 2, 2).astype(np.int32)

    # 結果描写
    result = cv2.polylines(frame2, line, False, (0, 0, 255))
    return result
```
最後に直後のイメージ（frame2）を定義し、オプティカルフローを実行して描画関数を呼び出します。その後、結果を表示します。この辺りは密なオプティカルフローについて検索するといろいろサンプルプログラムが出てくると思うので参考にしてみてください。
僕が記載しているのもOpenCVのチュートリアル（ http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_video/py_lucas_kanade/py_lucas_kanade.html ）とかに載ってあるような感じのやつです。モジュールを呼び出すだけで計算してくれるなんて便利だなぁ。

```python:opticalflow.py
while(1):
    ret, frame2 = cap.read()

    # frame2を直後のイメージとしてグレースケール化
    next = cv2.cvtColor(frame2,cv2.COLOR_BGR2GRAY)

    # アルゴリズムの引数
    # calcOpticalFlowFarneback(prevImg, nextImg, flow, pyrScale, levels, winsize, iterations, polyN, polySigma, flags）
    # prevImg : 直前のイメージ
    # nextImg : 直後のイメージ
    # flow : 計算済みのフロー画像
    # pyrScale : 各画像に対する画像ピラミッドを構築するためのスケール(<1)
    # levels : 最初の画像を含む画像ピラミッドの層の数
    # winsize : 平均化ウィンドウサイズ（ノイズに対する堅牢性）
    # iterations : 画像ピラミッドの各レベルで行う反復回数
    # polyN : ピクセル近傍領域のサイズ
    # polySigma : ガウス分布の標準偏差
    # flags : 処理フラグ
    # 今回のパラメータはほぼ適当な値とされているものを入れているだけである
    flow = cv2.calcOpticalFlowFarneback(prev,next, None, 0.5, 3, 15, 3, 5, 1.2, 0)
    
    # 描画処理を呼び出し
    opt_frame = drawOpticalFlow(frame2, flow, 16)
    cv2.imshow('opt_frame', opt_frame)
    
    # escキーを押されたらbreak
    if cv2.waitKey(30) & 0xff == 27:
        break

    # 前の画像を更新
    prev = next

cap.release()
cv2.destroyAllWindows()
```
# 3. 結果
得られた結果は以下のようになります。線が細いような気がするけど移動方向と移動量は正しそうですね。
![optflow.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/576581/a126d2b4-4aff-254a-4c68-ab68528caf06.gif)

閾値を小さくした場合（threshold=0.01）の結果も記載します。移動量が小さいものも対象にするとノイズがのってしまいますね。このあたりの数学的関係はよくわかってないので適切な値が選べれるように要調査です。今のところは1にすればある程度は大丈夫そうです。
![optflow_th=0.05.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/576581/9247048f-5bc3-356c-9af5-11c56e178d7d.gif)


ちなみに線の太さは以下のように処理を変えると太くなります。結果が見にくいという方は参考にしてください。

```python
cv2.polylines(img_copy, lines, False, (0, 0, 255), thickness=2) # thicknessに任意の数値を入れてください
```
# 4. まとめ
簡単にではあるが物体の移動方向と移動量を入力動画上に可視化することができました。
方向に対して色を変えることができればよりオプティカルフローっぽくなる気がしますね。
ただ研究を進めるにあたっては可視化できれば十分なのでおそらくこのままで進めます。時間があれば改変したいなと思います。