LoginSignup
9
16

More than 5 years have passed since last update.

OpenCV + Python で動画内の変化点を見つける

Last updated at Posted at 2019-01-31

目的

  • 動画をフレーム単位(画像)で扱い、加工および可視化ができるようになる
  • カラー画像から白黒画像に変換する
  • フレーム間の変化情報から変化点を見つける

前提

対象動画

下記ページからダウンロードできる「賢く著作物を共有する方法(クリエイティブコモンズの使い方)」という動画を利用する

この記事は、クリエイティブ・コモンズ 表示 ライセンスの下で利用可能です。 利用にあたっては、下記のクレジットを必ず表示してください。

© 2016 DBCLS TogoTV / CC-BY-4.0

動画として使うには動きがあまりないが、ライセンス上使って問題ないのでとりあえずこれにしました

インストール

  • anaconda 前提 5.3.0: python3.7
  • opencv 3.4.2 (conda search で探しても4系がなかった)
  • numpy with openblas
conda install "opencv==3.4.2"

おまけ: 3.4.3以上の場合

conda-forge にしか存在せず、anacondaで入るmklではなくopenblas なのでめんどう

conda install -c conda-forge "opencv==3.4.4"
    blas:         1.0-mkl                                           --> 1.1-openblas                           conda-f

conda install numpy
    The following NEW packages will be INSTALLED:
        libopenblas        pkgs/main/osx-64::libopenblas-0.3.3-hdc02c5d_3



import numpy as np
np.show_config()
las_mkl_info:
  NOT AVAILABLE
blis_info:
  NOT AVAILABLE
openblas_info:
    libraries = ['openblas', 'openblas']
    library_dirs = ['/Users/xxx/.pyenv/versions/anaconda3-5.3.0/lib']
    language = c
    define_macros = [('HAVE_CBLAS', None)]
blas_opt_info:
    libraries = ['openblas', 'openblas']
    library_dirs = ['/Users/xxx/.pyenv/versions/anaconda3-5.3.0/lib']
    language = c
    define_macros = [('HAVE_CBLAS', None)]
lapack_mkl_info:
  NOT AVAILABLE
openblas_lapack_info:
    libraries = ['openblas', 'openblas']
    library_dirs = ['/Users/xxx/.pyenv/versions/anaconda3-5.3.0/lib']
    language = c
    define_macros = [('HAVE_CBLAS', None)]
lapack_opt_info:
    libraries = ['openblas', 'openblas']
    library_dirs = ['/Users/xxx/.pyenv/versions/anaconda3-5.3.0/lib']
    language = c
    define_macros = [('HAVE_CBLAS', None)]

動画の読み込みと動画基本情報

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

%matplotlib inline
# download しておく
movie_path = "./091231MBSJ2009_yuko.mov"
cap = cv2.VideoCapture(movie_path)

fps = cap.get(cv2.CAP_PROP_FPS)
w = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
h = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT)
sec = frame_count / fps

fps, frame_count, round(sec, 3), (h, w)
(29.97, 36540.0, 1219.219, (480.0, 853.0))

フレーム間の変化情報から変化点を見つける

まずは、フレーム単位の読み込みを行う。
ただし、動画開始から40秒くらいで動きが止まるので、 40sec = n_frames / (fps≒30) より

n_frames = int(40 * fps + 1)
n_frames
1199

だけ取得してあとは利用しない

frames = []
while True:
    # generator にしたほうが使いやすい
    ret, frame = cap.read()
    if not ret:
        print("read error")
        break

    frames.append(frame)
    if len(frames) == n_frames:
        break

# context manager として扱えるようにし、releaseを意識しないようにしたほうがいい
if cap.isOpened():
    cap.release()

frames[0].shape
(480, 853, 3)

フレームはnumpy の配列として取得できる。numpy/pandas等に慣れている人ならあとは加工するだけ。
縦横サイズはOpenCVから取得したサイズと同じで、最後の情報はGBR各チャネルを表している

フレーム単位の可視化

最初の5フレームを可視化する。ちなみにOpenCVでのカラー画像はRGBではなく、BGRなので変換する必要がある

fig, axes = plt.subplots(1, 5, figsize=(20, 3))
for frame, ax in zip(frames[:5], axes):
    rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    ax.imshow(rgb_frame)
    ax.axis("off")
fig.suptitle("RGB変換後", fontsize=16)

fig, axes = plt.subplots(1, 5, figsize=(20, 3))
for frame, ax in zip(frames[:5], axes):
    ax.imshow(frame)
    ax.axis("off")
fig.suptitle("BGRのまま", fontsize=16)

gray_frames = [cv2.cvtColor(f, cv2.COLOR_BGR2GRAY) for f in frames]
fig, axes = plt.subplots(1, 5, figsize=(20, 3))
for frame, ax in zip(gray_frames[:5], axes):
    ax.imshow(frame, cmap="gray", vmin=0, vmax=255)
    ax.axis("off")
_ = fig.suptitle("Gray Scale", fontsize=16)

output_14_0.png
output_14_1.png
output_14_2.png

動きが少ないので圧縮する

  • 音声を利用しないのであれば、同じようなフレームでは情報が増えない
  • 同じかどうかは差分をとればわかるが
  • カラーの差分では解釈しにくいので、グレースケールしたフレームを利用する

0〜255 のピクセル値からもう片方も同じ範囲の値との差分をとるので、差分の範囲は-255〜255。差分もグレースケール画像として見るとき、解釈は差分=0ならグレーとして出力され、差分が大きいほど白や黒に近づく。

fig, axes = plt.subplots(1, 5, figsize=(20, 3))
for bef, aft, ax in zip(gray_frames[:5], gray_frames[1:5+1], axes):
    diff_frame = aft - bef
    ax.imshow(diff_frame, cmap="gray", vmin=-255, vmax=255)
    ax.axis("off")

_ = fig.suptitle("diff", fontsize=16)

output_16_0.png

差分から異なるピクセルの定義

とりあえずはフレーム間のピクセル差分は1でも違っていたら異なるとする

diff_frames = [aft - bef for bef, aft in zip(gray_frames, gray_frames[1:])]
n_diff_pixels = (diff_frames[0] != 0).sum()
n_diff_pixels
5054

フレーム間でどれくらいの割合で異なっていたかは、下記のとおり

area = w * h
"{:.2%}".format(n_diff_pixels / area)
'1.23%'

ほとんど変わらないと認識していても、1%は変わっている。全体としてどこでフレーム間の違いが大きいかを可視化する

fig = plt.figure(figsize=(20, 5))

diff_rates = np.array([(d != 0).sum() / area for d in diff_frames])
mean = diff_rates.mean()
median = np.median(diff_rates)

plt.plot(diff_rates)
plt.axhline(mean, color="red")
_ = plt.axhline(median, color="green")

output_22_1.png

中央値と平均値はほぼ同じであり、0.7を超えれば極端に変化のある部分が特定できそう

idx, = np.where(diff_rates >= 0.7)
idx, idx.shape
(array([ 198,  199,  201,  203,  204,  205,  206,  207,  209,  210,  211,
         212,  213,  214,  215,  216,  217,  218,  219,  220,  221,  222,
         223,  224,  225,  266,  267, 1189, 1190]), (29,))
for j in range(6):
    fig, axes = plt.subplots(1, 5, figsize=(20, 3))
    for i, ax in zip(idx[j*5:(j+1)*5], axes):
        ax.imshow(cv2.cvtColor(frames[i], cv2.COLOR_BGR2RGB))
        ax.axis("off")
        ax.set_title(f"{i+1} frame, {i / fps:.2f} sec")

output_25_0.png
output_25_1.png
output_25_2.png
output_25_3.png
output_25_4.png
output_25_5.png

動画と比較してみると、下記のときに差分が大きい

  • 文字だけの情報から人物が現れる場面
  • 光があるとき消えるとき
  • スライドが写ったり、切り替わったりしたとき

まとめ

  • 動画を読み込み、フレームのサイズやFPSおよび長さなどを取得できた
  • 1フレームごとに、カラー画像グレースケールとして可視化できた
  • フレーム間の違いを算出し、動画内における変化点を見つけた

今回は変化の度合い(ピクセル差分)や変化率を少しでも変わったら変化と判定しているため、。
この各変化に関する閾値をパラメータとして設定すれば、本当の変化点を(設定されていれば)見つけられるかもしれない。

参考

9
16
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
9
16