動画(mp4)から画像(jpg)を指定したフレーム間隔で切り出すプログラム | そう備忘録

動画(mp4)から画像(jpg)を指定したフレーム間隔で切り出すプログラム

動画から画像を切り出す

前回の記事でGoogleのAutoML Visionでレゴのパーツを機械学習させた。

その際は学習させる画像を用意するに辺り、動画から手動でjpgを一枚一枚切り出したのだがこれはかなり手間の掛かる作業だった。

何回もこの作業をやるのは気が重くなったのでPythonのプログラムで自動化してみた時の備忘録。

同じ様な事をしたい人の参考になれば幸いです

仕様

まずは必要な(欲しい)機能を羅列した。

  1. 読み込む動画ファイルを指定できる
  2. 指定したフレーム間隔ごとに保存できる
  3. 画像の出力先ディレクトリを指定できる(動画とは別のディレクトリに保存できるようにする)
  4. 画像ファイル名のプレフィックスを指定できる
  5. フレーム番号の始まりが指定可能できる(例えば3フレーム目から抽出する)
  6. 画像を指定したサイズでリサイズしてリサイズ後に正方形にトリミングできる
  7. ピンぼけ画像は出力しない

機械学習の他の例を見ると250✕250 px程度の画像で学習されている事が多く、大きな画像ほど学習の精度が高まるわけでは無いみたいなので6.のリサイズとトリミングの機能をつけることにした。

環境

プログラムを実行した環境は以下の通り。

Windows環境で実行しているが特別なプログラミングはしていないので恐らくだが他の環境でも動作すると思う。

OS

Windows10 Home

Pythonディストリビューション

Anaconda3

Pythonバージョン

Python3.7

プログラム

必要なモジュール

プログラム中で画像の各種加工にOpenCV(Open Source Computer Vision Library)を使用してるので事前にインストールしておく。

自分はWindows環境にAnacondaをインストールしてPythonを動かしていたのでAnaconda Promptから以下のコマンドでOpenCVをインストールした。

conda install -c conda-forge opencv

以下の画面が表示されるので”Y”を入力する。

OpenCVのインストール

OpenCVがインストールされた。

OpenCVがインストールされた

ロジック

プログラムは以下の通り。

冗長なロジックも多くて恥ずかしいのだが誰かの参考になるかも知れないので公開しておく。

# -*- coding: utf-8 -*-
"""
Created on Thu Oct 24 09:46:32 2019

@author: souichirou Kikuchi
動画を読み込んで画像(jpg)に分割するプログラム
--movie:読み込む動画ファイル名
--frame:何フレームごとに保存するか
--output_dir:画像の出力先ディレクトリ(無ければ作成する)
--prefix:画像ファイル名のプレフィックス
--start_number:フレーム番号の始まりを指定する(3を指定すると3フレーム目から抽出する)
--re_size:縦横のサイズ(省略すると250)リサイズ後に正方形にトリミングする
--lap:ピンぼけ判定のしきい値(省略すると25)この数値以上の画像のみが保存される

"""

import argparse
import cv2
import os

def save_frame_range(movie, frame, output_dir, prefix, start_frame, re_size, lap, ext='jpg'):
    # movieの読み込み
    cap = cv2.VideoCapture(movie)
    # Open出来なければ終了
    if not cap.isOpened():
        return
    # 幅
    width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
    # 高さ
    height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
    # 総フレーム数
    count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    # fps
    fps = cap.get(cv2.CAP_PROP_FPS)
    print("Original size:width:{}, height:{}, count:{}, fps:{}".format(width, height, count, fps))
    # 指定されたサイズにリサイズする為の計算
    # 短い方を指定されたサイズにした後、正方形にトリミングする為の計算
    if height <= width:
        re_width = int(width/height*re_size)
        re_height = re_size
        start_pos_w = int((re_width -re_size)/2)
        start_pos_h = 0
    else:
        re_width = re_size
        re_height = int(height/width*re_size)
        start_pos_w = 0
        start_pos_h = int((re_height -re_size)/2)

    # 指定されたサイズの配列を作成
    new_size = (re_width, re_height)

    # ディレクトリが無ければ作成
    os.makedirs(output_dir, exist_ok=True)
    # 出力先ディレクトリとファイル名を結合する
    base_path = os.path.join(output_dir, prefix)
    # 総フレーム数の桁数を取得する
    digit = len(str(int(count)))
    for n in range(start_frame, count, frame):
        cap.set(cv2.CAP_PROP_POS_FRAMES, n)
        ret, frame_image = cap.read() # 画像を読み込む
        # 指定されたサイズに変更する(縦横比は変更しない)
        resize_image = cv2.resize(frame_image, new_size)
        # 正方形にトリミングする
        output_image = resize_image[start_pos_h:start_pos_h + re_size, start_pos_w:start_pos_w + re_size]
        # ピンぼけ判定の為にLaplacianを呼び出し
        laplacian = cv2.Laplacian(output_image, cv2.CV_64F)
        if ret and laplacian.var() >= lap: # ピンぼけ判定がしきい値以上のもののみ出力
            cv2.imwrite('{}_{}.{}'.format(base_path, str(n).zfill(digit), ext), output_image)
# main
def main():
    print('--- start program ---')
    # パラメータの取得
    parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument(
            '--movie', help='動画(mp4)のファイルパスを指定する', required=True)
    parser.add_argument(
            '--frame', help='指定したフレーム毎に分割する', required=True)
    parser.add_argument(
            '--output_dir', help='出力先のディレクトリ', required=True)
    parser.add_argument(
            '--prefix', help='出力する画像ファイル(jpg)の接頭文字を指定する', required=True)
    parser.add_argument(
            '--start_frame', help='フレーム番号の始まりを指定する', type=int, default=1)
    parser.add_argument(
            '--re_size', help='縦横のサイズを指定する', type=int, default=250)
    parser.add_argument(
            '--lap', help='ピンぼけ判定のしきい値', type=int, default=25)
    args = parser.parse_args()

    try:
        # 動画からjpgファイルの切り出し
        save_frame_range(args.movie, int(args.frame), args.output_dir, args.prefix, args.start_frame, args.re_size, args.lap)
    finally:
      pass

if __name__ == '__main__':
  main()

説明

プログラム中にコメントを入れているので細かい説明は不要だと思うので分かりづらそうな所だけを説明をする。

36~47行目と61~64行目は読み込んだ動画の縦横サイズの短い辺を–re_sizeで指定したサイズにリサイズして長い辺をカットしている。

画像リサイズとトリミングのイメージ図

65~68行目はcv2(OpenCV)のLaplacianメソッドを使ってエッジ(輪郭)抽出を行い–lapで指定された値以下の画像についてはエッジが抽出できない(=ピンぼけ)と判断して画像を出力していない。

プログラムの起動方法

以下のコマンドでプログラムを起動する。

python MovieSpriter.py --movie xxx.mp4 --frame 10 --output_dir images --prefix yyy --start_frame 1 --re_size 250 --lap 40
–movie xxx.mp4読み込む動画ファイル名
–frame 1010フレーム毎に保存する
–output_dir imagesカレントディレクトリ配下の”images”フォルダーに出力する
–prefix yyyyyy_nnnn.jpgというファイル名で画像ファイルが作成される

nnnnはフレーム番号

–start_frame 11フレーム目から出力する
–re_size 250画像サイズを250✕250でリサイズ、トリミングする
–lap 40Laplacianメソッドでエッジが40以上検出された画像のみを出力する

実行結果

コマンド実行結果

コマンド実行結果

imagesフォルダーに250✕250で画像が出力される。

切り出した画像

souichirou

やった事を忘れない為の備忘録 同じような事をやりたい人の参考になればと思ってブログにしてます。 主にレゴ、AWS(Amazon Web Services)、WordPress、Deep Learning、RaspberryPiに関するブログを書いています。 仕事では工場に協働ロボットの導入や中小企業へのAI/IoT導入のアドバイザーをやっています。 2019年7月にJDLA(一般社団法人 日本デイープラーニング協会)Deep Learning for GENERALに合格しました。 質問は記事一番下にあるコメントかメニュー上部の問い合わせからお願いします。

おすすめ

2件のフィードバック

  1. 伊藤慎一 より:

    管理人様
    いつも新しい情報を公開していただきありがたく思っております。
    「動画(mp4)から画像(jpg)を切り出すプログラム」そして「AutoML Visionでレゴブロックの機械学習をさせてみた」の記事、とても参考になりました。
    とくにAutoML Visionに関してはまったく知らない情報だったので、今後Coral USB Acceleratorともども取り組みたいな思っております。
    こちらのサイトの情報はとても丁寧で正確で大変ありがたく思っております。
    動画そして活字での情報公開に感謝するとともに、管理人様のご苦労とご努力に頭が下がる思いでおります。

    • souichirou より:

      伊藤慎一さん
      嬉しいコメントありがとうございます。
      AutoML Visionは面白い技術ですよね。
      記事にはしていませんがあれから色々と試したところ学習用の画像の解像度(記事中では250✕250)を上げた所、より精度が上がりました。
      ただGoogleからの請求も上がりました?

伊藤慎一 へ返信する コメントをキャンセル

名前、メール、サイト欄は任意です。
またメールアドレスは公開されません。