LoginSignup
4
3

【OpenCV+matplotlib】ベクトル場をカラーマップで良い感じに表示する方法

Last updated at Posted at 2022-08-26

概要

ベクトル場の角度と大きさをカラーマップで表示したい場合がある。そんなときに便利なのがmatplotlibのquiverであるが、実はOpenCVのHSV空間を組み合わせるとベクトル場の角度と大きさを一目で見やすく表示できる。
本記事は筆者が個人的に見やすいと思うベクトル場の表示方法について紹介する。

実装

Google Colabで作成した本記事のコードは、こちらにあります。

各種インポート

各種インポート
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

準備

hsv, jetのカラーマップを見やすくするために背景を黒塗りにする

plt.style.use('dark_background')

座標変換用の関数

matplotlibのSecondary Axisを使って、ピクセル座標とベクトル場の座標の軸のスケールを調整する関数である。例えば、ベクトル場の角度を背景にカラーマップ表示のように背景にベクトル場の角度情報をax.imshow()で追加する場合、通常軸はピクセル座標で表示されてしまうが、この関数を適用すると軸のスケールを元のベクトル場のスケールに変換して表示できる。ただし、以降のax.set_aspect('equal')は、ピクセル座標でのアスペクト比が統一されるので、厳密にベクトル場でのアスペクト比が統一されない場合があるので注意してください。この関数を適用する上でアスペクト比が調節されないケースは、ベクトルの取得座標間隔がXYで異なるときである。

ピクセル座標とベクトル場の座標変換用の関数
def px2coord_axis(ax, X, Y):
    """
    input
    ax: matplotlib axis object
    X: 1D or 2D equally spaced vector field x array
    Y: array of vector fields y of the same dimension as `X`
    output
    ax: matplotlib ax object
    secax_x: vector field x coord object
    secax_y: vector field y coord object
    description
    Function to convert axis labels from pixel coordinates to vector field coordinates.
    """
    if X.ndim == 2:
        X_start = X[0, 0]
        X_step = X[0, 1] - X[0, 0]
        Y_start = Y[0, 0]
        Y_step = Y[1, 0] - Y[0, 0]
    else:
        X_start = X[0]
        X_step = X[1] - X[0]
        Y_start = Y[0]
        Y_step = Y[1] - Y[0]
    ax.tick_params(labelbottom=False, labelleft=False)
    secax_x = ax.secondary_xaxis('bottom',
                                functions=(lambda x: x*X_step + X_start, lambda x: (x - X_start) / X_step))
    secax_y = ax.secondary_yaxis('left',
                                functions=(lambda x: x*Y_step + Y_start, lambda x: (x - Y_start) / Y_step))
    return ax, secax_x, secax_y

サンプルのベクトル場の準備

注意点として、cv2.cartToPolarの入力のU, Vは、float型の同じ型である必要がある。cv2.cartToPolar(U, V, angleInDegrees=True)は、magangにそれぞれ各座標に対応したベクトルの大きさと角度が入った配列が入る。angleInDegrees=Trueとするとこで、angには反時計回りを正にした0°~360°の度数法に基づく角度が入る。サンプルでは、放射状のベクトルを用意した。

サンプルのベクトル場
X = np.arange(-10, 11, 1, dtype=np.float64)
Y = np.arange(-10, 11, 1, dtype=np.float64)
U, V = np.meshgrid(X, Y)
mag, ang = cv2.cartToPolar(U, V, angleInDegrees=True)
X_shape, Y_shape = X.shape[0], Y.shape[0]  # X, Yが1次元配列の場合
# X_shape, Y_shape = X.shape[1], Y.shape[0]  # X, Yが2次元配列の場合

サンプルのベクトル場を表示して確認する。

ベクトル場を表示
fig, ax = plt.subplots(figsize=(10, 8))
ax.quiver(X, Y, U, V, color='w')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_aspect('equal')
plt.show()

出力結果
image.png

方法

ベクトル場の角度をカラーマップ表示

ベクトル場の角度をカラーマップ表示
fig, ax = plt.subplots(figsize=(10, 8))
Q = ax.quiver(X, Y, U, V, ang, cmap='hsv')
ax.set_xlabel('X')
ax.set_ylabel('Y')
fig.colorbar(Q, label='vector angle (degree)')
ax.set_aspect('equal')
plt.show()

出力結果
image.png

ベクトル場の大きさをカラーマップ表示

ベクトル場の大きさをカラーマップ表示
fig, ax = plt.subplots(figsize=(10, 8))
Q = ax.quiver(X, Y, U, V, mag, cmap='jet')
ax.set_xlabel('X')
ax.set_ylabel('Y')
fig.colorbar(Q, label='vectorrr size')
ax.set_aspect('equal')
plt.show()

出力結果
image.png

ベクトル場の角度を背景にカラーマップ表示

ベクトル場の角度を背景にカラーマップ表示
fig, ax = plt.subplots(figsize=(10, 8))
cmap = plt.imshow(ang, cmap='hsv', origin='lower')
ax.quiver(np.arange(X_shape), np.arange(Y_shape), U, V, color='w')
ax, secax_x, secax_y = px2coord_axis(ax, X, Y)
secax_x.set_xlabel('X')
secax_y.set_ylabel('Y')
fig.colorbar(cmap, label='vector angle (degree)')
ax.set_aspect('equal')
plt.show()

出力結果
image.png

ベクトル場の大きさを背景にカラーマップ表示

ベクトル場の大きさを背景にカラーマップ表示
fig, ax = plt.subplots(figsize=(10, 8))
cmap = plt.imshow(mag, cmap='jet', origin='lower')
ax.quiver(np.arange(X_shape), np.arange(Y_shape), U, V, color='w')
ax, secax_x, secax_y = px2coord_axis(ax, X, Y)
secax_x.set_xlabel('X')
secax_y.set_ylabel('Y')
fig.colorbar(cmap, label='vector size')
ax.set_aspect('equal')
plt.show()

出力結果
image.png

HSV色空間にベクトル場の角度に色相を、大きさに角度を対応させて表示

hsvはHSV色空間の配列であり、hsvの配列の定義は以下の通りである。

  • hsv[..., 0]は色相を表し、0~180で定義される。
  • hsv[..., 1]は彩度を表し、0~255で定義される。ベクトル場を扱うときは、基本的に255で問題ないと思われる。
  • hsv[..., 2]は明度を表し、0~255で定義される。

cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)でRGB画像に変換している。

HSV色空間にベクトル場の角度に色相を、大きさに角度を対応させて表示
hsv = np.zeros((Y_shape, X_shape, 3), dtype='uint8')
hsv[..., 0] = ang / 2
hsv[..., 1] = 255
hsv[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)
flow_rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)

fig, ax = plt.subplots(figsize=(8, 8))
ax.imshow(flow_rgb, origin='lower')
ax.quiver(np.arange(X_shape), np.arange(Y_shape), U, V, color='w')
ax, secax_x, secax_y = px2coord_axis(ax, X, Y)
secax_x.set_xlabel('X')
secax_y.set_ylabel('Y')
ax.set_aspect('equal')
plt.show()

出力結果
image.png

HSV色空間にベクトル場の大きさに色相と明度を対応させて表示

HSV色空間にベクトル場の大きさに色相と明度を対応させて表示
hsv = np.zeros((Y_shape, X_shape, 3), dtype='uint8')
hsv[..., 0] = cv2.normalize(mag, None, 0, 180, cv2.NORM_MINMAX)
hsv[..., 1] = 255
hsv[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)
flow_rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)

fig, ax = plt.subplots(figsize=(8, 8))
ax.imshow(flow_rgb, origin='lower')
ax.quiver(np.arange(X_shape), np.arange(Y_shape), U, V, color='w')
ax, secax_x, secax_y = px2coord_axis(ax, X, Y)
secax_x.set_xlabel('X')
secax_y.set_ylabel('Y')
ax.set_aspect('equal')
plt.show()

出力結果
image.png

少し構造のあるベクトル場での例

少し構造のあるベクトル場
X, Y = np.meshgrid(np.arange(0, 3 * np.pi, .2), np.arange(0, 2 * np.pi, .2))
U = np.cos(X)
V = np.sin(Y)
mag, ang = cv2.cartToPolar(U, V, angleInDegrees=True)
X_shape, Y_shape = X.shape[1], Y.shape[0]  # 2次元配列の場合

ベクトル場を表示([ベクトル場を表示](figsize=(15, 8)としてベクトル場を表示)
image.png

ベクトル場の角度をカラーマップ表示(ベクトル場の角度をカラーマップ表示figsize=(15, 8)としたコード)
image.png

ベクトル場の大きさをカラーマップ表示(ベクトル場の大きさをカラーマップ表示figsize=(15, 8)としたコード)
image.png

ベクトル場の角度を背景にカラーマップ表示(ベクトル場の角度を背景にカラーマップ表示figsize=(15, 8)としたコード)
image.png

ベクトル場の大きさを背景にカラーマップ表示(ベクトル場の大きさを背景にカラーマップ表示figsize=(15, 8)としたコード)
image.png

HSV色空間にベクトル場の角度に色相を、大きさに角度を対応させて表示(HSV色空間にベクトル場の角度に色相を、大きさに角度を対応させて表示figsize=(15, 8)としたコード)
image.png

HSV色空間にベクトル場の大きさに色相と明度を対応させて表示(HSV色空間にベクトル場の大きさに色相と明度を対応させて表示figsize=(15, 8)としたコード)
image.png

補足(カラーマップを任意の範囲で設定)

matplotlibのquiverは、カラーマップの色の表示範囲(matplotlibに良くあるvmin, vmax等)の引数は2022/08/29時点ではなく、また角度の表示範囲は360°を超えると不連続なので、少し複雑で以下のコードを参考にしていただきたい。

カラーマップ任意の範囲で表示するためのコード

関数の準備

任意の範囲のカラーマップ用の関数
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
    ang_max: end angle of degree
    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 any_size_only(mag, ang, mag_min, mag_max):
    """
    input
    mag: `cv2.cartToPolar` method `mag` reuslts
    ang: `cv2.cartToPolar` method `ang` reuslts
    mag_min: range min mag
    mag_max: range max mag
    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)
    any_mag[(mag < mag_min) | (mag_max < mag)] = np.nan
    any_ang[(mag < mag_min) | (mag_max < mag)] = np.nan
    return any_mag, any_ang

ベクトル場を任意の範囲の角度のみでカラーマップ

ベクトル場を任意の範囲の角度のみでカラーマップで表すことで、ベクトル場の角度に対する色の感度を上げて詳細に見ることができる。ただ、360°を超える範囲を指定すると不連続なので少し複雑で、自作のany_angle_only関数で、角度を反時計回りに指定する。ang_minは反時計回りの角度の始まり、ang_max反時計回りの角度の終わりを度数法で指定する。例のany_angle_only(mag, ang, 270, 60)は、any_angle_only(mag, ang, 270, 420)としても同様に機能する。

ベクトル場を任意の範囲の角度のみでカラーマップ
"""サンプルのベクトル場"""
X = np.arange(-10, 11, 1, dtype=np.float64)
Y = np.arange(-10, 11, 1, dtype=np.float64)
U, V = np.meshgrid(X, Y)
mag, ang = cv2.cartToPolar(U, V, angleInDegrees=True)
any_mag, any_ang = any_angle_only(mag, ang, 270, 60)  # 270°から60°のみでカラーマップ表示
# any_mag, any_ang = any_angle_only(mag, ang, 270, 420)  # こちらでも可能
X_shape, Y_shape = X.shape[0], Y.shape[0]  # 1次元配列の場合

"""ベクトル場の角度をカラーマップ表示"""
fig, ax = plt.subplots(figsize=(10, 8))
ax.quiver(X, Y, U, V, color='w')
Q = ax.quiver(X, Y, U, V, any_ang, cmap='hsv')
fig.colorbar(Q, label='vector angle (degree)')
ax.set_aspect('equal')
plt.show()

出力結果
image.png

ベクトル場を任意の範囲の大きさのみでカラーマップ

ベクトル場を任意の範囲の大きさのみでカラーマップ
"""サンプルのベクトル場"""
X = np.arange(-10, 11, 1, dtype=np.float64)
Y = np.arange(-10, 11, 1, dtype=np.float64)
U, V = np.meshgrid(X, Y)
mag, ang = cv2.cartToPolar(U, V, angleInDegrees=True)
any_mag, any_ang = any_size_only(mag, ang, 5, 10)
X_shape, Y_shape = X.shape[0], Y.shape[0]  # 1次元配列の場合

"""ベクトル場の大きさをカラーマップ表示"""
fig, ax = plt.subplots(figsize=(10, 8))
ax.quiver(X, Y, U, V, color='w')
Q = ax.quiver(X, Y, U, V, any_mag, cmap='jet')
fig.colorbar(Q, label='vectorrr size')
ax.set_aspect('equal')
plt.show()

出力結果
image.png

ベクトル場を任意の範囲の角度のみで背景にカラーマップ

ベクトル場を任意の範囲の角度のみで背景にカラーマップ
"""サンプルのベクトル場"""
X = np.arange(-10, 11, 1, dtype=np.float64)
Y = np.arange(-10, 11, 1, dtype=np.float64)
U, V = np.meshgrid(X, Y)
mag, ang = cv2.cartToPolar(U, V, angleInDegrees=True)
any_mag, any_ang = any_angle_only(mag, ang, 270, 60)  # 270°から60°のみでカラーマップ表示
# any_mag, any_ang = any_angle_only(mag, ang, 270, 420)  # こちらでも可能
X_shape, Y_shape = X.shape[0], Y.shape[0]  # 1次元配列の場合

"""ベクトル場の角度を背景にカラーマップ表示"""
fig, ax = plt.subplots(figsize=(10, 8))
cmap = plt.imshow(any_ang, cmap='hsv', origin='lower')
ax.quiver(np.arange(X_shape), np.arange(Y_shape), U, V, color='w')
ax, secax_x, secax_y = px2coord_axis(ax, X, Y)
secax_x.set_xlabel('X')
secax_y.set_ylabel('Y')
fig.colorbar(cmap, label='vector angle (degree)')
ax.set_aspect('equal')
plt.show()

出力結果
image.png

ベクトル場を任意の範囲の大きさのみで背景にカラーマップ

ベクトル場を任意の範囲の大きさのみで背景にカラーマップ
"""サンプルのベクトル場"""
X = np.arange(-10, 11, 1, dtype=np.float64)
Y = np.arange(-10, 11, 1, dtype=np.float64)
U, V = np.meshgrid(X, Y)
mag, ang = cv2.cartToPolar(U, V, angleInDegrees=True)
any_mag, any_ang = any_size_only(mag, ang, 5, 10)
X_shape, Y_shape = X.shape[0], Y.shape[0]  # 1次元配列の場合

"""ベクトル場の大きさを背景にカラーマップ表示"""
fig, ax = plt.subplots(figsize=(10, 8))
cmap = plt.imshow(any_mag, cmap='jet', origin='lower')
ax.quiver(np.arange(X_shape), np.arange(Y_shape), U, V, color='w')
ax, secax_x, secax_y = px2coord_axis(ax, X, Y)
secax_x.set_xlabel('X')
secax_y.set_ylabel('Y')
fig.colorbar(cmap, label='vector size')
ax.set_aspect('equal')
plt.show()

出力結果
image.png

HSV空間にベクトル場を任意の範囲の角度に色相を、大きさに明度を対応させて表示

cv2.normalize(any_ang, None, 0, 180, cv2.NORM_MINMAX)とすることで、0~180に正規化するのがポイントである。

HSV空間にベクトル場を任意の範囲の角度に色相を、大きさに明度を対応させて表示
"""サンプルのベクトル場"""
X = np.arange(-10, 11, 1, dtype=np.float64)
Y = np.arange(-10, 11, 1, dtype=np.float64)
U, V = np.meshgrid(X, Y)
mag, ang = cv2.cartToPolar(U, V, angleInDegrees=True)
any_mag, any_ang = any_angle_only(mag, ang, 270, 60)  # 270°から60°のみでカラーマップ表示
# any_mag, any_ang = any_angle_only(mag, ang, 270, 420)  # こちらでも可能
X_shape, Y_shape = X.shape[0], Y.shape[0]  # 1次元配列の場合

"""HSV色空間にベクトル場の角度に色を、大きさに明度を対応させて表示"""
hsv = np.zeros((Y_shape, X_shape, 3), dtype='uint8')
hsv[..., 0] = cv2.normalize(any_ang, None, 0, 180, cv2.NORM_MINMAX)
hsv[..., 1] = 255
hsv[..., 2] = cv2.normalize(any_mag, None, 0, 255, cv2.NORM_MINMAX)
flow_rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)

"""ベクトル場の角度をカラーマップ表示"""
fig, ax = plt.subplots(figsize=(8, 8))
ax.imshow(flow_rgb, origin='lower')
ax.quiver(np.arange(X_shape), np.arange(Y_shape), U, V, color='w')
ax, secax_x, secax_y = px2coord_axis(ax, X, Y)
secax_x.set_xlabel('X')
secax_y.set_ylabel('Y')
ax.set_aspect('equal')
plt.show()

出力結果
image.png

HSV空間にベクトル場を任意の範囲の大きさに色相と明度を対応させて表示

HSV空間にベクトル場を任意の範囲の大きさに色相と明度を対応させて表示
"""サンプルのベクトル場"""
X = np.arange(-10, 11, 1, dtype=np.float64)
Y = np.arange(-10, 11, 1, dtype=np.float64)
U, V = np.meshgrid(X, Y)
mag, ang = cv2.cartToPolar(U, V, angleInDegrees=True)
any_mag, any_ang = any_size_only(mag, ang, 5, 10)
X_shape, Y_shape = X.shape[0], Y.shape[0]  # 1次元配列の場合

"""HSV色空間にベクトル場の大きさに色相と明度を対応させて表示"""
hsv = np.zeros((Y_shape, X_shape, 3), dtype='uint8')
hsv[..., 0] = cv2.normalize(any_mag, None, 0, 180, cv2.NORM_MINMAX)
hsv[..., 1] = 255
hsv[..., 2] = cv2.normalize(any_mag, None, 0, 255, cv2.NORM_MINMAX)
flow_rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)

"""ベクトル場の大きさをカラーマップ表示"""
fig, ax = plt.subplots(figsize=(10, 8))
ax.imshow(flow_rgb, origin='lower')
ax.quiver(np.arange(X_shape), np.arange(Y_shape), U, V, color='w')
ax, secax_x, secax_y = px2coord_axis(ax, X, Y)
secax_x.set_xlabel('X')
secax_y.set_ylabel('Y')
ax.set_aspect('equal')
plt.show()

出力結果
image.png

まとめ

ベクトル場の角度と大きさをカラー角度で表示する方法を紹介しました。ベクトル場は角度と大きさの2つの情報を一目で見たい場合が多いので、本記事が誰かのお役になれば幸いです。

参考資料

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3