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()
はTrue
かFalse
を返す。変更が許可されていないと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()
はFalse
とNone
を返す。この場合、現在位置は進まず終端のまま。
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)