Warning: Undefined variable $position in /home/pystyles/pystyle.info/public_html/wp/wp-content/themes/lionblog/functions.php on line 4897

OpenCV – VideoCapture、VideoWriter の使い方

OpenCV – VideoCapture、VideoWriter の使い方

概要

OpenCV の VideoCaptureVideoWriter の使い方を紹介します。

  • VideoCapture
    • Web カメラから映像を取得する
    • 動画ファイルからフレームを取得する
    • 連番の画像ファイルから順番に画像を取得する
  • VideoWriter
    • Web カメラから映像を取得し、動画として保存する
    • 動画ファイルからフレームを取得し、画像として保存する
Advertisement

VideoCapture

Web カメラから映像を取得する

引数に映像を取得するカメラデバイスの ID を指定します。 カメラが1台しか接続されていない場合は、0を指定します。

cv2.VideoCapture(index)
引数
名前 デフォルト値
index int
デバイス ID (0, 1, …)

Logicool の Logitech C270 HD Webcam を PC に接続して、確認しました。 フレームの大きさや FPS といった情報は VideoCapture.get() で取得できます。

In [ ]:
import cv2

# VideoCapture を作成する。
device_id = 0
cap = cv2.VideoCapture(device_id)

width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = int(cap.get(cv2.CAP_PROP_FPS))
print(f"size: ({width}, {height}), fps: {fps}")

while True:
    # 1フレームずつ取得する。
    ret, frame = cap.read()
    if not ret:
        break  # 取得に失敗した場合

    cv2.imshow("Frame", frame)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break  # q キーを押したら終了する。

cap.release()
cv2.destroyAllWindows()

動画ファイルからフレームを取得する

cv2.VideoCapture(filename)
引数
名前 デフォルト値
filename str
動画ファイル名
In [ ]:
import cv2

filepath = "sample.avi"
cap = cv2.VideoCapture(filepath)

width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = int(cap.get(cv2.CAP_PROP_FPS))
print(f"size: ({width}, {height}), fps: {fps}")

while True:
    # 1フレームずつ取得する。
    ret, frame = cap.read()
    if not ret:
        break  # 取得に失敗した場合

    cv2.imshow("Frame", frame)
    cv2.waitKey(1)

cap.release()
cv2.destroyAllWindows()

sleep を入れない場合、PC の処理能力で可能な速度で映像が取得されます。 動画の実際の FPS で再生したい場合は、適当に sleep する必要があります。

FPS は1秒間のフレームレートなので、フレームを取得する間に $\frac{1}{\text{FPS}}$ 秒だけ sleep すればよいことになります。 実際はフレームの取得自体にも時間がかかるので、それを除いた以下の時間 sleep すればよいです。

$$ \text{sleep する時間 (secs)} = \frac{1}{\text{FPS}} – \text{フレーム取得にかかった時間 (secs)} $$
In [ ]:
import cv2
import time

filepath = "sample.avi"
cap = cv2.VideoCapture(filepath)

width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = int(cap.get(cv2.CAP_PROP_FPS))
print(f"size: ({width}, {height}), fps: {fps}")

while True:
    start = time.time()
    # 1フレームずつ取得する。
    ret, frame = cap.read()
    if not ret:
        break  # 取得に失敗した場合
    elapsed_secs = time.time() - start  # 映像取得にかかった時間 (secs)

    sleep_secs = max(1, int((1 / fps - elapsed_secs) * 1000))  # sleep する時間 (secs)
    cv2.imshow("Frame", frame)
    if cv2.waitKey(sleep_secs) & 0xFF == ord("q"):
        break  # q キーを押したら終了する。

cap.release()
Advertisement

連番の画像ファイルから順番に画像を取得する

以下のようにあるディレクトリ以下に連番で画像ファイルがある場合、VideoCapture で順番に読み込むことができます。filename には、’frame_%03d.png’ のようにフレーム番号の部分を書式指定子で指定します。VideoWriter を使えば、連番画像を動画化できます。

output
├── frame_001.png
├── frame_002.png
├── frame_003.png
├── frame_004.png
├── frame_005.png
├── frame_006.png
├── frame_007.png
├── frame_008.png
├── frame_009.png
├── frame_010.png
...
In [ ]:
from pathlib import Path

import cv2

# 画像ファイルのパス
img_paths = Path("output") / "frame_%03d.png"

# VideoCapture を作成する。
cap = cv2.VideoCapture(str(img_paths))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = 15
print(f"width: {width}, height: {height}, fps: {fps}")

# VideoWriter を作成する。
fourcc = cv2.VideoWriter_fourcc(*"DIVX")
writer = cv2.VideoWriter("output.avi", fourcc, fps, (width, height))

while True:
    # 1フレームずつ取得する。
    ret, frame = cap.read()
    if not ret:
        break  # 取得に失敗した場合

    writer.write(frame)

writer.release()
cap.release()

VideoWriter

cv2.VideoWriter(filename, fourcc, fps, frameSize[, isColor])
引数
名前 デフォルト値
filename str
出力ファイル名
fourcc list of strs
FourCC を表す4つ文字のリスト (例: [‘H’, ‘2’, ‘6’, ‘4’])
fps float
FPS
frameSize tuple of 2-ints
フレームの大きさ
isColor bool True
write() で書き込む画像がグレースケールの場合は False にする

出力した動画が開けない場合

出力した動画が開けない場合は、フレームの書き込みに失敗しています。考えられる要因を以下に記載します。

  • fourcc に指定したエンコード方式が対応していない
  • frameSize に指定した大きさと実際に writer.write() で書き込むフレームの大きさが一致していない
  • isColor=True を指定しているのに、writer.write() で書き込むフレームがグレースケール形式になっている
Advertisement

Web カメラから映像を取得し、動画として保存する

In [ ]:
import cv2

# VideoCapture を作成する。
cap = cv2.VideoCapture(0)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)

# VideoWriter を作成する。
fourcc = cv2.VideoWriter_fourcc(*"DIVX")
writer = cv2.VideoWriter("output.avi", fourcc, fps, (width, height))

while True:
    # 1フレームずつ取得する。
    ret, frame = cap.read()
    if not ret:
        break  # 取得に失敗した場合

    writer.write(frame)  # フレームを書き込む。

writer.release()
cap.release()

動画の各フレームを画像として保存する

In [ ]:
from pathlib import Path

import cv2

# 保存するディレクトリ
output_dir = Path("output")
output_dir.mkdir(exist_ok=True)

# VideoCapture を作成する。
cap = cv2.VideoCapture("sample.avi")

n_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))  # フレーム数を取得する。
n_digits = len(str(n_frames))  # フレーム数の桁数を取得する。

while True:
    # 1フレームずつ取得する。
    ret, frame = cap.read()
    if not ret:
        break  # 取得に失敗した場合

    # フレームを画像として保存する。
    frame_no = int(cap.get(cv2.CAP_PROP_POS_FRAMES))

    save_path = output_dir / f"frame_{frame_no:0{n_digits}d}.png"
    cv2.imwrite(str(save_path), frame)

cap.release()

VideoCapture.get – フレームの大きさや FPS などを取得する

retval = cv2.VideoCapture.get(propId)
引数
名前 デフォルト値
propId int
プロパティID。VideoCaptureProperties を参照する。
返り値
名前 説明
retval 指定したプロパティの値。返り値は float になっている。
In [ ]:
import cv2

video_path = "sample.avi"
cap = cv2.VideoCapture(video_path)

# 各種プロパティを取得する。
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))  # フレームの幅
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))  # フレームの高さ
fps = int(cap.get(cv2.CAP_PROP_FPS))  # FPS
fourcc = int(cap.get(cv2.CAP_PROP_FOURCC))  # FOURCC
fourcc = fourcc.to_bytes(4, "little").decode("utf-8")  # int を4バイトずつ解釈する。
n_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))  # フレーム数の合計

print(
    f"width: {width}, height: {height}, fps: {fps}, forcc: {fourcc}, total frames: {n_frames}"
)

while True:
    # 1フレームずつ取得する。
    ret, frame = cap.read()
    if not ret:
        break  # 取得に失敗した場合

    curr_pos = int(cap.get(cv2.CAP_PROP_POS_MSEC))  # 動画の最初から何 ms のフレームか
    curr_frame_no = int(cap.get(cv2.CAP_PROP_POS_FRAMES))  # 現在のフレーム番号
    print(f"pos: {curr_pos} ms, frame no: {curr_frame_no}/{n_frames}")

    cv2.imshow("Frame", frame)
    cv2.waitKey(1)

cap.release()
cv2.destroyAllWindows()