LoginSignup
13
15

More than 1 year has passed since last update.

OpenCVとYOLOv5を使って動画切り抜きをしてみる

Last updated at Posted at 2022-09-09

はじめに

自動で動画切り抜きする方法を、クラウドを使わずにローカル環境で試してみた。
検知能力もそこそこ優秀で比較的処理が軽いと言われるYOLOv5と OpenCV を使って作ってみたので、メモとして残しておく。

実施環境

■ PCスペック
 CPU:Intel(R) Core(TM) i5-3470 CPU @ 3.20GHz 3.20 GHz
 メモリ:8.00 GB

■ OS
 Windows 10 Pro 

■ 各種SW&パッケージのバージョン(今回インストールするもの含む)
 python 3.9.5
 pip 21.3.1
 opencv-python 4.5.4.60
 YOLOv5

メモ内容

①環境準備の流れと、②Pythonコードをメモとして残す。

① 環境準備

色々なパッケージが必要となりローカル環境が散らかりそうだったため、自分は仮想環境上で環境を構築している。(手順は下記を参照)

パッケージのインストールの前にYoLov5のインストールに必要なソースをダウンロードする
 ※clone先は仮想環境(venv)で作成した場所を指定

git clone https://github.com/ultralytics/yolov5

必要パッケージのインストール

仮想環境をActivateにした状態で以下を実行。

pip install --upgrade pip

# パッケージのインストールコマンド
pip install
pip install datetime
pip install numpy pandas
pip install opencv-python

# 物体検出のための学習済みモデル利用に使うパッケージ一覧
pip install -r ./yolov5/requirements.txt

Pythonコマンドでimport cv2import torchが実行できれば、一旦インストール完了と考えてOK

(opencv) C:\Users\*******\*******>python  ## opencvという名前の仮想環境を作っているため(opencv)となっている。
Python 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> import torch

YOLOv5の動作確認
ターミナルから動かす場合は以下のコマンドを実行する。

cd yolov5
python detect.py --source ./data/images/ --weights yolov5s.pt --conf 0.4

上記コマンドの引数の説明。

 source:画像のフォルダ、または画像のパスを指定。
     ※./data/images にサンプル画像が入っている。
  conf:指定した値以下の確率値は表示しない。
weights:物体検出時に利用する事前学習済みモデルの重みファイルを指定。
     ※モデルが存在しない初回時はyolov5s.ptがダウンロードされる。。

実行後にフレームとラベル付きの画像ファイルがruns/detect/exp(デフォルト)に保存される。

※1).以下の様なオプションを付けることで、画像は保存せず、結果をテキストで出力すること可能。
 ・--save-txt  推論結果(検出座標と予測クラス)をtxtファイルに出力する
 ・--save-conf  推論結果(クラスの確率)をtxtファイルに出力する
 ・--nosave   画像を保存しない
  実行対象を動画ファイルにした場合、全てのフレームに対して画像検出処理がされる

※2).YOLOv5で検知できる物体の種類
person bicycle car motorcycle airplane bus train truck boat traffic light fire hydrant stop sign parking meter bench bird cat dog horse sheep cow elephant bear zebra giraffe backpack umbrella handbag tie suitcase frisbee skis snowboard sports ball kite baseball bat baseball glove skateboard surfboard tennis racket bottle wine glass cup fork knife spoon bowl banana apple sandwich orange broccoli carrot hot dog pizza donut cake chair couch potted plant bed dining table toilet tv laptop mouse remote keyboard cell phone microwave oven toaster sink refrigerator book clock vase scissors teddy bear hair drier toothbrus
 物体種類の指定の時は、クラス番号で指定する必要がある。詳しくは ./data/coco.yaml を見れば分かる。

② 実現するためのPython サンプルコードを紹介

今回は 『動画の中で鳥が映っている部分のみを自動で切り抜いてくれるプログラム』 を作ってみる。

サンプル動画は、森の中で一匹の鳥が見え隠れするような動画 を選定。

※以下の動画サンプルサイトから良さそうなものをダウンロードしました。
 https://pixabay.com/ja/videos/
 https://www.pexels.com/ja-jp/videos/
 https://www.motionelements.com/ja/free/stock-footage

以下の STEP で機能を作って実現していく。
 [STEP1] 動画のフレーム数を減らして短くする処理。
 [STEP2] 各フレームに対して物体検出を行い、鳥が映っているフレームのみ繋ぎ合わせる。

[STEP1] 動画のフレーム数を減らして短くする。

動画ファイルでそのまま物体検出を行うと、全てのフレームに対して検知処理が走ってしまい、必要以上の計算コストかかってしまう。
実際には 数秒間隔くらいで検出を行えれば十分なので、元の動画から数秒間隔のフレームだけで動画を繋ぎ合わす加工を行う。

動画のフレーム数を減らして短くする。
# パッケージインポート
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import cv2

###################################################################
# 指定した "秒" 間隔のみのフレームに絞り、動画ファイルを生成。
###################################################################

### 引数の説明
# path_in:元動画ファイルのパス
# path_out:出力動画ファイルのパス
# interval:抽出するフレームの間隔(秒)

def frame_out(file_in, file_out, interval):
    
    #
    movie= cv2.VideoCapture(file_in)
    
    # 元画像の画像サイズを取得
    w = int(movie.get(cv2.CAP_PROP_FRAME_WIDTH))
    h = int(movie.get(cv2.CAP_PROP_FRAME_HEIGHT))
    print(w, ' × ', h)

    # 元画像のfps 1秒あたりのフレーム数を取得
    fps = int(movie.get(cv2.CAP_PROP_FPS))
    
    # 再生時間と、抽出するフレームの間隔から、フレーム数を取得。
    end_frame = int(movie.get(cv2.CAP_PROP_FRAME_COUNT)/(movie.get(cv2.CAP_PROP_FPS) * interval))
    print('フレーム数:', end_frame)
        
    # 出力動画ファイルの生成
    fourcc = cv2.VideoWriter_fourcc('m','p','4', 'v')
    video = cv2.VideoWriter(file_out, fourcc, 1, (w, h))
    
    for sec in range(end_frame):
        
        # 取得するフレーム番号を指定
        pos_frame = int(fps * (sec + 1) * interval)        
        movie.set(cv2.CAP_PROP_POS_FRAMES, pos_frame)
        
        # フレームを読みだす。
        ret, frame = movie.read()
        if not ret:
            break
        video.write(frame)
            
    # 出力動画ファイルの解放
    video.release()

# 上記関数の実行処理(サンプル)
source_file = './サンプル動画_org.mp4'
output_file = './処理後動画.mp4'
interval = 1
frame_out(source_file, output_file, interval)

[STEP2] 各フレームに対して物体検出を行い、鳥が映っているフレームのみ繋ぎ合わせる。   

上記の[STEP1] を少し改良し、動画ファイルから抽出したフレームに対して物体検出を行い、"鳥" が映っているか判定する。
映っていた場合、生成動画に繋ぎ合わせる。

各フレームに対して物体検出を行い、鳥が映っているフレームのみ繋ぎ合わせる
# パッケージインポート
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import torch    # モデルを呼び出すために追加
import cv2

###################################################################
# 指定した "秒" 間隔のみのフレームに絞り、動画ファイルを生成。
###################################################################

### 引数の説明
# path_in:元動画ファイルのパス
# path_out:出力動画ファイルのパス
# interval:抽出するフレームの間隔(秒)
#   mdl:物体検知に使う学習済みモデル
#  label:検知したい物体
#  conf_border:検知したい物体の確率閾値

def frame_extract_withlabel(file_in, file_out, interval, mdl, label, conf_border):
    
    # 物体検出に使用するモデルの読み込み & 設定
    model = torch.hub.load('ultralytics/yolov5', mdl)
    model.classes = label
    model.conf = conf_border
    
    # 動画の読み込み
    movie= cv2.VideoCapture(file_in)
    
    # 元画像の画像サイズを取得
    w = int(movie.get(cv2.CAP_PROP_FRAME_WIDTH))
    h = int(movie.get(cv2.CAP_PROP_FRAME_HEIGHT))
    print(w, ' × ', h)

    # 元画像のfps 1秒あたりのフレーム数を取得
    fps = int(movie.get(cv2.CAP_PROP_FPS))
    
    # 再生時間と、抽出するフレームの間隔から、フレーム数を取得。
    end_frame = int(movie.get(cv2.CAP_PROP_FRAME_COUNT)/(movie.get(cv2.CAP_PROP_FPS) * interval))
    print('フレーム数:', end_frame)
        
    # 出力動画ファイルの生成
    fourcc = cv2.VideoWriter_fourcc('m','p','4', 'v')
    video = cv2.VideoWriter(file_out, fourcc, 1, (w, h))
    
    for sec in range(end_frame):
        
        # 取得するフレーム番号を指定
        pos_frame = int(fps * (sec + 1) * interval)        
        movie.set(cv2.CAP_PROP_POS_FRAMES, pos_frame)
        
        # フレームを読みだす。
        ret, frame = movie.read()
        
        if not ret:
            break
        
        ### 読み出されたフレーム毎に物体検知を行う
        results = model(frame)
        if len(results.pandas().xywh[0]) >0 :
            # 1~4カラムまでの物体位置を表す表現の仕方が異なる
            #display(results.pandas().xyxy[0])    # 検知した物体の square の4点の座標
            display(results.pandas().xywh[0])    #検知した物体の 中心X・Y座標 & square の高さと幅
            video.write(frame)
            
    # 出力動画ファイルの解放
    video.release()


# 上記関数の実行処理
source_file = './サンプル動画_自然_鳥.mp4'
output_file = './処理後動画.mp4'
interval = 0.5
mdl_name = 'yolov5x'
target_label = 14    # 鳥は "14" のため

frame_extract_withlabel(source_file, output_file, interval, mdl_name, target_label, 0.3)

上記pythonコードをを実行すれば、鳥だけが映っている動画を自動生成できる。

13
15
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
13
15