目的
- 動画をフレーム単位(画像)で扱い、加工および可視化ができるようになる
- カラー画像から白黒画像に変換する
- フレーム間の変化情報から変化点を見つける
前提
- 動画が画像の集まりであり、カラー画像とRGBおよびピクセルの関係について知っていること
- https://ja.wikipedia.org/wiki/RGB
- https://ja.wikipedia.org/wiki/%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%AB
対象動画
下記ページからダウンロードできる「賢く著作物を共有する方法(クリエイティブコモンズの使い方)」という動画を利用する
http://togotv.dbcls.jp/20091231.html
この記事は、クリエイティブ・コモンズ 表示 ライセンスの下で利用可能です。 利用にあたっては、下記のクレジットを必ず表示してください。
© 2016 DBCLS TogoTV / CC-BY-4.0
- https://creativecommons.org/licenses/by/4.0/deed.ja
- YouTube URL はこちら https://www.youtube.com/watch?v=e9E1RkMjMo8
動画として使うには動きがあまりないが、ライセンス上使って問題ないのでとりあえずこれにしました
インストール
- 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)
動きが少ないので圧縮する
- 音声を利用しないのであれば、同じようなフレームでは情報が増えない
- 同じかどうかは差分をとればわかるが
- カラーの差分では解釈しにくいので、グレースケールしたフレームを利用する
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)
差分から異なるピクセルの定義
とりあえずはフレーム間のピクセル差分は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")
中央値と平均値はほぼ同じであり、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")
動画と比較してみると、下記のときに差分が大きい
- 文字だけの情報から人物が現れる場面
- 光があるとき消えるとき
- スライドが写ったり、切り替わったりしたとき
まとめ
- 動画を読み込み、フレームのサイズやFPSおよび長さなどを取得できた
- 1フレームごとに、カラー画像グレースケールとして可視化できた
- フレーム間の違いを算出し、動画内における変化点を見つけた
今回は変化の度合い(ピクセル差分)や変化率を少しでも変わったら変化と判定しているため、。
この各変化に関する閾値をパラメータとして設定すれば、本当の変化点を(設定されていれば)見つけられるかもしれない。
参考
- https://docs.opencv.org/trunk/d6/d00/tutorial_py_root.html
- https://opencv-python-tutroals.readthedocs.io/en/latest/
- https://docs.opencv.org/3.4.2/d8/dfe/classcv_1_1VideoCapture.html
- https://matplotlib.org/gallery/subplots_axes_and_figures/figure_title.html
- https://matplotlib.org/api/_as_gen/matplotlib.pyplot.imshow.html