【OpenCV】Dense Optical Flowを矢印や色空間で表示する方法

# 概要
OpenCVで提供されているオプティカルフローは2種類あり、本記事ではDense Optical Flowについて実装方法と矢印や色空間で表示する方法を紹介する。Optical Flowの説明については、

https://qiita.com/icoxfog417/items/357e6e495b7a40da14d8

を参照されたい。

# 実装
Google Colabで作成した本記事のコードは、[こちら](https://colab.research.google.com/drive/1WutyF4B0q6Uzf1t0--aFfmU0b_wZqkXb?usp=sharing)にあります。
## 各種インポート
```Python:各種インポート
import cv2
import matplotlib.pyplot as plt
import numpy as np
```
実行時の各種バージョン

| Name | Version |
|:-:|:-:|
| opencv-python | 4.6.0.66 |
| matplotlib | 3.2.2 |
| numpy | 1.21.6 |

## 使用する関数
```Python:使用する関数
def flow_vector(flow, spacing, margin, minlength):
    """Parameters:
    input
    flow: motion vectors 3D-array
    spacing: pixel spacing of the flow
    margin: pixel margins of the flow
    minlength: minimum pixels to leave as flow
    output
    x: x coord 1D-array
    y: y coord 1D-array
    u: x direction flow vector 2D-array
    v: y direction flow vector 2D-array
    """
    h, w, _ = flow.shape

    x = np.arange(margin, w - margin, spacing, dtype=np.int64)
    y = np.arange(margin, h - margin, spacing, dtype=np.int64)

    mesh_flow = flow[np.ix_(y, x)]
    mag, _ = cv2.cartToPolar(mesh_flow[..., 0], mesh_flow[..., 1])
    mesh_flow[mag < minlength] = np.nan  # minlength以下をnanに置換

    u = mesh_flow[..., 0]
    v = mesh_flow[..., 1]

    return x, y, u, v

def adjust_ang(ang_min, ang_max):
    """Parameters
    input
    ang_min: start angle of degree
    ang_max: end angle of degree
    output
    unique_ang_min: angle after conversion to unique `ang_min`
    unique_ang_max: angle after conversion to unique `ang_max`
    """
    unique_ang_min = ang_min
    unique_ang_max = ang_max
    unique_ang_min %= 360
    unique_ang_max %= 360
    if unique_ang_min >= unique_ang_max:
        unique_ang_max += 360
    return unique_ang_min, unique_ang_max

def any_angle_only(mag, ang, ang_min, ang_max):
    """
    input
    mag: `cv2.cartToPolar` method `mag` reuslts
    ang: `cv2.cartToPolar` method `ang` reuslts
    ang_min: start angle of degree after `adjust_ang` function
    ang_max: end angle of degree after `adjust_ang` function
    output
    any_mag: array of replace any out of range `ang` with nan
    any_ang: array of replace any out of range `mag` with nan
    description
    Replace any out of range `mag` and `ang` with nan.
    """
    any_mag = np.copy(mag)
    any_ang = np.copy(ang)
    ang_min %= 360
    ang_max %= 360
    if ang_min < ang_max:
        any_mag[(ang < ang_min) | (ang_max < ang)] = np.nan
        any_ang[(ang < ang_min) | (ang_max < ang)] = np.nan
    else:
        any_mag[(ang_max < ang) & (ang < ang_min)] = np.nan
        any_ang[(ang_max < ang) & (ang < ang_min)] = np.nan
        any_ang[ang <= ang_max] += 360
    return any_mag, any_ang

def hsv_cmap(ang_min, ang_max, size):
    """
    input
    ang_min: start angle of degree after `adjust_ang` function
    ang_max: end angle of degree after `adjust_ang` function
    size: map px size
    output
    hsv_cmap_rgb: HSV color map in radial vector flow
    x, y, u, v: radial vector flow value
    x: x coord 1D-array
    y: y coord 1D-array
    u: x direction flow vector 2D-array
    v: y direction flow vector 2D-array
    description
    Create a normalized hsv colormap between `ang_min` and `ang_max`.
    """
    # 放射状に広がるベクトル場の生成
    half = size // 2
    x = np.arange(-half, half+1, 1, dtype=np.float64)
    y = np.arange(-half, half+1, 1, dtype=np.float64)
    u, v = np.meshgrid(x, y)

    # HSV色空間の配列に入れる
    hsv = np.zeros((len(y), len(x), 3), dtype='uint8')
    mag, ang = cv2.cartToPolar(u, v, angleInDegrees=True)
    any_mag, any_ang = any_angle_only(mag, ang, ang_min, ang_max)
    hsv[..., 0] = 180*(any_ang - ang_min) / (ang_max - ang_min)
    hsv[..., 1] = 255
    hsv[..., 2] = cv2.normalize(any_mag, None, 0, 255, cv2.NORM_MINMAX)
    hsv_cmap_rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)

    return hsv_cmap_rgb, x, y, u, v
```
関数の説明
* ``flow_vector``: この関数は[Quiver plot with optical flow? - Stack Overflow](https://stackoverflow.com/questions/61943240/quiver-plot-with-optical-flow)の回答からヒントを得て作成している。optical-flowの結果を矢印で表示するための関数で、**1 px単位で表示すると見にくいため、表示間隔を任意に調整して表示する**ことができる仕様である。``flow[np.ix_(y, x)]``で``y``と``x``の格子点の座標の配列を作っていて、スライスでの``flow[margin:w - margin:spacing, margin:h - margin:spacing]``と同義であるが、出力として``y``, ``x``を使うので可読性向上のため``np.ix_``を使っている。詳しい説明は私の記事で恐縮ですが、[【numpy】np.ix_を使った任意の格子点の要素を抜き出して配列を作成する方法](https://qiita.com/yusuke_s_yusuke/items/f7c093d4920a5a621a77)を参照されたい。
* ``adjust_ang``: 任意の度数法による角度範囲の開始角と終了角を入力に対して一意的な角度で出力する関数で、360°をまたぐ角度を指定したときに連続的に角度を表示できるように終了角に360°加えた値を返す。また、360で割った余りを内部で処理することで入力に負の値等も対応している。例えば、``ang_min=300``, ``ang_max=60``として、``adjust_ang(ang_min, ang_max)``に入力すると、``unique_ang_min=300``, ``unique_ang_max=420``として出力される。
* ``any_angle_only``: OpenCVの[``cv2.cartToPolar``](https://docs.opencv.org/3.4/d2/de8/group__core__array.html#gac5f92f48ec32cacf5275969c33ee837d)によるベクトルの大きさ(``mag``)と角度(``ang``)から、任意の角度範囲外の``mag``と``ang``を``nan``に置換する。``nan``にすることで、任意の角度範囲外の情報を使わないで処理することができる。
* ``hsv_cmap``: 放射状のベクトル場をHSV色空間を表示する関数で、この後のoptical-flowをHSV色空間で表示するときに角度と色の対応を見やすくするために使用する。ベクトル場をHSV色空間で表示する方法は私の記事で恐縮ですが、[【OpenCV+matplotlib】ベクトル場をカラーマップで良い感じに表示する方法](https://qiita.com/yusuke_s_yusuke/items/7659cf2d5cf2cf65af83)を参照されたい。

## vtest.aviから連番画像の作成
本記事ではOPtical-flowの[vtest.avi](https://github.com/opencv/opencv/blob/master/samples/data/vtest.avi)からダウンロードし、以下のコードより連番画像作成し、16, 17フレーム(``'00015.png'``, ``'00016.png'``)の画像を使用する。
```Python:vtest.aviから連番画像の作成
cap = cv2.VideoCapture('vtest.avi')
frame_num = 0
while True:
    ret, frame = cap.read()
    if ret:
        cv2.imwrite(f'{frame_num:05}.png', frame)
        frame_num += 1
    else:
        break
```
## フレームの読み込みと確認
OpenCVではRGB画像は``cv2.imread()``で、(B, G, R)の順で読み込まれるので注意してください。
```Python:フレームの読み込みと確認
# フレームの読み込み
prev_frame = cv2.imread('00015.png')
next_frame = cv2.imread('00016.png')

# グレースケール化
prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)
next_gray = cv2.cvtColor(next_frame, cv2.COLOR_BGR2GRAY)

# 表示
fig, axs = plt.subplots(2, 2, figsize=(20, 16))
prev_rgb = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2RGB)
axs[0, 0].set_title('prev_frame')
axs[0, 0].imshow(prev_rgb)
next_rgb = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2RGB)
axs[0, 1].set_title('next_frame')
axs[0, 1].imshow(next_rgb)
axs[1, 0].set_title('prev_gray')
axs[1, 0].imshow(prev_gray, cmap='gray')
axs[1, 1].set_title('next_gray')
axs[1, 1].imshow(next_gray, cmap='gray')

plt.show()
```
出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/df1195be-9b6d-4f4c-0a9e-bb46ef0dfc6c.png)

## dence optical-flowの計算
``cv2.calcOpticalFlowFarneback``は、**8-bit single-channel画像のみ対応しているのでグレースケール化した2枚の画像を入力画像に使用する**。また、各パラーメータはOpenCV公式[optical flow Dense Optical Flow in OpenCV](https://docs.opencv.org/3.4/d4/dee/tutorial_optical_flow.html)をそのまま使っていて、各パラメータの意味については[calcOpticalFlowFarneback()](https://docs.opencv.org/3.4/dc/d6b/group__video__track.html#ga5d10ebbd59fe09c5f650289ec0ece5af)を参照されたい。
```Python:dence optical-flowの計算
flow = cv2.calcOpticalFlowFarneback(prev_gray, next_gray, None, 0.5, 3, 15, 3, 5, 1.2, 0)
```

## 矢印で表示
``flow_vector(flow=flow, spacing=10, margin=0, minlength=1)``の引数の意味について、``spacing``は表示間隔(px)、``margin``は画像の縁の余白(px)、``minlength``は表示する矢印の大きさの最小値(px)である。
    ``minlength=1``を使う理由は``minlength=0``にすると矢印が格子状に残ってしまって見にくいためであるのと、1 px未満の変化ではノイズの影響が多いためである。
本記事は``margin=0``を使っているが、一般的に画像の縁では物体が画像外にはみ出てしまったりしてoptical-flowの精度が低いことが多いので、状況によって``margin``を設定すると良い。
```Python:矢印で表示
fig, ax = plt.subplots(figsize=(10, 8))
ax.set_title('prev_frame and prev2nextflow vector')
ax.imshow(prev_rgb)
x, y, u, v = flow_vector(flow=flow, spacing=10, margin=0, minlength=1)
ax.quiver(x, y, u, v, angles='xy', scale_units='xy', scale=1, color=[0.0, 1.0, 0.0])

plt.show()
```
出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/2f30a08e-4627-0c18-a9e2-5bcbc39727d0.png)

## ベクトル場をHSV色空間で表示
```Python:HSV色空間にベクトル場の角度に色相を、大きさに明度を対応させて表示
# 角度範囲のパラメータ
ang_min = 0
ang_max = 360
_ang_min, _ang_max = adjust_ang(ang_min, ang_max)  # 角度の表現を統一する

hsv_cmap_rgb, x, y, u, v = hsv_cmap(_ang_min, _ang_max, 31)
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(10, 8))
ax1.set_title('radial_flow_rgb')
ax1.imshow(hsv_cmap_rgb)

ax2.imshow(hsv_cmap_rgb)
ax2.set_title('radial_flow_rgb and vector')
ax2.quiver(np.arange(len(x)), np.arange(len(y)), u, v, angles='xy', scale_units='xy', color='w')

plt.show()
```
出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/31bca57c-b735-a455-7b16-f85d5f1aeb22.png)


## 実際のflowをHSV色空間で表示
画像の原点(0, 0)に矢印の角度に対応したHSV色空間を埋め込み、上の画像が矢印をHSV色空間で表示したもの、下の画像が上の画像に矢印を追加したものである。

``hsv``はHSV色空間の配列であり、``hsv``の配列の定義は以下の通りである。
* ``hsv[..., 0]``は色相を表し、0~180で定義される。
* ``hsv[..., 1]``は彩度を表し、0~255で定義される。ベクトル場を扱うときは、基本的に255で問題ないと思われる。
* ``hsv[..., 2]``は明度を表し、0~255で定義される。


``hsv[..., 0] = 180*(any_ang - _ang_min) / (_ang_max - _ang_min)``は、``_ang_min``を最小値、``_ang_max``を最大値として0~180に規格化している。ここで、``hsv[..., 0] = cv2.normalize(any_ang, None, 0, 180, cv2.NORM_MINMAX)``を使えば良いと思うかもしれないが、``any_ang``には``_ang_min``と``_ang_max``以内の配列が入っているが、``any_ang``の最小値と最大値で規格化すると左上のカラーマップの角度と色の対応が実際のoptical flowの画像とでずれてしまうので、このようなコードを使っている。
``hsv_cmap_bgr, *_ = hsv_cmap(_ang_min, _ang_max, 51)``は、``_ang_min``, ``_ang_max``の間の角度を51 pxの角度に対応した色空間を作る処理であり、原点(0, 0)に配置して確認するためのものなので入力画像の大きさに合わせて適宜画像サイズを調整してください。
```Python:実際のflowでHSV色空間に矢印の角度に色相を、大きさに明度を対応させて表示
# 角度範囲のパラメータ
ang_min = 0
ang_max = 360
_ang_min, _ang_max = adjust_ang(ang_min, ang_max)  # 角度の表現を統一する

# HSV色空間の配列に入れる
hsv = np.zeros_like(prev_frame, dtype='uint8')
mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1], angleInDegrees=True)
any_mag, any_ang = any_angle_only(mag, ang, ang_min, ang_max)
hsv[..., 0] = 180*(any_ang - _ang_min) / (_ang_max - _ang_min)
hsv[..., 1] = 255
hsv[..., 2] = cv2.normalize(any_mag, None, 0, 255, cv2.NORM_MINMAX)
flow_rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)

# 画像の原点にHSV色空間を埋め込み
flow_rgb_display = np.copy(flow_rgb)
hsv_cmap_rgb, *_ = hsv_cmap(_ang_min, _ang_max, 51)
flow_rgb_display[0:hsv_cmap_rgb.shape[0], 0:hsv_cmap_rgb.shape[1]] = hsv_cmap_rgb

fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(10, 16))
ax1.set_title('prev2nextflow rgb')
ax1.imshow(flow_rgb_display)

ax2.imshow(flow_rgb_display)
x, y, u, v = flow_vector(flow=flow, spacing=10, margin=0, minlength=1)
ax2.set_title('prev2nextflow rgb and vector')
ax2.quiver(x, y, u, v, angles='xy', scale_units='xy', scale=1, color='w')

plt.show()
```
出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/4d6ea54b-0ce8-91df-487e-ff48d80e4270.png)

## ベクトル場を任意の角度のHSV色空間で表示
**左上が原点(0, 0)の場合、角度は画像で時計回りになっていて、時計の3時の針が0°で6時が90°とうい対応関係であるので注意してください。**
```Python:任意の角度範囲のHSV色空間にベクトル場の角度に色相を、大きさに明度を対応させて表示
# 角度範囲のパラメータ
ang_min = 300
ang_max = 60
_ang_min, _ang_max = adjust_ang(ang_min, ang_max)  # 角度の表現を統一する

hsv_cmap_rgb, x, y, u, v = hsv_cmap(_ang_min, _ang_max, 31)
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(10, 8))
ax1.set_title('radial_flow_rgb')
ax1.imshow(hsv_cmap_rgb)

ax2.imshow(hsv_cmap_rgb)
ax2.set_title('radial_flow_rgb and vector')
ax2.quiver(np.arange(len(x)), np.arange(len(y)), u, v, angles='xy', scale_units='xy', color='w')

plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/3056c852-d622-6430-b428-8838526e0ec7.png)


## 実際のflowを任意の角度のHSV色空間で表示
``ang_min``, ``ang_max``に任意の開始角度、終了角度の範囲でHSV色空間を作成する。画像の原点に矢印の角度に対応したHSV色空間を埋め込み、上の画像が矢印をHSV色空間で表示したもの、下の画像が上の画像に矢印を追加したものである。
このように、任意の角度のHSV空間を使うことで、画像の部分的な角度に注目して詳細に見ることができる。
```Python:実際のflowで任意の角度範囲のHSV色空間に矢印の角度に色相を、大きさに明度を対応させて表示
# 角度範囲のパラメータ
ang_min = 300
ang_max = 60
_ang_min, _ang_max = adjust_ang(ang_min, ang_max)  # 角度の表現を統一する

# HSV色空間の配列に入れる
hsv = np.zeros_like(prev_frame, dtype='uint8')
mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1], angleInDegrees=True)
any_mag, any_ang = any_angle_only(mag, ang, ang_min, ang_max)
hsv[..., 0] = 180*(any_ang - _ang_min) / (_ang_max - _ang_min)
hsv[..., 1] = 255
hsv[..., 2] = cv2.normalize(any_mag, None, 0, 255, cv2.NORM_MINMAX)
flow_rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)

# 画像の原点にHSV色空間を埋め込み
flow_rgb_display = np.copy(flow_rgb)
hsv_cmap_rgb, *_ = hsv_cmap(_ang_min, _ang_max, 51)
flow_rgb_display[0:hsv_cmap_rgb.shape[0], 0:hsv_cmap_rgb.shape[1]] = hsv_cmap_rgb

fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(10, 16))
ax1.set_title('prev2nextflow rgb')
ax1.imshow(flow_rgb_display)

ax2.imshow(flow_rgb_display)
x, y, u, v = flow_vector(flow=flow, spacing=10, margin=0, minlength=1)
ax2.set_title('prev2nextflow rgb and vector')
ax2.quiver(x, y, u, v, angles='xy', scale_units='xy', scale=1, color='w')

plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/8a7aa076-7248-8028-89d0-f2bdae63bd8b.png)


# 補足(矢印を画像として保存)
矢印を画像として保存したい場合はOpenCVの[``cv2.arrowedLine``](https://docs.opencv.org/4.x/d6/d6e/group__imgproc__draw.html#ga0a165a3ca093fd488ac709fdf10c05b2)を使うとのが便利で、以下のコードを参考にしてください。

<details><summary>画像として保存するためのコード</summary>

## 使用する関数
```Python:使用する関数
def cv2_vector_display(img, color, x, y, u, v):
    """Parameters:
    input
    img: the first image used to calculate the flow
    color: 3 channels 0 to 255 color
    x, y, u, v: `flow_vector` function reuslts
    x: x coord 1D-array
    y: y coord 1D-array
    u: x direction flow vector 2D-array
    v: y direction flow vector 2D-array
    output
    flow_img: image with vector added to input `img`
    description
    Create an image with a vector added to the input `img` using cv2.
    e.g. u[j, i] is the vector of flow in the x direction at (x[i], y[j]) coord.
    """
    flow_img = np.copy(img)
    for i in range(len(x)):
        for j in range(len(y)):
            if np.isnan(u[j, i]) or np.isnan(v[j, i]):
                continue
            pts = np.array([[x[i], y[j]], [x[i]+u[j, i], y[j]+v[j, i]]], np.int64)
            cv2.arrowedLine(flow_img, pts[0], pts[1], color, thickness=1, tipLength=0.5)
    return flow_img
```
``cv2_vector_display``: optical-flowに使った前のフレームの画像と``flow_vector``関数を使った後の``x, y, u, v``を引数に入れることで、矢印を前のフレームの画像に埋め込んで出力する関数である。

## 矢印付きの画像で保存
``cv2_vector_display(prev_frame, (0, 255, 0), x, y, u, v)``について、``prev_frame``が前のRGB画像、``(0, 255, 0)``が表示する矢印の色で0~255で指定、``flow_vector``関数を使った後の``x, y, u, v``で、``prev_frame``に矢印を埋め込んだ画像が出力される。
```Python:矢印付きの画像で保存
x, y, u, v = flow_vector(flow=flow, spacing=10, margin=0, minlength=1)
flow_img_vector = cv2_vector_display(prev_frame, (0, 255, 0), x, y, u, v)
cv2.imwrite('flow_img_vector.png', flow_img_vector)
```
保存した画像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/1cbbf803-32a4-ef47-ceeb-c8d0424a8870.png)

## 矢印をHSV色空間で表示して画像で保存
```Python:矢印をHSV色空間で表示して画像で保存
# 角度範囲のパラメータ
ang_min = 0
ang_max = 360
_ang_min, _ang_max = adjust_ang(ang_min, ang_max)  # 角度の表現を統一する

hsv = np.zeros_like(prev_frame, dtype='uint8')
mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1], angleInDegrees=True)
any_mag, any_ang = any_angle_only(mag, ang, ang_min, ang_max)
hsv[..., 0] = 180*(any_ang - _ang_min) / (_ang_max - _ang_min)
hsv[..., 1] = 255
hsv[..., 2] = cv2.normalize(any_mag, None, 0, 255, cv2.NORM_MINMAX)
flow_bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)

# 画像の原点にHSV色空間を埋め込み
hsv_cmap_rgb, *_ = hsv_cmap(_ang_min, _ang_max, 51)
hsv_cmap_bgr = cv2.cvtColor(hsv_cmap_rgb, cv2.COLOR_RGB2BGR)
flow_bgr[0:hsv_cmap_bgr.shape[0], 0:hsv_cmap_bgr.shape[1]] = hsv_cmap_bgr
cv2.imwrite('flow_rgb.png', flow_bgr)
```
保存した画像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/ed77b016-f27d-ad38-a645-c02cce2c1b10.png)

## 矢印と矢印の色空間の両方を表示して画像で保存
```Python:矢印と矢印の色空間の両方を表示して画像で保存
# 角度範囲のパラメータ
ang_min = 0
ang_max = 360
_ang_min, _ang_max = adjust_ang(ang_min, ang_max)  # 角度の表現を統一する

hsv = np.zeros_like(prev_frame, dtype='uint8')
mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1], angleInDegrees=True)
any_mag, any_ang = any_angle_only(mag, ang, ang_min, ang_max)
hsv[..., 0] = 180*(any_ang - _ang_min) / (_ang_max - _ang_min)
hsv[..., 1] = 255
hsv[..., 2] = cv2.normalize(any_mag, None, 0, 255, cv2.NORM_MINMAX)
flow_bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
x, y, u, v = flow_vector(flow=flow, spacing=10, margin=0, minlength=1)
flow_bgr_vector = cv2_vector_display(flow_bgr, (255, 255, 255), x, y, u, v)

# 画像の原点にHSV色空間を埋め込み
hsv_cmap_rgb, *_ = hsv_cmap(_ang_min, _ang_max, 51)
hsv_cmap_bgr = cv2.cvtColor(hsv_cmap_rgb, cv2.COLOR_RGB2BGR)
flow_bgr_vector[0:hsv_cmap_bgr.shape[0], 0:hsv_cmap_bgr.shape[1]] = hsv_cmap_bgr
cv2.imwrite('flow_rgb_vector.png', flow_bgr_vector)
```
保存した画像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/1bb8a451-2a46-da8b-c099-ddf06780b498.png)

</details>

# まとめ
Dense Optical Flowを矢印や色空間で表示する方法を紹介しました。任意の角度の色空間で矢印を表示することで矢印の分布を空間で詳細に把握できるのでおすすめです。

# 参考資料
https://docs.opencv.org/3.4/d4/dee/tutorial_optical_flow.html

https://docs.opencv.org/3.4/dc/d6b/group__video__track.html#ga5d10ebbd59fe09c5f650289ec0ece5af

https://stackoverflow.com/questions/61943240/quiver-plot-with-optical-flow

https://en.wikipedia.org/wiki/Feature_scaling

https://docs.opencv.org/4.x/d6/d6e/group__imgproc__draw.html#ga0a165a3ca093fd488ac709fdf10c05b2

https://qiita.com/icoxfog417/items/357e6e495b7a40da14d8