En plus de l'article précédent, nous ajouterons la Région d'Intérêt (ROI) à sélectionner comme cible de l'opération. De plus, nous ajouterons une fonction pour enregistrer l'image avec le cadre et le ROI découpés. Divers codecs peuvent être sélectionnés, mais DIVX avec un taux de compression élevé, MJPG pouvant être analysé par ImageJ et GIF pouvant être collés sur Qiita peuvent être enregistrés.
Article précédent: Créer un lecteur vidéo avec PySimpleGUI + OpenCV
Vous pouvez modifier la taille de la vidéo avec le curseur. Vous pouvez ajouter des niveaux de gris et du flou à la partie sélectionnée sous forme de rectangle avec la souris.
Vous pouvez enregistrer la partie sélectionnée sous DIVX, MJPEG ou GIF.
Comme précédemment, utilisez PySimpleGUI pour générer une interface graphique de lecture de fichier.
class Main:
def __init__(self):
self.fp = file_read()
self.cap = cv2.VideoCapture(str(self.fp))
#Obtenez la première image
#Vérifiez s'il peut être obtenu
self.ret, self.f_frame = self.cap.read()
if self.ret:
self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
#Acquisition d'informations vidéo
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)
#Lié au cadre
self.frame_count = 0
self.s_frame = 0
self.e_frame = self.total_count
#Lire l'indicateur de pause
self.stop_flg = False
cv2.namedWindow("Movie")
else:
sg.Popup("Impossible de lire le fichier.")
return
class Main:
def __init__(self):
self.fp = file_read()
self.cap = cv2.VideoCapture(str(self.fp))
#Obtenez la première image
#Vérifiez s'il peut être obtenu
self.ret, self.f_frame = self.cap.read()
self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
#Si vous pouvez obtenir le cadre, obtenez divers paramètres
if self.ret:
self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
#Acquisition d'informations vidéo
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)
La taille initiale du retour sur investissement doit être identique à la taille de la vidéo.
# ROI
self.frames_roi = np.zeros((5, self.height, self.width))
#Enregistrer la taille d'origine
self.org_width = self.width
self.org_height = self.height
#Lié au cadre
self.frame_count = 0
self.s_frame = 0
self.e_frame = self.total_count
#Position de découpe de l'image
self.x1 = 0
self.y1 = 0
self.x2 = self.width
self.y2 = self.height
#Lire l'indicateur de pause
self.stop_flg = False
#Drapeau d'enregistrement vidéo
self.rec_flg = False
#Contrôle du mouvement de la souris
#Si le bouton de la souris est enfoncé
self.mouse_flg = False
self.event = ""
#S'il faut appliquer le calcul au retour sur investissement
self.roi_flg = True
cv2.namedWindow("Movie")
Enregistrez la fonction de rappel afin de pouvoir sélectionner un rectangle en cliquant avec le bouton gauche de la souris sur BAS → HAUT dans la fenêtre vidéo.
#Enregistrement de rappel pour les événements de souris
cv2.setMouseCallback("Movie", self.onMouse)
#Quitter si le cadre n'a pas pu être obtenu
else:
sg.Popup("Impossible de lire le fichier.")
return
#Événement souris
def onMouse(self, event, x, y, flags, param):
#Click gauche
if event == cv2.EVENT_LBUTTONDOWN:
self.x1 = self.x2 = x
self.y1 = self.y2 = y
#Commencez à dessiner un rectangle. Appuyez une fois sur la souris pour commencer à dessiner un rectangle.
self.mouse_flg = True
#Suspendre le calcul de la partie ROI
self.roi_flg = False
return
elif event == cv2.EVENT_LBUTTONUP:
#Arrêter de mettre à jour les rectangles
self.mouse_flg = False
#Démarrez le calcul du ROI
self.roi_flg = True
#Si la sélection de ROI est 0, réinitialisez-la et arrêtez le calcul de 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 <Faites-le 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
#Afficher la plage de retour sur investissement
print(
"ROI x:{0}:{1} y:{2}:{3}".format(
str(self.x1),
str(self.x2),
str(self.y1),
str(self.y2)
)
)
return
#Continue d'afficher le rectangle lorsque la souris est enfoncée
if self.mouse_flg:
self.x2 = x
self.y2 = y
return
L'échelle de gris et le flou sont ajoutés en tant que traitement à effectuer sur le retour sur investissement. Nous avons également ajouté un curseur pour modifier la taille de l'image.
def run(self):
# GUI #######################################################
#Disposition GUI
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
)
],
])
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)
)
],
])
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')]
]
#Générer la fenêtre
window = sg.Window('OpenCV Integration', layout, location=(0, 0))
#Affichage des informations vidéo
self.event, values = window.read(timeout=0)
print("Le fichier a été lu.")
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)))
#Boucle principale#########################################################
try:
while True:
#Chargement des événements GUI
self.event, values = window.read(
timeout=values["-SPEED SLIDER-"]
)
#Afficher l'événement dans la fenêtre
if self.event != "__TIMEOUT__":
print(self.event)
#Quitter lorsque le bouton Quitter est enfoncé ou lorsque le bouton de fermeture de fenêtre est enfoncé
if self.event in ('Exit', sg.WIN_CLOSED, None):
break
#Recharger la vidéo
#Fonctionne lorsque l'image de départ est définie
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
#Continuer à refléter les modifications apportées au curseur de progression
continue
Si vous souhaitez l'enregistrer en tant que fichier vidéo, utilisez cv2.VideoWriter_fourcc. Ici, il est configuré pour pouvoir être sauvegardé au format MJPEG qui peut être lu par DIVX, qui a un taux de compression élevé, et ImageJ, un logiciel gratuit d'analyse vidéo. Lors de l'enregistrement en tant que fichier GIF, Pillow est utilisé.
#Exporter la vidéo
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:
#Enregistrer en vidéo
#Sélection de code
#DIVX a un taux de compression élevé
#MJEG peut être analysé avec 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
#Fonctionnement du cadre################################################
#La priorité est donnée si le curseur est modifié directement
if self.event == '-PROGRESS SLIDER-':
#Définissez le nombre d'images sur la barre de progression
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-'])
#Si vous modifiez l'image de départ
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
#Si vous modifiez le cadre de fin
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
#Paramètres du cadre de fin
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)
#Si le compteur dépasse la trame de fin, redémarrez à partir de la trame de début
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
#Suspendre le chargement de la vidéo avec le bouton d'arrêt
if self.event == 'Play / Stop':
self.stop_flg = not self.stop_flg
#Sauf si l'indicateur d'arrêt est défini et qu'un événement se produit, comptez
#Arrêtez l'opération
#Si le bouton d'arrêt est enfoncé, le traitement vidéo sera arrêté, mais quelque chose
#Si un événement se produit, ne mettez à jour que l'image
#La même chose s'applique lors de l'utilisation de la souris
if(
(
self.stop_flg
and self.event == "__TIMEOUT__"
and self.mouse_flg is False
)
):
window['-PROGRESS SLIDER-'].update(self.frame_count)
continue
#Ignorer les cadres
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)
#Cadre de charge##############################################
self.ret, self.frame = self.cap.read()
self.valid_frame = int(self.frame_count - self.s_frame)
#Quand la dernière image est sur soi.s_Reprendre du cadre
if not self.ret:
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.s_frame)
self.frame_count = self.s_frame
continue
Après cela, nous décrirons le traitement tel que le redimensionnement, la mise à l'échelle des gris et le flou. Après le redimensionnement, qui est le processus effectué sur l'ensemble du cadre, les niveaux de gris et le flou ne sont effectués que sur le retour sur investissement.
#Décrivez le traitement de la trame après cela##################################
#Effectuez d'abord le traitement pour toute la trame##############################
#redimensionner
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
#Effectuer le traitement pour le retour sur investissement##########################################
if self.roi_flg:
self.frame_roi = self.frame[
self.y1:self.y2, self.x1:self.x2, :
]
#Brouiller
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
)
Le retour sur investissement traité est renvoyé au cadre et affiché.
#Renvoyer le retour sur investissement traité au cadre
self.frame[self.y1:self.y2, self.x1:self.x2, :] = self.frame_roi
#Enregistrer la vidéo
if self.rec_flg:
#Réduisez à nouveau le retour sur investissement après la correction du tremblement de la caméra
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)
#Affichage pendant la sauvegarde
cv2.putText(
self.frame,
str("Now Recording"),
(20, 60),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
(10, 10, 255),
1,
cv2.LINE_AA
)
# e_Terminer quand il devient un cadre
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
#Affichage du nombre d'images et des secondes écoulées
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
)
#Lors du calcul du ROI ou en appuyant sur le bouton gauche de la souris
#Dessinez un rectangle
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)
)
#Afficher l'image
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)
#Autre traitement###############################################
#Effacer la fenêtre du journal
if self.event == 'Clear':
window['-OUTPUT-'].update('')
finally:
cv2.destroyWindow("Movie")
self.cap.release()
window.close()
if __name__ == '__main__':
Main().run()
Wikipedia HSV Color Space [Python] Créer un GIF à partir d'une vidéo
Recommended Posts