note.nkmk.me

Python, OpenCVで動画ファイルからフレームを切り出して保存

Date: 2019-01-27 / tags: Python, OpenCV

Python, OpenCVでmp4やaviなどの動画ファイルからフレームを切り出して静止画の画像ファイルとして保存する方法について説明する。

単純に動画をフレームごとの静止画として切り出すだけならffmpegのコマンドで可能だが、何らかの画像処理を行ってから保存したい場合はPython + OpenCVを使うと便利。

ここでは以下のサンプルコードを示す。

  • すべてのフレームを画像ファイルとして保存
  • 任意のフレームを指定して画像ファイルとして保存
    • 単独のフレームを指定
    • 任意の範囲のフレームを指定
    • 時間(秒数)で指定
  • 動画を再生しながらキーボード押下で保存

動画ファイルではなく、カメラ(内蔵カメラ・USBカメラ・Webカメラ)のリアルタイム映像から静止画を切り出して保存したい場合は以下の記事を参照。

OpenCVにおける動画の読み込みや基本的な扱いについて以下の記事を参照。

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

スポンサーリンク

すべてのフレームを画像ファイルとして保存

動画ファイルのすべてのフレームを静止画の画像ファイルとして切り出して保存するサンプルコード。指定したディレクトリに<basename>_<連番>.<拡張子>というファイル名で保存する。

import cv2
import os

def save_all_frames(video_path, dir_path, basename, ext='jpg'):
    cap = cv2.VideoCapture(video_path)

    if not cap.isOpened():
        return

    os.makedirs(dir_path, exist_ok=True)
    base_path = os.path.join(dir_path, basename)

    digit = len(str(int(cap.get(cv2.CAP_PROP_FRAME_COUNT))))

    n = 0

    while True:
        ret, frame = cap.read()
        if ret:
            cv2.imwrite('{}_{}.{}'.format(base_path, str(n).zfill(digit), ext), frame)
            n += 1
        else:
            return

save_all_frames('data/temp/sample_video.mp4', 'data/temp/result', 'sample_video_img')

save_all_frames('data/temp/sample_video.mp4', 'data/temp/result_png', 'sample_video_img', 'png')

os.makedirs()でディレクトリを作成する。引数exist_okはPython 3.2より前のバージョンでは使用できないので注意。

パス文字列の結合はos.path.join()

総フレーム数をCAP_PROP_FRAME_COUNT(OpenCV2ではCV_CAP_PROP_FRAME_COUNT)で取得し、その桁数を算出。その桁数に応じてzfill()でゼロ埋めする。

任意のフレームを指定して画像ファイルとして保存

単独のフレームを指定

フレーム番号を指定して静止画の画像ファイルとして切り出して保存するサンプルコード。この例では保存先ディレクトリやファイル名、拡張子をまとめて保存先のパスとして指定するようにしている。

def save_frame(video_path, frame_num, result_path):
    cap = cv2.VideoCapture(video_path)

    if not cap.isOpened():
        return

    os.makedirs(os.path.dirname(result_path), exist_ok=True)

    cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num)

    ret, frame = cap.read()

    if ret:
        cv2.imwrite(result_path, frame)

save_frame('data/temp/sample_video.mp4', 100, 'data/temp/result_single/sample_100.jpg')

os.path.dirname()で保存先のパスのディレクトリを取得してos.makedirs()で作成。

任意の範囲のフレームを指定

任意の範囲のフレームを静止画の画像ファイルとして切り出して保存するサンプルコード。

def save_frame_range(video_path, start_frame, stop_frame, step_frame,
                     dir_path, basename, ext='jpg'):
    cap = cv2.VideoCapture(video_path)

    if not cap.isOpened():
        return

    os.makedirs(dir_path, exist_ok=True)
    base_path = os.path.join(dir_path, basename)

    digit = len(str(int(cap.get(cv2.CAP_PROP_FRAME_COUNT))))

    for n in range(start_frame, stop_frame, step_frame):
        cap.set(cv2.CAP_PROP_POS_FRAMES, n)
        ret, frame = cap.read()
        if ret:
            cv2.imwrite('{}_{}.{}'.format(base_path, str(n).zfill(digit), ext), frame)
            n += 1
        else:
            return

save_frame_range('data/temp/sample_video.mp4',
                 200, 300, 10,
                 'data/temp/result_range', 'sample_video_img')

引数step_frameで100フレーム毎などの設定も可能。開始フレームstart_frame、終了フレームstop_frameおよびstep_frameはそのままrange()に渡している。

時間(秒数)で指定

これまでの例はフレーム番号で指定しているが、秒数で指定したい場合はFPSをもとに計算すればよい。

FPS = frame / time[sec]なので、time[sec] = frame / FPSとなる。フレーム番号は整数である必要があるためround()で丸める。指定した秒数との間には誤差が発生するので注意。

あとはこれまでの例とほぼ同じ。

単独のフレームを指定。

def save_frame_sec(video_path, sec, result_path):
    cap = cv2.VideoCapture(video_path)

    if not cap.isOpened():
        return

    os.makedirs(os.path.dirname(result_path), exist_ok=True)

    fps = cap.get(cv2.CAP_PROP_FPS)

    cap.set(cv2.CAP_PROP_POS_FRAMES, round(fps * sec))

    ret, frame = cap.read()

    if ret:
        cv2.imwrite(result_path, frame)

save_frame_sec('data/temp/sample_video.mp4', 10, 'data/temp/result_10sec.jpg')

範囲を指定。ステップの指定を先に秒数から算出すると徐々にズレが大きくなるので、ここでは毎回算出している。

def save_frame_range_sec(video_path, start_sec, stop_sec, step_sec,
                         dir_path, basename, ext='jpg'):
    cap = cv2.VideoCapture(video_path)

    if not cap.isOpened():
        return

    os.makedirs(dir_path, exist_ok=True)
    base_path = os.path.join(dir_path, basename)

    digit = len(str(int(cap.get(cv2.CAP_PROP_FRAME_COUNT))))

    fps = cap.get(cv2.CAP_PROP_FPS)

    for sec in range(start_sec, stop_sec, step_sec):
        n = round(fps * sec)
        cap.set(cv2.CAP_PROP_POS_FRAMES, n)
        ret, frame = cap.read()
        if ret:
            cv2.imwrite('{}_{}.{}'.format(base_path, str(n).zfill(digit), ext), frame)
            n += 1
        else:
            return

動画を再生しながらキーボード押下で保存

動画を再生して確認しながらキーボードを押下したタイミングのフレームを静止画の画像ファイルとして切り出して保存するサンプルコード。

エンドレスリピートで動画が再生され、キーボードのqを押すと終了、cを押すと静止画が保存される。waitKey()に渡すdelayを0にすると、q, c以外のキーを押すたびにフレームが進むようになる。

import cv2
import os

def save_frame_play(video_path, dir_path, basename, ext='jpg', delay=1, window_name='frame'):
    cap = cv2.VideoCapture(video_path)

    if not cap.isOpened():
        return

    os.makedirs(dir_path, exist_ok=True)
    base_path = os.path.join(dir_path, basename)

    digit = len(str(int(cap.get(cv2.CAP_PROP_FRAME_COUNT))))

    n = 0
    while True:
        ret, frame = cap.read()
        if ret:
            cv2.imshow(window_name, frame)
            key = cv2.waitKey(delay) & 0xFF
            if key == ord('c'):
                cv2.imwrite('{}_{}.{}'.format(base_path, str(n).zfill(digit), ext), frame)
            elif key == ord('q'):
                break
            n += 1
        else:
            cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
            n = 0

    cv2.destroyWindow(window_name)


save_frame_play('data/temp/sample_video.mp4', 'data/temp', 'sample_video_cap', delay=0)

動画の再生についての詳細は以下の記事を参照。read()で読み出した画像をimshow()で表示しているだけ。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事