Python, OpenCVで動画を読み込み(ファイル・カメラ映像)

Posted: | Tags: Python, OpenCV

PythonのOpenCVで動画ファイルやカメラ(内蔵カメラ・USBカメラ・Webカメラ)の映像を読み込んで処理するにはVideoCaptureクラスを使う。後述のように、ビルド時にVideo I/Oが有効化されていないと動画の処理はできないので注意。

ここでは以下の内容について説明する。

  • Video I/Oが有効化されている必要あり
  • 動画の読み込み: VideoCapture()
    • 動画ファイルの読み込み
    • カメラ映像の読み込み
    • 動画を閉じる
  • 動画のプロパティ(サイズやFPS、フレーム数など)の取得・設定
    • プロパティの取得: get()
    • プロパティの設定: set()
  • フレームの画像をNumPy配列ndarrayで取得
    • 動画ファイルの場合
    • カメラ映像の場合
  • 動画の再生(表示)
    • 動画ファイルを無限ループで表示
    • カメラのリアルタイム映像を表示
  • リアルタイムで画像処理

動画に関するOpenCV公式ドキュメントのチュートリアルは以下。

本記事のサンプルコードのOpenCVのバージョンは4.0.1

動画ではなく静止画(画像ファイル)の読み書きについては以下の記事を参照。

Video I/Oが有効化されている必要あり

OpenCVで動画を扱うにはビルド時にVideo I/Oが有効化されている必要がある。エラーが発生して動画が読み込めない場合は、まずVideo I/Oが有効化されているかを確認したほうがいい。

OpenCVのビルド情報はgetBuildInformation()で確認できる。以下の記事を参照。

Video I/Oを有効にしてビルドする方法は環境によって様々なのでここでは触れない。

Macの場合はHomebrewを使ってbrew install opencvでインストールすればffmpegも同時にインストールされてVideo I/Oも有効になるはず(2019年1月27日時点)。たぶん。

動画の読み込み: VideoCapture()

コンストラクタVideoCapture()VideoCaptureオブジェクトを生成して動画を処理する。

動画ファイルの読み込み

動画ファイルを読み込む場合はVideoCapture()の引数に動画ファイルのパスを指定する。絶対パスでも相対パスでもOK。

正常に読み込めたかどうかはisOpened()メソッドで確認できる。問題なければTrueを返す。

import cv2

cap_file = cv2.VideoCapture('data/temp/sample_video.mp4')
print(type(cap_file))
# <class 'cv2.VideoCapture'>

print(cap_file.isOpened())
# True

間違ったパスを指定してもコンストラクタVideoCapture()の時点ではエラーにならないので注意。読み込めていないとisOpened()Falseを返すのでそちらで判断する。

cap_file_wrong = cv2.VideoCapture('wrong_path')
print(type(cap_file_wrong))
# <class 'cv2.VideoCapture'>

print(cap_file_wrong.isOpened())
# False

カメラ映像の読み込み

PCの内蔵カメラやUSBカメラ・Webカメラの映像を読み込む場合はVideoCapture()の引数にカメラの番号(ID)を指定する。

カメラの番号は、内蔵カメラが0、さらにUSBで追加のカメラを接続すると1のように基本的には0から順番に割り当てられているはず(公式ドキュメントのチュートリアルによると-1の場合もあるとのこと)。とりあえず順番に試してみればよい。

利用できるカメラの一覧を取得したりするAPIは2019年1月27日時点ではまだない模様。

正常に読み込めたかどうかは動画ファイルの場合と同様にisOpened()メソッドで確認できる。

import cv2

cap_cam = cv2.VideoCapture(0)
print(type(cap_cam))
# <class 'cv2.VideoCapture'>

print(cap_cam.isOpened())
# True

動画ファイルと同じく、存在しない番号を指定してもVideoCapture()の時点ではエラーにならないので注意。

cap_cam_wrong = cv2.VideoCapture(1)
print(type(cap_cam_wrong))
# <class 'cv2.VideoCapture'>

print(cap_cam_wrong.isOpened())
# False

動画を閉じる

読み込んだ動画ファイルやカメラデバイスを閉じるにはrelease()メソッドを実行する。

cap_cam.release()

明示的にrelease()を実行しなくてもVideoCaptureオブジェクトのデストラクタが正常に実行されれば自動的にrelease()が実行される。

動画のプロパティ(サイズやFPS、フレーム数など)の取得・設定

プロパティの取得: get()

VideoCaptureオブジェクトのget()メソッドで開いた動画のプロパティ(サイズやFPS、フレーム数など)を取得して確認できる。

ここでは以下のサンプル動画を例として使っている。

import cv2

cap = cv2.VideoCapture('data/temp/sample_video.mp4')
print(type(cap))
# <class 'cv2.VideoCapture'>

print(cap.isOpened())
# True

get()メソッドの引数に取得したいプロパティを指定する。

例えば、動画のサイズ(幅・高さ)、FPS(1秒あたりのフレーム数)、総フレーム数は以下のように取得できる。

print(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
# 640.0

print(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 360.0

print(cap.get(cv2.CAP_PROP_FPS))
# 29.97002997002997

print(cap.get(cv2.CAP_PROP_FRAME_COUNT))
# 360.0

動画の秒数(再生時間)は総フレーム数 / FPSで算出できる。

print(cap.get(cv2.CAP_PROP_FRAME_COUNT) / cap.get(cv2.CAP_PROP_FPS))
# 12.012

プロパティの識別名一覧は公式ドキュメントを参照。

OpenCV2ではCV_を先頭に付ける必要があるので注意。

上の例は動画ファイルだが、カメラの映像の場合も同様。ただし、カメラはリアルタイムの映像なので総フレーム数は設定されていない(常に0)などの違いがある。

プロパティの設定: set()

プロパティによってはset()メソッドで任意の値を設定できるものもある。第一引数にプロパティ、第二引数に値を指定する。

set()TrueFalseを返す。変更が許可されていないとFalseを返す。この場合は値は変わらない。

print(cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320))
# False

print(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
# 640.0

値が変更できるプロパティだとTrueを返す。

print(cap.get(cv2.CAP_PROP_POS_FRAMES))
# 0.0

print(cap.set(cv2.CAP_PROP_POS_FRAMES, 100))
# True

print(cap.get(cv2.CAP_PROP_POS_FRAMES))
# 100.0

このCAP_PROP_POS_FRAMESは動画の現在位置を表すプロパティ。後述。

なお、set()Trueを返しても値が変更されているとは限らない。例えば、カメラのFPSを変更する場合はset()を使うが、カメラ自体がそのFPSに対応していないとset()Trueを返しても値は変わっていないことがある。以下の記事を参照。

フレームの画像をNumPy配列ndarrayで取得

VideoCaptureオブジェクトのread()メソッドで動画のフレーム(コマ)をNumPy配列ndarrayとして取得できる。

動画ファイルの場合

動画ファイルの場合は現在の位置を考慮する必要がある。

get()メソッドで以下のように現在のフレーム数と経過時間(ミリ秒)を取得できる。ファイルを開いた時点ではどちらも0

import cv2

cap = cv2.VideoCapture('data/temp/sample_video.mp4')
print(type(cap))
# <class 'cv2.VideoCapture'>

print(cap.isOpened())
# True

print(cap.get(cv2.CAP_PROP_POS_FRAMES))
# 0.0

print(cap.get(cv2.CAP_PROP_POS_MSEC))
# 0.0

ここでread()メソッドを呼ぶ。

read()メソッドの返り値はフレームの画像が読み込めたかどうかを示すbool値と画像の配列ndarrayのタプル。アンパックでそれぞれの変数に代入できる。

ret, frame = cap.read()

print(ret)
# True

print(type(frame))
# <class 'numpy.ndarray'>

print(frame.shape)
# (360, 640, 3)

画像を保存したり何らかの処理をしたりしたい場合はこのndarrayに対して処理すればよい。

read()メソッドが実行されると現在位置が1フレームぶん進む。read()のたびに1フレームずつ読み込んでいくイメージ。1フレームの経過時間はFPSの逆数に等しい。ミリ秒なので1000をかけている。

print(cap.get(cv2.CAP_PROP_POS_FRAMES))
# 1.0

print(cap.get(cv2.CAP_PROP_POS_MSEC))
# 33.36666666666667

print(1 / cap.get(cv2.CAP_PROP_FPS) * 1000)
# 33.36666666666667

現在位置を任意のフレームに移動させたい場合はset()メソッドを使う。

cap.set(cv2.CAP_PROP_POS_FRAMES, 100)
print(cap.get(cv2.CAP_PROP_POS_FRAMES))
# 100.0

ここでread()を呼ぶと現在位置がそこから1フレームぶん進む。

ret, frame = cap.read()

print(ret)
# True

print(cap.get(cv2.CAP_PROP_POS_FRAMES))
# 101.0

print(cap.get(cv2.CAP_PROP_POS_MSEC))
# 3370.0333333333333

総フレーム数の値を現在位置に設定するとそれ以上のフレームは存在しないのでread()FalseNoneを返す。この場合、現在位置は進まず終端のまま。

cap.set(cv2.CAP_PROP_POS_FRAMES, cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(cap.get(cv2.CAP_PROP_POS_FRAMES))
# 360.0

ret, frame = cap.read()

print(ret)
# False

print(frame)
# None

print(cap.get(cv2.CAP_PROP_POS_FRAMES))
# 360.0

set()メソッドで総フレーム数より大きな値を設定した場合も現在位置は終端になる。

cap.set(cv2.CAP_PROP_POS_FRAMES, 1000)
print(cap.get(cv2.CAP_PROP_POS_FRAMES))
# 360.0

カメラ映像の場合

カメラ映像の場合はストリームで常に新しいフレームが入力されてくる。ファイルのように現在位置という概念はなく、現在位置を示すCAP_PROP_POS_FRAMESは常に0になる。

read()を呼ぶとそのタイミング以降に入力される最新の画像が読み込まれる。

import cv2

cap_cam = cv2.VideoCapture(0)
print(type(cap_cam))
# <class 'cv2.VideoCapture'>

print(cap_cam.isOpened())
# True

print(cap_cam.get(cv2.CAP_PROP_POS_FRAMES))
# 0.0

ret, frame = cap_cam.read()

print(ret)
# True

print(type(frame))
# <class 'numpy.ndarray'>

print(frame.shape)
# (720, 1280, 3)

print(cap_cam.get(cv2.CAP_PROP_POS_FRAMES))
# 0.0

動画の再生(表示)

OpenCVには動画を再生(表示)する特別なメソッドはない。read()で読み込んだ画像を順次imshow()で表示していくだけ。あくまでも画像の表示なので音声は無視される。

サンプルコードを示す。

動画再生時のFPSについての詳細は以下の記事を参照。

動画ファイルを無限ループで表示

動画ファイルの映像をエンドレスリピートで表示し続ける例。キーボードのqを押すと終了する。

import cv2
import sys

file_path = 'data/temp/sample_video.mp4'
delay = 1
window_name = 'frame'

cap = cv2.VideoCapture(file_path)

if not cap.isOpened():
    sys.exit()

while True:
    ret, frame = cap.read()
    if ret:
        cv2.imshow(window_name, frame)
        if cv2.waitKey(delay) & 0xFF == ord('q'):
            break
    else:
        cap.set(cv2.CAP_PROP_POS_FRAMES, 0)

cv2.destroyWindow(window_name)

isOpened()Falseの場合はその時点で終了する。isOpened()Trueの場合は、while文による無限ループを使いキーボードのqが押されるまで画像を読み込んで表示し続ける。

read()メソッドの返り値を判定して、終端まで到達したときにset()メソッドで現在位置を0(動画の開始位置)に戻すという処理になっている。ループさせずに1回だけ再生したい場合はelseの後をbreakにすればよい。

waitKey()は引数に指定した時間(単位はミリ秒)動作を止めてキーボード入力を待つ関数。0xFFとの&をとっているのは64bit環境対応のため。waitKey()の引数を0とするとキーボード入力がされるまで無限に待ち続けるので、何らかのキーが押されるまで表示画像は更新されない。q以外のキーを押すと画像が更新される。

destroyWindow()は引数に指定した名前のウインドウを閉じる関数。すべてのウインドウを閉じるdestroyAllWindows()という関数もある。ここでは先に開いたウインドウをのみを閉じている。

上述のように、release()メソッドはVideoCaptureオブジェクトのデストラクタで自動的に呼ばれるのでここでは省略している。このあともコードが続くようであれば明示的にrelease()で閉じておいたほうが安全かもしれない。

カメラのリアルタイム映像を表示

カメラの映像をそのまま再生する例。キーボードのqを押すと終了する。

import cv2
import sys

camera_id = 0
delay = 1
window_name = 'frame'

cap = cv2.VideoCapture(camera_id)

if not cap.isOpened():
    sys.exit()

while True:
    ret, frame = cap.read()
    cv2.imshow(window_name, frame)
    if cv2.waitKey(delay) & 0xFF == ord('q'):
        break

cv2.destroyWindow(window_name)

カメラが不安定な場合はread()で読み込めないフレームが発生する((False, None)が返される)ことがある。そのような場合はif retで判定して正しく読み込めた場合のみ表示すればよい。

リアルタイムで画像処理

読み込んだカメラの映像をリアルタイムで処理して表示(再生)するには、read()imshow()の間に任意の処理を加えればよい。

白黒化とぼかし処理を行う例は以下の通り。

import cv2
import sys

camera_id = 0
delay = 1
window_name = 'frame'

cap = cv2.VideoCapture(camera_id)

if not cap.isOpened():
    sys.exit()

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

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    blur = cv2.GaussianBlur(gray, (0, 0), 5)

    cv2.imshow(window_name, blur)
    if cv2.waitKey(delay) & 0xFF == ord('q'):
        break

cv2.destroyWindow(window_name)

関連カテゴリー

関連記事