雑食性雑感雑記

知識の整理場。ため込んだ知識をブログ記事として再構築します。

OpenCV + Matplotlib (Seaborn) で 2 画面の動画 + グラフ表示

前に「OpenCV映像とMatplotlibグラフを、Matplotlibの1つの画面で表示させる」というのをやりました。
OpenCV画像 + matplotlibグラフを合わせて表示する - 雑食性雑感雑記

今回は、綺麗に表示ができる「Seaborn」を使ってみたくなったのに合わせて、
OpenCV 側で2画面を出す方法が分かったので、それをやってみます。

完成形としては、こんな感じで動画のウィンドウとグラフのウィンドウが出る形です。

f:id:kazuki_nagasawa:20200519091112p:plain
完成形

※ 動画はフリーサイト Pexels より。https://www.pexels.com/video/river-with-strong-current-2019791/

コード

そこまで長くないので、全体のコードを貼っておきます。
グレースケール化した色値のヒストグラムを取ってます。
要 cv2, matplotlib, numpy, seaborn。

# for CLI
import matplotlib
matplotlib.use('TkAgg')

import cv2
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns

INPUT_MOVIE_PATH = "sample.mp4"


def hist(frame, fig) :
    """ Seaborn histgram """

    # Grayscale
    frame_g = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # Graph
    plt.clf()
    sns.distplot(frame_g.flatten())
    fig.canvas.draw()

    # Graph to np img
    frame_graph = np.fromstring(fig.canvas.tostring_rgb(), dtype = np.uint8, sep = '')
    frame_graph = frame_graph.reshape(fig.canvas.get_width_height()[::-1] + (3,))
    frame_graph = cv2.cvtColor(frame_graph, cv2.COLOR_RGB2BGR)
    return frame_graph


if __name__ == '__main__' :

    # Seaborn initialize
    sns.set()
    fig = plt.figure()

    # Start video capture
    cap = cv2.VideoCapture(INPUT_MOVIE_PATH)

    while True :
        ret, frame = cap.read()
        if not ret :
            break

        # Resize ... 大きすぎる場合に
        # size = (int(frame.shape[1] / 2), int(frame.shape[0] / 2))
        # frame = cv2.resize(frame, size)

        # Histogram
        frame_graph = hist(frame, fig)

        cv2.imshow("Window", frame)
        cv2.imshow("Graph", frame_graph)

        key = cv2.waitKey(30) & 0xFF
        if key == ord('q') :
            break

説明

OpenCV で動画表示

―― はどこでも書かれていると思うので略。

グラフの numpy 画像化

この↓

    # Graph
    plt.clf()
    sns.distplot(frame_g.flatten())
    fig.canvas.draw()

    # Graph to np img
    frame_graph = np.fromstring(fig.canvas.tostring_rgb(), dtype = np.uint8, sep = '')
    frame_graph = frame_graph.reshape(fig.canvas.get_width_height()[::-1] + (3,))
    frame_graph = cv2.cvtColor(frame_graph, cv2.COLOR_RGB2BGR)
    return frame_graph

あたり。

plt.clf() で canvas に書かれたグラフのクリア。
ここ入れないと同じ figure 上に描かれ続けてカラフルなグラフになる。(特定の場合には不要かもだけど…?)

sns.distplot() で描画し、それを fig.canvas.draw() で Matplotlib 内描画エリアに描画しているらしい。
その canvas エリアを numpy array として持ってきて reshape して cv2 用に色空間変換しているのが後半部分。

その他

今回、WSL 上の Python3 から MobaXTerm を使って実行しました。
CLI 実行の場合は python-tk が入ってないとダメとか、

import matplotlib
matplotlib.use('TkAgg')

の設定が無いとダメとかあるとのこと。

メモ

  • とりあえずやりたいことはできました。
  • ただし、遅い。。描画方法等要検討。
    • グラフ描画のための計算 (ヒストグラム) 自体は遅くないハズ。
    • fig.canvas.draw() 自体が遅いという話もある。特定の部分だけ描画更新すると速くなるらしい?
  • グラフも nparray の画像化できてるので、元画像上に重ねて貼るような表現にしてもよいかも。

参照