動画(mp4)から画像(jpg)を指定したフレーム間隔で切り出すプログラム
Contents
動画から画像を切り出す
前回の記事でGoogleのAutoML Visionでレゴのパーツを機械学習させた。
その際は学習させる画像を用意するに辺り、動画から手動でjpgを一枚一枚切り出したのだがこれはかなり手間の掛かる作業だった。
何回もこの作業をやるのは気が重くなったのでPythonのプログラムで自動化してみた時の備忘録。
仕様
まずは必要な(欲しい)機能を羅列した。
- 読み込む動画ファイルを指定できる
- 指定したフレーム間隔ごとに保存できる
- 画像の出力先ディレクトリを指定できる(動画とは別のディレクトリに保存できるようにする)
- 画像ファイル名のプレフィックスを指定できる
- フレーム番号の始まりが指定可能できる(例えば3フレーム目から抽出する)
- 画像を指定したサイズでリサイズしてリサイズ後に正方形にトリミングできる
- ピンぼけ画像は出力しない
機械学習の他の例を見ると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がインストールされた。
ロジック
プログラムは以下の通り。
冗長なロジックも多くて恥ずかしいのだが誰かの参考になるかも知れないので公開しておく。
# -*- 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 10 | 10フレーム毎に保存する |
–output_dir images | カレントディレクトリ配下の”images”フォルダーに出力する |
–prefix yyy | yyy_nnnn.jpgというファイル名で画像ファイルが作成される nnnnはフレーム番号 |
–start_frame 1 | 1フレーム目から出力する |
–re_size 250 | 画像サイズを250✕250でリサイズ、トリミングする |
–lap 40 | Laplacianメソッドでエッジが40以上検出された画像のみを出力する |
実行結果
コマンド実行結果
imagesフォルダーに250✕250で画像が出力される。
管理人様
いつも新しい情報を公開していただきありがたく思っております。
「動画(mp4)から画像(jpg)を切り出すプログラム」そして「AutoML Visionでレゴブロックの機械学習をさせてみた」の記事、とても参考になりました。
とくにAutoML Visionに関してはまったく知らない情報だったので、今後Coral USB Acceleratorともども取り組みたいな思っております。
こちらのサイトの情報はとても丁寧で正確で大変ありがたく思っております。
動画そして活字での情報公開に感謝するとともに、管理人様のご苦労とご努力に頭が下がる思いでおります。
伊藤慎一さん
嬉しいコメントありがとうございます。
AutoML Visionは面白い技術ですよね。
記事にはしていませんがあれから色々と試したところ学習用の画像の解像度(記事中では250✕250)を上げた所、より精度が上がりました。
ただGoogleからの請求も上がりました?