はじめに
OpenCV(Open Source Computer Vision Library)はBSDライセンスのオープンソース映像/画像処理ライブラリ集です。画像・動画を対象とした、各種フィルタ処理、テンプレートマッチング、物体認識、機械学習などの各種ライブラリが用意されています。
動画の解析時に、画像を確認しながらROI、マスクなどの設定ができるように、PySimpleGUIを使用して動画プレイヤーを作成していきます。
この記事について
1回目として、GUIを作成し、動画の再生・停止、開始・終了フレームの設定ができるようにします。動画の再生スピード、フレームスキップ数が設定できるようにしています。
ファイルの読み込み
PySimpleGUIを使用してファイルの読み込みGUIを作成します。
Submitをクリックすると、ファイル選択ダイアログが表示されます。
import numpy as np
import PySimpleGUI as sg
import cv2
from pathlib import Path
def file_read():
'''
ファイルを選択して読み込む
'''
fp = ""
# GUIのレイアウト
layout = [
[
sg.FileBrowse(key="file"),
sg.Text("ファイル"),
sg.InputText()
],
[sg.Submit(key="submit"), sg.Cancel("Exit")]
]
# WINDOWの生成
window = sg.Window("ファイル選択", layout)
# イベントループ
while True:
event, values = window.read(timeout=100)
if event == 'Exit' or event == sg.WIN_CLOSED:
break
elif event == 'submit':
if values[0] == "":
sg.popup("ファイルが入力されていません。")
event = ""
else:
fp = values[0]
break
window.close()
return Path(fp)
動画の読込
動画を読み込み、各種パラメータを設定します。読み込めなかった場合は、ポップアップを表示して終了します。
class Main:
def __init__(self):
self.fp = file_read()
self.cap = cv2.VideoCapture(str(self.fp))
# 1フレーム目の取得
# 取得可能かの確認
self.ret, self.f_frame = self.cap.read()
if self.ret:
self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
# 動画情報の取得
self.fps = self.cap.get(cv2.CAP_PROP_FPS)
self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
self.total_count = self.cap.get(cv2.CAP_PROP_FRAME_COUNT)
# フレーム関係
self.frame_count = 0
self.s_frame = 0
self.e_frame = self.total_count
# 再生の一時停止フラグ
self.stop_flg = False
cv2.namedWindow("Movie")
else:
sg.Popup("ファイルの読込に失敗しました。")
return
GUIの設定と表示
動画の操作用GUIのウィンドウを設定します。
PySimpleGUIだと、リストに書き加えるだけで各種オブジェクトを追加できるため、簡単なGUIを作る際に日以上に重宝します。
def run(self):
# GUI #######################################################
# GUIのレイアウト
layout = [
[
sg.Text("Start", size=(8, 1)),
sg.Slider(
(0, self.total_count - 1),
0,
1,
orientation='h',
size=(45, 15),
key='-START FRAME SLIDER-',
enable_events=True
)
],
[
sg.Text("End ", size=(8, 1)),
sg.Slider(
(0, self.total_count - 1), self.total_count - 1,
1,
orientation='h',
size=(45, 15),
key='-END FRAME SLIDER-',
enable_events=True
)
],
[sg.Slider(
(0, self.total_count - 1),
0,
1,
orientation='h',
size=(50, 15),
key='-PROGRESS SLIDER-',
enable_events=True
)],
[
sg.Button('<<<', size=(5, 1)),
sg.Button('<<', size=(5, 1)),
sg.Button('<', size=(5, 1)),
sg.Button('Play / Stop', size=(9, 1)),
sg.Button('Reset', size=(7, 1)),
sg.Button('>', size=(5, 1)),
sg.Button('>>', size=(5, 1)),
sg.Button('>>>', size=(5, 1))
],
[
sg.Text("Speed", size=(6, 1)),
sg.Slider(
(0, 240),
10,
10,
orientation='h',
size=(19.4, 15),
key='-SPEED SLIDER-',
enable_events=True
),
sg.Text("Skip", size=(6, 1)),
sg.Slider(
(0, 300),
0,
1,
orientation='h',
size=(19.4, 15),
key='-SKIP SLIDER-',
enable_events=True
)
],
[sg.HorizontalSeparator()],
[sg.Output(size=(65, 5), key='-OUTPUT-')],
[sg.Button('Clear')]
]
# Windowを生成
window = sg.Window('OpenCV Integration', layout, location=(0, 0))
# 動画情報の表示
self.event, values = window.read(timeout=0)
print("ファイルが読み込まれました。")
print("File Path: " + str(self.fp))
print("fps: " + str(int(self.fps)))
print("width: " + str(self.width))
print("height: " + str(self.height))
print("frame count: " + str(int(self.total_count)))
動画の再生
メインループを回して動画を再生していきます。
再生時は、
window.read (GUIイベントの読込)
↓
cap.read (動画フレームの読込)
↓
各種画像処理
↓
cv2.imshow (動画の表示)
↓
frame_count += 1 (フレームカウントを増加させる)
とループを回していきます。
Play/Stopボタンを押した場合は、
window.read (GUIイベントの読込)
↓
cap.read (動画フレームの読込)
↓
各種画像処理
↓
cv2.imshow (動画の表示)
↓
までは同じですが、
cap.set(cv2.CAP_PROP_POS_FRAMES, self.frame_count)
とフレームカウントを戻すことで、frame_countを増加させずに、同じフレームに対して各種画像処理を行うことが出来ます。
# メインループ #########################################################
try:
while True:
self.event, values = window.read(
timeout=values["-SPEED SLIDER-"]
)
if self.event == "Clear":
pass
if self.event != "__TIMEOUT__":
print(self.event)
# Exitボタンが押されたら、またはウィンドウの閉じるボタンが押されたら終了
if self.event in ('Exit', sg.WIN_CLOSED, None):
break
# 動画の再読み込み
# スタートフレームを設定していると動く
if self.event == 'Reset':
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.s_frame)
self.frame_count = self.s_frame
window['-PROGRESS SLIDER-'].update(self.frame_count)
# Progress sliderへの変更を反映させるためにcontinue
continue
# フレーム操作 ################################################
# スライダを直接変更した場合は優先する
if self.event == '-PROGRESS SLIDER-':
# フレームカウントをプログレスバーに合わせる
self.frame_count = int(values['-PROGRESS SLIDER-'])
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.frame_count)
if values['-PROGRESS SLIDER-'] > values['-END FRAME SLIDER-']:
window['-END FRAME SLIDER-'].update(
values['-PROGRESS SLIDER-'])
# スタートフレームを変更した場合
if self.event == '-START FRAME SLIDER-':
self.s_frame = int(values['-START FRAME SLIDER-'])
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.s_frame)
self.frame_count = self.s_frame
window['-PROGRESS SLIDER-'].update(self.frame_count)
if values['-START FRAME SLIDER-'] > values['-END FRAME SLIDER-']:
window['-END FRAME SLIDER-'].update(
values['-START FRAME SLIDER-'])
self.e_frame = self.s_frame
# エンドフレームを変更した場合
if self.event == '-END FRAME SLIDER-':
if values['-END FRAME SLIDER-'] < values['-START FRAME SLIDER-']:
window['-START FRAME SLIDER-'].update(
values['-END FRAME SLIDER-'])
self.s_frame = self.e_frame
# エンドフレームの設定
self.e_frame = int(values['-END FRAME SLIDER-'])
if self.event == '<<<':
self.frame_count = np.maximum(0, self.frame_count - 150)
window['-PROGRESS SLIDER-'].update(self.frame_count)
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.frame_count)
if self.event == '<<':
self.frame_count = np.maximum(0, self.frame_count - 30)
window['-PROGRESS SLIDER-'].update(self.frame_count)
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.frame_count)
if self.event == '<':
self.frame_count = np.maximum(0, self.frame_count - 1)
window['-PROGRESS SLIDER-'].update(self.frame_count)
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.frame_count)
if self.event == '>':
self.frame_count = self.frame_count + 1
window['-PROGRESS SLIDER-'].update(self.frame_count)
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.frame_count)
if self.event == '>>':
self.frame_count = self.frame_count + 30
window['-PROGRESS SLIDER-'].update(self.frame_count)
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.frame_count)
if self.event == '>>>':
self.frame_count = self.frame_count + 150
window['-PROGRESS SLIDER-'].update(self.frame_count)
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.frame_count)
# カウンタがエンドフレーム以上になった場合、スタートフレームから再開
if self.frame_count >= self.e_frame:
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.s_frame)
self.frame_count = self.s_frame
window['-PROGRESS SLIDER-'].update(self.frame_count)
continue
# ストップボタンで動画の読込を一時停止
if self.event == 'Play / Stop':
self.stop_flg = not self.stop_flg
# ストップフラグが立っており、eventが発生した場合以外はcountinueで
# 操作を停止しておく
# ストップボタンが押された場合は動画の処理を止めるが、何らかの
# eventが発生した場合は画像の更新のみ行う
# mouse操作を行っている場合も同様
if(
(
self.stop_flg
and self.event == "__TIMEOUT__"
)
):
window['-PROGRESS SLIDER-'].update(self.frame_count)
continue
# スキップフレーム分とばす
if not self.stop_flg and values['-SKIP SLIDER-'] != 0:
self.frame_count += values["-SKIP SLIDER-"]
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.frame_count)
# フレームの読込 ##############################################
self.ret, self.frame = self.cap.read()
self.valid_frame = int(self.frame_count - self.s_frame)
# 最後のフレームが終わった場合self.s_frameから再開
if not self.ret:
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.s_frame)
self.frame_count = self.s_frame
continue
# 以降にフレームに対する処理を記述 ##################################
# frame全体に対する処理をはじめに実施 ##############################
# フレーム数と経過秒数の表示
cv2.putText(
self.frame, str("framecount: {0:.0f}".format(self.frame_count)), (
15, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (240, 230, 0), 1, cv2.LINE_AA
)
cv2.putText(
self.frame, str("time: {0:.1f} sec".format(
self.frame_count / self.fps)), (15, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (240, 230, 0), 1, cv2.LINE_AA
)
# 画像を表示
cv2.imshow("Movie", self.frame)
if self.stop_flg:
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.frame_count)
else:
self.frame_count += 1
window['-PROGRESS SLIDER-'].update(self.frame_count + 1)
finally:
cv2.destroyWindow("Movie")
self.cap.release()
window.close()
if __name__ == '__main__':
Main().run()
#環境
Windows10(64bit)
python 3.5.2
OpenCV 4.1.0
#参考リンク
####PySimpleGUI
PySimpleGUI: popup_get_text
PySimpleGUIの基本的な使用方法
PySimpleGUIで画像処理ビューアーを作る
####OpenCV
Pythonでテニスの動画解析ツールを自作してみた