View  Edit  Attributes  History  Attach  Print  Search

PYQTLAB

PyQt Lab' : PyQt + OpenCV : Capture : Dessin : Dessiner un rectangle sous le curseur de la souris.

Par X. HINAULT - Juin 2013

Ce que l'on va faire ici

  • Dans ce code PyQt, capture d'une image webcam et dessin sur l'image vidéo d'un rectangle sous la souris en temps réel.

Pré-requis

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

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: Sun Jun 16 07:40:43 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(331, 250)
        self.labelImage = QtGui.QLabel(Form)
        self.labelImage.setGeometry(QtCore.QRect(5, 5, 320, 240))
        self.labelImage.setFrameShape(QtGui.QFrame.Box)
        self.labelImage.setFrameShadow(QtGui.QFrame.Sunken)
        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+Opencv+webcam : Capture image", 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 numpy as np

#from cv2 import * # importe module OpenCV qui contient sous-module cv
import cv2.cv as cv # importe sous-module cv du module OpenCV
#import cv2 # importe le module OpenCV


from  tuto_pyqt_opencv_webcam_capture_image_mouse_rect 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
                self.xMouse=None
                self.yMouse=None

                #---- paramètres graphiques généraux ----
                self.framerate=60 # nombre d'image par secondes (rafraîchissement de l'affichage)              
                self.widthCapture=320 # largeur image capture en pixels
                self.heightCapture=240 # hauteur image capture en pixels
                # la résolution utilisée doit être supportée par la webcam - 320x240 est un bon compromis -    

                # --- 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  ---

                #======== config capture openCV ==========
                #-- initialisation de la caméra
                self.indexCam=1 # index de la webcam à utiliser - voir ls /dev/video* - utiliser -1 si pas d'indice
                self.webcam=cv.CaptureFromCAM(self.indexCam) # déclare l'objet capture sans désigner la caméra - remplace CreateCameraCapture           print (self.capture) # debug
                cv.SetCaptureProperty(self.webcam,cv.CV_CAP_PROP_FRAME_WIDTH,self.widthCapture) # fixe largeur de capture
                cv.SetCaptureProperty(self.webcam,cv.CV_CAP_PROP_FRAME_HEIGHT,self.heightCapture) # fixe hauteur de capture
                #cv.SetCaptureProperty(self.webcam,cv.CV_CAP_PROP_FPS,125) # fixe le framerate hardware- pas toujours supporté par webcam

                # création d'un QImage RGB (permettant l'accès aux pixels) pour affichage OpenCV dans Pyqt
                self.imageCV=QImage(self.labelImage.size(),QImage.Format_RGB32) # crée image RGB 32 bits même taille que label

                # Dessin avec QPixmap (affichage) et QImage (I/O, accès pixels)

                # création d'un QImage RGB permettant l'accès aux pixels
                self.image=QImage(self.labelImage.size(),QImage.Format_RGB32) # crée image RGB 32 bits même taille que label

                #-- initialisation du QImage
                self.image.fill(QColor(255,255,255)) # fond blanc

                # coordonnées centre du QPixmap (même taille que label)
                #xo=self.image.width()/2
                #yo=self.image.height()/2

                #trace le point
                #self.image.setPixel(xo,yo,qRgb(0,0,0)) # modifie la couleur du pixel x,y - noter qRgb renvoie valeur RGB 0xFFRRGGBB

                #--- dessin sur QImage --
                self.painterImage=QPainter(self.image) # associe QPainter (classe de dessin) au Qimage

                # paramètres détaillés crayon dessin
                #self.painterImage.setPen(QPen(Qt.SolidLine)) # fixe la couleur du crayon pour le dessin - moins propre...
                #self.pen=QPen(Qt.SolidLine) # crayon par défaut pour le painter - ligne continue
                #self.pen.setWidth(3) # fixe largeur crayon
                #self.pen.setColor(QColor(0,0,255)) # fixe couleur crayon              
                #self.painterImage.setPen(self.pen) # affecte le crayon defini au dessin - les paramètres s'appliquent seulement une fois setPen() appelé

                # tracé de formes
                #self.painterImage.setPen(QPen(QColor(0,0,255),2)) # fixe la couleur du crayon et la largeur pour le dessin - forme compactée  
                #self.painterImage.setBrush(QBrush(QColor(255,255,255),Qt.SolidPattern)) # fixe la couleur et le style du remplissage (brosse)  
                #self.painterImage.drawRect(10,10,50,50) # dessin rectangle : drawRect (self, int x, int y, int w, int h)
                #self.painterImage.drawPoint(xo,yo) # trace un point drawPoint (self, int x, int y)
                #self.painterImage.fillRect(150,150,30,30,QColor(255,255,0)) # fillRect (self, int x, int y, int w, int h, QColor b)
                #self.painterImage.drawEllipse(xo,yo,50,50) # dessin cercle - x-y = coin sup gauche rect encadrant : drawEllipse (self, int x, int y, int w, int h)
                #self.painterImage.drawEllipse(QPointF(xo,yo),50,50) # dessin cercle avec x,y centre et rayon : drawEllipse (self, QPointF center, float rx, float ry)
                #self.painterImage.drawLine(0,0,xo*2,yo) # trace une ligne : drawLine (self, int x1, int y1, int x2, int y2)
                #self.painterImage.drawText(QPointF(xo/2, yo/2), "Hello") # drawText (self, QPointF p, QString s)

                # il existe d'autres possibilités de dessin (polygone, chemin, etc..) voir : http://pyqt.sourceforge.net/Docs/PyQt4/qpainter.html

                self.painterImage.end() # ferme le painter - n'est plus accessible après

                # -- fin dessin sur QImage

                #-- Initialisation du QPixmap
                self.pixmap=QPixmap.fromImage(self.image) # initialise  le QPixmap...

                self.updatePixmap(self.image) # affichage initial du QPixmap

                #----- initialisation capture évènement souris du Qlabel
                self.labelImage.setMouseTracking(True) # active le suivi position souris
                self.labelImage.installEventFilter(self) # défini un objet dans cette classe pour "filtrer" les évènements en provenance de l'objet label - méthode classe Object

        # -- Activation d'un Timer pour affichage image OpenCV- fixe le fps d'affichage en fait --
                self.timerRefresh=QTimer() # déclare un timer Qt
                self.connect(self.timerRefresh, SIGNAL("timeout()"), self.timerRefreshEvent) # connecte le signal timeOut de l'objet timer à l'appel de la fonction voulue
                self.timerRefresh.start(int(1000/self.framerate)) # lance le timer - mettre délai assez court pour fps élevé

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

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

        #----- fonction de classe : affichage d'une nouvelle image webcam ---
        def timerRefreshEvent(self): # fonction appelée lors de la survenue d'un évènement Timer = rafraîchissement image - nom fonction indiférrent

                #self.timerRefresh.stop() # +/- stoppe Timer pendant traitement

                print("Capture Image webcam")
                #print cv.GetCaptureProperty(self.webcam,cv.CV_CAP_PROP_FPS) # affiche propriété

                micros0=self.micros()

                self.iplImgSrc=cv.QueryFrame(self.webcam) # récupère un IplImage en provenance de la webcam - Grab + retrieve
                #self.iplImgSrc=self.webcam.read() # récupère un IplImage en provenance de la webcam - Grab + retrieve - forme cv2
                #print self.iplImgSrc

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

                # affichage de l'image webcam
                self.imageCV = IplQImage(self.iplImgSrc) # récupère le IplImage dans un QImage
                # durée = 1,5ms

                self.updatePixmap(self.imageCV) # met à jour le pixmap en utilisant imageCV

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

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

        # --- fin timerRefreshEvent

        # fonction pour gérer filtrage évènements
        #def eventFilter(self, _, event): # fonction pour gérer filtrage évènements
        def eventFilter(self, srcEvent, event): # fonction pour gérer filtrage évènements - reçoit l'objet source de l'évènement
                print ("event ")
                #print srcEvent

                if srcEvent==self.labelImage: # teste si la source de l'évènement est bien le label - utile si plusieurs sources d'évènements activées
                        if event.type() == QEvent.MouseMove: # si évènement "mouvement de la souris"
                                self.xMouse=event.pos().x() # coordonnée souris au moment évènement dans le widget source de l'évènement =
                                self.yMouse=event.pos().y()
                                print ("MouseMove : x="+ str(self.xMouse) + " y= " + str(self.yMouse)) # affiche coordonnées

                                self.updatePixmap(self.imageCV) # met à jour le pixmap en utilisant imageCV

                return False # obligatoire...

        # --- fonctions de classes autres---    
        # 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

        # fonction de MAJ du QPixmap : chargement QImage +/- dessin + affichage dans QLabel
        def updatePixmap(self, imageIn):

                # chargement du QImage dans le QPixmap
                self.pixmap.convertFromImage(imageIn) # recharge le QImage dans le QPixmap existant - met à jour le QPixmap

                # +/- dessin sur le Pixmap - ne modifie pas le QImage chargé
                self.painterPixmap=QPainter(self.pixmap) # associe QPainter (classe de dessin) au QPixmap

                if self.xMouse and self.yMouse: # si les coordonnées existent
                        # tracé rectangle sur le Pixmap = laise QImage inchangé ++
                        self.painterPixmap.setPen(QPen(QColor(255,0,0),2)) # fixe la couleur du crayon et la largeur pour le dessin - forme compactée
                        cote=20 # coté du carré
                        self.painterPixmap.drawRect(self.xMouse-(cote/2),self.yMouse-(cote/2),cote,cote) # dessin rectangle

                # il existe d'autres possibilités de dessin (polygone, chemin, etc..) voir : http://pyqt.sourceforge.net/Docs/PyQt4/qpainter.html

                self.painterPixmap.end() # ferme le painter - n'est plus accessible après

                #-- affichage du QPixmap dans QLabel
                self.labelImage.setPixmap(self.pixmap) # met à jour le qpixmap affiché dans le qlabel  

# -- Autres Classes utiles --

# --- classe de conversion d'une iplImage en QImage
class IplQImage(QImage): # fonction qui étend la classe QImage
# IplQImage est une classe qui transforme un iplImage (OpenCV) en un QImage (Qt)
# source : http://matthewshotton.wordpress.com/2011/03/31/python-opencv-iplimage-to-pyqt-qimage/
# A class for converting iplimages to qimages

    def __init__(self,iplimage):
        # Rough-n-ready but it works dammit
        alpha = cv.CreateMat(iplimage.height,iplimage.width, cv.CV_8UC1)
        cv.Rectangle(alpha, (0, 0), (iplimage.width,iplimage.height), cv.ScalarAll(255) ,-1)
        rgba = cv.CreateMat(iplimage.height, iplimage.width, cv.CV_8UC4)
        cv.Set(rgba, (1, 2, 3, 4))
        cv.MixChannels([iplimage, alpha],[rgba], [
        (0, 0), # rgba[0] -> bgr[2]
        (1, 1), # rgba[1] -> bgr[1]
        (2, 2), # rgba[2] -> bgr[0]
        (3, 3) # rgba[3] -> alpha[0]
        ])
        self.__imagedata = rgba.tostring()
        super(IplQImage,self).__init__(self.__imagedata, iplimage.width, iplimage.height, QImage.Format_RGB32)

# -- fin class IplQImage


# -- 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
  • Le rectangle s'affiche sur l'image lorsque l'on déplace la souris.