2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

動画からフレーム単位で連番ファイルを抽出してフレーム数と時間を付ける(cap.get(cv2.CAP_PROP_FRAME_COUNT)が、ファイルの実際のフレーム数と違う値を返す時の、雑な対応付き)

Posted at

###はじめに
動画からフレーム単位で連番ファイルを抽出するPythonプログラムを作った。

https://note.nkmk.me/python-opencv-video-to-still-image/
Python, OpenCVで動画ファイルからフレームを切り出して保存

を参考にした。と言うか、上記サイトにある、フレーム毎に出力するのと、時間を表記するのを合わせただけ。
細かい追加としては、時間を秒だけでなく、時間_分_秒表記にしたのと、全フレームでなく、指定した単位で飛ばして出力するようにした。

ただ、いくつかの動画で実行してみると、正しい時間が表示されないものがあり、調べて見ると、cap.get(cv2.CAP_PROP_FRAME_COUNT)で得られるフレーム数と、実際の動画のフレーム数が異なっているのが原因だった。
実際に数えてみる、という雑な手法で、解決したものを、後半に掲示する。

#動画からフレーム単位で連番ファイルを抽出してフレーム数と時間を付けるスクリプト

import cv2
import os

def save_all_frames(video_path, dir_path, basename, step_num, 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)
    fps_inv = 1 / fps

    n = 0

    while True:
        ret, frame = cap.read()
        #read()メソッドで動画を1フレームずつ読み込む。retにはbool値が入る。動画の最後まで来るとwhile処理が終了
        if ret:
            if n % step_num ==0:
                cv2.imwrite('{}_{}frm_{:02.0f}h_{:02.0f}min_{:02.0f}sec.{}'.format(base_path, str(n).zfill(digit), (n * fps_inv) // 3600, ((n * fps_inv) // 60) %60, (n * fps_inv) % 60, ext), frame)
            n += 1
        else:
            return

save_all_frames('data/temp/sample_video.mp4', 'data/temp/result', 'sample_video_img', 10)
#引数は、元の動画の場所、出力画像の保存フォルダ、出力画像の頭に付ける文字列、フレームを飛ばす数(全フレーム出力なら1を設定)

#cap.get(cv2.CAP_PROP_FRAME_COUNT)で得られるフレーム数と、実際の動画のフレーム数が異なっている場合に、定義したreal_frame_num_count関数で、実際のフレーム数を数えて、それを使って、ファイル名に時間を表記するプログラム

import cv2
import os

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

    if not cap.isOpened():
        return

    real_frame_num = real_frame_num_count(video_path)
    
    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))))

    total_sec = cap.get(cv2.CAP_PROP_FRAME_COUNT) / cap.get(cv2.CAP_PROP_FPS)
    fps_inv = total_sec / real_frame_num

    n = 0

    while True:
        ret, frame = cap.read()
        #read()メソッドで動画を1フレームずつ読み込む。retにはbool値が入る。動画の最後まで来るとwhile処理が終了
        if ret:
            if n % step_num ==0:
                cv2.imwrite('{}_{}frm_{:02.0f}h_{:02.0f}min_{:02.0f}sec.{}'.format(base_path, str(n).zfill(digit), (n * fps_inv) // 3600, ((n * fps_inv) // 60) %60, (n * fps_inv) % 60, ext), frame)
            n += 1
        else:
            return

def real_frame_num_count(video_path):
    real_frame_num = 0

    cap = cv2.VideoCapture(video_path)

    if not cap.isOpened():
        return

    while True:
        ret, frame = cap.read()
        if ret:
            real_frame_num += 1
        else:
            return real_frame_num


save_all_frames('data/temp/sample_video.mp4', 'data/temp/result', 'sample_video_img', 10)
#引数は、元の動画の場所、出力画像の保存フォルダ、出力画像の頭に付ける文字列、フレームを飛ばす数(全フレーム出力なら1を設定)

real_frame_num_count関数の実行だけで、結構な時間が掛かってしまい、実行してもすぐにはファイルは作成されていかないのが問題。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?