View  Edit  Attributes  History  Attach  Print  Search

PYQTLAB

PyQt Lab' : Capture Webcam avec GSVideo : capture simple avec GSVideo (résultat : 30 fps et délai 30 ms = idem OpenCV)

Par X. HINAULT - Juin 2013

Ce que l'on va faire ici

  • Dans ce code PyQt, capture Webcam avec GSVideo : capture simple avec GSVideo (résultat : 30 fps et délai 30 ms = idem OpenCV)

Pré-requis

  • python 2.7
  • pyqt4.x
  • modules :
    • python-opencv
    • pygst
    • gst

Téléchargement :

  • Ces codes sont disponibles ici :

Le fichier d'interface *.py

  • Fichier obtenu automatiquement avec l'utilitaire pyuic4 à partir du fichier *.ui créé avec QtDesigner :
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file
#
# Created: Wed Jun 12 10:09:29 2013
#      by: PyQt4 UI code generator 4.9.1
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    _fromUtf8 = lambda s: s

class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName(_fromUtf8("Form"))
        Form.resize(341, 265)
        self.labelImage = QtGui.QLabel(Form)
        self.labelImage.setGeometry(QtCore.QRect(10, 15, 320, 240))
        self.labelImage.setStyleSheet(_fromUtf8("background-color: rgb(180, 180, 180);"))
        self.labelImage.setText(_fromUtf8(""))
        self.labelImage.setObjectName(_fromUtf8("labelImage"))

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQt : dessin : qpixmap et qimage : numpy", None, QtGui.QApplication.UnicodeUTF8))


if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    Form = QtGui.QWidget()
    ui = Ui_Form()
    ui.setupUi(Form)
    Form.show()
    sys.exit(app.exec_())


 

Le fichier d'application *Main.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

# par X. HINAULT - Mai 2013 - Tous droits réservés
# GPLv3 - www.mon-club-elec.fr

# modules a importer
from PyQt4.QtGui import *
from PyQt4.QtCore import *  # inclut QTimer..
import os,sys

import time # temps - pour micros

import pygst # gsvideo
pygst.require('0.10')
import gst

import numpy as np

from tuto_pyqt_dessin_qpixmap_qimage_numpy_rgb_gsvideo import * # fichier obtenu à partir QtDesigner et pyuic4

# +/- variables et objets globaux

class myApp(QWidget, Ui_Form): # la classe reçoit le Qwidget principal ET la classe définie dans test.py obtenu avec pyuic4
        def __init__(self, parent=None):
                QWidget.__init__(self) # initialise le Qwidget principal
                self.setupUi(parent) # Obligatoire

                # --- Variables de classe

                # --- Paramétrage des widgets de l'interface GUI si nécessaire ---

                # --- Connexions entre signaux des widgets et fonctions
                # connecte chaque signal utilisé des objets à l'appel de la fonction voulue

                # --- Code actif initial  ---

                """
                # GS Video :
                self.player = gst.Pipeline("player") # # déclaration du pipeline

                #-- source
                self.source = gst.element_factory_make("v4l2src", "vsource")
                self.source.set_property("device", "/dev/video0")

                #self.scaler = gst.element_factory_make("videoscale", "vscale")

                #-- sortie
                #self.sink = gst.element_factory_make("autovideosink", "outsink")
                self.sink = gst.element_factory_make("appsink", "outsink")
                self.sink.set_property("emit-signals", "True")

                # initialisation du pipeline
                #self.player.add(self.source, self.scaler, self.sink)
                self.player.add(self.source, self.sink) # ajout des éléments aux pipeline

                #gst.element_link_many(self.source,self.scaler, self.sink)
                gst.element_link_many(self.source, self.sink) # construction du pipeline
                """


                # initialisation pipeline style "ligne de commande"
                self.pipeline = gst.parse_launch(
                '''v4l2src device=/dev/video0 ! video/x-raw-rgb,width=320,height=240,framerate=30/1 ! appsink name=sink emit-signals=true'''
                )

                #!xvimagesink''')
                #! appsink name=sink sync= False ''')

                #! appsink name=sink sync=False ''' % (self.uri, caps))

                # définition objets du pipeline pour accès code Python (si besoin)
                self.sink = self.pipeline.get_by_name('sink')

                # lancement du pipeline
                self.pipeline.set_state(gst.STATE_PLAYING)

                # affichage de test
                buf = self.sink.emit('pull-buffer')
                self.pixels = np.frombuffer(buf.data, dtype=np.uint8).reshape((320,240,3))
                print type(self.pixels)

                print self.pixels
                print self.pixels.shape

                # dessin : créer image à partir tableau numpy

                # initialisation du tableau de pixels
                #self.pixels=np.zeros((self.labelImage.height(),self.labelImage.width(),4),np.uint8,'C') # tableau 320x240 x 4 canaux et 8U
                # 4 canaux car image QImage est une une image ARGB... même si format RGB utilisé (qui est en fait ARGB avec A laissé à FF)

                # initialisation des canaux
                #self.pixels[:,:,1]=255 # canal G
                #self.pixels[:,:,0]=127 # canal B
                #self.pixels[:,:,2]=64 # canal R

                # accès à un pixel unitaire...
                #self.pixels[119,159,:]=[0,0,0,0] # accès à un pixel unitaire par pixels[y,x,:] (inversion x,y) et paramétrage de la couleur par (b,g,r,0)

                # accès à une ligne entière de pixels
                #self.pixels[50,:,:]=[0,0,255,0] # accès à une ligne par pixels[y,:,:] (inversion x,y) et paramétrage de la couleur par (b,g,r,0)

                # accès à une colonne entière de pixels
                #self.pixels[:,50,:]=[255,0,0,0] # accès à une colonne par pixels[:,x,:] (inversion x,y) et paramétrage de la couleur par (b,g,r,0)

                #QImage(self,str data, int width, int height, Format format)
                #self.image=QImage(self.pixels.tostring(), self.labelImage.width() , self.labelImage.height() ,QImage.Format_RGB32) # crée image RGB 32 bits même taille que label
                self.image=QImage(self.pixels.tostring(), self.labelImage.width() , self.labelImage.height() ,QImage.Format_RGB888) # crée image RGB 32 bits même taille que label

                #-- affichage du QImage via QPixmap dans QLabel
                self.pixmap=QPixmap.fromImage(self.image) # chargement qu QImage dans le QPixmap
                self.labelImage.setPixmap(self.pixmap) # met à jour le qpixmap affiché dans le qlabel

                #self.connect(self.sink,SIGNAL("pull-buffer"), self.bufferReady) # ne semble pas fonctionner

                #-- initialisation du Timer pour lecture Buffer GStreamer
                self.timerGStreamer=QTimer() # déclare un timer Qt
                self.timerGStreamer.start(5) # lance le timer - durée en ms - délai doit être idem ou inf ... framerate camera sinon, décalage..
                self.connect(self.timerGStreamer, SIGNAL("timeout()"), self.readBuffer) # connecte le signal timeOut de l'objet timer à l'appel de la fonction voulue


        # --- les fonctions appelées, utilisées par les signaux des widgets ---                

        # --- les fonctions appelées, utilisées par les signaux hors widgets ---
        # fonction micros : renvoie le nombre de microsecondes courant de l'horloge système
        def micros(self):
                return(int(round(time.time() * 1000000))) # microsecondes de l'horloge système


        def bufferReady(self):
                print ("Buffer ok")

        def readBuffer(self):

                micros0=self.micros()
                #print (str(self.micros()-micros0) + " us")    

                # affichage de test
                buf = self.sink.emit('pull-buffer')
                print ("signal pull-buffer"+str(self.micros()-micros0) + " us")

                micros0=self.micros()

                self.pixels = np.frombuffer(buf.data, dtype=np.uint8)

                print ("lecture data :" + str(self.micros()-micros0) + " us")  

                self.pixels=self.pixels.reshape((320,240,3))

                #print (str(self.micros()-micros0) + " us")    

                micros0=self.micros()

                self.pixelsOK=np.zeros((320, 240,3), np.uint8)
                self.pixelsOK[:,:,0]=self.pixels[:,:,2] # inversion R et B
                self.pixelsOK[:,:,1]=self.pixels[:,:,1] # G
                self.pixelsOK[:,:,2]=self.pixels[:,:,0] # inversion R et B
                # => 2,5ms

                print ("conversion data :"  +str(self.micros()-micros0) + " us")       

                #print type(self.pixels)
                #print self.pixels
                #print self.pixels.shape

                micros0=self.micros()


                self.image=QImage(self.pixelsOK.tostring(), self.labelImage.width() , self.labelImage.height() ,QImage.Format_RGB888) # crée image RGB 32 bits même taille que label

                #-- affichage du QImage via QPixmap dans QLabel
                #self.pixmap=QPixmap.fromImage(self.image) # chargement qu QImage dans le QPixmap  - ne marche pas..
                self.pixmap.convertFromImage(self.image) # recharge le QImage dans le QPixmap existant - met à jour le QPixmap
                self.labelImage.setPixmap(self.pixmap) # met à jour le qpixmap affiché dans le qlabel

                print ("affichage : " + str(self.micros()-micros0) + " us")    

        # --- fonctions de classes autres---    

# -- Autres Classes utiles --

# -- Classe principale (lancement)  --
def main(args):
        a=QApplication(args) # crée l'objet application
        f=QWidget() # crée le QWidget racine
        c=myApp(f) # appelle la classe contenant le code de l'application
        f.show() # affiche la fenêtre QWidget
        r=a.exec_() # lance l'exécution de l'application
        return r

if __name__=="__main__": # pour rendre le code exécutable
        main(sys.argv) # appelle la fonction main

 

Utilisation

  • Les 2 fichiers suivants sont à enregistrer dans un même répertoire, l'un en nom.py et l'autre en nomMain.py.
  • Puis lancer l'application depuis Geany ou équivalent, en exécutant le fichier nomMain.py
  • On obtient la fenêtre avec capture vidéo