はじめに
前回の記事に加えて、操作の対象として選ぶ領域ROI (Region of Interest)と特定の部分のみを処理対象とする処理(マスク)を付け加えます。
PySimpleGUI + OpenCVで動画プレイヤーを作る2 ROI設定と保存機能の追加(DIVX, MJPG, GIF)
この記事でできること
ROI内の特定の部分(マスク)に処理を掛けることが出来ます。
マスクの選択は画像をHSVに変換し、それぞれスライダで数値を選択できるようにしています。マスク部分は別ウィンドウで表示して確認できるようにしています。
赤色を選択する場合は、Hueの値を0-20と110-255程度の二つの範囲を選択する必要があるため、Hue Reverseというチェックボックスを作成し、チェックボックスが選択されている場合は、2つのスライダの外側の値をマスク範囲とするようにしています。
Hue Reverse 【オフ】
Hue Reverse 【オン】
Hueの左右のスライダの外側がマスク範囲となります。マスク範囲を反転することが出来ます。
指定範囲の動画をDIVX, MJEG, GIFで保存することが出来ます。
ファイルの読込
GUIを利用して読み込むファイルを選択します。
import PySimpleGUI as sg
import cv2
import numpy as np
from PIL import Image
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)
HSVによる色検出
HSV変換 → マスク処理を関数化しています。GUIから受け取ったH, S, Vそれぞれのmin, maxの値からmaskを作成し、cv2.bitwize_and()でマスク画像を作成します。
def hsv(frame, H_max, H_min, S_max, S_min, V_max, V_min, reverse=False):
frame_hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
if reverse:
lower1 = np.array([0, int(S_min), int(V_min)])
upper1 = np.array([int(H_min), int(S_max), int(V_max)])
mask1 = cv2.inRange(frame_hsv, lower1, upper1)
lower2 = np.array([int(H_max), int(S_min), int(V_min)])
upper2 = np.array([255, int(S_max), int(V_max)])
mask2 = cv2.inRange(frame_hsv, lower2, upper2)
mask = mask1 + mask2
frame = cv2.bitwise_and(frame, frame, mask=mask)
# mask = cv2.bitwise_and(frame, mask, mask=mask)
else:
lower = np.array([int(H_min), int(S_min), int(V_min)])
upper = np.array([int(H_max), int(S_max), int(V_max)])
mask = cv2.inRange(frame_hsv, lower, upper)
frame = cv2.bitwise_and(frame, frame, mask=mask)
return frame
class Main:
def __init__(self):
self.fp = file_read()
self.cap = cv2.VideoCapture(str(self.fp))
# 動画の保存フラグ
self.rec_flg = False
# 1フレーム目の取得
# 取得可能かの確認
self.ret, self.f_frame = self.cap.read()
self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
# フレームが取得できた場合、各種パラメータを取得
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)
# ROI
self.frames_roi = np.zeros((5, self.height, self.width))
マスク画像として、ROIと同サイズのグレースケールのマスク画像を用意しておきます。
# マスク画像の定義
self.mask = np.zeros_like(self.f_frame[:, :, 0])
# オリジナルサイズの保存
self.org_width = self.width
self.org_height = self.height
# フレーム関係
self.frame_count = 0
self.s_frame = 0
self.e_frame = self.total_count
# 画像きり抜き位置
self.x1 = 0
self.y1 = 0
self.x2 = self.width
self.y2 = self.height
# 再生の一時停止フラグ
self.stop_flg = False
# マウスの動きの制御
# マウスのボタンが押されているかどうか
self.mouse_flg = False
self.event = ""
# ROIへの演算を適応するかどうか
self.roi_flg = True
cv2.namedWindow("Movie")
# マウスイベントのコールバック登録
cv2.setMouseCallback("Movie", self.onMouse)
# フレームを取得出来なかった場合に終了する
else:
sg.Popup("ファイルの読込に失敗しました。")
return
# マウスイベント
def onMouse(self, event, x, y, flags, param):
# 左クリック
if event == cv2.EVENT_LBUTTONDOWN:
self.x1 = self.x2 = x
self.y1 = self.y2 = y
# 長方形の描写開始。マウスを一回押すと長方形描写を開始する。
self.mouse_flg = True
# ROI部分の演算を一時停止
self.roi_flg = False
return
elif event == cv2.EVENT_LBUTTONUP:
# 長方形の更新を停止
self.mouse_flg = False
# ROIへの演算を開始する
self.roi_flg = True
# ROIの選択で0の場合はリセットし、ROIの演算をストップ
if (
x == self.x1
or y == self.y1
or x <= 0
or y <= 0
):
self.x1 = 0
self.y1 = 0
self.x2 = self.width
self.y2 = self.height
return
# x1 < x2になるようにする
elif self.x1 < x:
self.x2 = x
else:
self.x2 = self.x1
self.x1 = x
if self.y1 < y:
self.y2 = y
else:
self.y2 = self.y1
self.y1 = y
# ROI範囲を表示
print(
"ROI x:{0}:{1} y:{2}:{3}".format(
str(self.x1),
str(self.x2),
str(self.y1),
str(self.y2)
)
)
return
# マウスが押下されている場合、長方形を表示し続ける
if self.mouse_flg:
self.x2 = x
self.y2 = y
return
def run(self):
# GUI #######################################################
# GUIのレイアウト
# タブ1
T1 = sg.Tab("Basic", [
[
sg.Text("Resize ", size=(13, 1)),
sg.Slider(
(0.1, 4),
1,
0.01,
orientation='h',
size=(40, 15),
key='-RESIZE SLIDER-',
enable_events=True
)
],
[
sg.Checkbox(
'blur',
size=(10, 1),
key='-BLUR-',
enable_events=True
),
sg.Slider(
(1, 10),
1,
1,
orientation='h',
size=(40, 15),
key='-BLUR SLIDER-',
enable_events=True
)
],
])
T2 = sg.Tab("processing", [
[
sg.Checkbox(
'gray',
size=(10, 1),
key='-GRAY-',
enable_events=True
)
],
])
マスク設定用GUI
マスク処理用のタブを用意します。
ラジオボタンでMaskingを選択した場合マスク処理を行います。
T3 = sg.Tab("mask", [
[
sg.Radio(
'Rectangle',
"RADIO2",
key='-RECTANGLE_MASK-',
default=True,
size=(8, 1)
),
sg.Radio(
'Masking',
"RADIO2",
key='-MASKING-',
size=(8, 1)
)
],
[
sg.Checkbox(
"Blue",
size=(10, 1),
default=True,
key='-BLUE_MASK-',
enable_events=True
),
sg.Checkbox(
"Green",
size=(10, 1),
default=True,
key='-GREEN_MASK-',
enable_events=True
),
sg.Checkbox(
"Red",
size=(10, 1),
default=True,
key='-RED_MASK-',
enable_events=True
)
],
[
sg.Text(
'hsv',
size=(10, 1),
key='-HSV_MASK-',
enable_events=True
),
sg.Button('Blue', size=(10, 1)),
sg.Button('Green', size=(10, 1)),
sg.Button('Red', size=(10, 1))
],
[
sg.Checkbox(
'Hue Reverse',
size=(10, 1),
key='-Hue Reverse_MASK-',
enable_events=True
)
],
[
sg.Text('Hue', size=(10, 1), key='-Hue_MASK-'),
sg.Slider(
(0, 255),
0,
1,
orientation='h',
size=(19.4, 15),
key='-H_MIN SLIDER_MASK-',
enable_events=True
),
sg.Slider(
(1, 255),
125,
1,
orientation='h',
size=(19.4, 15),
key='-H_MAX SLIDER_MASK-',
enable_events=True
)
],
[
sg.Text('Saturation', size=(10, 1), key='-Saturation_MASK-'),
sg.Slider(
(0, 255),
50,
1,
orientation='h',
size=(19.4, 15),
key='-S_MIN SLIDER_MASK-',
enable_events=True
),
sg.Slider(
(1, 255),
255,
1,
orientation='h',
size=(19.4, 15),
key='-S_MAX SLIDER_MASK-',
enable_events=True
)
],
[
sg.Text('Value', size=(10, 1), key='-Value_MASK-'),
sg.Slider(
(0, 255),
50,
1,
orientation='h',
size=(19.4, 15),
key='-V_MIN SLIDER_MASK-',
enable_events=True
),
sg.Slider(
(1, 255),
255,
1,
orientation='h',
size=(19.4, 15),
key='-V_MAX SLIDER_MASK-',
enable_events=True
)
]
])
T4 = sg.Tab("Save", [
[
sg.Button('Write', size=(10, 1)),
sg.Radio(
'DIVX',
"RADIO1",
key='-DIVX-',
default=True,
size=(8, 1)
),
sg.Radio('MJPG', "RADIO1", key='-MJPG-', size=(8, 1)),
sg.Radio('GIF', "RADIO1", key='-GIF-', size=(8, 1))
],
[
sg.Text('Caption', size=(10, 1)),
sg.InputText(
size=(32, 50),
key='-CAPTION-',
enable_events=True
)
]
])
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.TabGroup(
[[T1, T2, T3, T4]],
tab_background_color="#ccc",
selected_title_color="#fff",
selected_background_color="#444",
tab_location="topleft"
)
],
[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)))
# メインループ #########################################################
try:
while True:
# GUIイベントの読込
self.event, values = window.read(
timeout=values["-SPEED SLIDER-"]
)
# イベントをウィンドウに表示
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)
self.video_stabilization_flg = False
self.stab_prepare_flg = False
# Progress sliderへの変更を反映させるためにcontinue
continue
# 動画の書き出し
if self.event == 'Write':
self.rec_flg = True
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["-GIF-"]:
images = []
else:
# 動画として保存
# コーデックの選択
# DIVXは圧縮率高い
# MJEGはImageJで解析可能
if values["-DIVX-"]:
codec = "DIVX"
elif values["-MJPG-"]:
codec = "MJPG"
fourcc = cv2.VideoWriter_fourcc(*codec)
out = cv2.VideoWriter(
str((
self.fp.parent / (self.fp.stem + '_' + codec + '.avi')
)),
fourcc,
self.fps,
(int(self.x2 - self.x1), int(self.y2 - self.y1))
)
continue
if self.event == 'Stabilization':
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.s_frame)
self.frame_count = self.s_frame
window['-PROGRESS SLIDER-'].update(self.frame_count)
self.play_count = int(self.e_frame - self.s_frame)
self.video_stabilization_flg = True
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__"
and self.mouse_flg is False
)
):
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全体に対する処理をはじめに実施 ##############################
# リサイズ
self.width = int(self.org_width * values['-RESIZE SLIDER-'])
self.height = int(self.org_height * values['-RESIZE SLIDER-'])
self.frame = cv2.resize(self.frame, (self.width, self.height))
if self.event == '-RESIZE SLIDER-':
self.x1 = self.y1 = 0
self.x2 = self.width
self.y2 = self.height
# ROIに対して処理を実施 ##########################################
if self.roi_flg:
self.frame_roi = self.frame[
self.y1:self.y2, self.x1:self.x2, :
]
RGBマスク処理
マスク処理を記述します。
HSVではなく、RGBでの色指定を入れています。RGBで色を指定した後、HSVのV(明度)でマスク範囲を指定することが出来ます。この場合は、Hueは0-255の全範囲を指定して、Vのみ変更したほうが良いです。
# MASK画像への処理 ##################################################################
if values['-MASKING-']:
# RGB分離
self.mask = np.copy(self.frame_roi)
if not values['-BLUE_MASK-']:
self.mask[:, :, 0] = 0
if not values['-GREEN_MASK-']:
self.mask[:, :, 1] = 0
if not values['-RED_MASK-']:
self.mask[:, :, 2] = 0
HSVマスク処理
HSVでのマスク処理を記述します。
Red, Green, Blueボタンで各色をある程度適当な範囲で選択できるようにしています。元動画とマスク動画を見ながら、閾値を調整して対象物をマスクすることが出来ます。
if self.event == 'Blue':
window['-H_MIN SLIDER_MASK-'].update(70)
window['-H_MAX SLIDER_MASK-'].update(110)
window['-S_MIN SLIDER_MASK-'].update(70)
window['-S_MAX SLIDER_MASK-'].update(255)
window['-V_MIN SLIDER_MASK-'].update(0)
window['-V_MAX SLIDER_MASK-'].update(255)
window['-Hue Reverse_MASK-'].update(False)
if self.event == 'Green':
window['-H_MIN SLIDER_MASK-'].update(20)
window['-H_MAX SLIDER_MASK-'].update(70)
window['-S_MIN SLIDER_MASK-'].update(70)
window['-S_MAX SLIDER_MASK-'].update(255)
window['-V_MIN SLIDER_MASK-'].update(0)
window['-V_MAX SLIDER_MASK-'].update(255)
window['-Hue Reverse_MASK-'].update(False)
if self.event == 'Red':
window['-H_MIN SLIDER_MASK-'].update(20)
window['-H_MAX SLIDER_MASK-'].update(110)
window['-S_MIN SLIDER_MASK-'].update(70)
window['-S_MAX SLIDER_MASK-'].update(255)
window['-V_MIN SLIDER_MASK-'].update(0)
window['-V_MAX SLIDER_MASK-'].update(255)
window['-Hue Reverse_MASK-'].update(True)
self.mask = hsv(
self.mask,
values['-H_MAX SLIDER_MASK-'],
values['-H_MIN SLIDER_MASK-'],
values['-S_MAX SLIDER_MASK-'],
values['-S_MIN SLIDER_MASK-'],
values['-V_MAX SLIDER_MASK-'],
values['-V_MIN SLIDER_MASK-'],
values['-Hue Reverse_MASK-']
)
# グレイスケール
self.mask = cv2.cvtColor(
self.mask,
cv2.COLOR_BGR2GRAY
)
# ぼかし
if values['-BLUR-']:
self.frame_roi = cv2.GaussianBlur(
self.frame_roi, (21, 21), values['-BLUR SLIDER-']
)
if values['-GRAY-']:
self.frame_roi = cv2.cvtColor(
self.frame_roi,
cv2.COLOR_BGR2GRAY
)
self.frame_roi = cv2.cvtColor(
self.frame_roi,
cv2.COLOR_GRAY2BGR
)
ROI内のマスク範囲のみ処理を適用します。
cv2.bitwise_notを入れ子で使用して実施していますが、より適切な実現方法あるかもしれません。
if values['-MASKING-']:
# frame_roi内にマスクを適用
# マスク処理部のみをframe_roiに変える
self.frame_roi = cv2.bitwise_not(
cv2.bitwise_not(self.frame_roi),
self.frame[self.y1:self.y2, self.x1:self.x2, :],
mask=self.mask
)
# 処理したROIをframeに戻す
self.frame[self.y1:self.y2, self.x1:self.x2, :] = self.frame_roi
# 動画の保存
if self.rec_flg:
# 手振れ補正後再度roiを切り抜き
self.frame_roi = self.frame[
self.y1:self.y2, self.x1:self.x2, :
]
if values["-GIF-"]:
images.append(
Image.fromarray(
cv2.cvtColor(
self.frame_roi, cv2.COLOR_BGR2RGB
)
)
)
else:
out.write(self.frame_roi)
# 保存中の表示
cv2.putText(
self.frame,
str("Now Recording"),
(20, 60),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
(10, 10, 255),
1,
cv2.LINE_AA
)
# e_frameになったら終了
if self.frame_count >= self.e_frame - values["-SKIP SLIDER-"] - 1:
if values["-GIF-"]:
images[0].save(
str((self.fp.parent / (self.fp.stem + '.gif'))),
save_all=True,
append_images=images[1:],
optimize=False,
duration=1000 // self.fps,
loop=0
)
else:
out.release()
self.rec_flg = False
# フレーム数と経過秒数の表示
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
)
# ROIへの演算を実施している場合 or マウス左ボタンを押している最中
# 長方形を描写する
if self.roi_flg or self.mouse_flg:
cv2.rectangle(
self.frame,
(self.x1, self.y1),
(self.x2 - 1, self.y2 - 1),
(128, 128, 128)
)
Maskingを選択している場合のみ、マスク画像を表示するようにしています。
# 画像を表示
cv2.imshow("Movie", self.frame)
if values['-MASKING-']:
cv2.imshow("Mask", cv2.cvtColor(self.mask, cv2.COLOR_GRAY2BGR))
cv2.setWindowProperty("Mask", cv2.WND_PROP_VISIBLE, 0)
elif not values['-MASKING-'] and cv2.getWindowProperty("Mask", cv2.WND_PROP_VISIBLE):
cv2.destroyWindow("Mask")
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)
# その他の処理 ###############################################
# ログウィンドウのクリア
if self.event == 'Clear':
window['-OUTPUT-'].update('')
finally:
cv2.destroyWindow("Movie")
cv2.destroyWindow("Mask")
self.cap.release()
window.close()
if __name__ == '__main__':
Main().run()
参考リンク
pythonで赤い物体を認識しよう
Wikipedia: HSV色空間
Python, OpenCV, NumPyで画像のアルファブレンドとマスク処理