[PYTHON] Créez un lecteur vidéo avec PySimpleGUI + OpenCV 2 Ajoutez le paramètre ROI et la fonction d'enregistrement (DIVX, MJPG, GIF)

introduction

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

Que pouvez-vous faire avec cet article

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. 2-1.jpg

Vous pouvez enregistrer la partie sélectionnée sous DIVX, MJPEG ou GIF. test.gif

Chargement de la vidéo

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

Chargement de la vidéo

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

Génération GUI

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

Enregistrer la vidéo

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

Mise en œuvre du traitement d'image

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()

Lien de référence

Wikipedia HSV Color Space [Python] Créer un GIF à partir d'une vidéo

Recommended Posts

Créez un lecteur vidéo avec PySimpleGUI + OpenCV 2 Ajoutez le paramètre ROI et la fonction d'enregistrement (DIVX, MJPG, GIF)
Créer un lecteur vidéo avec PySimpleGUI + OpenCV 3 Ajout de la fonction de masque
Créer un lecteur vidéo avec PySimpleGUI + OpenCV
Comment boucler et lire une vidéo gif avec openCV
Créez une caméra de surveillance WEB avec Raspberry Pi et OpenCV
Créez un outil d'analyse vidéo simple avec python wxpython + openCV
Créons une IA à trois voies avec Pylearn2 --Save and load model -
Créez une illusion rayée avec correction gamma pour Python3 et openCV3
Créer un décorateur de fonction Python avec Class