[PYTHON] Programmation GUI avec kivy ~ Partie 3 Vidéo et barre de recherche ~

introduction

Dans l'article précédent (https://qiita.com/kurodae/items/ba0d1c7115d1dc9ad7cf), j'ai beaucoup écrit sur la barre de progression. Dans cet article, je vais essayer de créer moi-même quelque chose comme un lecteur vidéo. (Pas de son ...) A l'origine, Video Player fourni par kivy pour visualiser les résultats de l'analyse vidéo (détection d'objets, etc.) /api-kivy.uix.videoplayer.html), mais j'ai essayé de créer un lecteur personnalisable. Comme le titre l'indique, j'ai eu du mal à implémenter la barre de recherche pour le lecteur vidéo, donc j'écris cet article pour le partage d'informations.

Environnement

Comme mentionné au début, le but est de visualiser les résultats de l'analyse vidéo, donc installez la bibliothèque opencv pour l'analyse vidéo (image).

Mon environnement d'exploitation est le suivant.

OSX 10.14.6 Python 3.7.2

Si vous utilisez pip, vous pouvez l'installer avec uniquement la commande suivante.

pip install python-opencv

Qu'est-ce qu'une barre de recherche

Extrait de wiki Je vais.

La barre de recherche est l'une des fonctions fournies dans le logiciel de lecture de musique / vidéo, etc., et est une fonction qui affiche l'emplacement de lecture des données. Vous pouvez saisir visuellement à quelle distance vous lisez de la musique ou un film du début à la fin par la position du "curseur", vous pouvez déplacer le curseur directement avec la souris et vous pouvez commencer à jouer à partir de n'importe quel endroit Il existe des avantages tels que.

La barre de recherche est une sorte de curseur. Alors, qu'est-ce qu'un curseur? [wiki](https://ja.wikipedia.org/wiki/%E3%82%A6%E3%82%A3%E3%82%B8%E3%82%A7%E3%83%83%E3%83 Il est cité à partir de% 88_ (GUI) #% E9% 81% B8% E6% 8A% 9E).

Slider - Similaire à une barre de défilement, mais un widget utilisé pour définir une valeur, pas pour le défilement.

kivy a un widget Slider, vous pouvez donc en faire une barre de recherche.

Comment utiliser kivy.uix.slider

Je me sens comme cela. シークバー.gif

La source est la suivante.


from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock

Builder.load_string('''
<MySlider>
    orientation: 'vertical'
    Label : 
        text: "{}".format(slider.value)
        font_size: slider.value
        
    Slider: 
        id: slider
        step: 1
        min: 200
        max: 500
        
    Button: 
        text: "press"
        on_press: root.move_slider_start()
        
''')

class MySlider(BoxLayout):
    def __init__(self, **kwargs):
        super(MySlider, self).__init__(**kwargs)

    def move_slider_start(self):
        Clock.schedule_interval(self.move_slider, 1 / 60)

    def move_slider(self, dt):
        slider = self.ids['slider']
        if slider.max > slider.value:
            slider.value += 1
        else:
            return False
        
class sliderTest(App):

    def build(self):
        return MySlider()

sliderTest().run()

La source est que la valeur du curseur est liée au texte et à la police de l'étiquette en haut de l'écran, et l'étiquette change lorsque le curseur est déplacé. J'ai également ajouté un processus pour mettre à jour en continu la valeur du curseur avec l'horloge lorsque vous appuyez sur le bouton ci-dessous (je ferai quelque chose de similaire avec le lecteur vidéo plus tard).

Dans le langage kv, le texte de l'étiquette est mis à jour à l'aide de l'ID du curseur.

    Label : 
        text: "{}".format(slider.value)
        font_size: slider.value

    Slider: 
        id: slider #id. Il peut être appelé en langage kv ou en Python.
        step: 1 #La valeur minimale lors du déplacement du curseur. Si vous ne le définissez pas, vous obtiendrez une valeur flottante foirée
        min: 200 #Valeur minimale du curseur
        max: 500 #Valeur maximale du curseur

De plus, dans le processus de déplacement automatique du curseur, sélectionnez le widget dans "ids" où l'id attribué au widget est stocké sous forme de tableau associatif et modifiez les paramètres comme indiqué ci-dessous. Comme dans l'article précédent, il ne peut pas être utilisé avec l'instruction for, j'ai donc modifié la valeur du curseur avec Clock et mis à jour le dessin d'écran.


    def move_slider(self, dt):
        slider = self.ids['slider'] #ici!
        if slider.max > slider.value:
            slider.value += 1
        else:
            return False

Ce que vous essayez de faire

Ce que j'essaye de faire cette fois, c'est une image comme celle ci-dessous.

動画プレイヤー.png

C'est comme un lecteur vidéo avec seulement les fonctionnalités minimales. Tout ce que vous avez à faire est de charger la vidéo, de la lire, de spécifier un emplacement de lecture arbitraire et de vérifier le nombre actuel d'images.

La vidéo est comme la manipulation avec opencv.

La source

VideoApp.py


from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics.texture import Texture
from kivy.properties import ObjectProperty
from kivy.clock import Clock

from kivy.uix.floatlayout import FloatLayout
from kivy.uix.popup import Popup

import cv2

Builder.load_file('VideoApp.kv')

#Pop-up de sélection de vidéo
class LoadDialog(FloatLayout):
    load = ObjectProperty(None)
    cancel = ObjectProperty(None)

class MyVideoPlayer(BoxLayout):
    image_texture = ObjectProperty(None)
    image_capture = ObjectProperty(None)

    def __init__(self, **kwargs):
        super(MyVideoPlayer, self).__init__(**kwargs)
        self.flagPlay = False #La vidéo est-elle en cours de lecture?
        self.now_frame = 0 #Variable pour vérifier l'image de lecture de la vidéo pour la barre de recherche
        self.image_index = [] #Tableau pour stocker les images ouvertes pour la barre de recherche

    #Pop-up pour charger la vidéo
    def fileSelect(self):
        content = LoadDialog(load = self.load, cancel = self.dismiss_popup)
        self._popup = Popup( title="File Select", content=content, size_hint=(0.9,0.9))
        self._popup.open()

    #Chargement de fichiers vidéo
    def load (self, path, filename):
        txtFName = self.ids['txtFName']
        txtFName.text = filename[0]
        self.image_capture = cv2.VideoCapture(txtFName.text)
        self.sliderSetting()
        self.dismiss_popup()

    #Fermer la fenêtre contextuelle
    def dismiss_popup(self):
        self._popup.dismiss()

    #Paramètres de la barre de recherche
    def sliderSetting(self):
        count = self.image_capture.get(cv2.CAP_PROP_FRAME_COUNT)
        self.ids["timeSlider"].max = count

        #Chargez la vidéo une fois et enregistrez toutes les images dans un tableau
        while True:
            ret, frame = self.image_capture.read()
            if ret:
                self.image_index.append(frame)

            else:
                self.image_capture.set(cv2.CAP_PROP_POS_FRAMES, 0)
                break

    #Lecture vidéo
    def play(self):
        self.flagPlay = not self.flagPlay
        if self.flagPlay == True:
            self.image_capture.set(cv2.CAP_PROP_POS_FRAMES, self.now_frame)
            Clock.schedule_interval(self.update, 1.0 / self.image_capture.get(cv2.CAP_PROP_FPS))
        else:
            Clock.unschedule(self.update)

    #Traitement de l'horloge de lecture vidéo
    def update(self, dt):
        ret, frame = self.image_capture.read()
        #Quand l'image suivante peut être lue
        if ret:
            self.update_image(frame)
            time = self.image_capture.get(cv2.CAP_PROP_POS_FRAMES)
            self.ids["timeSlider"].value = time
            self.now_frame = int(time)

    #Barre de recherche
    def siderTouchMove(self):
        Clock.schedule_interval(self.sliderUpdate, 0)

    #Processus de dessin d'écran lorsque la barre de recherche est déplacée
    def sliderUpdate(self, dt):
        #Lorsque la valeur de la barre de recherche et la valeur de l'image de lecture sont différentes
        if self.now_frame != int(self.ids["timeSlider"].value):
            frame = self.image_index[self.now_frame-1]
            self.update_image(frame)
            self.now_frame = int(self.ids["timeSlider"].value)

    def update_image(self, frame):
        ##############################
        #Écrivez la source de traitement d'image ici! !!
        ##############################
        
        #retourner à l'envers
        buf = cv2.flip(frame, 0)
        image_texture = Texture.create(size=(frame.shape[1], frame.shape[0]), colorfmt='bgr')
        image_texture.blit_buffer(buf.tostring(), colorfmt='bgr', bufferfmt='ubyte')
        video = self.ids['video']
        video.texture = image_texture

class TestVideo(App):

    def build(self):
        return MyVideoPlayer()

TestVideo().run()

fichier kv

VideoApp.kv


<MyVideoPlayer>:
    orientation: 'vertical'
    padding: 0
    spacing: 1

    BoxLayout:
        orientation: 'horizontal'
        padding: 0
        spacing: 1
        size_hint: (1.0, 0.1)

        TextInput:
            id: txtFName
            text: ''
            multiline: False

        Button:
            text: 'file load'
            on_press: root.fileSelect()

    BoxLayout:
        orientation: 'horizontal'
        padding: 0
        spacing: 1

        Image:
            id: video

    BoxLayout:
        orientation: 'horizontal'
        padding: 0
        spacing: 1
        size_hint: (1.0, 0.1)
        Slider:
            id: timeSlider
            value: 0.0
            max: 0.0
            min: 0.0
            step: 1
            on_touch_move: root.siderTouchMove()

    BoxLayout:
        orientation: 'horizontal'
        padding: 0
        spacing: 1
        size_hint: (1.0, 0.1)

        ToggleButton:
            size_hint: (0.2, 1)
            text: 'Play'
            on_press: root.play()

        Label:
            size_hint: (0.2, 1)
            text: str(timeSlider.value) + "/" + str(timeSlider.max)

<LoadDialog>:
    BoxLayout:
        size: root.size
        pos: root.pos
        orientation: 'vertical'
        FileChooserListView:
            id: filechooser
            path: "./"

        BoxLayout:
            size_hint_y : None
            height : 30
            Button:
                text: 'Cancel'
                on_release: root.cancel()

            Button:
                text: 'Load'
                on_release: root.load(filechooser.path, filechooser.selection)

Lorsque vous l'exécutez, vous pouvez le lire comme ceci, ou vous pouvez déplacer la barre de recherche. La vidéo a été empruntée au site ici. Player.gif

Un petit commentaire

La vidéo elle-même est lue à la même dose que [Video Cupture] d'opencv (http://opencv.jp/opencv-2svn/cpp/reading_and_writing_images_and_video.html). Je suis sûr que personne ne l'a utilisé, mais c'est comme lire et afficher une vidéo image par image avec une déclaration while (comme ça / python-opencv-vidéocapture-file-camera /)). Encore une fois, si vous utilisez pendant ou pendant, il gèlera, utilisez donc Clock.

    #Traitement de l'horloge de lecture vidéo
    def update(self, dt):
        ret, frame = self.image_capture.read()
        #Quand l'image suivante peut être lue
        if ret:
            self.update_image(frame) #Traitement pour copier à l'écran
            time = self.image_capture.get(cv2.CAP_PROP_POS_FRAMES) #Obtenez le nombre d'images pour la barre de recherche
            self.ids["timeSlider"].value = time #Remplacez le nombre d'images de lecture par la valeur de la barre de recherche
            self.now_frame = int(time) #Pour lors du déplacement de la barre de recherche

Aussi, lors de l'affichage d'une image sur kivy, utilisez une classe appelée Texture avec blit_buffer```. Gérez l'image comme un tampon. À ce stade, les données d'image d'opencv seront à l'envers, alors ajoutez un processus pour les inverser. En faisant cela, vous pouvez implémenter la fonction de lecture vidéo de la même manière que la lecture vidéo openCV normale. Le traitement d'image n'est pas effectué cette fois, mais si vous ajoutez ici un traitement tel que opencv, vous pouvez facilement visualiser les résultats du traitement.

    def update_image(self, frame):
        ##############################
        #Écrivez la source de traitement d'image ici! !!
        ##############################
        
        #retourner à l'envers
        buf = cv2.flip(frame, 0)
        image_texture = Texture.create(size=(frame.shape[1], frame.shape[0]), colorfmt='bgr')
        image_texture.blit_buffer(buf.tostring(), colorfmt='bgr', bufferfmt='ubyte')
        video = self.ids['video']
        video.texture = image_texture

Dans la barre de recherche vidéo, initialement à partir de la fonction set (fonction qui spécifie l'image de lecture) de la classe VideoCupter d'opencv , J'essayais d'implémenter une barre de recherche en appliquant la valeur Slider au numéro d'image de la vidéo. Cependant, lorsque je l'ai mis en œuvre, l'opération est devenue extrêmement lourde. Parce que la fonction set semble être un processus très lourd, j'ai donc cherché une autre méthode d'implémentation.

En conséquence, il était possible de l'implémenter facilement en stockant les données d'image d'opencv telles qu'elles sont dans le tableau et en associant la valeur de Slider à l'indice du tableau d'images. Par conséquent, lors de la lecture d'une vidéo, un processus est fourni pour lire la vidéo une fois et l'attribuer au tableau.

Dans le programme, puisque la VideoCupture qui a été lue une fois est réutilisée, vous devez spécifier self.image_capture.set (cv2.CAP_PROP_POS_FRAMES, 0) '' pour ramener la position de lecture de la vidéo à l'état initial. , Vous ne pourrez pas lire la vidéo.

    #Paramètres de la barre de recherche
    def sliderSetting(self):
        count = self.image_capture.get(cv2.CAP_PROP_FRAME_COUNT) #Obtenez le nombre d'images dans une vidéo
        self.ids["timeSlider"].max = count #Remplacez le nombre d'images vidéo par la valeur maximale du curseur

        #Chargez la vidéo une fois et enregistrez toutes les images dans un tableau
        while True:
            ret, frame = self.image_capture.read()
            if ret:
                self.image_index.append(frame)

            else:
                #Après avoir lu la dernière image, revenez à la première image
                self.image_capture.set(cv2.CAP_PROP_POS_FRAMES, 0)
                break

Les références

Merci pour toute l'aide que vous m'avez apportée.

Comment afficher des images avec Opencv ou Pillow avec Python Kivy Manipulation de la texture, etc.

Premiers pas avec Python 7ème: Sélectionnez un fichier et lisez une vidéo - Mémo technique d'Akirachin Merci beaucoup pour la vidéo.

Recommended Posts

Programmation GUI avec kivy ~ Partie 3 Vidéo et barre de recherche ~
Programmation GUI à l'aide de kivy ~ Partie 4 Divers boutons ~
Programmation GUI utilisant kivy ~ Partie 2 Barre de progression ~
Programmation GUI à l'aide de kivy ~ Partie 5 Création de boutons avec des images ~
Programmation GUI avec kivy ~ Partie 6 Diverses dispositions ~
Programmation avec Python et Tkinter
[GUI en Python] PyQt5-Glisser-déposer-
Modulation et démodulation FM avec Python Partie 3
Exécuter la traduction Google et la traduction DeepL avec l'interface graphique
Apprenez les mathématiques et l'anglais grâce à la programmation (partie 1)
Apprenez les mathématiques et l'anglais grâce à la programmation (partie 2)
Création de l'interface graphique avec Pyside Partie 2 <Utilisation de la classe>